Prusa MINI Firmware overview
lwsapi.cpp File Reference
#include <cstring>
#include "dbg.h"
#include "lwip/def.h"
#include "lwsapi.h"
#include "connection.hpp"
#include "lwip/timeouts.h"

Macros

#define LIGHT_WSAPI_PORT   80
 
#define LIGHT_WSAPI_RETRIES   4
 
#define LIGHT_WSAPI_POLL_INTERVAL   4
 

Functions

static bool empty_message (const Message_t &msg)
 Check when message is empty - all data was processed yet. More...
 
static size_t lwsapi_write (Context *ctx, const uint8_t *data, size_t len)
 Write data to TCP output buffer. More...
 
static size_t lwsapi_write (Context *ctx, const char *data)
 
static err_t lwsapi_poll (void *arg, struct tcp_pcb *pcb)
 The poll function is called every 2nd second. More...
 
static err_t close_conn (struct tcp_pcb *pcb, Context *ctx=nullptr)
 Close connection and clean callbacks and other connection environment. More...
 
static void lwsapi_call (Context *ctx, const struct pbuf *input=nullptr)
 Process message from IResponse::generator, and call generator for next work. More...
 
static err_t lwsapi_recv (void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
 Process http request and call application. More...
 
static void lwsapi_err (void *arg, err_t err)
 tcp_err callback defined in LwIP More...
 
static err_t lwsapi_sent (void *arg, struct tcp_pcb *pcb, u16_t len)
 tcp_sent callback defined in LwIP More...
 
static err_t lwsapi_accept (void *arg, struct tcp_pcb *pcb, err_t err)
 tcp_accept callback defined in LwIP More...
 
err_t lwsapi_init (void)
 Init (start) the LwIP WSAPI server. More...
 
IHeaderdynamics_header_factory (const char *key, const char *value, size_t value_length)
 Return new DynamicsHeader. More...
 
IHeadernumber_header_factory (const char *key, const char *value, size_t value_length)
 Response new NumberHeader. More...
 

Variables

static const char * http_bad_request
 Full Bad Request response. More...
 
static const char * http_internal_server
 Full Internal Server Error response. More...
 

Macro Definition Documentation

◆ LIGHT_WSAPI_PORT

#define LIGHT_WSAPI_PORT   80

◆ LIGHT_WSAPI_RETRIES

#define LIGHT_WSAPI_RETRIES   4

◆ LIGHT_WSAPI_POLL_INTERVAL

#define LIGHT_WSAPI_POLL_INTERVAL   4

Function Documentation

◆ empty_message()

static bool empty_message ( const Message_t msg)
static

Check when message is empty - all data was processed yet.

39  {
40  return (msg.response == nullptr && msg.headers == nullptr && msg.length == 0);
41 }
Here is the caller graph for this function:

◆ lwsapi_write() [1/2]

static size_t lwsapi_write ( Context ctx,
const uint8_t data,
size_t  len 
)
static

Write data to TCP output buffer.

While there is place in output TCP buffer, lwsapi_write copy data from Context.buffer to TCP buffer. When TCP buffer is full, stop.

Parameters
ctxpointer to connection context
datapointer to array of data which must be copied to output tcp buffer
lenlength of data, which must be copied
Returns
size of data, which was be copied to output tcp buffer
182  {
183  if (ctx == nullptr) {
184  lwsapi_error("lwsapi_write: Bad input!\n");
185  return 0;
186  }
187 
188  size_t offset = 0;
189  size_t max_len;
190  size_t snd_len = len;
191  while (offset < len) {
192  /* We cannot send more data than space available in the send buffer. */
193  max_len = tcp_sndbuf(ctx->pcb);
194  if (max_len < snd_len) {
195  snd_len = max_len;
196  }
197 
198  if (max_len == 0) {
199  return offset;
200  }
201 
202  /* Additional limitation: e.g. don't enqueue more than 2*mss at once */
203  max_len = ((u16_t)(2 * tcp_mss(ctx->pcb)));
204  if (max_len < snd_len) {
205  snd_len = max_len;
206  }
207 
208  err_t err;
209  do {
210  /* Write packet to out-queue, but do not send it until tcp_output() is called. */
211  err = tcp_write(ctx->pcb, data + offset, snd_len, TCP_WRITE_FLAG_COPY);
212  if (err == ERR_MEM) {
213  if ((tcp_sndbuf(ctx->pcb) == 0) || (tcp_sndqueuelen(ctx->pcb) >= TCP_SND_QUEUELEN)) {
214  /* no need to try smaller sizes */
215  snd_len = 1;
216  } else {
217  snd_len /= 2;
218  }
219  }
220  } while ((err == ERR_MEM) && (snd_len > 1));
221  if (err == ERR_OK) {
222  offset += snd_len;
223  return offset;
224  } else {
225  lwsapi_dbg("[%p] lwsapi_write: tcp_write error: %d\n", ctx->pcb, err);
226  return offset;
227  }
228  }
229 
230  return 0; // len is zero
231 }
Here is the caller graph for this function:

◆ lwsapi_write() [2/2]

static size_t lwsapi_write ( Context ctx,
const char *  data 
)
static
44  {
45  return lwsapi_write(ctx, (const uint8_t *)(data), strlen(data));
46 }
Here is the call graph for this function:

◆ lwsapi_poll()

static err_t lwsapi_poll ( void arg,
struct tcp_pcb *  pcb 
)
static

The poll function is called every 2nd second.

If there has been no data sent (which resets the retries) in 8 seconds, close. If the last portion of a file has not been sent in 1 seconds, close. This could be increased, but we don't want to waste resources for bad connections. See http://www.nongnu.org/lwip/2_0_x/group__tcp__raw.html#gafba47015098ed7ce523dcf7bdf70f7e5

Parameters
argpointer to connection context (Context)
83  {
84  if (arg == nullptr) {
85  return close_conn(pcb);
86  }
87 
88  Context *ctx = static_cast<Context *>(arg);
89  ctx->retries++;
90  if (ctx->retries == LIGHT_WSAPI_RETRIES) {
91  lwsapi_dbg("\tmax retries, close\n");
92  return close_conn(pcb, ctx);
93  }
94 
95  return ERR_OK;
96 }
Here is the call graph for this function:
Here is the caller graph for this function:

◆ close_conn()

static err_t close_conn ( struct tcp_pcb *  pcb,
Context ctx = nullptr 
)
static

Close connection and clean callbacks and other connection environment.

51  {
52  if (ctx != nullptr) {
53  delete ctx;
54  }
55 
56  tcp_arg(pcb, nullptr);
57  tcp_recv(pcb, nullptr);
58  tcp_err(pcb, nullptr);
59  tcp_poll(pcb, nullptr, 0);
60  tcp_sent(pcb, nullptr);
61 
62  tcp_output(pcb); // flush all data before close
63 
64  if (tcp_close(pcb) != ERR_OK) { // by doc, only ERR_MEM could be happend
65  lwsapi_dbg("\tclose connetion fails, ABRT\n");
66  tcp_abort(pcb);
67  return ERR_ABRT;
68  }
69 
70  return ERR_OK;
71 }
Here is the caller graph for this function:

◆ lwsapi_call()

static void lwsapi_call ( Context ctx,
const struct pbuf input = nullptr 
)
static

Process message from IResponse::generator, and call generator for next work.

While all data from Message_t is not processed, call lwsapi_write to response data to http client. This function is called first time after application_fn return IResponse, and next time from lwsapi_sent callback.

When all data in Message_t is processed, generator is called. When Message_t.length is EOF, all data from generator is processed.

107  {
108  while (1) {
109  // internal buffer and message was sent, call the coroutine
110  if (ctx->buffer == nullptr && empty_message(ctx->message)) {
111  ctx->message = ctx->response->generator(input);
112  input = nullptr; // input must be processed only once
113  ctx->m_position = 0;
114  if (ctx->message.length == EOF) {
115  close_conn(ctx->pcb, ctx);
116  return;
117  }
118  }
119 
120  // try to send internal buffer if exists
121  if (ctx->buffer != nullptr) {
122  size_t size = strlen(ctx->buffer + ctx->m_position);
123  size_t send = lwsapi_write(ctx,
124  (const uint8_t *)ctx->buffer + ctx->m_position, size);
125  if (send == size) {
126  ctx->free_buffer();
127  ctx->m_position = 0;
128  } else if (send == 0) { // memory problem
129  close_conn(ctx->pcb, ctx);
130  return;
131  } else { // not send all data, tcp buffer is full
132  ctx->m_position += send;
133  tcp_output(ctx->pcb);
134  return;
135  }
136  }
137 
138  if (ctx->message.response != nullptr) {
139  if (ctx->prepare_response() != ERR_OK) {
140  close_conn(ctx->pcb, ctx);
141  }
142  continue;
143  }
144 
145  if (ctx->message.headers != nullptr) {
146  if (ctx->prepare_header() != ERR_OK) {
147  close_conn(ctx->pcb, ctx);
148  }
149  continue;
150  }
151 
152  if (ctx->message.length == 0) {
153  return; // call later
154  }
155 
156  size_t send = lwsapi_write(ctx, ctx->message.payload + ctx->m_position,
157  ctx->message.length);
158  if (send == 0 && ctx->message.length > 0) { // memory problem
159  close_conn(ctx->pcb, ctx);
160  return;
161  }
162  ctx->m_position += send;
163  ctx->message.length -= send;
164  if (ctx->message.length > 0) {
165  tcp_output(ctx->pcb); // flush the data => free the buffer
166  return;
167  }
168  }
169 }
Here is the call graph for this function:
Here is the caller graph for this function:

◆ lwsapi_recv()

static err_t lwsapi_recv ( void arg,
struct tcp_pcb *  pcb,
struct pbuf p,
err_t  err 
)
static

Process http request and call application.

This is callback defined in LwIP documentation http://www.nongnu.org/lwip/2_0_x/group__tcp__raw.html#ga8afd0b316a87a5eeff4726dc95006ed0

This function try to parse http request, fill Context attributes and call defined application_fn.

TODO: there is missing process big http request - that means with more (big) headers, which could be more than 1024 bytes and request payload typical for POST|PUT|PATCH requests.

Parameters
argis pointer to connection Context
249  {
250  if ((err != ERR_OK) || (p == nullptr) || (arg == nullptr)) {
251  /* error or closed by other side? */
252  if (p != nullptr) {
253  /* Inform TCP that we have taken the data. */
254  tcp_recved(pcb, p->tot_len);
255  pbuf_free(p);
256  }
257  return close_conn(pcb, static_cast<Context *>(arg));
258  }
259 
260  /* Inform TCP that we have taken the data. */
261  tcp_recved(pcb, p->tot_len);
262 
263  Context *ctx = static_cast<Context *>(arg);
264 
265  if (ctx->response.get() != nullptr) { // response exist yet
266  ctx->retries = 0;
267 
268  lwsapi_call(ctx, p); // call the generator with input
269  pbuf_free(p);
270  return ERR_OK;
271  }
272 
273  size_t eoh = 0; // offset from
274  if (ctx->state == Context::State::FIRST) {
275  ctx->state = Context::State::WAIT_FOR_EOH;
276  eoh = ctx->find_eoh(p->payload, p->len); // try pbuf first
277  if (eoh == 0) {
278  if (ctx->fill_request_buffer(p) == ERR_MEM) {
279  pbuf_free(p);
281  return close_conn(pcb, ctx);
282  }
283  eoh = ctx->find_eoh(); // use internal buffer
284  }
285  } else if (ctx->state == Context::State::WAIT_FOR_EOH) {
286  ctx->fill_request_buffer(p); // append data to buffer
287  eoh = ctx->find_eoh(); // use internal buffer
288  }
289 
290  if (eoh > 0) // only set when not found in last callback
291  {
292  // when internal buffer is exist, will be used than instead of pbuf
293  err_t rv = ctx->parse_request(p->payload, p->len);
294  if (rv == ERR_VAL) {
295  pbuf_free(p);
297  return close_conn(pcb, ctx);
298  }
299  if (rv == ERR_MEM) {
300  pbuf_free(p);
302  return close_conn(pcb, ctx);
303  }
304  ctx->state = Context::State::PAYLOAD;
305  }
306 
307  if (ctx->state != Context::State::PAYLOAD) // eoh not found yet
308  {
309  pbuf_free(p);
310  return ERR_OK; // stop this call and wait for another recv
311  }
312 
313  ctx->response = std::move(application(ctx->env));
314 
315  if (ctx->response.get() == nullptr) {
316  pbuf_free(p);
318  return close_conn(pcb, static_cast<Context *>(arg));
319  }
320 
321  struct pbuf *body;
322  if (eoh > 0) // eoh found in this call, need to create shadow pbuf
323  {
324  eoh = ctx->find_eoh(p->payload, p->len) + 1; // find EOH in pbuf
325  body = reinterpret_cast<struct pbuf *>(mem_malloc(sizeof(pbuf)));
326  //FIXME: create valid shadow buffer chain
327  body->payload = const_cast<void *> memshift(p->payload, eoh);
328  body->len = p->len - eoh;
329  body->tot_len = p->tot_len - eoh;
330  body->next = p->next;
331  } else {
332  body = p;
333  }
334 
335  lwsapi_call(ctx, body); // first try to call response generator
336  if (body != p) {
337  mem_free(body);
338  }
339  pbuf_free(p);
340  return ERR_OK;
341 }
Here is the call graph for this function:
Here is the caller graph for this function:

◆ lwsapi_err()

static void lwsapi_err ( void arg,
err_t  err 
)
static

tcp_err callback defined in LwIP

344  {
345  // lwsapi_error("lwsapi_err: (%d) %s\n", err, lwip_strerr(err));
346  switch (err) {
347  case ERR_RST:
348  lwsapi_dbg("lwsapi_err: connection reset by remote host");
349  break;
350  case ERR_ABRT:
351  lwsapi_dbg("lwsapi_err: connection aborted");
352  break;
353  default:
354  lwsapi_error("lwsapi_err: err number %d", err);
355  }
356 
357  if (arg != nullptr) {
358  Context *ctx = static_cast<Context *>(arg);
359  delete ctx;
360  }
361 }
Here is the caller graph for this function:

◆ lwsapi_sent()

static err_t lwsapi_sent ( void arg,
struct tcp_pcb *  pcb,
u16_t  len 
)
static

tcp_sent callback defined in LwIP

This callback is called after http client confirms the part of tcp response. So next data could be send by lwsapi_call function.

368  {
369  if (arg != nullptr) {
370  Context *ctx = static_cast<Context *>(arg);
371  ctx->retries = 0;
372 
373  lwsapi_call(ctx);
374  }
375 
376  return ERR_OK;
377 }
Here is the call graph for this function:
Here is the caller graph for this function:

◆ lwsapi_accept()

static err_t lwsapi_accept ( void arg,
struct tcp_pcb *  pcb,
err_t  err 
)
static

tcp_accept callback defined in LwIP

This function create connection Context, set it as new pcb argument, and set all callbacks for this new accepted connection. See http://www.nongnu.org/lwip/2_0_x/tcp_8h.html#a00517abce6856d6c82f0efebdafb734d

Parameters
pcbnew protocol control block of new created http connection
387  {
388  if ((err != ERR_OK) || (pcb == nullptr)) {
389  return ERR_VAL;
390  }
391 
392  tcp_setprio(pcb, TCP_PRIO_MIN);
393  Context *ctx = new Context(pcb);
394  if (ctx == nullptr) {
395  lwsapi_error("lwsapi_accept: Out of memory, RST\n");
396  return close_conn(pcb);
397  }
398 
399  /* Set up the various callback functions */
400  tcp_recv(pcb, lwsapi_recv);
401  tcp_err(pcb, lwsapi_err);
402  tcp_poll(pcb, lwsapi_poll, LIGHT_WSAPI_POLL_INTERVAL);
403  tcp_sent(pcb, lwsapi_sent);
404 
405  return ERR_OK;
406 }
Here is the call graph for this function:
Here is the caller graph for this function:

◆ lwsapi_init()

err_t lwsapi_init ( void  )

Init (start) the LwIP WSAPI server.

Start LwIP WSAPI http server, which means set TCP priority for new protocol control block, bind on TCP port and set callback for accepting new connection.

413  {
414  lwsapi_dbg("lwsapi: start\n");
415  struct tcp_pcb *pcb;
416  err_t err;
417 
418  pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
419  if (pcb == nullptr) {
420  return ERR_MEM;
421  }
422 
423  tcp_setprio(pcb, TCP_PRIO_MIN);
424  err = tcp_bind(pcb, IP_ANY_TYPE, LIGHT_WSAPI_PORT);
425  if (err != ERR_OK) {
426  lwsapi_error("lwsapi: tcp_bind failed: %d", err);
427  return err;
428  }
429 
430  pcb = tcp_listen(pcb);
431  if (pcb == nullptr) {
432  lwsapi_error("lwsapi: tcp_listen failed");
433  return ERR_CLSD;
434  }
435 
436  tcp_accept(pcb, lwsapi_accept);
437  return ERR_OK;
438 }
Here is the call graph for this function:
Here is the caller graph for this function:

◆ dynamics_header_factory()

IHeader* dynamics_header_factory ( const char *  key,
const char *  value,
size_t  value_length 
)

Return new DynamicsHeader.

441  {
442  return new DynamicsHeader(key, value, value_length);
443 }

◆ number_header_factory()

IHeader* number_header_factory ( const char *  key,
const char *  value,
size_t  value_length 
)

Response new NumberHeader.

446  {
447  return new NumberHeader(key, atoll(value));
448 }

Variable Documentation

◆ http_bad_request

const char* http_bad_request
static
Initial value:
= "HTTP/1.0 400 Bad Request\n"
"Server: LwIP WSAPI\n"
"Content-Type: text/plain\n"
"\n"
"Bed Request"

Full Bad Request response.

◆ http_internal_server

const char* http_internal_server
static
Initial value:
= "HTTP/1.0 500 Internal Server Error\n"
"Server: LwIP WSAPI\n"
"Content-Type: text/plain\n"
"\n"
"Internal Server Error"

Full Internal Server Error response.

NumberHeader
NumberHeader is for number headers like Content-Length.
Definition: lwsapi_app.hpp:88
pbuf::len
u16_t len
Definition: pbuf.h:159
Message_t::response
const char * response
Definition: lwsapi_app.hpp:212
Context::state
State state
Definition: connection.hpp:30
Context::message
Message_t message
Definition: connection.hpp:32
lwsapi_accept
static err_t lwsapi_accept(void *arg, struct tcp_pcb *pcb, err_t err)
tcp_accept callback defined in LwIP
Definition: lwsapi.cpp:387
ERR_ABRT
Definition: err.h:90
LIGHT_WSAPI_RETRIES
#define LIGHT_WSAPI_RETRIES
Definition: lwsapi.cpp:11
u16_t
uint16_t u16_t
Definition: arch.h:121
http_bad_request
static const char * http_bad_request
Full Bad Request response.
Definition: lwsapi.cpp:15
netif::input
netif_input_fn input
Definition: netif.h:244
pbuf::tot_len
u16_t tot_len
Definition: pbuf.h:156
IP_ANY_TYPE
#define IP_ANY_TYPE
Definition: ip_addr.h:400
TCP_SND_QUEUELEN
#define TCP_SND_QUEUELEN
Definition: opt.h:1212
data
uint8_t data[8]
Definition: masstorage.h:49
Context::fill_request_buffer
err_t fill_request_buffer(const struct pbuf *p)
Append pbuf to request_buffer.
Definition: connection.cpp:17
pbuf::next
struct pbuf * next
Definition: pbuf.h:144
pbuf_free
u8_t pbuf_free(struct pbuf *p)
Definition: pbuf.c:715
LIGHT_WSAPI_PORT
#define LIGHT_WSAPI_PORT
Definition: lwsapi.cpp:10
lwsapi_error
#define lwsapi_error
Definition: lwsapi_app.hpp:42
close_conn
static err_t close_conn(struct tcp_pcb *pcb, Context *ctx=nullptr)
Close connection and clean callbacks and other connection environment.
Definition: lwsapi.cpp:51
application
IResponse::unique_ptr_t application(Environment &env)
application_fn callback, which is called from LwIP WSAPI http server
Definition: connect.cpp:306
Context::prepare_response
err_t prepare_response()
Process message.response to internal buffer.
Definition: connection.cpp:152
Context
Internal connection structure which is used in LwIP tcp_ callbacks as arg.
Definition: connection.hpp:20
Context::prepare_header
err_t prepare_header()
Process message.headers to internal buffer.
Definition: connection.cpp:171
FIRST
Definition: connect.cpp:77
ERR_MEM
Definition: err.h:65
lwsapi_call
static void lwsapi_call(Context *ctx, const struct pbuf *input=nullptr)
Process message from IResponse::generator, and call generator for next work.
Definition: lwsapi.cpp:107
ERR_CLSD
Definition: err.h:94
Context::retries
uint8_t retries
Definition: connection.hpp:29
Context::find_eoh
size_t find_eoh(const void *data=nullptr, size_t length=0)
Try to find End Of Header (\r \r ) in buffer.
Definition: connection.cpp:39
lwsapi_sent
static err_t lwsapi_sent(void *arg, struct tcp_pcb *pcb, u16_t len)
tcp_sent callback defined in LwIP
Definition: lwsapi.cpp:368
lwsapi_dbg
#define lwsapi_dbg
Definition: lwsapi_app.hpp:41
LIGHT_WSAPI_POLL_INTERVAL
#define LIGHT_WSAPI_POLL_INTERVAL
Definition: lwsapi.cpp:12
Message_t::length
int length
Definition: lwsapi_app.hpp:215
Context::pcb
struct tcp_pcb * pcb
Definition: connection.hpp:28
Context::response
IResponse::unique_ptr_t response
Definition: connection.hpp:37
Context::free_buffer
void free_buffer()
Definition: connection.hpp:94
uint8_t
const uint8_t[]
Definition: 404_html.c:3
lwsapi_poll
static err_t lwsapi_poll(void *arg, struct tcp_pcb *pcb)
The poll function is called every 2nd second.
Definition: lwsapi.cpp:83
ERR_OK
Definition: err.h:63
err_t
s8_t err_t
Definition: err.h:57
ERR_RST
Definition: err.h:92
Message_t::payload
const uint8_t * payload
Definition: lwsapi_app.hpp:214
lwsapi_recv
static err_t lwsapi_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
Process http request and call application.
Definition: lwsapi.cpp:249
http_internal_server
static const char * http_internal_server
Full Internal Server Error response.
Definition: lwsapi.cpp:22
lwsapi_err
static void lwsapi_err(void *arg, err_t err)
tcp_err callback defined in LwIP
Definition: lwsapi.cpp:344
Context::m_position
size_t m_position
Definition: connection.hpp:34
Context::env
Environment env
Definition: connection.hpp:31
mem_malloc
void * mem_malloc(mem_size_t size)
Definition: mem.c:603
DynamicsHeader
DynamicsHeader store it's value to LwIP memory pool.
Definition: lwsapi_app.hpp:139
empty_message
static bool empty_message(const Message_t &msg)
Check when message is empty - all data was processed yet.
Definition: lwsapi.cpp:39
EOF
#define EOF
Definition: ff.h:286
mem_free
void mem_free(void *rmem)
Definition: mem.c:419
IPADDR_TYPE_ANY
Definition: ip_addr.h:60
ERR_VAL
Definition: err.h:75
pbuf
Definition: pbuf.h:142
lwsapi_write
static size_t lwsapi_write(Context *ctx, const uint8_t *data, size_t len)
Write data to TCP output buffer.
Definition: lwsapi.cpp:182
Context::parse_request
err_t parse_request(const void *data=nullptr, size_t length=0)
parste the request header (request line + request headers)
Definition: connection.cpp:110
memshift
#define memshift(ptr, size)
Definition: connection.hpp:10
pbuf::payload
void * payload
Definition: pbuf.h:147
Message_t::headers
const IHeader * headers
Definition: lwsapi_app.hpp:213
size
static png_bytep size_t size
Definition: pngwrite.c:2170
Context::buffer
char * buffer
Definition: connection.hpp:33