summaryrefslogtreecommitdiff
path: root/src/apps
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2024-10-16 08:10:14 -0600
committerTom Rini <[email protected]>2024-10-16 08:10:14 -0600
commitf3f86fd1fe0fb288356bff78f8a6fa2edf89e3fc (patch)
treef0a99ea87d92f63895a6d053e3185838ebecf2d0 /src/apps
Squashed 'lib/lwip/lwip/' content from commit 0a0452b2c39b
git-subtree-dir: lib/lwip/lwip git-subtree-split: 0a0452b2c39bdd91e252aef045c115f88f6ca773
Diffstat (limited to 'src/apps')
-rw-r--r--src/apps/altcp_tls/altcp_tls_mbedtls.c1367
-rw-r--r--src/apps/altcp_tls/altcp_tls_mbedtls_mem.c210
-rw-r--r--src/apps/altcp_tls/altcp_tls_mbedtls_mem.h72
-rw-r--r--src/apps/altcp_tls/altcp_tls_mbedtls_structs.h83
-rw-r--r--src/apps/http/altcp_proxyconnect.c584
-rw-r--r--src/apps/http/fs.c161
-rw-r--r--src/apps/http/fs/404.html21
-rw-r--r--src/apps/http/fs/img/sics.gifbin0 -> 724 bytes
-rw-r--r--src/apps/http/fs/index.html47
-rw-r--r--src/apps/http/fsdata.c336
-rw-r--r--src/apps/http/fsdata.h41
-rw-r--r--src/apps/http/http_client.c911
-rw-r--r--src/apps/http/httpd.c2770
-rw-r--r--src/apps/http/httpd_structs.h123
-rw-r--r--src/apps/http/makefsdata/makefsdata97
-rw-r--r--src/apps/http/makefsdata/makefsdata.c1307
-rw-r--r--src/apps/http/makefsdata/readme.txt23
-rw-r--r--src/apps/http/makefsdata/tinydir.h831
-rw-r--r--src/apps/lwiperf/lwiperf.c847
-rw-r--r--src/apps/mdns/mdns.c2855
-rw-r--r--src/apps/mdns/mdns_domain.c635
-rw-r--r--src/apps/mdns/mdns_out.c1163
-rw-r--r--src/apps/mqtt/mqtt.c1480
-rw-r--r--src/apps/netbiosns/netbiosns.c533
-rw-r--r--src/apps/smtp/smtp.c1554
-rw-r--r--src/apps/snmp/snmp_asn1.c704
-rw-r--r--src/apps/snmp/snmp_asn1.h113
-rw-r--r--src/apps/snmp/snmp_core.c1356
-rw-r--r--src/apps/snmp/snmp_core_priv.h91
-rw-r--r--src/apps/snmp/snmp_mib2.c116
-rw-r--r--src/apps/snmp/snmp_mib2_icmp.c182
-rw-r--r--src/apps/snmp/snmp_mib2_interfaces.c368
-rw-r--r--src/apps/snmp/snmp_mib2_ip.c731
-rw-r--r--src/apps/snmp/snmp_mib2_snmp.c227
-rw-r--r--src/apps/snmp/snmp_mib2_system.c376
-rw-r--r--src/apps/snmp/snmp_mib2_tcp.c607
-rw-r--r--src/apps/snmp/snmp_mib2_udp.c372
-rw-r--r--src/apps/snmp/snmp_msg.c1981
-rw-r--r--src/apps/snmp/snmp_msg.h185
-rw-r--r--src/apps/snmp/snmp_netconn.c122
-rw-r--r--src/apps/snmp/snmp_pbuf_stream.c156
-rw-r--r--src/apps/snmp/snmp_pbuf_stream.h72
-rw-r--r--src/apps/snmp/snmp_raw.c103
-rw-r--r--src/apps/snmp/snmp_scalar.c232
-rw-r--r--src/apps/snmp/snmp_snmpv2_framework.c90
-rw-r--r--src/apps/snmp/snmp_snmpv2_usm.c410
-rw-r--r--src/apps/snmp/snmp_table.c342
-rw-r--r--src/apps/snmp/snmp_threadsync.c231
-rw-r--r--src/apps/snmp/snmp_traps.c900
-rw-r--r--src/apps/snmp/snmpv3.c136
-rw-r--r--src/apps/snmp/snmpv3_mbedtls.c342
-rw-r--r--src/apps/snmp/snmpv3_priv.h69
-rw-r--r--src/apps/sntp/sntp.c948
-rw-r--r--src/apps/tftp/tftp.c548
54 files changed, 30161 insertions, 0 deletions
diff --git a/src/apps/altcp_tls/altcp_tls_mbedtls.c b/src/apps/altcp_tls/altcp_tls_mbedtls.c
new file mode 100644
index 00000000000..a8c2fc2ee2c
--- /dev/null
+++ b/src/apps/altcp_tls/altcp_tls_mbedtls.c
@@ -0,0 +1,1367 @@
+/**
+ * @file
+ * Application layered TCP/TLS connection API (to be used from TCPIP thread)
+ *
+ * This file provides a TLS layer using mbedTLS
+ *
+ * This version is currently compatible with the 2.x.x branch (current LTS).
+ */
+
+/*
+ * Copyright (c) 2017 Simon Goldschmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt <[email protected]>
+ *
+ * Watch out:
+ * - 'sent' is always called with len==0 to the upper layer. This is because keeping
+ * track of the ratio of application data and TLS overhead would be too much.
+ *
+ * Mandatory security-related configuration:
+ * - ensure to add at least one strong entropy source to your mbedtls port (implement
+ * mbedtls_platform_entropy_poll or mbedtls_hardware_poll providing strong entropy)
+ * - define ALTCP_MBEDTLS_ENTROPY_PTR and ALTCP_MBEDTLS_ENTROPY_LEN to something providing
+ * GOOD custom entropy
+ *
+ * Missing things / @todo:
+ * - some unhandled/untested things migh be caught by LWIP_ASSERTs...
+ */
+
+#include "lwip/opt.h"
+#include "lwip/sys.h"
+
+#if LWIP_ALTCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/altcp_tls_mbedtls_opts.h"
+
+#if LWIP_ALTCP_TLS && LWIP_ALTCP_TLS_MBEDTLS
+
+#include "lwip/altcp.h"
+#include "lwip/altcp_tls.h"
+#include "lwip/priv/altcp_priv.h"
+
+#include "altcp_tls_mbedtls_structs.h"
+#include "altcp_tls_mbedtls_mem.h"
+
+/* @todo: which includes are really needed? */
+#include "mbedtls/entropy.h"
+#include "mbedtls/ctr_drbg.h"
+#include "mbedtls/certs.h"
+#include "mbedtls/x509.h"
+#include "mbedtls/ssl.h"
+#include "mbedtls/net_sockets.h"
+#include "mbedtls/error.h"
+#include "mbedtls/debug.h"
+#include "mbedtls/platform.h"
+#include "mbedtls/memory_buffer_alloc.h"
+#include "mbedtls/ssl_cache.h"
+#include "mbedtls/ssl_ticket.h"
+
+#include "mbedtls/ssl_internal.h" /* to call mbedtls_flush_output after ERR_MEM */
+
+#include <string.h>
+
+#ifndef ALTCP_MBEDTLS_ENTROPY_PTR
+#define ALTCP_MBEDTLS_ENTROPY_PTR NULL
+#endif
+#ifndef ALTCP_MBEDTLS_ENTROPY_LEN
+#define ALTCP_MBEDTLS_ENTROPY_LEN 0
+#endif
+#ifndef ALTCP_MBEDTLS_RNG_FN
+#define ALTCP_MBEDTLS_RNG_FN mbedtls_entropy_func
+#endif
+
+/* Variable prototype, the actual declaration is at the end of this file
+ since it contains pointers to static functions declared here */
+extern const struct altcp_functions altcp_mbedtls_functions;
+
+/** Our global mbedTLS configuration (server-specific, not connection-specific) */
+struct altcp_tls_config {
+ mbedtls_ssl_config conf;
+ mbedtls_x509_crt *cert;
+ mbedtls_pk_context *pkey;
+ u8_t cert_count;
+ u8_t cert_max;
+ u8_t pkey_count;
+ u8_t pkey_max;
+ mbedtls_x509_crt *ca;
+#if defined(MBEDTLS_SSL_CACHE_C) && ALTCP_MBEDTLS_USE_SESSION_CACHE
+ /** Inter-connection cache for fast connection startup */
+ struct mbedtls_ssl_cache_context cache;
+#endif
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && ALTCP_MBEDTLS_USE_SESSION_TICKETS
+ mbedtls_ssl_ticket_context ticket_ctx;
+#endif
+};
+
+/** Entropy and random generator are shared by all mbedTLS configuration */
+struct altcp_tls_entropy_rng {
+ mbedtls_entropy_context entropy;
+ mbedtls_ctr_drbg_context ctr_drbg;
+ int ref;
+};
+static struct altcp_tls_entropy_rng *altcp_tls_entropy_rng;
+
+static err_t altcp_mbedtls_lower_recv(void *arg, struct altcp_pcb *inner_conn, struct pbuf *p, err_t err);
+static err_t altcp_mbedtls_setup(void *conf, struct altcp_pcb *conn, struct altcp_pcb *inner_conn);
+static err_t altcp_mbedtls_lower_recv_process(struct altcp_pcb *conn, altcp_mbedtls_state_t *state);
+static err_t altcp_mbedtls_handle_rx_appldata(struct altcp_pcb *conn, altcp_mbedtls_state_t *state);
+static int altcp_mbedtls_bio_send(void *ctx, const unsigned char *dataptr, size_t size);
+
+
+/* callback functions from inner/lower connection: */
+
+/** Accept callback from lower connection (i.e. TCP)
+ * Allocate one of our structures, assign it to the new connection's 'state' and
+ * call the new connection's 'accepted' callback. If that succeeds, we wait
+ * to receive connection setup handshake bytes from the client.
+ */
+static err_t
+altcp_mbedtls_lower_accept(void *arg, struct altcp_pcb *accepted_conn, err_t err)
+{
+ struct altcp_pcb *listen_conn = (struct altcp_pcb *)arg;
+ if (listen_conn && listen_conn->state && listen_conn->accept) {
+ err_t setup_err;
+ altcp_mbedtls_state_t *listen_state = (altcp_mbedtls_state_t *)listen_conn->state;
+ /* create a new altcp_conn to pass to the next 'accept' callback */
+ struct altcp_pcb *new_conn = altcp_alloc();
+ if (new_conn == NULL) {
+ return ERR_MEM;
+ }
+ setup_err = altcp_mbedtls_setup(listen_state->conf, new_conn, accepted_conn);
+ if (setup_err != ERR_OK) {
+ altcp_free(new_conn);
+ return setup_err;
+ }
+ return listen_conn->accept(listen_conn->arg, new_conn, err);
+ }
+ return ERR_ARG;
+}
+
+/** Connected callback from lower connection (i.e. TCP).
+ * Not really implemented/tested yet...
+ */
+static err_t
+altcp_mbedtls_lower_connected(void *arg, struct altcp_pcb *inner_conn, err_t err)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */
+ if (conn && conn->state) {
+ altcp_mbedtls_state_t *state;
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ /* upper connected is called when handshake is done */
+ if (err != ERR_OK) {
+ if (conn->connected) {
+ return conn->connected(conn->arg, conn, err);
+ }
+ }
+ state = (altcp_mbedtls_state_t *)conn->state;
+ /* ensure overhead value is valid before first write */
+ state->overhead_bytes_adjust = 0;
+ return altcp_mbedtls_lower_recv_process(conn, state);
+ }
+ return ERR_VAL;
+}
+
+/* Call recved for possibly more than an u16_t */
+static void
+altcp_mbedtls_lower_recved(struct altcp_pcb *inner_conn, int recvd_cnt)
+{
+ while (recvd_cnt > 0) {
+ u16_t recvd_part = (u16_t)LWIP_MIN(recvd_cnt, 0xFFFF);
+ altcp_recved(inner_conn, recvd_part);
+ recvd_cnt -= recvd_part;
+ }
+}
+
+/** Recv callback from lower connection (i.e. TCP)
+ * This one mainly differs between connection setup/handshake (data is fed into mbedTLS only)
+ * and application phase (data is decoded by mbedTLS and passed on to the application).
+ */
+static err_t
+altcp_mbedtls_lower_recv(void *arg, struct altcp_pcb *inner_conn, struct pbuf *p, err_t err)
+{
+ altcp_mbedtls_state_t *state;
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+
+ LWIP_ASSERT("no err expected", err == ERR_OK);
+ LWIP_UNUSED_ARG(err);
+
+ if (!conn) {
+ /* no connection given as arg? should not happen, but prevent pbuf/conn leaks */
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+ altcp_close(inner_conn);
+ return ERR_CLSD;
+ }
+ state = (altcp_mbedtls_state_t *)conn->state;
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ if (!state) {
+ /* already closed */
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+ altcp_close(inner_conn);
+ return ERR_CLSD;
+ }
+
+ /* handle NULL pbuf (inner connection closed) */
+ if (p == NULL) {
+ /* remote host sent FIN, remember this (SSL state is destroyed
+ when both sides are closed only!) */
+ if ((state->flags & (ALTCP_MBEDTLS_FLAGS_HANDSHAKE_DONE | ALTCP_MBEDTLS_FLAGS_UPPER_CALLED)) ==
+ (ALTCP_MBEDTLS_FLAGS_HANDSHAKE_DONE | ALTCP_MBEDTLS_FLAGS_UPPER_CALLED)) {
+ /* need to notify upper layer (e.g. 'accept' called or 'connect' succeeded) */
+ if ((state->rx != NULL) || (state->rx_app != NULL)) {
+ state->flags |= ALTCP_MBEDTLS_FLAGS_RX_CLOSE_QUEUED;
+ /* this is a normal close (FIN) but we have unprocessed data, so delay the FIN */
+ altcp_mbedtls_handle_rx_appldata(conn, state);
+ return ERR_OK;
+ }
+ state->flags |= ALTCP_MBEDTLS_FLAGS_RX_CLOSED;
+ if (conn->recv) {
+ return conn->recv(conn->arg, conn, NULL, ERR_OK);
+ }
+ } else {
+ /* before connection setup is done: call 'err' */
+ if (conn->err) {
+ conn->err(conn->arg, ERR_ABRT);
+ }
+ altcp_close(conn);
+ }
+ return ERR_OK;
+ }
+
+ /* If we come here, the connection is in good state (handshake phase or application data phase).
+ Queue up the pbuf for processing as handshake data or application data. */
+ if (state->rx == NULL) {
+ state->rx = p;
+ } else {
+ LWIP_ASSERT("rx pbuf overflow", (int)p->tot_len + (int)p->len <= 0xFFFF);
+ pbuf_cat(state->rx, p);
+ }
+ return altcp_mbedtls_lower_recv_process(conn, state);
+}
+
+static err_t
+altcp_mbedtls_lower_recv_process(struct altcp_pcb *conn, altcp_mbedtls_state_t *state)
+{
+ if (!(state->flags & ALTCP_MBEDTLS_FLAGS_HANDSHAKE_DONE)) {
+ /* handle connection setup (handshake not done) */
+ int ret = mbedtls_ssl_handshake(&state->ssl_context);
+ /* try to send data... */
+ altcp_output(conn->inner_conn);
+ if (state->bio_bytes_read) {
+ /* acknowledge all bytes read */
+ altcp_mbedtls_lower_recved(conn->inner_conn, state->bio_bytes_read);
+ state->bio_bytes_read = 0;
+ }
+
+ if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+ /* handshake not done, wait for more recv calls */
+ LWIP_ASSERT("in this state, the rx chain should be empty", state->rx == NULL);
+ return ERR_OK;
+ }
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_ssl_handshake failed: %d\n", ret));
+ /* handshake failed, connection has to be closed */
+ if (conn->err) {
+ conn->err(conn->arg, ERR_CLSD);
+ }
+
+ if (altcp_close(conn) != ERR_OK) {
+ altcp_abort(conn);
+ }
+ return ERR_OK;
+ }
+ /* If we come here, handshake succeeded. */
+ LWIP_ASSERT("state", state->bio_bytes_read == 0);
+ LWIP_ASSERT("state", state->bio_bytes_appl == 0);
+ state->flags |= ALTCP_MBEDTLS_FLAGS_HANDSHAKE_DONE;
+ /* issue "connect" callback" to upper connection (this can only happen for active open) */
+ if (conn->connected) {
+ err_t err;
+ err = conn->connected(conn->arg, conn, ERR_OK);
+ if (err != ERR_OK) {
+ return err;
+ }
+ }
+ if (state->rx == NULL) {
+ return ERR_OK;
+ }
+ }
+ /* handle application data */
+ return altcp_mbedtls_handle_rx_appldata(conn, state);
+}
+
+/* Pass queued decoded rx data to application */
+static err_t
+altcp_mbedtls_pass_rx_data(struct altcp_pcb *conn, altcp_mbedtls_state_t *state)
+{
+ err_t err;
+ struct pbuf *buf;
+ LWIP_ASSERT("conn != NULL", conn != NULL);
+ LWIP_ASSERT("state != NULL", state != NULL);
+ buf = state->rx_app;
+ if (buf) {
+ state->rx_app = NULL;
+ if (conn->recv) {
+ u16_t tot_len = buf->tot_len;
+ /* this needs to be increased first because the 'recved' call may come nested */
+ state->rx_passed_unrecved += tot_len;
+ state->flags |= ALTCP_MBEDTLS_FLAGS_UPPER_CALLED;
+ err = conn->recv(conn->arg, conn, buf, ERR_OK);
+ if (err != ERR_OK) {
+ if (err == ERR_ABRT) {
+ return ERR_ABRT;
+ }
+ /* not received, leave the pbuf(s) queued (and decrease 'unrecved' again) */
+ LWIP_ASSERT("state == conn->state", state == conn->state);
+ state->rx_app = buf;
+ state->rx_passed_unrecved -= tot_len;
+ LWIP_ASSERT("state->rx_passed_unrecved >= 0", state->rx_passed_unrecved >= 0);
+ if (state->rx_passed_unrecved < 0) {
+ state->rx_passed_unrecved = 0;
+ }
+ return err;
+ }
+ } else {
+ pbuf_free(buf);
+ }
+ } else if ((state->flags & (ALTCP_MBEDTLS_FLAGS_RX_CLOSE_QUEUED | ALTCP_MBEDTLS_FLAGS_RX_CLOSED)) ==
+ ALTCP_MBEDTLS_FLAGS_RX_CLOSE_QUEUED) {
+ state->flags |= ALTCP_MBEDTLS_FLAGS_RX_CLOSED;
+ if (conn->recv) {
+ return conn->recv(conn->arg, conn, NULL, ERR_OK);
+ }
+ }
+
+ /* application may have close the connection */
+ if (conn->state != state) {
+ /* return error code to ensure altcp_mbedtls_handle_rx_appldata() exits the loop */
+ return ERR_ARG;
+ }
+ return ERR_OK;
+}
+
+/* Helper function that processes rx application data stored in rx pbuf chain */
+static err_t
+altcp_mbedtls_handle_rx_appldata(struct altcp_pcb *conn, altcp_mbedtls_state_t *state)
+{
+ int ret;
+ LWIP_ASSERT("state != NULL", state != NULL);
+ if (!(state->flags & ALTCP_MBEDTLS_FLAGS_HANDSHAKE_DONE)) {
+ /* handshake not done yet */
+ return ERR_VAL;
+ }
+ do {
+ /* allocate a full-sized unchained PBUF_POOL: this is for RX! */
+ struct pbuf *buf = pbuf_alloc(PBUF_RAW, PBUF_POOL_BUFSIZE, PBUF_POOL);
+ if (buf == NULL) {
+ /* We're short on pbufs, try again later from 'poll' or 'recv' callbacks.
+ @todo: close on excessive allocation failures or leave this up to upper conn? */
+ return ERR_OK;
+ }
+
+ /* decrypt application data, this pulls encrypted RX data off state->rx pbuf chain */
+ ret = mbedtls_ssl_read(&state->ssl_context, (unsigned char *)buf->payload, PBUF_POOL_BUFSIZE);
+ if (ret < 0) {
+ if (ret == MBEDTLS_ERR_SSL_CLIENT_RECONNECT) {
+ /* client is initiating a new connection using the same source port -> close connection or make handshake */
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("new connection on same source port\n"));
+ LWIP_ASSERT("TODO: new connection on same source port, close this connection", 0);
+ } else if ((ret != MBEDTLS_ERR_SSL_WANT_READ) && (ret != MBEDTLS_ERR_SSL_WANT_WRITE)) {
+ if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("connection was closed gracefully\n"));
+ } else if (ret == MBEDTLS_ERR_NET_CONN_RESET) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("connection was reset by peer\n"));
+ }
+ pbuf_free(buf);
+ return ERR_OK;
+ } else {
+ pbuf_free(buf);
+ return ERR_OK;
+ }
+ pbuf_free(buf);
+ altcp_abort(conn);
+ return ERR_ABRT;
+ } else {
+ err_t err;
+ if (ret) {
+ LWIP_ASSERT("bogus receive length", ret <= PBUF_POOL_BUFSIZE);
+ /* trim pool pbuf to actually decoded length */
+ pbuf_realloc(buf, (u16_t)ret);
+
+ state->bio_bytes_appl += ret;
+ if (mbedtls_ssl_get_bytes_avail(&state->ssl_context) == 0) {
+ /* Record is done, now we know the share between application and protocol bytes
+ and can adjust the RX window by the protocol bytes.
+ The rest is 'recved' by the application calling our 'recved' fn. */
+ int overhead_bytes;
+ LWIP_ASSERT("bogus byte counts", state->bio_bytes_read > state->bio_bytes_appl);
+ overhead_bytes = state->bio_bytes_read - state->bio_bytes_appl;
+ altcp_mbedtls_lower_recved(conn->inner_conn, overhead_bytes);
+ state->bio_bytes_read = 0;
+ state->bio_bytes_appl = 0;
+ }
+
+ if (state->rx_app == NULL) {
+ state->rx_app = buf;
+ } else {
+ pbuf_cat(state->rx_app, buf);
+ }
+ } else {
+ pbuf_free(buf);
+ buf = NULL;
+ }
+ err = altcp_mbedtls_pass_rx_data(conn, state);
+ if (err != ERR_OK) {
+ if (err == ERR_ABRT) {
+ /* recv callback needs to return this as the pcb is deallocated */
+ return ERR_ABRT;
+ }
+ /* we hide all other errors as we retry feeding the pbuf to the app later */
+ return ERR_OK;
+ }
+ }
+ } while (ret > 0);
+ return ERR_OK;
+}
+
+/** Receive callback function called from mbedtls (set via mbedtls_ssl_set_bio)
+ * This function mainly copies data from pbufs and frees the pbufs after copying.
+ */
+static int
+altcp_mbedtls_bio_recv(void *ctx, unsigned char *buf, size_t len)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)ctx;
+ altcp_mbedtls_state_t *state;
+ struct pbuf *p;
+ u16_t ret;
+ u16_t copy_len;
+ err_t err;
+
+ LWIP_UNUSED_ARG(err); /* for LWIP_NOASSERT */
+ if ((conn == NULL) || (conn->state == NULL)) {
+ return MBEDTLS_ERR_NET_INVALID_CONTEXT;
+ }
+ state = (altcp_mbedtls_state_t *)conn->state;
+ LWIP_ASSERT("state != NULL", state != NULL);
+ p = state->rx;
+
+ /* @todo: return MBEDTLS_ERR_NET_CONN_RESET/MBEDTLS_ERR_NET_RECV_FAILED? */
+
+ if ((p == NULL) || ((p->len == 0) && (p->next == NULL))) {
+ if (p) {
+ pbuf_free(p);
+ }
+ state->rx = NULL;
+ if ((state->flags & (ALTCP_MBEDTLS_FLAGS_RX_CLOSE_QUEUED | ALTCP_MBEDTLS_FLAGS_RX_CLOSED)) ==
+ ALTCP_MBEDTLS_FLAGS_RX_CLOSE_QUEUED) {
+ /* close queued but not passed up yet */
+ return 0;
+ }
+ return MBEDTLS_ERR_SSL_WANT_READ;
+ }
+ /* limit number of bytes again to copy from first pbuf in a chain only */
+ copy_len = (u16_t)LWIP_MIN(len, p->len);
+ /* copy the data */
+ ret = pbuf_copy_partial(p, buf, copy_len, 0);
+ LWIP_ASSERT("ret == copy_len", ret == copy_len);
+ /* hide the copied bytes from the pbuf */
+ err = pbuf_remove_header(p, ret);
+ LWIP_ASSERT("error", err == ERR_OK);
+ if (p->len == 0) {
+ /* the first pbuf has been fully read, free it */
+ state->rx = p->next;
+ p->next = NULL;
+ pbuf_free(p);
+ }
+
+ state->bio_bytes_read += (int)ret;
+ return ret;
+}
+
+/** Sent callback from lower connection (i.e. TCP)
+ * This only informs the upper layer the number of ACKed bytes.
+ * This now take care of TLS added bytes so application receive
+ * correct ACKed bytes.
+ */
+static err_t
+altcp_mbedtls_lower_sent(void *arg, struct altcp_pcb *inner_conn, u16_t len)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */
+ if (conn) {
+ int overhead;
+ u16_t app_len;
+ altcp_mbedtls_state_t *state = (altcp_mbedtls_state_t *)conn->state;
+ LWIP_ASSERT("state", state != NULL);
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ /* calculate TLS overhead part to not send it to application */
+ overhead = state->overhead_bytes_adjust + state->ssl_context.out_left;
+ if ((unsigned)overhead > len) {
+ overhead = len;
+ }
+ /* remove ACKed bytes from overhead adjust counter */
+ state->overhead_bytes_adjust -= len;
+ /* try to send more if we failed before (may increase overhead adjust counter) */
+ mbedtls_ssl_flush_output(&state->ssl_context);
+ /* remove calculated overhead from ACKed bytes len */
+ app_len = len - (u16_t)overhead;
+ /* update application write counter and inform application */
+ if (app_len) {
+ state->overhead_bytes_adjust += app_len;
+ if (conn->sent)
+ return conn->sent(conn->arg, conn, app_len);
+ }
+ }
+ return ERR_OK;
+}
+
+/** Poll callback from lower connection (i.e. TCP)
+ * Just pass this on to the application.
+ * @todo: retry sending?
+ */
+static err_t
+altcp_mbedtls_lower_poll(void *arg, struct altcp_pcb *inner_conn)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */
+ if (conn) {
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ /* check if there's unreceived rx data */
+ if (conn->state) {
+ altcp_mbedtls_state_t *state = (altcp_mbedtls_state_t *)conn->state;
+ /* try to send more if we failed before */
+ mbedtls_ssl_flush_output(&state->ssl_context);
+ if (altcp_mbedtls_handle_rx_appldata(conn, state) == ERR_ABRT) {
+ return ERR_ABRT;
+ }
+ }
+ if (conn->poll) {
+ return conn->poll(conn->arg, conn);
+ }
+ }
+ return ERR_OK;
+}
+
+static void
+altcp_mbedtls_lower_err(void *arg, err_t err)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ if (conn) {
+ conn->inner_conn = NULL; /* already freed */
+ if (conn->err) {
+ conn->err(conn->arg, err);
+ }
+ altcp_free(conn);
+ }
+}
+
+/* setup functions */
+
+static void
+altcp_mbedtls_remove_callbacks(struct altcp_pcb *inner_conn)
+{
+ altcp_arg(inner_conn, NULL);
+ altcp_recv(inner_conn, NULL);
+ altcp_sent(inner_conn, NULL);
+ altcp_err(inner_conn, NULL);
+ altcp_poll(inner_conn, NULL, inner_conn->pollinterval);
+}
+
+static void
+altcp_mbedtls_setup_callbacks(struct altcp_pcb *conn, struct altcp_pcb *inner_conn)
+{
+ altcp_arg(inner_conn, conn);
+ altcp_recv(inner_conn, altcp_mbedtls_lower_recv);
+ altcp_sent(inner_conn, altcp_mbedtls_lower_sent);
+ altcp_err(inner_conn, altcp_mbedtls_lower_err);
+ /* tcp_poll is set when interval is set by application */
+ /* listen is set totally different :-) */
+}
+
+static err_t
+altcp_mbedtls_setup(void *conf, struct altcp_pcb *conn, struct altcp_pcb *inner_conn)
+{
+ int ret;
+ struct altcp_tls_config *config = (struct altcp_tls_config *)conf;
+ altcp_mbedtls_state_t *state;
+ if (!conf) {
+ return ERR_ARG;
+ }
+ LWIP_ASSERT("invalid inner_conn", conn != inner_conn);
+
+ /* allocate mbedtls context */
+ state = altcp_mbedtls_alloc(conf);
+ if (state == NULL) {
+ return ERR_MEM;
+ }
+ /* initialize mbedtls context: */
+ mbedtls_ssl_init(&state->ssl_context);
+ ret = mbedtls_ssl_setup(&state->ssl_context, &config->conf);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_ssl_setup failed\n"));
+ /* @todo: convert 'ret' to err_t */
+ altcp_mbedtls_free(conf, state);
+ return ERR_MEM;
+ }
+ /* tell mbedtls about our I/O functions */
+ mbedtls_ssl_set_bio(&state->ssl_context, conn, altcp_mbedtls_bio_send, altcp_mbedtls_bio_recv, NULL);
+
+ altcp_mbedtls_setup_callbacks(conn, inner_conn);
+ conn->inner_conn = inner_conn;
+ conn->fns = &altcp_mbedtls_functions;
+ conn->state = state;
+ return ERR_OK;
+}
+
+struct altcp_pcb *
+altcp_tls_wrap(struct altcp_tls_config *config, struct altcp_pcb *inner_pcb)
+{
+ struct altcp_pcb *ret;
+ if (inner_pcb == NULL) {
+ return NULL;
+ }
+ ret = altcp_alloc();
+ if (ret != NULL) {
+ if (altcp_mbedtls_setup(config, ret, inner_pcb) != ERR_OK) {
+ altcp_free(ret);
+ return NULL;
+ }
+ }
+ return ret;
+}
+
+void
+altcp_tls_init_session(struct altcp_tls_session *session)
+{
+ if (session)
+ mbedtls_ssl_session_init(&session->data);
+}
+
+err_t
+altcp_tls_get_session(struct altcp_pcb *conn, struct altcp_tls_session *session)
+{
+ if (session && conn && conn->state) {
+ altcp_mbedtls_state_t *state = (altcp_mbedtls_state_t *)conn->state;
+ int ret = mbedtls_ssl_get_session(&state->ssl_context, &session->data);
+ return ret < 0 ? ERR_VAL : ERR_OK;
+ }
+ return ERR_ARG;
+}
+
+err_t
+altcp_tls_set_session(struct altcp_pcb *conn, struct altcp_tls_session *session)
+{
+ if (session && conn && conn->state) {
+ altcp_mbedtls_state_t *state = (altcp_mbedtls_state_t *)conn->state;
+ int ret = -1;
+ if (session->data.start)
+ ret = mbedtls_ssl_set_session(&state->ssl_context, &session->data);
+ return ret < 0 ? ERR_VAL : ERR_OK;
+ }
+ return ERR_ARG;
+}
+
+void
+altcp_tls_free_session(struct altcp_tls_session *session)
+{
+ if (session)
+ mbedtls_ssl_session_free(&session->data);
+}
+
+void *
+altcp_tls_context(struct altcp_pcb *conn)
+{
+ if (conn && conn->state) {
+ altcp_mbedtls_state_t *state = (altcp_mbedtls_state_t *)conn->state;
+ return &state->ssl_context;
+ }
+ return NULL;
+}
+
+#if ALTCP_MBEDTLS_LIB_DEBUG != LWIP_DBG_OFF
+static void
+altcp_mbedtls_debug(void *ctx, int level, const char *file, int line, const char *str)
+{
+ LWIP_UNUSED_ARG(ctx);
+ LWIP_UNUSED_ARG(file);
+ LWIP_UNUSED_ARG(line);
+ LWIP_UNUSED_ARG(str);
+
+ if (level >= ALTCP_MBEDTLS_LIB_DEBUG_LEVEL_MIN) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_LIB_DEBUG, ("%s:%04d: %s\n", file, line, str));
+ }
+}
+#endif
+
+static err_t
+altcp_mbedtls_ref_entropy(void)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+
+ if (!altcp_tls_entropy_rng) {
+ altcp_tls_entropy_rng = (struct altcp_tls_entropy_rng *)altcp_mbedtls_alloc_config(sizeof(struct altcp_tls_entropy_rng));
+ if (altcp_tls_entropy_rng) {
+ int ret;
+ altcp_tls_entropy_rng->ref = 1;
+ mbedtls_entropy_init(&altcp_tls_entropy_rng->entropy);
+ mbedtls_ctr_drbg_init(&altcp_tls_entropy_rng->ctr_drbg);
+ /* Seed the RNG, only once */
+ ret = mbedtls_ctr_drbg_seed(&altcp_tls_entropy_rng->ctr_drbg,
+ ALTCP_MBEDTLS_RNG_FN, &altcp_tls_entropy_rng->entropy,
+ ALTCP_MBEDTLS_ENTROPY_PTR, ALTCP_MBEDTLS_ENTROPY_LEN);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_ctr_drbg_seed failed: %d\n", ret));
+ mbedtls_ctr_drbg_free(&altcp_tls_entropy_rng->ctr_drbg);
+ mbedtls_entropy_free(&altcp_tls_entropy_rng->entropy);
+ altcp_mbedtls_free_config(altcp_tls_entropy_rng);
+ altcp_tls_entropy_rng = NULL;
+ return ERR_ARG;
+ }
+ } else {
+ return ERR_MEM;
+ }
+ } else {
+ altcp_tls_entropy_rng->ref++;
+ }
+ return ERR_OK;
+}
+
+static void
+altcp_mbedtls_unref_entropy(void)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+
+ if (altcp_tls_entropy_rng && altcp_tls_entropy_rng->ref) {
+ altcp_tls_entropy_rng->ref--;
+ }
+}
+
+/** Create new TLS configuration
+ * ATTENTION: Server certificate and private key have to be added outside this function!
+ */
+static struct altcp_tls_config *
+altcp_tls_create_config(int is_server, u8_t cert_count, u8_t pkey_count, int have_ca)
+{
+ size_t sz;
+ int ret;
+ struct altcp_tls_config *conf;
+ mbedtls_x509_crt *mem;
+
+ if (TCP_WND < MBEDTLS_SSL_MAX_CONTENT_LEN) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG|LWIP_DBG_LEVEL_SERIOUS,
+ ("altcp_tls: TCP_WND is smaller than the RX decrypion buffer, connection RX might stall!\n"));
+ }
+
+ altcp_mbedtls_mem_init();
+
+ sz = sizeof(struct altcp_tls_config);
+ if (cert_count > 0) {
+ sz += (cert_count * sizeof(mbedtls_x509_crt));
+ }
+ if (have_ca) {
+ sz += sizeof(mbedtls_x509_crt);
+ }
+ if (pkey_count > 0) {
+ sz += (pkey_count * sizeof(mbedtls_pk_context));
+ }
+
+ conf = (struct altcp_tls_config *)altcp_mbedtls_alloc_config(sz);
+ if (conf == NULL) {
+ return NULL;
+ }
+ conf->cert_max = cert_count;
+ mem = (mbedtls_x509_crt *)(conf + 1);
+ if (cert_count > 0) {
+ conf->cert = mem;
+ mem += cert_count;
+ }
+ if (have_ca) {
+ conf->ca = mem;
+ mem++;
+ }
+ conf->pkey_max = pkey_count;
+ if (pkey_count > 0) {
+ conf->pkey = (mbedtls_pk_context *)mem;
+ }
+
+ mbedtls_ssl_config_init(&conf->conf);
+
+ if (altcp_mbedtls_ref_entropy() != ERR_OK) {
+ altcp_mbedtls_free_config(conf);
+ return NULL;
+ }
+
+ /* Setup ssl context (@todo: what's different for a client here? -> might better be done on listen/connect) */
+ ret = mbedtls_ssl_config_defaults(&conf->conf, is_server ? MBEDTLS_SSL_IS_SERVER : MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_ssl_config_defaults failed: %d\n", ret));
+ altcp_mbedtls_unref_entropy();
+ altcp_mbedtls_free_config(conf);
+ return NULL;
+ }
+ mbedtls_ssl_conf_authmode(&conf->conf, ALTCP_MBEDTLS_AUTHMODE);
+
+ mbedtls_ssl_conf_rng(&conf->conf, mbedtls_ctr_drbg_random, &altcp_tls_entropy_rng->ctr_drbg);
+#if ALTCP_MBEDTLS_LIB_DEBUG != LWIP_DBG_OFF
+ mbedtls_ssl_conf_dbg(&conf->conf, altcp_mbedtls_debug, stdout);
+#endif
+#if defined(MBEDTLS_SSL_CACHE_C) && ALTCP_MBEDTLS_USE_SESSION_CACHE
+ mbedtls_ssl_conf_session_cache(&conf->conf, &conf->cache, mbedtls_ssl_cache_get, mbedtls_ssl_cache_set);
+ mbedtls_ssl_cache_set_timeout(&conf->cache, ALTCP_MBEDTLS_SESSION_CACHE_TIMEOUT_SECONDS);
+ mbedtls_ssl_cache_set_max_entries(&conf->cache, ALTCP_MBEDTLS_SESSION_CACHE_SIZE);
+#endif
+
+#if defined(MBEDTLS_SSL_SESSION_TICKETS) && ALTCP_MBEDTLS_USE_SESSION_TICKETS
+ mbedtls_ssl_ticket_init(&conf->ticket_ctx);
+
+ ret = mbedtls_ssl_ticket_setup(&conf->ticket_ctx, mbedtls_ctr_drbg_random, &altcp_tls_entropy_rng->ctr_drbg,
+ ALTCP_MBEDTLS_SESSION_TICKET_CIPHER, ALTCP_MBEDTLS_SESSION_TICKET_TIMEOUT_SECONDS);
+ if (ret) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_ssl_ticket_setup failed: %d\n", ret));
+ altcp_mbedtls_unref_entropy();
+ altcp_mbedtls_free_config(conf);
+ return NULL;
+ }
+
+ mbedtls_ssl_conf_session_tickets_cb(&conf->conf, mbedtls_ssl_ticket_write, mbedtls_ssl_ticket_parse,
+ &conf->ticket_ctx);
+#endif
+
+ return conf;
+}
+
+struct altcp_tls_config *altcp_tls_create_config_server(u8_t cert_count)
+{
+ struct altcp_tls_config *conf = altcp_tls_create_config(1, cert_count, cert_count, 0);
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ mbedtls_ssl_conf_ca_chain(&conf->conf, NULL, NULL);
+ return conf;
+}
+
+err_t altcp_tls_config_server_add_privkey_cert(struct altcp_tls_config *config,
+ const u8_t *privkey, size_t privkey_len,
+ const u8_t *privkey_pass, size_t privkey_pass_len,
+ const u8_t *cert, size_t cert_len)
+{
+ int ret;
+ mbedtls_x509_crt *srvcert;
+ mbedtls_pk_context *pkey;
+
+ if (config->cert_count >= config->cert_max) {
+ return ERR_MEM;
+ }
+ if (config->pkey_count >= config->pkey_max) {
+ return ERR_MEM;
+ }
+
+ srvcert = config->cert + config->cert_count;
+ mbedtls_x509_crt_init(srvcert);
+
+ pkey = config->pkey + config->pkey_count;
+ mbedtls_pk_init(pkey);
+
+ /* Load the certificates and private key */
+ ret = mbedtls_x509_crt_parse(srvcert, cert, cert_len);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_x509_crt_parse failed: %d\n", ret));
+ return ERR_VAL;
+ }
+
+ ret = mbedtls_pk_parse_key(pkey, (const unsigned char *) privkey, privkey_len, privkey_pass, privkey_pass_len);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_pk_parse_public_key failed: %d\n", ret));
+ mbedtls_x509_crt_free(srvcert);
+ return ERR_VAL;
+ }
+
+ ret = mbedtls_ssl_conf_own_cert(&config->conf, srvcert, pkey);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_ssl_conf_own_cert failed: %d\n", ret));
+ mbedtls_x509_crt_free(srvcert);
+ mbedtls_pk_free(pkey);
+ return ERR_VAL;
+ }
+
+ config->cert_count++;
+ config->pkey_count++;
+ return ERR_OK;
+}
+
+/** Create new TLS configuration
+ * This is a suboptimal version that gets the encrypted private key and its password,
+ * as well as the server certificate.
+ */
+struct altcp_tls_config *
+altcp_tls_create_config_server_privkey_cert(const u8_t *privkey, size_t privkey_len,
+ const u8_t *privkey_pass, size_t privkey_pass_len,
+ const u8_t *cert, size_t cert_len)
+{
+ struct altcp_tls_config *conf = altcp_tls_create_config_server(1);
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ if (altcp_tls_config_server_add_privkey_cert(conf, privkey, privkey_len,
+ privkey_pass, privkey_pass_len, cert, cert_len) != ERR_OK) {
+ altcp_tls_free_config(conf);
+ return NULL;
+ }
+
+ return conf;
+}
+
+static struct altcp_tls_config *
+altcp_tls_create_config_client_common(const u8_t *ca, size_t ca_len, int is_2wayauth)
+{
+ int ret;
+ struct altcp_tls_config *conf = altcp_tls_create_config(0, (is_2wayauth) ? 1 : 0, (is_2wayauth) ? 1 : 0, ca != NULL);
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /* Initialize the CA certificate if provided
+ * CA certificate is optional (to save memory) but recommended for production environment
+ * Without CA certificate, connection will be prone to man-in-the-middle attacks */
+ if (ca) {
+ mbedtls_x509_crt_init(conf->ca);
+ ret = mbedtls_x509_crt_parse(conf->ca, ca, ca_len);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_x509_crt_parse ca failed: %d 0x%x\n", ret, -1*ret));
+ altcp_tls_free_config(conf);
+ return NULL;
+ }
+
+ mbedtls_ssl_conf_ca_chain(&conf->conf, conf->ca, NULL);
+ }
+ return conf;
+}
+
+struct altcp_tls_config *
+altcp_tls_create_config_client(const u8_t *ca, size_t ca_len)
+{
+ return altcp_tls_create_config_client_common(ca, ca_len, 0);
+}
+
+struct altcp_tls_config *
+altcp_tls_create_config_client_2wayauth(const u8_t *ca, size_t ca_len, const u8_t *privkey, size_t privkey_len,
+ const u8_t *privkey_pass, size_t privkey_pass_len,
+ const u8_t *cert, size_t cert_len)
+{
+ int ret;
+ struct altcp_tls_config *conf;
+
+ if (!cert || !privkey) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("altcp_tls_create_config_client_2wayauth: certificate and priv key required\n"));
+ return NULL;
+ }
+
+ conf = altcp_tls_create_config_client_common(ca, ca_len, 1);
+ if (conf == NULL) {
+ return NULL;
+ }
+
+ /* Initialize the client certificate and corresponding private key */
+ mbedtls_x509_crt_init(conf->cert);
+ ret = mbedtls_x509_crt_parse(conf->cert, cert, cert_len);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_x509_crt_parse cert failed: %d 0x%x\n", ret, -1*ret));
+ altcp_tls_free_config(conf);
+ return NULL;
+ }
+
+ mbedtls_pk_init(conf->pkey);
+ ret = mbedtls_pk_parse_key(conf->pkey, privkey, privkey_len, privkey_pass, privkey_pass_len);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_pk_parse_key failed: %d 0x%x\n", ret, -1*ret));
+ altcp_tls_free_config(conf);
+ return NULL;
+ }
+
+ ret = mbedtls_ssl_conf_own_cert(&conf->conf, conf->cert, conf->pkey);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_ssl_conf_own_cert failed: %d 0x%x\n", ret, -1*ret));
+ altcp_tls_free_config(conf);
+ return NULL;
+ }
+
+ return conf;
+}
+
+int
+altcp_tls_configure_alpn_protocols(struct altcp_tls_config *conf, const char **protos)
+{
+#if defined(MBEDTLS_SSL_ALPN)
+ int ret = mbedtls_ssl_conf_alpn_protocols(&conf->conf, protos);
+ if (ret != 0) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("mbedtls_ssl_conf_alpn_protocols failed: %d\n", ret));
+ }
+
+ return ret;
+#else
+ return -1;
+#endif
+}
+
+void
+altcp_tls_free_config(struct altcp_tls_config *conf)
+{
+ if (conf->pkey) {
+ mbedtls_pk_free(conf->pkey);
+ }
+ if (conf->cert) {
+ mbedtls_x509_crt_free(conf->cert);
+ }
+ if (conf->ca) {
+ mbedtls_x509_crt_free(conf->ca);
+ }
+ mbedtls_ssl_config_free(&conf->conf);
+ altcp_mbedtls_free_config(conf);
+ altcp_mbedtls_unref_entropy();
+}
+
+void
+altcp_tls_free_entropy(void)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+
+ if (altcp_tls_entropy_rng && altcp_tls_entropy_rng->ref == 0) {
+ mbedtls_ctr_drbg_free(&altcp_tls_entropy_rng->ctr_drbg);
+ mbedtls_entropy_free(&altcp_tls_entropy_rng->entropy);
+ altcp_mbedtls_free_config(altcp_tls_entropy_rng);
+ altcp_tls_entropy_rng = NULL;
+ }
+}
+
+/* "virtual" functions */
+static void
+altcp_mbedtls_set_poll(struct altcp_pcb *conn, u8_t interval)
+{
+ if (conn != NULL) {
+ altcp_poll(conn->inner_conn, altcp_mbedtls_lower_poll, interval);
+ }
+}
+
+static void
+altcp_mbedtls_recved(struct altcp_pcb *conn, u16_t len)
+{
+ u16_t lower_recved;
+ altcp_mbedtls_state_t *state;
+ if (conn == NULL) {
+ return;
+ }
+ state = (altcp_mbedtls_state_t *)conn->state;
+ if (state == NULL) {
+ return;
+ }
+ if (!(state->flags & ALTCP_MBEDTLS_FLAGS_HANDSHAKE_DONE)) {
+ return;
+ }
+ lower_recved = len;
+ if (lower_recved > state->rx_passed_unrecved) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_DEBUG, ("bogus recved count (len > state->rx_passed_unrecved / %d / %d)\n",
+ len, state->rx_passed_unrecved));
+ lower_recved = (u16_t)state->rx_passed_unrecved;
+ }
+ state->rx_passed_unrecved -= lower_recved;
+
+ altcp_recved(conn->inner_conn, lower_recved);
+}
+
+static err_t
+altcp_mbedtls_connect(struct altcp_pcb *conn, const ip_addr_t *ipaddr, u16_t port, altcp_connected_fn connected)
+{
+ if (conn == NULL) {
+ return ERR_VAL;
+ }
+ conn->connected = connected;
+ return altcp_connect(conn->inner_conn, ipaddr, port, altcp_mbedtls_lower_connected);
+}
+
+static struct altcp_pcb *
+altcp_mbedtls_listen(struct altcp_pcb *conn, u8_t backlog, err_t *err)
+{
+ struct altcp_pcb *lpcb;
+ if (conn == NULL) {
+ return NULL;
+ }
+ lpcb = altcp_listen_with_backlog_and_err(conn->inner_conn, backlog, err);
+ if (lpcb != NULL) {
+ altcp_mbedtls_state_t *state = (altcp_mbedtls_state_t *)conn->state;
+ /* Free members of the ssl context (not used on listening pcb). This
+ includes freeing input/output buffers, so saves ~32KByte by default */
+ mbedtls_ssl_free(&state->ssl_context);
+
+ conn->inner_conn = lpcb;
+ altcp_accept(lpcb, altcp_mbedtls_lower_accept);
+ return conn;
+ }
+ return NULL;
+}
+
+static void
+altcp_mbedtls_abort(struct altcp_pcb *conn)
+{
+ if (conn != NULL) {
+ altcp_abort(conn->inner_conn);
+ }
+}
+
+static err_t
+altcp_mbedtls_close(struct altcp_pcb *conn)
+{
+ struct altcp_pcb *inner_conn;
+ if (conn == NULL) {
+ return ERR_VAL;
+ }
+ inner_conn = conn->inner_conn;
+ if (inner_conn) {
+ err_t err;
+ altcp_poll_fn oldpoll = inner_conn->poll;
+ altcp_mbedtls_remove_callbacks(conn->inner_conn);
+ err = altcp_close(conn->inner_conn);
+ if (err != ERR_OK) {
+ /* not closed, set up all callbacks again */
+ altcp_mbedtls_setup_callbacks(conn, inner_conn);
+ /* poll callback is not included in the above */
+ altcp_poll(inner_conn, oldpoll, inner_conn->pollinterval);
+ return err;
+ }
+ conn->inner_conn = NULL;
+ }
+ altcp_free(conn);
+ return ERR_OK;
+}
+
+/** Allow caller of altcp_write() to limit to negotiated chunk size
+ * or remaining sndbuf space of inner_conn.
+ */
+static u16_t
+altcp_mbedtls_sndbuf(struct altcp_pcb *conn)
+{
+ if (conn) {
+ altcp_mbedtls_state_t *state;
+ state = (altcp_mbedtls_state_t*)conn->state;
+ if (!state || !(state->flags & ALTCP_MBEDTLS_FLAGS_HANDSHAKE_DONE)) {
+ return 0;
+ }
+ if (conn->inner_conn) {
+ u16_t sndbuf = altcp_sndbuf(conn->inner_conn);
+ /* Take care of record header, IV, AuthTag */
+ int ssl_expan = mbedtls_ssl_get_record_expansion(&state->ssl_context);
+ if (ssl_expan > 0) {
+ size_t ssl_added = (u16_t)LWIP_MIN(ssl_expan, 0xFFFF);
+ /* internal sndbuf smaller than our offset */
+ if (ssl_added < sndbuf) {
+ size_t max_len = 0xFFFF;
+ size_t ret;
+#if defined(MBEDTLS_SSL_MAX_FRAGMENT_LENGTH)
+ /* @todo: adjust ssl_added to real value related to negotiated cipher */
+ size_t max_frag_len = mbedtls_ssl_get_max_frag_len(&state->ssl_context);
+ max_len = LWIP_MIN(max_frag_len, max_len);
+#endif
+ /* Adjust sndbuf of inner_conn with what added by SSL */
+ ret = LWIP_MIN(sndbuf - ssl_added, max_len);
+ LWIP_ASSERT("sndbuf overflow", ret <= 0xFFFF);
+ return (u16_t)ret;
+ }
+ }
+ }
+ }
+ /* fallback: use sendbuf of the inner connection */
+ return altcp_default_sndbuf(conn);
+}
+
+/** Write data to a TLS connection. Calls into mbedTLS, which in turn calls into
+ * @ref altcp_mbedtls_bio_send() to send the encrypted data
+ */
+static err_t
+altcp_mbedtls_write(struct altcp_pcb *conn, const void *dataptr, u16_t len, u8_t apiflags)
+{
+ int ret;
+ altcp_mbedtls_state_t *state;
+
+ LWIP_UNUSED_ARG(apiflags);
+
+ if (conn == NULL) {
+ return ERR_VAL;
+ }
+
+ state = (altcp_mbedtls_state_t *)conn->state;
+ if (state == NULL) {
+ /* @todo: which error? */
+ return ERR_ARG;
+ }
+ if (!(state->flags & ALTCP_MBEDTLS_FLAGS_HANDSHAKE_DONE)) {
+ /* @todo: which error? */
+ return ERR_VAL;
+ }
+
+ /* HACK: if there is something left to send, try to flush it and only
+ allow sending more if this succeeded (this is a hack because neither
+ returning 0 nor MBEDTLS_ERR_SSL_WANT_WRITE worked for me) */
+ if (state->ssl_context.out_left) {
+ mbedtls_ssl_flush_output(&state->ssl_context);
+ if (state->ssl_context.out_left) {
+ return ERR_MEM;
+ }
+ }
+ ret = mbedtls_ssl_write(&state->ssl_context, (const unsigned char *)dataptr, len);
+ /* try to send data... */
+ altcp_output(conn->inner_conn);
+ if (ret >= 0) {
+ if (ret == len) {
+ /* update application sent counter */
+ state->overhead_bytes_adjust -= ret;
+ return ERR_OK;
+ } else {
+ /* @todo/@fixme: assumption: either everything sent or error */
+ LWIP_ASSERT("ret <= 0", 0);
+ return ERR_MEM;
+ }
+ } else {
+ if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+ /* @todo: convert error to err_t */
+ return ERR_MEM;
+ }
+ LWIP_ASSERT("unhandled error", 0);
+ return ERR_VAL;
+ }
+}
+
+/** Send callback function called from mbedtls (set via mbedtls_ssl_set_bio)
+ * This function is either called during handshake or when sending application
+ * data via @ref altcp_mbedtls_write (or altcp_write)
+ */
+static int
+altcp_mbedtls_bio_send(void *ctx, const unsigned char *dataptr, size_t size)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *) ctx;
+ altcp_mbedtls_state_t *state;
+ int written = 0;
+ size_t size_left = size;
+ u8_t apiflags = TCP_WRITE_FLAG_COPY;
+
+ LWIP_ASSERT("conn != NULL", conn != NULL);
+ if ((conn == NULL) || (conn->inner_conn == NULL)) {
+ return MBEDTLS_ERR_NET_INVALID_CONTEXT;
+ }
+ state = (altcp_mbedtls_state_t *)conn->state;
+ LWIP_ASSERT("state != NULL", state != NULL);
+
+ while (size_left) {
+ u16_t write_len = (u16_t)LWIP_MIN(size_left, 0xFFFF);
+ err_t err = altcp_write(conn->inner_conn, (const void *)dataptr, write_len, apiflags);
+ if (err == ERR_OK) {
+ written += write_len;
+ size_left -= write_len;
+ state->overhead_bytes_adjust += write_len;
+ } else if (err == ERR_MEM) {
+ if (written) {
+ return written;
+ }
+ return 0; /* MBEDTLS_ERR_SSL_WANT_WRITE; */
+ } else {
+ LWIP_ASSERT("tls_write, tcp_write: err != ERR MEM", 0);
+ /* @todo: return MBEDTLS_ERR_NET_CONN_RESET or MBEDTLS_ERR_NET_SEND_FAILED */
+ return MBEDTLS_ERR_NET_SEND_FAILED;
+ }
+ }
+ return written;
+}
+
+static u16_t
+altcp_mbedtls_mss(struct altcp_pcb *conn)
+{
+ if (conn == NULL) {
+ return 0;
+ }
+ /* @todo: LWIP_MIN(mss, mbedtls_ssl_get_max_frag_len()) ? */
+ return altcp_mss(conn->inner_conn);
+}
+
+static void
+altcp_mbedtls_dealloc(struct altcp_pcb *conn)
+{
+ /* clean up and free tls state */
+ if (conn) {
+ altcp_mbedtls_state_t *state = (altcp_mbedtls_state_t *)conn->state;
+ if (state) {
+ mbedtls_ssl_free(&state->ssl_context);
+ state->flags = 0;
+ if (state->rx) {
+ /* free leftover (unhandled) rx pbufs */
+ pbuf_free(state->rx);
+ state->rx = NULL;
+ }
+ altcp_mbedtls_free(state->conf, state);
+ conn->state = NULL;
+ }
+ }
+}
+
+const struct altcp_functions altcp_mbedtls_functions = {
+ altcp_mbedtls_set_poll,
+ altcp_mbedtls_recved,
+ altcp_default_bind,
+ altcp_mbedtls_connect,
+ altcp_mbedtls_listen,
+ altcp_mbedtls_abort,
+ altcp_mbedtls_close,
+ altcp_default_shutdown,
+ altcp_mbedtls_write,
+ altcp_default_output,
+ altcp_mbedtls_mss,
+ altcp_mbedtls_sndbuf,
+ altcp_default_sndqueuelen,
+ altcp_default_nagle_disable,
+ altcp_default_nagle_enable,
+ altcp_default_nagle_disabled,
+ altcp_default_setprio,
+ altcp_mbedtls_dealloc,
+ altcp_default_get_tcp_addrinfo,
+ altcp_default_get_ip,
+ altcp_default_get_port
+#if LWIP_TCP_KEEPALIVE
+ , altcp_default_keepalive_disable
+ , altcp_default_keepalive_enable
+#endif
+#ifdef LWIP_DEBUG
+ , altcp_default_dbg_get_tcp_state
+#endif
+};
+
+#endif /* LWIP_ALTCP_TLS && LWIP_ALTCP_TLS_MBEDTLS */
+#endif /* LWIP_ALTCP */
diff --git a/src/apps/altcp_tls/altcp_tls_mbedtls_mem.c b/src/apps/altcp_tls/altcp_tls_mbedtls_mem.c
new file mode 100644
index 00000000000..d2c3d58d6bf
--- /dev/null
+++ b/src/apps/altcp_tls/altcp_tls_mbedtls_mem.c
@@ -0,0 +1,210 @@
+/**
+ * @file
+ * Application layered TCP connection API (to be used from TCPIP thread)
+ *
+ * This file contains memory management functions for a TLS layer using mbedTLS.
+ *
+ * ATTENTION: For production usage, you might want to override this file with
+ * your own implementation since this implementation simply uses the
+ * lwIP heap without caring for fragmentation or leaving heap for
+ * other parts of lwIP!
+ */
+
+/*
+ * Copyright (c) 2017 Simon Goldschmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt <[email protected]>
+ *
+ * Missing things / @todo:
+ * - RX data is acknowledged after receiving (tcp_recved is called when enqueueing
+ * the pbuf for mbedTLS receive, not when processed by mbedTLS or the inner
+ * connection; altcp_recved() from inner connection does nothing)
+ * - TX data is marked as 'sent' (i.e. acknowledged; sent callback is called) right
+ * after enqueueing for transmission, not when actually ACKed be the remote host.
+ */
+
+#include "lwip/opt.h"
+
+#if LWIP_ALTCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/altcp_tls_mbedtls_opts.h"
+
+#if LWIP_ALTCP_TLS && LWIP_ALTCP_TLS_MBEDTLS
+
+#include "altcp_tls_mbedtls_mem.h"
+#include "altcp_tls_mbedtls_structs.h"
+#include "lwip/mem.h"
+
+#include "mbedtls/platform.h"
+
+#include <string.h>
+
+#ifndef ALTCP_MBEDTLS_MEM_DEBUG
+#define ALTCP_MBEDTLS_MEM_DEBUG LWIP_DBG_OFF
+#endif
+
+#if defined(MBEDTLS_PLATFORM_MEMORY) && \
+ (!defined(MBEDTLS_PLATFORM_FREE_MACRO) || \
+ !defined(MBEDTLS_PLATFORM_CALLOC_MACRO))
+#define ALTCP_MBEDTLS_PLATFORM_ALLOC 1
+#else
+#define ALTCP_MBEDTLS_PLATFORM_ALLOC 0
+#endif
+
+#if ALTCP_MBEDTLS_PLATFORM_ALLOC
+
+#ifndef ALTCP_MBEDTLS_PLATFORM_ALLOC_STATS
+#define ALTCP_MBEDTLS_PLATFORM_ALLOC_STATS 0
+#endif
+
+/* This is an example/debug implementation of alloc/free functions only */
+typedef struct altcp_mbedtls_malloc_helper_s {
+ size_t c;
+ size_t len;
+} altcp_mbedtls_malloc_helper_t;
+
+#if ALTCP_MBEDTLS_PLATFORM_ALLOC_STATS
+typedef struct altcp_mbedtls_malloc_stats_s {
+ size_t allocedBytes;
+ size_t allocCnt;
+ size_t maxBytes;
+ size_t totalBytes;
+} altcp_mbedtls_malloc_stats_t;
+altcp_mbedtls_malloc_stats_t altcp_mbedtls_malloc_stats;
+volatile int altcp_mbedtls_malloc_clear_stats;
+#endif
+
+static void *
+tls_malloc(size_t c, size_t len)
+{
+ altcp_mbedtls_malloc_helper_t *hlpr;
+ void *ret;
+ size_t alloc_size;
+#if ALTCP_MBEDTLS_PLATFORM_ALLOC_STATS
+ if (altcp_mbedtls_malloc_clear_stats) {
+ altcp_mbedtls_malloc_clear_stats = 0;
+ memset(&altcp_mbedtls_malloc_stats, 0, sizeof(altcp_mbedtls_malloc_stats));
+ }
+#endif
+ alloc_size = sizeof(altcp_mbedtls_malloc_helper_t) + (c * len);
+ /* check for maximum allocation size, mainly to prevent mem_size_t overflow */
+ if (alloc_size > MEM_SIZE) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_MEM_DEBUG, ("mbedtls allocation too big: %c * %d bytes vs MEM_SIZE=%d\n",
+ (int)c, (int)len, (int)MEM_SIZE));
+ return NULL;
+ }
+ hlpr = (altcp_mbedtls_malloc_helper_t *)mem_malloc((mem_size_t)alloc_size);
+ if (hlpr == NULL) {
+ LWIP_DEBUGF(ALTCP_MBEDTLS_MEM_DEBUG, ("mbedtls alloc callback failed for %c * %d bytes\n", (int)c, (int)len));
+ return NULL;
+ }
+#if ALTCP_MBEDTLS_PLATFORM_ALLOC_STATS
+ altcp_mbedtls_malloc_stats.allocCnt++;
+ altcp_mbedtls_malloc_stats.allocedBytes += c * len;
+ if (altcp_mbedtls_malloc_stats.allocedBytes > altcp_mbedtls_malloc_stats.maxBytes) {
+ altcp_mbedtls_malloc_stats.maxBytes = altcp_mbedtls_malloc_stats.allocedBytes;
+ }
+ altcp_mbedtls_malloc_stats.totalBytes += c * len;
+#endif
+ hlpr->c = c;
+ hlpr->len = len;
+ ret = hlpr + 1;
+ /* zeroing the allocated chunk is required by mbedTLS! */
+ memset(ret, 0, c * len);
+ return ret;
+}
+
+static void
+tls_free(void *ptr)
+{
+ altcp_mbedtls_malloc_helper_t *hlpr;
+ if (ptr == NULL) {
+ /* this obviously happened in mbedtls... */
+ return;
+ }
+ hlpr = ((altcp_mbedtls_malloc_helper_t *)ptr) - 1;
+#if ALTCP_MBEDTLS_PLATFORM_ALLOC_STATS
+ if (!altcp_mbedtls_malloc_clear_stats) {
+ altcp_mbedtls_malloc_stats.allocedBytes -= hlpr->c * hlpr->len;
+ }
+#endif
+ mem_free(hlpr);
+}
+#endif /* ALTCP_MBEDTLS_PLATFORM_ALLOC*/
+
+void
+altcp_mbedtls_mem_init(void)
+{
+ /* not much to do here when using the heap */
+
+#if ALTCP_MBEDTLS_PLATFORM_ALLOC
+ /* set mbedtls allocation methods */
+ mbedtls_platform_set_calloc_free(&tls_malloc, &tls_free);
+#endif
+}
+
+altcp_mbedtls_state_t *
+altcp_mbedtls_alloc(void *conf)
+{
+ altcp_mbedtls_state_t *ret = (altcp_mbedtls_state_t *)mem_calloc(1, sizeof(altcp_mbedtls_state_t));
+ if (ret != NULL) {
+ ret->conf = conf;
+ }
+ return ret;
+}
+
+void
+altcp_mbedtls_free(void *conf, altcp_mbedtls_state_t *state)
+{
+ LWIP_UNUSED_ARG(conf);
+ LWIP_ASSERT("state != NULL", state != NULL);
+ mem_free(state);
+}
+
+void *
+altcp_mbedtls_alloc_config(size_t size)
+{
+ void *ret;
+ size_t checked_size = (mem_size_t)size;
+ if (size != checked_size) {
+ /* allocation too big (mem_size_t overflow) */
+ return NULL;
+ }
+ ret = (altcp_mbedtls_state_t *)mem_calloc(1, (mem_size_t)size);
+ return ret;
+}
+
+void
+altcp_mbedtls_free_config(void *item)
+{
+ LWIP_ASSERT("item != NULL", item != NULL);
+ mem_free(item);
+}
+
+#endif /* LWIP_ALTCP_TLS && LWIP_ALTCP_TLS_MBEDTLS */
+#endif /* LWIP_ALTCP */
diff --git a/src/apps/altcp_tls/altcp_tls_mbedtls_mem.h b/src/apps/altcp_tls/altcp_tls_mbedtls_mem.h
new file mode 100644
index 00000000000..b391bf87419
--- /dev/null
+++ b/src/apps/altcp_tls/altcp_tls_mbedtls_mem.h
@@ -0,0 +1,72 @@
+/**
+ * @file
+ * Application layered TCP/TLS connection API (to be used from TCPIP thread)
+ *
+ * This file contains memory management function prototypes for a TLS layer using mbedTLS.
+ *
+ * Memory management contains:
+ * - allocating/freeing altcp_mbedtls_state_t
+ * - allocating/freeing memory used in the mbedTLS library
+ */
+
+/*
+ * Copyright (c) 2017 Simon Goldschmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt <[email protected]>
+ *
+ */
+#ifndef LWIP_HDR_ALTCP_MBEDTLS_MEM_H
+#define LWIP_HDR_ALTCP_MBEDTLS_MEM_H
+
+#include "lwip/opt.h"
+
+#if LWIP_ALTCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/altcp_tls_mbedtls_opts.h"
+
+#if LWIP_ALTCP_TLS && LWIP_ALTCP_TLS_MBEDTLS
+
+#include "altcp_tls_mbedtls_structs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void altcp_mbedtls_mem_init(void);
+altcp_mbedtls_state_t *altcp_mbedtls_alloc(void *conf);
+void altcp_mbedtls_free(void *conf, altcp_mbedtls_state_t *state);
+void *altcp_mbedtls_alloc_config(size_t size);
+void altcp_mbedtls_free_config(void *item);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_ALTCP_TLS && LWIP_ALTCP_TLS_MBEDTLS */
+#endif /* LWIP_ALTCP */
+#endif /* LWIP_HDR_ALTCP_MBEDTLS_MEM_H */
diff --git a/src/apps/altcp_tls/altcp_tls_mbedtls_structs.h b/src/apps/altcp_tls/altcp_tls_mbedtls_structs.h
new file mode 100644
index 00000000000..2ad2b60a543
--- /dev/null
+++ b/src/apps/altcp_tls/altcp_tls_mbedtls_structs.h
@@ -0,0 +1,83 @@
+/**
+ * @file
+ * Application layered TCP/TLS connection API (to be used from TCPIP thread)
+ *
+ * This file contains structure definitions for a TLS layer using mbedTLS.
+ */
+
+/*
+ * Copyright (c) 2017 Simon Goldschmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt <[email protected]>
+ *
+ */
+#ifndef LWIP_HDR_ALTCP_MBEDTLS_STRUCTS_H
+#define LWIP_HDR_ALTCP_MBEDTLS_STRUCTS_H
+
+#include "lwip/opt.h"
+
+#if LWIP_ALTCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/altcp_tls_mbedtls_opts.h"
+
+#if LWIP_ALTCP_TLS && LWIP_ALTCP_TLS_MBEDTLS
+
+#include "lwip/altcp.h"
+#include "lwip/pbuf.h"
+
+#include "mbedtls/ssl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ALTCP_MBEDTLS_FLAGS_HANDSHAKE_DONE 0x01
+#define ALTCP_MBEDTLS_FLAGS_UPPER_CALLED 0x02
+#define ALTCP_MBEDTLS_FLAGS_RX_CLOSE_QUEUED 0x04
+#define ALTCP_MBEDTLS_FLAGS_RX_CLOSED 0x08
+
+typedef struct altcp_mbedtls_state_s {
+ void *conf;
+ mbedtls_ssl_context ssl_context;
+ /* chain of rx pbufs (before decryption) */
+ struct pbuf *rx;
+ struct pbuf *rx_app;
+ u8_t flags;
+ int rx_passed_unrecved;
+ int bio_bytes_read;
+ int bio_bytes_appl;
+ int overhead_bytes_adjust;
+} altcp_mbedtls_state_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_ALTCP_TLS && LWIP_ALTCP_TLS_MBEDTLS */
+#endif /* LWIP_ALTCP */
+#endif /* LWIP_HDR_ALTCP_MBEDTLS_STRUCTS_H */
diff --git a/src/apps/http/altcp_proxyconnect.c b/src/apps/http/altcp_proxyconnect.c
new file mode 100644
index 00000000000..3d5e7e837a7
--- /dev/null
+++ b/src/apps/http/altcp_proxyconnect.c
@@ -0,0 +1,584 @@
+/**
+ * @file
+ * Application layered TCP connection API that executes a proxy-connect.
+ *
+ * This file provides a starting layer that executes a proxy-connect e.g. to
+ * set up TLS connections through a http proxy.
+ */
+
+/*
+ * Copyright (c) 2018 Simon Goldschmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt <[email protected]>
+ *
+ */
+
+#include "lwip/apps/altcp_proxyconnect.h"
+
+#if LWIP_ALTCP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/altcp.h"
+#include "lwip/priv/altcp_priv.h"
+
+#include "lwip/altcp_tcp.h"
+#include "lwip/altcp_tls.h"
+
+#include "lwip/mem.h"
+#include "lwip/init.h"
+
+#include <stdio.h>
+
+/** This string is passed in the HTTP header as "User-Agent: " */
+#ifndef ALTCP_PROXYCONNECT_CLIENT_AGENT
+#define ALTCP_PROXYCONNECT_CLIENT_AGENT "lwIP/" LWIP_VERSION_STRING " (http://savannah.nongnu.org/projects/lwip)"
+#endif
+
+#define ALTCP_PROXYCONNECT_FLAGS_CONNECT_STARTED 0x01
+#define ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE 0x02
+
+typedef struct altcp_proxyconnect_state_s
+{
+ ip_addr_t outer_addr;
+ u16_t outer_port;
+ struct altcp_proxyconnect_config *conf;
+ u8_t flags;
+} altcp_proxyconnect_state_t;
+
+/* Variable prototype, the actual declaration is at the end of this file
+ since it contains pointers to static functions declared here */
+extern const struct altcp_functions altcp_proxyconnect_functions;
+
+/* memory management functions: */
+
+static altcp_proxyconnect_state_t *
+altcp_proxyconnect_state_alloc(void)
+{
+ altcp_proxyconnect_state_t *ret = (altcp_proxyconnect_state_t *)mem_calloc(1, sizeof(altcp_proxyconnect_state_t));
+ return ret;
+}
+
+static void
+altcp_proxyconnect_state_free(altcp_proxyconnect_state_t *state)
+{
+ LWIP_ASSERT("state != NULL", state != NULL);
+ mem_free(state);
+}
+
+/* helper functions */
+
+#define PROXY_CONNECT "CONNECT %s:%d HTTP/1.1\r\n" /* HOST, PORT */ \
+ "User-Agent: %s\r\n" /* User-Agent */\
+ "Proxy-Connection: keep-alive\r\n" \
+ "Connection: keep-alive\r\n" \
+ "\r\n"
+#define PROXY_CONNECT_FORMAT(host, port) PROXY_CONNECT, host, port, ALTCP_PROXYCONNECT_CLIENT_AGENT
+
+/* Format the http proxy connect request via snprintf */
+static int
+altcp_proxyconnect_format_request(char *buffer, size_t bufsize, const char *host, int port)
+{
+ return snprintf(buffer, bufsize, PROXY_CONNECT_FORMAT(host, port));
+}
+
+/* Create and send the http proxy connect request */
+static err_t
+altcp_proxyconnect_send_request(struct altcp_pcb *conn)
+{
+ int len, len2;
+ mem_size_t alloc_len;
+ char *buffer, *host;
+ altcp_proxyconnect_state_t *state = (altcp_proxyconnect_state_t *)conn->state;
+
+ if (!state) {
+ return ERR_VAL;
+ }
+ /* Use printf with zero length to get the required allocation size */
+ len = altcp_proxyconnect_format_request(NULL, 0, "", state->outer_port);
+ if (len < 0) {
+ return ERR_VAL;
+ }
+ /* add allocation size for IP address strings */
+#if LWIP_IPV6
+ len += 40; /* worst-case IPv6 address length */
+#else
+ len += 16; /* worst-case IPv4 address length */
+#endif
+ alloc_len = (mem_size_t)len;
+ if ((len < 0) || (int)alloc_len != len) {
+ /* overflow */
+ return ERR_MEM;
+ }
+ /* Allocate a buffer for the request string */
+ buffer = (char *)mem_malloc(alloc_len);
+ if (buffer == NULL) {
+ return ERR_MEM;
+ }
+ host = ipaddr_ntoa(&state->outer_addr);
+ len2 = altcp_proxyconnect_format_request(buffer, alloc_len, host, state->outer_port);
+ if ((len2 > 0) && (len2 <= len) && (len2 <= 0xFFFF)) {
+ err_t err = altcp_write(conn->inner_conn, buffer, (u16_t)len2, TCP_WRITE_FLAG_COPY);
+ if (err != ERR_OK) {
+ /* @todo: abort? */
+ mem_free(buffer);
+ return err;
+ }
+ }
+ mem_free(buffer);
+ return ERR_OK;
+}
+
+/* callback functions from inner/lower connection: */
+
+/** Connected callback from lower connection (i.e. TCP).
+ * Not really implemented/tested yet...
+ */
+static err_t
+altcp_proxyconnect_lower_connected(void *arg, struct altcp_pcb *inner_conn, err_t err)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ if (conn && conn->state) {
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */
+ /* upper connected is called when handshake is done */
+ if (err != ERR_OK) {
+ if (conn->connected) {
+ if (conn->connected(conn->arg, conn, err) == ERR_ABRT) {
+ return ERR_ABRT;
+ }
+ return ERR_OK;
+ }
+ }
+ /* send proxy connect request here */
+ return altcp_proxyconnect_send_request(conn);
+ }
+ return ERR_VAL;
+}
+
+/** Recv callback from lower connection (i.e. TCP)
+ * This one mainly differs between connection setup (wait for proxy OK string)
+ * and application phase (data is passed on to the application).
+ */
+static err_t
+altcp_proxyconnect_lower_recv(void *arg, struct altcp_pcb *inner_conn, struct pbuf *p, err_t err)
+{
+ altcp_proxyconnect_state_t *state;
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+
+ LWIP_ASSERT("no err expected", err == ERR_OK);
+ LWIP_UNUSED_ARG(err);
+
+ if (!conn) {
+ /* no connection given as arg? should not happen, but prevent pbuf/conn leaks */
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+ altcp_close(inner_conn);
+ return ERR_CLSD;
+ }
+ state = (altcp_proxyconnect_state_t *)conn->state;
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ if (!state) {
+ /* already closed */
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+ altcp_close(inner_conn);
+ return ERR_CLSD;
+ }
+ if (state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE) {
+ /* application phase, just pass this through */
+ if (conn->recv) {
+ return conn->recv(conn->arg, conn, p, err);
+ }
+ pbuf_free(p);
+ return ERR_OK;
+ } else {
+ /* setup phase */
+ /* handle NULL pbuf (inner connection closed) */
+ if (p == NULL) {
+ if (altcp_close(conn) != ERR_OK) {
+ altcp_abort(conn);
+ return ERR_ABRT;
+ }
+ return ERR_OK;
+ } else {
+ /* @todo: parse setup phase rx data
+ for now, we just wait for the end of the header... */
+ u16_t idx = pbuf_memfind(p, "\r\n\r\n", 4, 0);
+ altcp_recved(inner_conn, p->tot_len);
+ pbuf_free(p);
+ if (idx != 0xFFFF) {
+ state->flags |= ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE;
+ if (conn->connected) {
+ return conn->connected(conn->arg, conn, ERR_OK);
+ }
+ }
+ return ERR_OK;
+ }
+ }
+}
+
+/** Sent callback from lower connection (i.e. TCP)
+ * This only informs the upper layer to try to send more, not about
+ * the number of ACKed bytes.
+ */
+static err_t
+altcp_proxyconnect_lower_sent(void *arg, struct altcp_pcb *inner_conn, u16_t len)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ LWIP_UNUSED_ARG(len);
+ if (conn) {
+ altcp_proxyconnect_state_t *state = (altcp_proxyconnect_state_t *)conn->state;
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */
+ if (!state || !(state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE)) {
+ /* @todo: do something here? */
+ return ERR_OK;
+ }
+ /* pass this on to upper sent */
+ if (conn->sent) {
+ return conn->sent(conn->arg, conn, len);
+ }
+ }
+ return ERR_OK;
+}
+
+/** Poll callback from lower connection (i.e. TCP)
+ * Just pass this on to the application.
+ * @todo: retry sending?
+ */
+static err_t
+altcp_proxyconnect_lower_poll(void *arg, struct altcp_pcb *inner_conn)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ if (conn) {
+ LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
+ LWIP_UNUSED_ARG(inner_conn); /* for LWIP_NOASSERT */
+ if (conn->poll) {
+ return conn->poll(conn->arg, conn);
+ }
+ }
+ return ERR_OK;
+}
+
+static void
+altcp_proxyconnect_lower_err(void *arg, err_t err)
+{
+ struct altcp_pcb *conn = (struct altcp_pcb *)arg;
+ if (conn) {
+ conn->inner_conn = NULL; /* already freed */
+ if (conn->err) {
+ conn->err(conn->arg, err);
+ }
+ altcp_free(conn);
+ }
+}
+
+
+/* setup functions */
+
+static void
+altcp_proxyconnect_setup_callbacks(struct altcp_pcb *conn, struct altcp_pcb *inner_conn)
+{
+ altcp_arg(inner_conn, conn);
+ altcp_recv(inner_conn, altcp_proxyconnect_lower_recv);
+ altcp_sent(inner_conn, altcp_proxyconnect_lower_sent);
+ altcp_err(inner_conn, altcp_proxyconnect_lower_err);
+ /* tcp_poll is set when interval is set by application */
+ /* listen is set totally different :-) */
+}
+
+static err_t
+altcp_proxyconnect_setup(struct altcp_proxyconnect_config *config, struct altcp_pcb *conn, struct altcp_pcb *inner_conn)
+{
+ altcp_proxyconnect_state_t *state;
+ if (!config) {
+ return ERR_ARG;
+ }
+ LWIP_ASSERT("invalid inner_conn", conn != inner_conn);
+
+ /* allocate proxyconnect context */
+ state = altcp_proxyconnect_state_alloc();
+ if (state == NULL) {
+ return ERR_MEM;
+ }
+ state->flags = 0;
+ state->conf = config;
+ altcp_proxyconnect_setup_callbacks(conn, inner_conn);
+ conn->inner_conn = inner_conn;
+ conn->fns = &altcp_proxyconnect_functions;
+ conn->state = state;
+ return ERR_OK;
+}
+
+/** Allocate a new altcp layer connecting through a proxy.
+ * This function gets the inner pcb passed.
+ *
+ * @param config struct altcp_proxyconnect_config that contains the proxy settings
+ * @param inner_pcb pcb that makes the connection to the proxy (i.e. tcp pcb)
+ */
+struct altcp_pcb *
+altcp_proxyconnect_new(struct altcp_proxyconnect_config *config, struct altcp_pcb *inner_pcb)
+{
+ struct altcp_pcb *ret;
+ if (inner_pcb == NULL) {
+ return NULL;
+ }
+ ret = altcp_alloc();
+ if (ret != NULL) {
+ if (altcp_proxyconnect_setup(config, ret, inner_pcb) != ERR_OK) {
+ altcp_free(ret);
+ return NULL;
+ }
+ }
+ return ret;
+}
+
+/** Allocate a new altcp layer connecting through a proxy.
+ * This function allocates the inner pcb as tcp pcb, resulting in a direct tcp
+ * connection to the proxy.
+ *
+ * @param config struct altcp_proxyconnect_config that contains the proxy settings
+ * @param ip_type IP type of the connection (@ref lwip_ip_addr_type)
+ */
+struct altcp_pcb *
+altcp_proxyconnect_new_tcp(struct altcp_proxyconnect_config *config, u8_t ip_type)
+{
+ struct altcp_pcb *inner_pcb, *ret;
+
+ /* inner pcb is tcp */
+ inner_pcb = altcp_tcp_new_ip_type(ip_type);
+ if (inner_pcb == NULL) {
+ return NULL;
+ }
+ ret = altcp_proxyconnect_new(config, inner_pcb);
+ if (ret == NULL) {
+ altcp_close(inner_pcb);
+ }
+ return ret;
+}
+
+/** Allocator function to allocate a proxy connect altcp pcb connecting directly
+ * via tcp to the proxy.
+ *
+ * The returned pcb is a chain: altcp_proxyconnect - altcp_tcp - tcp pcb
+ *
+ * This function is meant for use with @ref altcp_new.
+ *
+ * @param arg struct altcp_proxyconnect_config that contains the proxy settings
+ * @param ip_type IP type of the connection (@ref lwip_ip_addr_type)
+ */
+struct altcp_pcb *
+altcp_proxyconnect_alloc(void *arg, u8_t ip_type)
+{
+ return altcp_proxyconnect_new_tcp((struct altcp_proxyconnect_config *)arg, ip_type);
+}
+
+
+#if LWIP_ALTCP_TLS
+
+/** Allocator function to allocate a TLS connection through a proxy.
+ *
+ * The returned pcb is a chain: altcp_tls - altcp_proxyconnect - altcp_tcp - tcp pcb
+ *
+ * This function is meant for use with @ref altcp_new.
+ *
+ * @param arg struct altcp_proxyconnect_tls_config that contains the proxy settings
+ * and tls settings
+ * @param ip_type IP type of the connection (@ref lwip_ip_addr_type)
+ */
+struct altcp_pcb *
+altcp_proxyconnect_tls_alloc(void *arg, u8_t ip_type)
+{
+ struct altcp_proxyconnect_tls_config *cfg = (struct altcp_proxyconnect_tls_config *)arg;
+ struct altcp_pcb *proxy_pcb;
+ struct altcp_pcb *tls_pcb;
+
+ proxy_pcb = altcp_proxyconnect_new_tcp(&cfg->proxy, ip_type);
+ tls_pcb = altcp_tls_wrap(cfg->tls_config, proxy_pcb);
+
+ if (tls_pcb == NULL) {
+ altcp_close(proxy_pcb);
+ }
+ return tls_pcb;
+}
+#endif /* LWIP_ALTCP_TLS */
+
+/* "virtual" functions */
+static void
+altcp_proxyconnect_set_poll(struct altcp_pcb *conn, u8_t interval)
+{
+ if (conn != NULL) {
+ altcp_poll(conn->inner_conn, altcp_proxyconnect_lower_poll, interval);
+ }
+}
+
+static void
+altcp_proxyconnect_recved(struct altcp_pcb *conn, u16_t len)
+{
+ altcp_proxyconnect_state_t *state;
+ if (conn == NULL) {
+ return;
+ }
+ state = (altcp_proxyconnect_state_t *)conn->state;
+ if (state == NULL) {
+ return;
+ }
+ if (!(state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE)) {
+ return;
+ }
+ altcp_recved(conn->inner_conn, len);
+}
+
+static err_t
+altcp_proxyconnect_connect(struct altcp_pcb *conn, const ip_addr_t *ipaddr, u16_t port, altcp_connected_fn connected)
+{
+ altcp_proxyconnect_state_t *state;
+
+ if ((conn == NULL) || (ipaddr == NULL)) {
+ return ERR_VAL;
+ }
+ state = (altcp_proxyconnect_state_t *)conn->state;
+ if (state == NULL) {
+ return ERR_VAL;
+ }
+ if (state->flags & ALTCP_PROXYCONNECT_FLAGS_CONNECT_STARTED) {
+ return ERR_VAL;
+ }
+ state->flags |= ALTCP_PROXYCONNECT_FLAGS_CONNECT_STARTED;
+
+ conn->connected = connected;
+ /* connect to our proxy instead, but store the requested address and port */
+ ip_addr_copy(state->outer_addr, *ipaddr);
+ state->outer_port = port;
+
+ return altcp_connect(conn->inner_conn, &state->conf->proxy_addr, state->conf->proxy_port, altcp_proxyconnect_lower_connected);
+}
+
+static struct altcp_pcb *
+altcp_proxyconnect_listen(struct altcp_pcb *conn, u8_t backlog, err_t *err)
+{
+ LWIP_UNUSED_ARG(conn);
+ LWIP_UNUSED_ARG(backlog);
+ LWIP_UNUSED_ARG(err);
+ /* listen not supported! */
+ return NULL;
+}
+
+static void
+altcp_proxyconnect_abort(struct altcp_pcb *conn)
+{
+ if (conn != NULL) {
+ if (conn->inner_conn != NULL) {
+ altcp_abort(conn->inner_conn);
+ }
+ altcp_free(conn);
+ }
+}
+
+static err_t
+altcp_proxyconnect_close(struct altcp_pcb *conn)
+{
+ if (conn == NULL) {
+ return ERR_VAL;
+ }
+ if (conn->inner_conn != NULL) {
+ err_t err = altcp_close(conn->inner_conn);
+ if (err != ERR_OK) {
+ /* closing inner conn failed, return the error */
+ return err;
+ }
+ }
+ /* no inner conn or closing it succeeded, deallocate myself */
+ altcp_free(conn);
+ return ERR_OK;
+}
+
+static err_t
+altcp_proxyconnect_write(struct altcp_pcb *conn, const void *dataptr, u16_t len, u8_t apiflags)
+{
+ altcp_proxyconnect_state_t *state;
+
+ LWIP_UNUSED_ARG(apiflags);
+
+ if (conn == NULL) {
+ return ERR_VAL;
+ }
+
+ state = (altcp_proxyconnect_state_t *)conn->state;
+ if (state == NULL) {
+ /* @todo: which error? */
+ return ERR_CLSD;
+ }
+ if (!(state->flags & ALTCP_PROXYCONNECT_FLAGS_HANDSHAKE_DONE)) {
+ /* @todo: which error? */
+ return ERR_VAL;
+ }
+ return altcp_write(conn->inner_conn, dataptr, len, apiflags);
+}
+
+static void
+altcp_proxyconnect_dealloc(struct altcp_pcb *conn)
+{
+ /* clean up and free tls state */
+ if (conn) {
+ altcp_proxyconnect_state_t *state = (altcp_proxyconnect_state_t *)conn->state;
+ if (state) {
+ altcp_proxyconnect_state_free(state);
+ conn->state = NULL;
+ }
+ }
+}
+const struct altcp_functions altcp_proxyconnect_functions = {
+ altcp_proxyconnect_set_poll,
+ altcp_proxyconnect_recved,
+ altcp_default_bind,
+ altcp_proxyconnect_connect,
+ altcp_proxyconnect_listen,
+ altcp_proxyconnect_abort,
+ altcp_proxyconnect_close,
+ altcp_default_shutdown,
+ altcp_proxyconnect_write,
+ altcp_default_output,
+ altcp_default_mss,
+ altcp_default_sndbuf,
+ altcp_default_sndqueuelen,
+ altcp_default_nagle_disable,
+ altcp_default_nagle_enable,
+ altcp_default_nagle_disabled,
+ altcp_default_setprio,
+ altcp_proxyconnect_dealloc,
+ altcp_default_get_tcp_addrinfo,
+ altcp_default_get_ip,
+ altcp_default_get_port
+#ifdef LWIP_DEBUG
+ , altcp_default_dbg_get_tcp_state
+#endif
+};
+
+#endif /* LWIP_ALTCP */
diff --git a/src/apps/http/fs.c b/src/apps/http/fs.c
new file mode 100644
index 00000000000..e12e314e200
--- /dev/null
+++ b/src/apps/http/fs.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <[email protected]>
+ *
+ */
+
+#include "lwip/apps/httpd_opts.h"
+#include "lwip/def.h"
+#include "lwip/apps/fs.h"
+#include <string.h>
+
+
+#include HTTPD_FSDATA_FILE
+
+/*-----------------------------------------------------------------------------------*/
+err_t
+fs_open(struct fs_file *file, const char *name)
+{
+ const struct fsdata_file *f;
+
+ if ((file == NULL) || (name == NULL)) {
+ return ERR_ARG;
+ }
+
+#if LWIP_HTTPD_CUSTOM_FILES
+ if (fs_open_custom(file, name)) {
+ file->flags |= FS_FILE_FLAGS_CUSTOM;
+ return ERR_OK;
+ }
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+
+ for (f = FS_ROOT; f != NULL; f = f->next) {
+ if (!strcmp(name, (const char *)f->name)) {
+ file->data = (const char *)f->data;
+ file->len = f->len;
+ file->index = f->len;
+ file->flags = f->flags;
+#if HTTPD_PRECALCULATED_CHECKSUM
+ file->chksum_count = f->chksum_count;
+ file->chksum = f->chksum;
+#endif /* HTTPD_PRECALCULATED_CHECKSUM */
+#if LWIP_HTTPD_FILE_EXTENSION
+ file->pextension = NULL;
+#endif /* LWIP_HTTPD_FILE_EXTENSION */
+#if LWIP_HTTPD_FILE_STATE
+ file->state = fs_state_init(file, name);
+#endif /* #if LWIP_HTTPD_FILE_STATE */
+ return ERR_OK;
+ }
+ }
+ /* file not found */
+ return ERR_VAL;
+}
+
+/*-----------------------------------------------------------------------------------*/
+void
+fs_close(struct fs_file *file)
+{
+#if LWIP_HTTPD_CUSTOM_FILES
+ if ((file->flags & FS_FILE_FLAGS_CUSTOM) != 0) {
+ fs_close_custom(file);
+ }
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+#if LWIP_HTTPD_FILE_STATE
+ fs_state_free(file, file->state);
+#endif /* #if LWIP_HTTPD_FILE_STATE */
+ LWIP_UNUSED_ARG(file);
+}
+/*-----------------------------------------------------------------------------------*/
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+#if LWIP_HTTPD_FS_ASYNC_READ
+int
+fs_read_async(struct fs_file *file, char *buffer, int count, fs_wait_cb callback_fn, void *callback_arg)
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+int
+fs_read(struct fs_file *file, char *buffer, int count)
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+{
+ int read;
+ if (file->index == file->len) {
+ return FS_READ_EOF;
+ }
+#if LWIP_HTTPD_FS_ASYNC_READ
+ LWIP_UNUSED_ARG(callback_fn);
+ LWIP_UNUSED_ARG(callback_arg);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+#if LWIP_HTTPD_CUSTOM_FILES
+ if ((file->flags & FS_FILE_FLAGS_CUSTOM) != 0) {
+#if LWIP_HTTPD_FS_ASYNC_READ
+ return fs_read_async_custom(file, buffer, count, callback_fn, callback_arg);
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+ return fs_read_custom(file, buffer, count);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+ }
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+
+ read = file->len - file->index;
+ if (read > count) {
+ read = count;
+ }
+
+ MEMCPY(buffer, (file->data + file->index), read);
+ file->index += read;
+
+ return (read);
+}
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+/*-----------------------------------------------------------------------------------*/
+#if LWIP_HTTPD_FS_ASYNC_READ
+int
+fs_is_file_ready(struct fs_file *file, fs_wait_cb callback_fn, void *callback_arg)
+{
+ if (file != NULL) {
+#if LWIP_HTTPD_FS_ASYNC_READ
+#if LWIP_HTTPD_CUSTOM_FILES
+ if (!fs_canread_custom(file)) {
+ if (fs_wait_read_custom(file, callback_fn, callback_arg)) {
+ return 0;
+ }
+ }
+#else /* LWIP_HTTPD_CUSTOM_FILES */
+ LWIP_UNUSED_ARG(callback_fn);
+ LWIP_UNUSED_ARG(callback_arg);
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+ }
+ return 1;
+}
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+/*-----------------------------------------------------------------------------------*/
+int
+fs_bytes_left(struct fs_file *file)
+{
+ return file->len - file->index;
+}
diff --git a/src/apps/http/fs/404.html b/src/apps/http/fs/404.html
new file mode 100644
index 00000000000..40b343a91e2
--- /dev/null
+++ b/src/apps/http/fs/404.html
@@ -0,0 +1,21 @@
+<html>
+<head><title>lwIP - A Lightweight TCP/IP Stack</title></head>
+<body bgcolor="white" text="black">
+
+ <table width="100%">
+ <tr valign="top"><td width="80">
+ <a href="http://www.sics.se/"><img src="/img/sics.gif"
+ border="0" alt="SICS logo" title="SICS logo"></a>
+ </td><td width="500">
+ <h1>lwIP - A Lightweight TCP/IP Stack</h1>
+ <h2>404 - Page not found</h2>
+ <p>
+ Sorry, the page you are requesting was not found on this
+ server.
+ </p>
+ </td><td>
+ &nbsp;
+ </td></tr>
+ </table>
+</body>
+</html>
diff --git a/src/apps/http/fs/img/sics.gif b/src/apps/http/fs/img/sics.gif
new file mode 100644
index 00000000000..0a4fc7bb070
--- /dev/null
+++ b/src/apps/http/fs/img/sics.gif
Binary files differ
diff --git a/src/apps/http/fs/index.html b/src/apps/http/fs/index.html
new file mode 100644
index 00000000000..ab575ef0891
--- /dev/null
+++ b/src/apps/http/fs/index.html
@@ -0,0 +1,47 @@
+<html>
+<head><title>lwIP - A Lightweight TCP/IP Stack</title></head>
+<body bgcolor="white" text="black">
+
+ <table width="100%">
+ <tr valign="top"><td width="80">
+ <a href="http://www.sics.se/"><img src="/img/sics.gif"
+ border="0" alt="SICS logo" title="SICS logo"></a>
+ </td><td width="500">
+ <h1>lwIP - A Lightweight TCP/IP Stack</h1>
+ <p>
+ The web page you are watching was served by a simple web
+ server running on top of the lightweight TCP/IP stack <a
+ href="http://www.sics.se/~adam/lwip/">lwIP</a>.
+ </p>
+ <p>
+ lwIP is an open source implementation of the TCP/IP
+ protocol suite that was originally written by <a
+ href="http://www.sics.se/~adam/lwip/">Adam Dunkels
+ of the Swedish Institute of Computer Science</a> but now is
+ being actively developed by a team of developers
+ distributed world-wide. Since it's release, lwIP has
+ spurred a lot of interest and has been ported to several
+ platforms and operating systems. lwIP can be used either
+ with or without an underlying OS.
+ </p>
+ <p>
+ The focus of the lwIP TCP/IP implementation is to reduce
+ the RAM usage while still having a full scale TCP. This
+ makes lwIP suitable for use in embedded systems with tens
+ of kilobytes of free RAM and room for around 40 kilobytes
+ of code ROM.
+ </p>
+ <p>
+ More information about lwIP can be found at the lwIP
+ homepage at <a
+ href="http://savannah.nongnu.org/projects/lwip/">http://savannah.nongnu.org/projects/lwip/</a>
+ or at the lwIP wiki at <a
+ href="http://lwip.wikia.com/">http://lwip.wikia.com/</a>.
+ </p>
+ </td><td>
+ &nbsp;
+ </td></tr>
+ </table>
+</body>
+</html>
+
diff --git a/src/apps/http/fsdata.c b/src/apps/http/fsdata.c
new file mode 100644
index 00000000000..50bc87acfff
--- /dev/null
+++ b/src/apps/http/fsdata.c
@@ -0,0 +1,336 @@
+#include "lwip/apps/fs.h"
+#include "lwip/def.h"
+
+
+#define file_NULL (struct fsdata_file *) NULL
+
+
+#ifndef FS_FILE_FLAGS_HEADER_INCLUDED
+#define FS_FILE_FLAGS_HEADER_INCLUDED 1
+#endif
+#ifndef FS_FILE_FLAGS_HEADER_PERSISTENT
+#define FS_FILE_FLAGS_HEADER_PERSISTENT 0
+#endif
+/* FSDATA_FILE_ALIGNMENT: 0=off, 1=by variable, 2=by include */
+#ifndef FSDATA_FILE_ALIGNMENT
+#define FSDATA_FILE_ALIGNMENT 0
+#endif
+#ifndef FSDATA_ALIGN_PRE
+#define FSDATA_ALIGN_PRE
+#endif
+#ifndef FSDATA_ALIGN_POST
+#define FSDATA_ALIGN_POST
+#endif
+#if FSDATA_FILE_ALIGNMENT==2
+#include "fsdata_alignment.h"
+#endif
+#if FSDATA_FILE_ALIGNMENT==1
+static const unsigned int dummy_align__img_sics_gif = 0;
+#endif
+static const unsigned char FSDATA_ALIGN_PRE data__img_sics_gif[] FSDATA_ALIGN_POST = {
+/* /img/sics.gif (14 chars) */
+0x2f,0x69,0x6d,0x67,0x2f,0x73,0x69,0x63,0x73,0x2e,0x67,0x69,0x66,0x00,0x00,0x00,
+
+/* HTTP header */
+/* "HTTP/1.0 200 OK
+" (17 bytes) */
+0x48,0x54,0x54,0x50,0x2f,0x31,0x2e,0x30,0x20,0x32,0x30,0x30,0x20,0x4f,0x4b,0x0d,
+0x0a,
+/* "Server: lwIP/2.0.3d (http://savannah.nongnu.org/projects/lwip)
+" (64 bytes) */
+0x53,0x65,0x72,0x76,0x65,0x72,0x3a,0x20,0x6c,0x77,0x49,0x50,0x2f,0x32,0x2e,0x30,
+0x2e,0x33,0x64,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,
+0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,
+0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x29,0x0d,0x0a,
+
+/* "Content-Length: 724
+" (18+ bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x4c,0x65,0x6e,0x67,0x74,0x68,0x3a,0x20,
+0x37,0x32,0x34,0x0d,0x0a,
+/* "Content-Type: image/gif
+
+" (27 bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x54,0x79,0x70,0x65,0x3a,0x20,0x69,0x6d,
+0x61,0x67,0x65,0x2f,0x67,0x69,0x66,0x0d,0x0a,0x0d,0x0a,
+/* raw file data (724 bytes) */
+0x47,0x49,0x46,0x38,0x39,0x61,0x46,0x00,0x22,0x00,0xa5,0x00,0x00,0xd9,0x2b,0x39,
+0x6a,0x6a,0x6a,0xbf,0xbf,0xbf,0x93,0x93,0x93,0x0f,0x0f,0x0f,0xb0,0xb0,0xb0,0xa6,
+0xa6,0xa6,0x80,0x80,0x80,0x76,0x76,0x76,0x1e,0x1e,0x1e,0x9d,0x9d,0x9d,0x2e,0x2e,
+0x2e,0x49,0x49,0x49,0x54,0x54,0x54,0x8a,0x8a,0x8a,0x60,0x60,0x60,0xc6,0xa6,0x99,
+0xbd,0xb5,0xb2,0xc2,0xab,0xa1,0xd9,0x41,0x40,0xd5,0x67,0x55,0xc0,0xb0,0xaa,0xd5,
+0x5e,0x4e,0xd6,0x50,0x45,0xcc,0x93,0x7d,0xc8,0xa1,0x90,0xce,0x8b,0x76,0xd2,0x7b,
+0x65,0xd1,0x84,0x6d,0xc9,0x99,0x86,0x3a,0x3a,0x3a,0x00,0x00,0x00,0xb8,0xb8,0xb8,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x2c,0x00,0x00,
+0x00,0x00,0x46,0x00,0x22,0x00,0x00,0x06,0xfe,0x40,0x90,0x70,0x48,0x2c,0x1a,0x8f,
+0xc8,0xa4,0x72,0xc9,0x6c,0x3a,0x9f,0xd0,0xa8,0x74,0x4a,0xad,0x5a,0xaf,0xd8,0xac,
+0x76,0xa9,0x40,0x04,0xbe,0x83,0xe2,0x60,0x3c,0x50,0x20,0x0d,0x8e,0x6f,0x00,0x31,
+0x28,0x1c,0x0d,0x07,0xb5,0xc3,0x60,0x75,0x24,0x3e,0xf8,0xfc,0x87,0x11,0x06,0xe9,
+0x3d,0x46,0x07,0x0b,0x7a,0x7a,0x7c,0x43,0x06,0x1e,0x84,0x78,0x0b,0x07,0x6e,0x51,
+0x01,0x8a,0x84,0x08,0x7e,0x79,0x80,0x87,0x89,0x91,0x7a,0x93,0x0a,0x04,0x99,0x78,
+0x96,0x4f,0x03,0x9e,0x79,0x01,0x94,0x9f,0x43,0x9c,0xa3,0xa4,0x05,0x77,0xa3,0xa0,
+0x4e,0x98,0x79,0x0b,0x1e,0x83,0xa4,0xa6,0x1f,0x96,0x05,0x9d,0xaa,0x78,0x01,0x07,
+0x84,0x04,0x1e,0x1e,0xbb,0xb8,0x51,0x84,0x0e,0x43,0x05,0x07,0x77,0xa5,0x7f,0x42,
+0xb1,0xb2,0x01,0x63,0x08,0x0d,0xbb,0x01,0x0c,0x7a,0x0d,0x44,0x0e,0xd8,0xaf,0x4c,
+0x05,0x7a,0x04,0x47,0x07,0x07,0xb7,0x80,0xa2,0xe1,0x7d,0x44,0x05,0x01,0x04,0x01,
+0xd0,0xea,0x87,0x93,0x4f,0xe0,0x9a,0x49,0xce,0xd8,0x79,0x04,0x66,0x20,0x15,0x10,
+0x10,0x11,0x92,0x29,0x80,0xb6,0xc0,0x91,0x15,0x45,0x1e,0x90,0x19,0x71,0x46,0xa8,
+0x5c,0x04,0x0e,0x00,0x22,0x4e,0xe8,0x40,0x24,0x9f,0x3e,0x04,0x06,0xa7,0x58,0xd4,
+0x93,0xa0,0x1c,0x91,0x3f,0xe8,0xf0,0x88,0x03,0xb1,0x21,0xa2,0x49,0x00,0x19,0x86,
+0xfc,0x52,0x44,0xe0,0x01,0x9d,0x29,0x21,0x15,0x25,0x50,0xf7,0x67,0x25,0x1e,0x06,
+0xfd,0x4e,0x9a,0xb4,0x90,0xac,0x15,0xfa,0xcb,0x52,0x53,0x1e,0x8c,0xf2,0xf8,0x07,
+0x92,0x2d,0x08,0x3a,0x4d,0x12,0x49,0x95,0x49,0xdb,0x14,0x04,0xc4,0x14,0x85,0x29,
+0xaa,0xe7,0x01,0x08,0xa4,0x49,0x01,0x14,0x51,0xe0,0x53,0x91,0xd5,0x29,0x06,0x1a,
+0x64,0x02,0xf4,0xc7,0x81,0x9e,0x05,0x20,0x22,0x64,0xa5,0x30,0xae,0xab,0x9e,0x97,
+0x53,0xd8,0xb9,0xfd,0x50,0xef,0x93,0x02,0x42,0x74,0x34,0xe8,0x9c,0x20,0x21,0xc9,
+0x01,0x68,0x78,0xe6,0x55,0x29,0x20,0x56,0x4f,0x4c,0x40,0x51,0x71,0x82,0xc0,0x70,
+0x21,0x22,0x85,0xbe,0x4b,0x1c,0x44,0x05,0xea,0xa4,0x01,0xbf,0x22,0xb5,0xf0,0x1c,
+0x06,0x51,0x38,0x8f,0xe0,0x22,0xec,0x18,0xac,0x39,0x22,0xd4,0xd6,0x93,0x44,0x01,
+0x32,0x82,0xc8,0xfc,0x61,0xb3,0x01,0x45,0x0c,0x2e,0x83,0x30,0xd0,0x0e,0x17,0x24,
+0x0f,0x70,0x85,0x94,0xee,0x05,0x05,0x53,0x4b,0x32,0x1b,0x3f,0x98,0xd3,0x1d,0x29,
+0x81,0xb0,0xae,0x1e,0x8c,0x7e,0x68,0xe0,0x60,0x5a,0x54,0x8f,0xb0,0x78,0x69,0x73,
+0x06,0xa2,0x00,0x6b,0x57,0xca,0x3d,0x11,0x50,0xbd,0x04,0x30,0x4b,0x3a,0xd4,0xab,
+0x5f,0x1f,0x9b,0x3d,0x13,0x74,0x27,0x88,0x3c,0x25,0xe0,0x17,0xbe,0x7a,0x79,0x45,
+0x0d,0x0c,0xb0,0x8b,0xda,0x90,0xca,0x80,0x06,0x5d,0x17,0x60,0x1c,0x22,0x4c,0xd8,
+0x57,0x22,0x06,0x20,0x00,0x98,0x07,0x08,0xe4,0x56,0x80,0x80,0x1c,0xc5,0xb7,0xc5,
+0x82,0x0c,0x36,0xe8,0xe0,0x83,0x10,0x46,0x28,0xe1,0x84,0x14,0x56,0x68,0xa1,0x10,
+0x41,0x00,0x00,0x3b,};
+
+#if FSDATA_FILE_ALIGNMENT==1
+static const unsigned int dummy_align__404_html = 1;
+#endif
+static const unsigned char FSDATA_ALIGN_PRE data__404_html[] FSDATA_ALIGN_POST = {
+/* /404.html (10 chars) */
+0x2f,0x34,0x30,0x34,0x2e,0x68,0x74,0x6d,0x6c,0x00,0x00,0x00,
+
+/* HTTP header */
+/* "HTTP/1.0 404 File not found
+" (29 bytes) */
+0x48,0x54,0x54,0x50,0x2f,0x31,0x2e,0x30,0x20,0x34,0x30,0x34,0x20,0x46,0x69,0x6c,
+0x65,0x20,0x6e,0x6f,0x74,0x20,0x66,0x6f,0x75,0x6e,0x64,0x0d,0x0a,
+/* "Server: lwIP/2.0.3d (http://savannah.nongnu.org/projects/lwip)
+" (64 bytes) */
+0x53,0x65,0x72,0x76,0x65,0x72,0x3a,0x20,0x6c,0x77,0x49,0x50,0x2f,0x32,0x2e,0x30,
+0x2e,0x33,0x64,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,
+0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,
+0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x29,0x0d,0x0a,
+
+/* "Content-Length: 565
+" (18+ bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x4c,0x65,0x6e,0x67,0x74,0x68,0x3a,0x20,
+0x35,0x36,0x35,0x0d,0x0a,
+/* "Content-Type: text/html
+
+" (27 bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x54,0x79,0x70,0x65,0x3a,0x20,0x74,0x65,
+0x78,0x74,0x2f,0x68,0x74,0x6d,0x6c,0x0d,0x0a,0x0d,0x0a,
+/* raw file data (565 bytes) */
+0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x0d,0x0a,0x3c,0x68,0x65,0x61,0x64,0x3e,0x3c,0x74,
+0x69,0x74,0x6c,0x65,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,0x69,
+0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,0x50,
+0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x74,0x69,0x74,0x6c,0x65,0x3e,0x3c,0x2f,
+0x68,0x65,0x61,0x64,0x3e,0x0d,0x0a,0x3c,0x62,0x6f,0x64,0x79,0x20,0x62,0x67,0x63,
+0x6f,0x6c,0x6f,0x72,0x3d,0x22,0x77,0x68,0x69,0x74,0x65,0x22,0x20,0x74,0x65,0x78,
+0x74,0x3d,0x22,0x62,0x6c,0x61,0x63,0x6b,0x22,0x3e,0x0d,0x0a,0x0d,0x0a,0x20,0x20,
+0x20,0x20,0x3c,0x74,0x61,0x62,0x6c,0x65,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,
+0x31,0x30,0x30,0x25,0x22,0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x74,
+0x72,0x20,0x76,0x61,0x6c,0x69,0x67,0x6e,0x3d,0x22,0x74,0x6f,0x70,0x22,0x3e,0x3c,
+0x74,0x64,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x38,0x30,0x22,0x3e,0x09,0x20,
+0x20,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x61,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,
+0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,
+0x65,0x2f,0x22,0x3e,0x3c,0x69,0x6d,0x67,0x20,0x73,0x72,0x63,0x3d,0x22,0x2f,0x69,
+0x6d,0x67,0x2f,0x73,0x69,0x63,0x73,0x2e,0x67,0x69,0x66,0x22,0x0d,0x0a,0x09,0x20,
+0x20,0x62,0x6f,0x72,0x64,0x65,0x72,0x3d,0x22,0x30,0x22,0x20,0x61,0x6c,0x74,0x3d,
+0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x20,0x74,0x69,0x74,0x6c,
+0x65,0x3d,0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x3e,0x3c,0x2f,
+0x61,0x3e,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x20,0x77,0x69,
+0x64,0x74,0x68,0x3d,0x22,0x35,0x30,0x30,0x22,0x3e,0x09,0x20,0x20,0x0d,0x0a,0x09,
+0x20,0x20,0x3c,0x68,0x31,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,
+0x69,0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,
+0x50,0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x68,0x31,0x3e,0x0d,0x0a,0x09,0x20,
+0x20,0x3c,0x68,0x32,0x3e,0x34,0x30,0x34,0x20,0x2d,0x20,0x50,0x61,0x67,0x65,0x20,
+0x6e,0x6f,0x74,0x20,0x66,0x6f,0x75,0x6e,0x64,0x3c,0x2f,0x68,0x32,0x3e,0x0d,0x0a,
+0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x53,0x6f,0x72,
+0x72,0x79,0x2c,0x20,0x74,0x68,0x65,0x20,0x70,0x61,0x67,0x65,0x20,0x79,0x6f,0x75,
+0x20,0x61,0x72,0x65,0x20,0x72,0x65,0x71,0x75,0x65,0x73,0x74,0x69,0x6e,0x67,0x20,
+0x77,0x61,0x73,0x20,0x6e,0x6f,0x74,0x20,0x66,0x6f,0x75,0x6e,0x64,0x20,0x6f,0x6e,
+0x20,0x74,0x68,0x69,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x73,0x65,0x72,0x76,
+0x65,0x72,0x2e,0x20,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,
+0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x26,0x6e,
+0x62,0x73,0x70,0x3b,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x2f,0x74,0x72,
+0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,
+0x3e,0x0d,0x0a,0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x0d,0x0a,0x3c,0x2f,0x68,0x74,
+0x6d,0x6c,0x3e,0x0d,0x0a,};
+
+#if FSDATA_FILE_ALIGNMENT==1
+static const unsigned int dummy_align__index_html = 2;
+#endif
+static const unsigned char FSDATA_ALIGN_PRE data__index_html[] FSDATA_ALIGN_POST = {
+/* /index.html (12 chars) */
+0x2f,0x69,0x6e,0x64,0x65,0x78,0x2e,0x68,0x74,0x6d,0x6c,0x00,
+
+/* HTTP header */
+/* "HTTP/1.0 200 OK
+" (17 bytes) */
+0x48,0x54,0x54,0x50,0x2f,0x31,0x2e,0x30,0x20,0x32,0x30,0x30,0x20,0x4f,0x4b,0x0d,
+0x0a,
+/* "Server: lwIP/2.0.3d (http://savannah.nongnu.org/projects/lwip)
+" (64 bytes) */
+0x53,0x65,0x72,0x76,0x65,0x72,0x3a,0x20,0x6c,0x77,0x49,0x50,0x2f,0x32,0x2e,0x30,
+0x2e,0x33,0x64,0x20,0x28,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,
+0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,
+0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x29,0x0d,0x0a,
+
+/* "Content-Length: 1751
+" (18+ bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x4c,0x65,0x6e,0x67,0x74,0x68,0x3a,0x20,
+0x31,0x37,0x35,0x31,0x0d,0x0a,
+/* "Content-Type: text/html
+
+" (27 bytes) */
+0x43,0x6f,0x6e,0x74,0x65,0x6e,0x74,0x2d,0x54,0x79,0x70,0x65,0x3a,0x20,0x74,0x65,
+0x78,0x74,0x2f,0x68,0x74,0x6d,0x6c,0x0d,0x0a,0x0d,0x0a,
+/* raw file data (1751 bytes) */
+0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x0d,0x0a,0x3c,0x68,0x65,0x61,0x64,0x3e,0x3c,0x74,
+0x69,0x74,0x6c,0x65,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,0x69,
+0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,0x50,
+0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x74,0x69,0x74,0x6c,0x65,0x3e,0x3c,0x2f,
+0x68,0x65,0x61,0x64,0x3e,0x0d,0x0a,0x3c,0x62,0x6f,0x64,0x79,0x20,0x62,0x67,0x63,
+0x6f,0x6c,0x6f,0x72,0x3d,0x22,0x77,0x68,0x69,0x74,0x65,0x22,0x20,0x74,0x65,0x78,
+0x74,0x3d,0x22,0x62,0x6c,0x61,0x63,0x6b,0x22,0x3e,0x0d,0x0a,0x0d,0x0a,0x20,0x20,
+0x20,0x20,0x3c,0x74,0x61,0x62,0x6c,0x65,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,
+0x31,0x30,0x30,0x25,0x22,0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x74,
+0x72,0x20,0x76,0x61,0x6c,0x69,0x67,0x6e,0x3d,0x22,0x74,0x6f,0x70,0x22,0x3e,0x3c,
+0x74,0x64,0x20,0x77,0x69,0x64,0x74,0x68,0x3d,0x22,0x38,0x30,0x22,0x3e,0x09,0x20,
+0x20,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x61,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,
+0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,
+0x65,0x2f,0x22,0x3e,0x3c,0x69,0x6d,0x67,0x20,0x73,0x72,0x63,0x3d,0x22,0x2f,0x69,
+0x6d,0x67,0x2f,0x73,0x69,0x63,0x73,0x2e,0x67,0x69,0x66,0x22,0x0d,0x0a,0x09,0x20,
+0x20,0x62,0x6f,0x72,0x64,0x65,0x72,0x3d,0x22,0x30,0x22,0x20,0x61,0x6c,0x74,0x3d,
+0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x20,0x74,0x69,0x74,0x6c,
+0x65,0x3d,0x22,0x53,0x49,0x43,0x53,0x20,0x6c,0x6f,0x67,0x6f,0x22,0x3e,0x3c,0x2f,
+0x61,0x3e,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x20,0x77,0x69,
+0x64,0x74,0x68,0x3d,0x22,0x35,0x30,0x30,0x22,0x3e,0x09,0x20,0x20,0x0d,0x0a,0x09,
+0x20,0x20,0x3c,0x68,0x31,0x3e,0x6c,0x77,0x49,0x50,0x20,0x2d,0x20,0x41,0x20,0x4c,
+0x69,0x67,0x68,0x74,0x77,0x65,0x69,0x67,0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,
+0x50,0x20,0x53,0x74,0x61,0x63,0x6b,0x3c,0x2f,0x68,0x31,0x3e,0x0d,0x0a,0x09,0x20,
+0x20,0x3c,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x54,0x68,0x65,0x20,0x77,
+0x65,0x62,0x20,0x70,0x61,0x67,0x65,0x20,0x79,0x6f,0x75,0x20,0x61,0x72,0x65,0x20,
+0x77,0x61,0x74,0x63,0x68,0x69,0x6e,0x67,0x20,0x77,0x61,0x73,0x20,0x73,0x65,0x72,
+0x76,0x65,0x64,0x20,0x62,0x79,0x20,0x61,0x20,0x73,0x69,0x6d,0x70,0x6c,0x65,0x20,
+0x77,0x65,0x62,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x73,0x65,0x72,0x76,0x65,0x72,
+0x20,0x72,0x75,0x6e,0x6e,0x69,0x6e,0x67,0x20,0x6f,0x6e,0x20,0x74,0x6f,0x70,0x20,
+0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x6c,0x69,0x67,0x68,0x74,0x77,0x65,0x69,0x67,
+0x68,0x74,0x20,0x54,0x43,0x50,0x2f,0x49,0x50,0x20,0x73,0x74,0x61,0x63,0x6b,0x20,
+0x3c,0x61,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,
+0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,
+0x65,0x2f,0x7e,0x61,0x64,0x61,0x6d,0x2f,0x6c,0x77,0x69,0x70,0x2f,0x22,0x3e,0x6c,
+0x77,0x49,0x50,0x3c,0x2f,0x61,0x3e,0x2e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x70,
+0x3e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,
+0x6c,0x77,0x49,0x50,0x20,0x69,0x73,0x20,0x61,0x6e,0x20,0x6f,0x70,0x65,0x6e,0x20,
+0x73,0x6f,0x75,0x72,0x63,0x65,0x20,0x69,0x6d,0x70,0x6c,0x65,0x6d,0x65,0x6e,0x74,
+0x61,0x74,0x69,0x6f,0x6e,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x54,0x43,0x50,
+0x2f,0x49,0x50,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x70,0x72,0x6f,0x74,0x6f,0x63,
+0x6f,0x6c,0x20,0x73,0x75,0x69,0x74,0x65,0x20,0x74,0x68,0x61,0x74,0x20,0x77,0x61,
+0x73,0x20,0x6f,0x72,0x69,0x67,0x69,0x6e,0x61,0x6c,0x6c,0x79,0x20,0x77,0x72,0x69,
+0x74,0x74,0x65,0x6e,0x20,0x62,0x79,0x20,0x3c,0x61,0x0d,0x0a,0x09,0x20,0x20,0x20,
+0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x77,0x77,
+0x77,0x2e,0x73,0x69,0x63,0x73,0x2e,0x73,0x65,0x2f,0x7e,0x61,0x64,0x61,0x6d,0x2f,
+0x6c,0x77,0x69,0x70,0x2f,0x22,0x3e,0x41,0x64,0x61,0x6d,0x20,0x44,0x75,0x6e,0x6b,
+0x65,0x6c,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,
+0x20,0x53,0x77,0x65,0x64,0x69,0x73,0x68,0x20,0x49,0x6e,0x73,0x74,0x69,0x74,0x75,
+0x74,0x65,0x20,0x6f,0x66,0x20,0x43,0x6f,0x6d,0x70,0x75,0x74,0x65,0x72,0x20,0x53,
+0x63,0x69,0x65,0x6e,0x63,0x65,0x3c,0x2f,0x61,0x3e,0x20,0x62,0x75,0x74,0x20,0x6e,
+0x6f,0x77,0x20,0x69,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x62,0x65,0x69,0x6e,
+0x67,0x20,0x61,0x63,0x74,0x69,0x76,0x65,0x6c,0x79,0x20,0x64,0x65,0x76,0x65,0x6c,
+0x6f,0x70,0x65,0x64,0x20,0x62,0x79,0x20,0x61,0x20,0x74,0x65,0x61,0x6d,0x20,0x6f,
+0x66,0x20,0x64,0x65,0x76,0x65,0x6c,0x6f,0x70,0x65,0x72,0x73,0x0d,0x0a,0x09,0x20,
+0x20,0x20,0x20,0x64,0x69,0x73,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x64,0x20,0x77,
+0x6f,0x72,0x6c,0x64,0x2d,0x77,0x69,0x64,0x65,0x2e,0x20,0x53,0x69,0x6e,0x63,0x65,
+0x20,0x69,0x74,0x27,0x73,0x20,0x72,0x65,0x6c,0x65,0x61,0x73,0x65,0x2c,0x20,0x6c,
+0x77,0x49,0x50,0x20,0x68,0x61,0x73,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x73,0x70,
+0x75,0x72,0x72,0x65,0x64,0x20,0x61,0x20,0x6c,0x6f,0x74,0x20,0x6f,0x66,0x20,0x69,
+0x6e,0x74,0x65,0x72,0x65,0x73,0x74,0x20,0x61,0x6e,0x64,0x20,0x68,0x61,0x73,0x20,
+0x62,0x65,0x65,0x6e,0x20,0x70,0x6f,0x72,0x74,0x65,0x64,0x20,0x74,0x6f,0x20,0x73,
+0x65,0x76,0x65,0x72,0x61,0x6c,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x70,0x6c,0x61,
+0x74,0x66,0x6f,0x72,0x6d,0x73,0x20,0x61,0x6e,0x64,0x20,0x6f,0x70,0x65,0x72,0x61,
+0x74,0x69,0x6e,0x67,0x20,0x73,0x79,0x73,0x74,0x65,0x6d,0x73,0x2e,0x20,0x6c,0x77,
+0x49,0x50,0x20,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x75,0x73,0x65,0x64,0x20,0x65,
+0x69,0x74,0x68,0x65,0x72,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x77,0x69,0x74,0x68,
+0x20,0x6f,0x72,0x20,0x77,0x69,0x74,0x68,0x6f,0x75,0x74,0x20,0x61,0x6e,0x20,0x75,
+0x6e,0x64,0x65,0x72,0x6c,0x79,0x69,0x6e,0x67,0x20,0x4f,0x53,0x2e,0x0d,0x0a,0x09,
+0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,0x0a,
+0x09,0x20,0x20,0x20,0x20,0x54,0x68,0x65,0x20,0x66,0x6f,0x63,0x75,0x73,0x20,0x6f,
+0x66,0x20,0x74,0x68,0x65,0x20,0x6c,0x77,0x49,0x50,0x20,0x54,0x43,0x50,0x2f,0x49,
+0x50,0x20,0x69,0x6d,0x70,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x61,0x74,0x69,0x6f,0x6e,
+0x20,0x69,0x73,0x20,0x74,0x6f,0x20,0x72,0x65,0x64,0x75,0x63,0x65,0x0d,0x0a,0x09,
+0x20,0x20,0x20,0x20,0x74,0x68,0x65,0x20,0x52,0x41,0x4d,0x20,0x75,0x73,0x61,0x67,
+0x65,0x20,0x77,0x68,0x69,0x6c,0x65,0x20,0x73,0x74,0x69,0x6c,0x6c,0x20,0x68,0x61,
+0x76,0x69,0x6e,0x67,0x20,0x61,0x20,0x66,0x75,0x6c,0x6c,0x20,0x73,0x63,0x61,0x6c,
+0x65,0x20,0x54,0x43,0x50,0x2e,0x20,0x54,0x68,0x69,0x73,0x0d,0x0a,0x09,0x20,0x20,
+0x20,0x20,0x6d,0x61,0x6b,0x65,0x73,0x20,0x6c,0x77,0x49,0x50,0x20,0x73,0x75,0x69,
+0x74,0x61,0x62,0x6c,0x65,0x20,0x66,0x6f,0x72,0x20,0x75,0x73,0x65,0x20,0x69,0x6e,
+0x20,0x65,0x6d,0x62,0x65,0x64,0x64,0x65,0x64,0x20,0x73,0x79,0x73,0x74,0x65,0x6d,
+0x73,0x20,0x77,0x69,0x74,0x68,0x20,0x74,0x65,0x6e,0x73,0x0d,0x0a,0x09,0x20,0x20,
+0x20,0x20,0x6f,0x66,0x20,0x6b,0x69,0x6c,0x6f,0x62,0x79,0x74,0x65,0x73,0x20,0x6f,
+0x66,0x20,0x66,0x72,0x65,0x65,0x20,0x52,0x41,0x4d,0x20,0x61,0x6e,0x64,0x20,0x72,
+0x6f,0x6f,0x6d,0x20,0x66,0x6f,0x72,0x20,0x61,0x72,0x6f,0x75,0x6e,0x64,0x20,0x34,
+0x30,0x20,0x6b,0x69,0x6c,0x6f,0x62,0x79,0x74,0x65,0x73,0x0d,0x0a,0x09,0x20,0x20,
+0x20,0x20,0x6f,0x66,0x20,0x63,0x6f,0x64,0x65,0x20,0x52,0x4f,0x4d,0x2e,0x0d,0x0a,
+0x09,0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x70,0x3e,0x0d,
+0x0a,0x09,0x20,0x20,0x20,0x20,0x4d,0x6f,0x72,0x65,0x20,0x69,0x6e,0x66,0x6f,0x72,
+0x6d,0x61,0x74,0x69,0x6f,0x6e,0x20,0x61,0x62,0x6f,0x75,0x74,0x20,0x6c,0x77,0x49,
+0x50,0x20,0x63,0x61,0x6e,0x20,0x62,0x65,0x20,0x66,0x6f,0x75,0x6e,0x64,0x20,0x61,
+0x74,0x20,0x74,0x68,0x65,0x20,0x6c,0x77,0x49,0x50,0x0d,0x0a,0x09,0x20,0x20,0x20,
+0x20,0x68,0x6f,0x6d,0x65,0x70,0x61,0x67,0x65,0x20,0x61,0x74,0x20,0x3c,0x61,0x0d,
+0x0a,0x09,0x20,0x20,0x20,0x20,0x68,0x72,0x65,0x66,0x3d,0x22,0x68,0x74,0x74,0x70,
+0x3a,0x2f,0x2f,0x73,0x61,0x76,0x61,0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,
+0x6e,0x75,0x2e,0x6f,0x72,0x67,0x2f,0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,
+0x6c,0x77,0x69,0x70,0x2f,0x22,0x3e,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x73,0x61,
+0x76,0x61,0x6e,0x6e,0x61,0x68,0x2e,0x6e,0x6f,0x6e,0x67,0x6e,0x75,0x2e,0x6f,0x72,
+0x67,0x2f,0x70,0x72,0x6f,0x6a,0x65,0x63,0x74,0x73,0x2f,0x6c,0x77,0x69,0x70,0x2f,
+0x3c,0x2f,0x61,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x6f,0x72,0x20,0x61,0x74,
+0x20,0x74,0x68,0x65,0x20,0x6c,0x77,0x49,0x50,0x20,0x77,0x69,0x6b,0x69,0x20,0x61,
+0x74,0x20,0x3c,0x61,0x0d,0x0a,0x09,0x20,0x20,0x20,0x20,0x68,0x72,0x65,0x66,0x3d,
+0x22,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,0x6c,0x77,0x69,0x70,0x2e,0x77,0x69,0x6b,
+0x69,0x61,0x2e,0x63,0x6f,0x6d,0x2f,0x22,0x3e,0x68,0x74,0x74,0x70,0x3a,0x2f,0x2f,
+0x6c,0x77,0x69,0x70,0x2e,0x77,0x69,0x6b,0x69,0x61,0x2e,0x63,0x6f,0x6d,0x2f,0x3c,
+0x2f,0x61,0x3e,0x2e,0x0d,0x0a,0x09,0x20,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a,0x09,
+0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x74,0x64,0x3e,0x0d,0x0a,0x09,0x20,0x20,0x26,0x6e,
+0x62,0x73,0x70,0x3b,0x0d,0x0a,0x09,0x3c,0x2f,0x74,0x64,0x3e,0x3c,0x2f,0x74,0x72,
+0x3e,0x0d,0x0a,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x2f,0x74,0x61,0x62,0x6c,0x65,
+0x3e,0x0d,0x0a,0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x0d,0x0a,0x3c,0x2f,0x68,0x74,
+0x6d,0x6c,0x3e,0x0d,0x0a,0x0d,0x0a,};
+
+
+
+const struct fsdata_file file__img_sics_gif[] = { {
+file_NULL,
+data__img_sics_gif,
+data__img_sics_gif + 16,
+sizeof(data__img_sics_gif) - 16,
+FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT,
+}};
+
+const struct fsdata_file file__404_html[] = { {
+file__img_sics_gif,
+data__404_html,
+data__404_html + 12,
+sizeof(data__404_html) - 12,
+FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT,
+}};
+
+const struct fsdata_file file__index_html[] = { {
+file__404_html,
+data__index_html,
+data__index_html + 12,
+sizeof(data__index_html) - 12,
+FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT,
+}};
+
+#define FS_ROOT file__index_html
+#define FS_NUMFILES 3
diff --git a/src/apps/http/fsdata.h b/src/apps/http/fsdata.h
new file mode 100644
index 00000000000..d31550d7113
--- /dev/null
+++ b/src/apps/http/fsdata.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <[email protected]>
+ *
+ */
+#ifndef LWIP_FSDATA_H
+#define LWIP_FSDATA_H
+
+#include "lwip/apps/httpd_opts.h"
+#include "lwip/apps/fs.h"
+
+/* THIS FILE IS DEPRECATED AND WILL BE REMOVED IN THE FUTURE */
+/* content was moved to fs.h to simplify #include structure */
+
+#endif /* LWIP_FSDATA_H */
diff --git a/src/apps/http/http_client.c b/src/apps/http/http_client.c
new file mode 100644
index 00000000000..1973e79e723
--- /dev/null
+++ b/src/apps/http/http_client.c
@@ -0,0 +1,911 @@
+/**
+ * @file
+ * HTTP client
+ */
+
+/*
+ * Copyright (c) 2018 Simon Goldschmidt <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt <[email protected]>
+ */
+
+/**
+ * @defgroup httpc HTTP client
+ * @ingroup apps
+ * @todo:
+ * - persistent connections
+ * - select outgoing http version
+ * - optionally follow redirect
+ * - check request uri for invalid characters? (e.g. encode spaces)
+ * - IPv6 support
+ */
+
+#include "lwip/apps/http_client.h"
+
+#include "lwip/altcp_tcp.h"
+#include "lwip/dns.h"
+#include "lwip/debug.h"
+#include "lwip/mem.h"
+#include "lwip/altcp_tls.h"
+#include "lwip/init.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if LWIP_TCP && LWIP_CALLBACK_API
+
+/**
+ * HTTPC_DEBUG: Enable debugging for HTTP client.
+ */
+#ifndef HTTPC_DEBUG
+#define HTTPC_DEBUG LWIP_DBG_OFF
+#endif
+
+/** Set this to 1 to keep server name and uri in request state */
+#ifndef HTTPC_DEBUG_REQUEST
+#define HTTPC_DEBUG_REQUEST 0
+#endif
+
+/** This string is passed in the HTTP header as "User-Agent: " */
+#ifndef HTTPC_CLIENT_AGENT
+#define HTTPC_CLIENT_AGENT "lwIP/" LWIP_VERSION_STRING " (http://savannah.nongnu.org/projects/lwip)"
+#endif
+
+/* the various debug levels for this file */
+#define HTTPC_DEBUG_TRACE (HTTPC_DEBUG | LWIP_DBG_TRACE)
+#define HTTPC_DEBUG_STATE (HTTPC_DEBUG | LWIP_DBG_STATE)
+#define HTTPC_DEBUG_WARN (HTTPC_DEBUG | LWIP_DBG_LEVEL_WARNING)
+#define HTTPC_DEBUG_WARN_STATE (HTTPC_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
+#define HTTPC_DEBUG_SERIOUS (HTTPC_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
+
+#define HTTPC_POLL_INTERVAL 1
+#define HTTPC_POLL_TIMEOUT 30 /* 15 seconds */
+
+#define HTTPC_CONTENT_LEN_INVALID 0xFFFFFFFF
+
+/* GET request basic */
+#define HTTPC_REQ_11 "GET %s HTTP/1.1\r\n" /* URI */\
+ "User-Agent: %s\r\n" /* User-Agent */ \
+ "Accept: */*\r\n" \
+ "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
+ "\r\n"
+#define HTTPC_REQ_11_FORMAT(uri) HTTPC_REQ_11, uri, HTTPC_CLIENT_AGENT
+
+/* GET request with host */
+#define HTTPC_REQ_11_HOST "GET %s HTTP/1.1\r\n" /* URI */\
+ "User-Agent: %s\r\n" /* User-Agent */ \
+ "Accept: */*\r\n" \
+ "Host: %s\r\n" /* server name */ \
+ "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
+ "\r\n"
+#define HTTPC_REQ_11_HOST_FORMAT(uri, srv_name) HTTPC_REQ_11_HOST, uri, HTTPC_CLIENT_AGENT, srv_name
+
+/* GET request with proxy */
+#define HTTPC_REQ_11_PROXY "GET http://%s%s HTTP/1.1\r\n" /* HOST, URI */\
+ "User-Agent: %s\r\n" /* User-Agent */ \
+ "Accept: */*\r\n" \
+ "Host: %s\r\n" /* server name */ \
+ "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
+ "\r\n"
+#define HTTPC_REQ_11_PROXY_FORMAT(host, uri, srv_name) HTTPC_REQ_11_PROXY, host, uri, HTTPC_CLIENT_AGENT, srv_name
+
+/* GET request with proxy (non-default server port) */
+#define HTTPC_REQ_11_PROXY_PORT "GET http://%s:%d%s HTTP/1.1\r\n" /* HOST, host-port, URI */\
+ "User-Agent: %s\r\n" /* User-Agent */ \
+ "Accept: */*\r\n" \
+ "Host: %s\r\n" /* server name */ \
+ "Connection: Close\r\n" /* we don't support persistent connections, yet */ \
+ "\r\n"
+#define HTTPC_REQ_11_PROXY_PORT_FORMAT(host, host_port, uri, srv_name) HTTPC_REQ_11_PROXY_PORT, host, host_port, uri, HTTPC_CLIENT_AGENT, srv_name
+
+typedef enum ehttpc_parse_state {
+ HTTPC_PARSE_WAIT_FIRST_LINE = 0,
+ HTTPC_PARSE_WAIT_HEADERS,
+ HTTPC_PARSE_RX_DATA
+} httpc_parse_state_t;
+
+typedef struct _httpc_state
+{
+ struct altcp_pcb* pcb;
+ ip_addr_t remote_addr;
+ u16_t remote_port;
+ int timeout_ticks;
+ struct pbuf *request;
+ struct pbuf *rx_hdrs;
+ u16_t rx_http_version;
+ u16_t rx_status;
+ altcp_recv_fn recv_fn;
+ const httpc_connection_t *conn_settings;
+ void* callback_arg;
+ u32_t rx_content_len;
+ u32_t hdr_content_len;
+ httpc_parse_state_t parse_state;
+#if HTTPC_DEBUG_REQUEST
+ char* server_name;
+ char* uri;
+#endif
+} httpc_state_t;
+
+/** Free http client state and deallocate all resources within */
+static err_t
+httpc_free_state(httpc_state_t* req)
+{
+ struct altcp_pcb* tpcb;
+
+ if (req->request != NULL) {
+ pbuf_free(req->request);
+ req->request = NULL;
+ }
+ if (req->rx_hdrs != NULL) {
+ pbuf_free(req->rx_hdrs);
+ req->rx_hdrs = NULL;
+ }
+
+ tpcb = req->pcb;
+ mem_free(req);
+ req = NULL;
+
+ if (tpcb != NULL) {
+ err_t r;
+ altcp_arg(tpcb, NULL);
+ altcp_recv(tpcb, NULL);
+ altcp_err(tpcb, NULL);
+ altcp_poll(tpcb, NULL, 0);
+ altcp_sent(tpcb, NULL);
+ r = altcp_close(tpcb);
+ if (r != ERR_OK) {
+ altcp_abort(tpcb);
+ return ERR_ABRT;
+ }
+ }
+ return ERR_OK;
+}
+
+/** Close the connection: call finished callback and free the state */
+static err_t
+httpc_close(httpc_state_t* req, httpc_result_t result, u32_t server_response, err_t err)
+{
+ if (req != NULL) {
+ if (req->conn_settings != NULL) {
+ if (req->conn_settings->result_fn != NULL) {
+ req->conn_settings->result_fn(req->callback_arg, result, req->rx_content_len, server_response, err);
+ }
+ }
+ return httpc_free_state(req);
+ }
+ return ERR_OK;
+}
+
+/** Parse http header response line 1 */
+static err_t
+http_parse_response_status(struct pbuf *p, u16_t *http_version, u16_t *http_status, u16_t *http_status_str_offset)
+{
+ u16_t end1 = pbuf_memfind(p, "\r\n", 2, 0);
+ if (end1 != 0xFFFF) {
+ /* get parts of first line */
+ u16_t space1, space2;
+ space1 = pbuf_memfind(p, " ", 1, 0);
+ if (space1 != 0xFFFF) {
+ if ((pbuf_memcmp(p, 0, "HTTP/", 5) == 0) && (pbuf_get_at(p, 6) == '.')) {
+ char status_num[10];
+ size_t status_num_len;
+ /* parse http version */
+ u16_t version = pbuf_get_at(p, 5) - '0';
+ version <<= 8;
+ version |= pbuf_get_at(p, 7) - '0';
+ *http_version = version;
+
+ /* parse http status number */
+ space2 = pbuf_memfind(p, " ", 1, space1 + 1);
+ if (space2 != 0xFFFF) {
+ *http_status_str_offset = space2 + 1;
+ status_num_len = space2 - space1 - 1;
+ } else {
+ status_num_len = end1 - space1 - 1;
+ }
+ memset(status_num, 0, sizeof(status_num));
+ if (pbuf_copy_partial(p, status_num, (u16_t)status_num_len, space1 + 1) == status_num_len) {
+ int status = atoi(status_num);
+ if ((status > 0) && (status <= 0xFFFF)) {
+ *http_status = (u16_t)status;
+ return ERR_OK;
+ }
+ }
+ }
+ }
+ }
+ return ERR_VAL;
+}
+
+/** Wait for all headers to be received, return its length and content-length (if available) */
+static err_t
+http_wait_headers(struct pbuf *p, u32_t *content_length, u16_t *total_header_len)
+{
+ u16_t end1 = pbuf_memfind(p, "\r\n\r\n", 4, 0);
+ if (end1 < (0xFFFF - 2)) {
+ /* all headers received */
+ /* check if we have a content length (@todo: case insensitive?) */
+ u16_t content_len_hdr;
+ *content_length = HTTPC_CONTENT_LEN_INVALID;
+ *total_header_len = end1 + 4;
+
+ content_len_hdr = pbuf_memfind(p, "Content-Length: ", 16, 0);
+ if (content_len_hdr != 0xFFFF) {
+ u16_t content_len_line_end = pbuf_memfind(p, "\r\n", 2, content_len_hdr);
+ if (content_len_line_end != 0xFFFF) {
+ char content_len_num[16];
+ u16_t content_len_num_len = (u16_t)(content_len_line_end - content_len_hdr - 16);
+ memset(content_len_num, 0, sizeof(content_len_num));
+ if (pbuf_copy_partial(p, content_len_num, content_len_num_len, content_len_hdr + 16) == content_len_num_len) {
+ int len = atoi(content_len_num);
+ if ((len >= 0) && ((u32_t)len < HTTPC_CONTENT_LEN_INVALID)) {
+ *content_length = (u32_t)len;
+ }
+ }
+ }
+ }
+ return ERR_OK;
+ }
+ return ERR_VAL;
+}
+
+/** http client tcp recv callback */
+static err_t
+httpc_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t r)
+{
+ httpc_state_t* req = (httpc_state_t*)arg;
+ LWIP_UNUSED_ARG(r);
+
+ if (p == NULL) {
+ httpc_result_t result;
+ if (req->parse_state != HTTPC_PARSE_RX_DATA) {
+ /* did not get RX data yet */
+ result = HTTPC_RESULT_ERR_CLOSED;
+ } else if ((req->hdr_content_len != HTTPC_CONTENT_LEN_INVALID) &&
+ (req->hdr_content_len != req->rx_content_len)) {
+ /* header has been received with content length but not all data received */
+ result = HTTPC_RESULT_ERR_CONTENT_LEN;
+ } else {
+ /* receiving data and either all data received or no content length header */
+ result = HTTPC_RESULT_OK;
+ }
+ return httpc_close(req, result, req->rx_status, ERR_OK);
+ }
+ if (req->parse_state != HTTPC_PARSE_RX_DATA) {
+ if (req->rx_hdrs == NULL) {
+ req->rx_hdrs = p;
+ } else {
+ pbuf_cat(req->rx_hdrs, p);
+ }
+ if (req->parse_state == HTTPC_PARSE_WAIT_FIRST_LINE) {
+ u16_t status_str_off;
+ err_t err = http_parse_response_status(req->rx_hdrs, &req->rx_http_version, &req->rx_status, &status_str_off);
+ if (err == ERR_OK) {
+ /* don't care status string */
+ req->parse_state = HTTPC_PARSE_WAIT_HEADERS;
+ }
+ }
+ if (req->parse_state == HTTPC_PARSE_WAIT_HEADERS) {
+ u16_t total_header_len;
+ err_t err = http_wait_headers(req->rx_hdrs, &req->hdr_content_len, &total_header_len);
+ if (err == ERR_OK) {
+ struct pbuf *q;
+ /* full header received, send window update for header bytes and call into client callback */
+ altcp_recved(pcb, total_header_len);
+ if (req->conn_settings) {
+ if (req->conn_settings->headers_done_fn) {
+ err = req->conn_settings->headers_done_fn(req, req->callback_arg, req->rx_hdrs, total_header_len, req->hdr_content_len);
+ if (err != ERR_OK) {
+ return httpc_close(req, HTTPC_RESULT_LOCAL_ABORT, req->rx_status, err);
+ }
+ }
+ }
+ /* hide header bytes in pbuf */
+ q = pbuf_free_header(req->rx_hdrs, total_header_len);
+ p = q;
+ req->rx_hdrs = NULL;
+ /* go on with data */
+ req->parse_state = HTTPC_PARSE_RX_DATA;
+ }
+ }
+ }
+ if ((p != NULL) && (req->parse_state == HTTPC_PARSE_RX_DATA)) {
+ req->rx_content_len += p->tot_len;
+ /* received valid data: reset timeout */
+ req->timeout_ticks = HTTPC_POLL_TIMEOUT;
+ if (req->recv_fn != NULL) {
+ /* directly return here: the connection might already be aborted from the callback! */
+ return req->recv_fn(req->callback_arg, pcb, p, r);
+ } else {
+ altcp_recved(pcb, p->tot_len);
+ pbuf_free(p);
+ }
+ }
+ return ERR_OK;
+}
+
+/** http client tcp err callback */
+static void
+httpc_tcp_err(void *arg, err_t err)
+{
+ httpc_state_t* req = (httpc_state_t*)arg;
+ if (req != NULL) {
+ /* pcb has already been deallocated */
+ req->pcb = NULL;
+ httpc_close(req, HTTPC_RESULT_ERR_CLOSED, 0, err);
+ }
+}
+
+/** http client tcp poll callback */
+static err_t
+httpc_tcp_poll(void *arg, struct altcp_pcb *pcb)
+{
+ /* implement timeout */
+ httpc_state_t* req = (httpc_state_t*)arg;
+ LWIP_UNUSED_ARG(pcb);
+ if (req != NULL) {
+ if (req->timeout_ticks) {
+ req->timeout_ticks--;
+ }
+ if (!req->timeout_ticks) {
+ return httpc_close(req, HTTPC_RESULT_ERR_TIMEOUT, 0, ERR_OK);
+ }
+ }
+ return ERR_OK;
+}
+
+/** http client tcp sent callback */
+static err_t
+httpc_tcp_sent(void *arg, struct altcp_pcb *pcb, u16_t len)
+{
+ /* nothing to do here for now */
+ LWIP_UNUSED_ARG(arg);
+ LWIP_UNUSED_ARG(pcb);
+ LWIP_UNUSED_ARG(len);
+ return ERR_OK;
+}
+
+/** http client tcp connected callback */
+static err_t
+httpc_tcp_connected(void *arg, struct altcp_pcb *pcb, err_t err)
+{
+ err_t r;
+ httpc_state_t* req = (httpc_state_t*)arg;
+ LWIP_UNUSED_ARG(pcb);
+ LWIP_UNUSED_ARG(err);
+
+ /* send request; last char is zero termination */
+ r = altcp_write(req->pcb, req->request->payload, req->request->len - 1, TCP_WRITE_FLAG_COPY);
+ if (r != ERR_OK) {
+ /* could not write the single small request -> fail, don't retry */
+ return httpc_close(req, HTTPC_RESULT_ERR_MEM, 0, r);
+ }
+ /* everything written, we can free the request */
+ pbuf_free(req->request);
+ req->request = NULL;
+
+ altcp_output(req->pcb);
+ return ERR_OK;
+}
+
+/** Start the http request when the server IP addr is known */
+static err_t
+httpc_get_internal_addr(httpc_state_t* req, const ip_addr_t *ipaddr)
+{
+ err_t err;
+ LWIP_ASSERT("req != NULL", req != NULL);
+
+ if (&req->remote_addr != ipaddr) {
+ /* fill in remote addr if called externally */
+ req->remote_addr = *ipaddr;
+ }
+
+ err = altcp_connect(req->pcb, &req->remote_addr, req->remote_port, httpc_tcp_connected);
+ if (err == ERR_OK) {
+ return ERR_OK;
+ }
+ LWIP_DEBUGF(HTTPC_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err));
+ return err;
+}
+
+#if LWIP_DNS
+/** DNS callback
+ * If ipaddr is non-NULL, resolving succeeded and the request can be sent, otherwise it failed.
+ */
+static void
+httpc_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg)
+{
+ httpc_state_t* req = (httpc_state_t*)arg;
+ err_t err;
+ httpc_result_t result;
+
+ LWIP_UNUSED_ARG(hostname);
+
+ if (ipaddr != NULL) {
+ err = httpc_get_internal_addr(req, ipaddr);
+ if (err == ERR_OK) {
+ return;
+ }
+ result = HTTPC_RESULT_ERR_CONNECT;
+ } else {
+ LWIP_DEBUGF(HTTPC_DEBUG_WARN_STATE, ("httpc_dns_found: failed to resolve hostname: %s\n",
+ hostname));
+ result = HTTPC_RESULT_ERR_HOSTNAME;
+ err = ERR_ARG;
+ }
+ httpc_close(req, result, 0, err);
+}
+#endif /* LWIP_DNS */
+
+/** Start the http request after converting 'server_name' to ip address (DNS or address string) */
+static err_t
+httpc_get_internal_dns(httpc_state_t* req, const char* server_name)
+{
+ err_t err;
+ LWIP_ASSERT("req != NULL", req != NULL);
+
+#if LWIP_DNS
+ err = dns_gethostbyname(server_name, &req->remote_addr, httpc_dns_found, req);
+#else
+ err = ipaddr_aton(server_name, &req->remote_addr) ? ERR_OK : ERR_ARG;
+#endif
+
+ if (err == ERR_OK) {
+ /* cached or IP-string */
+ err = httpc_get_internal_addr(req, &req->remote_addr);
+ } else if (err == ERR_INPROGRESS) {
+ return ERR_OK;
+ }
+ return err;
+}
+
+static int
+httpc_create_request_string(const httpc_connection_t *settings, const char* server_name, int server_port, const char* uri,
+ int use_host, char *buffer, size_t buffer_size)
+{
+ if (settings->use_proxy) {
+ LWIP_ASSERT("server_name != NULL", server_name != NULL);
+ if (server_port != HTTP_DEFAULT_PORT) {
+ return snprintf(buffer, buffer_size, HTTPC_REQ_11_PROXY_PORT_FORMAT(server_name, server_port, uri, server_name));
+ } else {
+ return snprintf(buffer, buffer_size, HTTPC_REQ_11_PROXY_FORMAT(server_name, uri, server_name));
+ }
+ } else if (use_host) {
+ LWIP_ASSERT("server_name != NULL", server_name != NULL);
+ return snprintf(buffer, buffer_size, HTTPC_REQ_11_HOST_FORMAT(uri, server_name));
+ } else {
+ return snprintf(buffer, buffer_size, HTTPC_REQ_11_FORMAT(uri));
+ }
+}
+
+/** Initialize the connection struct */
+static err_t
+httpc_init_connection_common(httpc_state_t **connection, const httpc_connection_t *settings, const char* server_name,
+ u16_t server_port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg, int use_host)
+{
+ size_t alloc_len;
+ mem_size_t mem_alloc_len;
+ int req_len, req_len2;
+ httpc_state_t *req;
+#if HTTPC_DEBUG_REQUEST
+ size_t server_name_len, uri_len;
+#endif
+
+ LWIP_ASSERT("uri != NULL", uri != NULL);
+
+ /* get request len */
+ req_len = httpc_create_request_string(settings, server_name, server_port, uri, use_host, NULL, 0);
+ if ((req_len < 0) || (req_len > 0xFFFF)) {
+ return ERR_VAL;
+ }
+ /* alloc state and request in one block */
+ alloc_len = sizeof(httpc_state_t);
+#if HTTPC_DEBUG_REQUEST
+ server_name_len = server_name ? strlen(server_name) : 0;
+ uri_len = strlen(uri);
+ alloc_len += server_name_len + 1 + uri_len + 1;
+#endif
+ mem_alloc_len = (mem_size_t)alloc_len;
+ if ((mem_alloc_len < alloc_len) || (req_len + 1 > 0xFFFF)) {
+ return ERR_VAL;
+ }
+
+ req = (httpc_state_t*)mem_malloc((mem_size_t)alloc_len);
+ if(req == NULL) {
+ return ERR_MEM;
+ }
+ memset(req, 0, sizeof(httpc_state_t));
+ req->timeout_ticks = HTTPC_POLL_TIMEOUT;
+ req->request = pbuf_alloc(PBUF_RAW, (u16_t)(req_len + 1), PBUF_RAM);
+ if (req->request == NULL) {
+ httpc_free_state(req);
+ return ERR_MEM;
+ }
+ if (req->request->next != NULL) {
+ /* need a pbuf in one piece */
+ httpc_free_state(req);
+ return ERR_MEM;
+ }
+ req->hdr_content_len = HTTPC_CONTENT_LEN_INVALID;
+#if HTTPC_DEBUG_REQUEST
+ req->server_name = (char*)(req + 1);
+ if (server_name) {
+ memcpy(req->server_name, server_name, server_name_len + 1);
+ }
+ req->uri = req->server_name + server_name_len + 1;
+ memcpy(req->uri, uri, uri_len + 1);
+#endif
+ req->pcb = altcp_new(settings->altcp_allocator);
+ if(req->pcb == NULL) {
+ httpc_free_state(req);
+ return ERR_MEM;
+ }
+ req->remote_port = settings->use_proxy ? settings->proxy_port : server_port;
+ altcp_arg(req->pcb, req);
+ altcp_recv(req->pcb, httpc_tcp_recv);
+ altcp_err(req->pcb, httpc_tcp_err);
+ altcp_poll(req->pcb, httpc_tcp_poll, HTTPC_POLL_INTERVAL);
+ altcp_sent(req->pcb, httpc_tcp_sent);
+
+ /* set up request buffer */
+ req_len2 = httpc_create_request_string(settings, server_name, server_port, uri, use_host,
+ (char *)req->request->payload, req_len + 1);
+ if (req_len2 != req_len) {
+ httpc_free_state(req);
+ return ERR_VAL;
+ }
+
+ req->recv_fn = recv_fn;
+ req->conn_settings = settings;
+ req->callback_arg = callback_arg;
+
+ *connection = req;
+ return ERR_OK;
+}
+
+/**
+ * Initialize the connection struct
+ */
+static err_t
+httpc_init_connection(httpc_state_t **connection, const httpc_connection_t *settings, const char* server_name,
+ u16_t server_port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg)
+{
+ return httpc_init_connection_common(connection, settings, server_name, server_port, uri, recv_fn, callback_arg, 1);
+}
+
+
+/**
+ * Initialize the connection struct (from IP address)
+ */
+static err_t
+httpc_init_connection_addr(httpc_state_t **connection, const httpc_connection_t *settings,
+ const ip_addr_t* server_addr, u16_t server_port, const char* uri,
+ altcp_recv_fn recv_fn, void* callback_arg)
+{
+ char *server_addr_str = ipaddr_ntoa(server_addr);
+ if (server_addr_str == NULL) {
+ return ERR_VAL;
+ }
+ return httpc_init_connection_common(connection, settings, server_addr_str, server_port, uri,
+ recv_fn, callback_arg, 1);
+}
+
+/**
+ * @ingroup httpc
+ * HTTP client API: get a file by passing server IP address
+ *
+ * @param server_addr IP address of the server to connect
+ * @param port tcp port of the server
+ * @param uri uri to get from the server, remember leading "/"!
+ * @param settings connection settings (callbacks, proxy, etc.)
+ * @param recv_fn the http body (not the headers) are passed to this callback
+ * @param callback_arg argument passed to all the callbacks
+ * @param connection retreives the connection handle (to match in callbacks)
+ * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
+ * or an error code
+ */
+err_t
+httpc_get_file(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings,
+ altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection)
+{
+ err_t err;
+ httpc_state_t* req;
+
+ LWIP_ERROR("invalid parameters", (server_addr != NULL) && (uri != NULL) && (recv_fn != NULL), return ERR_ARG;);
+
+ err = httpc_init_connection_addr(&req, settings, server_addr, port,
+ uri, recv_fn, callback_arg);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ if (settings->use_proxy) {
+ err = httpc_get_internal_addr(req, &settings->proxy_addr);
+ } else {
+ err = httpc_get_internal_addr(req, server_addr);
+ }
+ if(err != ERR_OK) {
+ httpc_free_state(req);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+ return ERR_OK;
+}
+
+/**
+ * @ingroup httpc
+ * HTTP client API: get a file by passing server name as string (DNS name or IP address string)
+ *
+ * @param server_name server name as string (DNS name or IP address string)
+ * @param port tcp port of the server
+ * @param uri uri to get from the server, remember leading "/"!
+ * @param settings connection settings (callbacks, proxy, etc.)
+ * @param recv_fn the http body (not the headers) are passed to this callback
+ * @param callback_arg argument passed to all the callbacks
+ * @param connection retreives the connection handle (to match in callbacks)
+ * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
+ * or an error code
+ */
+err_t
+httpc_get_file_dns(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings,
+ altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection)
+{
+ err_t err;
+ httpc_state_t* req;
+
+ LWIP_ERROR("invalid parameters", (server_name != NULL) && (uri != NULL) && (recv_fn != NULL), return ERR_ARG;);
+
+ err = httpc_init_connection(&req, settings, server_name, port, uri, recv_fn, callback_arg);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ if (settings->use_proxy) {
+ err = httpc_get_internal_addr(req, &settings->proxy_addr);
+ } else {
+ err = httpc_get_internal_dns(req, server_name);
+ }
+ if(err != ERR_OK) {
+ httpc_free_state(req);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+ return ERR_OK;
+}
+
+#if LWIP_HTTPC_HAVE_FILE_IO
+/* Implementation to disk via fopen/fwrite/fclose follows */
+
+typedef struct _httpc_filestate
+{
+ const char* local_file_name;
+ FILE *file;
+ httpc_connection_t settings;
+ const httpc_connection_t *client_settings;
+ void *callback_arg;
+} httpc_filestate_t;
+
+static void httpc_fs_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len,
+ u32_t srv_res, err_t err);
+
+/** Initialize http client state for download to file system */
+static err_t
+httpc_fs_init(httpc_filestate_t **filestate_out, const char* local_file_name,
+ const httpc_connection_t *settings, void* callback_arg)
+{
+ httpc_filestate_t *filestate;
+ size_t file_len, alloc_len;
+ FILE *f;
+
+ file_len = strlen(local_file_name);
+ alloc_len = sizeof(httpc_filestate_t) + file_len + 1;
+
+ filestate = (httpc_filestate_t *)mem_malloc((mem_size_t)alloc_len);
+ if (filestate == NULL) {
+ return ERR_MEM;
+ }
+ memset(filestate, 0, sizeof(httpc_filestate_t));
+ filestate->local_file_name = (const char *)(filestate + 1);
+ memcpy((char *)(filestate + 1), local_file_name, file_len + 1);
+ filestate->file = NULL;
+ filestate->client_settings = settings;
+ filestate->callback_arg = callback_arg;
+ /* copy client settings but override result callback */
+ memcpy(&filestate->settings, settings, sizeof(httpc_connection_t));
+ filestate->settings.result_fn = httpc_fs_result;
+
+ f = fopen(local_file_name, "wb");
+ if(f == NULL) {
+ /* could not open file */
+ mem_free(filestate);
+ return ERR_VAL;
+ }
+ filestate->file = f;
+ *filestate_out = filestate;
+ return ERR_OK;
+}
+
+/** Free http client state for download to file system */
+static void
+httpc_fs_free(httpc_filestate_t *filestate)
+{
+ if (filestate != NULL) {
+ if (filestate->file != NULL) {
+ fclose(filestate->file);
+ filestate->file = NULL;
+ }
+ mem_free(filestate);
+ }
+}
+
+/** Connection closed (success or error) */
+static void
+httpc_fs_result(void *arg, httpc_result_t httpc_result, u32_t rx_content_len,
+ u32_t srv_res, err_t err)
+{
+ httpc_filestate_t *filestate = (httpc_filestate_t *)arg;
+ if (filestate != NULL) {
+ if (filestate->client_settings->result_fn != NULL) {
+ filestate->client_settings->result_fn(filestate->callback_arg, httpc_result, rx_content_len,
+ srv_res, err);
+ }
+ httpc_fs_free(filestate);
+ }
+}
+
+/** tcp recv callback */
+static err_t
+httpc_fs_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err)
+{
+ httpc_filestate_t *filestate = (httpc_filestate_t*)arg;
+ struct pbuf* q;
+ LWIP_UNUSED_ARG(err);
+
+ LWIP_ASSERT("p != NULL", p != NULL);
+
+ for (q = p; q != NULL; q = q->next) {
+ fwrite(q->payload, 1, q->len, filestate->file);
+ }
+ altcp_recved(pcb, p->tot_len);
+ pbuf_free(p);
+ return ERR_OK;
+}
+
+/**
+ * @ingroup httpc
+ * HTTP client API: get a file to disk by passing server IP address
+ *
+ * @param server_addr IP address of the server to connect
+ * @param port tcp port of the server
+ * @param uri uri to get from the server, remember leading "/"!
+ * @param settings connection settings (callbacks, proxy, etc.)
+ * @param callback_arg argument passed to all the callbacks
+ * @param connection retreives the connection handle (to match in callbacks)
+ * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
+ * or an error code
+ */
+err_t
+httpc_get_file_to_disk(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings,
+ void* callback_arg, const char* local_file_name, httpc_state_t **connection)
+{
+ err_t err;
+ httpc_state_t* req;
+ httpc_filestate_t *filestate;
+
+ LWIP_ERROR("invalid parameters", (server_addr != NULL) && (uri != NULL) && (local_file_name != NULL), return ERR_ARG;);
+
+ err = httpc_fs_init(&filestate, local_file_name, settings, callback_arg);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ err = httpc_init_connection_addr(&req, &filestate->settings, server_addr, port,
+ uri, httpc_fs_tcp_recv, filestate);
+ if (err != ERR_OK) {
+ httpc_fs_free(filestate);
+ return err;
+ }
+
+ if (settings->use_proxy) {
+ err = httpc_get_internal_addr(req, &settings->proxy_addr);
+ } else {
+ err = httpc_get_internal_addr(req, server_addr);
+ }
+ if(err != ERR_OK) {
+ httpc_fs_free(filestate);
+ httpc_free_state(req);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+ return ERR_OK;
+}
+
+/**
+ * @ingroup httpc
+ * HTTP client API: get a file to disk by passing server name as string (DNS name or IP address string)
+ *
+ * @param server_name server name as string (DNS name or IP address string)
+ * @param port tcp port of the server
+ * @param uri uri to get from the server, remember leading "/"!
+ * @param settings connection settings (callbacks, proxy, etc.)
+ * @param callback_arg argument passed to all the callbacks
+ * @param connection retreives the connection handle (to match in callbacks)
+ * @return ERR_OK if starting the request succeeds (callback_fn will be called later)
+ * or an error code
+ */
+err_t
+httpc_get_file_dns_to_disk(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings,
+ void* callback_arg, const char* local_file_name, httpc_state_t **connection)
+{
+ err_t err;
+ httpc_state_t* req;
+ httpc_filestate_t *filestate;
+
+ LWIP_ERROR("invalid parameters", (server_name != NULL) && (uri != NULL) && (local_file_name != NULL), return ERR_ARG;);
+
+ err = httpc_fs_init(&filestate, local_file_name, settings, callback_arg);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ err = httpc_init_connection(&req, &filestate->settings, server_name, port,
+ uri, httpc_fs_tcp_recv, filestate);
+ if (err != ERR_OK) {
+ httpc_fs_free(filestate);
+ return err;
+ }
+
+ if (settings->use_proxy) {
+ err = httpc_get_internal_addr(req, &settings->proxy_addr);
+ } else {
+ err = httpc_get_internal_dns(req, server_name);
+ }
+ if(err != ERR_OK) {
+ httpc_fs_free(filestate);
+ httpc_free_state(req);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+ return ERR_OK;
+}
+#endif /* LWIP_HTTPC_HAVE_FILE_IO */
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API */
diff --git a/src/apps/http/httpd.c b/src/apps/http/httpd.c
new file mode 100644
index 00000000000..9477e855e77
--- /dev/null
+++ b/src/apps/http/httpd.c
@@ -0,0 +1,2770 @@
+/**
+ * @file
+ * LWIP HTTP server implementation
+ */
+
+/*
+ * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <[email protected]>
+ * Simon Goldschmidt
+ *
+ */
+
+/**
+ * @defgroup httpd HTTP server
+ * @ingroup apps
+ *
+ * This httpd supports for a
+ * rudimentary server-side-include facility which will replace tags of the form
+ * <!--#tag--> in any file whose extension is .shtml, .shtm or .ssi with
+ * strings provided by an include handler whose pointer is provided to the
+ * module via function http_set_ssi_handler().
+ * Additionally, a simple common
+ * gateway interface (CGI) handling mechanism has been added to allow clients
+ * to hook functions to particular request URIs.
+ *
+ * To enable SSI support, define label LWIP_HTTPD_SSI in lwipopts.h.
+ * To enable CGI support, define label LWIP_HTTPD_CGI in lwipopts.h.
+ *
+ * By default, the server assumes that HTTP headers are already present in
+ * each file stored in the file system. By defining LWIP_HTTPD_DYNAMIC_HEADERS in
+ * lwipopts.h, this behavior can be changed such that the server inserts the
+ * headers automatically based on the extension of the file being served. If
+ * this mode is used, be careful to ensure that the file system image used
+ * does not already contain the header information.
+ *
+ * File system images without headers can be created using the makefsfile
+ * tool with the -h command line option.
+ *
+ *
+ * Notes about valid SSI tags
+ * --------------------------
+ *
+ * The following assumptions are made about tags used in SSI markers:
+ *
+ * 1. No tag may contain '-' or whitespace characters within the tag name.
+ * 2. Whitespace is allowed between the tag leadin "<!--#" and the start of
+ * the tag name and between the tag name and the leadout string "-->".
+ * 3. The maximum tag name length is LWIP_HTTPD_MAX_TAG_NAME_LEN, currently 8 characters.
+ *
+ * Notes on CGI usage
+ * ------------------
+ *
+ * The simple CGI support offered here works with GET method requests only
+ * and can handle up to 16 parameters encoded into the URI. The handler
+ * function may not write directly to the HTTP output but must return a
+ * filename that the HTTP server will send to the browser as a response to
+ * the incoming CGI request.
+ *
+ *
+ *
+ * The list of supported file types is quite short, so if makefsdata complains
+ * about an unknown extension, make sure to add it (and its doctype) to
+ * the 'g_psHTTPHeaders' list.
+ */
+#include "lwip/init.h"
+#include "lwip/apps/httpd.h"
+#include "lwip/debug.h"
+#include "lwip/stats.h"
+#include "lwip/apps/fs.h"
+#include "httpd_structs.h"
+#include "lwip/def.h"
+
+#include "lwip/altcp.h"
+#include "lwip/altcp_tcp.h"
+#if HTTPD_ENABLE_HTTPS
+#include "lwip/altcp_tls.h"
+#endif
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+#if LWIP_HTTPD_TIMING
+#include "lwip/sys.h"
+#endif /* LWIP_HTTPD_TIMING */
+
+#include <string.h> /* memset */
+#include <stdlib.h> /* atoi */
+#include <stdio.h>
+
+#if LWIP_TCP && LWIP_CALLBACK_API
+
+/** Minimum length for a valid HTTP/0.9 request: "GET /\r\n" -> 7 bytes */
+#define MIN_REQ_LEN 7
+
+#define CRLF "\r\n"
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+#define HTTP11_CONNECTIONKEEPALIVE "Connection: keep-alive"
+#define HTTP11_CONNECTIONKEEPALIVE2 "Connection: Keep-Alive"
+#endif
+
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+#define HTTP_IS_DYNAMIC_FILE(hs) ((hs)->buf != NULL)
+#else
+#define HTTP_IS_DYNAMIC_FILE(hs) 0
+#endif
+
+/* This defines checks whether tcp_write has to copy data or not */
+
+#ifndef HTTP_IS_DATA_VOLATILE
+/** tcp_write does not have to copy data when sent from rom-file-system directly */
+#define HTTP_IS_DATA_VOLATILE(hs) (HTTP_IS_DYNAMIC_FILE(hs) ? TCP_WRITE_FLAG_COPY : 0)
+#endif
+/** Default: dynamic headers are sent from ROM (non-dynamic headers are handled like file data) */
+#ifndef HTTP_IS_HDR_VOLATILE
+#define HTTP_IS_HDR_VOLATILE(hs, ptr) 0
+#endif
+
+/* Return values for http_send_*() */
+#define HTTP_DATA_TO_SEND_FREED 3
+#define HTTP_DATA_TO_SEND_BREAK 2
+#define HTTP_DATA_TO_SEND_CONTINUE 1
+#define HTTP_NO_DATA_TO_SEND 0
+
+typedef struct {
+ const char *name;
+ u8_t shtml;
+} default_filename;
+
+static const default_filename httpd_default_filenames[] = {
+ {"/index.shtml", 1 },
+ {"/index.ssi", 1 },
+ {"/index.shtm", 1 },
+ {"/index.html", 0 },
+ {"/index.htm", 0 }
+};
+
+#define NUM_DEFAULT_FILENAMES LWIP_ARRAYSIZE(httpd_default_filenames)
+
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+/** HTTP request is copied here from pbufs for simple parsing */
+static char httpd_req_buf[LWIP_HTTPD_MAX_REQ_LENGTH + 1];
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+
+#if LWIP_HTTPD_SUPPORT_POST
+#if LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN > LWIP_HTTPD_MAX_REQUEST_URI_LEN
+#define LWIP_HTTPD_URI_BUF_LEN LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN
+#endif
+#endif
+#ifndef LWIP_HTTPD_URI_BUF_LEN
+#define LWIP_HTTPD_URI_BUF_LEN LWIP_HTTPD_MAX_REQUEST_URI_LEN
+#endif
+#if LWIP_HTTPD_URI_BUF_LEN
+/* Filename for response file to send when POST is finished or
+ * search for default files when a directory is requested. */
+static char http_uri_buf[LWIP_HTTPD_URI_BUF_LEN + 1];
+#endif
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+/* The number of individual strings that comprise the headers sent before each
+ * requested file.
+ */
+#define NUM_FILE_HDR_STRINGS 5
+#define HDR_STRINGS_IDX_HTTP_STATUS 0 /* e.g. "HTTP/1.0 200 OK\r\n" */
+#define HDR_STRINGS_IDX_SERVER_NAME 1 /* e.g. "Server: "HTTPD_SERVER_AGENT"\r\n" */
+#define HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE 2 /* e.g. "Content-Length: xy\r\n" and/or "Connection: keep-alive\r\n" */
+#define HDR_STRINGS_IDX_CONTENT_LEN_NR 3 /* the byte count, when content-length is used */
+#define HDR_STRINGS_IDX_CONTENT_TYPE 4 /* the content type (or default answer content type including default document) */
+
+/* The dynamically generated Content-Length buffer needs space for CRLF + NULL */
+#define LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET 3
+#ifndef LWIP_HTTPD_MAX_CONTENT_LEN_SIZE
+/* The dynamically generated Content-Length buffer shall be able to work with
+ ~953 MB (9 digits) */
+#define LWIP_HTTPD_MAX_CONTENT_LEN_SIZE (9 + LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET)
+#endif
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+#if LWIP_HTTPD_SSI
+
+#define HTTPD_LAST_TAG_PART 0xFFFF
+
+enum tag_check_state {
+ TAG_NONE, /* Not processing an SSI tag */
+ TAG_LEADIN, /* Tag lead in "<!--#" being processed */
+ TAG_FOUND, /* Tag name being read, looking for lead-out start */
+ TAG_LEADOUT, /* Tag lead out "-->" being processed */
+ TAG_SENDING /* Sending tag replacement string */
+};
+
+struct http_ssi_state {
+ const char *parsed; /* Pointer to the first unparsed byte in buf. */
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ const char *tag_started;/* Pointer to the first opening '<' of the tag. */
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG */
+ const char *tag_end; /* Pointer to char after the closing '>' of the tag. */
+ u32_t parse_left; /* Number of unparsed bytes in buf. */
+ u16_t tag_index; /* Counter used by tag parsing state machine */
+ u16_t tag_insert_len; /* Length of insert in string tag_insert */
+#if LWIP_HTTPD_SSI_MULTIPART
+ u16_t tag_part; /* Counter passed to and changed by tag insertion function to insert multiple times */
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+ u8_t tag_type; /* index into http_ssi_tag_desc array */
+ u8_t tag_name_len; /* Length of the tag name in string tag_name */
+ char tag_name[LWIP_HTTPD_MAX_TAG_NAME_LEN + 1]; /* Last tag name extracted */
+ char tag_insert[LWIP_HTTPD_MAX_TAG_INSERT_LEN + 1]; /* Insert string for tag_name */
+ enum tag_check_state tag_state; /* State of the tag processor */
+};
+
+struct http_ssi_tag_description {
+ const char *lead_in;
+ const char *lead_out;
+};
+
+#endif /* LWIP_HTTPD_SSI */
+
+struct http_state {
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+ struct http_state *next;
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+ struct fs_file file_handle;
+ struct fs_file *handle;
+ const char *file; /* Pointer to first unsent byte in buf. */
+
+ struct altcp_pcb *pcb;
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ struct pbuf *req;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+ char *buf; /* File read buffer. */
+ int buf_len; /* Size of file read buffer, buf. */
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+ u32_t left; /* Number of unsent bytes in buf. */
+ u8_t retries;
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ u8_t keepalive;
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+#if LWIP_HTTPD_SSI
+ struct http_ssi_state *ssi;
+#endif /* LWIP_HTTPD_SSI */
+#if LWIP_HTTPD_CGI
+ char *params[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Params extracted from the request URI */
+ char *param_vals[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Values for each extracted param */
+#endif /* LWIP_HTTPD_CGI */
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+ const char *hdrs[NUM_FILE_HDR_STRINGS]; /* HTTP headers to be sent. */
+ char hdr_content_len[LWIP_HTTPD_MAX_CONTENT_LEN_SIZE];
+ u16_t hdr_pos; /* The position of the first unsent header byte in the
+ current string */
+ u16_t hdr_index; /* The index of the hdr string currently being sent. */
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+#if LWIP_HTTPD_TIMING
+ u32_t time_started;
+#endif /* LWIP_HTTPD_TIMING */
+#if LWIP_HTTPD_SUPPORT_POST
+ u32_t post_content_len_left;
+#if LWIP_HTTPD_POST_MANUAL_WND
+ u32_t unrecved_bytes;
+ u8_t no_auto_wnd;
+ u8_t post_finished;
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+#endif /* LWIP_HTTPD_SUPPORT_POST*/
+};
+
+#if HTTPD_USE_MEM_POOL
+LWIP_MEMPOOL_DECLARE(HTTPD_STATE, MEMP_NUM_PARALLEL_HTTPD_CONNS, sizeof(struct http_state), "HTTPD_STATE")
+#if LWIP_HTTPD_SSI
+LWIP_MEMPOOL_DECLARE(HTTPD_SSI_STATE, MEMP_NUM_PARALLEL_HTTPD_SSI_CONNS, sizeof(struct http_ssi_state), "HTTPD_SSI_STATE")
+#define HTTP_FREE_SSI_STATE(x) LWIP_MEMPOOL_FREE(HTTPD_SSI_STATE, (x))
+#define HTTP_ALLOC_SSI_STATE() (struct http_ssi_state *)LWIP_MEMPOOL_ALLOC(HTTPD_SSI_STATE)
+#endif /* LWIP_HTTPD_SSI */
+#define HTTP_ALLOC_HTTP_STATE() (struct http_state *)LWIP_MEMPOOL_ALLOC(HTTPD_STATE)
+#define HTTP_FREE_HTTP_STATE(x) LWIP_MEMPOOL_FREE(HTTPD_STATE, (x))
+#else /* HTTPD_USE_MEM_POOL */
+#define HTTP_ALLOC_HTTP_STATE() (struct http_state *)mem_malloc(sizeof(struct http_state))
+#define HTTP_FREE_HTTP_STATE(x) mem_free(x)
+#if LWIP_HTTPD_SSI
+#define HTTP_ALLOC_SSI_STATE() (struct http_ssi_state *)mem_malloc(sizeof(struct http_ssi_state))
+#define HTTP_FREE_SSI_STATE(x) mem_free(x)
+#endif /* LWIP_HTTPD_SSI */
+#endif /* HTTPD_USE_MEM_POOL */
+
+static err_t http_close_conn(struct altcp_pcb *pcb, struct http_state *hs);
+static err_t http_close_or_abort_conn(struct altcp_pcb *pcb, struct http_state *hs, u8_t abort_conn);
+static err_t http_find_file(struct http_state *hs, const char *uri, int is_09);
+static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri, u8_t tag_check, char *params);
+static err_t http_poll(void *arg, struct altcp_pcb *pcb);
+static u8_t http_check_eof(struct altcp_pcb *pcb, struct http_state *hs);
+#if LWIP_HTTPD_FS_ASYNC_READ
+static void http_continue(void *connection);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+
+#if LWIP_HTTPD_SSI
+/* SSI insert handler function pointer. */
+static tSSIHandler httpd_ssi_handler;
+#if !LWIP_HTTPD_SSI_RAW
+static int httpd_num_tags;
+static const char **httpd_tags;
+#endif /* !LWIP_HTTPD_SSI_RAW */
+
+/* Define the available tag lead-ins and corresponding lead-outs.
+ * ATTENTION: for the algorithm below using this array, it is essential
+ * that the lead in differs in the first character! */
+const struct http_ssi_tag_description http_ssi_tag_desc[] = {
+ {"<!--#", "-->"},
+ {"/*#", "*/"}
+};
+
+#endif /* LWIP_HTTPD_SSI */
+
+#if LWIP_HTTPD_CGI
+/* CGI handler information */
+static const tCGI *httpd_cgis;
+static int httpd_num_cgis;
+static int http_cgi_paramcount;
+#define http_cgi_params hs->params
+#define http_cgi_param_vals hs->param_vals
+#elif LWIP_HTTPD_CGI_SSI
+static char *http_cgi_params[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Params extracted from the request URI */
+static char *http_cgi_param_vals[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Values for each extracted param */
+#endif /* LWIP_HTTPD_CGI */
+
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+/** global list of active HTTP connections, use to kill the oldest when
+ running out of memory */
+static struct http_state *http_connections;
+
+static void
+http_add_connection(struct http_state *hs)
+{
+ /* add the connection to the list */
+ hs->next = http_connections;
+ http_connections = hs;
+}
+
+static void
+http_remove_connection(struct http_state *hs)
+{
+ /* take the connection off the list */
+ if (http_connections) {
+ if (http_connections == hs) {
+ http_connections = hs->next;
+ } else {
+ struct http_state *last;
+ for (last = http_connections; last->next != NULL; last = last->next) {
+ if (last->next == hs) {
+ last->next = hs->next;
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void
+http_kill_oldest_connection(u8_t ssi_required)
+{
+ struct http_state *hs = http_connections;
+ struct http_state *hs_free_next = NULL;
+ while (hs && hs->next) {
+#if LWIP_HTTPD_SSI
+ if (ssi_required) {
+ if (hs->next->ssi != NULL) {
+ hs_free_next = hs;
+ }
+ } else
+#else /* LWIP_HTTPD_SSI */
+ LWIP_UNUSED_ARG(ssi_required);
+#endif /* LWIP_HTTPD_SSI */
+ {
+ hs_free_next = hs;
+ }
+ LWIP_ASSERT("broken list", hs != hs->next);
+ hs = hs->next;
+ }
+ if (hs_free_next != NULL) {
+ LWIP_ASSERT("hs_free_next->next != NULL", hs_free_next->next != NULL);
+ LWIP_ASSERT("hs_free_next->next->pcb != NULL", hs_free_next->next->pcb != NULL);
+ /* send RST when killing a connection because of memory shortage */
+ http_close_or_abort_conn(hs_free_next->next->pcb, hs_free_next->next, 1); /* this also unlinks the http_state from the list */
+ }
+}
+#else /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+
+#define http_add_connection(hs)
+#define http_remove_connection(hs)
+
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+
+#if LWIP_HTTPD_SSI
+/** Allocate as struct http_ssi_state. */
+static struct http_ssi_state *
+http_ssi_state_alloc(void)
+{
+ struct http_ssi_state *ret = HTTP_ALLOC_SSI_STATE();
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+ if (ret == NULL) {
+ http_kill_oldest_connection(1);
+ ret = HTTP_ALLOC_SSI_STATE();
+ }
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+ if (ret != NULL) {
+ memset(ret, 0, sizeof(struct http_ssi_state));
+ }
+ return ret;
+}
+
+/** Free a struct http_ssi_state. */
+static void
+http_ssi_state_free(struct http_ssi_state *ssi)
+{
+ if (ssi != NULL) {
+ HTTP_FREE_SSI_STATE(ssi);
+ }
+}
+#endif /* LWIP_HTTPD_SSI */
+
+/** Initialize a struct http_state.
+ */
+static void
+http_state_init(struct http_state *hs)
+{
+ /* Initialize the structure. */
+ memset(hs, 0, sizeof(struct http_state));
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+ /* Indicate that the headers are not yet valid */
+ hs->hdr_index = NUM_FILE_HDR_STRINGS;
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+}
+
+/** Allocate a struct http_state. */
+static struct http_state *
+http_state_alloc(void)
+{
+ struct http_state *ret = HTTP_ALLOC_HTTP_STATE();
+#if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED
+ if (ret == NULL) {
+ http_kill_oldest_connection(0);
+ ret = HTTP_ALLOC_HTTP_STATE();
+ }
+#endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */
+ if (ret != NULL) {
+ http_state_init(ret);
+ http_add_connection(ret);
+ }
+ return ret;
+}
+
+/** Free a struct http_state.
+ * Also frees the file data if dynamic.
+ */
+static void
+http_state_eof(struct http_state *hs)
+{
+ if (hs->handle) {
+#if LWIP_HTTPD_TIMING
+ u32_t ms_needed = sys_now() - hs->time_started;
+ u32_t needed = LWIP_MAX(1, (ms_needed / 100));
+ LWIP_DEBUGF(HTTPD_DEBUG_TIMING, ("httpd: needed %"U32_F" ms to send file of %d bytes -> %"U32_F" bytes/sec\n",
+ ms_needed, hs->handle->len, ((((u32_t)hs->handle->len) * 10) / needed)));
+#endif /* LWIP_HTTPD_TIMING */
+ fs_close(hs->handle);
+ hs->handle = NULL;
+ }
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+ if (hs->buf != NULL) {
+ mem_free(hs->buf);
+ hs->buf = NULL;
+ }
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+#if LWIP_HTTPD_SSI
+ if (hs->ssi) {
+ http_ssi_state_free(hs->ssi);
+ hs->ssi = NULL;
+ }
+#endif /* LWIP_HTTPD_SSI */
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ if (hs->req) {
+ pbuf_free(hs->req);
+ hs->req = NULL;
+ }
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+}
+
+/** Free a struct http_state.
+ * Also frees the file data if dynamic.
+ */
+static void
+http_state_free(struct http_state *hs)
+{
+ if (hs != NULL) {
+ http_state_eof(hs);
+ http_remove_connection(hs);
+ HTTP_FREE_HTTP_STATE(hs);
+ }
+}
+
+/** Call tcp_write() in a loop trying smaller and smaller length
+ *
+ * @param pcb altcp_pcb to send
+ * @param ptr Data to send
+ * @param length Length of data to send (in/out: on return, contains the
+ * amount of data sent)
+ * @param apiflags directly passed to tcp_write
+ * @return the return value of tcp_write
+ */
+static err_t
+http_write(struct altcp_pcb *pcb, const void *ptr, u16_t *length, u8_t apiflags)
+{
+ u16_t len, max_len;
+ err_t err;
+ LWIP_ASSERT("length != NULL", length != NULL);
+ len = *length;
+ if (len == 0) {
+ return ERR_OK;
+ }
+ /* We cannot send more data than space available in the send buffer. */
+ max_len = altcp_sndbuf(pcb);
+ if (max_len < len) {
+ len = max_len;
+ }
+#ifdef HTTPD_MAX_WRITE_LEN
+ /* Additional limitation: e.g. don't enqueue more than 2*mss at once */
+ max_len = HTTPD_MAX_WRITE_LEN(pcb);
+ if (len > max_len) {
+ len = max_len;
+ }
+#endif /* HTTPD_MAX_WRITE_LEN */
+ do {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Trying to send %d bytes\n", len));
+ err = altcp_write(pcb, ptr, len, apiflags);
+ if (err == ERR_MEM) {
+ if ((altcp_sndbuf(pcb) == 0) ||
+ (altcp_sndqueuelen(pcb) >= TCP_SND_QUEUELEN)) {
+ /* no need to try smaller sizes */
+ len = 1;
+ } else {
+ len /= 2;
+ }
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE,
+ ("Send failed, trying less (%d bytes)\n", len));
+ }
+ } while ((err == ERR_MEM) && (len > 1));
+
+ if (err == ERR_OK) {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Sent %d bytes\n", len));
+ *length = len;
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Send failed with err %d (\"%s\")\n", err, lwip_strerr(err)));
+ *length = 0;
+ }
+
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ /* ensure nagle is normally enabled (only disabled for persistent connections
+ when all data has been enqueued but the connection stays open for the next
+ request */
+ altcp_nagle_enable(pcb);
+#endif
+
+ return err;
+}
+
+/**
+ * The connection shall be actively closed (using RST to close from fault states).
+ * Reset the sent- and recv-callbacks.
+ *
+ * @param pcb the tcp pcb to reset callbacks
+ * @param hs connection state to free
+ */
+static err_t
+http_close_or_abort_conn(struct altcp_pcb *pcb, struct http_state *hs, u8_t abort_conn)
+{
+ err_t err;
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Closing connection %p\n", (void *)pcb));
+
+#if LWIP_HTTPD_SUPPORT_POST
+ if (hs != NULL) {
+ if ((hs->post_content_len_left != 0)
+#if LWIP_HTTPD_POST_MANUAL_WND
+ || ((hs->no_auto_wnd != 0) && (hs->unrecved_bytes != 0))
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+ ) {
+ /* make sure the post code knows that the connection is closed */
+ http_uri_buf[0] = 0;
+ httpd_post_finished(hs, http_uri_buf, LWIP_HTTPD_URI_BUF_LEN);
+ }
+ }
+#endif /* LWIP_HTTPD_SUPPORT_POST*/
+
+
+ altcp_arg(pcb, NULL);
+ altcp_recv(pcb, NULL);
+ altcp_err(pcb, NULL);
+ altcp_poll(pcb, NULL, 0);
+ altcp_sent(pcb, NULL);
+ if (hs != NULL) {
+ http_state_free(hs);
+ }
+
+ if (abort_conn) {
+ altcp_abort(pcb);
+ return ERR_OK;
+ }
+ err = altcp_close(pcb);
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Error %d closing %p\n", err, (void *)pcb));
+ /* error closing, try again later in poll */
+ altcp_poll(pcb, http_poll, HTTPD_POLL_INTERVAL);
+ }
+ return err;
+}
+
+/**
+ * The connection shall be actively closed.
+ * Reset the sent- and recv-callbacks.
+ *
+ * @param pcb the tcp pcb to reset callbacks
+ * @param hs connection state to free
+ */
+static err_t
+http_close_conn(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ return http_close_or_abort_conn(pcb, hs, 0);
+}
+
+/** End of file: either close the connection (Connection: close) or
+ * close the file (Connection: keep-alive)
+ */
+static void
+http_eof(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ /* HTTP/1.1 persistent connection? (Not supported for SSI) */
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ if (hs->keepalive) {
+ http_remove_connection(hs);
+
+ http_state_eof(hs);
+ http_state_init(hs);
+ /* restore state: */
+ hs->pcb = pcb;
+ hs->keepalive = 1;
+ http_add_connection(hs);
+ /* ensure nagle doesn't interfere with sending all data as fast as possible: */
+ altcp_nagle_disable(pcb);
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+ {
+ http_close_conn(pcb, hs);
+ }
+}
+
+#if LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI
+/**
+ * Extract URI parameters from the parameter-part of an URI in the form
+ * "test.cgi?x=y" @todo: better explanation!
+ * Pointers to the parameters are stored in hs->param_vals.
+ *
+ * @param hs http connection state
+ * @param params pointer to the NULL-terminated parameter string from the URI
+ * @return number of parameters extracted
+ */
+static int
+extract_uri_parameters(struct http_state *hs, char *params)
+{
+ char *pair;
+ char *equals;
+ int loop;
+
+ LWIP_UNUSED_ARG(hs);
+
+ /* If we have no parameters at all, return immediately. */
+ if (!params || (params[0] == '\0')) {
+ return (0);
+ }
+
+ /* Get a pointer to our first parameter */
+ pair = params;
+
+ /* Parse up to LWIP_HTTPD_MAX_CGI_PARAMETERS from the passed string and ignore the
+ * remainder (if any) */
+ for (loop = 0; (loop < LWIP_HTTPD_MAX_CGI_PARAMETERS) && pair; loop++) {
+
+ /* Save the name of the parameter */
+ http_cgi_params[loop] = pair;
+
+ /* Remember the start of this name=value pair */
+ equals = pair;
+
+ /* Find the start of the next name=value pair and replace the delimiter
+ * with a 0 to terminate the previous pair string. */
+ pair = strchr(pair, '&');
+ if (pair) {
+ *pair = '\0';
+ pair++;
+ } else {
+ /* We didn't find a new parameter so find the end of the URI and
+ * replace the space with a '\0' */
+ pair = strchr(equals, ' ');
+ if (pair) {
+ *pair = '\0';
+ }
+
+ /* Revert to NULL so that we exit the loop as expected. */
+ pair = NULL;
+ }
+
+ /* Now find the '=' in the previous pair, replace it with '\0' and save
+ * the parameter value string. */
+ equals = strchr(equals, '=');
+ if (equals) {
+ *equals = '\0';
+ http_cgi_param_vals[loop] = equals + 1;
+ } else {
+ http_cgi_param_vals[loop] = NULL;
+ }
+ }
+
+ return loop;
+}
+#endif /* LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI */
+
+#if LWIP_HTTPD_SSI
+/**
+ * Insert a tag (found in an shtml in the form of "<!--#tagname-->" into the file.
+ * The tag's name is stored in ssi->tag_name (NULL-terminated), the replacement
+ * should be written to hs->tag_insert (up to a length of LWIP_HTTPD_MAX_TAG_INSERT_LEN).
+ * The amount of data written is stored to ssi->tag_insert_len.
+ *
+ * @todo: return tag_insert_len - maybe it can be removed from struct http_state?
+ *
+ * @param hs http connection state
+ */
+static void
+get_tag_insert(struct http_state *hs)
+{
+#if LWIP_HTTPD_SSI_RAW
+ const char *tag;
+#else /* LWIP_HTTPD_SSI_RAW */
+ int tag;
+#endif /* LWIP_HTTPD_SSI_RAW */
+ size_t len;
+ struct http_ssi_state *ssi;
+#if LWIP_HTTPD_SSI_MULTIPART
+ u16_t current_tag_part;
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+
+ LWIP_ASSERT("hs != NULL", hs != NULL);
+ ssi = hs->ssi;
+ LWIP_ASSERT("ssi != NULL", ssi != NULL);
+#if LWIP_HTTPD_SSI_MULTIPART
+ current_tag_part = ssi->tag_part;
+ ssi->tag_part = HTTPD_LAST_TAG_PART;
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+#if LWIP_HTTPD_SSI_RAW
+ tag = ssi->tag_name;
+#endif
+
+ if (httpd_ssi_handler
+#if !LWIP_HTTPD_SSI_RAW
+ && httpd_tags && httpd_num_tags
+#endif /* !LWIP_HTTPD_SSI_RAW */
+ ) {
+
+ /* Find this tag in the list we have been provided. */
+#if LWIP_HTTPD_SSI_RAW
+ {
+#else /* LWIP_HTTPD_SSI_RAW */
+ for (tag = 0; tag < httpd_num_tags; tag++) {
+ if (strcmp(ssi->tag_name, httpd_tags[tag]) == 0)
+#endif /* LWIP_HTTPD_SSI_RAW */
+ {
+ ssi->tag_insert_len = httpd_ssi_handler(tag, ssi->tag_insert,
+ LWIP_HTTPD_MAX_TAG_INSERT_LEN
+#if LWIP_HTTPD_SSI_MULTIPART
+ , current_tag_part, &ssi->tag_part
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+#if LWIP_HTTPD_FILE_STATE
+ , (hs->handle ? hs->handle->state : NULL)
+#endif /* LWIP_HTTPD_FILE_STATE */
+ );
+#if LWIP_HTTPD_SSI_RAW
+ if (ssi->tag_insert_len != HTTPD_SSI_TAG_UNKNOWN)
+#endif /* LWIP_HTTPD_SSI_RAW */
+ {
+ return;
+ }
+ }
+ }
+ }
+
+ /* If we drop out, we were asked to serve a page which contains tags that
+ * we don't have a handler for. Merely echo back the tags with an error
+ * marker. */
+#define UNKNOWN_TAG1_TEXT "<b>***UNKNOWN TAG "
+#define UNKNOWN_TAG1_LEN 18
+#define UNKNOWN_TAG2_TEXT "***</b>"
+#define UNKNOWN_TAG2_LEN 7
+ len = LWIP_MIN(sizeof(ssi->tag_name), LWIP_MIN(strlen(ssi->tag_name),
+ LWIP_HTTPD_MAX_TAG_INSERT_LEN - (UNKNOWN_TAG1_LEN + UNKNOWN_TAG2_LEN)));
+ MEMCPY(ssi->tag_insert, UNKNOWN_TAG1_TEXT, UNKNOWN_TAG1_LEN);
+ MEMCPY(&ssi->tag_insert[UNKNOWN_TAG1_LEN], ssi->tag_name, len);
+ MEMCPY(&ssi->tag_insert[UNKNOWN_TAG1_LEN + len], UNKNOWN_TAG2_TEXT, UNKNOWN_TAG2_LEN);
+ ssi->tag_insert[UNKNOWN_TAG1_LEN + len + UNKNOWN_TAG2_LEN] = 0;
+
+ len = strlen(ssi->tag_insert);
+ LWIP_ASSERT("len <= 0xffff", len <= 0xffff);
+ ssi->tag_insert_len = (u16_t)len;
+}
+#endif /* LWIP_HTTPD_SSI */
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+/**
+ * Generate the relevant HTTP headers for the given filename and write
+ * them into the supplied buffer.
+ */
+static void
+get_http_headers(struct http_state *hs, const char *uri)
+{
+ size_t content_type;
+ char *tmp;
+ char *ext;
+ char *vars;
+
+ /* In all cases, the second header we send is the server identification
+ so set it here. */
+ hs->hdrs[HDR_STRINGS_IDX_SERVER_NAME] = g_psHTTPHeaderStrings[HTTP_HDR_SERVER];
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] = NULL;
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_NR] = NULL;
+
+ /* Is this a normal file or the special case we use to send back the
+ default "404: Page not found" response? */
+ if (uri == NULL) {
+ hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_FOUND];
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ if (hs->keepalive) {
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaderStrings[DEFAULT_404_HTML_PERSISTENT];
+ } else
+#endif
+ {
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaderStrings[DEFAULT_404_HTML];
+ }
+
+ /* Set up to send the first header string. */
+ hs->hdr_index = 0;
+ hs->hdr_pos = 0;
+ return;
+ }
+ /* We are dealing with a particular filename. Look for one other
+ special case. We assume that any filename with "404" in it must be
+ indicative of a 404 server error whereas all other files require
+ the 200 OK header. */
+ if (memcmp(uri, "/404.", 5) == 0) {
+ hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_FOUND];
+ } else if (memcmp(uri, "/400.", 5) == 0) {
+ hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_BAD_REQUEST];
+ } else if (memcmp(uri, "/501.", 5) == 0) {
+ hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_IMPL];
+ } else {
+ hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_OK];
+ }
+
+ /* Determine if the URI has any variables and, if so, temporarily remove
+ them. */
+ vars = strchr(uri, '?');
+ if (vars) {
+ *vars = '\0';
+ }
+
+ /* Get a pointer to the file extension. We find this by looking for the
+ last occurrence of "." in the filename passed. */
+ ext = NULL;
+ tmp = strchr(uri, '.');
+ while (tmp) {
+ ext = tmp + 1;
+ tmp = strchr(ext, '.');
+ }
+ if (ext != NULL) {
+ /* Now determine the content type and add the relevant header for that. */
+ for (content_type = 0; content_type < NUM_HTTP_HEADERS; content_type++) {
+ /* Have we found a matching extension? */
+ if (!lwip_stricmp(g_psHTTPHeaders[content_type].extension, ext)) {
+ break;
+ }
+ }
+ } else {
+ content_type = NUM_HTTP_HEADERS;
+ }
+
+ /* Reinstate the parameter marker if there was one in the original URI. */
+ if (vars) {
+ *vars = '?';
+ }
+
+#if LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI
+ /* Does the URL passed have any file extension? If not, we assume it
+ is a special-case URL used for control state notification and we do
+ not send any HTTP headers with the response. */
+ if (!ext) {
+ /* Force the header index to a value indicating that all headers
+ have already been sent. */
+ hs->hdr_index = NUM_FILE_HDR_STRINGS;
+ return;
+ }
+#endif /* LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI */
+ /* Did we find a matching extension? */
+ if (content_type < NUM_HTTP_HEADERS) {
+ /* yes, store it */
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaders[content_type].content_type;
+ } else if (!ext) {
+ /* no, no extension found -> use binary transfer to prevent the browser adding '.txt' on save */
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = HTTP_HDR_APP;
+ } else {
+ /* No - use the default, plain text file type. */
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = HTTP_HDR_DEFAULT_TYPE;
+ }
+ /* Set up to send the first header string. */
+ hs->hdr_index = 0;
+ hs->hdr_pos = 0;
+}
+
+/* Add content-length header? */
+static void
+get_http_content_length(struct http_state *hs)
+{
+ u8_t add_content_len = 0;
+
+ LWIP_ASSERT("already been here?", hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] == NULL);
+
+ add_content_len = 0;
+#if LWIP_HTTPD_SSI
+ if (hs->ssi == NULL) /* @todo: get maximum file length from SSI */
+#endif /* LWIP_HTTPD_SSI */
+ {
+ if ((hs->handle != NULL) && (hs->handle->flags & FS_FILE_FLAGS_HEADER_PERSISTENT)) {
+ add_content_len = 1;
+ }
+ }
+ if (add_content_len) {
+ size_t len;
+ lwip_itoa(hs->hdr_content_len, (size_t)LWIP_HTTPD_MAX_CONTENT_LEN_SIZE,
+ hs->handle->len);
+ len = strlen(hs->hdr_content_len);
+ if (len <= LWIP_HTTPD_MAX_CONTENT_LEN_SIZE - LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET) {
+ SMEMCPY(&hs->hdr_content_len[len], CRLF, 3);
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_NR] = hs->hdr_content_len;
+ } else {
+ add_content_len = 0;
+ }
+ }
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ if (add_content_len) {
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_KEEPALIVE_LEN];
+ } else {
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_CONN_CLOSE];
+ hs->keepalive = 0;
+ }
+#else /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+ if (add_content_len) {
+ hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_CONTENT_LENGTH];
+ }
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+}
+
+/** Sub-function of http_send(): send dynamic headers
+ *
+ * @returns: - HTTP_NO_DATA_TO_SEND: no new data has been enqueued
+ * - HTTP_DATA_TO_SEND_CONTINUE: continue with sending HTTP body
+ * - HTTP_DATA_TO_SEND_BREAK: data has been enqueued, headers pending,
+ * so don't send HTTP body yet
+ * - HTTP_DATA_TO_SEND_FREED: http_state and pcb are already freed
+ */
+static u8_t
+http_send_headers(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ err_t err;
+ u16_t len;
+ u8_t data_to_send = HTTP_NO_DATA_TO_SEND;
+ u16_t hdrlen, sendlen;
+
+ if (hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEEPALIVE] == NULL) {
+ /* set up "content-length" and "connection:" headers */
+ get_http_content_length(hs);
+ }
+
+ /* How much data can we send? */
+ len = altcp_sndbuf(pcb);
+ sendlen = len;
+
+ while (len && (hs->hdr_index < NUM_FILE_HDR_STRINGS) && sendlen) {
+ const void *ptr;
+ u16_t old_sendlen;
+ u8_t apiflags;
+ /* How much do we have to send from the current header? */
+ hdrlen = (u16_t)strlen(hs->hdrs[hs->hdr_index]);
+
+ /* How much of this can we send? */
+ sendlen = (len < (hdrlen - hs->hdr_pos)) ? len : (hdrlen - hs->hdr_pos);
+
+ /* Send this amount of data or as much as we can given memory
+ * constraints. */
+ ptr = (const void *)(hs->hdrs[hs->hdr_index] + hs->hdr_pos);
+ old_sendlen = sendlen;
+ apiflags = HTTP_IS_HDR_VOLATILE(hs, ptr);
+ if (hs->hdr_index == HDR_STRINGS_IDX_CONTENT_LEN_NR) {
+ /* content-length is always volatile */
+ apiflags |= TCP_WRITE_FLAG_COPY;
+ }
+ if (hs->hdr_index < NUM_FILE_HDR_STRINGS - 1) {
+ apiflags |= TCP_WRITE_FLAG_MORE;
+ }
+ err = http_write(pcb, ptr, &sendlen, apiflags);
+ if ((err == ERR_OK) && (old_sendlen != sendlen)) {
+ /* Remember that we added some more data to be transmitted. */
+ data_to_send = HTTP_DATA_TO_SEND_CONTINUE;
+ } else if (err != ERR_OK) {
+ /* special case: http_write does not try to send 1 byte */
+ sendlen = 0;
+ }
+
+ /* Fix up the header position for the next time round. */
+ hs->hdr_pos += sendlen;
+ len -= sendlen;
+
+ /* Have we finished sending this string? */
+ if (hs->hdr_pos == hdrlen) {
+ /* Yes - move on to the next one */
+ hs->hdr_index++;
+ /* skip headers that are NULL (not all headers are required) */
+ while ((hs->hdr_index < NUM_FILE_HDR_STRINGS) &&
+ (hs->hdrs[hs->hdr_index] == NULL)) {
+ hs->hdr_index++;
+ }
+ hs->hdr_pos = 0;
+ }
+ }
+
+ if ((hs->hdr_index >= NUM_FILE_HDR_STRINGS) && (hs->file == NULL)) {
+ /* When we are at the end of the headers, check for data to send
+ * instead of waiting for ACK from remote side to continue
+ * (which would happen when sending files from async read). */
+ if (http_check_eof(pcb, hs)) {
+ data_to_send = HTTP_DATA_TO_SEND_BREAK;
+ } else {
+ /* At this point, for non-keepalive connections, hs is deallocated an
+ pcb is closed. */
+ return HTTP_DATA_TO_SEND_FREED;
+ }
+ }
+ /* If we get here and there are still header bytes to send, we send
+ * the header information we just wrote immediately. If there are no
+ * more headers to send, but we do have file data to send, drop through
+ * to try to send some file data too. */
+ if ((hs->hdr_index < NUM_FILE_HDR_STRINGS) || !hs->file) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("tcp_output\n"));
+ return HTTP_DATA_TO_SEND_BREAK;
+ }
+ return data_to_send;
+}
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+/** Sub-function of http_send(): end-of-file (or block) is reached,
+ * either close the file or read the next block (if supported).
+ *
+ * @returns: 0 if the file is finished or no data has been read
+ * 1 if the file is not finished and data has been read
+ */
+static u8_t
+http_check_eof(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ int bytes_left;
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+ int count;
+#ifdef HTTPD_MAX_WRITE_LEN
+ int max_write_len;
+#endif /* HTTPD_MAX_WRITE_LEN */
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+
+ /* Do we have a valid file handle? */
+ if (hs->handle == NULL) {
+ /* No - close the connection. */
+ http_eof(pcb, hs);
+ return 0;
+ }
+ bytes_left = fs_bytes_left(hs->handle);
+ if (bytes_left <= 0) {
+ /* We reached the end of the file so this request is done. */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n"));
+ http_eof(pcb, hs);
+ return 0;
+ }
+#if LWIP_HTTPD_DYNAMIC_FILE_READ
+ /* Do we already have a send buffer allocated? */
+ if (hs->buf) {
+ /* Yes - get the length of the buffer */
+ count = LWIP_MIN(hs->buf_len, bytes_left);
+ } else {
+ /* We don't have a send buffer so allocate one now */
+ count = altcp_sndbuf(pcb);
+ if (bytes_left < count) {
+ count = bytes_left;
+ }
+#ifdef HTTPD_MAX_WRITE_LEN
+ /* Additional limitation: e.g. don't enqueue more than 2*mss at once */
+ max_write_len = HTTPD_MAX_WRITE_LEN(pcb);
+ if (count > max_write_len) {
+ count = max_write_len;
+ }
+#endif /* HTTPD_MAX_WRITE_LEN */
+ do {
+ hs->buf = (char *)mem_malloc((mem_size_t)count);
+ if (hs->buf != NULL) {
+ hs->buf_len = count;
+ break;
+ }
+ count = count / 2;
+ } while (count > 100);
+
+ /* Did we get a send buffer? If not, return immediately. */
+ if (hs->buf == NULL) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("No buff\n"));
+ return 0;
+ }
+ }
+
+ /* Read a block of data from the file. */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Trying to read %d bytes.\n", count));
+
+#if LWIP_HTTPD_FS_ASYNC_READ
+ count = fs_read_async(hs->handle, hs->buf, count, http_continue, hs);
+#else /* LWIP_HTTPD_FS_ASYNC_READ */
+ count = fs_read(hs->handle, hs->buf, count);
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+ if (count < 0) {
+ if (count == FS_READ_DELAYED) {
+ /* Delayed read, wait for FS to unblock us */
+ return 0;
+ }
+ /* We reached the end of the file so this request is done.
+ * @todo: close here for HTTP/1.1 when reading file fails */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n"));
+ http_eof(pcb, hs);
+ return 0;
+ }
+
+ /* Set up to send the block of data we just read */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Read %d bytes.\n", count));
+ hs->left = count;
+ hs->file = hs->buf;
+#if LWIP_HTTPD_SSI
+ if (hs->ssi) {
+ hs->ssi->parse_left = count;
+ hs->ssi->parsed = hs->buf;
+ }
+#endif /* LWIP_HTTPD_SSI */
+#else /* LWIP_HTTPD_DYNAMIC_FILE_READ */
+ LWIP_ASSERT("SSI and DYNAMIC_HEADERS turned off but eof not reached", 0);
+#endif /* LWIP_HTTPD_SSI || LWIP_HTTPD_DYNAMIC_HEADERS */
+ return 1;
+}
+
+/** Sub-function of http_send(): This is the normal send-routine for non-ssi files
+ *
+ * @returns: - 1: data has been written (so call tcp_ouput)
+ * - 0: no data has been written (no need to call tcp_output)
+ */
+static u8_t
+http_send_data_nonssi(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ err_t err;
+ u16_t len;
+ u8_t data_to_send = 0;
+
+ /* We are not processing an SHTML file so no tag checking is necessary.
+ * Just send the data as we received it from the file. */
+ len = (u16_t)LWIP_MIN(hs->left, 0xffff);
+
+ err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+ if (err == ERR_OK) {
+ data_to_send = 1;
+ hs->file += len;
+ hs->left -= len;
+ }
+
+ return data_to_send;
+}
+
+#if LWIP_HTTPD_SSI
+/** Sub-function of http_send(): This is the send-routine for ssi files
+ *
+ * @returns: - 1: data has been written (so call tcp_ouput)
+ * - 0: no data has been written (no need to call tcp_output)
+ */
+static u8_t
+http_send_data_ssi(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ err_t err = ERR_OK;
+ u16_t len;
+ u8_t data_to_send = 0;
+ u8_t tag_type;
+
+ struct http_ssi_state *ssi = hs->ssi;
+ LWIP_ASSERT("ssi != NULL", ssi != NULL);
+ /* We are processing an SHTML file so need to scan for tags and replace
+ * them with insert strings. We need to be careful here since a tag may
+ * straddle the boundary of two blocks read from the file and we may also
+ * have to split the insert string between two tcp_write operations. */
+
+ /* How much data could we send? */
+ len = altcp_sndbuf(pcb);
+
+ /* Do we have remaining data to send before parsing more? */
+ if (ssi->parsed > hs->file) {
+ len = (u16_t)LWIP_MIN(ssi->parsed - hs->file, 0xffff);
+
+ err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+ if (err == ERR_OK) {
+ data_to_send = 1;
+ hs->file += len;
+ hs->left -= len;
+ }
+
+ /* If the send buffer is full, return now. */
+ if (altcp_sndbuf(pcb) == 0) {
+ return data_to_send;
+ }
+ }
+
+ LWIP_DEBUGF(HTTPD_DEBUG, ("State %d, %d left\n", ssi->tag_state, (int)ssi->parse_left));
+
+ /* We have sent all the data that was already parsed so continue parsing
+ * the buffer contents looking for SSI tags. */
+ while (((ssi->tag_state == TAG_SENDING) || ssi->parse_left) && (err == ERR_OK)) {
+ if (len == 0) {
+ return data_to_send;
+ }
+ switch (ssi->tag_state) {
+ case TAG_NONE:
+ /* We are not currently processing an SSI tag so scan for the
+ * start of the lead-in marker. */
+ for (tag_type = 0; tag_type < LWIP_ARRAYSIZE(http_ssi_tag_desc); tag_type++) {
+ if (*ssi->parsed == http_ssi_tag_desc[tag_type].lead_in[0]) {
+ /* We found what could be the lead-in for a new tag so change
+ * state appropriately. */
+ ssi->tag_type = tag_type;
+ ssi->tag_state = TAG_LEADIN;
+ ssi->tag_index = 1;
+ #if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ ssi->tag_started = ssi->parsed;
+ #endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG */
+ break;
+ }
+ }
+
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+ break;
+
+ case TAG_LEADIN:
+ /* We are processing the lead-in marker, looking for the start of
+ * the tag name. */
+
+ /* Have we reached the end of the leadin? */
+ if (http_ssi_tag_desc[ssi->tag_type].lead_in[ssi->tag_index] == 0) {
+ ssi->tag_index = 0;
+ ssi->tag_state = TAG_FOUND;
+ } else {
+ /* Have we found the next character we expect for the tag leadin? */
+ if (*ssi->parsed == http_ssi_tag_desc[ssi->tag_type].lead_in[ssi->tag_index]) {
+ /* Yes - move to the next one unless we have found the complete
+ * leadin, in which case we start looking for the tag itself */
+ ssi->tag_index++;
+ } else {
+ /* We found an unexpected character so this is not a tag. Move
+ * back to idle state. */
+ ssi->tag_state = TAG_NONE;
+ }
+
+#if LWIP_HTTPD_DYNAMIC_FILE_READ && !LWIP_HTTPD_SSI_INCLUDE_TAG
+ if ((ssi->tag_state == TAG_NONE) &&
+ (ssi->parsed - hs->file < ssi->tag_index)) {
+ for(u16_t i = 0;i < ssi->tag_index;i++) {
+ ssi->tag_insert[i] = http_ssi_tag_desc[ssi->tag_type].lead_in[i];
+ }
+ ssi->tag_insert_len = ssi->tag_index;
+ hs->file += ssi->parsed - hs->file;
+ hs->left -= ssi->parsed - hs->file;
+ ssi->tag_end = hs->file;
+ ssi->tag_index = 0;
+ ssi->tag_state = TAG_SENDING;
+ break;
+ }
+#endif
+
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+ }
+ break;
+
+ case TAG_FOUND:
+ /* We are reading the tag name, looking for the start of the
+ * lead-out marker and removing any whitespace found. */
+
+ /* Remove leading whitespace between the tag leading and the first
+ * tag name character. */
+ if ((ssi->tag_index == 0) && ((*ssi->parsed == ' ') ||
+ (*ssi->parsed == '\t') || (*ssi->parsed == '\n') ||
+ (*ssi->parsed == '\r'))) {
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+ break;
+ }
+
+ /* Have we found the end of the tag name? This is signalled by
+ * us finding the first leadout character or whitespace */
+ if ((*ssi->parsed == http_ssi_tag_desc[ssi->tag_type].lead_out[0]) ||
+ (*ssi->parsed == ' ') || (*ssi->parsed == '\t') ||
+ (*ssi->parsed == '\n') || (*ssi->parsed == '\r')) {
+
+ if (ssi->tag_index == 0) {
+ /* We read a zero length tag so ignore it. */
+ ssi->tag_state = TAG_NONE;
+ } else {
+ /* We read a non-empty tag so go ahead and look for the
+ * leadout string. */
+ ssi->tag_state = TAG_LEADOUT;
+ LWIP_ASSERT("ssi->tag_index <= 0xff", ssi->tag_index <= 0xff);
+ ssi->tag_name_len = (u8_t)ssi->tag_index;
+ ssi->tag_name[ssi->tag_index] = '\0';
+ if (*ssi->parsed == http_ssi_tag_desc[ssi->tag_type].lead_out[0]) {
+ ssi->tag_index = 1;
+ } else {
+ ssi->tag_index = 0;
+ }
+ }
+ } else {
+ /* This character is part of the tag name so save it */
+ if (ssi->tag_index < LWIP_HTTPD_MAX_TAG_NAME_LEN) {
+ ssi->tag_name[ssi->tag_index++] = *ssi->parsed;
+ } else {
+ /* The tag was too long so ignore it. */
+ ssi->tag_state = TAG_NONE;
+ }
+ }
+
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+
+ break;
+
+ /* We are looking for the end of the lead-out marker. */
+ case TAG_LEADOUT:
+ /* Remove leading whitespace between the tag leading and the first
+ * tag leadout character. */
+ if ((ssi->tag_index == 0) && ((*ssi->parsed == ' ') ||
+ (*ssi->parsed == '\t') || (*ssi->parsed == '\n') ||
+ (*ssi->parsed == '\r'))) {
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+ break;
+ }
+
+ /* Have we found the next character we expect for the tag leadout? */
+ if (*ssi->parsed == http_ssi_tag_desc[ssi->tag_type].lead_out[ssi->tag_index]) {
+ /* Yes - move to the next one unless we have found the complete
+ * leadout, in which case we need to call the client to process
+ * the tag. */
+
+ /* Move on to the next character in the buffer */
+ ssi->parse_left--;
+ ssi->parsed++;
+ ssi->tag_index++;
+
+ if (http_ssi_tag_desc[ssi->tag_type].lead_out[ssi->tag_index] == 0) {
+ /* Call the client to ask for the insert string for the
+ * tag we just found. */
+#if LWIP_HTTPD_SSI_MULTIPART
+ ssi->tag_part = 0; /* start with tag part 0 */
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+ get_tag_insert(hs);
+
+ /* Next time through, we are going to be sending data
+ * immediately, either the end of the block we start
+ * sending here or the insert string. */
+ ssi->tag_index = 0;
+ ssi->tag_state = TAG_SENDING;
+ ssi->tag_end = ssi->parsed;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ ssi->parsed = ssi->tag_started;
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+
+ /* If there is any unsent data in the buffer prior to the
+ * tag, we need to send it now. */
+ if (ssi->tag_end > hs->file) {
+ /* How much of the data can we send? */
+#if LWIP_HTTPD_SSI_INCLUDE_TAG
+ len = (u16_t)LWIP_MIN(ssi->tag_end - hs->file, 0xffff);
+#else /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ /* we would include the tag in sending */
+ len = (u16_t)LWIP_MIN(ssi->tag_started - hs->file, 0xffff);
+#endif /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+
+ err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+ if (err == ERR_OK) {
+ data_to_send = 1;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ if (ssi->tag_started <= hs->file) {
+ /* pretend to have sent the tag, too */
+ len += (u16_t)(ssi->tag_end - ssi->tag_started);
+ }
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ hs->file += len;
+ hs->left -= len;
+ }
+ }
+ }
+ } else {
+ /* We found an unexpected character so this is not a tag. Move
+ * back to idle state. */
+ ssi->parse_left--;
+ ssi->parsed++;
+ ssi->tag_state = TAG_NONE;
+ }
+ break;
+
+ /*
+ * We have found a valid tag and are in the process of sending
+ * data as a result of that discovery. We send either remaining data
+ * from the file prior to the insert point or the insert string itself.
+ */
+ case TAG_SENDING:
+ /* Do we have any remaining file data to send from the buffer prior
+ * to the tag? */
+ if (ssi->tag_end > hs->file) {
+ /* How much of the data can we send? */
+#if LWIP_HTTPD_SSI_INCLUDE_TAG
+ len = (u16_t)LWIP_MIN(ssi->tag_end - hs->file, 0xffff);
+#else /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ LWIP_ASSERT("hs->started >= hs->file", ssi->tag_started >= hs->file);
+ /* we would include the tag in sending */
+ len = (u16_t)LWIP_MIN(ssi->tag_started - hs->file, 0xffff);
+#endif /* LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ if (len != 0) {
+ err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+ } else {
+ err = ERR_OK;
+ }
+ if (err == ERR_OK) {
+ data_to_send = 1;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ if (ssi->tag_started <= hs->file) {
+ /* pretend to have sent the tag, too */
+ len += (u16_t)(ssi->tag_end - ssi->tag_started);
+ }
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ hs->file += len;
+ hs->left -= len;
+ }
+ } else {
+#if LWIP_HTTPD_SSI_MULTIPART
+ if (ssi->tag_index >= ssi->tag_insert_len) {
+ /* Did the last SSIHandler have more to send? */
+ if (ssi->tag_part != HTTPD_LAST_TAG_PART) {
+ /* If so, call it again */
+ ssi->tag_index = 0;
+ get_tag_insert(hs);
+ }
+ }
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+
+ /* Do we still have insert data left to send? */
+ if (ssi->tag_index < ssi->tag_insert_len) {
+ /* We are sending the insert string itself. How much of the
+ * insert can we send? */
+ len = (ssi->tag_insert_len - ssi->tag_index);
+
+ /* Note that we set the copy flag here since we only have a
+ * single tag insert buffer per connection. If we don't do
+ * this, insert corruption can occur if more than one insert
+ * is processed before we call tcp_output. */
+ err = http_write(pcb, &(ssi->tag_insert[ssi->tag_index]), &len,
+ HTTP_IS_TAG_VOLATILE(hs));
+ if (err == ERR_OK) {
+ data_to_send = 1;
+ ssi->tag_index += len;
+ /* Don't return here: keep on sending data */
+ }
+ } else {
+#if LWIP_HTTPD_SSI_MULTIPART
+ if (ssi->tag_part == HTTPD_LAST_TAG_PART)
+#endif /* LWIP_HTTPD_SSI_MULTIPART */
+ {
+ /* We have sent all the insert data so go back to looking for
+ * a new tag. */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Everything sent.\n"));
+ ssi->tag_index = 0;
+ ssi->tag_state = TAG_NONE;
+#if !LWIP_HTTPD_SSI_INCLUDE_TAG
+ ssi->parsed = ssi->tag_end;
+#endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* If we drop out of the end of the for loop, this implies we must have
+ * file data to send so send it now. In TAG_SENDING state, we've already
+ * handled this so skip the send if that's the case. */
+ if ((ssi->tag_state != TAG_SENDING) && (ssi->parsed > hs->file)) {
+#if LWIP_HTTPD_DYNAMIC_FILE_READ && !LWIP_HTTPD_SSI_INCLUDE_TAG
+ if ((ssi->tag_state != TAG_NONE) && (ssi->tag_started > ssi->tag_end)) {
+ /* If we found tag on the edge of the read buffer: just throw away the first part
+ (we have copied/saved everything required for parsing on later). */
+ len = (u16_t)(ssi->tag_started - hs->file);
+ hs->left -= (ssi->parsed - ssi->tag_started);
+ ssi->parsed = ssi->tag_started;
+ ssi->tag_started = hs->buf;
+ } else
+#endif /* LWIP_HTTPD_DYNAMIC_FILE_READ && !LWIP_HTTPD_SSI_INCLUDE_TAG */
+ {
+ len = (u16_t)LWIP_MIN(ssi->parsed - hs->file, 0xffff);
+ }
+
+ err = http_write(pcb, hs->file, &len, HTTP_IS_DATA_VOLATILE(hs));
+ if (err == ERR_OK) {
+ data_to_send = 1;
+ hs->file += len;
+ hs->left -= len;
+ }
+ }
+ return data_to_send;
+}
+#endif /* LWIP_HTTPD_SSI */
+
+/**
+ * Try to send more data on this pcb.
+ *
+ * @param pcb the pcb to send data
+ * @param hs connection state
+ */
+static u8_t
+http_send(struct altcp_pcb *pcb, struct http_state *hs)
+{
+ u8_t data_to_send = HTTP_NO_DATA_TO_SEND;
+
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_send: pcb=%p hs=%p left=%d\n", (void *)pcb,
+ (void *)hs, hs != NULL ? (int)hs->left : 0));
+
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+ if (hs->unrecved_bytes != 0) {
+ return 0;
+ }
+#endif /* LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND */
+
+ /* If we were passed a NULL state structure pointer, ignore the call. */
+ if (hs == NULL) {
+ return 0;
+ }
+
+#if LWIP_HTTPD_FS_ASYNC_READ
+ /* Check if we are allowed to read from this file.
+ (e.g. SSI might want to delay sending until data is available) */
+ if (!fs_is_file_ready(hs->handle, http_continue, hs)) {
+ return 0;
+ }
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+ /* Do we have any more header data to send for this file? */
+ if (hs->hdr_index < NUM_FILE_HDR_STRINGS) {
+ data_to_send = http_send_headers(pcb, hs);
+ if ((data_to_send == HTTP_DATA_TO_SEND_FREED) ||
+ ((data_to_send != HTTP_DATA_TO_SEND_CONTINUE) &&
+ (hs->hdr_index < NUM_FILE_HDR_STRINGS))) {
+ return data_to_send;
+ }
+ }
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+#if LWIP_HTTPD_SSI
+ if (hs->ssi && (hs->ssi->tag_state == TAG_SENDING)) {
+ /* do not check the condition below */
+ } else
+#endif
+ /* Have we run out of file data to send? If so, we need to read the next
+ * block from the file. */
+ if (hs->left == 0) {
+ if (!http_check_eof(pcb, hs)) {
+ return 0;
+ }
+ }
+
+#if LWIP_HTTPD_SSI
+ if (hs->ssi) {
+ data_to_send = http_send_data_ssi(pcb, hs);
+ if (hs->ssi->tag_state == TAG_SENDING) {
+ return data_to_send;
+ }
+ } else
+#endif /* LWIP_HTTPD_SSI */
+ {
+ data_to_send = http_send_data_nonssi(pcb, hs);
+ }
+
+ if ((hs->left == 0) && (fs_bytes_left(hs->handle) <= 0)) {
+ /* We reached the end of the file so this request is done.
+ * This adds the FIN flag right into the last data segment. */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n"));
+ http_eof(pcb, hs);
+ return 0;
+ }
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("send_data end.\n"));
+ return data_to_send;
+}
+
+#if LWIP_HTTPD_SUPPORT_EXTSTATUS
+/** Initialize a http connection with a file to send for an error message
+ *
+ * @param hs http connection state
+ * @param error_nr HTTP error number
+ * @return ERR_OK if file was found and hs has been initialized correctly
+ * another err_t otherwise
+ */
+static err_t
+http_find_error_file(struct http_state *hs, u16_t error_nr)
+{
+ const char *uri, *uri1, *uri2, *uri3;
+
+ if (error_nr == 501) {
+ uri1 = "/501.html";
+ uri2 = "/501.htm";
+ uri3 = "/501.shtml";
+ } else {
+ /* 400 (bad request is the default) */
+ uri1 = "/400.html";
+ uri2 = "/400.htm";
+ uri3 = "/400.shtml";
+ }
+ if (fs_open(&hs->file_handle, uri1) == ERR_OK) {
+ uri = uri1;
+ } else if (fs_open(&hs->file_handle, uri2) == ERR_OK) {
+ uri = uri2;
+ } else if (fs_open(&hs->file_handle, uri3) == ERR_OK) {
+ uri = uri3;
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Error page for error %"U16_F" not found\n",
+ error_nr));
+ return ERR_ARG;
+ }
+ return http_init_file(hs, &hs->file_handle, 0, uri, 0, NULL);
+}
+#else /* LWIP_HTTPD_SUPPORT_EXTSTATUS */
+#define http_find_error_file(hs, error_nr) ERR_ARG
+#endif /* LWIP_HTTPD_SUPPORT_EXTSTATUS */
+
+/**
+ * Get the file struct for a 404 error page.
+ * Tries some file names and returns NULL if none found.
+ *
+ * @param uri pointer that receives the actual file name URI
+ * @return file struct for the error page or NULL no matching file was found
+ */
+static struct fs_file *
+http_get_404_file(struct http_state *hs, const char **uri)
+{
+ err_t err;
+
+ *uri = "/404.html";
+ err = fs_open(&hs->file_handle, *uri);
+ if (err != ERR_OK) {
+ /* 404.html doesn't exist. Try 404.htm instead. */
+ *uri = "/404.htm";
+ err = fs_open(&hs->file_handle, *uri);
+ if (err != ERR_OK) {
+ /* 404.htm doesn't exist either. Try 404.shtml instead. */
+ *uri = "/404.shtml";
+ err = fs_open(&hs->file_handle, *uri);
+ if (err != ERR_OK) {
+ /* 404.htm doesn't exist either. Indicate to the caller that it should
+ * send back a default 404 page.
+ */
+ *uri = NULL;
+ return NULL;
+ }
+ }
+ }
+
+ return &hs->file_handle;
+}
+
+#if LWIP_HTTPD_SUPPORT_POST
+static err_t
+http_handle_post_finished(struct http_state *hs)
+{
+#if LWIP_HTTPD_POST_MANUAL_WND
+ /* Prevent multiple calls to httpd_post_finished, since it might have already
+ been called before from httpd_post_data_recved(). */
+ if (hs->post_finished) {
+ return ERR_OK;
+ }
+ hs->post_finished = 1;
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+ /* application error or POST finished */
+ /* NULL-terminate the buffer */
+ http_uri_buf[0] = 0;
+ httpd_post_finished(hs, http_uri_buf, LWIP_HTTPD_URI_BUF_LEN);
+ return http_find_file(hs, http_uri_buf, 0);
+}
+
+/** Pass received POST body data to the application and correctly handle
+ * returning a response document or closing the connection.
+ * ATTENTION: The application is responsible for the pbuf now, so don't free it!
+ *
+ * @param hs http connection state
+ * @param p pbuf to pass to the application
+ * @return ERR_OK if passed successfully, another err_t if the response file
+ * hasn't been found (after POST finished)
+ */
+static err_t
+http_post_rxpbuf(struct http_state *hs, struct pbuf *p)
+{
+ err_t err;
+
+ if (p != NULL) {
+ /* adjust remaining Content-Length */
+ if (hs->post_content_len_left < p->tot_len) {
+ hs->post_content_len_left = 0;
+ } else {
+ hs->post_content_len_left -= p->tot_len;
+ }
+ }
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+ /* prevent connection being closed if httpd_post_data_recved() is called nested */
+ hs->unrecved_bytes++;
+#endif
+ if (p != NULL) {
+ err = httpd_post_receive_data(hs, p);
+ } else {
+ err = ERR_OK;
+ }
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+ hs->unrecved_bytes--;
+#endif
+ if (err != ERR_OK) {
+ /* Ignore remaining content in case of application error */
+ hs->post_content_len_left = 0;
+ }
+ if (hs->post_content_len_left == 0) {
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+ if (hs->unrecved_bytes != 0) {
+ return ERR_OK;
+ }
+#endif /* LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND */
+ /* application error or POST finished */
+ return http_handle_post_finished(hs);
+ }
+
+ return ERR_OK;
+}
+
+/** Handle a post request. Called from http_parse_request when method 'POST'
+ * is found.
+ *
+ * @param p The input pbuf (containing the POST header and body).
+ * @param hs The http connection state.
+ * @param data HTTP request (header and part of body) from input pbuf(s).
+ * @param data_len Size of 'data'.
+ * @param uri The HTTP URI parsed from input pbuf(s).
+ * @param uri_end Pointer to the end of 'uri' (here, the rest of the HTTP
+ * header starts).
+ * @return ERR_OK: POST correctly parsed and accepted by the application.
+ * ERR_INPROGRESS: POST not completely parsed (no error yet)
+ * another err_t: Error parsing POST or denied by the application
+ */
+static err_t
+http_post_request(struct pbuf *inp, struct http_state *hs,
+ char *data, u16_t data_len, char *uri, char *uri_end)
+{
+ err_t err;
+ /* search for end-of-header (first double-CRLF) */
+ char *crlfcrlf = lwip_strnstr(uri_end + 1, CRLF CRLF, data_len - (uri_end + 1 - data));
+
+ if (crlfcrlf != NULL) {
+ /* search for "Content-Length: " */
+#define HTTP_HDR_CONTENT_LEN "Content-Length: "
+#define HTTP_HDR_CONTENT_LEN_LEN 16
+#define HTTP_HDR_CONTENT_LEN_DIGIT_MAX_LEN 10
+ char *scontent_len = lwip_strnstr(uri_end + 1, HTTP_HDR_CONTENT_LEN, crlfcrlf - (uri_end + 1));
+ if (scontent_len != NULL) {
+ char *scontent_len_end = lwip_strnstr(scontent_len + HTTP_HDR_CONTENT_LEN_LEN, CRLF, HTTP_HDR_CONTENT_LEN_DIGIT_MAX_LEN);
+ if (scontent_len_end != NULL) {
+ int content_len;
+ char *content_len_num = scontent_len + HTTP_HDR_CONTENT_LEN_LEN;
+ content_len = atoi(content_len_num);
+ if (content_len == 0) {
+ /* if atoi returns 0 on error, fix this */
+ if ((content_len_num[0] != '0') || (content_len_num[1] != '\r')) {
+ content_len = -1;
+ }
+ }
+ if (content_len >= 0) {
+ /* adjust length of HTTP header passed to application */
+ const char *hdr_start_after_uri = uri_end + 1;
+ u16_t hdr_len = (u16_t)LWIP_MIN(data_len, crlfcrlf + 4 - data);
+ u16_t hdr_data_len = (u16_t)LWIP_MIN(data_len, crlfcrlf + 4 - hdr_start_after_uri);
+ u8_t post_auto_wnd = 1;
+ http_uri_buf[0] = 0;
+ /* trim http header */
+ *crlfcrlf = 0;
+ err = httpd_post_begin(hs, uri, hdr_start_after_uri, hdr_data_len, content_len,
+ http_uri_buf, LWIP_HTTPD_URI_BUF_LEN, &post_auto_wnd);
+ if (err == ERR_OK) {
+ /* try to pass in data of the first pbuf(s) */
+ struct pbuf *q = inp;
+ u16_t start_offset = hdr_len;
+#if LWIP_HTTPD_POST_MANUAL_WND
+ hs->no_auto_wnd = !post_auto_wnd;
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+ /* set the Content-Length to be received for this POST */
+ hs->post_content_len_left = (u32_t)content_len;
+
+ /* get to the pbuf where the body starts */
+ while ((q != NULL) && (q->len <= start_offset)) {
+ start_offset -= q->len;
+ q = q->next;
+ }
+ if (q != NULL) {
+ /* hide the remaining HTTP header */
+ pbuf_remove_header(q, start_offset);
+#if LWIP_HTTPD_POST_MANUAL_WND
+ if (!post_auto_wnd) {
+ /* already tcp_recved() this data... */
+ hs->unrecved_bytes = q->tot_len;
+ }
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+ pbuf_ref(q);
+ return http_post_rxpbuf(hs, q);
+ } else if (hs->post_content_len_left == 0) {
+ q = pbuf_alloc(PBUF_RAW, 0, PBUF_REF);
+ return http_post_rxpbuf(hs, q);
+ } else {
+ return ERR_OK;
+ }
+ } else {
+ /* return file passed from application */
+ return http_find_file(hs, http_uri_buf, 0);
+ }
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("POST received invalid Content-Length: %s\n",
+ content_len_num));
+ return ERR_ARG;
+ }
+ }
+ }
+ /* If we come here, headers are fully received (double-crlf), but Content-Length
+ was not included. Since this is currently the only supported method, we have
+ to fail in this case! */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Error when parsing Content-Length\n"));
+ return ERR_ARG;
+ }
+ /* if we come here, the POST is incomplete */
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ return ERR_INPROGRESS;
+#else /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ return ERR_ARG;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+}
+
+#if LWIP_HTTPD_POST_MANUAL_WND
+/**
+ * @ingroup httpd
+ * A POST implementation can call this function to update the TCP window.
+ * This can be used to throttle data reception (e.g. when received data is
+ * programmed to flash and data is received faster than programmed).
+ *
+ * @param connection A connection handle passed to httpd_post_begin for which
+ * httpd_post_finished has *NOT* been called yet!
+ * @param recved_len Length of data received (for window update)
+ */
+void httpd_post_data_recved(void *connection, u16_t recved_len)
+{
+ struct http_state *hs = (struct http_state *)connection;
+ if (hs != NULL) {
+ if (hs->no_auto_wnd) {
+ u16_t len = recved_len;
+ if (hs->unrecved_bytes >= recved_len) {
+ hs->unrecved_bytes -= recved_len;
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_LEVEL_WARNING, ("httpd_post_data_recved: recved_len too big\n"));
+ len = (u16_t)hs->unrecved_bytes;
+ hs->unrecved_bytes = 0;
+ }
+ if (hs->pcb != NULL) {
+ if (len != 0) {
+ altcp_recved(hs->pcb, len);
+ }
+ if ((hs->post_content_len_left == 0) && (hs->unrecved_bytes == 0)) {
+ /* finished handling POST */
+ http_handle_post_finished(hs);
+ http_send(hs->pcb, hs);
+ }
+ }
+ }
+ }
+}
+#endif /* LWIP_HTTPD_POST_MANUAL_WND */
+
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+
+#if LWIP_HTTPD_FS_ASYNC_READ
+/** Try to send more data if file has been blocked before
+ * This is a callback function passed to fs_read_async().
+ */
+static void
+http_continue(void *connection)
+{
+ struct http_state *hs = (struct http_state *)connection;
+ LWIP_ASSERT_CORE_LOCKED();
+ if (hs && (hs->pcb) && (hs->handle)) {
+ LWIP_ASSERT("hs->pcb != NULL", hs->pcb != NULL);
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("httpd_continue: try to send more data\n"));
+ if (http_send(hs->pcb, hs)) {
+ /* If we wrote anything to be sent, go ahead and send it now. */
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("tcp_output\n"));
+ altcp_output(hs->pcb);
+ }
+ }
+}
+#endif /* LWIP_HTTPD_FS_ASYNC_READ */
+
+/**
+ * When data has been received in the correct state, try to parse it
+ * as a HTTP request.
+ *
+ * @param inp the received pbuf
+ * @param hs the connection state
+ * @param pcb the altcp_pcb which received this packet
+ * @return ERR_OK if request was OK and hs has been initialized correctly
+ * ERR_INPROGRESS if request was OK so far but not fully received
+ * another err_t otherwise
+ */
+static err_t
+http_parse_request(struct pbuf *inp, struct http_state *hs, struct altcp_pcb *pcb)
+{
+ char *data;
+ char *crlf;
+ u16_t data_len;
+ struct pbuf *p = inp;
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ u16_t clen;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+#if LWIP_HTTPD_SUPPORT_POST
+ err_t err;
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+
+ LWIP_UNUSED_ARG(pcb); /* only used for post */
+ LWIP_ASSERT("p != NULL", p != NULL);
+ LWIP_ASSERT("hs != NULL", hs != NULL);
+
+ if ((hs->handle != NULL) || (hs->file != NULL)) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Received data while sending a file\n"));
+ /* already sending a file */
+ /* @todo: abort? */
+ return ERR_USE;
+ }
+
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Received %"U16_F" bytes\n", p->tot_len));
+
+ /* first check allowed characters in this pbuf? */
+
+ /* enqueue the pbuf */
+ if (hs->req == NULL) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("First pbuf\n"));
+ hs->req = p;
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("pbuf enqueued\n"));
+ pbuf_cat(hs->req, p);
+ }
+ /* increase pbuf ref counter as it is freed when we return but we want to
+ keep it on the req list */
+ pbuf_ref(p);
+
+ if (hs->req->next != NULL) {
+ data_len = LWIP_MIN(hs->req->tot_len, LWIP_HTTPD_MAX_REQ_LENGTH);
+ pbuf_copy_partial(hs->req, httpd_req_buf, data_len, 0);
+ data = httpd_req_buf;
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ {
+ data = (char *)p->payload;
+ data_len = p->len;
+ if (p->len != p->tot_len) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Warning: incomplete header due to chained pbufs\n"));
+ }
+ }
+
+ /* received enough data for minimal request? */
+ if (data_len >= MIN_REQ_LEN) {
+ /* wait for CRLF before parsing anything */
+ crlf = lwip_strnstr(data, CRLF, data_len);
+ if (crlf != NULL) {
+#if LWIP_HTTPD_SUPPORT_POST
+ int is_post = 0;
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ int is_09 = 0;
+ char *sp1, *sp2;
+ u16_t left_len, uri_len;
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("CRLF received, parsing request\n"));
+ /* parse method */
+ if (!strncmp(data, "GET ", 4)) {
+ sp1 = data + 3;
+ /* received GET request */
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Received GET request\"\n"));
+#if LWIP_HTTPD_SUPPORT_POST
+ } else if (!strncmp(data, "POST ", 5)) {
+ /* store request type */
+ is_post = 1;
+ sp1 = data + 4;
+ /* received GET request */
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Received POST request\n"));
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ } else {
+ /* null-terminate the METHOD (pbuf is freed anyway wen returning) */
+ data[4] = 0;
+ /* unsupported method! */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Unsupported request method (not implemented): \"%s\"\n",
+ data));
+ return http_find_error_file(hs, 501);
+ }
+ /* if we come here, method is OK, parse URI */
+ left_len = (u16_t)(data_len - ((sp1 + 1) - data));
+ sp2 = lwip_strnstr(sp1 + 1, " ", left_len);
+#if LWIP_HTTPD_SUPPORT_V09
+ if (sp2 == NULL) {
+ /* HTTP 0.9: respond with correct protocol version */
+ sp2 = lwip_strnstr(sp1 + 1, CRLF, left_len);
+ is_09 = 1;
+#if LWIP_HTTPD_SUPPORT_POST
+ if (is_post) {
+ /* HTTP/0.9 does not support POST */
+ goto badrequest;
+ }
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ }
+#endif /* LWIP_HTTPD_SUPPORT_V09 */
+ uri_len = (u16_t)(sp2 - (sp1 + 1));
+ if ((sp2 != NULL) && (sp2 > sp1)) {
+ /* wait for CRLFCRLF (indicating end of HTTP headers) before parsing anything */
+ if (lwip_strnstr(data, CRLF CRLF, data_len) != NULL) {
+ char *uri = sp1 + 1;
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ /* This is HTTP/1.0 compatible: for strict 1.1, a connection
+ would always be persistent unless "close" was specified. */
+ if (!is_09 && (lwip_strnstr(data, HTTP11_CONNECTIONKEEPALIVE, data_len) ||
+ lwip_strnstr(data, HTTP11_CONNECTIONKEEPALIVE2, data_len))) {
+ hs->keepalive = 1;
+ } else {
+ hs->keepalive = 0;
+ }
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+ /* null-terminate the METHOD (pbuf is freed anyway wen returning) */
+ *sp1 = 0;
+ uri[uri_len] = 0;
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Received \"%s\" request for URI: \"%s\"\n",
+ data, uri));
+#if LWIP_HTTPD_SUPPORT_POST
+ if (is_post) {
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ struct pbuf *q = hs->req;
+#else /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ struct pbuf *q = inp;
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ err = http_post_request(q, hs, data, data_len, uri, sp2);
+ if (err != ERR_OK) {
+ /* restore header for next try */
+ *sp1 = ' ';
+ *sp2 = ' ';
+ uri[uri_len] = ' ';
+ }
+ if (err == ERR_ARG) {
+ goto badrequest;
+ }
+ return err;
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ {
+ return http_find_file(hs, uri, is_09);
+ }
+ }
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("invalid URI\n"));
+ }
+ }
+ }
+
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ clen = pbuf_clen(hs->req);
+ if ((hs->req->tot_len <= LWIP_HTTPD_REQ_BUFSIZE) &&
+ (clen <= LWIP_HTTPD_REQ_QUEUELEN)) {
+ /* request not fully received (too short or CRLF is missing) */
+ return ERR_INPROGRESS;
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ {
+#if LWIP_HTTPD_SUPPORT_POST
+badrequest:
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("bad request\n"));
+ /* could not parse request */
+ return http_find_error_file(hs, 400);
+ }
+}
+
+#if LWIP_HTTPD_SSI && (LWIP_HTTPD_SSI_BY_FILE_EXTENSION == 1)
+/* Check if SSI should be parsed for this file/URL
+ * (With LWIP_HTTPD_SSI_BY_FILE_EXTENSION == 2, this function can be
+ * overridden by an external implementation.)
+ *
+ * @return 1 for SSI, 0 for standard files
+ */
+static u8_t
+http_uri_is_ssi(struct fs_file *file, const char *uri)
+{
+ size_t loop;
+ u8_t tag_check = 0;
+ if (file != NULL) {
+ /* See if we have been asked for an shtml file and, if so,
+ enable tag checking. */
+ const char *ext = NULL, *sub;
+ char *param = (char *)strstr(uri, "?");
+ if (param != NULL) {
+ /* separate uri from parameters for now, set back later */
+ *param = 0;
+ }
+ sub = uri;
+ ext = uri;
+ for (sub = strstr(sub, "."); sub != NULL; sub = strstr(sub, ".")) {
+ ext = sub;
+ sub++;
+ }
+ for (loop = 0; loop < NUM_SHTML_EXTENSIONS; loop++) {
+ if (!lwip_stricmp(ext, g_pcSSIExtensions[loop])) {
+ tag_check = 1;
+ break;
+ }
+ }
+ if (param != NULL) {
+ *param = '?';
+ }
+ }
+ return tag_check;
+}
+#endif /* LWIP_HTTPD_SSI */
+
+/** Try to find the file specified by uri and, if found, initialize hs
+ * accordingly.
+ *
+ * @param hs the connection state
+ * @param uri the HTTP header URI
+ * @param is_09 1 if the request is HTTP/0.9 (no HTTP headers in response)
+ * @return ERR_OK if file was found and hs has been initialized correctly
+ * another err_t otherwise
+ */
+static err_t
+http_find_file(struct http_state *hs, const char *uri, int is_09)
+{
+ size_t loop;
+ struct fs_file *file = NULL;
+ char *params = NULL;
+ err_t err;
+#if LWIP_HTTPD_CGI
+ int i;
+#endif /* LWIP_HTTPD_CGI */
+#if !LWIP_HTTPD_SSI
+ const
+#endif /* !LWIP_HTTPD_SSI */
+ /* By default, assume we will not be processing server-side-includes tags */
+ u8_t tag_check = 0;
+
+ /* Have we been asked for the default file (in root or a directory) ? */
+#if LWIP_HTTPD_MAX_REQUEST_URI_LEN
+ size_t uri_len = strlen(uri);
+ if ((uri_len > 0) && (uri[uri_len - 1] == '/') &&
+ ((uri != http_uri_buf) || (uri_len == 1))) {
+ size_t copy_len = LWIP_MIN(sizeof(http_uri_buf) - 1, uri_len - 1);
+ if (copy_len > 0) {
+ MEMCPY(http_uri_buf, uri, copy_len);
+ http_uri_buf[copy_len] = 0;
+ }
+#else /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */
+ if ((uri[0] == '/') && (uri[1] == 0)) {
+#endif /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */
+ /* Try each of the configured default filenames until we find one
+ that exists. */
+ for (loop = 0; loop < NUM_DEFAULT_FILENAMES; loop++) {
+ const char *file_name;
+#if LWIP_HTTPD_MAX_REQUEST_URI_LEN
+ if (copy_len > 0) {
+ size_t len_left = sizeof(http_uri_buf) - copy_len - 1;
+ if (len_left > 0) {
+ size_t name_len = strlen(httpd_default_filenames[loop].name);
+ size_t name_copy_len = LWIP_MIN(len_left, name_len);
+ MEMCPY(&http_uri_buf[copy_len], httpd_default_filenames[loop].name, name_copy_len);
+ http_uri_buf[copy_len + name_copy_len] = 0;
+ }
+ file_name = http_uri_buf;
+ } else
+#endif /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */
+ {
+ file_name = httpd_default_filenames[loop].name;
+ }
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Looking for %s...\n", file_name));
+ err = fs_open(&hs->file_handle, file_name);
+ if (err == ERR_OK) {
+ uri = file_name;
+ file = &hs->file_handle;
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Opened.\n"));
+#if LWIP_HTTPD_SSI
+ tag_check = httpd_default_filenames[loop].shtml;
+#endif /* LWIP_HTTPD_SSI */
+ break;
+ }
+ }
+ }
+ if (file == NULL) {
+ /* No - we've been asked for a specific file. */
+ /* First, isolate the base URI (without any parameters) */
+ params = (char *)strchr(uri, '?');
+ if (params != NULL) {
+ /* URI contains parameters. NULL-terminate the base URI */
+ *params = '\0';
+ params++;
+ }
+
+#if LWIP_HTTPD_CGI
+ http_cgi_paramcount = -1;
+ /* Does the base URI we have isolated correspond to a CGI handler? */
+ if (httpd_num_cgis && httpd_cgis) {
+ for (i = 0; i < httpd_num_cgis; i++) {
+ if (strcmp(uri, httpd_cgis[i].pcCGIName) == 0) {
+ /*
+ * We found a CGI that handles this URI so extract the
+ * parameters and call the handler.
+ */
+ http_cgi_paramcount = extract_uri_parameters(hs, params);
+ uri = httpd_cgis[i].pfnCGIHandler(i, http_cgi_paramcount, hs->params,
+ hs->param_vals);
+ break;
+ }
+ }
+ }
+#endif /* LWIP_HTTPD_CGI */
+
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Opening %s\n", uri));
+
+ err = fs_open(&hs->file_handle, uri);
+ if (err == ERR_OK) {
+ file = &hs->file_handle;
+ } else {
+ file = http_get_404_file(hs, &uri);
+ }
+#if LWIP_HTTPD_SSI
+ if (file != NULL) {
+ if (file->flags & FS_FILE_FLAGS_SSI) {
+ tag_check = 1;
+ } else {
+#if LWIP_HTTPD_SSI_BY_FILE_EXTENSION
+ tag_check = http_uri_is_ssi(file, uri);
+#endif /* LWIP_HTTPD_SSI_BY_FILE_EXTENSION */
+ }
+ }
+#endif /* LWIP_HTTPD_SSI */
+ }
+ if (file == NULL) {
+ /* None of the default filenames exist so send back a 404 page */
+ file = http_get_404_file(hs, &uri);
+ }
+ return http_init_file(hs, file, is_09, uri, tag_check, params);
+}
+
+/** Initialize a http connection with a file to send (if found).
+ * Called by http_find_file and http_find_error_file.
+ *
+ * @param hs http connection state
+ * @param file file structure to send (or NULL if not found)
+ * @param is_09 1 if the request is HTTP/0.9 (no HTTP headers in response)
+ * @param uri the HTTP header URI
+ * @param tag_check enable SSI tag checking
+ * @param params != NULL if URI has parameters (separated by '?')
+ * @return ERR_OK if file was found and hs has been initialized correctly
+ * another err_t otherwise
+ */
+static err_t
+http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri,
+ u8_t tag_check, char *params)
+{
+#if !LWIP_HTTPD_SUPPORT_V09
+ LWIP_UNUSED_ARG(is_09);
+#endif
+ if (file != NULL) {
+ /* file opened, initialise struct http_state */
+#if !LWIP_HTTPD_DYNAMIC_FILE_READ
+ /* If dynamic read is disabled, file data must be in one piece and available now */
+ LWIP_ASSERT("file->data != NULL", file->data != NULL);
+#endif
+
+#if LWIP_HTTPD_SSI
+ if (tag_check) {
+ struct http_ssi_state *ssi = http_ssi_state_alloc();
+ if (ssi != NULL) {
+ ssi->tag_index = 0;
+ ssi->tag_state = TAG_NONE;
+ ssi->parsed = file->data;
+ ssi->parse_left = file->len;
+ ssi->tag_end = file->data;
+ hs->ssi = ssi;
+ }
+ }
+#else /* LWIP_HTTPD_SSI */
+ LWIP_UNUSED_ARG(tag_check);
+#endif /* LWIP_HTTPD_SSI */
+ hs->handle = file;
+#if LWIP_HTTPD_CGI_SSI
+ if (params != NULL) {
+ /* URI contains parameters, call generic CGI handler */
+ int count;
+#if LWIP_HTTPD_CGI
+ if (http_cgi_paramcount >= 0) {
+ count = http_cgi_paramcount;
+ } else
+#endif
+ {
+ count = extract_uri_parameters(hs, params);
+ }
+ httpd_cgi_handler(file, uri, count, http_cgi_params, http_cgi_param_vals
+#if defined(LWIP_HTTPD_FILE_STATE) && LWIP_HTTPD_FILE_STATE
+ , file->state
+#endif /* LWIP_HTTPD_FILE_STATE */
+ );
+ }
+#else /* LWIP_HTTPD_CGI_SSI */
+ LWIP_UNUSED_ARG(params);
+#endif /* LWIP_HTTPD_CGI_SSI */
+ hs->file = file->data;
+ LWIP_ASSERT("File length must be positive!", (file->len >= 0));
+#if LWIP_HTTPD_CUSTOM_FILES
+ if (((file->flags & FS_FILE_FLAGS_CUSTOM) != 0) && (file->data == NULL)) {
+ /* custom file, need to read data first (via fs_read_custom) */
+ hs->left = 0;
+ } else
+#endif /* LWIP_HTTPD_CUSTOM_FILES */
+ {
+ hs->left = (u32_t)file->len;
+ }
+ hs->retries = 0;
+#if LWIP_HTTPD_TIMING
+ hs->time_started = sys_now();
+#endif /* LWIP_HTTPD_TIMING */
+#if !LWIP_HTTPD_DYNAMIC_HEADERS
+ LWIP_ASSERT("HTTP headers not included in file system",
+ (hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) != 0);
+#endif /* !LWIP_HTTPD_DYNAMIC_HEADERS */
+#if LWIP_HTTPD_SUPPORT_V09
+ if (is_09 && ((hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) != 0)) {
+ /* HTTP/0.9 responses are sent without HTTP header,
+ search for the end of the header. */
+ char *file_start = lwip_strnstr(hs->file, CRLF CRLF, hs->left);
+ if (file_start != NULL) {
+ int diff = file_start + 4 - hs->file;
+ hs->file += diff;
+ hs->left -= (u32_t)diff;
+ }
+ }
+#endif /* LWIP_HTTPD_SUPPORT_V09*/
+ } else {
+ hs->handle = NULL;
+ hs->file = NULL;
+ hs->left = 0;
+ hs->retries = 0;
+ }
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+ /* Determine the HTTP headers to send based on the file extension of
+ * the requested URI. */
+ if ((hs->handle == NULL) || ((hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) == 0)) {
+ get_http_headers(hs, uri);
+ }
+#else /* LWIP_HTTPD_DYNAMIC_HEADERS */
+ LWIP_UNUSED_ARG(uri);
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ if (hs->keepalive) {
+#if LWIP_HTTPD_SSI
+ if (hs->ssi != NULL) {
+ hs->keepalive = 0;
+ } else
+#endif /* LWIP_HTTPD_SSI */
+ {
+ if ((hs->handle != NULL) &&
+ ((hs->handle->flags & (FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT)) == FS_FILE_FLAGS_HEADER_INCLUDED)) {
+ hs->keepalive = 0;
+ }
+ }
+ }
+#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */
+ return ERR_OK;
+}
+
+/**
+ * The pcb had an error and is already deallocated.
+ * The argument might still be valid (if != NULL).
+ */
+static void
+http_err(void *arg, err_t err)
+{
+ struct http_state *hs = (struct http_state *)arg;
+ LWIP_UNUSED_ARG(err);
+
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_err: %s\n", lwip_strerr(err)));
+
+ if (hs != NULL) {
+ http_state_free(hs);
+ }
+}
+
+/**
+ * Data has been sent and acknowledged by the remote host.
+ * This means that more data can be sent.
+ */
+static err_t
+http_sent(void *arg, struct altcp_pcb *pcb, u16_t len)
+{
+ struct http_state *hs = (struct http_state *)arg;
+
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_sent %p\n", (void *)pcb));
+
+ LWIP_UNUSED_ARG(len);
+
+ if (hs == NULL) {
+ return ERR_OK;
+ }
+
+ hs->retries = 0;
+
+ http_send(pcb, hs);
+
+ return ERR_OK;
+}
+
+/**
+ * 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 2 seconds, close.
+ *
+ * This could be increased, but we don't want to waste resources for bad connections.
+ */
+static err_t
+http_poll(void *arg, struct altcp_pcb *pcb)
+{
+ struct http_state *hs = (struct http_state *)arg;
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_poll: pcb=%p hs=%p pcb_state=%s\n",
+ (void *)pcb, (void *)hs, tcp_debug_state_str(altcp_dbg_get_tcp_state(pcb))));
+
+ if (hs == NULL) {
+ err_t closed;
+ /* arg is null, close. */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: arg is NULL, close\n"));
+ closed = http_close_conn(pcb, NULL);
+ LWIP_UNUSED_ARG(closed);
+#if LWIP_HTTPD_ABORT_ON_CLOSE_MEM_ERROR
+ if (closed == ERR_MEM) {
+ altcp_abort(pcb);
+ return ERR_ABRT;
+ }
+#endif /* LWIP_HTTPD_ABORT_ON_CLOSE_MEM_ERROR */
+ return ERR_OK;
+ } else {
+ hs->retries++;
+ if (hs->retries == HTTPD_MAX_RETRIES) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_poll: too many retries, close\n"));
+ http_close_conn(pcb, hs);
+ return ERR_OK;
+ }
+
+ /* If this connection has a file open, try to send some more data. If
+ * it has not yet received a GET request, don't do this since it will
+ * cause the connection to close immediately. */
+ if (hs->handle) {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_poll: try to send more data\n"));
+ if (http_send(pcb, hs)) {
+ /* If we wrote anything to be sent, go ahead and send it now. */
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("tcp_output\n"));
+ altcp_output(pcb);
+ }
+ }
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * Data has been received on this pcb.
+ * For HTTP 1.0, this should normally only happen once (if the request fits in one packet).
+ */
+static err_t
+http_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err)
+{
+ struct http_state *hs = (struct http_state *)arg;
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: pcb=%p pbuf=%p err=%s\n", (void *)pcb,
+ (void *)p, lwip_strerr(err)));
+
+ if ((err != ERR_OK) || (p == NULL) || (hs == NULL)) {
+ /* error or closed by other side? */
+ if (p != NULL) {
+ /* Inform TCP that we have taken the data. */
+ altcp_recved(pcb, p->tot_len);
+ pbuf_free(p);
+ }
+ if (hs == NULL) {
+ /* this should not happen, only to be robust */
+ LWIP_DEBUGF(HTTPD_DEBUG, ("Error, http_recv: hs is NULL, close\n"));
+ }
+ http_close_conn(pcb, hs);
+ return ERR_OK;
+ }
+
+#if LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND
+ if (hs->no_auto_wnd) {
+ hs->unrecved_bytes += p->tot_len;
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_POST && LWIP_HTTPD_POST_MANUAL_WND */
+ {
+ /* Inform TCP that we have taken the data. */
+ altcp_recved(pcb, p->tot_len);
+ }
+
+#if LWIP_HTTPD_SUPPORT_POST
+ if (hs->post_content_len_left > 0) {
+ /* reset idle counter when POST data is received */
+ hs->retries = 0;
+ /* this is data for a POST, pass the complete pbuf to the application */
+ http_post_rxpbuf(hs, p);
+ /* pbuf is passed to the application, don't free it! */
+ if (hs->post_content_len_left == 0) {
+ /* all data received, send response or close connection */
+ http_send(pcb, hs);
+ }
+ return ERR_OK;
+ } else
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ {
+ if (hs->handle == NULL) {
+ err_t parsed = http_parse_request(p, hs, pcb);
+ LWIP_ASSERT("http_parse_request: unexpected return value", parsed == ERR_OK
+ || parsed == ERR_INPROGRESS || parsed == ERR_ARG || parsed == ERR_USE);
+#if LWIP_HTTPD_SUPPORT_REQUESTLIST
+ if (parsed != ERR_INPROGRESS) {
+ /* request fully parsed or error */
+ if (hs->req != NULL) {
+ pbuf_free(hs->req);
+ hs->req = NULL;
+ }
+ }
+#endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */
+ pbuf_free(p);
+ if (parsed == ERR_OK) {
+#if LWIP_HTTPD_SUPPORT_POST
+ if (hs->post_content_len_left == 0)
+#endif /* LWIP_HTTPD_SUPPORT_POST */
+ {
+ LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("http_recv: data %p len %"S32_F"\n", (const void *)hs->file, hs->left));
+ http_send(pcb, hs);
+ }
+ } else if (parsed == ERR_ARG) {
+ /* @todo: close on ERR_USE? */
+ http_close_conn(pcb, hs);
+ }
+ } else {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_recv: already sending data\n"));
+ /* already sending but still receiving data, we might want to RST here? */
+ pbuf_free(p);
+ }
+ }
+ return ERR_OK;
+}
+
+/**
+ * A new incoming connection has been accepted.
+ */
+static err_t
+http_accept(void *arg, struct altcp_pcb *pcb, err_t err)
+{
+ struct http_state *hs;
+ LWIP_UNUSED_ARG(err);
+ LWIP_UNUSED_ARG(arg);
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept %p / %p\n", (void *)pcb, arg));
+
+ if ((err != ERR_OK) || (pcb == NULL)) {
+ return ERR_VAL;
+ }
+
+ /* Set priority */
+ altcp_setprio(pcb, HTTPD_TCP_PRIO);
+
+ /* Allocate memory for the structure that holds the state of the
+ connection - initialized by that function. */
+ hs = http_state_alloc();
+ if (hs == NULL) {
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept: Out of memory, RST\n"));
+ return ERR_MEM;
+ }
+ hs->pcb = pcb;
+
+ /* Tell TCP that this is the structure we wish to be passed for our
+ callbacks. */
+ altcp_arg(pcb, hs);
+
+ /* Set up the various callback functions */
+ altcp_recv(pcb, http_recv);
+ altcp_err(pcb, http_err);
+ altcp_poll(pcb, http_poll, HTTPD_POLL_INTERVAL);
+ altcp_sent(pcb, http_sent);
+
+ return ERR_OK;
+}
+
+static void
+httpd_init_pcb(struct altcp_pcb *pcb, u16_t port)
+{
+ err_t err;
+
+ if (pcb) {
+ altcp_setprio(pcb, HTTPD_TCP_PRIO);
+ /* set SOF_REUSEADDR here to explicitly bind httpd to multiple interfaces */
+ err = altcp_bind(pcb, IP_ANY_TYPE, port);
+ LWIP_UNUSED_ARG(err); /* in case of LWIP_NOASSERT */
+ LWIP_ASSERT("httpd_init: tcp_bind failed", err == ERR_OK);
+ pcb = altcp_listen(pcb);
+ LWIP_ASSERT("httpd_init: tcp_listen failed", pcb != NULL);
+ altcp_accept(pcb, http_accept);
+ }
+}
+
+/**
+ * @ingroup httpd
+ * Initialize the httpd: set up a listening PCB and bind it to the defined port
+ */
+void
+httpd_init(void)
+{
+ struct altcp_pcb *pcb;
+
+#if HTTPD_USE_MEM_POOL
+ LWIP_MEMPOOL_INIT(HTTPD_STATE);
+#if LWIP_HTTPD_SSI
+ LWIP_MEMPOOL_INIT(HTTPD_SSI_STATE);
+#endif
+#endif
+ LWIP_DEBUGF(HTTPD_DEBUG, ("httpd_init\n"));
+
+ /* LWIP_ASSERT_CORE_LOCKED(); is checked by tcp_new() */
+
+ pcb = altcp_tcp_new_ip_type(IPADDR_TYPE_ANY);
+ LWIP_ASSERT("httpd_init: tcp_new failed", pcb != NULL);
+ httpd_init_pcb(pcb, HTTPD_SERVER_PORT);
+}
+
+#if HTTPD_ENABLE_HTTPS
+/**
+ * @ingroup httpd
+ * Initialize the httpd: set up a listening PCB and bind it to the defined port.
+ * Also set up TLS connection handling (HTTPS).
+ */
+void
+httpd_inits(struct altcp_tls_config *conf)
+{
+#if LWIP_ALTCP_TLS
+ struct altcp_pcb *pcb_tls = altcp_tls_new(conf, IPADDR_TYPE_ANY);
+ LWIP_ASSERT("httpd_init: altcp_tls_new failed", pcb_tls != NULL);
+ httpd_init_pcb(pcb_tls, HTTPD_SERVER_PORT_HTTPS);
+#else /* LWIP_ALTCP_TLS */
+ LWIP_UNUSED_ARG(conf);
+#endif /* LWIP_ALTCP_TLS */
+}
+#endif /* HTTPD_ENABLE_HTTPS */
+
+#if LWIP_HTTPD_SSI
+/**
+ * @ingroup httpd
+ * Set the SSI handler function.
+ *
+ * @param ssi_handler the SSI handler function
+ * @param tags an array of SSI tag strings to search for in SSI-enabled files
+ * @param num_tags number of tags in the 'tags' array
+ */
+void
+http_set_ssi_handler(tSSIHandler ssi_handler, const char **tags, int num_tags)
+{
+ LWIP_DEBUGF(HTTPD_DEBUG, ("http_set_ssi_handler\n"));
+
+ LWIP_ASSERT("no ssi_handler given", ssi_handler != NULL);
+ httpd_ssi_handler = ssi_handler;
+
+#if LWIP_HTTPD_SSI_RAW
+ LWIP_UNUSED_ARG(tags);
+ LWIP_UNUSED_ARG(num_tags);
+#else /* LWIP_HTTPD_SSI_RAW */
+ LWIP_ASSERT("no tags given", tags != NULL);
+ LWIP_ASSERT("invalid number of tags", num_tags > 0);
+
+ httpd_tags = tags;
+ httpd_num_tags = num_tags;
+#endif /* !LWIP_HTTPD_SSI_RAW */
+}
+#endif /* LWIP_HTTPD_SSI */
+
+#if LWIP_HTTPD_CGI
+/**
+ * @ingroup httpd
+ * Set an array of CGI filenames/handler functions
+ *
+ * @param cgis an array of CGI filenames/handler functions
+ * @param num_handlers number of elements in the 'cgis' array
+ */
+void
+http_set_cgi_handlers(const tCGI *cgis, int num_handlers)
+{
+ LWIP_ASSERT("no cgis given", cgis != NULL);
+ LWIP_ASSERT("invalid number of handlers", num_handlers > 0);
+
+ httpd_cgis = cgis;
+ httpd_num_cgis = num_handlers;
+}
+#endif /* LWIP_HTTPD_CGI */
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API */
diff --git a/src/apps/http/httpd_structs.h b/src/apps/http/httpd_structs.h
new file mode 100644
index 00000000000..aa5bce2ffbb
--- /dev/null
+++ b/src/apps/http/httpd_structs.h
@@ -0,0 +1,123 @@
+#ifndef LWIP_HTTPD_STRUCTS_H
+#define LWIP_HTTPD_STRUCTS_H
+
+#include "lwip/apps/httpd.h"
+
+#if LWIP_HTTPD_DYNAMIC_HEADERS
+/** This struct is used for a list of HTTP header strings for various
+ * filename extensions. */
+typedef struct {
+ const char *extension;
+ const char *content_type;
+} tHTTPHeader;
+
+/** A list of strings used in HTTP headers (see RFC 1945 HTTP/1.0 and
+ * RFC 2616 HTTP/1.1 for header field definitions) */
+static const char *const g_psHTTPHeaderStrings[] = {
+ "HTTP/1.0 200 OK\r\n",
+ "HTTP/1.0 404 File not found\r\n",
+ "HTTP/1.0 400 Bad Request\r\n",
+ "HTTP/1.0 501 Not Implemented\r\n",
+ "HTTP/1.1 200 OK\r\n",
+ "HTTP/1.1 404 File not found\r\n",
+ "HTTP/1.1 400 Bad Request\r\n",
+ "HTTP/1.1 501 Not Implemented\r\n",
+ "Content-Length: ",
+ "Connection: Close\r\n",
+ "Connection: keep-alive\r\n",
+ "Connection: keep-alive\r\nContent-Length: ",
+ "Server: "HTTPD_SERVER_AGENT"\r\n",
+ "\r\n<html><body><h2>404: The requested file cannot be found.</h2></body></html>\r\n"
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+ , "Connection: keep-alive\r\nContent-Length: 77\r\n\r\n<html><body><h2>404: The requested file cannot be found.</h2></body></html>\r\n"
+#endif
+};
+
+/* Indexes into the g_psHTTPHeaderStrings array */
+#define HTTP_HDR_OK 0 /* 200 OK */
+#define HTTP_HDR_NOT_FOUND 1 /* 404 File not found */
+#define HTTP_HDR_BAD_REQUEST 2 /* 400 Bad request */
+#define HTTP_HDR_NOT_IMPL 3 /* 501 Not Implemented */
+#define HTTP_HDR_OK_11 4 /* 200 OK */
+#define HTTP_HDR_NOT_FOUND_11 5 /* 404 File not found */
+#define HTTP_HDR_BAD_REQUEST_11 6 /* 400 Bad request */
+#define HTTP_HDR_NOT_IMPL_11 7 /* 501 Not Implemented */
+#define HTTP_HDR_CONTENT_LENGTH 8 /* Content-Length: (HTTP 1.0)*/
+#define HTTP_HDR_CONN_CLOSE 9 /* Connection: Close (HTTP 1.1) */
+#define HTTP_HDR_CONN_KEEPALIVE 10 /* Connection: keep-alive (HTTP 1.1) */
+#define HTTP_HDR_KEEPALIVE_LEN 11 /* Connection: keep-alive + Content-Length: (HTTP 1.1)*/
+#define HTTP_HDR_SERVER 12 /* Server: HTTPD_SERVER_AGENT */
+#define DEFAULT_404_HTML 13 /* default 404 body */
+#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE
+#define DEFAULT_404_HTML_PERSISTENT 14 /* default 404 body, but including Connection: keep-alive */
+#endif
+
+#define HTTP_CONTENT_TYPE(contenttype) "Content-Type: "contenttype"\r\n\r\n"
+#define HTTP_CONTENT_TYPE_ENCODING(contenttype, encoding) "Content-Type: "contenttype"\r\nContent-Encoding: "encoding"\r\n\r\n"
+
+#define HTTP_HDR_HTML HTTP_CONTENT_TYPE("text/html")
+#define HTTP_HDR_SSI HTTP_CONTENT_TYPE("text/html\r\nExpires: Fri, 10 Apr 2008 14:00:00 GMT\r\nPragma: no-cache")
+#define HTTP_HDR_GIF HTTP_CONTENT_TYPE("image/gif")
+#define HTTP_HDR_PNG HTTP_CONTENT_TYPE("image/png")
+#define HTTP_HDR_JPG HTTP_CONTENT_TYPE("image/jpeg")
+#define HTTP_HDR_BMP HTTP_CONTENT_TYPE("image/bmp")
+#define HTTP_HDR_ICO HTTP_CONTENT_TYPE("image/x-icon")
+#define HTTP_HDR_APP HTTP_CONTENT_TYPE("application/octet-stream")
+#define HTTP_HDR_JS HTTP_CONTENT_TYPE("application/javascript")
+#define HTTP_HDR_RA HTTP_CONTENT_TYPE("application/javascript")
+#define HTTP_HDR_CSS HTTP_CONTENT_TYPE("text/css")
+#define HTTP_HDR_SWF HTTP_CONTENT_TYPE("application/x-shockwave-flash")
+#define HTTP_HDR_XML HTTP_CONTENT_TYPE("text/xml")
+#define HTTP_HDR_PDF HTTP_CONTENT_TYPE("application/pdf")
+#define HTTP_HDR_JSON HTTP_CONTENT_TYPE("application/json")
+#define HTTP_HDR_CSV HTTP_CONTENT_TYPE("text/csv")
+#define HTTP_HDR_TSV HTTP_CONTENT_TYPE("text/tsv")
+#define HTTP_HDR_SVG HTTP_CONTENT_TYPE("image/svg+xml")
+#define HTTP_HDR_SVGZ HTTP_CONTENT_TYPE_ENCODING("image/svg+xml", "gzip")
+
+#define HTTP_HDR_DEFAULT_TYPE HTTP_CONTENT_TYPE("text/plain")
+
+/** A list of extension-to-HTTP header strings (see outdated RFC 1700 MEDIA TYPES
+ * and http://www.iana.org/assignments/media-types for registered content types
+ * and subtypes) */
+static const tHTTPHeader g_psHTTPHeaders[] = {
+ { "html", HTTP_HDR_HTML},
+ { "htm", HTTP_HDR_HTML},
+ { "shtml", HTTP_HDR_SSI},
+ { "shtm", HTTP_HDR_SSI},
+ { "ssi", HTTP_HDR_SSI},
+ { "gif", HTTP_HDR_GIF},
+ { "png", HTTP_HDR_PNG},
+ { "jpg", HTTP_HDR_JPG},
+ { "bmp", HTTP_HDR_BMP},
+ { "ico", HTTP_HDR_ICO},
+ { "class", HTTP_HDR_APP},
+ { "cls", HTTP_HDR_APP},
+ { "js", HTTP_HDR_JS},
+ { "ram", HTTP_HDR_RA},
+ { "css", HTTP_HDR_CSS},
+ { "swf", HTTP_HDR_SWF},
+ { "xml", HTTP_HDR_XML},
+ { "xsl", HTTP_HDR_XML},
+ { "pdf", HTTP_HDR_PDF},
+ { "json", HTTP_HDR_JSON}
+#ifdef HTTPD_ADDITIONAL_CONTENT_TYPES
+ /* If you need to add content types not listed here:
+ * #define HTTPD_ADDITIONAL_CONTENT_TYPES {"ct1", HTTP_CONTENT_TYPE("text/ct1")}, {"exe", HTTP_CONTENT_TYPE("application/exe")}
+ */
+ , HTTPD_ADDITIONAL_CONTENT_TYPES
+#endif
+};
+
+#define NUM_HTTP_HEADERS LWIP_ARRAYSIZE(g_psHTTPHeaders)
+
+#endif /* LWIP_HTTPD_DYNAMIC_HEADERS */
+
+#if LWIP_HTTPD_SSI && LWIP_HTTPD_SSI_BY_FILE_EXTENSION
+static const char *const g_pcSSIExtensions[] = {
+ LWIP_HTTPD_SSI_EXTENSIONS
+};
+#define NUM_SHTML_EXTENSIONS LWIP_ARRAYSIZE(g_pcSSIExtensions)
+#endif /* LWIP_HTTPD_SSI && LWIP_HTTPD_SSI_BY_FILE_EXTENSION */
+
+#endif /* LWIP_HTTPD_STRUCTS_H */
diff --git a/src/apps/http/makefsdata/makefsdata b/src/apps/http/makefsdata/makefsdata
new file mode 100644
index 00000000000..667eb888513
--- /dev/null
+++ b/src/apps/http/makefsdata/makefsdata
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+
+open(OUTPUT, "> fsdata.c");
+
+chdir("fs");
+open(FILES, "find . -type f |");
+
+while($file = <FILES>) {
+
+ # Do not include files in CVS directories nor backup files.
+ if($file =~ /(CVS|~)/) {
+ next;
+ }
+
+ chop($file);
+
+ open(HEADER, "> /tmp/header") || die $!;
+ if($file =~ /404/) {
+ print(HEADER "HTTP/1.0 404 File not found\r\n");
+ } else {
+ print(HEADER "HTTP/1.0 200 OK\r\n");
+ }
+ print(HEADER "Server: lwIP/pre-0.6 (http://www.sics.se/~adam/lwip/)\r\n");
+ if($file =~ /\.html$/) {
+ print(HEADER "Content-type: text/html\r\n");
+ } elsif($file =~ /\.gif$/) {
+ print(HEADER "Content-type: image/gif\r\n");
+ } elsif($file =~ /\.png$/) {
+ print(HEADER "Content-type: image/png\r\n");
+ } elsif($file =~ /\.jpg$/) {
+ print(HEADER "Content-type: image/jpeg\r\n");
+ } elsif($file =~ /\.class$/) {
+ print(HEADER "Content-type: application/octet-stream\r\n");
+ } elsif($file =~ /\.ram$/) {
+ print(HEADER "Content-type: audio/x-pn-realaudio\r\n");
+ } else {
+ print(HEADER "Content-type: text/plain\r\n");
+ }
+ print(HEADER "\r\n");
+ close(HEADER);
+
+ unless($file =~ /\.plain$/ || $file =~ /cgi/) {
+ system("cat /tmp/header $file > /tmp/file");
+ } else {
+ system("cp $file /tmp/file");
+ }
+
+ open(FILE, "/tmp/file");
+ unlink("/tmp/file");
+ unlink("/tmp/header");
+
+ $file =~ s/\.//;
+ $fvar = $file;
+ $fvar =~ s-/-_-g;
+ $fvar =~ s-\.-_-g;
+ print(OUTPUT "static const unsigned char data".$fvar."[] = {\n");
+ print(OUTPUT "\t/* $file */\n\t");
+ for($j = 0; $j < length($file); $j++) {
+ printf(OUTPUT "%#02x, ", unpack("C", substr($file, $j, 1)));
+ }
+ printf(OUTPUT "0,\n");
+
+
+ $i = 0;
+ while(read(FILE, $data, 1)) {
+ if($i == 0) {
+ print(OUTPUT "\t");
+ }
+ printf(OUTPUT "%#02x, ", unpack("C", $data));
+ $i++;
+ if($i == 10) {
+ print(OUTPUT "\n");
+ $i = 0;
+ }
+ }
+ print(OUTPUT "};\n\n");
+ close(FILE);
+ push(@fvars, $fvar);
+ push(@files, $file);
+}
+
+for($i = 0; $i < @fvars; $i++) {
+ $file = $files[$i];
+ $fvar = $fvars[$i];
+
+ if($i == 0) {
+ $prevfile = "NULL";
+ } else {
+ $prevfile = "file" . $fvars[$i - 1];
+ }
+ print(OUTPUT "const struct fsdata_file file".$fvar."[] = {{$prevfile, data$fvar, ");
+ print(OUTPUT "data$fvar + ". (length($file) + 1) .", ");
+ print(OUTPUT "sizeof(data$fvar) - ". (length($file) + 1) .", FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT}};\n\n");
+}
+
+print(OUTPUT "#define FS_ROOT file$fvars[$i - 1]\n\n");
+print(OUTPUT "#define FS_NUMFILES $i\n");
diff --git a/src/apps/http/makefsdata/makefsdata.c b/src/apps/http/makefsdata/makefsdata.c
new file mode 100644
index 00000000000..240c72e4be8
--- /dev/null
+++ b/src/apps/http/makefsdata/makefsdata.c
@@ -0,0 +1,1307 @@
+/**
+ * makefsdata: Converts a directory structure for use with the lwIP httpd.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Jim Pettinato
+ * Simon Goldschmidt
+ *
+ * @todo:
+ * - take TCP_MSS, LWIP_TCP_TIMESTAMPS and
+ * PAYLOAD_ALIGN_TYPE/PAYLOAD_ALIGNMENT as arguments
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#include "tinydir.h"
+
+/** Makefsdata can generate *all* files deflate-compressed (where file size shrinks).
+ * Since nearly all browsers support this, this is a good way to reduce ROM size.
+ * To compress the files, "miniz.c" must be downloaded separately OR
+ * MAKEFS_SUPPORT_DEFLATE_ZLIB must be set and the zlib library and headers
+ * must be present on the system compiling this program.
+ */
+#ifndef MAKEFS_SUPPORT_DEFLATE
+#define MAKEFS_SUPPORT_DEFLATE 0
+#ifndef MAKEFS_SUPPORT_DEFLATE_ZLIB
+#define MAKEFS_SUPPORT_DEFLATE_ZLIB 0
+#endif /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+#endif /* MAKEFS_SUPPORT_DEFLATE */
+
+#define COPY_BUFSIZE (1024*1024) /* 1 MByte */
+
+#if MAKEFS_SUPPORT_DEFLATE
+#if MAKEFS_SUPPORT_DEFLATE_ZLIB
+#include <zlib.h>
+#else
+#include "../miniz.c"
+#endif /* MAKEFS_SUPPORT_DEFLATE */
+
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef unsigned int uint;
+
+#define my_max(a,b) (((a) > (b)) ? (a) : (b))
+#define my_min(a,b) (((a) < (b)) ? (a) : (b))
+
+/* COMP_OUT_BUF_SIZE is the size of the output buffer used during compression.
+ COMP_OUT_BUF_SIZE must be >= 1 and <= OUT_BUF_SIZE */
+#define COMP_OUT_BUF_SIZE COPY_BUFSIZE
+
+/* OUT_BUF_SIZE is the size of the output buffer used during decompression.
+ OUT_BUF_SIZE must be a power of 2 >= TINFL_LZ_DICT_SIZE (because the low-level decompressor not only writes, but reads from the output buffer as it decompresses) */
+#define OUT_BUF_SIZE COPY_BUFSIZE
+static uint8 s_outbuf[OUT_BUF_SIZE];
+static uint8 s_checkbuf[OUT_BUF_SIZE];
+
+#ifndef MAKEFS_SUPPORT_DEFLATE_ZLIB
+/* tdefl_compressor contains all the state needed by the low-level compressor so it's a pretty big struct (~300k).
+ This example makes it a global vs. putting it on the stack, of course in real-world usage you'll probably malloc() or new it. */
+tdefl_compressor g_deflator;
+#endif /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+
+static int deflate_level; /* default compression level, can be changed via command line */
+#define USAGE_ARG_DEFLATE " [-defl<:compr_level>]"
+#else /* MAKEFS_SUPPORT_DEFLATE */
+#define USAGE_ARG_DEFLATE ""
+#endif /* MAKEFS_SUPPORT_DEFLATE */
+
+#ifdef WIN32
+
+#define GETCWD(path, len) GetCurrentDirectoryA(len, path)
+#define GETCWD_SUCCEEDED(ret) (ret != 0)
+#define CHDIR(path) SetCurrentDirectoryA(path)
+#define CHDIR_SUCCEEDED(ret) (ret == TRUE)
+
+#elif __linux__
+
+#define GETCWD(path, len) getcwd(path, len)
+#define GETCWD_SUCCEEDED(ret) (ret != NULL)
+#define CHDIR(path) chdir(path)
+#define CHDIR_SUCCEEDED(ret) (ret == 0)
+
+#else
+
+#error makefsdata not supported on this platform
+
+#endif
+
+#define NEWLINE "\r\n"
+#define NEWLINE_LEN 2
+
+/* Define this here since we don't include any external C files and ports might override it */
+#define LWIP_PLATFORM_ASSERT(x) do {printf("Assertion \"%s\" failed at line %d in %s\n", \
+ x, __LINE__, __FILE__); fflush(NULL); abort();} while(0)
+
+/* define this to get the header variables we use to build HTTP headers */
+#define LWIP_HTTPD_DYNAMIC_HEADERS 1
+#define LWIP_HTTPD_SSI 1
+#include "lwip/init.h"
+#include "../httpd_structs.h"
+#include "lwip/apps/fs.h"
+
+#include "../core/inet_chksum.c"
+#include "../core/def.c"
+
+/** (Your server name here) */
+static const char *serverID = "Server: "HTTPD_SERVER_AGENT"\r\n";
+static char serverIDBuffer[1024];
+
+/* change this to suit your MEM_ALIGNMENT */
+#define PAYLOAD_ALIGNMENT 4
+/* set this to 0 to prevent aligning payload */
+#define ALIGN_PAYLOAD 1
+/* define this to a type that has the required alignment */
+#define PAYLOAD_ALIGN_TYPE "unsigned int"
+static int payload_alingment_dummy_counter = 0;
+
+#define HEX_BYTES_PER_LINE 16
+
+#define MAX_PATH_LEN 256
+
+struct file_entry {
+ struct file_entry *next;
+ const char *filename_c;
+};
+
+int process_sub(FILE *data_file, FILE *struct_file);
+int process_file(FILE *data_file, FILE *struct_file, const char *filename);
+int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t *http_hdr_len,
+ u16_t *http_hdr_chksum, u8_t provide_content_len, int is_compressed);
+int file_put_ascii(FILE *file, const char *ascii_string, int len, int *i);
+int s_put_ascii(char *buf, const char *ascii_string, int len, int *i);
+void concat_files(const char *file1, const char *file2, const char *targetfile);
+int check_path(char *path, size_t size);
+static int checkSsiByFilelist(const char* filename_listfile);
+static int ext_in_list(const char* filename, const char *ext_list);
+static int file_to_exclude(const char* filename);
+static int file_can_be_compressed(const char* filename);
+
+/* 5 bytes per char + 3 bytes per line */
+static char file_buffer_c[COPY_BUFSIZE * 5 + ((COPY_BUFSIZE / HEX_BYTES_PER_LINE) * 3)];
+
+static char curSubdir[MAX_PATH_LEN-3];
+static char lastFileVar[MAX_PATH_LEN];
+static char hdr_buf[4096];
+
+static unsigned char processSubs = 1;
+static unsigned char includeHttpHeader = 1;
+static unsigned char useHttp11 = 0;
+static unsigned char supportSsi = 1;
+static unsigned char precalcChksum = 0;
+static unsigned char includeLastModified = 0;
+#if MAKEFS_SUPPORT_DEFLATE
+static unsigned char deflateNonSsiFiles = 0;
+static size_t deflatedBytesReduced = 0;
+static size_t overallDataBytes = 0;
+#endif
+static const char *exclude_list = NULL;
+static const char *ncompress_list = NULL;
+
+static struct file_entry *first_file = NULL;
+static struct file_entry *last_file = NULL;
+
+static char *ssi_file_buffer;
+static char **ssi_file_lines;
+static size_t ssi_file_num_lines;
+
+static void print_usage(void)
+{
+ printf(" Usage: htmlgen [targetdir] [-s] [-e] [-11] [-nossi] [-ssi:<filename>] [-c] [-f:<filename>] [-m] [-svr:<name>] [-x:<ext_list>] [-xc:<ext_list>" USAGE_ARG_DEFLATE NEWLINE NEWLINE);
+ printf(" targetdir: relative or absolute path to files to convert" NEWLINE);
+ printf(" switch -s: toggle processing of subdirectories (default is on)" NEWLINE);
+ printf(" switch -e: exclude HTTP header from file (header is created at runtime, default is off)" NEWLINE);
+ printf(" switch -11: include HTTP 1.1 header (1.0 is default)" NEWLINE);
+ printf(" switch -nossi: no support for SSI (cannot calculate Content-Length for SSI)" NEWLINE);
+ printf(" switch -ssi: ssi filename (ssi support controlled by file list, not by extension)" NEWLINE);
+ printf(" switch -c: precalculate checksums for all pages (default is off)" NEWLINE);
+ printf(" switch -f: target filename (default is \"fsdata.c\")" NEWLINE);
+ printf(" switch -m: include \"Last-Modified\" header based on file time" NEWLINE);
+ printf(" switch -svr: server identifier sent in HTTP response header ('Server' field)" NEWLINE);
+ printf(" switch -x: comma separated list of extensions of files to exclude (e.g., -x:json,txt)" NEWLINE);
+ printf(" switch -xc: comma separated list of extensions of files to not compress (e.g., -xc:mp3,jpg)" NEWLINE);
+#if MAKEFS_SUPPORT_DEFLATE
+ printf(" switch -defl: deflate-compress all non-SSI files (with opt. compr.-level, default=10)" NEWLINE);
+ printf(" ATTENTION: browser has to support \"Content-Encoding: deflate\"!" NEWLINE);
+#endif
+ printf(" if targetdir not specified, htmlgen will attempt to" NEWLINE);
+ printf(" process files in subdirectory 'fs'" NEWLINE);
+}
+
+int main(int argc, char *argv[])
+{
+ char path[MAX_PATH_LEN];
+ char appPath[MAX_PATH_LEN];
+ FILE *data_file;
+ FILE *struct_file;
+ int filesProcessed;
+ int i;
+ char targetfile[MAX_PATH_LEN];
+ strcpy(targetfile, "fsdata.c");
+
+ memset(path, 0, sizeof(path));
+ memset(appPath, 0, sizeof(appPath));
+
+ printf(NEWLINE " makefsdata v" LWIP_VERSION_STRING " - HTML to C source converter" NEWLINE);
+ printf(" by Jim Pettinato - circa 2003 " NEWLINE);
+ printf(" extended by Simon Goldschmidt - 2009 " NEWLINE NEWLINE);
+
+ LWIP_ASSERT("sizeof(hdr_buf) must fit into an u16_t", sizeof(hdr_buf) <= 0xffff);
+
+ strcpy(path, "fs");
+ for (i = 1; i < argc; i++) {
+ if (argv[i] == NULL) {
+ continue;
+ }
+ if (argv[i][0] == '-') {
+ if (strstr(argv[i], "-svr:") == argv[i]) {
+ snprintf(serverIDBuffer, sizeof(serverIDBuffer), "Server: %s\r\n", &argv[i][5]);
+ serverID = serverIDBuffer;
+ printf("Using Server-ID: \"%s\"\n", serverID);
+ } else if (!strcmp(argv[i], "-s")) {
+ processSubs = 0;
+ } else if (!strcmp(argv[i], "-e")) {
+ includeHttpHeader = 0;
+ } else if (!strcmp(argv[i], "-11")) {
+ useHttp11 = 1;
+ } else if (!strcmp(argv[i], "-nossi")) {
+ supportSsi = 0;
+ } else if (strstr(argv[i], "-ssi:") == argv[i]) {
+ const char* ssi_list_filename = &argv[i][5];
+ if (checkSsiByFilelist(ssi_list_filename)) {
+ printf("Reading list of SSI files from \"%s\"\n", ssi_list_filename);
+ } else {
+ printf("Failed to load list of SSI files from \"%s\"\n", ssi_list_filename);
+ }
+ } else if (!strcmp(argv[i], "-c")) {
+ precalcChksum = 1;
+ } else if (strstr(argv[i], "-f:") == argv[i]) {
+ strncpy(targetfile, &argv[i][3], sizeof(targetfile) - 1);
+ targetfile[sizeof(targetfile) - 1] = 0;
+ printf("Writing to file \"%s\"\n", targetfile);
+ } else if (!strcmp(argv[i], "-m")) {
+ includeLastModified = 1;
+ } else if (strstr(argv[i], "-defl") == argv[i]) {
+#if MAKEFS_SUPPORT_DEFLATE
+ const char *colon = &argv[i][5];
+ if (*colon == ':') {
+ int defl_level = atoi(&colon[1]);
+ if ((colon[1] != 0) && (defl_level >= 0) && (defl_level <= 10)) {
+ deflate_level = defl_level;
+ } else {
+ printf("ERROR: deflate level must be [0..10]" NEWLINE);
+ exit(0);
+ }
+ } else {
+ /* default to highest compression */
+ deflate_level = 10;
+ }
+ deflateNonSsiFiles = 1;
+ printf("Deflating all non-SSI files with level %d (but only if size is reduced)" NEWLINE, deflate_level);
+#else
+ printf("WARNING: Deflate support is disabled\n");
+#endif
+ } else if (strstr(argv[i], "-x:") == argv[i]) {
+ exclude_list = &argv[i][3];
+ printf("Excluding files with extensions %s" NEWLINE, exclude_list);
+ } else if (strstr(argv[i], "-xc:") == argv[i]) {
+ ncompress_list = &argv[i][4];
+ printf("Skipping compression for files with extensions %s" NEWLINE, ncompress_list);
+ } else if ((strstr(argv[i], "-?")) || (strstr(argv[i], "-h"))) {
+ print_usage();
+ exit(0);
+ }
+ } else if ((argv[i][0] == '/') && (argv[i][1] == '?') && (argv[i][2] == 0)) {
+ print_usage();
+ exit(0);
+ } else {
+ strncpy(path, argv[i], sizeof(path) - 1);
+ path[sizeof(path) - 1] = 0;
+ }
+ }
+
+ if (!check_path(path, sizeof(path))) {
+ printf("Invalid path: \"%s\"." NEWLINE, path);
+ exit(-1);
+ }
+
+ if(!GETCWD_SUCCEEDED(GETCWD(appPath, MAX_PATH_LEN))) {
+ printf("Unable to get current dir." NEWLINE);
+ exit(-1);
+ }
+ /* if command line param or subdir named 'fs' not found spout usage verbiage */
+ if (!CHDIR_SUCCEEDED(CHDIR(path))) {
+ /* if no subdir named 'fs' (or the one which was given) exists, spout usage verbiage */
+ printf(" Failed to open directory \"%s\"." NEWLINE NEWLINE, path);
+ print_usage();
+ exit(-1);
+ }
+ if(!CHDIR_SUCCEEDED(CHDIR(appPath))) {
+ printf("Invalid path: \"%s\"." NEWLINE, appPath);
+ exit(-1);
+ }
+
+ printf("HTTP %sheader will %s statically included." NEWLINE,
+ (includeHttpHeader ? (useHttp11 ? "1.1 " : "1.0 ") : ""),
+ (includeHttpHeader ? "be" : "not be"));
+
+ curSubdir[0] = '\0'; /* start off in web page's root directory - relative paths */
+ printf(" Processing all files in directory %s", path);
+ if (processSubs) {
+ printf(" and subdirectories..." NEWLINE NEWLINE);
+ } else {
+ printf("..." NEWLINE NEWLINE);
+ }
+
+ data_file = fopen("fsdata.tmp", "wb");
+ if (data_file == NULL) {
+ printf("Failed to create file \"fsdata.tmp\"\n");
+ exit(-1);
+ }
+ struct_file = fopen("fshdr.tmp", "wb");
+ if (struct_file == NULL) {
+ printf("Failed to create file \"fshdr.tmp\"\n");
+ fclose(data_file);
+ exit(-1);
+ }
+
+ if(!CHDIR_SUCCEEDED(CHDIR(path))) {
+ printf("Invalid path: \"%s\"." NEWLINE, path);
+ exit(-1);
+ }
+
+ fprintf(data_file, "#include \"lwip/apps/fs.h\"" NEWLINE);
+ fprintf(data_file, "#include \"lwip/def.h\"" NEWLINE NEWLINE NEWLINE);
+
+ fprintf(data_file, "#define file_NULL (struct fsdata_file *) NULL" NEWLINE NEWLINE NEWLINE);
+ /* define FS_FILE_FLAGS_HEADER_INCLUDED to 1 if not defined (compatibility with older httpd/fs) */
+ fprintf(data_file, "#ifndef FS_FILE_FLAGS_HEADER_INCLUDED" NEWLINE "#define FS_FILE_FLAGS_HEADER_INCLUDED 1" NEWLINE "#endif" NEWLINE);
+ /* define FS_FILE_FLAGS_HEADER_PERSISTENT to 0 if not defined (compatibility with older httpd/fs: wasn't supported back then) */
+ fprintf(data_file, "#ifndef FS_FILE_FLAGS_HEADER_PERSISTENT" NEWLINE "#define FS_FILE_FLAGS_HEADER_PERSISTENT 0" NEWLINE "#endif" NEWLINE);
+
+ /* define alignment defines */
+#if ALIGN_PAYLOAD
+ fprintf(data_file, "/* FSDATA_FILE_ALIGNMENT: 0=off, 1=by variable, 2=by include */" NEWLINE "#ifndef FSDATA_FILE_ALIGNMENT" NEWLINE "#define FSDATA_FILE_ALIGNMENT 0" NEWLINE "#endif" NEWLINE);
+#endif
+ fprintf(data_file, "#ifndef FSDATA_ALIGN_PRE" NEWLINE "#define FSDATA_ALIGN_PRE" NEWLINE "#endif" NEWLINE);
+ fprintf(data_file, "#ifndef FSDATA_ALIGN_POST" NEWLINE "#define FSDATA_ALIGN_POST" NEWLINE "#endif" NEWLINE);
+#if ALIGN_PAYLOAD
+ fprintf(data_file, "#if FSDATA_FILE_ALIGNMENT==2" NEWLINE "#include \"fsdata_alignment.h\"" NEWLINE "#endif" NEWLINE);
+#endif
+
+ sprintf(lastFileVar, "NULL");
+
+ filesProcessed = process_sub(data_file, struct_file);
+
+ /* data_file now contains all of the raw data.. now append linked list of
+ * file header structs to allow embedded app to search for a file name */
+ fprintf(data_file, NEWLINE NEWLINE);
+ fprintf(struct_file, "#define FS_ROOT file_%s" NEWLINE, lastFileVar);
+ fprintf(struct_file, "#define FS_NUMFILES %d" NEWLINE NEWLINE, filesProcessed);
+
+ fclose(data_file);
+ fclose(struct_file);
+
+ if(!CHDIR_SUCCEEDED(CHDIR(appPath))) {
+ printf("Invalid path: \"%s\"." NEWLINE, appPath);
+ exit(-1);
+ }
+
+ /* append struct_file to data_file */
+ printf(NEWLINE "Creating target file..." NEWLINE NEWLINE);
+ concat_files("fsdata.tmp", "fshdr.tmp", targetfile);
+
+ /* if succeeded, delete the temporary files */
+ if (remove("fsdata.tmp") != 0) {
+ printf("Warning: failed to delete fsdata.tmp\n");
+ }
+ if (remove("fshdr.tmp") != 0) {
+ printf("Warning: failed to delete fshdr.tmp\n");
+ }
+
+ printf(NEWLINE "Processed %d files - done." NEWLINE, filesProcessed);
+#if MAKEFS_SUPPORT_DEFLATE
+ if (deflateNonSsiFiles) {
+ printf("(Deflated total byte reduction: %d bytes -> %d bytes (%.02f%%)" NEWLINE,
+ (int)overallDataBytes, (int)deflatedBytesReduced, (float)((deflatedBytesReduced * 100.0) / overallDataBytes));
+ }
+#endif
+ printf(NEWLINE);
+
+ while (first_file != NULL) {
+ struct file_entry *fe = first_file;
+ first_file = fe->next;
+ free(fe);
+ }
+
+ if (ssi_file_buffer) {
+ free(ssi_file_buffer);
+ }
+ if (ssi_file_lines) {
+ free(ssi_file_lines);
+ }
+
+ return 0;
+}
+
+int check_path(char *path, size_t size)
+{
+ size_t slen;
+ if (path[0] == 0) {
+ /* empty */
+ return 0;
+ }
+ slen = strlen(path);
+ if (slen >= size) {
+ /* not NULL-terminated */
+ return 0;
+ }
+ while ((slen > 0) && ((path[slen] == '\\') || (path[slen] == '/'))) {
+ /* path should not end with trailing backslash */
+ path[slen] = 0;
+ slen--;
+ }
+ if (slen == 0) {
+ return 0;
+ }
+ return 1;
+}
+
+static void copy_file(const char *filename_in, FILE *fout)
+{
+ FILE *fin;
+ size_t len;
+ void *buf;
+ fin = fopen(filename_in, "rb");
+ if (fin == NULL) {
+ printf("Failed to open file \"%s\"\n", filename_in);
+ exit(-1);
+ }
+ buf = malloc(COPY_BUFSIZE);
+ while ((len = fread(buf, 1, COPY_BUFSIZE, fin)) > 0) {
+ fwrite(buf, 1, len, fout);
+ }
+ free(buf);
+ fclose(fin);
+}
+
+void concat_files(const char *file1, const char *file2, const char *targetfile)
+{
+ FILE *fout;
+ fout = fopen(targetfile, "wb");
+ if (fout == NULL) {
+ printf("Failed to open file \"%s\"\n", targetfile);
+ exit(-1);
+ }
+ copy_file(file1, fout);
+ copy_file(file2, fout);
+ fclose(fout);
+}
+
+int process_sub(FILE *data_file, FILE *struct_file)
+{
+ tinydir_dir dir;
+ int filesProcessed = 0;
+
+ if (processSubs) {
+ /* process subs recursively */
+ size_t sublen = strlen(curSubdir);
+ size_t freelen = sizeof(curSubdir) - sublen - 1;
+ int ret;
+ LWIP_ASSERT("sublen < sizeof(curSubdir)", sublen < sizeof(curSubdir));
+
+ ret = tinydir_open_sorted(&dir, TINYDIR_STRING("."));
+
+ if (ret == 0) {
+ unsigned int i;
+ for (i = 0; i < dir.n_files; i++) {
+ tinydir_file file;
+
+ ret = tinydir_readfile_n(&dir, &file, i);
+
+ if (ret == 0) {
+#if (defined _MSC_VER || defined __MINGW32__) && (defined _UNICODE)
+ size_t num_char_converted;
+ char currName[256];
+ wcstombs_s(&num_char_converted, currName, sizeof(currName), file.name, sizeof(currName));
+#else
+ const char *currName = file.name;
+#endif
+
+ if (currName[0] == '.') {
+ continue;
+ }
+ if (!file.is_dir) {
+ continue;
+ }
+ if (freelen > 0) {
+ if(!CHDIR_SUCCEEDED(CHDIR(currName))) {
+ printf("Invalid path: \"%s\"." NEWLINE, currName);
+ exit(-1);
+ }
+ strncat(curSubdir, "/", freelen);
+ strncat(curSubdir, currName, freelen - 1);
+ curSubdir[sizeof(curSubdir) - 1] = 0;
+ printf("processing subdirectory %s/..." NEWLINE, curSubdir);
+ filesProcessed += process_sub(data_file, struct_file);
+ if(!CHDIR_SUCCEEDED(CHDIR(".."))) {
+ printf("Unable to get back to parent dir of: \"%s\"." NEWLINE, currName);
+ exit(-1);
+ }
+ curSubdir[sublen] = 0;
+ } else {
+ printf("WARNING: cannot process sub due to path length restrictions: \"%s/%s\"\n", curSubdir, currName);
+ }
+ }
+ }
+ }
+
+ ret = tinydir_open_sorted(&dir, TINYDIR_STRING("."));
+ if (ret == 0) {
+ unsigned int i;
+ for (i = 0; i < dir.n_files; i++) {
+ tinydir_file file;
+
+ ret = tinydir_readfile_n(&dir, &file, i);
+
+ if (ret == 0) {
+ if (!file.is_dir) {
+#if (defined _MSC_VER || defined __MINGW32__) && (defined _UNICODE)
+ size_t num_char_converted;
+ char curName[256];
+ wcstombs_s(&num_char_converted, curName, sizeof(curName), file.name, sizeof(curName));
+#else
+ const char *curName = file.name;
+#endif
+
+ if (strcmp(curName, "fsdata.tmp") == 0) {
+ continue;
+ }
+ if (strcmp(curName, "fshdr.tmp") == 0) {
+ continue;
+ }
+ if (file_to_exclude(curName)) {
+ printf("skipping %s/%s by exclude list (-x option)..." NEWLINE, curSubdir, curName);
+ continue;
+ }
+
+ printf("processing %s/%s..." NEWLINE, curSubdir, curName);
+
+ if (process_file(data_file, struct_file, curName) < 0) {
+ printf(NEWLINE "Error... aborting" NEWLINE);
+ return -1;
+ }
+ filesProcessed++;
+ }
+ }
+ }
+ }
+ }
+
+ return filesProcessed;
+}
+
+static u8_t *get_file_data(const char *filename, int *file_size, int can_be_compressed, int *is_compressed)
+{
+ FILE *inFile;
+ size_t fsize = 0;
+ u8_t *buf;
+ size_t r;
+ int rs;
+ LWIP_UNUSED_ARG(r); /* for LWIP_NOASSERT */
+ inFile = fopen(filename, "rb");
+ if (inFile == NULL) {
+ printf("Failed to open file \"%s\"\n", filename);
+ exit(-1);
+ }
+ fseek(inFile, 0, SEEK_END);
+ rs = ftell(inFile);
+ if (rs < 0) {
+ printf("ftell failed with %d\n", errno);
+ exit(-1);
+ }
+ fsize = (size_t)rs;
+ fseek(inFile, 0, SEEK_SET);
+ buf = (u8_t *)malloc(fsize);
+ LWIP_ASSERT("buf != NULL", buf != NULL);
+ r = fread(buf, 1, fsize, inFile);
+ LWIP_ASSERT("r == fsize", r == fsize);
+ *file_size = fsize;
+ *is_compressed = 0;
+#if MAKEFS_SUPPORT_DEFLATE
+ overallDataBytes += fsize;
+ if (deflateNonSsiFiles) {
+ if (can_be_compressed) {
+ if (fsize < OUT_BUF_SIZE) {
+ u8_t *ret_buf;
+#ifndef MAKEFS_SUPPORT_DEFLATE_ZLIB
+ tdefl_status status;
+#else /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ int status;
+#endif /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ size_t in_bytes = fsize;
+ size_t out_bytes = OUT_BUF_SIZE;
+ const void *next_in = buf;
+ void *next_out = s_outbuf;
+ memset(s_outbuf, 0, sizeof(s_outbuf));
+#ifndef MAKEFS_SUPPORT_DEFLATE_ZLIB
+ /* create tdefl() compatible flags (we have to compose the low-level flags ourselves, or use tdefl_create_comp_flags_from_zip_params() but that means MINIZ_NO_ZLIB_APIS can't be defined). */
+ mz_uint comp_flags = s_tdefl_num_probes[MZ_MIN(10, deflate_level)] | ((deflate_level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);
+ if (!deflate_level) {
+ comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;
+ }
+ status = tdefl_init(&g_deflator, NULL, NULL, comp_flags);
+ if (status != TDEFL_STATUS_OKAY) {
+ printf("tdefl_init() failed!\n");
+ exit(-1);
+ }
+ status = tdefl_compress(&g_deflator, next_in, &in_bytes, next_out, &out_bytes, TDEFL_FINISH);
+ if (status != TDEFL_STATUS_DONE) {
+ printf("deflate failed: %d\n", status);
+ exit(-1);
+ }
+#else /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ status = compress2(next_out, &out_bytes, next_in, in_bytes, deflate_level);
+ if (status != Z_OK) {
+ printf("deflate failed: %d\n", status);
+ exit(-1);
+ }
+#endif /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ LWIP_ASSERT("out_bytes <= COPY_BUFSIZE", out_bytes <= OUT_BUF_SIZE);
+ if (out_bytes < fsize) {
+ ret_buf = (u8_t *)malloc(out_bytes);
+ LWIP_ASSERT("ret_buf != NULL", ret_buf != NULL);
+ memcpy(ret_buf, s_outbuf, out_bytes);
+ {
+ /* sanity-check compression be inflating and comparing to the original */
+ size_t dec_in_bytes = out_bytes;
+ size_t dec_out_bytes = OUT_BUF_SIZE;
+ next_out = s_checkbuf;
+ memset(s_checkbuf, 0, sizeof(s_checkbuf));
+#ifndef MAKEFS_SUPPORT_DEFLATE_ZLIB
+ tinfl_status dec_status;
+ tinfl_decompressor inflator;
+
+ tinfl_init(&inflator);
+ dec_status = tinfl_decompress(&inflator, (const mz_uint8 *)ret_buf, &dec_in_bytes, s_checkbuf, (mz_uint8 *)next_out, &dec_out_bytes, 0);
+ LWIP_ASSERT("tinfl_decompress failed", dec_status == TINFL_STATUS_DONE);
+#else /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ int dec_status;
+ dec_status = uncompress2 (s_checkbuf, &dec_out_bytes, ret_buf, &dec_in_bytes);
+ LWIP_ASSERT("tinfl_decompress failed", dec_status == Z_OK);
+#endif /* MAKEFS_SUPPORT_DEFLATE_ZLIB */
+ LWIP_ASSERT("tinfl_decompress size mismatch", fsize == dec_out_bytes);
+ LWIP_ASSERT("decompressed memcmp failed", !memcmp(s_checkbuf, buf, fsize));
+ }
+ /* free original buffer, use compressed data + size */
+ free(buf);
+ buf = ret_buf;
+ *file_size = out_bytes;
+ printf(" - deflate: %d bytes -> %d bytes (%.02f%%)" NEWLINE, (int)fsize, (int)out_bytes, (float)((out_bytes * 100.0) / fsize));
+ deflatedBytesReduced += (size_t)(fsize - out_bytes);
+ *is_compressed = 1;
+ } else {
+ printf(" - uncompressed: (would be %d bytes larger using deflate)" NEWLINE, (int)(out_bytes - fsize));
+ }
+ } else {
+ printf(" - uncompressed: (file is larger than deflate buffer)" NEWLINE);
+ }
+ } else {
+ printf(" - cannot be compressed" NEWLINE);
+ }
+ }
+#else
+ LWIP_UNUSED_ARG(can_be_compressed);
+#endif
+ fclose(inFile);
+ return buf;
+}
+
+static void process_file_data(FILE *data_file, u8_t *file_data, size_t file_size)
+{
+ size_t written, i, src_off = 0;
+ size_t off = 0;
+ LWIP_UNUSED_ARG(written); /* for LWIP_NOASSERT */
+ for (i = 0; i < file_size; i++) {
+ LWIP_ASSERT("file_buffer_c overflow", off < sizeof(file_buffer_c) - 5);
+ sprintf(&file_buffer_c[off], "0x%02x,", file_data[i]);
+ off += 5;
+ if ((++src_off % HEX_BYTES_PER_LINE) == 0) {
+ LWIP_ASSERT("file_buffer_c overflow", off < sizeof(file_buffer_c) - NEWLINE_LEN);
+ memcpy(&file_buffer_c[off], NEWLINE, NEWLINE_LEN);
+ off += NEWLINE_LEN;
+ }
+ if (off + 20 >= sizeof(file_buffer_c)) {
+ written = fwrite(file_buffer_c, 1, off, data_file);
+ LWIP_ASSERT("written == off", written == off);
+ off = 0;
+ }
+ }
+ written = fwrite(file_buffer_c, 1, off, data_file);
+ LWIP_ASSERT("written == off", written == off);
+}
+
+static int write_checksums(FILE *struct_file, const char *varname,
+ u16_t hdr_len, u16_t hdr_chksum, const u8_t *file_data, size_t file_size)
+{
+ int chunk_size = TCP_MSS;
+ int offset, src_offset;
+ size_t len;
+ int i = 0;
+#if LWIP_TCP_TIMESTAMPS
+ /* when timestamps are used, usable space is 12 bytes less per segment */
+ chunk_size -= 12;
+#endif
+
+ fprintf(struct_file, "#if HTTPD_PRECALCULATED_CHECKSUM" NEWLINE);
+ fprintf(struct_file, "const struct fsdata_chksum chksums_%s[] = {" NEWLINE, varname);
+
+ if (hdr_len > 0) {
+ /* add checksum for HTTP header */
+ fprintf(struct_file, "{%d, 0x%04x, %d}," NEWLINE, 0, hdr_chksum, hdr_len);
+ i++;
+ }
+ src_offset = 0;
+ for (offset = hdr_len; ; offset += len) {
+ unsigned short chksum;
+ const void *data = (const void *)&file_data[src_offset];
+ len = LWIP_MIN(chunk_size, (int)file_size - src_offset);
+ if (len == 0) {
+ break;
+ }
+ chksum = ~inet_chksum(data, (u16_t)len);
+ /* add checksum for data */
+ fprintf(struct_file, "{%d, 0x%04x, %"SZT_F"}," NEWLINE, offset, chksum, len);
+ i++;
+ }
+ fprintf(struct_file, "};" NEWLINE);
+ fprintf(struct_file, "#endif /* HTTPD_PRECALCULATED_CHECKSUM */" NEWLINE);
+ return i;
+}
+
+static int is_valid_char_for_c_var(char x)
+{
+ if (((x >= 'A') && (x <= 'Z')) ||
+ ((x >= 'a') && (x <= 'z')) ||
+ ((x >= '0') && (x <= '9')) ||
+ (x == '_')) {
+ return 1;
+ }
+ return 0;
+}
+
+static void fix_filename_for_c(char *qualifiedName, size_t max_len)
+{
+ struct file_entry *f;
+ size_t len = strlen(qualifiedName);
+ char *new_name = (char *)malloc(len + 2);
+ int filename_ok;
+ int cnt = 0;
+ size_t i;
+ if (len + 3 == max_len) {
+ printf("File name too long: \"%s\"\n", qualifiedName);
+ exit(-1);
+ }
+ strcpy(new_name, qualifiedName);
+ for (i = 0; i < len; i++) {
+ if (!is_valid_char_for_c_var(new_name[i])) {
+ new_name[i] = '_';
+ }
+ }
+ do {
+ filename_ok = 1;
+ for (f = first_file; f != NULL; f = f->next) {
+ if (!strcmp(f->filename_c, new_name)) {
+ filename_ok = 0;
+ cnt++;
+ /* try next unique file name */
+ sprintf(&new_name[len], "%d", cnt);
+ break;
+ }
+ }
+ } while (!filename_ok && (cnt < 999));
+ if (!filename_ok) {
+ printf("Failed to get unique file name: \"%s\"\n", qualifiedName);
+ exit(-1);
+ }
+ strcpy(qualifiedName, new_name);
+ free(new_name);
+}
+
+static void register_filename(const char *qualifiedName)
+{
+ struct file_entry *fe = (struct file_entry *)malloc(sizeof(struct file_entry));
+ fe->filename_c = strdup(qualifiedName);
+ fe->next = NULL;
+ if (first_file == NULL) {
+ first_file = last_file = fe;
+ } else {
+ last_file->next = fe;
+ last_file = fe;
+ }
+}
+
+static int checkSsiByFilelist(const char* filename_listfile)
+{
+ FILE *f = fopen(filename_listfile, "r");
+ if (f != NULL) {
+ char *buf;
+ long rs;
+ size_t fsize, readcount;
+ size_t i, l, num_lines;
+ char **lines;
+ int state;
+
+ fseek(f, 0, SEEK_END);
+ rs = ftell(f);
+ if (rs < 0) {
+ printf("ftell failed with %d\n", errno);
+ fclose(f);
+ return 0;
+ }
+ fsize = (size_t)rs;
+ fseek(f, 0, SEEK_SET);
+ buf = (char*)malloc(fsize);
+ if (!buf) {
+ printf("failed to allocate ssi file buffer\n");
+ fclose(f);
+ return 0;
+ }
+ memset(buf, 0, fsize);
+ readcount = fread(buf, 1, fsize, f);
+ fclose(f);
+ if ((readcount > fsize) || !readcount) {
+ printf("failed to read data from ssi file\n");
+ free(buf);
+ return 0;
+ }
+
+ /* first pass: get the number of lines (and convert newlines to '0') */
+ num_lines = 1;
+ for (i = 0; i < readcount; i++) {
+ if (buf[i] == '\n') {
+ num_lines++;
+ buf[i] = 0;
+ } else if (buf[i] == '\r') {
+ buf[i] = 0;
+ }
+ }
+ /* allocate the line pointer array */
+ lines = (char**)malloc(sizeof(char*) * num_lines);
+ if (!lines) {
+ printf("failed to allocate ssi line buffer\n");
+ free(buf);
+ return 0;
+ }
+ memset(lines, 0, sizeof(char*) * num_lines);
+ l = 0;
+ state = 0;
+ for (i = 0; i < readcount; i++) {
+ if (state) {
+ /* waiting for null */
+ if (buf[i] == 0) {
+ state = 0;
+ }
+ } else {
+ /* waiting for beginning of new string */
+ if (buf[i] != 0) {
+ LWIP_ASSERT("lines array overflow", l < num_lines);
+ lines[l] = &buf[i];
+ state = 1;
+ l++;
+ }
+ }
+ }
+ LWIP_ASSERT("lines array overflow", l < num_lines);
+
+ ssi_file_buffer = buf;
+ ssi_file_lines = lines;
+ ssi_file_num_lines = l;
+ }
+ return 0;
+}
+
+static int is_ssi_file(const char *filename)
+{
+ if (supportSsi) {
+ if (ssi_file_buffer) {
+ /* compare by list */
+ size_t i;
+ int ret = 0;
+ /* build up the relative path to this file */
+ size_t sublen = strlen(curSubdir);
+ size_t freelen = sizeof(curSubdir) - sublen - 1;
+ strncat(curSubdir, "/", freelen);
+ strncat(curSubdir, filename, freelen - 1);
+ curSubdir[sizeof(curSubdir) - 1] = 0;
+ for (i = 0; i < ssi_file_num_lines; i++) {
+ const char *listed_file = ssi_file_lines[i];
+ /* compare without the leading '/' */
+ if (!strcmp(&curSubdir[1], listed_file)) {
+ ret = 1;
+ }
+ }
+ curSubdir[sublen] = 0;
+ return ret;
+#if LWIP_HTTPD_SSI_BY_FILE_EXTENSION
+ } else {
+ /* check file extension */
+ size_t loop;
+ for (loop = 0; loop < NUM_SHTML_EXTENSIONS; loop++) {
+ if (strstr(filename, g_pcSSIExtensions[loop])) {
+ return 1;
+ }
+ }
+#endif /* LWIP_HTTPD_SSI_BY_FILE_EXTENSION */
+ }
+ }
+ return 0;
+}
+
+static int ext_in_list(const char* filename, const char *ext_list)
+{
+ int found = 0;
+ const char *ext = ext_list;
+ if (ext_list == NULL) {
+ return 0;
+ }
+ while(*ext != '\0') {
+ const char *comma = strchr(ext, ',');
+ size_t ext_size;
+ size_t filename_size = strlen(filename);
+ if (comma == NULL) {
+ comma = strchr(ext, '\0');
+ }
+ ext_size = comma - ext;
+ if ((filename[filename_size - ext_size - 1] == '.') &&
+ !strncmp(&filename[filename_size - ext_size], ext, ext_size)) {
+ found = 1;
+ break;
+ }
+ ext = comma + 1;
+ }
+
+ return found;
+}
+
+static int file_to_exclude(const char *filename)
+{
+ return (exclude_list != NULL) && ext_in_list(filename, exclude_list);
+}
+
+static int file_can_be_compressed(const char *filename)
+{
+ return (ncompress_list == NULL) || !ext_in_list(filename, ncompress_list);
+}
+
+int process_file(FILE *data_file, FILE *struct_file, const char *filename)
+{
+ char varname[MAX_PATH_LEN];
+ int i = 0;
+ char qualifiedName[MAX_PATH_LEN];
+ int file_size;
+ u16_t http_hdr_chksum = 0;
+ u16_t http_hdr_len = 0;
+ int chksum_count = 0;
+ u8_t flags = 0;
+ u8_t has_content_len;
+ u8_t *file_data;
+ int is_ssi;
+ int can_be_compressed;
+ int is_compressed = 0;
+ int flags_printed;
+
+ /* create qualified name (@todo: prepend slash or not?) */
+ snprintf(qualifiedName, sizeof(qualifiedName), "%s/%s", curSubdir, filename);
+ /* create C variable name */
+ strncpy(varname, qualifiedName, sizeof(varname));
+ /* convert slashes & dots to underscores */
+ fix_filename_for_c(varname, MAX_PATH_LEN);
+ register_filename(varname);
+#if ALIGN_PAYLOAD
+ /* to force even alignment of array, type 1 */
+ fprintf(data_file, "#if FSDATA_FILE_ALIGNMENT==1" NEWLINE);
+ fprintf(data_file, "static const " PAYLOAD_ALIGN_TYPE " dummy_align_%s = %d;" NEWLINE, varname, payload_alingment_dummy_counter++);
+ fprintf(data_file, "#endif" NEWLINE);
+#endif /* ALIGN_PAYLOAD */
+ fprintf(data_file, "static const unsigned char FSDATA_ALIGN_PRE data_%s[] FSDATA_ALIGN_POST = {" NEWLINE, varname);
+ /* encode source file name (used by file system, not returned to browser) */
+ fprintf(data_file, "/* %s (%"SZT_F" chars) */" NEWLINE, qualifiedName, strlen(qualifiedName) + 1);
+ file_put_ascii(data_file, qualifiedName, strlen(qualifiedName) + 1, &i);
+#if ALIGN_PAYLOAD
+ /* pad to even number of bytes to assure payload is on aligned boundary */
+ while (i % PAYLOAD_ALIGNMENT != 0) {
+ fprintf(data_file, "0x%02x,", 0);
+ i++;
+ }
+#endif /* ALIGN_PAYLOAD */
+ fprintf(data_file, NEWLINE);
+
+ is_ssi = is_ssi_file(filename);
+ if (is_ssi) {
+ flags |= FS_FILE_FLAGS_SSI;
+ }
+ has_content_len = !is_ssi;
+ can_be_compressed = includeHttpHeader && !is_ssi && file_can_be_compressed(filename);
+ file_data = get_file_data(filename, &file_size, can_be_compressed, &is_compressed);
+ if (includeHttpHeader) {
+ file_write_http_header(data_file, filename, file_size, &http_hdr_len, &http_hdr_chksum, has_content_len, is_compressed);
+ flags |= FS_FILE_FLAGS_HEADER_INCLUDED;
+ if (has_content_len) {
+ flags |= FS_FILE_FLAGS_HEADER_PERSISTENT;
+ if (useHttp11) {
+ flags |= FS_FILE_FLAGS_HEADER_HTTPVER_1_1;
+ }
+ }
+ }
+ if (precalcChksum) {
+ chksum_count = write_checksums(struct_file, varname, http_hdr_len, http_hdr_chksum, file_data, file_size);
+ }
+
+ /* build declaration of struct fsdata_file in temp file */
+ fprintf(struct_file, "const struct fsdata_file file_%s[] = { {" NEWLINE, varname);
+ fprintf(struct_file, "file_%s," NEWLINE, lastFileVar);
+ fprintf(struct_file, "data_%s," NEWLINE, varname);
+ fprintf(struct_file, "data_%s + %d," NEWLINE, varname, i);
+ fprintf(struct_file, "sizeof(data_%s) - %d," NEWLINE, varname, i);
+
+ flags_printed = 0;
+ if (flags & FS_FILE_FLAGS_HEADER_INCLUDED) {
+ fputs("FS_FILE_FLAGS_HEADER_INCLUDED", struct_file);
+ flags_printed = 1;
+ }
+ if (flags & FS_FILE_FLAGS_HEADER_PERSISTENT) {
+ if (flags_printed) {
+ fputs(" | ", struct_file);
+ }
+ fputs("FS_FILE_FLAGS_HEADER_PERSISTENT", struct_file);
+ flags_printed = 1;
+ }
+ if (flags & FS_FILE_FLAGS_HEADER_HTTPVER_1_1) {
+ if (flags_printed) {
+ fputs(" | ", struct_file);
+ }
+ fputs("FS_FILE_FLAGS_HEADER_HTTPVER_1_1", struct_file);
+ flags_printed = 1;
+ }
+ if (flags & FS_FILE_FLAGS_SSI) {
+ if (flags_printed) {
+ fputs(" | ", struct_file);
+ }
+ fputs("FS_FILE_FLAGS_SSI", struct_file);
+ flags_printed = 1;
+ }
+ if (!flags_printed) {
+ fputs("0", struct_file);
+ }
+ fputs("," NEWLINE, struct_file);
+ if (precalcChksum) {
+ fprintf(struct_file, "#if HTTPD_PRECALCULATED_CHECKSUM" NEWLINE);
+ fprintf(struct_file, "%d, chksums_%s," NEWLINE, chksum_count, varname);
+ fprintf(struct_file, "#endif /* HTTPD_PRECALCULATED_CHECKSUM */" NEWLINE);
+ }
+ fprintf(struct_file, "}};" NEWLINE NEWLINE);
+ strcpy(lastFileVar, varname);
+
+ /* write actual file contents */
+ i = 0;
+ fprintf(data_file, NEWLINE "/* raw file data (%d bytes) */" NEWLINE, file_size);
+ process_file_data(data_file, file_data, file_size);
+ fprintf(data_file, "};" NEWLINE NEWLINE);
+ free(file_data);
+ return 0;
+}
+
+int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t *http_hdr_len,
+ u16_t *http_hdr_chksum, u8_t provide_content_len, int is_compressed)
+{
+ int i = 0;
+ int response_type = HTTP_HDR_OK;
+ const char *file_type;
+ const char *cur_string;
+ size_t cur_len;
+ int written = 0;
+ size_t hdr_len = 0;
+ u16_t acc;
+ const char *file_ext;
+ size_t j;
+ u8_t provide_last_modified = includeLastModified;
+
+ memset(hdr_buf, 0, sizeof(hdr_buf));
+
+ if (useHttp11) {
+ response_type = HTTP_HDR_OK_11;
+ }
+
+ fprintf(data_file, NEWLINE "/* HTTP header */");
+ if (strstr(filename, "404.") == filename) {
+ response_type = HTTP_HDR_NOT_FOUND;
+ if (useHttp11) {
+ response_type = HTTP_HDR_NOT_FOUND_11;
+ }
+ } else if (strstr(filename, "400.") == filename) {
+ response_type = HTTP_HDR_BAD_REQUEST;
+ if (useHttp11) {
+ response_type = HTTP_HDR_BAD_REQUEST_11;
+ }
+ } else if (strstr(filename, "501.") == filename) {
+ response_type = HTTP_HDR_NOT_IMPL;
+ if (useHttp11) {
+ response_type = HTTP_HDR_NOT_IMPL_11;
+ }
+ }
+ cur_string = g_psHTTPHeaderStrings[response_type];
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\" (%"SZT_F" bytes) */" NEWLINE, cur_string, cur_len);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ i = 0;
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+ }
+
+ cur_string = serverID;
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\" (%"SZT_F" bytes) */" NEWLINE, cur_string, cur_len);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ i = 0;
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+ }
+
+ file_ext = filename;
+ if (file_ext != NULL) {
+ while (strstr(file_ext, ".") != NULL) {
+ file_ext = strstr(file_ext, ".");
+ file_ext++;
+ }
+ }
+ if ((file_ext == NULL) || (*file_ext == 0)) {
+ printf("failed to get extension for file \"%s\", using default.\n", filename);
+ file_type = HTTP_HDR_DEFAULT_TYPE;
+ } else {
+ file_type = NULL;
+ for (j = 0; j < NUM_HTTP_HEADERS; j++) {
+ if (!strcmp(file_ext, g_psHTTPHeaders[j].extension)) {
+ file_type = g_psHTTPHeaders[j].content_type;
+ break;
+ }
+ }
+ if (file_type == NULL) {
+ printf("failed to get file type for extension \"%s\", using default.\n", file_ext);
+ file_type = HTTP_HDR_DEFAULT_TYPE;
+ }
+ }
+
+ /* Content-Length is used for persistent connections in HTTP/1.1 but also for
+ download progress in older versions
+ @todo: just use a big-enough buffer and let the HTTPD send spaces? */
+ if (provide_content_len) {
+ char intbuf[MAX_PATH_LEN];
+ int content_len = file_size;
+ memset(intbuf, 0, sizeof(intbuf));
+ cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONTENT_LENGTH];
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s%d\r\n\" (%"SZT_F"+ bytes) */" NEWLINE, cur_string, content_len, cur_len + 2);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+ }
+
+ lwip_itoa(intbuf, sizeof(intbuf), content_len);
+ strcat(intbuf, "\r\n");
+ cur_len = strlen(intbuf);
+ written += file_put_ascii(data_file, intbuf, cur_len, &i);
+ i = 0;
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], intbuf, cur_len);
+ hdr_len += cur_len;
+ }
+ }
+ if (provide_last_modified) {
+ char modbuf[256];
+ struct stat stat_data;
+ struct tm *t;
+ memset(modbuf, 0, sizeof(modbuf));
+ memset(&stat_data, 0, sizeof(stat_data));
+ cur_string = modbuf;
+ strcpy(modbuf, "Last-Modified: ");
+ if (stat(filename, &stat_data) != 0) {
+ printf("stat(%s) failed with error %d\n", filename, errno);
+ exit(-1);
+ }
+ t = gmtime(&stat_data.st_mtime);
+ if (t == NULL) {
+ printf("gmtime() failed with error %d\n", errno);
+ exit(-1);
+ }
+ strftime(&modbuf[15], sizeof(modbuf) - 15, "%a, %d %b %Y %H:%M:%S GMT", t);
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\"\r\n\" (%"SZT_F"+ bytes) */" NEWLINE, cur_string, cur_len + 2);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+ }
+
+ modbuf[0] = 0;
+ strcat(modbuf, "\r\n");
+ cur_len = strlen(modbuf);
+ written += file_put_ascii(data_file, modbuf, cur_len, &i);
+ i = 0;
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], modbuf, cur_len);
+ hdr_len += cur_len;
+ }
+ }
+
+ /* HTTP/1.1 implements persistent connections */
+ if (useHttp11) {
+ if (provide_content_len) {
+ cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONN_KEEPALIVE];
+ } else {
+ /* no Content-Length available, so a persistent connection is no possible
+ because the client does not know the data length */
+ cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONN_CLOSE];
+ }
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\" (%"SZT_F" bytes) */" NEWLINE, cur_string, cur_len);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ i = 0;
+ if (precalcChksum) {
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+ }
+ }
+
+#if MAKEFS_SUPPORT_DEFLATE
+ if (is_compressed) {
+ /* tell the client about the deflate encoding */
+ LWIP_ASSERT("error", deflateNonSsiFiles);
+ cur_string = "Content-Encoding: deflate\r\n";
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ i = 0;
+ }
+#else
+ LWIP_UNUSED_ARG(is_compressed);
+#endif
+
+ /* write content-type, ATTENTION: this includes the double-CRLF! */
+ cur_string = file_type;
+ cur_len = strlen(cur_string);
+ fprintf(data_file, NEWLINE "/* \"%s\" (%"SZT_F" bytes) */" NEWLINE, cur_string, cur_len);
+ written += file_put_ascii(data_file, cur_string, cur_len, &i);
+ i = 0;
+
+ /* ATTENTION: headers are done now (double-CRLF has been written!) */
+
+ if (precalcChksum) {
+ LWIP_ASSERT("hdr_len + cur_len <= sizeof(hdr_buf)", hdr_len + cur_len <= sizeof(hdr_buf));
+ memcpy(&hdr_buf[hdr_len], cur_string, cur_len);
+ hdr_len += cur_len;
+
+ LWIP_ASSERT("strlen(hdr_buf) == hdr_len", strlen(hdr_buf) == hdr_len);
+ acc = ~inet_chksum(hdr_buf, (u16_t)hdr_len);
+ *http_hdr_len = (u16_t)hdr_len;
+ *http_hdr_chksum = acc;
+ }
+
+ return written;
+}
+
+int file_put_ascii(FILE *file, const char *ascii_string, int len, int *i)
+{
+ int x;
+ for (x = 0; x < len; x++) {
+ unsigned char cur = ascii_string[x];
+ fprintf(file, "0x%02x,", cur);
+ if ((++(*i) % HEX_BYTES_PER_LINE) == 0) {
+ fprintf(file, NEWLINE);
+ }
+ }
+ return len;
+}
+
+int s_put_ascii(char *buf, const char *ascii_string, int len, int *i)
+{
+ int x;
+ int idx = 0;
+ for (x = 0; x < len; x++) {
+ unsigned char cur = ascii_string[x];
+ sprintf(&buf[idx], "0x%02x,", cur);
+ idx += 5;
+ if ((++(*i) % HEX_BYTES_PER_LINE) == 0) {
+ sprintf(&buf[idx], NEWLINE);
+ idx += NEWLINE_LEN;
+ }
+ }
+ return len;
+}
diff --git a/src/apps/http/makefsdata/readme.txt b/src/apps/http/makefsdata/readme.txt
new file mode 100644
index 00000000000..929179a1110
--- /dev/null
+++ b/src/apps/http/makefsdata/readme.txt
@@ -0,0 +1,23 @@
+This directory contains a script ('makefsdata') to create C code suitable for
+httpd for given html pages (or other files) in a directory.
+
+There is also a plain C console application doing the same and extended a bit.
+
+Usage: htmlgen [targetdir] [-s] [-i]s
+ targetdir: relative or absolute path to files to convert
+ switch -s: toggle processing of subdirectories (default is on)
+ switch -e: exclude HTTP header from file (header is created at runtime, default is on)
+ switch -11: include HTTP 1.1 header (1.0 is default)
+
+ if targetdir not specified, makefsdata will attempt to
+ process files in subdirectory 'fs'.
+
+The C version of this program can optionally store the none-SSI files in
+a compressed form in which they are also sent to the web client (which
+must support the Deflate content encoding). Files that grow during compression
+(due to being not compressible well), will stored umcompressed automatically.
+In order to do so, compile the program with MAKEFS_SUPPORT_DEFLATE set to 1. You must
+manually download minizip.c for this to work. As an alternative, you can additionally
+define MAKEFS_SUPPORT_DEFLATE_ZLIB to use your system's zlib instead.
+Compression of .html, .js, .css and .svg files usually yields very good compression
+rates and is a great way of reducing your program's size.
diff --git a/src/apps/http/makefsdata/tinydir.h b/src/apps/http/makefsdata/tinydir.h
new file mode 100644
index 00000000000..e08eb84ec97
--- /dev/null
+++ b/src/apps/http/makefsdata/tinydir.h
@@ -0,0 +1,831 @@
+/*
+Copyright (c) 2013-2019, tinydir authors:
+- Cong Xu
+- Lautis Sun
+- Baudouin Feildel
+- Andargor <[email protected]>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+#ifndef TINYDIR_H
+#define TINYDIR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if ((defined _UNICODE) && !(defined UNICODE))
+#define UNICODE
+#endif
+
+#if ((defined UNICODE) && !(defined _UNICODE))
+#define _UNICODE
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _MSC_VER
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+# include <windows.h>
+# include <tchar.h>
+# pragma warning(push)
+# pragma warning (disable : 4996)
+#else
+# include <dirent.h>
+# include <libgen.h>
+# include <sys/stat.h>
+# include <stddef.h>
+#endif
+#ifdef __MINGW32__
+# include <tchar.h>
+#endif
+
+
+/* types */
+
+/* Windows UNICODE wide character support */
+#if defined _MSC_VER || defined __MINGW32__
+# define _tinydir_char_t TCHAR
+# define TINYDIR_STRING(s) _TEXT(s)
+# define _tinydir_strlen _tcslen
+# define _tinydir_strcpy _tcscpy
+# define _tinydir_strcat _tcscat
+# define _tinydir_strcmp _tcscmp
+# define _tinydir_strrchr _tcsrchr
+# define _tinydir_strncmp _tcsncmp
+#else
+# define _tinydir_char_t char
+# define TINYDIR_STRING(s) s
+# define _tinydir_strlen strlen
+# define _tinydir_strcpy strcpy
+# define _tinydir_strcat strcat
+# define _tinydir_strcmp strcmp
+# define _tinydir_strrchr strrchr
+# define _tinydir_strncmp strncmp
+#endif
+
+#if (defined _MSC_VER || defined __MINGW32__)
+# include <windows.h>
+# define _TINYDIR_PATH_MAX MAX_PATH
+#elif defined __linux__
+# include <limits.h>
+# ifdef PATH_MAX
+# define _TINYDIR_PATH_MAX PATH_MAX
+# endif
+#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
+# include <sys/param.h>
+# if defined(BSD)
+# include <limits.h>
+# ifdef PATH_MAX
+# define _TINYDIR_PATH_MAX PATH_MAX
+# endif
+# endif
+#endif
+
+#ifndef _TINYDIR_PATH_MAX
+#define _TINYDIR_PATH_MAX 4096
+#endif
+
+#ifdef _MSC_VER
+/* extra chars for the "\\*" mask */
+# define _TINYDIR_PATH_EXTRA 2
+#else
+# define _TINYDIR_PATH_EXTRA 0
+#endif
+
+#define _TINYDIR_FILENAME_MAX 256
+
+#if (defined _MSC_VER || defined __MINGW32__)
+#define _TINYDIR_DRIVE_MAX 3
+#endif
+
+#ifdef _MSC_VER
+# define _TINYDIR_FUNC static __inline
+#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
+# define _TINYDIR_FUNC static __inline__
+#else
+# define _TINYDIR_FUNC static inline
+#endif
+
+/* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */
+#ifdef TINYDIR_USE_READDIR_R
+
+/* readdir_r is a POSIX-only function, and may not be available under various
+ * environments/settings, e.g. MinGW. Use readdir fallback */
+#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\
+ _POSIX_SOURCE
+# define _TINYDIR_HAS_READDIR_R
+#endif
+#if _POSIX_C_SOURCE >= 200112L
+# define _TINYDIR_HAS_FPATHCONF
+# include <unistd.h>
+#endif
+#if _BSD_SOURCE || _SVID_SOURCE || \
+ (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
+# define _TINYDIR_HAS_DIRFD
+# include <sys/types.h>
+#endif
+#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\
+ defined _PC_NAME_MAX
+# define _TINYDIR_USE_FPATHCONF
+#endif
+#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\
+ !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX)
+# define _TINYDIR_USE_READDIR
+#endif
+
+/* Use readdir by default */
+#else
+# define _TINYDIR_USE_READDIR
+#endif
+
+/* MINGW32 has two versions of dirent, ASCII and UNICODE*/
+#ifndef _MSC_VER
+#if (defined __MINGW32__) && (defined _UNICODE)
+#define _TINYDIR_DIR _WDIR
+#define _tinydir_dirent _wdirent
+#define _tinydir_opendir _wopendir
+#define _tinydir_readdir _wreaddir
+#define _tinydir_closedir _wclosedir
+#else
+#define _TINYDIR_DIR DIR
+#define _tinydir_dirent dirent
+#define _tinydir_opendir opendir
+#define _tinydir_readdir readdir
+#define _tinydir_closedir closedir
+#endif
+#endif
+
+/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */
+#if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE)
+#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE)
+#else
+#error "Either define both alloc and free or none of them!"
+#endif
+
+#if !defined(_TINYDIR_MALLOC)
+ #define _TINYDIR_MALLOC(_size) malloc(_size)
+ #define _TINYDIR_FREE(_ptr) free(_ptr)
+#endif /* !defined(_TINYDIR_MALLOC) */
+
+typedef struct tinydir_file
+{
+ _tinydir_char_t path[_TINYDIR_PATH_MAX];
+ _tinydir_char_t name[_TINYDIR_FILENAME_MAX];
+ _tinydir_char_t *extension;
+ int is_dir;
+ int is_reg;
+
+#ifndef _MSC_VER
+#ifdef __MINGW32__
+ struct _stat _s;
+#else
+ struct stat _s;
+#endif
+#endif
+} tinydir_file;
+
+typedef struct tinydir_dir
+{
+ _tinydir_char_t path[_TINYDIR_PATH_MAX];
+ int has_next;
+ size_t n_files;
+
+ tinydir_file *_files;
+#ifdef _MSC_VER
+ HANDLE _h;
+ WIN32_FIND_DATA _f;
+#else
+ _TINYDIR_DIR *_d;
+ struct _tinydir_dirent *_e;
+#ifndef _TINYDIR_USE_READDIR
+ struct _tinydir_dirent *_ep;
+#endif
+#endif
+} tinydir_dir;
+
+
+/* declarations */
+
+_TINYDIR_FUNC
+int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+void tinydir_close(tinydir_dir *dir);
+
+_TINYDIR_FUNC
+int tinydir_next(tinydir_dir *dir);
+_TINYDIR_FUNC
+int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file);
+_TINYDIR_FUNC
+int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i);
+_TINYDIR_FUNC
+int tinydir_open_subdir_n(tinydir_dir *dir, size_t i);
+
+_TINYDIR_FUNC
+int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+void _tinydir_get_ext(tinydir_file *file);
+_TINYDIR_FUNC
+int _tinydir_file_cmp(const void *a, const void *b);
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+_TINYDIR_FUNC
+size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp);
+#endif
+#endif
+
+
+/* definitions*/
+
+_TINYDIR_FUNC
+int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path)
+{
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+ int error;
+ int size; /* using int size */
+#endif
+#else
+ _tinydir_char_t path_buf[_TINYDIR_PATH_MAX];
+#endif
+ _tinydir_char_t *pathp;
+
+ if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ /* initialise dir */
+ dir->_files = NULL;
+#ifdef _MSC_VER
+ dir->_h = INVALID_HANDLE_VALUE;
+#else
+ dir->_d = NULL;
+#ifndef _TINYDIR_USE_READDIR
+ dir->_ep = NULL;
+#endif
+#endif
+ tinydir_close(dir);
+
+ _tinydir_strcpy(dir->path, path);
+ /* Remove trailing slashes */
+ pathp = &dir->path[_tinydir_strlen(dir->path) - 1];
+ while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/')))
+ {
+ *pathp = TINYDIR_STRING('\0');
+ pathp++;
+ }
+#ifdef _MSC_VER
+ _tinydir_strcpy(path_buf, dir->path);
+ _tinydir_strcat(path_buf, TINYDIR_STRING("\\*"));
+#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP)
+ dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0);
+#else
+ dir->_h = FindFirstFile(path_buf, &dir->_f);
+#endif
+ if (dir->_h == INVALID_HANDLE_VALUE)
+ {
+ errno = ENOENT;
+#else
+ dir->_d = _tinydir_opendir(path);
+ if (dir->_d == NULL)
+ {
+#endif
+ goto bail;
+ }
+
+ /* read first file */
+ dir->has_next = 1;
+#ifndef _MSC_VER
+#ifdef _TINYDIR_USE_READDIR
+ dir->_e = _tinydir_readdir(dir->_d);
+#else
+ /* allocate dirent buffer for readdir_r */
+ size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */
+ if (size == -1) return -1;
+ dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size);
+ if (dir->_ep == NULL) return -1;
+
+ error = readdir_r(dir->_d, dir->_ep, &dir->_e);
+ if (error != 0) return -1;
+#endif
+ if (dir->_e == NULL)
+ {
+ dir->has_next = 0;
+ }
+#endif
+
+ return 0;
+
+bail:
+ tinydir_close(dir);
+ return -1;
+}
+
+_TINYDIR_FUNC
+int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path)
+{
+ /* Count the number of files first, to pre-allocate the files array */
+ size_t n_files = 0;
+ if (tinydir_open(dir, path) == -1)
+ {
+ return -1;
+ }
+ while (dir->has_next)
+ {
+ n_files++;
+ if (tinydir_next(dir) == -1)
+ {
+ goto bail;
+ }
+ }
+ tinydir_close(dir);
+
+ if (n_files == 0 || tinydir_open(dir, path) == -1)
+ {
+ return -1;
+ }
+
+ dir->n_files = 0;
+ dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files);
+ if (dir->_files == NULL)
+ {
+ goto bail;
+ }
+ while (dir->has_next)
+ {
+ tinydir_file *p_file;
+ dir->n_files++;
+
+ p_file = &dir->_files[dir->n_files - 1];
+ if (tinydir_readfile(dir, p_file) == -1)
+ {
+ goto bail;
+ }
+
+ if (tinydir_next(dir) == -1)
+ {
+ goto bail;
+ }
+
+ /* Just in case the number of files has changed between the first and
+ second reads, terminate without writing into unallocated memory */
+ if (dir->n_files == n_files)
+ {
+ break;
+ }
+ }
+
+ qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp);
+
+ return 0;
+
+bail:
+ tinydir_close(dir);
+ return -1;
+}
+
+_TINYDIR_FUNC
+void tinydir_close(tinydir_dir *dir)
+{
+ if (dir == NULL)
+ {
+ return;
+ }
+
+ memset(dir->path, 0, sizeof(dir->path));
+ dir->has_next = 0;
+ dir->n_files = 0;
+ _TINYDIR_FREE(dir->_files);
+ dir->_files = NULL;
+#ifdef _MSC_VER
+ if (dir->_h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(dir->_h);
+ }
+ dir->_h = INVALID_HANDLE_VALUE;
+#else
+ if (dir->_d)
+ {
+ _tinydir_closedir(dir->_d);
+ }
+ dir->_d = NULL;
+ dir->_e = NULL;
+#ifndef _TINYDIR_USE_READDIR
+ _TINYDIR_FREE(dir->_ep);
+ dir->_ep = NULL;
+#endif
+#endif
+}
+
+_TINYDIR_FUNC
+int tinydir_next(tinydir_dir *dir)
+{
+ if (dir == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!dir->has_next)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+#ifdef _MSC_VER
+ if (FindNextFile(dir->_h, &dir->_f) == 0)
+#else
+#ifdef _TINYDIR_USE_READDIR
+ dir->_e = _tinydir_readdir(dir->_d);
+#else
+ if (dir->_ep == NULL)
+ {
+ return -1;
+ }
+ if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0)
+ {
+ return -1;
+ }
+#endif
+ if (dir->_e == NULL)
+#endif
+ {
+ dir->has_next = 0;
+#ifdef _MSC_VER
+ if (GetLastError() != ERROR_SUCCESS &&
+ GetLastError() != ERROR_NO_MORE_FILES)
+ {
+ tinydir_close(dir);
+ errno = EIO;
+ return -1;
+ }
+#endif
+ }
+
+ return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file)
+{
+ const _tinydir_char_t *filename;
+ if (dir == NULL || file == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+#ifdef _MSC_VER
+ if (dir->_h == INVALID_HANDLE_VALUE)
+#else
+ if (dir->_e == NULL)
+#endif
+ {
+ errno = ENOENT;
+ return -1;
+ }
+ filename =
+#ifdef _MSC_VER
+ dir->_f.cFileName;
+#else
+ dir->_e->d_name;
+#endif
+ if (_tinydir_strlen(dir->path) +
+ _tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >=
+ _TINYDIR_PATH_MAX)
+ {
+ /* the path for the file will be too long */
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ _tinydir_strcpy(file->path, dir->path);
+ if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0)
+ _tinydir_strcat(file->path, TINYDIR_STRING("/"));
+ _tinydir_strcpy(file->name, filename);
+ _tinydir_strcat(file->path, filename);
+#ifndef _MSC_VER
+#ifdef __MINGW32__
+ if (_tstat(
+#elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE) \
+ || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \
+ || ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L))
+ if (lstat(
+#else
+ if (stat(
+#endif
+ file->path, &file->_s) == -1)
+ {
+ return -1;
+ }
+#endif
+ _tinydir_get_ext(file);
+
+ file->is_dir =
+#ifdef _MSC_VER
+ !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+#else
+ S_ISDIR(file->_s.st_mode);
+#endif
+ file->is_reg =
+#ifdef _MSC_VER
+ !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
+ (
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) &&
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) &&
+#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) &&
+#endif
+#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) &&
+#endif
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) &&
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY));
+#else
+ S_ISREG(file->_s.st_mode);
+#endif
+
+ return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i)
+{
+ if (dir == NULL || file == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (i >= dir->n_files)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ memcpy(file, &dir->_files[i], sizeof(tinydir_file));
+ _tinydir_get_ext(file);
+
+ return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_open_subdir_n(tinydir_dir *dir, size_t i)
+{
+ _tinydir_char_t path[_TINYDIR_PATH_MAX];
+ if (dir == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (i >= dir->n_files || !dir->_files[i].is_dir)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ _tinydir_strcpy(path, dir->_files[i].path);
+ tinydir_close(dir);
+ if (tinydir_open_sorted(dir, path) == -1)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Open a single file given its path */
+_TINYDIR_FUNC
+int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path)
+{
+ tinydir_dir dir;
+ int result = 0;
+ int found = 0;
+ _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX];
+ _tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX];
+ _tinydir_char_t *dir_name;
+ _tinydir_char_t *base_name;
+#if (defined _MSC_VER || defined __MINGW32__)
+ _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX];
+ _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX];
+#endif
+
+ if (file == NULL || path == NULL || _tinydir_strlen(path) == 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ /* Get the parent path */
+#if (defined _MSC_VER || defined __MINGW32__)
+#if ((defined _MSC_VER) && (_MSC_VER >= 1400))
+ errno = _tsplitpath_s(
+ path,
+ drive_buf, _TINYDIR_DRIVE_MAX,
+ dir_name_buf, _TINYDIR_FILENAME_MAX,
+ file_name_buf, _TINYDIR_FILENAME_MAX,
+ ext_buf, _TINYDIR_FILENAME_MAX);
+#else
+ _tsplitpath(
+ path,
+ drive_buf,
+ dir_name_buf,
+ file_name_buf,
+ ext_buf);
+#endif
+
+ if (errno)
+ {
+ return -1;
+ }
+
+/* _splitpath_s not work fine with only filename and widechar support */
+#ifdef _UNICODE
+ if (drive_buf[0] == L'\xFEFE')
+ drive_buf[0] = '\0';
+ if (dir_name_buf[0] == L'\xFEFE')
+ dir_name_buf[0] = '\0';
+#endif
+
+ /* Emulate the behavior of dirname by returning "." for dir name if it's
+ empty */
+ if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0')
+ {
+ _tinydir_strcpy(dir_name_buf, TINYDIR_STRING("."));
+ }
+ /* Concatenate the drive letter and dir name to form full dir name */
+ _tinydir_strcat(drive_buf, dir_name_buf);
+ dir_name = drive_buf;
+ /* Concatenate the file name and extension to form base name */
+ _tinydir_strcat(file_name_buf, ext_buf);
+ base_name = file_name_buf;
+#else
+ _tinydir_strcpy(dir_name_buf, path);
+ dir_name = dirname(dir_name_buf);
+ _tinydir_strcpy(file_name_buf, path);
+ base_name = basename(file_name_buf);
+#endif
+
+ /* Special case: if the path is a root dir, open the parent dir as the file */
+#if (defined _MSC_VER || defined __MINGW32__)
+ if (_tinydir_strlen(base_name) == 0)
+#else
+ if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0)
+#endif
+ {
+ memset(file, 0, sizeof * file);
+ file->is_dir = 1;
+ file->is_reg = 0;
+ _tinydir_strcpy(file->path, dir_name);
+ file->extension = file->path + _tinydir_strlen(file->path);
+ return 0;
+ }
+
+ /* Open the parent directory */
+ if (tinydir_open(&dir, dir_name) == -1)
+ {
+ return -1;
+ }
+
+ /* Read through the parent directory and look for the file */
+ while (dir.has_next)
+ {
+ if (tinydir_readfile(&dir, file) == -1)
+ {
+ result = -1;
+ goto bail;
+ }
+ if (_tinydir_strcmp(file->name, base_name) == 0)
+ {
+ /* File found */
+ found = 1;
+ break;
+ }
+ tinydir_next(&dir);
+ }
+ if (!found)
+ {
+ result = -1;
+ errno = ENOENT;
+ }
+
+bail:
+ tinydir_close(&dir);
+ return result;
+}
+
+_TINYDIR_FUNC
+void _tinydir_get_ext(tinydir_file *file)
+{
+ _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.'));
+ if (period == NULL)
+ {
+ file->extension = &(file->name[_tinydir_strlen(file->name)]);
+ }
+ else
+ {
+ file->extension = period + 1;
+ }
+}
+
+_TINYDIR_FUNC
+int _tinydir_file_cmp(const void *a, const void *b)
+{
+ const tinydir_file *fa = (const tinydir_file *)a;
+ const tinydir_file *fb = (const tinydir_file *)b;
+ if (fa->is_dir != fb->is_dir)
+ {
+ return -(fa->is_dir - fb->is_dir);
+ }
+ return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX);
+}
+
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+/*
+The following authored by Ben Hutchings <[email protected]>
+from https://womble.decadent.org.uk/readdir_r-advisory.html
+*/
+/* Calculate the required buffer size (in bytes) for directory *
+* entries read from the given directory handle. Return -1 if this *
+* this cannot be done. *
+* *
+* This code does not trust values of NAME_MAX that are less than *
+* 255, since some systems (including at least HP-UX) incorrectly *
+* define it to be a smaller value. */
+_TINYDIR_FUNC
+size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp)
+{
+ long name_max;
+ size_t name_end;
+ /* parameter may be unused */
+ (void)dirp;
+
+#if defined _TINYDIR_USE_FPATHCONF
+ name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
+ if (name_max == -1)
+#if defined(NAME_MAX)
+ name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
+#else
+ return (size_t)(-1);
+#endif
+#elif defined(NAME_MAX)
+ name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
+#else
+#error "buffer size for readdir_r cannot be determined"
+#endif
+ name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1;
+ return (name_end > sizeof(struct _tinydir_dirent) ?
+ name_end : sizeof(struct _tinydir_dirent));
+}
+#endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+# if defined (_MSC_VER)
+# pragma warning(pop)
+# endif
+
+#endif
diff --git a/src/apps/lwiperf/lwiperf.c b/src/apps/lwiperf/lwiperf.c
new file mode 100644
index 00000000000..26b1e3b591b
--- /dev/null
+++ b/src/apps/lwiperf/lwiperf.c
@@ -0,0 +1,847 @@
+/**
+ * @file
+ * lwIP iPerf server implementation
+ */
+
+/**
+ * @defgroup iperf Iperf server
+ * @ingroup apps
+ *
+ * This is a simple performance measuring client/server to check your bandwidth using
+ * iPerf2 on a PC as server/client.
+ * It is currently a minimal implementation providing a TCP client/server only.
+ *
+ * @todo:
+ * - implement UDP mode
+ * - protect combined sessions handling (via 'related_master_state') against reallocation
+ * (this is a pointer address, currently, so if the same memory is allocated again,
+ * session pairs (tx/rx) can be confused on reallocation)
+ */
+
+/*
+ * Copyright (c) 2014 Simon Goldschmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt
+ */
+
+#include "lwip/apps/lwiperf.h"
+
+#include "lwip/tcp.h"
+#include "lwip/sys.h"
+#include "lwip/inet.h"
+
+#include <string.h>
+
+/* Currently, only TCP is implemented */
+#if LWIP_TCP && LWIP_CALLBACK_API
+
+/** Specify the idle timeout (in seconds) after that the test fails */
+#ifndef LWIPERF_TCP_MAX_IDLE_SEC
+#define LWIPERF_TCP_MAX_IDLE_SEC 10U
+#endif
+#if LWIPERF_TCP_MAX_IDLE_SEC > 255
+#error LWIPERF_TCP_MAX_IDLE_SEC must fit into an u8_t
+#endif
+
+/** Change this if you don't want to lwiperf to listen to any IP version */
+#ifndef LWIPERF_SERVER_IP_TYPE
+#define LWIPERF_SERVER_IP_TYPE IPADDR_TYPE_ANY
+#endif
+
+/* File internal memory allocation (struct lwiperf_*): this defaults to
+ the heap */
+#ifndef LWIPERF_ALLOC
+#define LWIPERF_ALLOC(type) mem_malloc(sizeof(type))
+#define LWIPERF_FREE(type, item) mem_free(item)
+#endif
+
+/** If this is 1, check that received data has the correct format */
+#ifndef LWIPERF_CHECK_RX_DATA
+#define LWIPERF_CHECK_RX_DATA 0
+#endif
+
+/** This is the Iperf settings struct sent from the client */
+typedef struct _lwiperf_settings {
+#define LWIPERF_FLAGS_ANSWER_TEST 0x80000000
+#define LWIPERF_FLAGS_ANSWER_NOW 0x00000001
+ u32_t flags;
+ u32_t num_threads; /* unused for now */
+ u32_t remote_port;
+ u32_t buffer_len; /* unused for now */
+ u32_t win_band; /* TCP window / UDP rate: unused for now */
+ u32_t amount; /* pos. value: bytes?; neg. values: time (unit is 10ms: 1/100 second) */
+} lwiperf_settings_t;
+
+/** Basic connection handle */
+struct _lwiperf_state_base;
+typedef struct _lwiperf_state_base lwiperf_state_base_t;
+struct _lwiperf_state_base {
+ /* linked list */
+ lwiperf_state_base_t *next;
+ /* 1=tcp, 0=udp */
+ u8_t tcp;
+ /* 1=server, 0=client */
+ u8_t server;
+ /* master state used to abort sessions (e.g. listener, main client) */
+ lwiperf_state_base_t *related_master_state;
+};
+
+/** Connection handle for a TCP iperf session */
+typedef struct _lwiperf_state_tcp {
+ lwiperf_state_base_t base;
+ struct tcp_pcb *server_pcb;
+ struct tcp_pcb *conn_pcb;
+ u32_t time_started;
+ lwiperf_report_fn report_fn;
+ void *report_arg;
+ u8_t poll_count;
+ u8_t next_num;
+ /* 1=start server when client is closed */
+ u8_t client_tradeoff_mode;
+ u32_t bytes_transferred;
+ lwiperf_settings_t settings;
+ u8_t have_settings_buf;
+ u8_t specific_remote;
+ ip_addr_t remote_addr;
+} lwiperf_state_tcp_t;
+
+/** List of active iperf sessions */
+static lwiperf_state_base_t *lwiperf_all_connections;
+/** A const buffer to send from: we want to measure sending, not copying! */
+static const u8_t lwiperf_txbuf_const[1600] = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+};
+
+static err_t lwiperf_tcp_poll(void *arg, struct tcp_pcb *tpcb);
+static void lwiperf_tcp_err(void *arg, err_t err);
+static err_t lwiperf_start_tcp_server_impl(const ip_addr_t *local_addr, u16_t local_port,
+ lwiperf_report_fn report_fn, void *report_arg,
+ lwiperf_state_base_t *related_master_state, lwiperf_state_tcp_t **state);
+
+
+/** Add an iperf session to the 'active' list */
+static void
+lwiperf_list_add(lwiperf_state_base_t *item)
+{
+ item->next = lwiperf_all_connections;
+ lwiperf_all_connections = item;
+}
+
+/** Remove an iperf session from the 'active' list */
+static void
+lwiperf_list_remove(lwiperf_state_base_t *item)
+{
+ lwiperf_state_base_t *prev = NULL;
+ lwiperf_state_base_t *iter;
+ for (iter = lwiperf_all_connections; iter != NULL; prev = iter, iter = iter->next) {
+ if (iter == item) {
+ if (prev == NULL) {
+ lwiperf_all_connections = iter->next;
+ } else {
+ prev->next = iter->next;
+ }
+ /* @debug: ensure this item is listed only once */
+ for (iter = iter->next; iter != NULL; iter = iter->next) {
+ LWIP_ASSERT("duplicate entry", iter != item);
+ }
+ break;
+ }
+ }
+}
+
+static lwiperf_state_base_t *
+lwiperf_list_find(lwiperf_state_base_t *item)
+{
+ lwiperf_state_base_t *iter;
+ for (iter = lwiperf_all_connections; iter != NULL; iter = iter->next) {
+ if (iter == item) {
+ return item;
+ }
+ }
+ return NULL;
+}
+
+/** Call the report function of an iperf tcp session */
+static void
+lwip_tcp_conn_report(lwiperf_state_tcp_t *conn, enum lwiperf_report_type report_type)
+{
+ if ((conn != NULL) && (conn->report_fn != NULL)) {
+ u32_t now, duration_ms, bandwidth_kbitpsec;
+ now = sys_now();
+ duration_ms = now - conn->time_started;
+ if (duration_ms == 0) {
+ bandwidth_kbitpsec = 0;
+ } else {
+ bandwidth_kbitpsec = (conn->bytes_transferred / duration_ms) * 8U;
+ }
+ conn->report_fn(conn->report_arg, report_type,
+ &conn->conn_pcb->local_ip, conn->conn_pcb->local_port,
+ &conn->conn_pcb->remote_ip, conn->conn_pcb->remote_port,
+ conn->bytes_transferred, duration_ms, bandwidth_kbitpsec);
+ }
+}
+
+/** Close an iperf tcp session */
+static void
+lwiperf_tcp_close(lwiperf_state_tcp_t *conn, enum lwiperf_report_type report_type)
+{
+ err_t err;
+
+ lwiperf_list_remove(&conn->base);
+ lwip_tcp_conn_report(conn, report_type);
+ if (conn->conn_pcb != NULL) {
+ tcp_arg(conn->conn_pcb, NULL);
+ tcp_poll(conn->conn_pcb, NULL, 0);
+ tcp_sent(conn->conn_pcb, NULL);
+ tcp_recv(conn->conn_pcb, NULL);
+ tcp_err(conn->conn_pcb, NULL);
+ err = tcp_close(conn->conn_pcb);
+ if (err != ERR_OK) {
+ /* don't want to wait for free memory here... */
+ tcp_abort(conn->conn_pcb);
+ }
+ } else if (conn->server_pcb != NULL) {
+ /* no conn pcb, this is the listener pcb */
+ err = tcp_close(conn->server_pcb);
+ LWIP_ASSERT("error", err == ERR_OK);
+ }
+ LWIPERF_FREE(lwiperf_state_tcp_t, conn);
+}
+
+/** Try to send more data on an iperf tcp session */
+static err_t
+lwiperf_tcp_client_send_more(lwiperf_state_tcp_t *conn)
+{
+ int send_more;
+ err_t err;
+ u16_t txlen;
+ u16_t txlen_max;
+ void *txptr;
+ u8_t apiflags;
+
+ LWIP_ASSERT("conn invalid", (conn != NULL) && conn->base.tcp && (conn->base.server == 0));
+
+ do {
+ send_more = 0;
+ if (conn->settings.amount & PP_HTONL(0x80000000)) {
+ /* this session is time-limited */
+ u32_t now = sys_now();
+ u32_t diff_ms = now - conn->time_started;
+ u32_t time = (u32_t) - (s32_t)lwip_htonl(conn->settings.amount);
+ u32_t time_ms = time * 10;
+ if (diff_ms >= time_ms) {
+ /* time specified by the client is over -> close the connection */
+ lwiperf_tcp_close(conn, LWIPERF_TCP_DONE_CLIENT);
+ return ERR_OK;
+ }
+ } else {
+ /* this session is byte-limited */
+ u32_t amount_bytes = lwip_htonl(conn->settings.amount);
+ /* @todo: this can send up to 1*MSS more than requested... */
+ if (amount_bytes >= conn->bytes_transferred) {
+ /* all requested bytes transferred -> close the connection */
+ lwiperf_tcp_close(conn, LWIPERF_TCP_DONE_CLIENT);
+ return ERR_OK;
+ }
+ }
+
+ if (conn->bytes_transferred < 24) {
+ /* transmit the settings a first time */
+ txptr = &((u8_t *)&conn->settings)[conn->bytes_transferred];
+ txlen_max = (u16_t)(24 - conn->bytes_transferred);
+ apiflags = TCP_WRITE_FLAG_COPY;
+ } else if (conn->bytes_transferred < 48) {
+ /* transmit the settings a second time */
+ txptr = &((u8_t *)&conn->settings)[conn->bytes_transferred - 24];
+ txlen_max = (u16_t)(48 - conn->bytes_transferred);
+ apiflags = TCP_WRITE_FLAG_COPY | TCP_WRITE_FLAG_MORE;
+ send_more = 1;
+ } else {
+ /* transmit data */
+ /* @todo: every x bytes, transmit the settings again */
+ txptr = LWIP_CONST_CAST(void *, &lwiperf_txbuf_const[conn->bytes_transferred % 10]);
+ txlen_max = TCP_MSS;
+ if (conn->bytes_transferred == 48) { /* @todo: fix this for intermediate settings, too */
+ txlen_max = TCP_MSS - 24;
+ }
+ apiflags = 0; /* no copying needed */
+ send_more = 1;
+ }
+ txlen = txlen_max;
+ do {
+ err = tcp_write(conn->conn_pcb, txptr, txlen, apiflags);
+ if (err == ERR_MEM) {
+ txlen /= 2;
+ }
+ } while ((err == ERR_MEM) && (txlen >= (TCP_MSS / 2)));
+
+ if (err == ERR_OK) {
+ conn->bytes_transferred += txlen;
+ } else {
+ send_more = 0;
+ }
+ } while (send_more);
+
+ tcp_output(conn->conn_pcb);
+ return ERR_OK;
+}
+
+/** TCP sent callback, try to send more data */
+static err_t
+lwiperf_tcp_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
+{
+ lwiperf_state_tcp_t *conn = (lwiperf_state_tcp_t *)arg;
+ /* @todo: check 'len' (e.g. to time ACK of all data)? for now, we just send more... */
+ LWIP_ASSERT("invalid conn", conn->conn_pcb == tpcb);
+ LWIP_UNUSED_ARG(tpcb);
+ LWIP_UNUSED_ARG(len);
+
+ conn->poll_count = 0;
+
+ return lwiperf_tcp_client_send_more(conn);
+}
+
+/** TCP connected callback (active connection), send data now */
+static err_t
+lwiperf_tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
+{
+ lwiperf_state_tcp_t *conn = (lwiperf_state_tcp_t *)arg;
+ LWIP_ASSERT("invalid conn", conn->conn_pcb == tpcb);
+ LWIP_UNUSED_ARG(tpcb);
+ if (err != ERR_OK) {
+ lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_REMOTE);
+ return ERR_OK;
+ }
+ conn->poll_count = 0;
+ conn->time_started = sys_now();
+ return lwiperf_tcp_client_send_more(conn);
+}
+
+/** Start TCP connection back to the client (either parallel or after the
+ * receive test has finished.
+ */
+static err_t
+lwiperf_tx_start_impl(const ip_addr_t *remote_ip, u16_t remote_port, lwiperf_settings_t *settings, lwiperf_report_fn report_fn,
+ void *report_arg, lwiperf_state_base_t *related_master_state, lwiperf_state_tcp_t **new_conn)
+{
+ err_t err;
+ lwiperf_state_tcp_t *client_conn;
+ struct tcp_pcb *newpcb;
+ ip_addr_t remote_addr;
+
+ LWIP_ASSERT("remote_ip != NULL", remote_ip != NULL);
+ LWIP_ASSERT("remote_ip != NULL", settings != NULL);
+ LWIP_ASSERT("new_conn != NULL", new_conn != NULL);
+ *new_conn = NULL;
+
+ client_conn = (lwiperf_state_tcp_t *)LWIPERF_ALLOC(lwiperf_state_tcp_t);
+ if (client_conn == NULL) {
+ return ERR_MEM;
+ }
+ newpcb = tcp_new_ip_type(IP_GET_TYPE(remote_ip));
+ if (newpcb == NULL) {
+ LWIPERF_FREE(lwiperf_state_tcp_t, client_conn);
+ return ERR_MEM;
+ }
+ memset(client_conn, 0, sizeof(lwiperf_state_tcp_t));
+ client_conn->base.tcp = 1;
+ client_conn->base.related_master_state = related_master_state;
+ client_conn->conn_pcb = newpcb;
+ client_conn->time_started = sys_now(); /* @todo: set this again on 'connected' */
+ client_conn->report_fn = report_fn;
+ client_conn->report_arg = report_arg;
+ client_conn->next_num = 4; /* initial nr is '4' since the header has 24 byte */
+ client_conn->bytes_transferred = 0;
+ memcpy(&client_conn->settings, settings, sizeof(*settings));
+ client_conn->have_settings_buf = 1;
+
+ tcp_arg(newpcb, client_conn);
+ tcp_sent(newpcb, lwiperf_tcp_client_sent);
+ tcp_poll(newpcb, lwiperf_tcp_poll, 2U);
+ tcp_err(newpcb, lwiperf_tcp_err);
+
+ ip_addr_copy(remote_addr, *remote_ip);
+
+ err = tcp_connect(newpcb, &remote_addr, remote_port, lwiperf_tcp_client_connected);
+ if (err != ERR_OK) {
+ lwiperf_tcp_close(client_conn, LWIPERF_TCP_ABORTED_LOCAL);
+ return err;
+ }
+ lwiperf_list_add(&client_conn->base);
+ *new_conn = client_conn;
+ return ERR_OK;
+}
+
+static err_t
+lwiperf_tx_start_passive(lwiperf_state_tcp_t *conn)
+{
+ err_t ret;
+ lwiperf_state_tcp_t *new_conn = NULL;
+ u16_t remote_port = (u16_t)lwip_htonl(conn->settings.remote_port);
+
+ ret = lwiperf_tx_start_impl(&conn->conn_pcb->remote_ip, remote_port, &conn->settings, conn->report_fn, conn->report_arg,
+ conn->base.related_master_state, &new_conn);
+ if (ret == ERR_OK) {
+ LWIP_ASSERT("new_conn != NULL", new_conn != NULL);
+ new_conn->settings.flags = 0; /* prevent the remote side starting back as client again */
+ }
+ return ret;
+}
+
+/** Receive data on an iperf tcp session */
+static err_t
+lwiperf_tcp_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
+{
+ u8_t tmp;
+ u16_t tot_len;
+ u32_t packet_idx;
+ struct pbuf *q;
+ lwiperf_state_tcp_t *conn = (lwiperf_state_tcp_t *)arg;
+
+ LWIP_ASSERT("pcb mismatch", conn->conn_pcb == tpcb);
+ LWIP_UNUSED_ARG(tpcb);
+
+ if (err != ERR_OK) {
+ lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_REMOTE);
+ return ERR_OK;
+ }
+ if (p == NULL) {
+ /* connection closed -> test done */
+ if (conn->settings.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST)) {
+ if ((conn->settings.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_NOW)) == 0) {
+ /* client requested transmission after end of test */
+ lwiperf_tx_start_passive(conn);
+ }
+ }
+ lwiperf_tcp_close(conn, LWIPERF_TCP_DONE_SERVER);
+ return ERR_OK;
+ }
+ tot_len = p->tot_len;
+
+ conn->poll_count = 0;
+
+ if ((!conn->have_settings_buf) || ((conn->bytes_transferred - 24) % (1024 * 128) == 0)) {
+ /* wait for 24-byte header */
+ if (p->tot_len < sizeof(lwiperf_settings_t)) {
+ lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL_DATAERROR);
+ pbuf_free(p);
+ return ERR_OK;
+ }
+ if (!conn->have_settings_buf) {
+ if (pbuf_copy_partial(p, &conn->settings, sizeof(lwiperf_settings_t), 0) != sizeof(lwiperf_settings_t)) {
+ lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL);
+ pbuf_free(p);
+ return ERR_OK;
+ }
+ conn->have_settings_buf = 1;
+ if (conn->settings.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST)) {
+ if (conn->settings.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_NOW)) {
+ /* client requested parallel transmission test */
+ err_t err2 = lwiperf_tx_start_passive(conn);
+ if (err2 != ERR_OK) {
+ lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL_TXERROR);
+ pbuf_free(p);
+ return ERR_OK;
+ }
+ }
+ }
+ } else {
+ if (conn->settings.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST)) {
+ if (pbuf_memcmp(p, 0, &conn->settings, sizeof(lwiperf_settings_t)) != 0) {
+ lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL_DATAERROR);
+ pbuf_free(p);
+ return ERR_OK;
+ }
+ }
+ }
+ conn->bytes_transferred += sizeof(lwiperf_settings_t);
+ if (conn->bytes_transferred <= 24) {
+ conn->time_started = sys_now();
+ tcp_recved(tpcb, p->tot_len);
+ pbuf_free(p);
+ return ERR_OK;
+ }
+ conn->next_num = 4; /* 24 bytes received... */
+ tmp = pbuf_remove_header(p, 24);
+ LWIP_ASSERT("pbuf_remove_header failed", tmp == 0);
+ LWIP_UNUSED_ARG(tmp); /* for LWIP_NOASSERT */
+ }
+
+ packet_idx = 0;
+ for (q = p; q != NULL; q = q->next) {
+#if LWIPERF_CHECK_RX_DATA
+ const u8_t *payload = (const u8_t *)q->payload;
+ u16_t i;
+ for (i = 0; i < q->len; i++) {
+ u8_t val = payload[i];
+ u8_t num = val - '0';
+ if (num == conn->next_num) {
+ conn->next_num++;
+ if (conn->next_num == 10) {
+ conn->next_num = 0;
+ }
+ } else {
+ lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL_DATAERROR);
+ pbuf_free(p);
+ return ERR_OK;
+ }
+ }
+#endif
+ packet_idx += q->len;
+ }
+ LWIP_ASSERT("count mismatch", packet_idx == p->tot_len);
+ conn->bytes_transferred += packet_idx;
+ tcp_recved(tpcb, tot_len);
+ pbuf_free(p);
+ return ERR_OK;
+}
+
+/** Error callback, iperf tcp session aborted */
+static void
+lwiperf_tcp_err(void *arg, err_t err)
+{
+ lwiperf_state_tcp_t *conn = (lwiperf_state_tcp_t *)arg;
+ LWIP_UNUSED_ARG(err);
+
+ /* pcb is already deallocated, prevent double-free */
+ conn->conn_pcb = NULL;
+ conn->server_pcb = NULL;
+
+ lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_REMOTE);
+}
+
+/** TCP poll callback, try to send more data */
+static err_t
+lwiperf_tcp_poll(void *arg, struct tcp_pcb *tpcb)
+{
+ lwiperf_state_tcp_t *conn = (lwiperf_state_tcp_t *)arg;
+ LWIP_ASSERT("pcb mismatch", conn->conn_pcb == tpcb);
+ LWIP_UNUSED_ARG(tpcb);
+ if (++conn->poll_count >= LWIPERF_TCP_MAX_IDLE_SEC) {
+ lwiperf_tcp_close(conn, LWIPERF_TCP_ABORTED_LOCAL);
+ return ERR_OK; /* lwiperf_tcp_close frees conn */
+ }
+
+ if (!conn->base.server) {
+ lwiperf_tcp_client_send_more(conn);
+ }
+
+ return ERR_OK;
+}
+
+/** This is called when a new client connects for an iperf tcp session */
+static err_t
+lwiperf_tcp_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
+{
+ lwiperf_state_tcp_t *s, *conn;
+ if ((err != ERR_OK) || (newpcb == NULL) || (arg == NULL)) {
+ return ERR_VAL;
+ }
+
+ s = (lwiperf_state_tcp_t *)arg;
+ LWIP_ASSERT("invalid session", s->base.server);
+ LWIP_ASSERT("invalid listen pcb", s->server_pcb != NULL);
+ LWIP_ASSERT("invalid conn pcb", s->conn_pcb == NULL);
+ if (s->specific_remote) {
+ LWIP_ASSERT("s->base.related_master_state != NULL", s->base.related_master_state != NULL);
+ if (!ip_addr_eq(&newpcb->remote_ip, &s->remote_addr)) {
+ /* this listener belongs to a client session, and this is not the correct remote */
+ return ERR_VAL;
+ }
+ } else {
+ LWIP_ASSERT("s->base.related_master_state == NULL", s->base.related_master_state == NULL);
+ }
+
+ conn = (lwiperf_state_tcp_t *)LWIPERF_ALLOC(lwiperf_state_tcp_t);
+ if (conn == NULL) {
+ return ERR_MEM;
+ }
+ memset(conn, 0, sizeof(lwiperf_state_tcp_t));
+ conn->base.tcp = 1;
+ conn->base.server = 1;
+ conn->base.related_master_state = &s->base;
+ conn->conn_pcb = newpcb;
+ conn->time_started = sys_now();
+ conn->report_fn = s->report_fn;
+ conn->report_arg = s->report_arg;
+
+ /* setup the tcp rx connection */
+ tcp_arg(newpcb, conn);
+ tcp_recv(newpcb, lwiperf_tcp_recv);
+ tcp_poll(newpcb, lwiperf_tcp_poll, 2U);
+ tcp_err(conn->conn_pcb, lwiperf_tcp_err);
+
+ if (s->specific_remote) {
+ /* this listener belongs to a client, so make the client the master of the newly created connection */
+ conn->base.related_master_state = s->base.related_master_state;
+ /* if dual mode or (tradeoff mode AND client is done): close the listener */
+ if (!s->client_tradeoff_mode || !lwiperf_list_find(s->base.related_master_state)) {
+ /* prevent report when closing: this is expected */
+ s->report_fn = NULL;
+ lwiperf_tcp_close(s, LWIPERF_TCP_ABORTED_LOCAL);
+ }
+ }
+ lwiperf_list_add(&conn->base);
+ return ERR_OK;
+}
+
+/**
+ * @ingroup iperf
+ * Start a TCP iperf server on the default TCP port (5001) and listen for
+ * incoming connections from iperf clients.
+ *
+ * @returns a connection handle that can be used to abort the server
+ * by calling @ref lwiperf_abort()
+ */
+void *
+lwiperf_start_tcp_server_default(lwiperf_report_fn report_fn, void *report_arg)
+{
+ return lwiperf_start_tcp_server(IP_ADDR_ANY, LWIPERF_TCP_PORT_DEFAULT,
+ report_fn, report_arg);
+}
+
+/**
+ * @ingroup iperf
+ * Start a TCP iperf server on a specific IP address and port and listen for
+ * incoming connections from iperf clients.
+ *
+ * @returns a connection handle that can be used to abort the server
+ * by calling @ref lwiperf_abort()
+ */
+void *
+lwiperf_start_tcp_server(const ip_addr_t *local_addr, u16_t local_port,
+ lwiperf_report_fn report_fn, void *report_arg)
+{
+ err_t err;
+ lwiperf_state_tcp_t *state = NULL;
+
+ err = lwiperf_start_tcp_server_impl(local_addr, local_port, report_fn, report_arg,
+ NULL, &state);
+ if (err == ERR_OK) {
+ return state;
+ }
+ return NULL;
+}
+
+static err_t lwiperf_start_tcp_server_impl(const ip_addr_t *local_addr, u16_t local_port,
+ lwiperf_report_fn report_fn, void *report_arg,
+ lwiperf_state_base_t *related_master_state, lwiperf_state_tcp_t **state)
+{
+ err_t err;
+ struct tcp_pcb *pcb;
+ lwiperf_state_tcp_t *s;
+
+ LWIP_ASSERT_CORE_LOCKED();
+
+ LWIP_ASSERT("state != NULL", state != NULL);
+
+ if (local_addr == NULL) {
+ return ERR_ARG;
+ }
+
+ s = (lwiperf_state_tcp_t *)LWIPERF_ALLOC(lwiperf_state_tcp_t);
+ if (s == NULL) {
+ return ERR_MEM;
+ }
+ memset(s, 0, sizeof(lwiperf_state_tcp_t));
+ s->base.tcp = 1;
+ s->base.server = 1;
+ s->base.related_master_state = related_master_state;
+ s->report_fn = report_fn;
+ s->report_arg = report_arg;
+
+ pcb = tcp_new_ip_type(LWIPERF_SERVER_IP_TYPE);
+ if (pcb == NULL) {
+ return ERR_MEM;
+ }
+ err = tcp_bind(pcb, local_addr, local_port);
+ if (err != ERR_OK) {
+ return err;
+ }
+ s->server_pcb = tcp_listen_with_backlog(pcb, 1);
+ if (s->server_pcb == NULL) {
+ if (pcb != NULL) {
+ tcp_close(pcb);
+ }
+ LWIPERF_FREE(lwiperf_state_tcp_t, s);
+ return ERR_MEM;
+ }
+ pcb = NULL;
+
+ tcp_arg(s->server_pcb, s);
+ tcp_accept(s->server_pcb, lwiperf_tcp_accept);
+
+ lwiperf_list_add(&s->base);
+ *state = s;
+ return ERR_OK;
+}
+
+/**
+ * @ingroup iperf
+ * Start a TCP iperf client to the default TCP port (5001).
+ *
+ * @returns a connection handle that can be used to abort the client
+ * by calling @ref lwiperf_abort()
+ */
+void* lwiperf_start_tcp_client_default(const ip_addr_t* remote_addr,
+ lwiperf_report_fn report_fn, void* report_arg)
+{
+ return lwiperf_start_tcp_client(remote_addr, LWIPERF_TCP_PORT_DEFAULT, LWIPERF_CLIENT,
+ report_fn, report_arg);
+}
+
+/**
+ * @ingroup iperf
+ * Start a TCP iperf client to a specific IP address and port.
+ *
+ * @returns a connection handle that can be used to abort the client
+ * by calling @ref lwiperf_abort()
+ */
+void* lwiperf_start_tcp_client(const ip_addr_t* remote_addr, u16_t remote_port,
+ enum lwiperf_client_type type, lwiperf_report_fn report_fn, void* report_arg)
+{
+ err_t ret;
+ lwiperf_settings_t settings;
+ lwiperf_state_tcp_t *state = NULL;
+
+ memset(&settings, 0, sizeof(settings));
+ switch (type) {
+ case LWIPERF_CLIENT:
+ /* Unidirectional tx only test */
+ settings.flags = 0;
+ break;
+ case LWIPERF_DUAL:
+ /* Do a bidirectional test simultaneously */
+ settings.flags = htonl(LWIPERF_FLAGS_ANSWER_TEST | LWIPERF_FLAGS_ANSWER_NOW);
+ break;
+ case LWIPERF_TRADEOFF:
+ /* Do a bidirectional test individually */
+ settings.flags = htonl(LWIPERF_FLAGS_ANSWER_TEST);
+ break;
+ default:
+ /* invalid argument */
+ return NULL;
+ }
+ settings.num_threads = htonl(1);
+ settings.remote_port = htonl(LWIPERF_TCP_PORT_DEFAULT);
+ /* TODO: implement passing duration/amount of bytes to transfer */
+ settings.amount = htonl((u32_t)-1000);
+
+ ret = lwiperf_tx_start_impl(remote_addr, remote_port, &settings, report_fn, report_arg, NULL, &state);
+ if (ret == ERR_OK) {
+ LWIP_ASSERT("state != NULL", state != NULL);
+ if (type != LWIPERF_CLIENT) {
+ /* start corresponding server now */
+ lwiperf_state_tcp_t *server = NULL;
+ ret = lwiperf_start_tcp_server_impl(&state->conn_pcb->local_ip, LWIPERF_TCP_PORT_DEFAULT,
+ report_fn, report_arg, (lwiperf_state_base_t *)state, &server);
+ if (ret != ERR_OK) {
+ /* starting server failed, abort client */
+ lwiperf_abort(state);
+ return NULL;
+ }
+ /* make this server accept one connection only */
+ server->specific_remote = 1;
+ server->remote_addr = state->conn_pcb->remote_ip;
+ if (type == LWIPERF_TRADEOFF) {
+ /* tradeoff means that the remote host connects only after the client is done,
+ so keep the listen pcb open until the client is done */
+ server->client_tradeoff_mode = 1;
+ }
+ }
+ return state;
+ }
+ return NULL;
+}
+
+/**
+ * @ingroup iperf
+ * Abort an iperf session (handle returned by lwiperf_start_tcp_server*())
+ */
+void
+lwiperf_abort(void *lwiperf_session)
+{
+ lwiperf_state_base_t *i, *dealloc, *last = NULL;
+
+ LWIP_ASSERT_CORE_LOCKED();
+
+ for (i = lwiperf_all_connections; i != NULL; ) {
+ if ((i == lwiperf_session) || (i->related_master_state == lwiperf_session)) {
+ dealloc = i;
+ i = i->next;
+ if (last != NULL) {
+ last->next = i;
+ }
+ LWIPERF_FREE(lwiperf_state_tcp_t, dealloc); /* @todo: type? */
+ } else {
+ last = i;
+ i = i->next;
+ }
+ }
+}
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API */
diff --git a/src/apps/mdns/mdns.c b/src/apps/mdns/mdns.c
new file mode 100644
index 00000000000..394d9d1c713
--- /dev/null
+++ b/src/apps/mdns/mdns.c
@@ -0,0 +1,2855 @@
+/**
+ * @file
+ * MDNS responder implementation
+ *
+ * @defgroup mdns MDNS
+ * @ingroup apps
+ *
+ * RFC 6762 - Multicast DNS<br>
+ * RFC 6763 - DNS-Based Service Discovery
+ *
+ * You need to increase MEMP_NUM_SYS_TIMEOUT by one if you use MDNS!
+ *
+ * @verbinclude mdns.txt
+ *
+ * Things left to implement:
+ * -------------------------
+ *
+ * - Sending goodbye messages (zero ttl) - shutdown, DHCP lease about to expire, DHCP turned off...
+ * - Sending negative responses NSEC
+ * - Fragmenting replies if required
+ * - Individual known answer detection for all local IPv6 addresses
+ * - Dynamic size of outgoing packet
+ */
+
+/*
+ * Copyright (c) 2015 Verisure Innovation AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Erik Ekman <[email protected]>
+ * Author: Jasper Verschueren <[email protected]>
+ *
+ */
+
+#include "lwip/apps/mdns.h"
+#include "lwip/apps/mdns_priv.h"
+#include "lwip/apps/mdns_domain.h"
+#include "lwip/apps/mdns_out.h"
+#include "lwip/netif.h"
+#include "lwip/udp.h"
+#include "lwip/ip_addr.h"
+#include "lwip/mem.h"
+#include "lwip/memp.h"
+#include "lwip/prot/dns.h"
+#include "lwip/prot/iana.h"
+#include "lwip/timeouts.h"
+#include "lwip/sys.h"
+
+#include <string.h> /* memset */
+#include <stdio.h> /* snprintf */
+
+#if LWIP_MDNS_RESPONDER
+
+#if (LWIP_IPV4 && !LWIP_IGMP)
+#error "If you want to use MDNS with IPv4, you have to define LWIP_IGMP=1 in your lwipopts.h"
+#endif
+#if (LWIP_IPV6 && !LWIP_IPV6_MLD)
+#error "If you want to use MDNS with IPv6, you have to define LWIP_IPV6_MLD=1 in your lwipopts.h"
+#endif
+#if (!LWIP_UDP)
+#error "If you want to use MDNS, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+#ifndef LWIP_RAND
+#error "If you want to use MDNS, you have to define LWIP_RAND=(random function) in your lwipopts.h"
+#endif
+
+#if LWIP_IPV4
+#include "lwip/igmp.h"
+/* IPv4 multicast group 224.0.0.251 */
+static const ip_addr_t v4group = DNS_MQUERY_IPV4_GROUP_INIT;
+#endif
+
+#if LWIP_IPV6
+#include "lwip/mld6.h"
+/* IPv6 multicast group FF02::FB */
+static const ip_addr_t v6group = DNS_MQUERY_IPV6_GROUP_INIT;
+#endif
+
+#define MDNS_IP_TTL 255
+
+#if LWIP_MDNS_SEARCH
+static struct mdns_request mdns_requests[MDNS_MAX_REQUESTS];
+#endif
+
+static u8_t mdns_netif_client_id;
+static struct udp_pcb *mdns_pcb;
+#if MDNS_RESP_USENETIF_EXTCALLBACK
+NETIF_DECLARE_EXT_CALLBACK(netif_callback)
+#endif
+static mdns_name_result_cb_t mdns_name_result_cb;
+
+#define NETIF_TO_HOST(netif) (struct mdns_host*)(netif_get_client_data(netif, mdns_netif_client_id))
+
+/** Delayed response defines */
+#define MDNS_RESPONSE_DELAY_MAX 120
+#define MDNS_RESPONSE_DELAY_MIN 20
+#define MDNS_RESPONSE_DELAY (LWIP_RAND() %(MDNS_RESPONSE_DELAY_MAX - \
+ MDNS_RESPONSE_DELAY_MIN) + MDNS_RESPONSE_DELAY_MIN)
+/* Delayed response for truncated question defines */
+#define MDNS_RESPONSE_TC_DELAY_MAX 500
+#define MDNS_RESPONSE_TC_DELAY_MIN 400
+#define MDNS_RESPONSE_TC_DELAY_MS (LWIP_RAND() % (MDNS_RESPONSE_TC_DELAY_MAX - \
+ MDNS_RESPONSE_TC_DELAY_MIN) + MDNS_RESPONSE_TC_DELAY_MIN)
+
+/** Probing & announcing defines */
+#define MDNS_PROBE_COUNT 3
+#ifdef LWIP_RAND
+/* first probe timeout SHOULD be random 0-250 ms*/
+#define MDNS_INITIAL_PROBE_DELAY_MS (LWIP_RAND() % MDNS_PROBE_DELAY_MS)
+#else
+#define MDNS_INITIAL_PROBE_DELAY_MS MDNS_PROBE_DELAY_MS
+#endif
+
+#define MDNS_PROBE_TIEBREAK_CONFLICT_DELAY_MS 1000
+#define MDNS_PROBE_TIEBREAK_MAX_ANSWERS 5
+
+#define MDNS_LEXICOGRAPHICAL_EQUAL 0
+#define MDNS_LEXICOGRAPHICAL_EARLIER 1
+#define MDNS_LEXICOGRAPHICAL_LATER 2
+
+/* Delay between successive announcements (RFC6762 section 8.3)
+ * -> increase by a factor 2 with every response sent.
+ */
+#define MDNS_ANNOUNCE_DELAY_MS 1000
+/* Minimum 2 announces, may send up to 8 (RFC6762 section 8.3) */
+#define MDNS_ANNOUNCE_COUNT 2
+
+/** Information about received packet */
+struct mdns_packet {
+ /** Sender IP/port */
+ ip_addr_t source_addr;
+ u16_t source_port;
+ /** If packet was received unicast */
+ u16_t recv_unicast;
+ /** Packet data */
+ struct pbuf *pbuf;
+ /** Current parsing offset in packet */
+ u16_t parse_offset;
+ /** Identifier. Used in legacy queries */
+ u16_t tx_id;
+ /** Number of questions in packet,
+ * read from packet header */
+ u16_t questions;
+ /** Number of unparsed questions */
+ u16_t questions_left;
+ /** Number of answers in packet */
+ u16_t answers;
+ /** Number of unparsed answers */
+ u16_t answers_left;
+ /** Number of authoritative answers in packet */
+ u16_t authoritative;
+ /** Number of unparsed authoritative answers */
+ u16_t authoritative_left;
+ /** Number of additional answers in packet */
+ u16_t additional;
+ /** Number of unparsed additional answers */
+ u16_t additional_left;
+ /** Chained list of known answer received after a truncated question */
+ struct mdns_packet *next_answer;
+ /** Chained list of truncated question that are waiting */
+ struct mdns_packet *next_tc_question;
+};
+
+/* list of received questions with TC flags set, waiting for known answers */
+static struct mdns_packet *pending_tc_questions;
+
+/* pool of received packets */
+LWIP_MEMPOOL_DECLARE(MDNS_PKTS, MDNS_MAX_STORED_PKTS, sizeof (struct mdns_packet), "Stored mDNS packets")
+
+struct mdns_question {
+ struct mdns_rr_info info;
+ /** unicast reply requested */
+ u16_t unicast;
+};
+
+struct mdns_answer_list {
+ u16_t offset[MDNS_PROBE_TIEBREAK_MAX_ANSWERS];
+ u16_t size;
+};
+
+static err_t mdns_parse_pkt_questions(struct netif *netif,
+ struct mdns_packet *pkt,
+ struct mdns_outmsg *reply);
+static void mdns_define_probe_rrs_to_send(struct netif *netif,
+ struct mdns_outmsg *outmsg);
+static void mdns_probe_and_announce(void* arg);
+static void mdns_conflict_save_time(struct netif *netif);
+
+/**
+ * Construction to make mdns struct accessible from mdns_out.c
+ * TODO:
+ * can we add the mdns struct to the netif like we do for dhcp, autoip,...?
+ * Then this is not needed any more.
+ *
+ * @param netif The network interface
+ * @return mdns struct
+ */
+struct mdns_host*
+netif_mdns_data(struct netif *netif) {
+ return NETIF_TO_HOST(netif);
+}
+
+/**
+ * Construction to access the mdns udp pcb.
+ *
+ * @return udp_pcb struct of mdns
+ */
+struct udp_pcb*
+get_mdns_pcb(void)
+{
+ return mdns_pcb;
+}
+
+/**
+ * Check which replies we should send for a host/netif based on question
+ * @param netif The network interface that received the question
+ * @param rr Domain/type/class from a question
+ * @param reverse_v6_reply Bitmask of which IPv6 addresses to send reverse PTRs for
+ * if reply bit has REPLY_HOST_PTR_V6 set
+ * @return Bitmask of which replies to send
+ */
+static int
+check_host(struct netif *netif, struct mdns_rr_info *rr, u8_t *reverse_v6_reply)
+{
+ err_t res;
+ int replies = 0;
+ struct mdns_domain mydomain;
+
+ LWIP_UNUSED_ARG(reverse_v6_reply); /* if ipv6 is disabled */
+
+ if (rr->klass != DNS_RRCLASS_IN && rr->klass != DNS_RRCLASS_ANY) {
+ /* Invalid class */
+ return replies;
+ }
+
+ /* Handle PTR for our addresses */
+ if (rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY) {
+#if LWIP_IPV6
+ int i;
+ for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+ if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) {
+ res = mdns_build_reverse_v6_domain(&mydomain, netif_ip6_addr(netif, i));
+ if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) {
+ replies |= REPLY_HOST_PTR_V6;
+ /* Mark which addresses where requested */
+ if (reverse_v6_reply) {
+ *reverse_v6_reply |= (1 << i);
+ }
+ }
+ }
+ }
+#endif
+#if LWIP_IPV4
+ if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+ res = mdns_build_reverse_v4_domain(&mydomain, netif_ip4_addr(netif));
+ if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) {
+ replies |= REPLY_HOST_PTR_V4;
+ }
+ }
+#endif
+ }
+
+ res = mdns_build_host_domain(&mydomain, NETIF_TO_HOST(netif));
+ /* Handle requests for our hostname */
+ if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) {
+ /* TODO return NSEC if unsupported protocol requested */
+#if LWIP_IPV4
+ if (!ip4_addr_isany_val(*netif_ip4_addr(netif))
+ && (rr->type == DNS_RRTYPE_A || rr->type == DNS_RRTYPE_ANY)) {
+ replies |= REPLY_HOST_A;
+ }
+#endif
+#if LWIP_IPV6
+ if (rr->type == DNS_RRTYPE_AAAA || rr->type == DNS_RRTYPE_ANY) {
+ replies |= REPLY_HOST_AAAA;
+ }
+#endif
+ }
+
+ return replies;
+}
+
+/**
+ * Check which replies we should send for a service based on question
+ * @param service A registered MDNS service
+ * @param rr Domain/type/class from a question
+ * @return Bitmask of which replies to send
+ */
+static int
+check_service(struct mdns_service *service, struct mdns_rr_info *rr)
+{
+ err_t res;
+ int replies = 0;
+ struct mdns_domain mydomain;
+
+ if (rr->klass != DNS_RRCLASS_IN && rr->klass != DNS_RRCLASS_ANY) {
+ /* Invalid class */
+ return 0;
+ }
+
+ res = mdns_build_dnssd_domain(&mydomain);
+ if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain) &&
+ (rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY)) {
+ /* Request for all service types */
+ replies |= REPLY_SERVICE_TYPE_PTR;
+ }
+
+ res = mdns_build_service_domain(&mydomain, service, 0);
+ if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain) &&
+ (rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY)) {
+ /* Request for the instance of my service */
+ replies |= REPLY_SERVICE_NAME_PTR;
+ }
+
+ res = mdns_build_service_domain(&mydomain, service, 1);
+ if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) {
+ /* Request for info about my service */
+ if (rr->type == DNS_RRTYPE_SRV || rr->type == DNS_RRTYPE_ANY) {
+ replies |= REPLY_SERVICE_SRV;
+ }
+ if (rr->type == DNS_RRTYPE_TXT || rr->type == DNS_RRTYPE_ANY) {
+ replies |= REPLY_SERVICE_TXT;
+ }
+ }
+
+ return replies;
+}
+
+#if LWIP_MDNS_SEARCH
+/**
+ * Check if question belong to a specified request
+ * @param request A ongoing MDNS request
+ * @param rr Domain/type/class from an answer
+ * @return Bitmask of which matching replies
+ */
+static int
+check_request(struct mdns_request *request, struct mdns_rr_info *rr)
+{
+ err_t res;
+ int replies = 0;
+ struct mdns_domain mydomain;
+
+ if (rr->klass != DNS_RRCLASS_IN && rr->klass != DNS_RRCLASS_ANY) {
+ /* Invalid class */
+ return 0;
+ }
+
+ res = mdns_build_request_domain(&mydomain, request, 0);
+ if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain) &&
+ (rr->type == DNS_RRTYPE_PTR || rr->type == DNS_RRTYPE_ANY)) {
+ /* Request for the instance of my service */
+ replies |= REPLY_SERVICE_TYPE_PTR;
+ }
+ res = mdns_build_request_domain(&mydomain, request, 1);
+ if (res == ERR_OK && mdns_domain_eq(&rr->domain, &mydomain)) {
+ /* Request for info about my service */
+ if (rr->type == DNS_RRTYPE_SRV || rr->type == DNS_RRTYPE_ANY) {
+ replies |= REPLY_SERVICE_SRV;
+ }
+ if (rr->type == DNS_RRTYPE_TXT || rr->type == DNS_RRTYPE_ANY) {
+ replies |= REPLY_SERVICE_TXT;
+ }
+ }
+ return replies;
+}
+#endif
+
+/**
+ * Helper function for mdns_read_question/mdns_read_answer
+ * Reads a domain, type and class from the packet
+ * @param pkt The MDNS packet to read from. The parse_offset field will be
+ * incremented to point to the next unparsed byte.
+ * @param info The struct to fill with domain, type and class
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_read_rr_info(struct mdns_packet *pkt, struct mdns_rr_info *info)
+{
+ u16_t field16, copied;
+ pkt->parse_offset = mdns_readname(pkt->pbuf, pkt->parse_offset, &info->domain);
+ if (pkt->parse_offset == MDNS_READNAME_ERROR) {
+ return ERR_VAL;
+ }
+
+ copied = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), pkt->parse_offset);
+ if (copied != sizeof(field16)) {
+ return ERR_VAL;
+ }
+ pkt->parse_offset += copied;
+ info->type = lwip_ntohs(field16);
+
+ copied = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), pkt->parse_offset);
+ if (copied != sizeof(field16)) {
+ return ERR_VAL;
+ }
+ pkt->parse_offset += copied;
+ info->klass = lwip_ntohs(field16);
+
+ return ERR_OK;
+}
+
+/**
+ * Read a question from the packet.
+ * All questions have to be read before the answers.
+ * @param pkt The MDNS packet to read from. The questions_left field will be decremented
+ * and the parse_offset will be updated.
+ * @param question The struct to fill with question data
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_read_question(struct mdns_packet *pkt, struct mdns_question *question)
+{
+ /* Safety check */
+ if (pkt->pbuf->tot_len < pkt->parse_offset) {
+ return ERR_VAL;
+ }
+
+ if (pkt->questions_left) {
+ err_t res;
+ pkt->questions_left--;
+
+ memset(question, 0, sizeof(struct mdns_question));
+ res = mdns_read_rr_info(pkt, &question->info);
+ if (res != ERR_OK) {
+ return res;
+ }
+
+ /* Extract unicast flag from class field */
+ question->unicast = question->info.klass & 0x8000;
+ question->info.klass &= 0x7FFF;
+
+ return ERR_OK;
+ }
+ return ERR_VAL;
+}
+
+/**
+ * Read an answer from the packet
+ * The variable length reply is not copied, its pbuf offset and length is stored instead.
+ * @param pkt The MDNS packet to read. The num_left field will be decremented and
+ * the parse_offset will be updated.
+ * @param answer The struct to fill with answer data
+ * @param num_left number of answers left -> answers, authoritative or additional
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_read_answer(struct mdns_packet *pkt, struct mdns_answer *answer, u16_t *num_left)
+{
+ /* Read questions first */
+ if (pkt->questions_left) {
+ return ERR_VAL;
+ }
+
+ /* Safety check */
+ if (pkt->pbuf->tot_len < pkt->parse_offset) {
+ return ERR_VAL;
+ }
+
+ if (*num_left) {
+ u16_t copied, field16;
+ u32_t ttl;
+ err_t res;
+ (*num_left)--;
+
+ memset(answer, 0, sizeof(struct mdns_answer));
+ res = mdns_read_rr_info(pkt, &answer->info);
+ if (res != ERR_OK) {
+ return res;
+ }
+
+ /* Extract cache_flush flag from class field */
+ answer->cache_flush = answer->info.klass & 0x8000;
+ answer->info.klass &= 0x7FFF;
+
+ copied = pbuf_copy_partial(pkt->pbuf, &ttl, sizeof(ttl), pkt->parse_offset);
+ if (copied != sizeof(ttl)) {
+ return ERR_VAL;
+ }
+ pkt->parse_offset += copied;
+ answer->ttl = lwip_ntohl(ttl);
+
+ copied = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), pkt->parse_offset);
+ if (copied != sizeof(field16)) {
+ return ERR_VAL;
+ }
+ pkt->parse_offset += copied;
+ answer->rd_length = lwip_ntohs(field16);
+
+ answer->rd_offset = pkt->parse_offset;
+ pkt->parse_offset += answer->rd_length;
+
+ return ERR_OK;
+ }
+ return ERR_VAL;
+}
+
+/**
+ * Send unsolicited answer containing all our known data
+ * @param netif The network interface to send on
+ * @param destination The target address to send to (usually multicast address)
+ */
+static void
+mdns_announce(struct netif *netif, const ip_addr_t *destination)
+{
+ struct mdns_outmsg announce;
+ int i;
+ struct mdns_host *mdns = NETIF_TO_HOST(netif);
+
+ memset(&announce, 0, sizeof(announce));
+ announce.cache_flush = 1;
+#if LWIP_IPV4
+ if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+ announce.host_replies = REPLY_HOST_A | REPLY_HOST_PTR_V4;
+ }
+#endif
+#if LWIP_IPV6
+ for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+ if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) {
+ announce.host_replies |= REPLY_HOST_AAAA | REPLY_HOST_PTR_V6;
+ announce.host_reverse_v6_replies |= (1 << i);
+ }
+ }
+#endif
+
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ struct mdns_service *serv = mdns->services[i];
+ if (serv) {
+ announce.serv_replies[i] = REPLY_SERVICE_TYPE_PTR | REPLY_SERVICE_NAME_PTR |
+ REPLY_SERVICE_SRV | REPLY_SERVICE_TXT;
+ }
+ }
+
+ announce.dest_port = LWIP_IANA_PORT_MDNS;
+ SMEMCPY(&announce.dest_addr, destination, sizeof(announce.dest_addr));
+ announce.flags = DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE;
+ mdns_send_outpacket(&announce, netif);
+}
+
+/**
+ * Perform lexicographical comparison to define the lexicographical order of the
+ * records.
+ *
+ * @param pkt_a first packet (needed for rr data)
+ * @param pkt_b second packet (needed for rr data)
+ * @param ans_a first rr
+ * @param ans_b second rr
+ * @param result pointer to save result in -> MDNS_LEXICOGRAPHICAL_EQUAL,
+ * MDNS_LEXICOGRAPHICAL_LATER or MDNS_LEXICOGRAPHICAL_EARLIER.
+ * @return err_t ERR_OK if result is good, ERR_VAL if domain decompression failed.
+ */
+static err_t
+mdns_lexicographical_comparison(struct mdns_packet *pkt_a, struct mdns_packet *pkt_b,
+ struct mdns_answer *ans_a, struct mdns_answer *ans_b,
+ u8_t *result)
+{
+ int len, i;
+ u8_t a_rd, b_rd;
+ u16_t res;
+ struct mdns_domain domain_a, domain_b;
+
+ /* Compare classes */
+ if (ans_a->info.klass != ans_b->info.klass) {
+ if (ans_a->info.klass > ans_b->info.klass) {
+ *result = MDNS_LEXICOGRAPHICAL_LATER;
+ return ERR_OK;
+ }
+ else {
+ *result = MDNS_LEXICOGRAPHICAL_EARLIER;
+ return ERR_OK;
+ }
+ }
+ /* Compare types */
+ if (ans_a->info.type != ans_b->info.type) {
+ if (ans_a->info.type > ans_b->info.type) {
+ *result = MDNS_LEXICOGRAPHICAL_LATER;
+ return ERR_OK;
+ }
+ else {
+ *result = MDNS_LEXICOGRAPHICAL_EARLIER;
+ return ERR_OK;
+ }
+ }
+
+ /* Compare rr data section
+ * Name compression:
+ * We have 4 different RR types in our authoritative section (if IPv4 and IPv6 is enabled): A,
+ * AAAA, SRV and TXT. Only one of the 4 can be subject to name compression in the rdata, the SRV
+ * record. As stated in the RFC6762 section 8.2: the names must be uncompressed before comparison.
+ * We only need to take the SRV record into account. It's the only one that in a comparison with
+ * compressed data could lead to rdata comparison. Others will already stop after the type
+ * comparison. So if we get passed the class and type comparison we need to check if the
+ * comparison contains an SRV record. If so, we need a different comparison method.
+ */
+
+ /* The answers do not contain an SRV record */
+ if (ans_a->info.type != DNS_RRTYPE_SRV && ans_b->info.type != DNS_RRTYPE_SRV) {
+ len = LWIP_MIN(ans_a->rd_length, ans_b->rd_length);
+ for (i = 0; i < len; i++) {
+ a_rd = pbuf_get_at(pkt_a->pbuf, (u16_t)(ans_a->rd_offset + i));
+ b_rd = pbuf_get_at(pkt_b->pbuf, (u16_t)(ans_b->rd_offset + i));
+ if (a_rd != b_rd) {
+ if (a_rd > b_rd) {
+ *result = MDNS_LEXICOGRAPHICAL_LATER;
+ return ERR_OK;
+ }
+ else {
+ *result = MDNS_LEXICOGRAPHICAL_EARLIER;
+ return ERR_OK;
+ }
+ }
+ }
+ /* If the overlapping data is the same, compare the length */
+ if (ans_a->rd_length != ans_b->rd_length) {
+ if (ans_a->rd_length > ans_b->rd_length) {
+ *result = MDNS_LEXICOGRAPHICAL_LATER;
+ return ERR_OK;
+ }
+ else {
+ *result = MDNS_LEXICOGRAPHICAL_EARLIER;
+ return ERR_OK;
+ }
+ }
+ }
+ /* Because the types are guaranteed equal here, we know they are both SRV RRs */
+ else {
+ /* We will first compare the priority, weight and port */
+ for (i = 0; i < 6; i++) {
+ a_rd = pbuf_get_at(pkt_a->pbuf, (u16_t)(ans_a->rd_offset + i));
+ b_rd = pbuf_get_at(pkt_b->pbuf, (u16_t)(ans_b->rd_offset + i));
+ if (a_rd != b_rd) {
+ if (a_rd > b_rd) {
+ *result = MDNS_LEXICOGRAPHICAL_LATER;
+ return ERR_OK;
+ }
+ else {
+ *result = MDNS_LEXICOGRAPHICAL_EARLIER;
+ return ERR_OK;
+ }
+ }
+ }
+ /* Decompress names if compressed and save in domain_a or domain_b */
+ res = mdns_readname(pkt_a->pbuf, ans_a->rd_offset + 6, &domain_a);
+ if (res == MDNS_READNAME_ERROR) {
+ return ERR_VAL;
+ }
+ res = mdns_readname(pkt_b->pbuf, ans_b->rd_offset + 6, &domain_b);
+ if (res == MDNS_READNAME_ERROR) {
+ return ERR_VAL;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: domain a: len = %d, name = ", domain_a.name[0]));
+ mdns_domain_debug_print(&domain_a);
+ LWIP_DEBUGF(MDNS_DEBUG, ("\n"));
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: domain b: len = %d, name = ", domain_b.name[0]));
+ mdns_domain_debug_print(&domain_b);
+ LWIP_DEBUGF(MDNS_DEBUG, ("\n"));
+ /* Compare names pairwise */
+ len = LWIP_MIN(domain_a.length, domain_b.length);
+ for (i = 0; i < len; i++) {
+ if (domain_a.name[i] != domain_b.name[i]) {
+ if (domain_a.name[i] > domain_b.name[i]) {
+ *result = MDNS_LEXICOGRAPHICAL_LATER;
+ return ERR_OK;
+ }
+ else {
+ *result = MDNS_LEXICOGRAPHICAL_EARLIER;
+ return ERR_OK;
+ }
+ }
+ }
+ /* If the overlapping data is the same, compare the length */
+ if (domain_a.length != domain_b.length) {
+ if (domain_a.length > domain_b.length) {
+ *result = MDNS_LEXICOGRAPHICAL_LATER;
+ return ERR_OK;
+ }
+ else {
+ *result = MDNS_LEXICOGRAPHICAL_EARLIER;
+ return ERR_OK;
+ }
+ }
+ }
+ /* They are exactly the same */
+ *result = MDNS_LEXICOGRAPHICAL_EQUAL;
+ return ERR_OK;
+}
+
+/**
+ * Clear authoritative answer list
+ *
+ * @param a_list answer list to clear
+ */
+static void
+mdns_init_answer_list(struct mdns_answer_list *a_list)
+{
+ int i;
+ a_list->size = 0;
+ for(i = 0; i < MDNS_PROBE_TIEBREAK_MAX_ANSWERS; i++) {
+ a_list->offset[i] = 0;
+ }
+}
+
+/**
+ * Pushes the offset of the answer on a lexicographically later sorted list.
+ * We use a simple insertion sort because most of the time we are only sorting
+ * two items. The answers are sorted from the smallest to the largest.
+ *
+ * @param a_list Answer list to which to add the answer
+ * @param pkt Packet where answer originated
+ * @param new_offset Offset of the new answer in the packet
+ * @param new_answer The new answer
+ * @return err_t ERR_MEM if list is full
+ */
+static err_t
+mdns_push_answer_to_sorted_list(struct mdns_answer_list *a_list,
+ struct mdns_packet *pkt,
+ u16_t new_offset,
+ struct mdns_answer *new_answer)
+{
+ int i;
+ struct mdns_answer a;
+ int pos = a_list->size;
+ err_t res = ERR_OK;
+ u8_t result;
+ u16_t num_left = pkt->authoritative;
+ u16_t parse_offset = pkt->parse_offset;
+
+ /* Check size */
+ if ((a_list->size + 1) >= MDNS_PROBE_TIEBREAK_MAX_ANSWERS) {
+ return ERR_MEM;
+ }
+ /* Search location and open a location */
+ for (i = 0; i < a_list->size; i++) {
+ /* Read answers already in the list from pkt */
+ pkt->parse_offset = a_list->offset[i];
+ res = mdns_read_answer(pkt, &a, &num_left);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping probe packet\n"));
+ return res;
+ }
+ /* Compare them with the new answer to find it's place */
+ res = mdns_lexicographical_comparison(pkt, pkt, &a, new_answer, &result);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to compare answers, skipping probe packet\n"));
+ return res;
+ }
+ if (result == MDNS_LEXICOGRAPHICAL_LATER) {
+ int j;
+ pos = i;
+ for (j = (a_list->size + 1); j>i; j--) {
+ a_list->offset[j] = a_list->offset[j-1];
+ }
+ break;
+ }
+ }
+ /* Insert new value */
+ a_list->offset[pos] = new_offset;
+ a_list->size++;
+ /* Reset parse offset for further evaluation */
+ pkt->parse_offset = parse_offset;
+ return res;
+}
+
+/**
+ * Check if the given answer answers the give question
+ *
+ * @param q query to find answer for
+ * @param a answer to given query
+ * @return 1 it a answers q, 0 if not
+ */
+static u8_t
+mdns_is_answer_to_question(struct mdns_question *q, struct mdns_answer *a)
+{
+ if (q->info.type == DNS_RRTYPE_ANY || q->info.type == a->info.type) {
+ /* The types match or question type is any */
+ if (mdns_domain_eq(&q->info.domain, &a->info.domain)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Converts the output packet to the input packet format for probe tiebreaking
+ *
+ * @param inpkt destination packet for conversion
+ * @param outpkt source packet for conversion
+ */
+static void
+mdns_convert_out_to_in_pkt(struct mdns_packet *inpkt, struct mdns_outpacket *outpkt)
+{
+ inpkt->pbuf = outpkt->pbuf;
+ inpkt->parse_offset = SIZEOF_DNS_HDR;
+
+ inpkt->questions = inpkt->questions_left = outpkt->questions;
+ inpkt->answers = inpkt->answers_left = outpkt->answers;
+ inpkt->authoritative = inpkt->authoritative_left = outpkt->authoritative;
+ inpkt->additional = inpkt->additional_left = outpkt->additional;
+}
+
+/**
+ * Debug print to print the answer part that is lexicographically compared
+ *
+ * @param pkt Packet where answer originated
+ * @param a The answer to print
+ */
+static void
+mdns_debug_print_answer(struct mdns_packet *pkt, struct mdns_answer *a)
+{
+#ifdef LWIP_DEBUG
+ /* Arbitrarily chose 200 -> don't want to see more then that. It's only
+ * for debug so not that important. */
+ char string[200];
+ int i;
+ int pos;
+
+ pos = snprintf(string, sizeof(string), "Type = %2d, class = %1d, rdata = ", a->info.type, a->info.klass);
+ for (i = 0; ((i < a->rd_length) && ((pos + 4*i) < 195)) ; i++) {
+ snprintf(&string[pos + 4*i], 5, "%3d ", (u8_t)pbuf_get_at(pkt->pbuf, (u16_t)(a->rd_offset + i)));
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: %s\n", string));
+#else
+ LWIP_UNUSED_ARG(pkt);
+ LWIP_UNUSED_ARG(a);
+#endif
+}
+
+/**
+ * Perform probe tiebreaking according to RFC6762 section 8.2
+ *
+ * @param netif network interface of incoming packet
+ * @param pkt incoming packet
+ */
+static void
+mdns_handle_probe_tiebreaking(struct netif *netif, struct mdns_packet *pkt)
+{
+ struct mdns_question pkt_q, my_q, q_dummy;
+ struct mdns_answer pkt_a, my_a;
+ struct mdns_outmsg myprobe_msg;
+ struct mdns_outpacket myprobe_outpkt;
+ struct mdns_packet myprobe_inpkt;
+ struct mdns_answer_list pkt_a_list, my_a_list;
+ u16_t save_parse_offset;
+ u16_t pkt_parse_offset, myprobe_parse_offset, myprobe_questions_left;
+ err_t res;
+ u8_t match, result;
+ int min, i;
+
+ /* Generate probe packet to perform comparison.
+ * This is a lot of calculation at this stage without any pre calculation
+ * needed. It should be evaluated if this is the best approach.
+ */
+ mdns_define_probe_rrs_to_send(netif, &myprobe_msg);
+ memset(&myprobe_outpkt, 0, sizeof(myprobe_outpkt));
+ memset(&myprobe_inpkt, 0, sizeof(myprobe_inpkt));
+ res = mdns_create_outpacket(netif, &myprobe_msg, &myprobe_outpkt);
+ if (res != ERR_OK) {
+ goto cleanup;
+ }
+ mdns_convert_out_to_in_pkt(&myprobe_inpkt, &myprobe_outpkt);
+
+ /* Loop over all our probes to search for matches */
+ while (myprobe_inpkt.questions_left) {
+ /* Read one of our probe questions to check if pkt contains same question */
+ res = mdns_read_question(&myprobe_inpkt, &my_q);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping probe packet\n"));
+ goto cleanup;
+ }
+ /* Remember parse offsets so we can restart the search for the next question */
+ pkt_parse_offset = pkt->parse_offset;
+ myprobe_parse_offset = myprobe_inpkt.parse_offset;
+ /* Remember questions left of our probe packet */
+ myprobe_questions_left = myprobe_inpkt.questions_left;
+ /* Reset match flag */
+ match = 0;
+ /* Search for a matching probe in the incoming packet */
+ while (pkt->questions_left) {
+ /* Read probe questions one by one */
+ res = mdns_read_question(pkt, &pkt_q);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping probe packet\n"));
+ goto cleanup;
+ }
+ /* Stop evaluating if the class is not supported */
+ if (pkt_q.info.klass != DNS_RRCLASS_IN && pkt_q.info.klass != DNS_RRCLASS_ANY) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: question class not supported, skipping probe packet\n"));
+ goto cleanup;
+ }
+ /* We probe for type any, so we do not have to compare types */
+ /* Compare if we are probing for the same domain */
+ if (mdns_domain_eq(&pkt_q.info.domain, &my_q.info.domain)) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: We are probing for the same rr\n"));
+ match = 1;
+ break;
+ }
+ }
+ /* When matched start evaluating the authoritative section */
+ if (match) {
+ /* Ignore all following questions to be able to get to the authoritative answers */
+ while (pkt->questions_left) {
+ res = mdns_read_question(pkt, &q_dummy);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping probe packet\n"));
+ goto cleanup;
+ }
+ }
+ while (myprobe_inpkt.questions_left) {
+ res = mdns_read_question(&myprobe_inpkt, &q_dummy);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping probe packet\n"));
+ goto cleanup;
+ }
+ }
+
+ /* Extract and sort our authoritative answers that answer our question */
+ mdns_init_answer_list(&my_a_list);
+ while(myprobe_inpkt.authoritative_left) {
+ save_parse_offset = myprobe_inpkt.parse_offset;
+ res = mdns_read_answer(&myprobe_inpkt, &my_a, &myprobe_inpkt.authoritative_left);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping probe packet\n"));
+ goto cleanup;
+ }
+ if (mdns_is_answer_to_question(&my_q, &my_a)) {
+ /* Add to list */
+ res = mdns_push_answer_to_sorted_list(&my_a_list, &myprobe_inpkt, save_parse_offset, &my_a);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to add answer, skipping probe packet\n"));
+ goto cleanup;
+ }
+ }
+ }
+ /* Extract and sort the packets authoritative answers that answer the
+ question */
+ mdns_init_answer_list(&pkt_a_list);
+ while(pkt->authoritative_left) {
+ save_parse_offset = pkt->parse_offset;
+ res = mdns_read_answer(pkt, &pkt_a, &pkt->authoritative_left);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping probe packet\n"));
+ goto cleanup;
+ }
+ if (mdns_is_answer_to_question(&my_q, &pkt_a)) {
+ /* Add to list */
+ res = mdns_push_answer_to_sorted_list(&pkt_a_list, pkt, save_parse_offset, &pkt_a);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to add answer, skipping probe packet\n"));
+ goto cleanup;
+ }
+ }
+ }
+
+ /* Reinitiate authoritative left */
+ myprobe_inpkt.authoritative_left = myprobe_inpkt.authoritative;
+ pkt->authoritative_left = pkt->authoritative;
+
+ /* Compare pairwise.
+ * - lexicographically later? -> we win, ignore the packet.
+ * - lexicographically earlier? -> we loose, wait one second and retry.
+ * - lexicographically equal? -> no conflict, check other probes.
+ */
+ min = LWIP_MIN(my_a_list.size, pkt_a_list.size);
+ for (i = 0; i < min; i++) {
+ /* Get answer of our own list */
+ myprobe_inpkt.parse_offset = my_a_list.offset[i];
+ res = mdns_read_answer(&myprobe_inpkt, &my_a, &myprobe_inpkt.authoritative_left);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping probe packet\n"));
+ goto cleanup;
+ }
+ /* Get answer of the packets list */
+ pkt->parse_offset = pkt_a_list.offset[i];
+ res = mdns_read_answer(pkt, &pkt_a, &pkt->authoritative_left);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping probe packet\n"));
+ goto cleanup;
+ }
+ /* Print both answers for debugging */
+ mdns_debug_print_answer(pkt, &pkt_a);
+ mdns_debug_print_answer(&myprobe_inpkt, &my_a);
+ /* Define the winner */
+ res = mdns_lexicographical_comparison(&myprobe_inpkt, pkt, &my_a, &pkt_a, &result);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to compare answers, skipping probe packet\n"));
+ goto cleanup;
+ }
+ if (result == MDNS_LEXICOGRAPHICAL_LATER) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: we win, we are lexicographically later\n"));
+ goto cleanup;
+ }
+ else if (result == MDNS_LEXICOGRAPHICAL_EARLIER) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: we loose, we are lexicographically earlier. 1s timeout started\n"));
+ /* Increase the number of conflicts occurred */
+ mdns_conflict_save_time(netif);
+ /* then restart with 1s delay */
+ mdns_resp_restart_delay(netif, MDNS_PROBE_TIEBREAK_CONFLICT_DELAY_MS);
+ goto cleanup;
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: lexicographically equal, so no conclusion\n"));
+ }
+ }
+ /* All compared RR were equal, otherwise we would not be here
+ * -> check if one of both have more answers to the question */
+ if (my_a_list.size != pkt_a_list.size) {
+ if (my_a_list.size > pkt_a_list.size) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: we win, we have more records answering the probe\n"));
+ goto cleanup;
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: we loose, we have less records. 1s timeout started\n"));
+ /* Increase the number of conflicts occurred */
+ mdns_conflict_save_time(netif);
+ /* then restart with 1s delay */
+ mdns_resp_restart_delay(netif, MDNS_PROBE_TIEBREAK_CONFLICT_DELAY_MS);
+ goto cleanup;
+ }
+ }
+ else {
+ /* There is no conflict on this probe, both devices have the same data
+ * in the authoritative section. We should still check the other probes
+ * for conflicts. */
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: no conflict, all records answering the probe are equal\n"));
+ }
+ }
+ /* Evaluate other probes if any. */
+ /* Reinitiate parse offsets */
+ pkt->parse_offset = pkt_parse_offset;
+ myprobe_inpkt.parse_offset = myprobe_parse_offset;
+ /* Reinitiate questions_left and authoritative_left */
+ pkt->questions_left = pkt->questions;
+ pkt->authoritative_left = pkt->authoritative;
+ myprobe_inpkt.questions_left = myprobe_questions_left;
+ myprobe_inpkt.authoritative_left = myprobe_inpkt.authoritative;
+ }
+
+cleanup:
+ if (myprobe_inpkt.pbuf != NULL) {
+ pbuf_free(myprobe_inpkt.pbuf);
+ }
+}
+
+/**
+ * Check the incoming packet and parse all questions
+ *
+ * @param netif network interface of incoming packet
+ * @param pkt incoming packet
+ * @param reply outgoing message
+ * @return err_t
+ */
+static err_t
+mdns_parse_pkt_questions(struct netif *netif, struct mdns_packet *pkt,
+ struct mdns_outmsg *reply)
+{
+ struct mdns_host *mdns = NETIF_TO_HOST(netif);
+ struct mdns_service *service;
+ int i;
+ err_t res;
+
+ while (pkt->questions_left) {
+ struct mdns_question q;
+
+ res = mdns_read_question(pkt, &q);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping query packet\n"));
+ return res;
+ }
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Query for domain "));
+ mdns_domain_debug_print(&q.info.domain);
+ LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", q.info.type, q.info.klass));
+
+ if (q.unicast) {
+ /* Reply unicast if it is requested in the question */
+ reply->unicast_reply_requested = 1;
+ }
+
+ reply->host_replies |= check_host(netif, &q.info, &reply->host_reverse_v6_replies);
+
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ service = mdns->services[i];
+ if (!service) {
+ continue;
+ }
+ reply->serv_replies[i] |= check_service(service, &q.info);
+ }
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * Check the incoming packet and parse all (known) answers
+ *
+ * @param netif network interface of incoming packet
+ * @param pkt incoming packet
+ * @param reply outgoing message
+ * @return err_t
+ */
+static err_t
+mdns_parse_pkt_known_answers(struct netif *netif, struct mdns_packet *pkt,
+ struct mdns_outmsg *reply)
+{
+ struct mdns_host *mdns = NETIF_TO_HOST(netif);
+ struct mdns_service *service;
+ int i;
+ err_t res;
+
+ while (pkt->answers_left) {
+ struct mdns_answer ans;
+ u8_t rev_v6;
+ int match;
+ u32_t rr_ttl = MDNS_TTL_120;
+
+ res = mdns_read_answer(pkt, &ans, &pkt->answers_left);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping query packet\n"));
+ return res;
+ }
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Known answer for domain "));
+ mdns_domain_debug_print(&ans.info.domain);
+ LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", ans.info.type, ans.info.klass));
+
+
+ if (ans.info.type == DNS_RRTYPE_ANY || ans.info.klass == DNS_RRCLASS_ANY) {
+ /* Skip known answers for ANY type & class */
+ continue;
+ }
+
+ rev_v6 = 0;
+ match = reply->host_replies & check_host(netif, &ans.info, &rev_v6);
+ if (match && (ans.ttl > (rr_ttl / 2))) {
+ /* The RR in the known answer matches an RR we are planning to send,
+ * and the TTL is less than half gone.
+ * If the payload matches we should not send that answer.
+ */
+ if (ans.info.type == DNS_RRTYPE_PTR) {
+ /* Read domain and compare */
+ struct mdns_domain known_ans, my_ans;
+ u16_t len;
+ len = mdns_readname(pkt->pbuf, ans.rd_offset, &known_ans);
+ res = mdns_build_host_domain(&my_ans, mdns);
+ if (len != MDNS_READNAME_ERROR && res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) {
+#if LWIP_IPV4
+ if (match & REPLY_HOST_PTR_V4) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: v4 PTR\n"));
+ reply->host_replies &= ~REPLY_HOST_PTR_V4;
+ }
+#endif
+#if LWIP_IPV6
+ if (match & REPLY_HOST_PTR_V6) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: v6 PTR\n"));
+ reply->host_reverse_v6_replies &= ~rev_v6;
+ if (reply->host_reverse_v6_replies == 0) {
+ reply->host_replies &= ~REPLY_HOST_PTR_V6;
+ }
+ }
+#endif
+ }
+ } else if (match & REPLY_HOST_A) {
+#if LWIP_IPV4
+ if (ans.rd_length == sizeof(ip4_addr_t) &&
+ pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip4_addr(netif), ans.rd_length) == 0) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: A\n"));
+ reply->host_replies &= ~REPLY_HOST_A;
+ }
+#endif
+ } else if (match & REPLY_HOST_AAAA) {
+#if LWIP_IPV6
+ if (ans.rd_length == sizeof(ip6_addr_p_t) &&
+ /* TODO this clears all AAAA responses if first addr is set as known */
+ pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip6_addr(netif, 0), ans.rd_length) == 0) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: AAAA\n"));
+ reply->host_replies &= ~REPLY_HOST_AAAA;
+ }
+#endif
+ }
+ }
+
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ service = mdns->services[i];
+ if (!service) {
+ continue;
+ }
+ match = reply->serv_replies[i] & check_service(service, &ans.info);
+ if (match & REPLY_SERVICE_TYPE_PTR) {
+ rr_ttl = MDNS_TTL_4500;
+ }
+ if (match && (ans.ttl > (rr_ttl / 2))) {
+ /* The RR in the known answer matches an RR we are planning to send,
+ * and the TTL is less than half gone.
+ * If the payload matches we should not send that answer.
+ */
+ if (ans.info.type == DNS_RRTYPE_PTR) {
+ /* Read domain and compare */
+ struct mdns_domain known_ans, my_ans;
+ u16_t len;
+ len = mdns_readname(pkt->pbuf, ans.rd_offset, &known_ans);
+ if (len != MDNS_READNAME_ERROR) {
+ if (match & REPLY_SERVICE_TYPE_PTR) {
+ res = mdns_build_service_domain(&my_ans, service, 0);
+ if (res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: service type PTR\n"));
+ reply->serv_replies[i] &= ~REPLY_SERVICE_TYPE_PTR;
+ }
+ }
+ if (match & REPLY_SERVICE_NAME_PTR) {
+ res = mdns_build_service_domain(&my_ans, service, 1);
+ if (res == ERR_OK && mdns_domain_eq(&known_ans, &my_ans)) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: service name PTR\n"));
+ reply->serv_replies[i] &= ~REPLY_SERVICE_NAME_PTR;
+ }
+ }
+ }
+ } else if (match & REPLY_SERVICE_SRV) {
+ /* Read and compare to my SRV record */
+ u16_t field16, len, read_pos;
+ struct mdns_domain known_ans, my_ans;
+ read_pos = ans.rd_offset;
+ do {
+ /* Check priority field */
+ len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+ if (len != sizeof(field16) || lwip_ntohs(field16) != SRV_PRIORITY) {
+ break;
+ }
+ read_pos += len;
+ /* Check weight field */
+ len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+ if (len != sizeof(field16) || lwip_ntohs(field16) != SRV_WEIGHT) {
+ break;
+ }
+ read_pos += len;
+ /* Check port field */
+ len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+ if (len != sizeof(field16) || lwip_ntohs(field16) != service->port) {
+ break;
+ }
+ read_pos += len;
+ /* Check host field */
+ len = mdns_readname(pkt->pbuf, read_pos, &known_ans);
+ mdns_build_host_domain(&my_ans, mdns);
+ if (len == MDNS_READNAME_ERROR || !mdns_domain_eq(&known_ans, &my_ans)) {
+ break;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: SRV\n"));
+ reply->serv_replies[i] &= ~REPLY_SERVICE_SRV;
+ } while (0);
+ } else if (match & REPLY_SERVICE_TXT) {
+ mdns_prepare_txtdata(service);
+ if (service->txtdata.length == ans.rd_length &&
+ pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.name, ans.rd_length) == 0) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Skipping known answer: TXT\n"));
+ reply->serv_replies[i] &= ~REPLY_SERVICE_TXT;
+ }
+ }
+ }
+ }
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * Check the incoming packet and parse all authoritative answers to see if the
+ * query is a probe query.
+ *
+ * @param netif network interface of incoming packet
+ * @param pkt incoming packet
+ * @param reply outgoing message
+ * @return err_t
+ */
+static err_t
+mdns_parse_pkt_authoritative_answers(struct netif *netif, struct mdns_packet *pkt,
+ struct mdns_outmsg *reply)
+{
+ struct mdns_host *mdns = NETIF_TO_HOST(netif);
+ struct mdns_service *service;
+ int i;
+ err_t res;
+
+ while (pkt->authoritative_left) {
+ struct mdns_answer ans;
+ u8_t rev_v6;
+ int match;
+
+ res = mdns_read_answer(pkt, &ans, &pkt->authoritative_left);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping query packet\n"));
+ return res;
+ }
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Authoritative answer for domain "));
+ mdns_domain_debug_print(&ans.info.domain);
+ LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", ans.info.type, ans.info.klass));
+
+
+ if (ans.info.type == DNS_RRTYPE_ANY || ans.info.klass == DNS_RRCLASS_ANY) {
+ /* Skip known answers for ANY type & class */
+ continue;
+ }
+
+ rev_v6 = 0;
+ match = reply->host_replies & check_host(netif, &ans.info, &rev_v6);
+ if (match) {
+ reply->probe_query_recv = 1;
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe for own host info received\n"));
+ }
+
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ service = mdns->services[i];
+ if (!service) {
+ continue;
+ }
+ match = reply->serv_replies[i] & check_service(service, &ans.info);
+
+ if (match) {
+ reply->probe_query_recv = 1;
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe for own service info received\n"));
+ }
+ }
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * Add / copy message to delaying message buffer.
+ *
+ * @param dest destination msg struct
+ * @param src source msg struct
+ */
+static void
+mdns_add_msg_to_delayed(struct mdns_outmsg *dest, struct mdns_outmsg *src)
+{
+ int i;
+
+ dest->host_questions |= src->host_questions;
+ dest->host_replies |= src->host_replies;
+ dest->host_reverse_v6_replies |= src->host_reverse_v6_replies;
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ dest->serv_questions[i] |= src->serv_questions[i];
+ dest->serv_replies[i] |= src->serv_replies[i];
+ }
+
+ dest->flags = src->flags;
+ dest->cache_flush = src->cache_flush;
+ dest->tx_id = src->tx_id;
+ dest->legacy_query = src->legacy_query;
+}
+
+/**
+ * Handle question MDNS packet
+ * - Perform probe tiebreaking when in probing state
+ * - Parse all questions and set bits what answers to send
+ * - Clear pending answers if known answers are supplied
+ * - Define which type of answer is requested
+ * - Send out packet or put it on hold until after random time
+ *
+ * @param pkt incoming packet (in stack)
+ * @param netif network interface of incoming packet
+ */
+static void
+mdns_handle_question(struct mdns_packet *pkt, struct netif *netif)
+{
+ struct mdns_host *mdns = NETIF_TO_HOST(netif);
+ struct mdns_outmsg reply;
+ u8_t rrs_to_send;
+ u8_t shared_answer = 0;
+ u8_t delay_response = 1;
+ u8_t send_unicast = 0;
+ u8_t listen_to_QU_bit = 0;
+ int i;
+ err_t res;
+
+ if ((mdns->state == MDNS_STATE_PROBING) ||
+ (mdns->state == MDNS_STATE_ANNOUNCE_WAIT)) {
+ /* Probe Tiebreaking */
+ /* Check if packet is a probe message */
+ if ((pkt->questions > 0) && (pkt->answers == 0) &&
+ (pkt->authoritative > 0) && (pkt->additional == 0)) {
+ /* This should be a probe message -> call probe handler */
+ mdns_handle_probe_tiebreaking(netif, pkt);
+ }
+ }
+
+ if ((mdns->state != MDNS_STATE_COMPLETE) &&
+ (mdns->state != MDNS_STATE_ANNOUNCING)) {
+ /* Don't answer questions until we've verified our domains via probing */
+ /* @todo we should check incoming questions during probing for tiebreaking */
+ return;
+ }
+
+ memset(&reply, 0, sizeof(struct mdns_outmsg));
+
+ /* Parse question */
+ res = mdns_parse_pkt_questions(netif, pkt, &reply);
+ if (res != ERR_OK) {
+ return;
+ }
+ /* Parse answers -> count as known answers because it's a question */
+ res = mdns_parse_pkt_known_answers(netif, pkt, &reply);
+ if (res != ERR_OK) {
+ return;
+ }
+ if (pkt->next_answer) {
+ /* Also parse known-answers from additional packets */
+ struct mdns_packet *pkta = pkt->next_answer;
+ while (pkta) {
+ res = mdns_parse_pkt_known_answers(netif, pkta, &reply);
+ if (res != ERR_OK) {
+ return;
+ }
+ pkta = pkta->next_answer;
+ }
+ }
+ /* Parse authoritative answers -> probing */
+ /* If it's a probe query, we need to directly answer via unicast. */
+ res = mdns_parse_pkt_authoritative_answers(netif, pkt, &reply);
+ if (res != ERR_OK) {
+ return;
+ }
+ /* Ignore additional answers -> do not have any need for them at the moment */
+ if(pkt->additional) {
+ LWIP_DEBUGF(MDNS_DEBUG,
+ ("MDNS: Query contains additional answers -> they are discarded\n"));
+ }
+
+ /* Any replies on question? */
+ rrs_to_send = reply.host_replies | reply.host_questions;
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ rrs_to_send |= reply.serv_replies[i] | reply.serv_questions[i];
+ }
+
+ if (!rrs_to_send) {
+ /* This case is most common */
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Nothing to answer\n"));
+ return;
+ }
+
+ reply.flags = DNS_FLAG1_RESPONSE | DNS_FLAG1_AUTHORATIVE;
+
+ /* Detect if it's a legacy querier asking the question
+ * How to detect legacy DNS query? (RFC6762 section 6.7)
+ * - source port != 5353
+ * - a legacy query can only contain 1 question
+ */
+ if (pkt->source_port != LWIP_IANA_PORT_MDNS) {
+ if (pkt->questions == 1) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: request from legacy querier\n"));
+ reply.legacy_query = 1;
+ reply.tx_id = pkt->tx_id;
+ reply.cache_flush = 0;
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: ignore query if (src UDP port != 5353) && (!= legacy query)\n"));
+ return;
+ }
+ }
+ else {
+ reply.cache_flush = 1;
+ }
+
+ /* Delaying response. (RFC6762 section 6)
+ * Always delay the response, unicast or multicast, except when:
+ * - Answering to a single question with a unique answer (not a probe).
+ * - Answering to a probe query via unicast.
+ * - Answering to a probe query via multicast if not multicasted within 250ms.
+ *
+ * unique answer? -> not if it includes service type or name ptr's
+ */
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ shared_answer |= (reply.serv_replies[i] &
+ (REPLY_SERVICE_TYPE_PTR | REPLY_SERVICE_NAME_PTR));
+ }
+ if ( ((pkt->questions == 1) && (!shared_answer) && !reply.probe_query_recv)
+ || (reply.probe_query_recv && reply.unicast_reply_requested)) {
+ delay_response = 0;
+ }
+#if LWIP_IPV6
+ if (IP_IS_V6_VAL(pkt->source_addr) && reply.probe_query_recv
+ && !reply.unicast_reply_requested && !mdns->ipv6.multicast_probe_timeout) {
+ delay_response = 0;
+ }
+#endif
+#if LWIP_IPV4
+ if (IP_IS_V4_VAL(pkt->source_addr) && reply.probe_query_recv
+ && !reply.unicast_reply_requested && !mdns->ipv4.multicast_probe_timeout) {
+ delay_response = 0;
+ }
+#endif
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: response %s delayed\n", (delay_response ? "randomly" : "not")));
+
+ /* Unicast / multicast response:
+ * Answering to (m)DNS querier via unicast response.
+ * When:
+ * a) Unicast reply requested && recently multicasted 1/4ttl (RFC6762 section 5.4)
+ * b) Direct unicast query to port 5353 (RFC6762 section 5.5)
+ * c) Reply to Legacy DNS querier (RFC6762 section 6.7)
+ * d) A probe message is received requesting unicast (RFC6762 section 6)
+ */
+
+#if LWIP_IPV6
+ if ((IP_IS_V6_VAL(pkt->source_addr) && mdns->ipv6.multicast_timeout_25TTL)) {
+ listen_to_QU_bit = 1;
+ }
+#endif
+#if LWIP_IPV4
+ if ((IP_IS_V4_VAL(pkt->source_addr) && mdns->ipv4.multicast_timeout_25TTL)) {
+ listen_to_QU_bit = 1;
+ }
+#endif
+ if ( (reply.unicast_reply_requested && listen_to_QU_bit)
+ || pkt->recv_unicast
+ || reply.legacy_query
+ || (reply.probe_query_recv && reply.unicast_reply_requested)) {
+ send_unicast = 1;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: send response via %s\n", (send_unicast ? "unicast" : "multicast")));
+
+ /* Send out or put on waiting list */
+ if (delay_response) {
+ if (send_unicast) {
+#if LWIP_IPV6
+ /* Add answers to IPv6 waiting list if:
+ * - it's a IPv6 incoming packet
+ * - no message is in it yet
+ */
+ if (IP_IS_V6_VAL(pkt->source_addr) && !mdns->ipv6.unicast_msg_in_use) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to unicast IPv6 waiting list\n"));
+ SMEMCPY(&mdns->ipv6.delayed_msg_unicast.dest_addr, &pkt->source_addr, sizeof(ip_addr_t));
+ mdns->ipv6.delayed_msg_unicast.dest_port = pkt->source_port;
+
+ mdns_add_msg_to_delayed(&mdns->ipv6.delayed_msg_unicast, &reply);
+
+ mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_unicast_msg_delayed_ipv6,
+ &mdns->ipv6.unicast_msg_in_use);
+ }
+#endif
+#if LWIP_IPV4
+ /* Add answers to IPv4 waiting list if:
+ * - it's a IPv4 incoming packet
+ * - no message is in it yet
+ */
+ if (IP_IS_V4_VAL(pkt->source_addr) && !mdns->ipv4.unicast_msg_in_use) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to unicast IPv4 waiting list\n"));
+ SMEMCPY(&mdns->ipv4.delayed_msg_unicast.dest_addr, &pkt->source_addr, sizeof(ip_addr_t));
+ mdns->ipv4.delayed_msg_unicast.dest_port = pkt->source_port;
+
+ mdns_add_msg_to_delayed(&mdns->ipv4.delayed_msg_unicast, &reply);
+
+ mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_unicast_msg_delayed_ipv4,
+ &mdns->ipv4.unicast_msg_in_use);
+ }
+#endif
+ }
+ else {
+#if LWIP_IPV6
+ /* Add answers to IPv6 waiting list if:
+ * - it's a IPv6 incoming packet
+ * - the 1 second timeout is passed (RFC6762 section 6)
+ * - and it's not a probe packet
+ * Or if:
+ * - it's a IPv6 incoming packet
+ * - and it's a probe packet
+ */
+ if (IP_IS_V6_VAL(pkt->source_addr) && !mdns->ipv6.multicast_timeout
+ && !reply.probe_query_recv) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to multicast IPv6 waiting list\n"));
+
+ mdns_add_msg_to_delayed(&mdns->ipv6.delayed_msg_multicast, &reply);
+
+ mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_multicast_msg_delayed_ipv6,
+ &mdns->ipv6.multicast_msg_waiting);
+ }
+ else if (IP_IS_V6_VAL(pkt->source_addr) && reply.probe_query_recv) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to probe multicast IPv6 waiting list\n"));
+
+ mdns_add_msg_to_delayed(&mdns->ipv6.delayed_msg_multicast, &reply);
+
+ mdns->ipv6.multicast_msg_waiting = 1;
+ }
+#endif
+#if LWIP_IPV4
+ /* Add answers to IPv4 waiting list if:
+ * - it's a IPv4 incoming packet
+ * - the 1 second timeout is passed (RFC6762 section 6)
+ * - and it's not a probe packet
+ * Or if:
+ * - it's a IPv4 incoming packet
+ * - and it's a probe packet
+ */
+ if (IP_IS_V4_VAL(pkt->source_addr) && !mdns->ipv4.multicast_timeout
+ && !reply.probe_query_recv) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to multicast IPv4 waiting list\n"));
+
+ mdns_add_msg_to_delayed(&mdns->ipv4.delayed_msg_multicast, &reply);
+
+ mdns_set_timeout(netif, MDNS_RESPONSE_DELAY, mdns_send_multicast_msg_delayed_ipv4,
+ &mdns->ipv4.multicast_msg_waiting);
+ }
+ else if (IP_IS_V4_VAL(pkt->source_addr) && reply.probe_query_recv) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: add answers to probe multicast IPv4 waiting list\n"));
+
+ mdns_add_msg_to_delayed(&mdns->ipv4.delayed_msg_multicast, &reply);
+
+ mdns->ipv4.multicast_msg_waiting = 1;
+ }
+#endif
+ }
+ }
+ else {
+ if (send_unicast) {
+ /* Copy source IP/port to use when responding unicast */
+ SMEMCPY(&reply.dest_addr, &pkt->source_addr, sizeof(ip_addr_t));
+ reply.dest_port = pkt->source_port;
+ /* send answer directly via unicast */
+ res = mdns_send_outpacket(&reply, netif);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Unicast answer could not be send\n"));
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Unicast answer send successfully\n"));
+ }
+ return;
+ }
+ else {
+ /* Set IP/port to use when responding multicast */
+#if LWIP_IPV6
+ if (IP_IS_V6_VAL(pkt->source_addr)) {
+ if (mdns->ipv6.multicast_timeout && !reply.probe_query_recv) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: we just multicasted, ignore question\n"));
+ return;
+ }
+ SMEMCPY(&reply.dest_addr, &v6group, sizeof(ip_addr_t));
+ }
+#endif
+#if LWIP_IPV4
+ if (IP_IS_V4_VAL(pkt->source_addr)) {
+ if (mdns->ipv4.multicast_timeout && !reply.probe_query_recv) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: we just multicasted, ignore question\n"));
+ return;
+ }
+ SMEMCPY(&reply.dest_addr, &v4group, sizeof(ip_addr_t));
+ }
+#endif
+ reply.dest_port = LWIP_IANA_PORT_MDNS;
+ /* send answer directly via multicast */
+ res = mdns_send_outpacket(&reply, netif);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Multicast answer could not be send\n"));
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Multicast answer send successfully\n"));
+#if LWIP_IPV6
+ if (IP_IS_V6_VAL(pkt->source_addr)) {
+ mdns_start_multicast_timeouts_ipv6(netif);
+ }
+#endif
+#if LWIP_IPV4
+ if (IP_IS_V4_VAL(pkt->source_addr)) {
+ mdns_start_multicast_timeouts_ipv4(netif);
+ }
+#endif
+ }
+ return;
+ }
+ }
+}
+
+/**
+ * Handle truncated question MDNS packet
+ * - Called by timer
+ * - Call mdns_handle_question
+ * - Do cleanup
+ *
+ * @param arg incoming packet (in pool)
+ */
+static void
+mdns_handle_tc_question(void *arg)
+{
+ struct mdns_packet *pkt = (struct mdns_packet *)arg;
+ struct netif *from = netif_get_by_index(pkt->pbuf->if_idx);
+ /* timer as elapsed, now handle this question */
+ mdns_handle_question(pkt, from);
+ /* remove from pending list */
+ if (pending_tc_questions == pkt) {
+ pending_tc_questions = pkt->next_tc_question;
+ }
+ else {
+ struct mdns_packet *prev = pending_tc_questions;
+ while (prev && prev->next_tc_question != pkt) {
+ prev = prev->next_tc_question;
+ }
+ LWIP_ASSERT("pkt not found in pending_tc_questions list", prev != NULL);
+ prev->next_tc_question = pkt->next_tc_question;
+ }
+ /* free linked answers and this question */
+ while (pkt->next_answer) {
+ struct mdns_packet *ans = pkt->next_answer;
+ pkt->next_answer = ans->next_answer;
+ pbuf_free(ans->pbuf);
+ LWIP_MEMPOOL_FREE(MDNS_PKTS, ans);
+ }
+ pbuf_free(pkt->pbuf);
+ LWIP_MEMPOOL_FREE(MDNS_PKTS, pkt);
+}
+
+/**
+ * Save time when a probe conflict occurs:
+ * - Check if we exceeded the maximum of 15 conflicts in 10seconds.
+ *
+ * @param netif network interface on which the conflict occurred.
+ */
+static void
+mdns_conflict_save_time(struct netif *netif)
+{
+ struct mdns_host* mdns = NETIF_TO_HOST(netif);
+ int i;
+ u32_t diff;
+ u8_t index2;
+
+ /* Increase the number of conflicts occurred */
+ mdns->num_conflicts++;
+ mdns->conflict_time[mdns->index] = sys_now();
+ /* Print timestamp list */
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: conflict timestamp list, insert index = %d\n", mdns->index));
+ for(i = 0; i < MDNS_PROBE_MAX_CONFLICTS_BEFORE_RATE_LIMIT; i++) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: time no. %d = %"U32_F"\n", i, mdns->conflict_time[i]));
+ }
+ /* Check if we had enough conflicts, minimum 15 */
+ if (mdns->num_conflicts >= MDNS_PROBE_MAX_CONFLICTS_BEFORE_RATE_LIMIT) {
+ /* Get the index to the oldest timestamp */
+ index2 = (mdns->index + 1) % MDNS_PROBE_MAX_CONFLICTS_BEFORE_RATE_LIMIT;
+ /* Compare the oldest vs newest time stamp */
+ diff = mdns->conflict_time[mdns->index] - mdns->conflict_time[index2];
+ /* If they are less then 10 seconds apart, initiate rate limit */
+ if (diff < MDNS_PROBE_MAX_CONFLICTS_TIME_WINDOW) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: probe rate limit enabled\n"));
+ mdns->rate_limit_activated = 1;
+ }
+ }
+ /* Increase index */
+ mdns->index = (mdns->index + 1) % MDNS_PROBE_MAX_CONFLICTS_BEFORE_RATE_LIMIT;
+}
+
+/**
+ * Handle a probe conflict:
+ * - Check if we exceeded the maximum of 15 conflicts in 10seconds.
+ * - Let the user know there is a conflict.
+ *
+ * @param netif network interface on which the conflict occurred.
+ * @param slot service index +1 on which the conflict occurred (0 indicate hostname conflict).
+ */
+static void
+mdns_probe_conflict(struct netif *netif, s8_t slot)
+{
+ /* Increase the number of conflicts occurred and check rate limiting */
+ mdns_conflict_save_time(netif);
+
+ /* Disable currently running probe / announce timer */
+ sys_untimeout(mdns_probe_and_announce, netif);
+
+ /* Inform the host on the conflict, if a callback is set */
+ if (mdns_name_result_cb != NULL) {
+ mdns_name_result_cb(netif, MDNS_PROBING_CONFLICT, slot);
+ }
+ /* TODO: rename and call restart if no mdns_name_result_cb was set? */
+}
+
+/**
+ * Loockup matching request for response MDNS packet
+ */
+#if LWIP_MDNS_SEARCH
+static struct mdns_request *
+mdns_lookup_request(struct mdns_rr_info *rr)
+{
+ int i;
+ /* search originating request */
+ for (i = 0; i < MDNS_MAX_REQUESTS; i++) {
+ if ((mdns_requests[i].result_fn != NULL) &&
+ (check_request(&mdns_requests[i], rr) != 0)) {
+ return &mdns_requests[i];
+ }
+ }
+ return NULL;
+}
+#endif
+
+/**
+ * Handle response MDNS packet:
+ * - Handle responses on probe query
+ * - Perform conflict resolution on every packet (RFC6762 section 9)
+ *
+ * @param pkt incoming packet
+ * @param netif network interface on which packet was received
+ */
+static void
+mdns_handle_response(struct mdns_packet *pkt, struct netif *netif)
+{
+ struct mdns_host* mdns = NETIF_TO_HOST(netif);
+ u16_t total_answers_left;
+#if LWIP_MDNS_SEARCH
+ struct mdns_request *req = NULL;
+ s8_t first = 1;
+#endif
+
+ /* Ignore responses with a source port different from 5353
+ * (LWIP_IANA_PORT_MDNS) -> RFC6762 section 6 */
+ if (pkt->source_port != LWIP_IANA_PORT_MDNS) {
+ return;
+ }
+
+ /* Ignore all questions */
+ while (pkt->questions_left) {
+ struct mdns_question q;
+ err_t res;
+ res = mdns_read_question(pkt, &q);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse question, skipping response packet\n"));
+ return;
+ }
+#if LWIP_MDNS_SEARCH
+ else {
+ req = mdns_lookup_request(&q.info);
+ }
+#endif
+ }
+ /* We need to check all resource record sections: answers, authoritative and additional */
+ total_answers_left = pkt->answers_left + pkt->authoritative_left + pkt->additional_left;
+ while (total_answers_left) {
+ struct mdns_answer ans;
+ err_t res;
+
+ res = mdns_read_answer(pkt, &ans, &total_answers_left);
+ if (res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Failed to parse answer, skipping response packet\n"));
+ return;
+ }
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Answer for domain "));
+ mdns_domain_debug_print(&ans.info.domain);
+ LWIP_DEBUGF(MDNS_DEBUG, (" type %d class %d\n", ans.info.type, ans.info.klass));
+
+ if (ans.info.type == DNS_RRTYPE_ANY || ans.info.klass != DNS_RRCLASS_IN) {
+ /* Skip answers for ANY type or if class != IN */
+ continue;
+ }
+
+#if LWIP_MDNS_SEARCH
+ if (req && req->only_ptr) {
+ /* Need to recheck that this answer match request that match previous answer */
+ if (memcmp (req->service.name, ans.info.domain.name, req->service.length) != 0)
+ req = NULL;
+ }
+ if (!req) {
+ /* Try hard to search matching request */
+ req = mdns_lookup_request(&ans.info);
+ }
+ if (req && req->result_fn) {
+ u16_t offset;
+ struct pbuf *p;
+ int flags = (first ? MDNS_SEARCH_RESULT_FIRST : 0) |
+ (!total_answers_left ? MDNS_SEARCH_RESULT_LAST : 0);
+ if (req->only_ptr) {
+ if (ans.info.type != DNS_RRTYPE_PTR)
+ continue; /* Ignore non matching answer type */
+ flags = MDNS_SEARCH_RESULT_FIRST | MDNS_SEARCH_RESULT_LAST;
+ }
+ p = pbuf_skip(pkt->pbuf, ans.rd_offset, &offset);
+ if (p == NULL) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Malformed response packet, aborting\n"));
+ return;
+ }
+ if (ans.info.type == DNS_RRTYPE_PTR || ans.info.type == DNS_RRTYPE_SRV) {
+ /* Those RR types have compressed domain name. Must uncompress here,
+ since cannot be done without pbuf. */
+ struct {
+ u16_t values[3]; /* SRV: Prio, Weight, Port */
+ struct mdns_domain dom; /* PTR & SRV: Domain (uncompressed) */
+ } data;
+ u16_t off = (ans.info.type == DNS_RRTYPE_SRV ? 6 : 0);
+ u16_t len = mdns_readname(pkt->pbuf, ans.rd_offset + off, &data.dom);
+ if (len == MDNS_READNAME_ERROR) {
+ /* Ensure result_fn is called anyway, just copy failed domain as is */
+ data.dom.length = ans.rd_length - off;
+ memcpy(&data.dom, (const char *)p->payload + offset + off, data.dom.length);
+ }
+ /* Adjust len/off according RR type */
+ if (ans.info.type == DNS_RRTYPE_SRV) {
+ memcpy(&data, (const char *)p->payload + offset, 6);
+ len = data.dom.length + 6;
+ off = 0;
+ } else {
+ len = data.dom.length;
+ off = 6;
+ }
+ req->result_fn(&ans, (const char *)&data + off, len, flags, req->arg);
+ } else {
+ /* Direct call result_fn with varpart pointing in pbuf payload */
+ req->result_fn(&ans, (const char *)p->payload + offset, ans.rd_length, flags, req->arg);
+ }
+ first = 0;
+ }
+#endif
+
+ /* "Conflicting Multicast DNS responses received *before* the first probe
+ * packet is sent MUST be silently ignored" so drop answer if we haven't
+ * started probing yet. */
+ if ((mdns->state == MDNS_STATE_PROBING) ||
+ (mdns->state == MDNS_STATE_ANNOUNCE_WAIT)) {
+ struct mdns_domain domain;
+ u8_t i;
+
+ res = mdns_build_host_domain(&domain, mdns);
+ if (res == ERR_OK && mdns_domain_eq(&ans.info.domain, &domain)) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe response matches host domain!\n"));
+ mdns_probe_conflict(netif, 0);
+ break;
+ }
+
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ struct mdns_service* service = mdns->services[i];
+ if (!service) {
+ continue;
+ }
+ res = mdns_build_service_domain(&domain, service, 1);
+ if ((res == ERR_OK) && mdns_domain_eq(&ans.info.domain, &domain)) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Probe response matches service domain!\n"));
+ mdns_probe_conflict(netif, i + 1);
+ break;
+ }
+ }
+ if (i < MDNS_MAX_SERVICES)
+ break;
+ }
+ /* Perform conflict resolution (RFC6762 section 9):
+ * We assume a conflict if the hostname or service name matches the answers
+ * domain. Only if the rdata matches exactly we reset our assumption to no
+ * conflict. As stated in the RFC:
+ * What may be considered inconsistent is context sensitive, except that
+ * resource records with identical rdata are never considered inconsistent,
+ * even if they originate from different hosts.
+ */
+ else if ((mdns->state == MDNS_STATE_ANNOUNCING) ||
+ (mdns->state == MDNS_STATE_COMPLETE)) {
+ struct mdns_domain domain;
+ u8_t i;
+ u8_t conflict = 0;
+
+ /* Evaluate unique hostname records -> A and AAAA */
+ res = mdns_build_host_domain(&domain, mdns);
+ if (res == ERR_OK && mdns_domain_eq(&ans.info.domain, &domain)) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response matches host domain, assuming conflict\n"));
+ /* This means a conflict has taken place, except when the packet contains
+ * exactly the same rdata. */
+ conflict = 1;
+ /* Evaluate rdata -> to see if it's a copy of our own data */
+ if (ans.info.type == DNS_RRTYPE_A) {
+#if LWIP_IPV4
+ if (ans.rd_length == sizeof(ip4_addr_t) &&
+ pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip4_addr(netif), ans.rd_length) == 0) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response equals our own IPv4 address record -> no conflict\n"));
+ conflict = 0;
+ }
+#endif
+ }
+ else if (ans.info.type == DNS_RRTYPE_AAAA) {
+#if LWIP_IPV6
+ if (ans.rd_length == sizeof(ip6_addr_p_t)) {
+ for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+ if (pbuf_memcmp(pkt->pbuf, ans.rd_offset, netif_ip6_addr(netif, i), ans.rd_length) == 0) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response equals our own iPv6 address record, num = %d -> no conflict\n",i));
+ conflict = 0;
+ }
+ }
+ }
+#endif
+ }
+ }
+ /* Evaluate unique service name records -> SRV and TXT */
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ struct mdns_service* service = mdns->services[i];
+ if (!service) {
+ continue;
+ }
+ res = mdns_build_service_domain(&domain, service, 1);
+ if ((res == ERR_OK) && mdns_domain_eq(&ans.info.domain, &domain)) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response matches service domain, assuming conflict\n"));
+ /* This means a conflict has taken place, except when the packet contains
+ * exactly the same rdata. */
+ conflict = 1;
+ /* Evaluate rdata -> to see if it's a copy of our own data */
+ if (ans.info.type == DNS_RRTYPE_SRV) {
+ /* Read and compare to with our SRV record */
+ u16_t field16, len, read_pos;
+ struct mdns_domain srv_ans, my_ans;
+ read_pos = ans.rd_offset;
+ do {
+ /* Check priority field */
+ len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+ if (len != sizeof(field16) || lwip_ntohs(field16) != SRV_PRIORITY) {
+ break;
+ }
+ read_pos += len;
+ /* Check weight field */
+ len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+ if (len != sizeof(field16) || lwip_ntohs(field16) != SRV_WEIGHT) {
+ break;
+ }
+ read_pos += len;
+ /* Check port field */
+ len = pbuf_copy_partial(pkt->pbuf, &field16, sizeof(field16), read_pos);
+ if (len != sizeof(field16) || lwip_ntohs(field16) != service->port) {
+ break;
+ }
+ read_pos += len;
+ /* Check host field */
+ len = mdns_readname(pkt->pbuf, read_pos, &srv_ans);
+ mdns_build_host_domain(&my_ans, mdns);
+ if (len == MDNS_READNAME_ERROR || !mdns_domain_eq(&srv_ans, &my_ans)) {
+ break;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response equals our own SRV record -> no conflict\n"));
+ conflict = 0;
+ } while (0);
+ } else if (ans.info.type == DNS_RRTYPE_TXT) {
+ mdns_prepare_txtdata(service);
+ if (service->txtdata.length == ans.rd_length &&
+ pbuf_memcmp(pkt->pbuf, ans.rd_offset, service->txtdata.name, ans.rd_length) == 0) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: response equals our own TXT record -> no conflict\n"));
+ conflict = 0;
+ }
+ }
+ }
+ }
+ if (conflict != 0) {
+ /* Reset host to probing to reconfirm uniqueness */
+ LWIP_DEBUGF(MDNS_DEBUG, ("mDNS: Conflict resolution -> reset to probing state\n"));
+ mdns_resp_restart(netif);
+ break;
+ }
+ }
+ }
+ /* Clear all xxx_left variables because we parsed all answers */
+ pkt->answers_left = 0;
+ pkt->authoritative_left = 0;
+ pkt->additional_left = 0;
+}
+
+/**
+ * Receive input function for MDNS packets.
+ * Handles both IPv4 and IPv6 UDP pcbs.
+ */
+static void
+mdns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+ struct dns_hdr hdr;
+ struct mdns_packet packet;
+ struct netif *recv_netif = ip_current_input_netif();
+ u16_t offset = 0;
+
+ LWIP_UNUSED_ARG(arg);
+ LWIP_UNUSED_ARG(pcb);
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Received IPv%d MDNS packet, len %d\n", IP_IS_V6(addr) ? 6 : 4, p->tot_len));
+
+ if (NETIF_TO_HOST(recv_netif) == NULL) {
+ /* From netif not configured for MDNS */
+ goto dealloc;
+ }
+
+ if (pbuf_copy_partial(p, &hdr, SIZEOF_DNS_HDR, offset) < SIZEOF_DNS_HDR) {
+ /* Too small */
+ goto dealloc;
+ }
+ offset += SIZEOF_DNS_HDR;
+
+ if (DNS_HDR_GET_OPCODE(&hdr)) {
+ /* Ignore non-standard queries in multicast packets (RFC 6762, section 18.3) */
+ goto dealloc;
+ }
+
+ memset(&packet, 0, sizeof(packet));
+ SMEMCPY(&packet.source_addr, addr, sizeof(packet.source_addr));
+ packet.source_port = port;
+ packet.pbuf = p;
+ packet.parse_offset = offset;
+ packet.tx_id = lwip_ntohs(hdr.id);
+ packet.questions = packet.questions_left = lwip_ntohs(hdr.numquestions);
+ packet.answers = packet.answers_left = lwip_ntohs(hdr.numanswers);
+ packet.authoritative = packet.authoritative_left = lwip_ntohs(hdr.numauthrr);
+ packet.additional = packet.additional_left = lwip_ntohs(hdr.numextrarr);
+
+ /* Source address check (RFC6762 section 11) -> for responses.
+ * Source address check (RFC6762 section 5.5) -> for queries.
+ * When the dest addr == multicast addr we know the packet originated on that
+ * link. If not, we need to check the source address. We only accept queries
+ * that originated on the link. Others are discarded.
+ */
+#if LWIP_IPV6
+ if (IP_IS_V6(ip_current_dest_addr())) {
+ /* instead of having one 'v6group' per netif, just compare zoneless here */
+ if (!ip_addr_zoneless_eq(ip_current_dest_addr(), &v6group)) {
+ packet.recv_unicast = 1;
+
+ if (ip6_addr_ismulticast_global(ip_2_ip6(ip_current_src_addr()))
+ || ip6_addr_isglobal(ip_2_ip6(ip_current_src_addr()))) {
+ goto dealloc;
+ }
+ }
+ }
+#endif
+#if LWIP_IPV4
+ if (!IP_IS_V6(ip_current_dest_addr())) {
+ if (!ip_addr_eq(ip_current_dest_addr(), &v4group)) {
+ packet.recv_unicast = 1;
+
+ if (!ip4_addr_net_eq(ip_2_ip4(ip_current_src_addr()),
+ netif_ip4_addr(recv_netif),
+ netif_ip4_netmask(recv_netif))){
+ goto dealloc;
+ }
+ }
+ }
+#endif
+
+ if (hdr.flags1 & DNS_FLAG1_RESPONSE) {
+ mdns_handle_response(&packet, recv_netif);
+ } else {
+ if (packet.questions && hdr.flags1 & DNS_FLAG1_TRUNC) {
+ /* this is a new truncated question */
+ struct mdns_packet *pkt = (struct mdns_packet *)LWIP_MEMPOOL_ALLOC(MDNS_PKTS);
+ if (!pkt)
+ goto dealloc; /* don't reply truncated question if alloc error */
+ SMEMCPY(pkt, &packet, sizeof(packet));
+ /* insert this question in pending list */
+ pkt->next_tc_question = pending_tc_questions;
+ pending_tc_questions = pkt;
+ /* question with truncated flags, need to wait 400-500ms before replying */
+ sys_timeout(MDNS_RESPONSE_TC_DELAY_MS, mdns_handle_tc_question, pkt);
+ /* return without dealloc pbuf */
+ return;
+ }
+ else if (!packet.questions && packet.answers && pending_tc_questions) {
+ /* this packet is a known-answer packet for a truncated question previously received */
+ struct mdns_packet *q = pending_tc_questions;
+ while (q) {
+ if ((packet.source_port == q->source_port) &&
+ ip_addr_eq(&packet.source_addr, &q->source_addr))
+ break;
+ q = q->next_tc_question;
+ }
+ if (q) {
+ /* found question from the same source */
+ struct mdns_packet *pkt = (struct mdns_packet *)LWIP_MEMPOOL_ALLOC(MDNS_PKTS);
+ if (!pkt)
+ goto dealloc; /* don't reply truncated question if alloc error */
+ SMEMCPY(pkt, &packet, sizeof(packet));
+ /* insert this known-ansert in question */
+ pkt->next_answer = q->next_answer;
+ q->next_answer = pkt;
+ /* nothing more to do */
+ return;
+ }
+ }
+ /* if previous tests fail, handle this question normally */
+ mdns_handle_question(&packet, recv_netif);
+ }
+
+dealloc:
+ pbuf_free(p);
+}
+
+#if LWIP_NETIF_EXT_STATUS_CALLBACK && MDNS_RESP_USENETIF_EXTCALLBACK
+static void
+mdns_netif_ext_status_callback(struct netif *netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t *args)
+{
+ LWIP_UNUSED_ARG(args);
+
+ /* MDNS enabled on netif? */
+ if (NETIF_TO_HOST(netif) == NULL) {
+ return;
+ }
+
+ if (reason & LWIP_NSC_STATUS_CHANGED) {
+ if (args->status_changed.state != 0) {
+ mdns_resp_restart(netif);
+ }
+ /* TODO: send goodbye message */
+ }
+ if (reason & LWIP_NSC_LINK_CHANGED) {
+ if (args->link_changed.state != 0) {
+ mdns_resp_restart(netif);
+ }
+ }
+ if (reason & (LWIP_NSC_IPV4_ADDRESS_CHANGED | LWIP_NSC_IPV4_GATEWAY_CHANGED |
+ LWIP_NSC_IPV4_NETMASK_CHANGED | LWIP_NSC_IPV4_SETTINGS_CHANGED |
+ LWIP_NSC_IPV6_SET | LWIP_NSC_IPV6_ADDR_STATE_CHANGED)) {
+ mdns_resp_restart(netif);
+ }
+}
+#endif /* LWIP_NETIF_EXT_STATUS_CALLBACK && MDNS_RESP_USENETIF_EXTCALLBACK */
+
+static void
+mdns_define_probe_rrs_to_send(struct netif *netif, struct mdns_outmsg *outmsg)
+{
+ struct mdns_host *mdns = NETIF_TO_HOST(netif);
+ int i;
+
+ memset(outmsg, 0, sizeof(struct mdns_outmsg));
+
+ /* Add unicast questions with rtype ANY for all our desired records */
+ outmsg->host_questions = QUESTION_PROBE_HOST_ANY;
+
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ struct mdns_service* service = mdns->services[i];
+ if (!service) {
+ continue;
+ }
+ outmsg->serv_questions[i] = QUESTION_PROBE_SERVICE_NAME_ANY;
+ }
+
+ /* Add answers to the questions above into the authority section for tiebreaking */
+#if LWIP_IPV4
+ if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+ outmsg->host_replies = REPLY_HOST_A;
+ }
+#endif
+#if LWIP_IPV6
+ for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) {
+ if (ip6_addr_isvalid(netif_ip6_addr_state(netif, i))) {
+ outmsg->host_replies |= REPLY_HOST_AAAA;
+ }
+ }
+#endif
+
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ struct mdns_service *serv = mdns->services[i];
+ if (serv) {
+ outmsg->serv_replies[i] = REPLY_SERVICE_SRV;
+ }
+ }
+}
+
+static err_t
+mdns_send_probe(struct netif* netif, const ip_addr_t *destination)
+{
+ struct mdns_outmsg outmsg;
+
+ mdns_define_probe_rrs_to_send(netif, &outmsg);
+
+ outmsg.tx_id = 0;
+ outmsg.dest_port = LWIP_IANA_PORT_MDNS;
+ SMEMCPY(&outmsg.dest_addr, destination, sizeof(outmsg.dest_addr));
+ return mdns_send_outpacket(&outmsg, netif);
+}
+
+/**
+ * Timer callback for probing and announcing on the network.
+ */
+static void
+mdns_probe_and_announce(void* arg)
+{
+ struct netif *netif = (struct netif *)arg;
+ struct mdns_host* mdns = NETIF_TO_HOST(netif);
+ u32_t announce_delay;
+
+
+ switch (mdns->state) {
+ case MDNS_STATE_OFF:
+ case MDNS_STATE_PROBE_WAIT:
+ case MDNS_STATE_PROBING:
+#if LWIP_IPV4
+ /*if ipv4 wait with probing until address is set*/
+ if (!ip4_addr_isany_val(*netif_ip4_addr(netif)) &&
+ mdns_send_probe(netif, &v4group) == ERR_OK)
+#endif
+ {
+#if LWIP_IPV6
+ if (mdns_send_probe(netif, &v6group) == ERR_OK)
+#endif
+ {
+ mdns->state = MDNS_STATE_PROBING;
+ mdns->sent_num++;
+ }
+ }
+
+ if (mdns->sent_num >= MDNS_PROBE_COUNT) {
+ mdns->state = MDNS_STATE_ANNOUNCE_WAIT;
+ mdns->sent_num = 0;
+ }
+
+ if (mdns->sent_num && mdns->rate_limit_activated == 1) {
+ /* delay second probe if rate limiting activated */
+ sys_timeout(MDNS_PROBE_MAX_CONFLICTS_TIMEOUT, mdns_probe_and_announce, netif);
+ }
+ else {
+ sys_timeout(MDNS_PROBE_DELAY_MS, mdns_probe_and_announce, netif);
+ }
+ break;
+ case MDNS_STATE_ANNOUNCE_WAIT:
+ case MDNS_STATE_ANNOUNCING:
+ if (mdns->sent_num == 0) {
+ /* probing was successful, announce all records */
+ mdns->state = MDNS_STATE_ANNOUNCING;
+ /* Reset rate limit max probe conflict timeout flag */
+ mdns->rate_limit_activated = 0;
+ /* Let the client know probing was successful */
+ if (mdns_name_result_cb != NULL) {
+ mdns_name_result_cb(netif, MDNS_PROBING_SUCCESSFUL, 0);
+ }
+ }
+
+ mdns_resp_announce(netif);
+ mdns->sent_num++;
+
+ if (mdns->sent_num >= MDNS_ANNOUNCE_COUNT) {
+ /* Announcing and probing complete */
+ mdns->state = MDNS_STATE_COMPLETE;
+ mdns->sent_num = 0;
+ }
+ else {
+ announce_delay = MDNS_ANNOUNCE_DELAY_MS * (1 << (mdns->sent_num - 1));
+ sys_timeout(announce_delay, mdns_probe_and_announce, netif);
+ }
+ break;
+ case MDNS_STATE_COMPLETE:
+ default:
+ /* Do nothing */
+ break;
+ }
+}
+
+/**
+ * @ingroup mdns
+ * Activate MDNS responder for a network interface.
+ * @param netif The network interface to activate.
+ * @param hostname Name to use. Queries for &lt;hostname&gt;.local will be answered
+ * with the IP addresses of the netif. The hostname will be copied, the
+ * given pointer can be on the stack.
+ * @return ERR_OK if netif was added, an err_t otherwise
+ */
+err_t
+mdns_resp_add_netif(struct netif *netif, const char *hostname)
+{
+ err_t res;
+ struct mdns_host *mdns;
+
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ERROR("mdns_resp_add_netif: netif != NULL", (netif != NULL), return ERR_VAL);
+ LWIP_ERROR("mdns_resp_add_netif: Hostname too long", (strlen(hostname) <= MDNS_LABEL_MAXLEN), return ERR_VAL);
+
+ LWIP_ASSERT("mdns_resp_add_netif: Double add", NETIF_TO_HOST(netif) == NULL);
+ mdns = (struct mdns_host *) mem_calloc(1, sizeof(struct mdns_host));
+ LWIP_ERROR("mdns_resp_add_netif: Alloc failed", (mdns != NULL), return ERR_MEM);
+
+ netif_set_client_data(netif, mdns_netif_client_id, mdns);
+
+ MEMCPY(&mdns->name, hostname, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(hostname)));
+
+ /* Init delayed message structs with address and port */
+#if LWIP_IPV4
+ mdns->ipv4.delayed_msg_multicast.dest_port = LWIP_IANA_PORT_MDNS;
+ SMEMCPY(&mdns->ipv4.delayed_msg_multicast.dest_addr, &v4group,
+ sizeof(ip_addr_t));
+#endif
+
+#if LWIP_IPV6
+ mdns->ipv6.delayed_msg_multicast.dest_port = LWIP_IANA_PORT_MDNS;
+ SMEMCPY(&mdns->ipv6.delayed_msg_multicast.dest_addr, &v6group,
+ sizeof(ip_addr_t));
+#endif
+
+ /* Join multicast groups */
+#if LWIP_IPV4
+ res = igmp_joingroup_netif(netif, ip_2_ip4(&v4group));
+ if (res != ERR_OK) {
+ goto cleanup;
+ }
+#endif
+#if LWIP_IPV6
+ res = mld6_joingroup_netif(netif, ip_2_ip6(&v6group));
+ if (res != ERR_OK) {
+ goto cleanup;
+ }
+#endif
+
+ mdns_resp_restart(netif);
+
+ return ERR_OK;
+
+cleanup:
+ mem_free(mdns);
+ netif_set_client_data(netif, mdns_netif_client_id, NULL);
+ return res;
+}
+
+/**
+ * @ingroup mdns
+ * Stop responding to MDNS queries on this interface, leave multicast groups,
+ * and free the helper structure and any of its services.
+ * @param netif The network interface to remove.
+ * @return ERR_OK if netif was removed, an err_t otherwise
+ */
+err_t
+mdns_resp_remove_netif(struct netif *netif)
+{
+ int i;
+ struct mdns_host *mdns;
+
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("mdns_resp_remove_netif: Null pointer", netif);
+ mdns = NETIF_TO_HOST(netif);
+ LWIP_ERROR("mdns_resp_remove_netif: Not an active netif", (mdns != NULL), return ERR_VAL);
+
+ sys_untimeout(mdns_probe_and_announce, netif);
+
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ struct mdns_service *service = mdns->services[i];
+ if (service) {
+ mem_free(service);
+ }
+ }
+
+ /* Leave multicast groups */
+#if LWIP_IPV4
+ igmp_leavegroup_netif(netif, ip_2_ip4(&v4group));
+#endif
+#if LWIP_IPV6
+ mld6_leavegroup_netif(netif, ip_2_ip6(&v6group));
+#endif
+
+ mem_free(mdns);
+ netif_set_client_data(netif, mdns_netif_client_id, NULL);
+ return ERR_OK;
+}
+
+/**
+ * @ingroup mdns
+ * Update MDNS hostname for a network interface.
+ * @param netif The network interface to activate.
+ * @param hostname Name to use. Queries for &lt;hostname&gt;.local will be answered
+ * with the IP addresses of the netif. The hostname will be copied, the
+ * given pointer can be on the stack.
+ * @return ERR_OK if name could be set on netif, an err_t otherwise
+ */
+err_t
+mdns_resp_rename_netif(struct netif *netif, const char *hostname)
+{
+ struct mdns_host *mdns;
+ size_t len;
+
+ LWIP_ASSERT_CORE_LOCKED();
+ len = strlen(hostname);
+ LWIP_ERROR("mdns_resp_rename_netif: netif != NULL", (netif != NULL), return ERR_VAL);
+ LWIP_ERROR("mdns_resp_rename_netif: Hostname too long", (len <= MDNS_LABEL_MAXLEN), return ERR_VAL);
+ mdns = NETIF_TO_HOST(netif);
+ LWIP_ERROR("mdns_resp_rename_netif: Not an mdns netif", (mdns != NULL), return ERR_VAL);
+
+ MEMCPY(&mdns->name, hostname, LWIP_MIN(MDNS_LABEL_MAXLEN, len));
+ mdns->name[len] = '\0'; /* null termination in case new name is shorter than previous */
+
+ mdns_resp_restart_delay(netif, MDNS_PROBE_DELAY_MS);
+
+ return ERR_OK;
+}
+
+/**
+ * @ingroup mdns
+ * Checks if an MDNS responder is active for a given network interface.
+ * @param netif The network interface to test.
+ * @return nonzero if responder active, zero otherwise.
+ */
+int
+mdns_resp_netif_active(struct netif *netif)
+{
+ return NETIF_TO_HOST(netif) != NULL;
+}
+
+/**
+ * @ingroup mdns
+ * Add a service to the selected network interface.
+ * @param netif The network interface to publish this service on
+ * @param name The name of the service
+ * @param service The service type, like "_http"
+ * @param proto The service protocol, DNSSD_PROTO_TCP for TCP ("_tcp") and DNSSD_PROTO_UDP
+ * for others ("_udp")
+ * @param port The port the service listens to
+ * @param txt_fn Callback to get TXT data. Will be called each time a TXT reply is created to
+ * allow dynamic replies.
+ * @param txt_data Userdata pointer for txt_fn
+ * @return service_id if the service was added to the netif, an err_t otherwise
+ */
+s8_t
+mdns_resp_add_service(struct netif *netif, const char *name, const char *service, enum mdns_sd_proto proto, u16_t port, service_get_txt_fn_t txt_fn, void *txt_data)
+{
+ u8_t slot;
+ struct mdns_service *srv;
+ struct mdns_host *mdns;
+
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("mdns_resp_add_service: netif != NULL", netif);
+ mdns = NETIF_TO_HOST(netif);
+ LWIP_ERROR("mdns_resp_add_service: Not an mdns netif", (mdns != NULL), return ERR_VAL);
+
+ LWIP_ERROR("mdns_resp_add_service: Name too long", (strlen(name) <= MDNS_LABEL_MAXLEN), return ERR_VAL);
+ LWIP_ERROR("mdns_resp_add_service: Service too long", (strlen(service) <= MDNS_LABEL_MAXLEN), return ERR_VAL);
+ LWIP_ERROR("mdns_resp_add_service: Bad proto (need TCP or UDP)", (proto == DNSSD_PROTO_TCP || proto == DNSSD_PROTO_UDP), return ERR_VAL);
+
+ for (slot = 0; slot < MDNS_MAX_SERVICES; slot++) {
+ if (mdns->services[slot] == NULL) {
+ break;
+ }
+ }
+ LWIP_ERROR("mdns_resp_add_service: Service list full (increase MDNS_MAX_SERVICES)", (slot < MDNS_MAX_SERVICES), return ERR_MEM);
+
+ srv = (struct mdns_service *)mem_calloc(1, sizeof(struct mdns_service));
+ LWIP_ERROR("mdns_resp_add_service: Alloc failed", (srv != NULL), return ERR_MEM);
+
+ MEMCPY(&srv->name, name, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(name)));
+ MEMCPY(&srv->service, service, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(service)));
+ srv->txt_fn = txt_fn;
+ srv->txt_userdata = txt_data;
+ srv->proto = (u16_t)proto;
+ srv->port = port;
+
+ mdns->services[slot] = srv;
+
+ mdns_resp_restart(netif);
+
+ return slot;
+}
+
+/**
+ * @ingroup mdns
+ * Delete a service on the selected network interface.
+ * @param netif The network interface on which service should be removed
+ * @param slot The service slot number returned by mdns_resp_add_service
+ * @return ERR_OK if the service was removed from the netif, an err_t otherwise
+ */
+err_t
+mdns_resp_del_service(struct netif *netif, u8_t slot)
+{
+ struct mdns_host *mdns;
+ struct mdns_service *srv;
+ LWIP_ASSERT("mdns_resp_del_service: netif != NULL", netif);
+ mdns = NETIF_TO_HOST(netif);
+ LWIP_ERROR("mdns_resp_del_service: Not an mdns netif", (mdns != NULL), return ERR_VAL);
+ LWIP_ERROR("mdns_resp_del_service: Invalid Service ID", slot < MDNS_MAX_SERVICES, return ERR_VAL);
+ LWIP_ERROR("mdns_resp_del_service: Invalid Service ID", (mdns->services[slot] != NULL), return ERR_VAL);
+
+ srv = mdns->services[slot];
+ mdns->services[slot] = NULL;
+ mem_free(srv);
+ return ERR_OK;
+}
+
+/**
+ * @ingroup mdns
+ * Update name for an MDNS service.
+ * @param netif The network interface to activate.
+ * @param slot The service slot number returned by mdns_resp_add_service
+ * @param name The new name for the service
+ * @return ERR_OK if name could be set on service, an err_t otherwise
+ */
+err_t
+mdns_resp_rename_service(struct netif *netif, u8_t slot, const char *name)
+{
+ struct mdns_service *srv;
+ struct mdns_host *mdns;
+ size_t len;
+
+ LWIP_ASSERT_CORE_LOCKED();
+ len = strlen(name);
+ LWIP_ASSERT("mdns_resp_rename_service: netif != NULL", netif);
+ mdns = NETIF_TO_HOST(netif);
+ LWIP_ERROR("mdns_resp_rename_service: Not an mdns netif", (mdns != NULL), return ERR_VAL);
+ LWIP_ERROR("mdns_resp_rename_service: Name too long", (len <= MDNS_LABEL_MAXLEN), return ERR_VAL);
+ LWIP_ERROR("mdns_resp_rename_service: Invalid Service ID", slot < MDNS_MAX_SERVICES, return ERR_VAL);
+ LWIP_ERROR("mdns_resp_rename_service: Invalid Service ID", (mdns->services[slot] != NULL), return ERR_VAL);
+
+ srv = mdns->services[slot];
+
+ MEMCPY(&srv->name, name, LWIP_MIN(MDNS_LABEL_MAXLEN, len));
+ srv->name[len] = '\0'; /* null termination in case new name is shorter than previous */
+
+ mdns_resp_restart_delay(netif, MDNS_PROBE_DELAY_MS);
+
+ return ERR_OK;
+}
+
+/**
+ * @ingroup mdns
+ * Call this function from inside the service_get_txt_fn_t callback to add text data.
+ * Buffer for TXT data is 256 bytes, and each field is prefixed with a length byte.
+ * @param service The service provided to the get_txt callback
+ * @param txt String to add to the TXT field.
+ * @param txt_len Length of string
+ * @return ERR_OK if the string was added to the reply, an err_t otherwise
+ */
+err_t
+mdns_resp_add_service_txtitem(struct mdns_service *service, const char *txt, u8_t txt_len)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("mdns_resp_add_service_txtitem: service != NULL", service);
+
+ /* Use a mdns_domain struct to store txt chunks since it is the same encoding */
+ return mdns_domain_add_label(&service->txtdata, txt, txt_len);
+}
+
+#if LWIP_MDNS_SEARCH
+/**
+ * @ingroup mdns
+ * Stop a search request.
+ * @param request_id The search request to stop
+ */
+void
+mdns_search_stop(u8_t request_id)
+{
+ struct mdns_request *req;
+ LWIP_ASSERT("mdns_search_stop: bad request_id", request_id < MDNS_MAX_REQUESTS);
+ req = &mdns_requests[request_id];
+ if (req && req->result_fn) {
+ req->result_fn = NULL;
+ }
+}
+
+/**
+ * @ingroup mdns
+ * Search a specific service on the network.
+ * @param name The name of the service
+ * @param service The service type, like "_http"
+ * @param proto The service protocol, DNSSD_PROTO_TCP for TCP ("_tcp") and DNSSD_PROTO_UDP
+ * for others ("_udp")
+ * @param netif The network interface where to send search request
+ * @param result_fn Callback to send answer received. Will be called for each answer of a
+ * response frame matching request sent.
+ * @param arg Userdata pointer for result_fn
+ * @param request_id Returned request identifier to allow stop it.
+ * @return ERR_OK if the search request was created and sent, an err_t otherwise
+ */
+err_t
+mdns_search_service(const char *name, const char *service, enum mdns_sd_proto proto,
+ struct netif *netif, search_result_fn_t result_fn, void *arg,
+ u8_t *request_id)
+{
+ u8_t slot;
+ struct mdns_request *req;
+ if (name) {
+ LWIP_ERROR("mdns_search_service: Name too long", (strlen(name) <= MDNS_LABEL_MAXLEN), return ERR_VAL);
+ }
+ LWIP_ERROR("mdns_search_service: Service too long", (strlen(service) < MDNS_DOMAIN_MAXLEN), return ERR_VAL);
+ LWIP_ERROR("mdns_search_service: Bad reqid pointer", request_id, return ERR_VAL);
+ LWIP_ERROR("mdns_search_service: Bad proto (need TCP or UDP)", (proto == DNSSD_PROTO_TCP || proto == DNSSD_PROTO_UDP), return ERR_VAL);
+ for (slot = 0; slot < MDNS_MAX_REQUESTS; slot++) {
+ if (mdns_requests[slot].result_fn == NULL) {
+ break;
+ }
+ }
+ if (slot >= MDNS_MAX_REQUESTS) {
+ /* Don't assert if no more space in mdns_request table. Just return an error. */
+ return ERR_MEM;
+ }
+
+ req = &mdns_requests[slot];
+ memset(req, 0, sizeof(struct mdns_request));
+ req->result_fn = result_fn;
+ req->arg = arg;
+ req->proto = (u16_t)proto;
+ req->qtype = DNS_RRTYPE_PTR;
+ if (proto == DNSSD_PROTO_UDP && strcmp(service, "_services._dns-sd") == 0) {
+ req->only_ptr = 1; /* don't check other answers */
+ }
+ mdns_domain_add_string(&req->service, service);
+ if (name) {
+ MEMCPY(&req->name, name, LWIP_MIN(MDNS_LABEL_MAXLEN, strlen(name)));
+ }
+ /* save request id (slot) in pointer provided by caller */
+ *request_id = slot;
+ /* now prepare a MDNS request and send it (on specified interface) */
+#if LWIP_IPV6
+ mdns_send_request(req, netif, &v6group);
+#endif
+#if LWIP_IPV4
+ mdns_send_request(req, netif, &v4group);
+#endif
+ return ERR_OK;
+}
+#endif
+
+/**
+ * @ingroup mdns
+ * Send unsolicited answer containing all our known data
+ * @param netif The network interface to send on
+ */
+void
+mdns_resp_announce(struct netif *netif)
+{
+ struct mdns_host* mdns;
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ERROR("mdns_resp_announce: netif != NULL", (netif != NULL), return);
+
+ mdns = NETIF_TO_HOST(netif);
+ if (mdns == NULL) {
+ return;
+ }
+
+ /* Do not announce if the mdns responder is off, waiting to probe, probing or
+ * waiting to announce. */
+ if (mdns->state >= MDNS_STATE_ANNOUNCING) {
+ /* Announce on IPv6 and IPv4 */
+#if LWIP_IPV6
+ mdns_announce(netif, &v6group);
+ mdns_start_multicast_timeouts_ipv6(netif);
+#endif
+#if LWIP_IPV4
+ if (!ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+ mdns_announce(netif, &v4group);
+ mdns_start_multicast_timeouts_ipv4(netif);
+ }
+#endif
+ } /* else: ip address changed while probing was ongoing? @todo reset counter to restart? */
+}
+
+/** Register a callback function that is called if probing is completed successfully
+ * or with a conflict. */
+void
+mdns_resp_register_name_result_cb(mdns_name_result_cb_t cb)
+{
+ mdns_name_result_cb = cb;
+}
+
+/**
+ * @ingroup mdns
+ * Restart mdns responder after a specified delay. Call this when cable is connected
+ * after being disconnected or administrative interface is set up after being down
+ * @param netif The network interface to send on
+ * @param delay The delay to use before sending probe
+ */
+void
+mdns_resp_restart_delay(struct netif *netif, uint32_t delay)
+{
+ struct mdns_host* mdns;
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ERROR("mdns_resp_restart: netif != NULL", (netif != NULL), return);
+
+ mdns = NETIF_TO_HOST(netif);
+ if (mdns == NULL) {
+ return;
+ }
+ /* Make sure timer is not running */
+ sys_untimeout(mdns_probe_and_announce, netif);
+
+ mdns->sent_num = 0;
+ mdns->state = MDNS_STATE_PROBE_WAIT;
+
+ /* RFC6762 section 8.1: If fifteen conflicts occur within any ten-second period,
+ * then the host MUST wait at least five seconds before each successive
+ * additional probe attempt.
+ */
+ if (mdns->rate_limit_activated == 1) {
+ sys_timeout(MDNS_PROBE_MAX_CONFLICTS_TIMEOUT, mdns_probe_and_announce, netif);
+ }
+ else {
+ /* Adjust probe delay according sent probe count. */
+ sys_timeout(delay, mdns_probe_and_announce, netif);
+ }
+}
+
+/**
+ * @ingroup mdns
+ * Restart mdns responder. Call this when cable is connected after being disconnected or
+ * administrative interface is set up after being down
+ * @param netif The network interface to send on
+ */
+void
+mdns_resp_restart(struct netif *netif)
+{
+ mdns_resp_restart_delay(netif, MDNS_INITIAL_PROBE_DELAY_MS);
+}
+
+/**
+ * @ingroup mdns
+ * Initiate MDNS responder. Will open UDP sockets on port 5353
+ */
+void
+mdns_resp_init(void)
+{
+ err_t res;
+
+ /* LWIP_ASSERT_CORE_LOCKED(); is checked by udp_new() */
+#if LWIP_MDNS_SEARCH
+ memset(mdns_requests, 0, sizeof(mdns_requests));
+#endif
+ LWIP_MEMPOOL_INIT(MDNS_PKTS);
+ mdns_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
+ LWIP_ASSERT("Failed to allocate pcb", mdns_pcb != NULL);
+#if LWIP_MULTICAST_TX_OPTIONS
+ udp_set_multicast_ttl(mdns_pcb, MDNS_IP_TTL);
+#else
+ mdns_pcb->ttl = MDNS_IP_TTL;
+#endif
+ res = udp_bind(mdns_pcb, IP_ANY_TYPE, LWIP_IANA_PORT_MDNS);
+ LWIP_UNUSED_ARG(res); /* in case of LWIP_NOASSERT */
+ LWIP_ASSERT("Failed to bind pcb", res == ERR_OK);
+ udp_recv(mdns_pcb, mdns_recv, NULL);
+
+ mdns_netif_client_id = netif_alloc_client_data_id();
+
+#if MDNS_RESP_USENETIF_EXTCALLBACK
+ /* register for netif events when started on first netif */
+ netif_add_ext_callback(&netif_callback, mdns_netif_ext_status_callback);
+#endif
+}
+
+/**
+ * @ingroup mdns
+ * Return TXT userdata of a specific service on a network interface.
+ * @param netif Network interface.
+ * @param slot Service index.
+ */
+void *mdns_get_service_txt_userdata(struct netif *netif, s8_t slot)
+{
+ struct mdns_host *mdns = NETIF_TO_HOST(netif);
+ struct mdns_service *s;
+ LWIP_ASSERT("mdns_get_service_txt_userdata: index out of range", slot < MDNS_MAX_SERVICES);
+ s = mdns->services[slot];
+ return s ? s->txt_userdata : NULL;
+}
+
+#endif /* LWIP_MDNS_RESPONDER */
diff --git a/src/apps/mdns/mdns_domain.c b/src/apps/mdns/mdns_domain.c
new file mode 100644
index 00000000000..265b5e6af02
--- /dev/null
+++ b/src/apps/mdns/mdns_domain.c
@@ -0,0 +1,635 @@
+/**
+ * @file
+ * MDNS responder implementation - domain related functionalities
+ */
+
+/*
+ * Copyright (c) 2015 Verisure Innovation AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Erik Ekman <[email protected]>
+ * Author: Jasper Verschueren <[email protected]>
+ *
+ */
+
+#include "lwip/apps/mdns.h"
+#include "lwip/apps/mdns_domain.h"
+#include "lwip/apps/mdns_priv.h"
+#include "lwip/prot/dns.h"
+
+#include <string.h>
+
+#if LWIP_IPV6
+#include "lwip/prot/ip6.h"
+#endif
+
+#if LWIP_MDNS_RESPONDER
+
+/* Stored offsets to beginning of domain names
+ * Used for compression.
+ */
+#define DOMAIN_JUMP_SIZE 2
+#define DOMAIN_JUMP 0xc000
+
+#define TOPDOMAIN_LOCAL "local"
+
+#define REVERSE_PTR_TOPDOMAIN "arpa"
+#define REVERSE_PTR_V4_DOMAIN "in-addr"
+#define REVERSE_PTR_V6_DOMAIN "ip6"
+
+static const char *dnssd_protos[] = {
+ "_udp", /* DNSSD_PROTO_UDP */
+ "_tcp", /* DNSSD_PROTO_TCP */
+};
+
+/* forward declarations (function prototypes)*/
+static err_t mdns_domain_add_label_base(struct mdns_domain *domain, u8_t len);
+static err_t mdns_domain_add_label_pbuf(struct mdns_domain *domain,
+ const struct pbuf *p, u16_t offset,
+ u8_t len);
+static u16_t mdns_readname_loop(struct pbuf *p, u16_t offset,
+ struct mdns_domain *domain, unsigned depth);
+static err_t mdns_add_dotlocal(struct mdns_domain *domain);
+
+
+static err_t
+mdns_domain_add_label_base(struct mdns_domain *domain, u8_t len)
+{
+ if (len > MDNS_LABEL_MAXLEN) {
+ return ERR_VAL;
+ }
+ if (len > 0 && (1 + len + domain->length >= MDNS_DOMAIN_MAXLEN)) {
+ return ERR_VAL;
+ }
+ /* Allow only zero marker on last byte */
+ if (len == 0 && (1 + domain->length > MDNS_DOMAIN_MAXLEN)) {
+ return ERR_VAL;
+ }
+ domain->name[domain->length] = len;
+ domain->length++;
+ return ERR_OK;
+}
+
+/**
+ * Add a label part to a domain
+ * @param domain The domain to add a label to
+ * @param label The label to add, like &lt;hostname&gt;, 'local', 'com' or ''
+ * @param len The length of the label
+ * @return ERR_OK on success, an err_t otherwise if label too long
+ */
+err_t
+mdns_domain_add_label(struct mdns_domain *domain, const char *label, u8_t len)
+{
+ err_t err = mdns_domain_add_label_base(domain, len);
+ if (err != ERR_OK) {
+ return err;
+ }
+ if (len) {
+ MEMCPY(&domain->name[domain->length], label, len);
+ domain->length += len;
+ }
+ return ERR_OK;
+}
+
+/**
+ * Add a label part to a domain (@see mdns_domain_add_label but copy directly from pbuf)
+ */
+static err_t
+mdns_domain_add_label_pbuf(struct mdns_domain *domain, const struct pbuf *p, u16_t offset, u8_t len)
+{
+ err_t err = mdns_domain_add_label_base(domain, len);
+ if (err != ERR_OK) {
+ return err;
+ }
+ if (len) {
+ if (pbuf_copy_partial(p, &domain->name[domain->length], len, offset) != len) {
+ /* take back the ++ done before */
+ domain->length--;
+ return ERR_ARG;
+ }
+ domain->length += len;
+ }
+ return ERR_OK;
+}
+
+/**
+ * Add a partial domain to a domain
+ * @param domain The domain to add a label to
+ * @param source The domain to add, like &lt;\\x09_services\\007_dns-sd\\000&gt;
+ * @return ERR_OK on success, an err_t otherwise if label too long
+ */
+err_t
+mdns_domain_add_domain(struct mdns_domain *domain, struct mdns_domain *source)
+{
+ u16_t len = source->length;
+ if (len > 0 && (1 + len + domain->length >= MDNS_DOMAIN_MAXLEN)) {
+ return ERR_VAL;
+ }
+ /* Allow only zero marker on last byte */
+ if (len == 0 && (1 + domain->length > MDNS_DOMAIN_MAXLEN)) {
+ return ERR_VAL;
+ }
+ if (len) {
+ /* Copy partial domain */
+ MEMCPY(&domain->name[domain->length], source->name, len);
+ domain->length += len;
+ } else {
+ /* Add zero marker */
+ domain->name[domain->length] = 0;
+ domain->length++;
+ }
+ return ERR_OK;
+}
+
+/**
+ * Add a string domain to a domain
+ * @param domain The domain to add a label to
+ * @param source The string to add, like &lt;_services._dns-sd&gt;
+ * @return ERR_OK on success, an err_t otherwise if label too long
+ */
+err_t
+mdns_domain_add_string(struct mdns_domain *domain, const char *source)
+{
+ u8_t *len = &domain->name[domain->length];
+ u8_t *end = &domain->name[MDNS_DOMAIN_MAXLEN];
+ u8_t *start = len + 1;
+ *len = 0;
+ while (*source && start < end) {
+ if (*source == '.') {
+ len = start++;
+ *len = 0;
+ source++;
+ } else {
+ *start++ = *source++;
+ *len = *len + 1;
+ }
+ }
+ if (start == end) {
+ return ERR_VAL;
+ }
+ domain->length = (u16_t)(start - &domain->name[0]);
+ return ERR_OK;
+}
+
+
+/**
+ * Internal readname function with max 6 levels of recursion following jumps
+ * while decompressing name
+ */
+static u16_t
+mdns_readname_loop(struct pbuf *p, u16_t offset, struct mdns_domain *domain, unsigned depth)
+{
+ u8_t c;
+
+ do {
+ if (depth > 5) {
+ /* Too many jumps */
+ return MDNS_READNAME_ERROR;
+ }
+
+ c = pbuf_get_at(p, offset);
+ offset++;
+
+ /* is this a compressed label? */
+ if ((c & 0xc0) == 0xc0) {
+ u16_t jumpaddr;
+ if (offset >= p->tot_len) {
+ /* Make sure both jump bytes fit in the packet */
+ return MDNS_READNAME_ERROR;
+ }
+ jumpaddr = (((c & 0x3f) << 8) | (pbuf_get_at(p, offset) & 0xff));
+ offset++;
+ if (jumpaddr >= SIZEOF_DNS_HDR && jumpaddr < p->tot_len) {
+ u16_t res;
+ /* Recursive call, maximum depth will be checked */
+ res = mdns_readname_loop(p, jumpaddr, domain, depth + 1);
+ /* Don't return offset since new bytes were not read (jumped to somewhere in packet) */
+ if (res == MDNS_READNAME_ERROR) {
+ return res;
+ }
+ } else {
+ return MDNS_READNAME_ERROR;
+ }
+ break;
+ }
+
+ /* normal label */
+ if (c <= MDNS_LABEL_MAXLEN) {
+ err_t res;
+
+ if (c + domain->length >= MDNS_DOMAIN_MAXLEN) {
+ return MDNS_READNAME_ERROR;
+ }
+ res = mdns_domain_add_label_pbuf(domain, p, offset, c);
+ if (res != ERR_OK) {
+ return MDNS_READNAME_ERROR;
+ }
+ offset += c;
+ } else {
+ /* bad length byte */
+ return MDNS_READNAME_ERROR;
+ }
+ } while (c != 0);
+
+ return offset;
+}
+
+/**
+ * Read possibly compressed domain name from packet buffer
+ * @param p The packet
+ * @param offset start position of domain name in packet
+ * @param domain The domain name destination
+ * @return The new offset after the domain, or MDNS_READNAME_ERROR
+ * if reading failed
+ */
+u16_t
+mdns_readname(struct pbuf *p, u16_t offset, struct mdns_domain *domain)
+{
+ memset(domain, 0, sizeof(struct mdns_domain));
+ return mdns_readname_loop(p, offset, domain, 0);
+}
+
+/**
+ * Print domain name to debug output
+ * @param domain The domain name
+ */
+void
+mdns_domain_debug_print(struct mdns_domain *domain)
+{
+ u8_t *src = domain->name;
+ u8_t i;
+
+ while (*src) {
+ u8_t label_len = *src;
+ src++;
+ for (i = 0; i < label_len; i++) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("%c", src[i]));
+ }
+ src += label_len;
+ LWIP_DEBUGF(MDNS_DEBUG, ("."));
+ }
+}
+
+/**
+ * Return 1 if contents of domains match (case-insensitive)
+ * @param a Domain name to compare 1
+ * @param b Domain name to compare 2
+ * @return 1 if domains are equal ignoring case, 0 otherwise
+ */
+int
+mdns_domain_eq(struct mdns_domain *a, struct mdns_domain *b)
+{
+ u8_t *ptra, *ptrb;
+ u8_t len;
+ int res;
+
+ if (a->length != b->length) {
+ return 0;
+ }
+
+ ptra = a->name;
+ ptrb = b->name;
+ while (*ptra && *ptrb && ptra < &a->name[a->length]) {
+ if (*ptra != *ptrb) {
+ return 0;
+ }
+ len = *ptra;
+ ptra++;
+ ptrb++;
+ res = lwip_strnicmp((char *) ptra, (char *) ptrb, len);
+ if (res != 0) {
+ return 0;
+ }
+ ptra += len;
+ ptrb += len;
+ }
+ if (*ptra != *ptrb && ptra < &a->name[a->length]) {
+ return 0;
+ }
+ return 1;
+}
+
+#if LWIP_IPV4
+/**
+ * Build domain for reverse lookup of IPv4 address
+ * like 12.0.168.192.in-addr.arpa. for 192.168.0.12
+ * @param domain Where to write the domain name
+ * @param addr Pointer to an IPv4 address to encode
+ * @return ERR_OK if domain was written, an err_t otherwise
+ */
+err_t
+mdns_build_reverse_v4_domain(struct mdns_domain *domain, const ip4_addr_t *addr)
+{
+ int i;
+ err_t res;
+ const u8_t *ptr;
+
+ LWIP_UNUSED_ARG(res);
+ if (!domain || !addr) {
+ return ERR_ARG;
+ }
+ memset(domain, 0, sizeof(struct mdns_domain));
+ ptr = (const u8_t *) addr;
+ for (i = sizeof(ip4_addr_t) - 1; i >= 0; i--) {
+ char buf[4];
+ u8_t val = ptr[i];
+
+ lwip_itoa(buf, sizeof(buf), val);
+ res = mdns_domain_add_label(domain, buf, (u8_t)strlen(buf));
+ LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res);
+ }
+ res = mdns_domain_add_label(domain, REVERSE_PTR_V4_DOMAIN, (u8_t)(sizeof(REVERSE_PTR_V4_DOMAIN) - 1));
+ LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res);
+ res = mdns_domain_add_label(domain, REVERSE_PTR_TOPDOMAIN, (u8_t)(sizeof(REVERSE_PTR_TOPDOMAIN) - 1));
+ LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res);
+ res = mdns_domain_add_label(domain, NULL, 0);
+ LWIP_ERROR("mdns_build_reverse_v4_domain: Failed to add label", (res == ERR_OK), return res);
+
+ return ERR_OK;
+}
+#endif
+
+#if LWIP_IPV6
+/**
+ * Build domain for reverse lookup of IP address
+ * like b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa. for 2001:db8::567:89ab
+ * @param domain Where to write the domain name
+ * @param addr Pointer to an IPv6 address to encode
+ * @return ERR_OK if domain was written, an err_t otherwise
+ */
+err_t
+mdns_build_reverse_v6_domain(struct mdns_domain *domain, const ip6_addr_t *addr)
+{
+ int i;
+ err_t res;
+ const u8_t *ptr;
+ LWIP_UNUSED_ARG(res);
+ if (!domain || !addr) {
+ return ERR_ARG;
+ }
+ memset(domain, 0, sizeof(struct mdns_domain));
+ ptr = (const u8_t *) addr;
+ for (i = sizeof(ip6_addr_p_t) - 1; i >= 0; i--) {
+ char buf;
+ u8_t byte = ptr[i];
+ int j;
+ for (j = 0; j < 2; j++) {
+ if ((byte & 0x0F) < 0xA) {
+ buf = '0' + (byte & 0x0F);
+ } else {
+ buf = 'a' + (byte & 0x0F) - 0xA;
+ }
+ res = mdns_domain_add_label(domain, &buf, sizeof(buf));
+ LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res);
+ byte >>= 4;
+ }
+ }
+ res = mdns_domain_add_label(domain, REVERSE_PTR_V6_DOMAIN, (u8_t)(sizeof(REVERSE_PTR_V6_DOMAIN) - 1));
+ LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res);
+ res = mdns_domain_add_label(domain, REVERSE_PTR_TOPDOMAIN, (u8_t)(sizeof(REVERSE_PTR_TOPDOMAIN) - 1));
+ LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res);
+ res = mdns_domain_add_label(domain, NULL, 0);
+ LWIP_ERROR("mdns_build_reverse_v6_domain: Failed to add label", (res == ERR_OK), return res);
+
+ return ERR_OK;
+}
+#endif
+
+/* Add .local. to domain */
+static err_t
+mdns_add_dotlocal(struct mdns_domain *domain)
+{
+ err_t res = mdns_domain_add_label(domain, TOPDOMAIN_LOCAL, (u8_t)(sizeof(TOPDOMAIN_LOCAL) - 1));
+ LWIP_UNUSED_ARG(res);
+ LWIP_ERROR("mdns_add_dotlocal: Failed to add label", (res == ERR_OK), return res);
+ return mdns_domain_add_label(domain, NULL, 0);
+}
+
+/**
+ * Build the \<hostname\>.local. domain name
+ * @param domain Where to write the domain name
+ * @param mdns TMDNS netif descriptor.
+ * @return ERR_OK if domain \<hostname\>.local. was written, an err_t otherwise
+ */
+err_t
+mdns_build_host_domain(struct mdns_domain *domain, struct mdns_host *mdns)
+{
+ err_t res;
+ LWIP_UNUSED_ARG(res);
+ memset(domain, 0, sizeof(struct mdns_domain));
+ LWIP_ERROR("mdns_build_host_domain: mdns != NULL", (mdns != NULL), return ERR_VAL);
+ res = mdns_domain_add_label(domain, mdns->name, (u8_t)strlen(mdns->name));
+ LWIP_ERROR("mdns_build_host_domain: Failed to add label", (res == ERR_OK), return res);
+ return mdns_add_dotlocal(domain);
+}
+
+/**
+ * Build the lookup-all-services special DNS-SD domain name
+ * @param domain Where to write the domain name
+ * @return ERR_OK if domain _services._dns-sd._udp.local. was written, an err_t otherwise
+ */
+err_t
+mdns_build_dnssd_domain(struct mdns_domain *domain)
+{
+ err_t res;
+ LWIP_UNUSED_ARG(res);
+ memset(domain, 0, sizeof(struct mdns_domain));
+ res = mdns_domain_add_label(domain, "_services", (u8_t)(sizeof("_services") - 1));
+ LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res);
+ res = mdns_domain_add_label(domain, "_dns-sd", (u8_t)(sizeof("_dns-sd") - 1));
+ LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res);
+ res = mdns_domain_add_label(domain, dnssd_protos[DNSSD_PROTO_UDP], (u8_t)strlen(dnssd_protos[DNSSD_PROTO_UDP]));
+ LWIP_ERROR("mdns_build_dnssd_domain: Failed to add label", (res == ERR_OK), return res);
+ return mdns_add_dotlocal(domain);
+}
+
+/**
+ * Build domain name for a service
+ * @param domain Where to write the domain name
+ * @param service The service struct, containing service name, type and protocol
+ * @param include_name Whether to include the service name in the domain
+ * @return ERR_OK if domain was written. If service name is included,
+ * \<name\>.\<type\>.\<proto\>.local. will be written, otherwise \<type\>.\<proto\>.local.
+ * An err_t is returned on error.
+ */
+err_t
+mdns_build_service_domain(struct mdns_domain *domain, struct mdns_service *service, int include_name)
+{
+ err_t res;
+ LWIP_UNUSED_ARG(res);
+ memset(domain, 0, sizeof(struct mdns_domain));
+ if (include_name) {
+ res = mdns_domain_add_label(domain, service->name, (u8_t)strlen(service->name));
+ LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res);
+ }
+ res = mdns_domain_add_label(domain, service->service, (u8_t)strlen(service->service));
+ LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res);
+ res = mdns_domain_add_label(domain, dnssd_protos[service->proto], (u8_t)strlen(dnssd_protos[service->proto]));
+ LWIP_ERROR("mdns_build_service_domain: Failed to add label", (res == ERR_OK), return res);
+ return mdns_add_dotlocal(domain);
+}
+
+#if LWIP_MDNS_SEARCH
+/**
+ * Build domain name for a request
+ * @param domain Where to write the domain name
+ * @param request The request struct, containing service name, type and protocol
+ * @param include_name Whether to include the service name in the domain
+ * @return ERR_OK if domain was written. If service name is included,
+ * \<name\>.\<type\>.\<proto\>.local. will be written, otherwise \<type\>.\<proto\>.local.
+ * An err_t is returned on error.
+ */
+err_t
+mdns_build_request_domain(struct mdns_domain *domain, struct mdns_request *request, int include_name)
+{
+ err_t res;
+ memset(domain, 0, sizeof(struct mdns_domain));
+ if (include_name) {
+ res = mdns_domain_add_label(domain, request->name, (u8_t)strlen(request->name));
+ LWIP_ERROR("mdns_build_request_domain: Failed to add label", (res == ERR_OK), return res);
+ }
+ res = mdns_domain_add_domain(domain, &request->service);
+ LWIP_ERROR("mdns_build_request_domain: Failed to add domain", (res == ERR_OK), return res);
+ res = mdns_domain_add_label(domain, dnssd_protos[request->proto], (u8_t)strlen(dnssd_protos[request->proto]));
+ LWIP_ERROR("mdns_build_request_domain: Failed to add label", (res == ERR_OK), return res);
+ return mdns_add_dotlocal(domain);
+}
+#endif
+
+/**
+ * Return bytes needed to write before jump for best result of compressing supplied domain
+ * against domain in outpacket starting at specified offset.
+ * If a match is found, offset is updated to where to jump to
+ * @param pbuf Pointer to pbuf with the partially constructed DNS packet
+ * @param offset Start position of a domain written earlier. If this location is suitable
+ * for compression, the pointer is updated to where in the domain to jump to.
+ * @param domain The domain to write
+ * @return Number of bytes to write of the new domain before writing a jump to the offset.
+ * If compression can not be done against this previous domain name, the full new
+ * domain length is returned.
+ */
+u16_t
+mdns_compress_domain(struct pbuf *pbuf, u16_t *offset, struct mdns_domain *domain)
+{
+ struct mdns_domain target;
+ u16_t target_end;
+ u8_t target_len;
+ u8_t writelen = 0;
+ u8_t *ptr;
+ if (pbuf == NULL) {
+ return domain->length;
+ }
+ target_end = mdns_readname(pbuf, *offset, &target);
+ if (target_end == MDNS_READNAME_ERROR) {
+ return domain->length;
+ }
+ target_len = (u8_t)(target_end - *offset);
+ ptr = domain->name;
+ while (writelen < domain->length) {
+ u8_t domainlen = (u8_t)(domain->length - writelen);
+ u8_t labellen;
+ if (domainlen <= target.length && domainlen > DOMAIN_JUMP_SIZE) {
+ /* Compare domains if target is long enough, and we have enough left of the domain */
+ u8_t targetpos = (u8_t)(target.length - domainlen);
+ if ((targetpos + DOMAIN_JUMP_SIZE) >= target_len) {
+ /* We are checking at or beyond a jump in the original, stop looking */
+ break;
+ }
+ if (target.length >= domainlen &&
+ memcmp(&domain->name[writelen], &target.name[targetpos], domainlen) == 0) {
+ *offset += targetpos;
+ return writelen;
+ }
+ }
+ /* Skip to next label in domain */
+ labellen = *ptr;
+ writelen += 1 + labellen;
+ ptr += 1 + labellen;
+ }
+ /* Nothing found */
+ return domain->length;
+}
+
+/**
+ * Write domain to outpacket. Compression will be attempted,
+ * unless domain->skip_compression is set.
+ * @param outpkt The outpacket to write to
+ * @param domain The domain name to write
+ * @return ERR_OK on success, an err_t otherwise
+ */
+err_t
+mdns_write_domain(struct mdns_outpacket *outpkt, struct mdns_domain *domain)
+{
+ int i;
+ err_t res;
+ u16_t writelen = domain->length;
+ u16_t jump_offset = 0;
+ u16_t jump;
+
+ if (!domain->skip_compression) {
+ for (i = 0; i < NUM_DOMAIN_OFFSETS; i++) {
+ u16_t offset = outpkt->domain_offsets[i];
+ if (offset) {
+ u16_t len = mdns_compress_domain(outpkt->pbuf, &offset, domain);
+ if (len < writelen) {
+ writelen = len;
+ jump_offset = offset;
+ }
+ }
+ }
+ }
+
+ if (writelen) {
+ /* Write uncompressed part of name */
+ res = pbuf_take_at(outpkt->pbuf, domain->name, writelen, outpkt->write_offset);
+ if (res != ERR_OK) {
+ return res;
+ }
+
+ /* Store offset of this new domain */
+ for (i = 0; i < NUM_DOMAIN_OFFSETS; i++) {
+ if (outpkt->domain_offsets[i] == 0) {
+ outpkt->domain_offsets[i] = outpkt->write_offset;
+ break;
+ }
+ }
+
+ outpkt->write_offset += writelen;
+ }
+ if (jump_offset) {
+ /* Write jump */
+ jump = lwip_htons(DOMAIN_JUMP | jump_offset);
+ res = pbuf_take_at(outpkt->pbuf, &jump, DOMAIN_JUMP_SIZE, outpkt->write_offset);
+ if (res != ERR_OK) {
+ return res;
+ }
+ outpkt->write_offset += DOMAIN_JUMP_SIZE;
+ }
+ return ERR_OK;
+}
+
+#endif /* LWIP_MDNS_RESPONDER */
diff --git a/src/apps/mdns/mdns_out.c b/src/apps/mdns/mdns_out.c
new file mode 100644
index 00000000000..5c6d26b7f0d
--- /dev/null
+++ b/src/apps/mdns/mdns_out.c
@@ -0,0 +1,1163 @@
+/**
+ * @file
+ * MDNS responder implementation - output related functionalities
+ */
+
+/*
+ * Copyright (c) 2015 Verisure Innovation AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Erik Ekman <[email protected]>
+ * Author: Jasper Verschueren <[email protected]>
+ *
+ */
+
+#include "lwip/apps/mdns_out.h"
+#include "lwip/apps/mdns_priv.h"
+#include "lwip/apps/mdns_domain.h"
+#include "lwip/prot/dns.h"
+#include "lwip/prot/iana.h"
+#include "lwip/udp.h"
+
+#include <string.h>
+
+#if LWIP_IPV6
+#include "lwip/prot/ip6.h"
+#endif
+
+
+#if LWIP_MDNS_RESPONDER
+
+/* Function prototypes */
+static void mdns_clear_outmsg(struct mdns_outmsg *outmsg);
+
+/**
+ * Call user supplied function to setup TXT data
+ * @param service The service to build TXT record for
+ */
+void
+mdns_prepare_txtdata(struct mdns_service *service)
+{
+ memset(&service->txtdata, 0, sizeof(struct mdns_domain));
+ if (service->txt_fn) {
+ service->txt_fn(service, service->txt_userdata);
+ }
+}
+
+/**
+ * Write a question to an outpacket
+ * A question contains domain, type and class. Since an answer also starts with these fields this function is also
+ * called from mdns_add_answer().
+ * @param outpkt The outpacket to write to
+ * @param domain The domain name the answer is for
+ * @param type The DNS type of the answer (like 'AAAA', 'SRV')
+ * @param klass The DNS type of the answer (like 'IN')
+ * @param unicast If highest bit in class should be set, to instruct the responder to
+ * reply with a unicast packet
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_add_question(struct mdns_outpacket *outpkt, struct mdns_domain *domain,
+ u16_t type, u16_t klass, u16_t unicast)
+{
+ u16_t question_len;
+ u16_t field16;
+ err_t res;
+
+ if (!outpkt->pbuf) {
+ /* If no pbuf is active, allocate one */
+ outpkt->pbuf = pbuf_alloc(PBUF_TRANSPORT, MDNS_OUTPUT_PACKET_SIZE, PBUF_RAM);
+ if (!outpkt->pbuf) {
+ return ERR_MEM;
+ }
+ outpkt->write_offset = SIZEOF_DNS_HDR;
+ }
+
+ /* Worst case calculation. Domain string might be compressed */
+ question_len = domain->length + sizeof(type) + sizeof(klass);
+ if (outpkt->write_offset + question_len > outpkt->pbuf->tot_len) {
+ /* No space */
+ return ERR_MEM;
+ }
+
+ /* Write name */
+ res = mdns_write_domain(outpkt, domain);
+ if (res != ERR_OK) {
+ return res;
+ }
+
+ /* Write type */
+ field16 = lwip_htons(type);
+ res = pbuf_take_at(outpkt->pbuf, &field16, sizeof(field16), outpkt->write_offset);
+ if (res != ERR_OK) {
+ return res;
+ }
+ outpkt->write_offset += sizeof(field16);
+
+ /* Write class */
+ if (unicast) {
+ klass |= 0x8000;
+ }
+ field16 = lwip_htons(klass);
+ res = pbuf_take_at(outpkt->pbuf, &field16, sizeof(field16), outpkt->write_offset);
+ if (res != ERR_OK) {
+ return res;
+ }
+ outpkt->write_offset += sizeof(field16);
+
+ return ERR_OK;
+}
+
+/**
+ * Write answer to reply packet.
+ * buf or answer_domain can be null. The rd_length written will be buf_length +
+ * size of (compressed) domain. Most uses will need either buf or answer_domain,
+ * special case is SRV that starts with 3 u16 and then a domain name.
+ * @param reply The outpacket to write to
+ * @param domain The domain name the answer is for
+ * @param type The DNS type of the answer (like 'AAAA', 'SRV')
+ * @param klass The DNS type of the answer (like 'IN')
+ * @param cache_flush If highest bit in class should be set, to instruct receiver that
+ * this reply replaces any earlier answer for this domain/type/class
+ * @param ttl Validity time in seconds to send out for IP address data in DNS replies
+ * @param buf Pointer to buffer of answer data
+ * @param buf_length Length of variable data
+ * @param answer_domain A domain to write after any buffer data as answer
+ * @return ERR_OK on success, an err_t otherwise
+ */
+static err_t
+mdns_add_answer(struct mdns_outpacket *reply, struct mdns_domain *domain,
+ u16_t type, u16_t klass, u16_t cache_flush, u32_t ttl,
+ const u8_t *buf, size_t buf_length, struct mdns_domain *answer_domain)
+{
+ u16_t answer_len;
+ u16_t field16;
+ u16_t rdlen_offset;
+ u16_t answer_offset;
+ u32_t field32;
+ err_t res;
+
+ if (!reply->pbuf) {
+ /* If no pbuf is active, allocate one */
+ reply->pbuf = pbuf_alloc(PBUF_TRANSPORT, MDNS_OUTPUT_PACKET_SIZE, PBUF_RAM);
+ if (!reply->pbuf) {
+ return ERR_MEM;
+ }
+ reply->write_offset = SIZEOF_DNS_HDR;
+ }
+
+ /* Worst case calculation. Domain strings might be compressed */
+ answer_len = domain->length + sizeof(type) + sizeof(klass) + sizeof(ttl) + sizeof(field16)/*rd_length*/;
+ if (buf) {
+ answer_len += (u16_t)buf_length;
+ }
+ if (answer_domain) {
+ answer_len += answer_domain->length;
+ }
+ if (reply->write_offset + answer_len > reply->pbuf->tot_len) {
+ /* No space */
+ return ERR_MEM;
+ }
+
+ /* Answer starts with same data as question, then more fields */
+ mdns_add_question(reply, domain, type, klass, cache_flush);
+
+ /* Write TTL */
+ field32 = lwip_htonl(ttl);
+ res = pbuf_take_at(reply->pbuf, &field32, sizeof(field32), reply->write_offset);
+ if (res != ERR_OK) {
+ return res;
+ }
+ reply->write_offset += sizeof(field32);
+
+ /* Store offsets and skip forward to the data */
+ rdlen_offset = reply->write_offset;
+ reply->write_offset += sizeof(field16);
+ answer_offset = reply->write_offset;
+
+ if (buf) {
+ /* Write static data */
+ res = pbuf_take_at(reply->pbuf, buf, (u16_t)buf_length, reply->write_offset);
+ if (res != ERR_OK) {
+ return res;
+ }
+ reply->write_offset += (u16_t)buf_length;
+ }
+
+ if (answer_domain) {
+ /* Write name answer (compressed if possible) */
+ res = mdns_write_domain(reply, answer_domain);
+ if (res != ERR_OK) {
+ return res;
+ }
+ }
+
+ /* Write rd_length after when we know the answer size */
+ field16 = lwip_htons(reply->write_offset - answer_offset);
+ res = pbuf_take_at(reply->pbuf, &field16, sizeof(field16), rdlen_offset);
+
+ return res;
+}
+
+/** Write an ANY host question to outpacket */
+static err_t
+mdns_add_any_host_question(struct mdns_outpacket *outpkt,
+ struct mdns_host *mdns,
+ u16_t request_unicast_reply)
+{
+ struct mdns_domain host;
+ mdns_build_host_domain(&host, mdns);
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Adding host question for ANY type\n"));
+ return mdns_add_question(outpkt, &host, DNS_RRTYPE_ANY, DNS_RRCLASS_IN,
+ request_unicast_reply);
+}
+
+/** Write an ANY service instance question to outpacket */
+static err_t
+mdns_add_any_service_question(struct mdns_outpacket *outpkt,
+ struct mdns_service *service,
+ u16_t request_unicast_reply)
+{
+ struct mdns_domain domain;
+ mdns_build_service_domain(&domain, service, 1);
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Adding service instance question for ANY type\n"));
+ return mdns_add_question(outpkt, &domain, DNS_RRTYPE_ANY, DNS_RRCLASS_IN,
+ request_unicast_reply);
+}
+
+#if LWIP_IPV4
+/** Write an IPv4 address (A) RR to outpacket */
+static err_t
+mdns_add_a_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
+ struct netif *netif)
+{
+ err_t res;
+ u32_t ttl = MDNS_TTL_120;
+ struct mdns_domain host;
+ mdns_build_host_domain(&host, netif_mdns_data(netif));
+ /* When answering to a legacy querier, we need to repeat the question and
+ * limit the ttl to the short legacy ttl */
+ if(msg->legacy_query) {
+ /* Repeating the question only needs to be done for the question asked
+ * (max one question), not for the additional records. */
+ if(reply->questions < 1) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n"));
+ res = mdns_add_question(reply, &host, DNS_RRTYPE_A, DNS_RRCLASS_IN, 0);
+ if (res != ERR_OK) {
+ return res;
+ }
+ reply->questions = 1;
+ }
+ /* ttl of legacy answer may not be greater then 10 seconds */
+ ttl = MDNS_TTL_10;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with A record\n"));
+ return mdns_add_answer(reply, &host, DNS_RRTYPE_A, DNS_RRCLASS_IN, msg->cache_flush,
+ ttl, (const u8_t *) netif_ip4_addr(netif),
+ sizeof(ip4_addr_t), NULL);
+}
+
+/** Write a 4.3.2.1.in-addr.arpa -> hostname.local PTR RR to outpacket */
+static err_t
+mdns_add_hostv4_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
+ struct netif *netif)
+{
+ err_t res;
+ u32_t ttl = MDNS_TTL_120;
+ struct mdns_domain host, revhost;
+ mdns_build_host_domain(&host, netif_mdns_data(netif));
+ mdns_build_reverse_v4_domain(&revhost, netif_ip4_addr(netif));
+ /* When answering to a legacy querier, we need to repeat the question and
+ * limit the ttl to the short legacy ttl */
+ if(msg->legacy_query) {
+ /* Repeating the question only needs to be done for the question asked
+ * (max one question), not for the additional records. */
+ if(reply->questions < 1) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n"));
+ res = mdns_add_question(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0);
+ if (res != ERR_OK) {
+ return res;
+ }
+ reply->questions = 1;
+ }
+ /* ttl of legacy answer may not be greater then 10 seconds */
+ ttl = MDNS_TTL_10;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with v4 PTR record\n"));
+ return mdns_add_answer(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN,
+ msg->cache_flush, ttl, NULL, 0, &host);
+}
+#endif
+
+#if LWIP_IPV6
+/** Write an IPv6 address (AAAA) RR to outpacket */
+static err_t
+mdns_add_aaaa_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
+ struct netif *netif, int addrindex)
+{
+ err_t res;
+ u32_t ttl = MDNS_TTL_120;
+ struct mdns_domain host;
+ mdns_build_host_domain(&host, netif_mdns_data(netif));
+ /* When answering to a legacy querier, we need to repeat the question and
+ * limit the ttl to the short legacy ttl */
+ if(msg->legacy_query) {
+ /* Repeating the question only needs to be done for the question asked
+ * (max one question), not for the additional records. */
+ if(reply->questions < 1) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n"));
+ res = mdns_add_question(reply, &host, DNS_RRTYPE_AAAA, DNS_RRCLASS_IN, 0);
+ if (res != ERR_OK) {
+ return res;
+ }
+ reply->questions = 1;
+ }
+ /* ttl of legacy answer may not be greater then 10 seconds */
+ ttl = MDNS_TTL_10;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with AAAA record\n"));
+ return mdns_add_answer(reply, &host, DNS_RRTYPE_AAAA, DNS_RRCLASS_IN, msg->cache_flush,
+ ttl, (const u8_t *) netif_ip6_addr(netif, addrindex),
+ sizeof(ip6_addr_p_t), NULL);
+}
+
+/** Write a x.y.z.ip6.arpa -> hostname.local PTR RR to outpacket */
+static err_t
+mdns_add_hostv6_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
+ struct netif *netif, int addrindex)
+{
+ err_t res;
+ u32_t ttl = MDNS_TTL_120;
+ struct mdns_domain host, revhost;
+ mdns_build_host_domain(&host, netif_mdns_data(netif));
+ mdns_build_reverse_v6_domain(&revhost, netif_ip6_addr(netif, addrindex));
+ /* When answering to a legacy querier, we need to repeat the question and
+ * limit the ttl to the short legacy ttl */
+ if(msg->legacy_query) {
+ /* Repeating the question only needs to be done for the question asked
+ * (max one question), not for the additional records. */
+ if(reply->questions < 1) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n"));
+ res = mdns_add_question(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0);
+ if (res != ERR_OK) {
+ return res;
+ }
+ reply->questions = 1;
+ }
+ /* ttl of legacy answer may not be greater then 10 seconds */
+ ttl = MDNS_TTL_10;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with v6 PTR record\n"));
+ return mdns_add_answer(reply, &revhost, DNS_RRTYPE_PTR, DNS_RRCLASS_IN,
+ msg->cache_flush, ttl, NULL, 0, &host);
+}
+#endif
+
+/** Write an all-services -> servicetype PTR RR to outpacket */
+static err_t
+mdns_add_servicetype_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
+ struct mdns_service *service)
+{
+ err_t res;
+ u32_t ttl = MDNS_TTL_4500;
+ struct mdns_domain service_type, service_dnssd;
+ mdns_build_service_domain(&service_type, service, 0);
+ mdns_build_dnssd_domain(&service_dnssd);
+ /* When answering to a legacy querier, we need to repeat the question and
+ * limit the ttl to the short legacy ttl */
+ if(msg->legacy_query) {
+ /* Repeating the question only needs to be done for the question asked
+ * (max one question), not for the additional records. */
+ if(reply->questions < 1) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n"));
+ res = mdns_add_question(reply, &service_dnssd, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0);
+ if (res != ERR_OK) {
+ return res;
+ }
+ reply->questions = 1;
+ }
+ /* ttl of legacy answer may not be greater then 10 seconds */
+ ttl = MDNS_TTL_10;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with service type PTR record\n"));
+ return mdns_add_answer(reply, &service_dnssd, DNS_RRTYPE_PTR, DNS_RRCLASS_IN,
+ 0, ttl, NULL, 0, &service_type);
+}
+
+/** Write a servicetype -> servicename PTR RR to outpacket */
+static err_t
+mdns_add_servicename_ptr_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
+ struct mdns_service *service)
+{
+ err_t res;
+ u32_t ttl = MDNS_TTL_120;
+ struct mdns_domain service_type, service_instance;
+ mdns_build_service_domain(&service_type, service, 0);
+ mdns_build_service_domain(&service_instance, service, 1);
+ /* When answering to a legacy querier, we need to repeat the question and
+ * limit the ttl to the short legacy ttl */
+ if(msg->legacy_query) {
+ /* Repeating the question only needs to be done for the question asked
+ * (max one question), not for the additional records. */
+ if(reply->questions < 1) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n"));
+ res = mdns_add_question(reply, &service_type, DNS_RRTYPE_PTR, DNS_RRCLASS_IN, 0);
+ if (res != ERR_OK) {
+ return res;
+ }
+ reply->questions = 1;
+ }
+ /* ttl of legacy answer may not be greater then 10 seconds */
+ ttl = MDNS_TTL_10;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with service name PTR record\n"));
+ return mdns_add_answer(reply, &service_type, DNS_RRTYPE_PTR, DNS_RRCLASS_IN,
+ 0, ttl, NULL, 0, &service_instance);
+}
+
+/** Write a SRV RR to outpacket */
+static err_t
+mdns_add_srv_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
+ struct mdns_host *mdns, struct mdns_service *service)
+{
+ err_t res;
+ u32_t ttl = MDNS_TTL_120;
+ struct mdns_domain service_instance, srvhost;
+ u16_t srvdata[3];
+ mdns_build_service_domain(&service_instance, service, 1);
+ mdns_build_host_domain(&srvhost, mdns);
+ if (msg->legacy_query) {
+ /* RFC 6762 section 18.14:
+ * In legacy unicast responses generated to answer legacy queries,
+ * name compression MUST NOT be performed on SRV records.
+ */
+ srvhost.skip_compression = 1;
+ /* When answering to a legacy querier, we need to repeat the question and
+ * limit the ttl to the short legacy ttl.
+ * Repeating the question only needs to be done for the question asked
+ * (max one question), not for the additional records. */
+ if(reply->questions < 1) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n"));
+ res = mdns_add_question(reply, &service_instance, DNS_RRTYPE_SRV, DNS_RRCLASS_IN, 0);
+ if (res != ERR_OK) {
+ return res;
+ }
+ reply->questions = 1;
+ }
+ /* ttl of legacy answer may not be greater then 10 seconds */
+ ttl = MDNS_TTL_10;
+ }
+ srvdata[0] = lwip_htons(SRV_PRIORITY);
+ srvdata[1] = lwip_htons(SRV_WEIGHT);
+ srvdata[2] = lwip_htons(service->port);
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with SRV record\n"));
+ return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_SRV, DNS_RRCLASS_IN,
+ msg->cache_flush, ttl,
+ (const u8_t *) &srvdata, sizeof(srvdata), &srvhost);
+}
+
+/** Write a TXT RR to outpacket */
+static err_t
+mdns_add_txt_answer(struct mdns_outpacket *reply, struct mdns_outmsg *msg,
+ struct mdns_service *service)
+{
+ err_t res;
+ u32_t ttl = MDNS_TTL_120;
+ struct mdns_domain service_instance;
+ mdns_build_service_domain(&service_instance, service, 1);
+ mdns_prepare_txtdata(service);
+ /* When answering to a legacy querier, we need to repeat the question and
+ * limit the ttl to the short legacy ttl */
+ if(msg->legacy_query) {
+ /* Repeating the question only needs to be done for the question asked
+ * (max one question), not for the additional records. */
+ if(reply->questions < 1) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Add question for legacy query\n"));
+ res = mdns_add_question(reply, &service_instance, DNS_RRTYPE_TXT, DNS_RRCLASS_IN, 0);
+ if (res != ERR_OK) {
+ return res;
+ }
+ reply->questions = 1;
+ }
+ /* ttl of legacy answer may not be greater then 10 seconds */
+ ttl = MDNS_TTL_10;
+ }
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Responding with TXT record\n"));
+ return mdns_add_answer(reply, &service_instance, DNS_RRTYPE_TXT, DNS_RRCLASS_IN,
+ msg->cache_flush, ttl, (u8_t *) &service->txtdata.name,
+ service->txtdata.length, NULL);
+}
+
+
+static err_t
+mdns_add_probe_questions_to_outpacket(struct mdns_outpacket *outpkt, struct mdns_outmsg *msg,
+ struct netif *netif)
+{
+ err_t res;
+ int i;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+
+ /* Write host questions (probing or legacy query) */
+ if(msg->host_questions & QUESTION_PROBE_HOST_ANY) {
+ res = mdns_add_any_host_question(outpkt, mdns, 1);
+ if (res != ERR_OK) {
+ return res;
+ }
+ outpkt->questions++;
+ }
+ /* Write service questions (probing or legacy query) */
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ struct mdns_service* service = mdns->services[i];
+ if (!service) {
+ continue;
+ }
+ if(msg->serv_questions[i] & QUESTION_PROBE_SERVICE_NAME_ANY) {
+ res = mdns_add_any_service_question(outpkt, service, 1);
+ if (res != ERR_OK) {
+ return res;
+ }
+ outpkt->questions++;
+ }
+ }
+ return ERR_OK;
+}
+
+#if LWIP_MDNS_SEARCH
+static err_t
+mdns_add_query_question_to_outpacket(struct mdns_outpacket *outpkt, struct mdns_outmsg *msg)
+{
+ err_t res;
+ /* Write legacy query question */
+ if(msg->query) {
+ struct mdns_request *req = msg->query;
+ struct mdns_domain dom;
+ /* Build question domain */
+ mdns_build_request_domain(&dom, req, req->name[0]);
+ /* Add query question to output packet */
+ res = mdns_add_question(outpkt, &dom, req->qtype, DNS_RRCLASS_IN, 0);
+ if (res != ERR_OK) {
+ return res;
+ }
+ outpkt->questions++;
+ }
+ return ERR_OK;
+}
+#endif
+
+/**
+ * Create packet with chosen answers as a reply
+ *
+ * Add all selected answers / questions
+ * Add additional answers based on the selected answers
+ */
+err_t
+mdns_create_outpacket(struct netif *netif, struct mdns_outmsg *msg,
+ struct mdns_outpacket *outpkt)
+{
+ struct mdns_host *mdns = netif_mdns_data(netif);
+ struct mdns_service *service;
+ err_t res;
+ int i;
+ u16_t answers = 0;
+
+#if LWIP_MDNS_SEARCH
+ res = mdns_add_query_question_to_outpacket(outpkt, msg);
+ if (res != ERR_OK) {
+ return res;
+ }
+#endif
+
+ res = mdns_add_probe_questions_to_outpacket(outpkt, msg, netif);
+ if (res != ERR_OK) {
+ return res;
+ }
+
+ /* Write answers to host questions */
+#if LWIP_IPV4
+ if (msg->host_replies & REPLY_HOST_A) {
+ res = mdns_add_a_answer(outpkt, msg, netif);
+ if (res != ERR_OK) {
+ return res;
+ }
+ answers++;
+ }
+ if (msg->host_replies & REPLY_HOST_PTR_V4) {
+ res = mdns_add_hostv4_ptr_answer(outpkt, msg, netif);
+ if (res != ERR_OK) {
+ return res;
+ }
+ answers++;
+ }
+#endif
+#if LWIP_IPV6
+ if (msg->host_replies & REPLY_HOST_AAAA) {
+ int addrindex;
+ for (addrindex = 0; addrindex < LWIP_IPV6_NUM_ADDRESSES; addrindex++) {
+ if (ip6_addr_isvalid(netif_ip6_addr_state(netif, addrindex))) {
+ res = mdns_add_aaaa_answer(outpkt, msg, netif, addrindex);
+ if (res != ERR_OK) {
+ return res;
+ }
+ answers++;
+ }
+ }
+ }
+ if (msg->host_replies & REPLY_HOST_PTR_V6) {
+ u8_t rev_addrs = msg->host_reverse_v6_replies;
+ int addrindex = 0;
+ while (rev_addrs) {
+ if (rev_addrs & 1) {
+ res = mdns_add_hostv6_ptr_answer(outpkt, msg, netif, addrindex);
+ if (res != ERR_OK) {
+ return res;
+ }
+ answers++;
+ }
+ addrindex++;
+ rev_addrs >>= 1;
+ }
+ }
+#endif
+
+ /* Write answers to service questions */
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ service = mdns->services[i];
+ if (!service) {
+ continue;
+ }
+
+ if (msg->serv_replies[i] & REPLY_SERVICE_TYPE_PTR) {
+ res = mdns_add_servicetype_ptr_answer(outpkt, msg, service);
+ if (res != ERR_OK) {
+ return res;
+ }
+ answers++;
+ }
+
+ if (msg->serv_replies[i] & REPLY_SERVICE_NAME_PTR) {
+ res = mdns_add_servicename_ptr_answer(outpkt, msg, service);
+ if (res != ERR_OK) {
+ return res;
+ }
+ answers++;
+ }
+
+ if (msg->serv_replies[i] & REPLY_SERVICE_SRV) {
+ res = mdns_add_srv_answer(outpkt, msg, mdns, service);
+ if (res != ERR_OK) {
+ return res;
+ }
+ answers++;
+ }
+
+ if (msg->serv_replies[i] & REPLY_SERVICE_TXT) {
+ res = mdns_add_txt_answer(outpkt, msg, service);
+ if (res != ERR_OK) {
+ return res;
+ }
+ answers++;
+ }
+ }
+
+ /* if this is a response, the data above is anwers, else this is a probe and
+ * the answers above goes into auth section */
+ if (msg->flags & DNS_FLAG1_RESPONSE) {
+ outpkt->answers += answers;
+ } else {
+ outpkt->authoritative += answers;
+ }
+
+ /* All answers written, add additional RRs */
+ for (i = 0; i < MDNS_MAX_SERVICES; i++) {
+ service = mdns->services[i];
+ if (!service) {
+ continue;
+ }
+
+ if (msg->serv_replies[i] & REPLY_SERVICE_NAME_PTR) {
+ /* Our service instance requested, include SRV & TXT
+ * if they are already not requested. */
+ if (!(msg->serv_replies[i] & REPLY_SERVICE_SRV)) {
+ res = mdns_add_srv_answer(outpkt, msg, mdns, service);
+ if (res != ERR_OK) {
+ return res;
+ }
+ outpkt->additional++;
+ }
+
+ if (!(msg->serv_replies[i] & REPLY_SERVICE_TXT)) {
+ res = mdns_add_txt_answer(outpkt, msg, service);
+ if (res != ERR_OK) {
+ return res;
+ }
+ outpkt->additional++;
+ }
+ }
+
+ /* If service instance, SRV, record or an IP address is requested,
+ * supply all addresses for the host
+ */
+ if ((msg->serv_replies[i] & (REPLY_SERVICE_NAME_PTR | REPLY_SERVICE_SRV)) ||
+ (msg->host_replies & (REPLY_HOST_A | REPLY_HOST_AAAA))) {
+#if LWIP_IPV6
+ if (!(msg->host_replies & REPLY_HOST_AAAA)) {
+ int addrindex;
+ for (addrindex = 0; addrindex < LWIP_IPV6_NUM_ADDRESSES; addrindex++) {
+ if (ip6_addr_isvalid(netif_ip6_addr_state(netif, addrindex))) {
+ res = mdns_add_aaaa_answer(outpkt, msg, netif, addrindex);
+ if (res != ERR_OK) {
+ return res;
+ }
+ outpkt->additional++;
+ }
+ }
+ }
+#endif
+#if LWIP_IPV4
+ if (!(msg->host_replies & REPLY_HOST_A) &&
+ !ip4_addr_isany_val(*netif_ip4_addr(netif))) {
+ res = mdns_add_a_answer(outpkt, msg, netif);
+ if (res != ERR_OK) {
+ return res;
+ }
+ outpkt->additional++;
+ }
+#endif
+ }
+ }
+
+ return res;
+}
+
+/**
+ * Send chosen answers as a reply
+ *
+ * Create the packet
+ * Send the packet
+ */
+err_t
+mdns_send_outpacket(struct mdns_outmsg *msg, struct netif *netif)
+{
+ struct mdns_outpacket outpkt;
+ err_t res;
+
+ memset(&outpkt, 0, sizeof(outpkt));
+
+ res = mdns_create_outpacket(netif, msg, &outpkt);
+ if (res != ERR_OK) {
+ goto cleanup;
+ }
+
+ if (outpkt.pbuf) {
+ struct dns_hdr hdr;
+
+ /* Write header */
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.flags1 = msg->flags;
+ hdr.numquestions = lwip_htons(outpkt.questions);
+ hdr.numanswers = lwip_htons(outpkt.answers);
+ hdr.numauthrr = lwip_htons(outpkt.authoritative);
+ hdr.numextrarr = lwip_htons(outpkt.additional);
+ hdr.id = lwip_htons(msg->tx_id);
+ pbuf_take(outpkt.pbuf, &hdr, sizeof(hdr));
+
+ /* Shrink packet */
+ pbuf_realloc(outpkt.pbuf, outpkt.write_offset);
+
+ /* Send created packet */
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Sending packet, len=%d\n",
+ outpkt.write_offset));
+
+ res = udp_sendto_if(get_mdns_pcb(), outpkt.pbuf, &msg->dest_addr, msg->dest_port, netif);
+ }
+
+cleanup:
+ if (outpkt.pbuf) {
+ pbuf_free(outpkt.pbuf);
+ outpkt.pbuf = NULL;
+ }
+ return res;
+}
+
+#if LWIP_IPV4
+/**
+ * Called by timeouts when timer is passed, allows multicast IPv4 traffic again.
+ *
+ * @param arg pointer to netif of timeout.
+ */
+void
+mdns_multicast_timeout_reset_ipv4(void *arg)
+{
+ struct netif *netif = (struct netif*)arg;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout finished - IPv4\n"));
+
+ mdns->ipv4.multicast_timeout = 0;
+}
+
+/**
+ * Called by timeouts when timer is passed, allows direct multicast IPv4 probe
+ * response traffic again and sends out probe response if one was pending
+ *
+ * @param arg pointer to netif of timeout.
+ */
+void
+mdns_multicast_probe_timeout_reset_ipv4(void *arg)
+{
+ struct netif *netif = (struct netif*)arg;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+ err_t res;
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast probe timeout finished - IPv4\n"));
+
+ mdns->ipv4.multicast_probe_timeout = 0;
+
+ if (mdns->ipv4.multicast_msg_waiting) {
+ res = mdns_send_outpacket(&mdns->ipv4.delayed_msg_multicast, netif);
+ if(res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Waiting probe multicast send failed - IPv4\n"));
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Waiting probe multicast send successful - IPv4\n"));
+ mdns_clear_outmsg(&mdns->ipv4.delayed_msg_multicast);
+ mdns->ipv4.multicast_msg_waiting = 0;
+ mdns_start_multicast_timeouts_ipv4(netif);
+ }
+ }
+}
+
+/**
+ * Called by timeouts when timer is passed, allows to send an answer on a QU
+ * question via multicast.
+ *
+ * @param arg pointer to netif of timeout.
+ */
+void
+mdns_multicast_timeout_25ttl_reset_ipv4(void *arg)
+{
+ struct netif *netif = (struct netif*)arg;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl finished - IPv4\n"));
+
+ mdns->ipv4.multicast_timeout_25TTL = 0;
+}
+
+/**
+ * Called by timeouts when timer is passed, sends out delayed multicast IPv4 response.
+ *
+ * @param arg pointer to netif of timeout.
+ */
+void
+mdns_send_multicast_msg_delayed_ipv4(void *arg)
+{
+ struct netif *netif = (struct netif*)arg;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+ err_t res;
+
+ res = mdns_send_outpacket(&mdns->ipv4.delayed_msg_multicast, netif);
+ if(res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send failed - IPv4\n"));
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send successful - IPv4\n"));
+ mdns_clear_outmsg(&mdns->ipv4.delayed_msg_multicast);
+ mdns->ipv4.multicast_msg_waiting = 0;
+ mdns_start_multicast_timeouts_ipv4(netif);
+ }
+}
+
+/**
+ * Called by timeouts when timer is passed, sends out delayed unicast IPv4 response.
+ *
+ * @param arg pointer to netif of timeout.
+ */
+void
+mdns_send_unicast_msg_delayed_ipv4(void *arg)
+{
+ struct netif *netif = (struct netif*)arg;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+ err_t res;
+
+ res = mdns_send_outpacket(&mdns->ipv4.delayed_msg_unicast, netif);
+ if(res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send failed - IPv4\n"));
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send successful - IPv4\n"));
+ mdns_clear_outmsg(&mdns->ipv4.delayed_msg_unicast);
+ mdns->ipv4.unicast_msg_in_use = 0;
+ }
+}
+
+/** Start all multicast timeouts for IPv4
+ * Timeouts started:
+ * - do not multicast within one second
+ * - do not multicast a probe response within 250ms
+ * - send a multicast answer on a QU question if not send recently.
+ *
+ * @param netif network interface to start timeouts on
+ */
+void
+mdns_start_multicast_timeouts_ipv4(struct netif *netif)
+{
+ struct mdns_host *mdns = netif_mdns_data(netif);
+
+ mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv4,
+ &mdns->ipv4.multicast_timeout);
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv4\n"));
+ mdns_set_timeout(netif, MDNS_MULTICAST_PROBE_TIMEOUT, mdns_multicast_probe_timeout_reset_ipv4,
+ &mdns->ipv4.multicast_probe_timeout);
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast probe timeout started - IPv4\n"));
+ mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv4,
+ &mdns->ipv4.multicast_timeout_25TTL);
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv4\n"));
+}
+#endif
+
+#if LWIP_IPV6
+/**
+ * Called by timeouts when timer is passed, allows multicast IPv6 traffic again.
+ *
+ * @param arg pointer to netif of timeout.
+ */
+void
+mdns_multicast_timeout_reset_ipv6(void *arg)
+{
+ struct netif *netif = (struct netif*)arg;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout finished - IPv6\n"));
+
+ mdns->ipv6.multicast_timeout = 0;
+}
+
+/**
+ * Called by timeouts when timer is passed, allows direct multicast IPv6 probe
+ * response traffic again and sends out probe response if one was pending
+ *
+ * @param arg pointer to netif of timeout.
+ */
+void
+mdns_multicast_probe_timeout_reset_ipv6(void *arg)
+{
+ struct netif *netif = (struct netif*)arg;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+ err_t res;
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast probe timeout finished - IPv6\n"));
+
+ mdns->ipv6.multicast_probe_timeout = 0;
+
+ if (mdns->ipv6.multicast_msg_waiting) {
+ res = mdns_send_outpacket(&mdns->ipv6.delayed_msg_multicast, netif);
+ if(res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Waiting probe multicast send failed - IPv6\n"));
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Waiting probe multicast send successful - IPv6\n"));
+ mdns_clear_outmsg(&mdns->ipv6.delayed_msg_multicast);
+ mdns->ipv6.multicast_msg_waiting = 0;
+ mdns_start_multicast_timeouts_ipv6(netif);
+ }
+ }
+}
+
+/**
+ * Called by timeouts when timer is passed, allows to send an answer on a QU
+ * question via multicast.
+ *
+ * @param arg pointer to netif of timeout.
+ */
+void
+mdns_multicast_timeout_25ttl_reset_ipv6(void *arg)
+{
+ struct netif *netif = (struct netif*)arg;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl finished - IPv6\n"));
+
+ mdns->ipv6.multicast_timeout_25TTL = 0;
+}
+
+/**
+ * Called by timeouts when timer is passed, sends out delayed multicast IPv6 response.
+ *
+ * @param arg pointer to netif of timeout.
+ */
+void
+mdns_send_multicast_msg_delayed_ipv6(void *arg)
+{
+ struct netif *netif = (struct netif*)arg;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+ err_t res;
+
+ res = mdns_send_outpacket(&mdns->ipv6.delayed_msg_multicast, netif);
+ if(res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send failed - IPv6\n"));
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed multicast send successful - IPv6\n"));
+ mdns_clear_outmsg(&mdns->ipv6.delayed_msg_multicast);
+ mdns->ipv6.multicast_msg_waiting = 0;
+ mdns_start_multicast_timeouts_ipv6(netif);
+ }
+}
+
+/**
+ * Called by timeouts when timer is passed, sends out delayed unicast IPv6 response.
+ *
+ * @param arg pointer to netif of timeout.
+ */
+void
+mdns_send_unicast_msg_delayed_ipv6(void *arg)
+{
+ struct netif *netif = (struct netif*)arg;
+ struct mdns_host *mdns = netif_mdns_data(netif);
+ err_t res;
+
+ res = mdns_send_outpacket(&mdns->ipv6.delayed_msg_unicast, netif);
+ if(res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send failed - IPv6\n"));
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Delayed unicast send successful - IPv6\n"));
+ mdns_clear_outmsg(&mdns->ipv6.delayed_msg_unicast);
+ mdns->ipv6.unicast_msg_in_use = 0;
+ }
+}
+
+/** Start all multicast timeouts for IPv6
+ * Timeouts started:
+ * - do not multicast within one second
+ * - do not multicast a probe response within 250ms
+ * - send a multicast answer on a QU question if not send recently.
+ *
+ * @param netif network interface to start timeouts on
+ */
+void
+mdns_start_multicast_timeouts_ipv6(struct netif *netif)
+{
+ struct mdns_host *mdns = netif_mdns_data(netif);
+
+ mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT, mdns_multicast_timeout_reset_ipv6,
+ &mdns->ipv6.multicast_timeout);
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout started - IPv6\n"));
+ mdns_set_timeout(netif, MDNS_MULTICAST_PROBE_TIMEOUT, mdns_multicast_probe_timeout_reset_ipv6,
+ &mdns->ipv6.multicast_probe_timeout);
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast probe timeout started - IPv6\n"));
+ mdns_set_timeout(netif, MDNS_MULTICAST_TIMEOUT_25TTL, mdns_multicast_timeout_25ttl_reset_ipv6,
+ &mdns->ipv6.multicast_timeout_25TTL);
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: multicast timeout 1/4 of ttl started - IPv6\n"));
+}
+#endif
+
+/**
+ * This function clears the output message without changing the destination
+ * address or port. This is useful for clearing the delayed msg structs without
+ * losing the set IP.
+ *
+ * @param outmsg pointer to output message to clear.
+ */
+static void
+mdns_clear_outmsg(struct mdns_outmsg *outmsg)
+{
+ int i;
+
+ outmsg->tx_id = 0;
+ outmsg->flags = 0;
+ outmsg->cache_flush = 0;
+ outmsg->unicast_reply_requested = 0;
+ outmsg->legacy_query = 0;
+ outmsg->probe_query_recv = 0;
+ outmsg->host_questions = 0;
+ outmsg->host_replies = 0;
+ outmsg->host_reverse_v6_replies = 0;
+
+ for(i = 0; i < MDNS_MAX_SERVICES; i++) {
+ outmsg->serv_questions[i] = 0;
+ outmsg->serv_replies[i] = 0;
+ }
+}
+
+/**
+ * Sets a timer that calls the handler when finished.
+ * Depending on the busy_flag the timer is restarted or started. The flag is
+ * set before return. Sys_timeout does not give us this functionality.
+ *
+ * @param netif Network interface info
+ * @param msecs Time value to set
+ * @param handler Callback function to call
+ * @param busy_flag Pointer to flag that displays if the timer is running or not.
+ */
+void
+mdns_set_timeout(struct netif *netif, u32_t msecs, sys_timeout_handler handler,
+ u8_t *busy_flag)
+{
+ if(*busy_flag) {
+ /* restart timer */
+ sys_untimeout(handler, netif);
+ sys_timeout(msecs, handler, netif);
+ }
+ else {
+ /* start timer */
+ sys_timeout(msecs, handler, netif);
+ }
+ /* Now we have a timer running */
+ *busy_flag = 1;
+}
+
+#ifdef LWIP_MDNS_SEARCH
+/**
+ * Send search request containing all our known data
+ * @param req The request to send
+ * @param netif The network interface to send on
+ * @param destination The target address to send to (usually multicast address)
+ */
+err_t
+mdns_send_request(struct mdns_request *req, struct netif *netif, const ip_addr_t *destination)
+{
+ struct mdns_outmsg outmsg;
+ err_t res;
+
+ memset(&outmsg, 0, sizeof(outmsg));
+ outmsg.query = req;
+ outmsg.dest_port = LWIP_IANA_PORT_MDNS;
+ SMEMCPY(&outmsg.dest_addr, destination, sizeof(outmsg.dest_addr));
+ res = mdns_send_outpacket(&outmsg, netif);
+ if(res != ERR_OK) {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Multicast query request send failed\n"));
+ }
+ else {
+ LWIP_DEBUGF(MDNS_DEBUG, ("MDNS: Multicast query request send successful\n"));
+ }
+ return res;
+}
+#endif
+
+#endif /* LWIP_MDNS_RESPONDER */
diff --git a/src/apps/mqtt/mqtt.c b/src/apps/mqtt/mqtt.c
new file mode 100644
index 00000000000..699061b2869
--- /dev/null
+++ b/src/apps/mqtt/mqtt.c
@@ -0,0 +1,1480 @@
+/**
+ * @file
+ * MQTT client
+ *
+ * @defgroup mqtt MQTT client
+ * @ingroup apps
+ * @verbinclude mqtt_client.txt
+ */
+
+/*
+ * Copyright (c) 2016 Erik Andersson <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack
+ *
+ * Author: Erik Andersson <[email protected]>
+ *
+ *
+ * @todo:
+ * - Handle large outgoing payloads for PUBLISH messages
+ * - Fix restriction of a single topic in each (UN)SUBSCRIBE message (protocol has support for multiple topics)
+ * - Add support for legacy MQTT protocol version
+ *
+ * Please coordinate changes and requests with Erik Andersson
+ * Erik Andersson <[email protected]>
+ *
+ */
+#include "lwip/apps/mqtt.h"
+#include "lwip/apps/mqtt_priv.h"
+#include "lwip/timeouts.h"
+#include "lwip/ip_addr.h"
+#include "lwip/mem.h"
+#include "lwip/err.h"
+#include "lwip/pbuf.h"
+#include "lwip/altcp.h"
+#include "lwip/altcp_tcp.h"
+#include "lwip/altcp_tls.h"
+#include <string.h>
+
+#if LWIP_TCP && LWIP_CALLBACK_API
+
+/**
+ * MQTT_DEBUG: Default is off.
+ */
+#if !defined MQTT_DEBUG || defined __DOXYGEN__
+#define MQTT_DEBUG LWIP_DBG_OFF
+#endif
+
+#define MQTT_DEBUG_TRACE (MQTT_DEBUG | LWIP_DBG_TRACE)
+#define MQTT_DEBUG_STATE (MQTT_DEBUG | LWIP_DBG_STATE)
+#define MQTT_DEBUG_WARN (MQTT_DEBUG | LWIP_DBG_LEVEL_WARNING)
+#define MQTT_DEBUG_WARN_STATE (MQTT_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
+#define MQTT_DEBUG_SERIOUS (MQTT_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
+
+
+
+/**
+ * MQTT client connection states
+ */
+enum {
+ TCP_DISCONNECTED,
+ TCP_CONNECTING,
+ MQTT_CONNECTING,
+ MQTT_CONNECTED
+};
+
+/**
+ * MQTT control message types
+ */
+enum mqtt_message_type {
+ MQTT_MSG_TYPE_CONNECT = 1,
+ MQTT_MSG_TYPE_CONNACK = 2,
+ MQTT_MSG_TYPE_PUBLISH = 3,
+ MQTT_MSG_TYPE_PUBACK = 4,
+ MQTT_MSG_TYPE_PUBREC = 5,
+ MQTT_MSG_TYPE_PUBREL = 6,
+ MQTT_MSG_TYPE_PUBCOMP = 7,
+ MQTT_MSG_TYPE_SUBSCRIBE = 8,
+ MQTT_MSG_TYPE_SUBACK = 9,
+ MQTT_MSG_TYPE_UNSUBSCRIBE = 10,
+ MQTT_MSG_TYPE_UNSUBACK = 11,
+ MQTT_MSG_TYPE_PINGREQ = 12,
+ MQTT_MSG_TYPE_PINGRESP = 13,
+ MQTT_MSG_TYPE_DISCONNECT = 14
+};
+
+/** Helpers to extract control packet type and qos from first byte in fixed header */
+#define MQTT_CTL_PACKET_TYPE(fixed_hdr_byte0) ((fixed_hdr_byte0 & 0xf0) >> 4)
+#define MQTT_CTL_PACKET_QOS(fixed_hdr_byte0) ((fixed_hdr_byte0 & 0x6) >> 1)
+
+/**
+ * MQTT connect flags, only used in CONNECT message
+ */
+enum mqtt_connect_flag {
+ MQTT_CONNECT_FLAG_USERNAME = 1 << 7,
+ MQTT_CONNECT_FLAG_PASSWORD = 1 << 6,
+ MQTT_CONNECT_FLAG_WILL_RETAIN = 1 << 5,
+ MQTT_CONNECT_FLAG_WILL = 1 << 2,
+ MQTT_CONNECT_FLAG_CLEAN_SESSION = 1 << 1
+};
+
+
+static void mqtt_cyclic_timer(void *arg);
+
+#if defined(LWIP_DEBUG)
+static const char *const mqtt_message_type_str[15] = {
+ "UNDEFINED",
+ "CONNECT",
+ "CONNACK",
+ "PUBLISH",
+ "PUBACK",
+ "PUBREC",
+ "PUBREL",
+ "PUBCOMP",
+ "SUBSCRIBE",
+ "SUBACK",
+ "UNSUBSCRIBE",
+ "UNSUBACK",
+ "PINGREQ",
+ "PINGRESP",
+ "DISCONNECT"
+};
+
+/**
+ * Message type value to string
+ * @param msg_type see enum mqtt_message_type
+ *
+ * @return Control message type text string
+ */
+static const char *
+mqtt_msg_type_to_str(u8_t msg_type)
+{
+ if (msg_type >= LWIP_ARRAYSIZE(mqtt_message_type_str)) {
+ msg_type = 0;
+ }
+ return mqtt_message_type_str[msg_type];
+}
+
+#endif
+
+
+/**
+ * Generate MQTT packet identifier
+ * @param client MQTT client
+ * @return New packet identifier, range 1 to 65535
+ */
+static u16_t
+msg_generate_packet_id(mqtt_client_t *client)
+{
+ client->pkt_id_seq++;
+ if (client->pkt_id_seq == 0) {
+ client->pkt_id_seq++;
+ }
+ return client->pkt_id_seq;
+}
+
+/*--------------------------------------------------------------------------------------------------------------------- */
+/* Output ring buffer */
+
+/** Add single item to ring buffer */
+static void
+mqtt_ringbuf_put(struct mqtt_ringbuf_t *rb, u8_t item)
+{
+ rb->buf[rb->put] = item;
+ rb->put++;
+ if (rb->put >= MQTT_OUTPUT_RINGBUF_SIZE) {
+ rb->put = 0;
+ }
+}
+
+/** Return pointer to ring buffer get position */
+static u8_t *
+mqtt_ringbuf_get_ptr(struct mqtt_ringbuf_t *rb)
+{
+ return &rb->buf[rb->get];
+}
+
+static void
+mqtt_ringbuf_advance_get_idx(struct mqtt_ringbuf_t *rb, u16_t len)
+{
+ LWIP_ASSERT("mqtt_ringbuf_advance_get_idx: len < MQTT_OUTPUT_RINGBUF_SIZE", len < MQTT_OUTPUT_RINGBUF_SIZE);
+
+ rb->get += len;
+ if (rb->get >= MQTT_OUTPUT_RINGBUF_SIZE) {
+ rb->get = rb->get - MQTT_OUTPUT_RINGBUF_SIZE;
+ }
+}
+
+/** Return number of bytes in ring buffer */
+static u16_t
+mqtt_ringbuf_len(struct mqtt_ringbuf_t *rb)
+{
+ u32_t len = rb->put - rb->get;
+ if (len > 0xFFFF) {
+ len += MQTT_OUTPUT_RINGBUF_SIZE;
+ }
+ return (u16_t)len;
+}
+
+/** Return number of bytes free in ring buffer */
+#define mqtt_ringbuf_free(rb) (MQTT_OUTPUT_RINGBUF_SIZE - mqtt_ringbuf_len(rb))
+
+/** Return number of bytes possible to read without wrapping around */
+#define mqtt_ringbuf_linear_read_length(rb) LWIP_MIN(mqtt_ringbuf_len(rb), (MQTT_OUTPUT_RINGBUF_SIZE - (rb)->get))
+
+/**
+ * Try send as many bytes as possible from output ring buffer
+ * @param rb Output ring buffer
+ * @param tpcb TCP connection handle
+ */
+static void
+mqtt_output_send(struct mqtt_ringbuf_t *rb, struct altcp_pcb *tpcb)
+{
+ err_t err;
+ u8_t wrap = 0;
+ u16_t ringbuf_lin_len = mqtt_ringbuf_linear_read_length(rb);
+ u16_t send_len = altcp_sndbuf(tpcb);
+ LWIP_ASSERT("mqtt_output_send: tpcb != NULL", tpcb != NULL);
+
+ if (send_len == 0 || ringbuf_lin_len == 0) {
+ return;
+ }
+
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_output_send: tcp_sndbuf: %d bytes, ringbuf_linear_available: %d, get %d, put %d\n",
+ send_len, ringbuf_lin_len, rb->get, rb->put));
+
+ if (send_len > ringbuf_lin_len) {
+ /* Space in TCP output buffer is larger than available in ring buffer linear portion */
+ send_len = ringbuf_lin_len;
+ /* Wrap around if more data in ring buffer after linear portion */
+ wrap = (mqtt_ringbuf_len(rb) > ringbuf_lin_len);
+ }
+ err = altcp_write(tpcb, mqtt_ringbuf_get_ptr(rb), send_len, TCP_WRITE_FLAG_COPY | (wrap ? TCP_WRITE_FLAG_MORE : 0));
+ if ((err == ERR_OK) && wrap) {
+ mqtt_ringbuf_advance_get_idx(rb, send_len);
+ /* Use the lesser one of ring buffer linear length and TCP send buffer size */
+ send_len = LWIP_MIN(altcp_sndbuf(tpcb), mqtt_ringbuf_linear_read_length(rb));
+ err = altcp_write(tpcb, mqtt_ringbuf_get_ptr(rb), send_len, TCP_WRITE_FLAG_COPY);
+ }
+
+ if (err == ERR_OK) {
+ mqtt_ringbuf_advance_get_idx(rb, send_len);
+ /* Flush */
+ altcp_output(tpcb);
+ } else {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_output_send: Send failed with err %d (\"%s\")\n", err, lwip_strerr(err)));
+ }
+}
+
+
+
+/*--------------------------------------------------------------------------------------------------------------------- */
+/* Request queue */
+
+/**
+ * Create request item
+ * @param r_objs Pointer to request objects
+ * @param r_objs_len Number of array entries
+ * @param pkt_id Packet identifier of request
+ * @param cb Packet callback to call when requests lifetime ends
+ * @param arg Parameter following callback
+ * @return Request or NULL if failed to create
+ */
+static struct mqtt_request_t *
+mqtt_create_request(struct mqtt_request_t *r_objs, size_t r_objs_len, u16_t pkt_id, mqtt_request_cb_t cb, void *arg)
+{
+ struct mqtt_request_t *r = NULL;
+ u8_t n;
+ LWIP_ASSERT("mqtt_create_request: r_objs != NULL", r_objs != NULL);
+ for (n = 0; n < r_objs_len; n++) {
+ /* Item point to itself if not in use */
+ if (r_objs[n].next == &r_objs[n]) {
+ r = &r_objs[n];
+ r->next = NULL;
+ r->cb = cb;
+ r->arg = arg;
+ r->pkt_id = pkt_id;
+ break;
+ }
+ }
+ return r;
+}
+
+
+/**
+ * Append request to pending request queue
+ * @param tail Pointer to request queue tail pointer
+ * @param r Request to append
+ */
+static void
+mqtt_append_request(struct mqtt_request_t **tail, struct mqtt_request_t *r)
+{
+ struct mqtt_request_t *head = NULL;
+ s16_t time_before = 0;
+ struct mqtt_request_t *iter;
+
+ LWIP_ASSERT("mqtt_append_request: tail != NULL", tail != NULL);
+
+ /* Iterate trough queue to find head, and count total timeout time */
+ for (iter = *tail; iter != NULL; iter = iter->next) {
+ time_before += iter->timeout_diff;
+ head = iter;
+ }
+
+ LWIP_ASSERT("mqtt_append_request: time_before <= MQTT_REQ_TIMEOUT", time_before <= MQTT_REQ_TIMEOUT);
+ r->timeout_diff = MQTT_REQ_TIMEOUT - time_before;
+ if (head == NULL) {
+ *tail = r;
+ } else {
+ head->next = r;
+ }
+}
+
+
+/**
+ * Delete request item
+ * @param r Request item to delete
+ */
+static void
+mqtt_delete_request(struct mqtt_request_t *r)
+{
+ if (r != NULL) {
+ r->next = r;
+ }
+}
+
+/**
+ * Remove a request item with a specific packet identifier from request queue
+ * @param tail Pointer to request queue tail pointer
+ * @param pkt_id Packet identifier of request to take
+ * @return Request item if found, NULL if not
+ */
+static struct mqtt_request_t *
+mqtt_take_request(struct mqtt_request_t **tail, u16_t pkt_id)
+{
+ struct mqtt_request_t *iter = NULL, *prev = NULL;
+ LWIP_ASSERT("mqtt_take_request: tail != NULL", tail != NULL);
+ /* Search all request for pkt_id */
+ for (iter = *tail; iter != NULL; iter = iter->next) {
+ if (iter->pkt_id == pkt_id) {
+ break;
+ }
+ prev = iter;
+ }
+
+ /* If request was found */
+ if (iter != NULL) {
+ /* unchain */
+ if (prev == NULL) {
+ *tail = iter->next;
+ } else {
+ prev->next = iter->next;
+ }
+ /* If exists, add remaining timeout time for the request to next */
+ if (iter->next != NULL) {
+ iter->next->timeout_diff += iter->timeout_diff;
+ }
+ iter->next = NULL;
+ }
+ return iter;
+}
+
+/**
+ * Handle requests timeout
+ * @param tail Pointer to request queue tail pointer
+ * @param t Time since last call in seconds
+ */
+static void
+mqtt_request_time_elapsed(struct mqtt_request_t **tail, u8_t t)
+{
+ struct mqtt_request_t *r;
+ LWIP_ASSERT("mqtt_request_time_elapsed: tail != NULL", tail != NULL);
+ r = *tail;
+ while (t > 0 && r != NULL) {
+ if (t >= r->timeout_diff) {
+ t -= (u8_t)r->timeout_diff;
+ /* Unchain */
+ *tail = r->next;
+ /* Notify upper layer about timeout */
+ if (r->cb != NULL) {
+ r->cb(r->arg, ERR_TIMEOUT);
+ }
+ mqtt_delete_request(r);
+ /* Tail might be be modified in callback, so re-read it in every iteration */
+ r = *(struct mqtt_request_t *const volatile *)tail;
+ } else {
+ r->timeout_diff -= t;
+ t = 0;
+ }
+ }
+}
+
+/**
+ * Free all request items
+ * @param tail Pointer to request queue tail pointer
+ */
+static void
+mqtt_clear_requests(struct mqtt_request_t **tail)
+{
+ struct mqtt_request_t *iter, *next;
+ LWIP_ASSERT("mqtt_clear_requests: tail != NULL", tail != NULL);
+ for (iter = *tail; iter != NULL; iter = next) {
+ next = iter->next;
+ mqtt_delete_request(iter);
+ }
+ *tail = NULL;
+}
+/**
+ * Initialize all request items
+ * @param r_objs Pointer to request objects
+ * @param r_objs_len Number of array entries
+ */
+static void
+mqtt_init_requests(struct mqtt_request_t *r_objs, size_t r_objs_len)
+{
+ u8_t n;
+ LWIP_ASSERT("mqtt_init_requests: r_objs != NULL", r_objs != NULL);
+ for (n = 0; n < r_objs_len; n++) {
+ /* Item pointing to itself indicates unused */
+ r_objs[n].next = &r_objs[n];
+ }
+}
+
+/*--------------------------------------------------------------------------------------------------------------------- */
+/* Output message build helpers */
+
+
+static void
+mqtt_output_append_u8(struct mqtt_ringbuf_t *rb, u8_t value)
+{
+ mqtt_ringbuf_put(rb, value);
+}
+
+static
+void mqtt_output_append_u16(struct mqtt_ringbuf_t *rb, u16_t value)
+{
+ mqtt_ringbuf_put(rb, value >> 8);
+ mqtt_ringbuf_put(rb, value & 0xff);
+}
+
+static void
+mqtt_output_append_buf(struct mqtt_ringbuf_t *rb, const void *data, u16_t length)
+{
+ u16_t n;
+ for (n = 0; n < length; n++) {
+ mqtt_ringbuf_put(rb, ((const u8_t *)data)[n]);
+ }
+}
+
+static void
+mqtt_output_append_string(struct mqtt_ringbuf_t *rb, const char *str, u16_t length)
+{
+ u16_t n;
+ mqtt_ringbuf_put(rb, length >> 8);
+ mqtt_ringbuf_put(rb, length & 0xff);
+ for (n = 0; n < length; n++) {
+ mqtt_ringbuf_put(rb, str[n]);
+ }
+}
+
+/**
+ * Append fixed header
+ * @param rb Output ring buffer
+ * @param msg_type see enum mqtt_message_type
+ * @param fdup MQTT DUP flag
+ * @param fqos MQTT QoS field
+ * @param fretain MQTT retain flag
+ * @param r_length Remaining length after fixed header
+ */
+
+static void
+mqtt_output_append_fixed_header(struct mqtt_ringbuf_t *rb, u8_t msg_type, u8_t fdup,
+ u8_t fqos, u8_t fretain, u16_t r_length)
+{
+ /* Start with control byte */
+ mqtt_output_append_u8(rb, (((msg_type & 0x0f) << 4) | ((fdup & 1) << 3) | ((fqos & 3) << 1) | (fretain & 1)));
+ /* Encode remaining length field */
+ do {
+ mqtt_output_append_u8(rb, (r_length & 0x7f) | (r_length >= 128 ? 0x80 : 0));
+ r_length >>= 7;
+ } while (r_length > 0);
+}
+
+
+/**
+ * Check output buffer space
+ * @param rb Output ring buffer
+ * @param r_length Remaining length after fixed header
+ * @return 1 if message will fit, 0 if not enough buffer space
+ */
+static u8_t
+mqtt_output_check_space(struct mqtt_ringbuf_t *rb, u16_t r_length)
+{
+ /* Start with length of type byte + remaining length */
+ u16_t total_len = 1 + r_length;
+
+ LWIP_ASSERT("mqtt_output_check_space: rb != NULL", rb != NULL);
+
+ /* Calculate number of required bytes to contain the remaining bytes field and add to total*/
+ do {
+ total_len++;
+ r_length >>= 7;
+ } while (r_length > 0);
+
+ return (total_len <= mqtt_ringbuf_free(rb));
+}
+
+
+/**
+ * Close connection to server
+ * @param client MQTT client
+ * @param reason Reason for disconnection
+ */
+static void
+mqtt_close(mqtt_client_t *client, mqtt_connection_status_t reason)
+{
+ LWIP_ASSERT("mqtt_close: client != NULL", client != NULL);
+
+ /* Bring down TCP connection if not already done */
+ if (client->conn != NULL) {
+ err_t res;
+ altcp_recv(client->conn, NULL);
+ altcp_err(client->conn, NULL);
+ altcp_sent(client->conn, NULL);
+ res = altcp_close(client->conn);
+ if (res != ERR_OK) {
+ altcp_abort(client->conn);
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_close: Close err=%s\n", lwip_strerr(res)));
+ }
+ client->conn = NULL;
+ }
+
+ /* Remove all pending requests */
+ mqtt_clear_requests(&client->pend_req_queue);
+ /* Stop cyclic timer */
+ sys_untimeout(mqtt_cyclic_timer, client);
+
+ /* Notify upper layer of disconnection if changed state */
+ if (client->conn_state != TCP_DISCONNECTED) {
+
+ client->conn_state = TCP_DISCONNECTED;
+ if (client->connect_cb != NULL) {
+ client->connect_cb(client, client->connect_arg, reason);
+ }
+ }
+}
+
+
+/**
+ * Interval timer, called every MQTT_CYCLIC_TIMER_INTERVAL seconds in MQTT_CONNECTING and MQTT_CONNECTED states
+ * @param arg MQTT client
+ */
+static void
+mqtt_cyclic_timer(void *arg)
+{
+ u8_t restart_timer = 1;
+ mqtt_client_t *client = (mqtt_client_t *)arg;
+ LWIP_ASSERT("mqtt_cyclic_timer: client != NULL", client != NULL);
+
+ if (client->conn_state == MQTT_CONNECTING) {
+ client->cyclic_tick++;
+ if ((client->cyclic_tick * MQTT_CYCLIC_TIMER_INTERVAL) >= MQTT_CONNECT_TIMOUT) {
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_cyclic_timer: CONNECT attempt to server timed out\n"));
+ /* Disconnect TCP */
+ mqtt_close(client, MQTT_CONNECT_TIMEOUT);
+ restart_timer = 0;
+ }
+ } else if (client->conn_state == MQTT_CONNECTED) {
+ /* Handle timeout for pending requests */
+ mqtt_request_time_elapsed(&client->pend_req_queue, MQTT_CYCLIC_TIMER_INTERVAL);
+
+ /* keep_alive > 0 means keep alive functionality shall be used */
+ if (client->keep_alive > 0) {
+
+ client->server_watchdog++;
+ /* If reception from server has been idle for 1.5*keep_alive time, server is considered unresponsive */
+ if ((client->server_watchdog * MQTT_CYCLIC_TIMER_INTERVAL) > (client->keep_alive + client->keep_alive / 2)) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_cyclic_timer: Server incoming keep-alive timeout\n"));
+ mqtt_close(client, MQTT_CONNECT_TIMEOUT);
+ restart_timer = 0;
+ }
+
+ /* If time for a keep alive message to be sent, transmission has been idle for keep_alive time */
+ client->cyclic_tick++;
+ if ((client->cyclic_tick * MQTT_CYCLIC_TIMER_INTERVAL) >= client->keep_alive) {
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_cyclic_timer: Sending keep-alive message to server\n"));
+ if (mqtt_output_check_space(&client->output, 0) != 0) {
+ mqtt_output_append_fixed_header(&client->output, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0, 0);
+ client->cyclic_tick = 0;
+ }
+ }
+ }
+ } else {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_cyclic_timer: Timer should not be running in state %d\n", client->conn_state));
+ restart_timer = 0;
+ }
+ if (restart_timer) {
+ sys_timeout(MQTT_CYCLIC_TIMER_INTERVAL * 1000, mqtt_cyclic_timer, arg);
+ }
+}
+
+
+/**
+ * Send PUBACK, PUBREC or PUBREL response message
+ * @param client MQTT client
+ * @param msg PUBACK, PUBREC or PUBREL
+ * @param pkt_id Packet identifier
+ * @param qos QoS value
+ * @return ERR_OK if successful, ERR_MEM if out of memory
+ */
+static err_t
+pub_ack_rec_rel_response(mqtt_client_t *client, u8_t msg, u16_t pkt_id, u8_t qos)
+{
+ err_t err = ERR_OK;
+ if (mqtt_output_check_space(&client->output, 2)) {
+ mqtt_output_append_fixed_header(&client->output, msg, 0, qos, 0, 2);
+ mqtt_output_append_u16(&client->output, pkt_id);
+ mqtt_output_send(&client->output, client->conn);
+ } else {
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("pub_ack_rec_rel_response: OOM creating response: %s with pkt_id: %d\n",
+ mqtt_msg_type_to_str(msg), pkt_id));
+ err = ERR_MEM;
+ }
+ return err;
+}
+
+/**
+ * Subscribe response from server
+ * @param r Matching request
+ * @param result Result code from server
+ */
+static void
+mqtt_incoming_suback(struct mqtt_request_t *r, u8_t result)
+{
+ if (r->cb != NULL) {
+ r->cb(r->arg, result < 3 ? ERR_OK : ERR_ABRT);
+ }
+}
+
+
+/**
+ * Complete MQTT message received or buffer full
+ * @param client MQTT client
+ * @param fixed_hdr_len length of fixed header
+ * @param length length received part
+ * @param remaining_length Remaining length of complete message
+ */
+static mqtt_connection_status_t
+mqtt_message_received(mqtt_client_t *client, u8_t fixed_hdr_len, u16_t length, u32_t remaining_length,
+ u8_t *var_hdr_payload)
+{
+ mqtt_connection_status_t res = MQTT_CONNECT_ACCEPTED;
+
+ /* Control packet type */
+ u8_t pkt_type = MQTT_CTL_PACKET_TYPE(client->rx_buffer[0]);
+ u16_t pkt_id = 0;
+
+ LWIP_ASSERT("fixed_hdr_len <= client->msg_idx", fixed_hdr_len <= client->msg_idx);
+ LWIP_ERROR("buffer length mismatch", fixed_hdr_len + length <= MQTT_VAR_HEADER_BUFFER_LEN,
+ return MQTT_CONNECT_DISCONNECTED);
+
+ if (pkt_type == MQTT_MSG_TYPE_CONNACK) {
+ if (client->conn_state == MQTT_CONNECTING) {
+ if (length < 2) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN,( "mqtt_message_received: Received short CONNACK message\n"));
+ goto out_disconnect;
+ }
+ /* Get result code from CONNACK */
+ res = (mqtt_connection_status_t)var_hdr_payload[1];
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_message_received: Connect response code %d\n", res));
+ if (res == MQTT_CONNECT_ACCEPTED) {
+ /* Reset cyclic_tick when changing to connected state */
+ client->cyclic_tick = 0;
+ client->conn_state = MQTT_CONNECTED;
+ /* Notify upper layer */
+ if (client->connect_cb != NULL) {
+ client->connect_cb(client, client->connect_arg, res);
+ }
+ }
+ } else {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_message_received: Received CONNACK in connected state\n"));
+ }
+ } else if (pkt_type == MQTT_MSG_TYPE_PINGRESP) {
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ( "mqtt_message_received: Received PINGRESP from server\n"));
+
+ } else if (pkt_type == MQTT_MSG_TYPE_PUBLISH) {
+ u16_t payload_offset = 0;
+ u16_t payload_length = length;
+ u8_t qos = MQTT_CTL_PACKET_QOS(client->rx_buffer[0]);
+
+ if (client->msg_idx == (u32_t)(fixed_hdr_len + length)) {
+ /* First publish message frame. Should have topic and pkt id*/
+ size_t var_hdr_payload_bufsize = sizeof(client->rx_buffer) - fixed_hdr_len;
+ u8_t *topic;
+ u16_t after_topic;
+ u8_t bkp;
+ u16_t topic_len;
+ u16_t qos_len = (qos ? 2U : 0U);
+ if (length < 2 + qos_len) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN,( "mqtt_message_received: Received short PUBLISH packet\n"));
+ goto out_disconnect;
+ }
+ topic_len = var_hdr_payload[0];
+ topic_len = (topic_len << 8) + (u16_t)(var_hdr_payload[1]);
+ if ((topic_len > length - (2 + qos_len)) ||
+ (topic_len > var_hdr_payload_bufsize - (2 + qos_len))) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN,( "mqtt_message_received: Received short PUBLISH packet (topic)\n"));
+ goto out_disconnect;
+ }
+
+ topic = var_hdr_payload + 2;
+ after_topic = 2 + topic_len;
+ /* Check buffer length, add one byte even for QoS 0 so that zero termination will fit */
+ if ((after_topic + (qos ? 2U : 1U)) > var_hdr_payload_bufsize) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_message_received: Receive buffer can not fit topic + pkt_id\n"));
+ goto out_disconnect;
+ }
+
+ /* id for QoS 1 and 2 */
+ if (qos > 0) {
+ if (length < after_topic + 2U) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN,( "mqtt_message_received: Received short PUBLISH packet (after_topic)\n"));
+ goto out_disconnect;
+ }
+ client->inpub_pkt_id = ((u16_t)var_hdr_payload[after_topic] << 8) + (u16_t)var_hdr_payload[after_topic + 1];
+ after_topic += 2;
+ } else {
+ client->inpub_pkt_id = 0;
+ }
+ /* Take backup of byte after topic */
+ bkp = topic[topic_len];
+ /* Zero terminate string */
+ topic[topic_len] = 0;
+ /* Payload data remaining in receive buffer */
+ payload_length = length - after_topic;
+ payload_offset = after_topic;
+
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_incoming_publish: Received message with QoS %d at topic: %s, payload length %"U32_F"\n",
+ qos, topic, remaining_length + payload_length));
+ if (client->pub_cb != NULL) {
+ client->pub_cb(client->inpub_arg, (const char *)topic, remaining_length + payload_length);
+ }
+ /* Restore byte after topic */
+ topic[topic_len] = bkp;
+ }
+ if (payload_length > 0 || remaining_length == 0) {
+ if (length < (size_t)(payload_offset + payload_length)) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN,( "mqtt_message_received: Received short packet (payload)\n"));
+ goto out_disconnect;
+ }
+ if (client->data_cb != NULL) {
+ client->data_cb(client->inpub_arg, var_hdr_payload + payload_offset, payload_length, remaining_length == 0 ? MQTT_DATA_FLAG_LAST : 0);
+ }
+ /* Reply if QoS > 0 */
+ if (remaining_length == 0 && qos > 0) {
+ /* Send PUBACK for QoS 1 or PUBREC for QoS 2 */
+ u8_t resp_msg = (qos == 1) ? MQTT_MSG_TYPE_PUBACK : MQTT_MSG_TYPE_PUBREC;
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_incoming_publish: Sending publish response: %s with pkt_id: %d\n",
+ mqtt_msg_type_to_str(resp_msg), client->inpub_pkt_id));
+ pub_ack_rec_rel_response(client, resp_msg, client->inpub_pkt_id, 0);
+ }
+ }
+ } else {
+ if (length < 2) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN,( "mqtt_message_received: Received short message\n"));
+ goto out_disconnect;
+ }
+ /* Get packet identifier */
+ pkt_id = (u16_t)var_hdr_payload[0] << 8;
+ pkt_id |= (u16_t)var_hdr_payload[1];
+ if (pkt_id == 0) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_message_received: Got message with illegal packet identifier: 0\n"));
+ goto out_disconnect;
+ }
+ if (pkt_type == MQTT_MSG_TYPE_PUBREC) {
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_message_received: PUBREC, sending PUBREL with pkt_id: %d\n", pkt_id));
+ pub_ack_rec_rel_response(client, MQTT_MSG_TYPE_PUBREL, pkt_id, 1);
+
+ } else if (pkt_type == MQTT_MSG_TYPE_PUBREL) {
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_message_received: PUBREL, sending PUBCOMP response with pkt_id: %d\n", pkt_id));
+ pub_ack_rec_rel_response(client, MQTT_MSG_TYPE_PUBCOMP, pkt_id, 0);
+
+ } else if (pkt_type == MQTT_MSG_TYPE_SUBACK || pkt_type == MQTT_MSG_TYPE_UNSUBACK ||
+ pkt_type == MQTT_MSG_TYPE_PUBCOMP || pkt_type == MQTT_MSG_TYPE_PUBACK) {
+ struct mqtt_request_t *r = mqtt_take_request(&client->pend_req_queue, pkt_id);
+ if (r != NULL) {
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_message_received: %s response with id %d\n", mqtt_msg_type_to_str(pkt_type), pkt_id));
+ if (pkt_type == MQTT_MSG_TYPE_SUBACK) {
+ if (length < 3) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_message_received: To small SUBACK packet\n"));
+ goto out_disconnect;
+ } else {
+ mqtt_incoming_suback(r, var_hdr_payload[2]);
+ }
+ } else if (r->cb != NULL) {
+ r->cb(r->arg, ERR_OK);
+ }
+ mqtt_delete_request(r);
+ } else {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ( "mqtt_message_received: Received %s reply, with wrong pkt_id: %d\n", mqtt_msg_type_to_str(pkt_type), pkt_id));
+ }
+ } else {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ( "mqtt_message_received: Received unknown message type: %d\n", pkt_type));
+ goto out_disconnect;
+ }
+ }
+ return res;
+out_disconnect:
+ return MQTT_CONNECT_DISCONNECTED;
+}
+
+
+/**
+ * MQTT incoming message parser
+ * @param client MQTT client
+ * @param p PBUF chain of received data
+ * @return Connection status
+ */
+static mqtt_connection_status_t
+mqtt_parse_incoming(mqtt_client_t *client, struct pbuf *p)
+{
+ u16_t in_offset = 0;
+ u32_t msg_rem_len = 0;
+ u8_t fixed_hdr_len = 0;
+ u8_t b = 0;
+
+ while (p->tot_len > in_offset) {
+ /* We ALWAYS parse the header here first. Even if the header was not
+ included in this segment, we re-parse it here by buffering it in
+ client->rx_buffer. client->msg_idx keeps track of this. */
+ if ((fixed_hdr_len < 2) || ((b & 0x80) != 0)) {
+
+ if (fixed_hdr_len < client->msg_idx) {
+ /* parse header from old pbuf (buffered in client->rx_buffer) */
+ b = client->rx_buffer[fixed_hdr_len];
+ } else {
+ /* parse header from this pbuf and save it in client->rx_buffer in case
+ it comes in segmented */
+ b = pbuf_get_at(p, in_offset++);
+ client->rx_buffer[client->msg_idx++] = b;
+ }
+ fixed_hdr_len++;
+
+ if (fixed_hdr_len >= 2) {
+ /* fixed header contains at least 2 bytes but can contain more, depending on
+ 'remaining length'. All bytes but the last of this have 0x80 set to
+ indicate more bytes are coming. */
+ msg_rem_len |= (u32_t)(b & 0x7f) << ((fixed_hdr_len - 2) * 7);
+ if ((b & 0x80) == 0) {
+ /* fixed header is done */
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_parse_incoming: Remaining length after fixed header: %"U32_F"\n", msg_rem_len));
+ if (msg_rem_len == 0) {
+ /* Complete message with no extra headers of payload received */
+ mqtt_message_received(client, fixed_hdr_len, 0, 0, NULL);
+ client->msg_idx = 0;
+ fixed_hdr_len = 0;
+ } else {
+ /* Bytes remaining in message (changes remaining length if this is
+ not the first segment of this message) */
+ msg_rem_len = (msg_rem_len + fixed_hdr_len) - client->msg_idx;
+ }
+ }
+ }
+ } else {
+ /* Fixed header has been parsed, parse variable header */
+ u16_t cpy_len, buffer_space;
+ u8_t *var_hdr_payload;
+ mqtt_connection_status_t res;
+
+ /* Allow to copy the lesser one of available length in input data or bytes remaining in message */
+ cpy_len = (u16_t)LWIP_MIN((u16_t)(p->tot_len - in_offset), msg_rem_len);
+
+ /* Limit to available space in buffer */
+ buffer_space = MQTT_VAR_HEADER_BUFFER_LEN - fixed_hdr_len;
+ if (cpy_len > buffer_space) {
+ cpy_len = buffer_space;
+ }
+ /* Adjust cpy_len to ensure zero-copy operation for remaining parts of current message */
+ if (client->msg_idx >= MQTT_VAR_HEADER_BUFFER_LEN) {
+ if (cpy_len > (p->len - in_offset))
+ cpy_len = p->len - in_offset;
+ }
+ var_hdr_payload = (u8_t*)pbuf_get_contiguous(p, client->rx_buffer + fixed_hdr_len,
+ buffer_space, cpy_len, in_offset);
+
+ /* Advance get and put indexes */
+ client->msg_idx += cpy_len;
+ in_offset += cpy_len;
+ msg_rem_len -= cpy_len;
+
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_parse_incoming: msg_idx: %"U32_F", cpy_len: %"U16_F", remaining %"U32_F"\n", client->msg_idx, cpy_len, msg_rem_len));
+ /* Whole or partial message received */
+ res = mqtt_message_received(client, fixed_hdr_len, cpy_len, msg_rem_len, var_hdr_payload);
+ if (res != MQTT_CONNECT_ACCEPTED) {
+ return res;
+ }
+ if (msg_rem_len == 0) {
+ /* Reset parser state */
+ client->msg_idx = 0;
+ /* msg_tot_len = 0; */
+ fixed_hdr_len = 0;
+ }
+ }
+ }
+ return MQTT_CONNECT_ACCEPTED;
+}
+
+
+/**
+ * TCP received callback function. @see tcp_recv_fn
+ * @param arg MQTT client
+ * @param p PBUF chain of received data
+ * @param err Passed as return value if not ERR_OK
+ * @return ERR_OK or err passed into callback
+ */
+static err_t
+mqtt_tcp_recv_cb(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err)
+{
+ mqtt_client_t *client = (mqtt_client_t *)arg;
+ LWIP_ASSERT("mqtt_tcp_recv_cb: client != NULL", client != NULL);
+ LWIP_ASSERT("mqtt_tcp_recv_cb: client->conn == pcb", client->conn == pcb);
+
+ if (p == NULL) {
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_tcp_recv_cb: Recv pbuf=NULL, remote has closed connection\n"));
+ mqtt_close(client, MQTT_CONNECT_DISCONNECTED);
+ } else {
+ mqtt_connection_status_t res;
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_tcp_recv_cb: Recv err=%d\n", err));
+ pbuf_free(p);
+ return err;
+ }
+
+ /* Tell remote that data has been received */
+ altcp_recved(pcb, p->tot_len);
+ res = mqtt_parse_incoming(client, p);
+ pbuf_free(p);
+
+ if (res != MQTT_CONNECT_ACCEPTED) {
+ mqtt_close(client, res);
+ }
+ /* If keep alive functionality is used */
+ if (client->keep_alive != 0) {
+ /* Reset server alive watchdog */
+ client->server_watchdog = 0;
+ }
+
+ }
+ return ERR_OK;
+}
+
+
+/**
+ * TCP data sent callback function. @see tcp_sent_fn
+ * @param arg MQTT client
+ * @param tpcb TCP connection handle
+ * @param len Number of bytes sent
+ * @return ERR_OK
+ */
+static err_t
+mqtt_tcp_sent_cb(void *arg, struct altcp_pcb *tpcb, u16_t len)
+{
+ mqtt_client_t *client = (mqtt_client_t *)arg;
+
+ LWIP_UNUSED_ARG(tpcb);
+ LWIP_UNUSED_ARG(len);
+
+ if (client->conn_state == MQTT_CONNECTED) {
+ struct mqtt_request_t *r;
+
+ /* Reset keep-alive send timer and server watchdog */
+ client->cyclic_tick = 0;
+ client->server_watchdog = 0;
+ /* QoS 0 publish has no response from server, so call its callbacks here */
+ while ((r = mqtt_take_request(&client->pend_req_queue, 0)) != NULL) {
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_tcp_sent_cb: Calling QoS 0 publish complete callback\n"));
+ if (r->cb != NULL) {
+ r->cb(r->arg, ERR_OK);
+ }
+ mqtt_delete_request(r);
+ }
+ /* Try send any remaining buffers from output queue */
+ mqtt_output_send(&client->output, client->conn);
+ }
+ return ERR_OK;
+}
+
+/**
+ * TCP error callback function. @see tcp_err_fn
+ * @param arg MQTT client
+ * @param err Error encountered
+ */
+static void
+mqtt_tcp_err_cb(void *arg, err_t err)
+{
+ mqtt_client_t *client = (mqtt_client_t *)arg;
+ LWIP_UNUSED_ARG(err); /* only used for debug output */
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_tcp_err_cb: TCP error callback: error %d, arg: %p\n", err, arg));
+ LWIP_ASSERT("mqtt_tcp_err_cb: client != NULL", client != NULL);
+ /* Set conn to null before calling close as pcb is already deallocated*/
+ client->conn = NULL;
+ mqtt_close(client, MQTT_CONNECT_DISCONNECTED);
+}
+
+/**
+ * TCP poll callback function. @see tcp_poll_fn
+ * @param arg MQTT client
+ * @param tpcb TCP connection handle
+ * @return err ERR_OK
+ */
+static err_t
+mqtt_tcp_poll_cb(void *arg, struct altcp_pcb *tpcb)
+{
+ mqtt_client_t *client = (mqtt_client_t *)arg;
+ if (client->conn_state == MQTT_CONNECTED) {
+ /* Try send any remaining buffers from output queue */
+ mqtt_output_send(&client->output, tpcb);
+ }
+ return ERR_OK;
+}
+
+/**
+ * TCP connect callback function. @see tcp_connected_fn
+ * @param arg MQTT client
+ * @param err Always ERR_OK, mqtt_tcp_err_cb is called in case of error
+ * @return ERR_OK
+ */
+static err_t
+mqtt_tcp_connect_cb(void *arg, struct altcp_pcb *tpcb, err_t err)
+{
+ mqtt_client_t *client = (mqtt_client_t *)arg;
+
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_tcp_connect_cb: TCP connect error %d\n", err));
+ return err;
+ }
+
+ /* Initiate receiver state */
+ client->msg_idx = 0;
+
+ /* Setup TCP callbacks */
+ altcp_recv(tpcb, mqtt_tcp_recv_cb);
+ altcp_sent(tpcb, mqtt_tcp_sent_cb);
+ altcp_poll(tpcb, mqtt_tcp_poll_cb, 2);
+
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_tcp_connect_cb: TCP connection established to server\n"));
+ /* Enter MQTT connect state */
+ client->conn_state = MQTT_CONNECTING;
+
+ /* Start cyclic timer */
+ sys_timeout(MQTT_CYCLIC_TIMER_INTERVAL * 1000, mqtt_cyclic_timer, client);
+ client->cyclic_tick = 0;
+
+ /* Start transmission from output queue, connect message is the first one out*/
+ mqtt_output_send(&client->output, client->conn);
+
+ return ERR_OK;
+}
+
+
+
+/*---------------------------------------------------------------------------------------------------- */
+/* Public API */
+
+
+/**
+ * @ingroup mqtt
+ * MQTT publish function.
+ * @param client MQTT client
+ * @param topic Publish topic string
+ * @param payload Data to publish (NULL is allowed)
+ * @param payload_length Length of payload (0 is allowed)
+ * @param qos Quality of service, 0 1 or 2
+ * @param retain MQTT retain flag
+ * @param cb Callback to call when publish is complete or has timed out
+ * @param arg User supplied argument to publish callback
+ * @return ERR_OK if successful
+ * ERR_CONN if client is disconnected
+ * ERR_MEM if short on memory
+ */
+err_t
+mqtt_publish(mqtt_client_t *client, const char *topic, const void *payload, u16_t payload_length, u8_t qos, u8_t retain,
+ mqtt_request_cb_t cb, void *arg)
+{
+ struct mqtt_request_t *r;
+ u16_t pkt_id;
+ size_t topic_strlen;
+ size_t total_len;
+ u16_t topic_len;
+ u16_t remaining_length;
+
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("mqtt_publish: client != NULL", client);
+ LWIP_ASSERT("mqtt_publish: topic != NULL", topic);
+ LWIP_ERROR("mqtt_publish: TCP disconnected", (client->conn_state != TCP_DISCONNECTED), return ERR_CONN);
+
+ topic_strlen = strlen(topic);
+ LWIP_ERROR("mqtt_publish: topic length overflow", (topic_strlen <= (0xFFFF - 2)), return ERR_ARG);
+ topic_len = (u16_t)topic_strlen;
+ total_len = 2 + topic_len + payload_length;
+
+ if (qos > 0) {
+ total_len += 2;
+ /* Generate pkt_id id for QoS1 and 2 */
+ pkt_id = msg_generate_packet_id(client);
+ } else {
+ /* Use reserved value pkt_id 0 for QoS 0 in request handle */
+ pkt_id = 0;
+ }
+ LWIP_ERROR("mqtt_publish: total length overflow", (total_len <= 0xFFFF), return ERR_ARG);
+ remaining_length = (u16_t)total_len;
+
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_publish: Publish with payload length %d to topic \"%s\"\n", payload_length, topic));
+
+ r = mqtt_create_request(client->req_list, LWIP_ARRAYSIZE(client->req_list), pkt_id, cb, arg);
+ if (r == NULL) {
+ return ERR_MEM;
+ }
+
+ if (mqtt_output_check_space(&client->output, remaining_length) == 0) {
+ mqtt_delete_request(r);
+ return ERR_MEM;
+ }
+ /* Append fixed header */
+ mqtt_output_append_fixed_header(&client->output, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain, remaining_length);
+
+ /* Append Topic */
+ mqtt_output_append_string(&client->output, topic, topic_len);
+
+ /* Append packet if for QoS 1 and 2*/
+ if (qos > 0) {
+ mqtt_output_append_u16(&client->output, pkt_id);
+ }
+
+ /* Append optional publish payload */
+ if ((payload != NULL) && (payload_length > 0)) {
+ mqtt_output_append_buf(&client->output, payload, payload_length);
+ }
+
+ mqtt_append_request(&client->pend_req_queue, r);
+ mqtt_output_send(&client->output, client->conn);
+ return ERR_OK;
+}
+
+
+/**
+ * @ingroup mqtt
+ * MQTT subscribe/unsubscribe function.
+ * @param client MQTT client
+ * @param topic topic to subscribe to
+ * @param qos Quality of service, 0 1 or 2 (only used for subscribe)
+ * @param cb Callback to call when subscribe/unsubscribe response is received
+ * @param arg User supplied argument to publish callback
+ * @param sub 1 for subscribe, 0 for unsubscribe
+ * @return ERR_OK if successful, @see err_t enum for other results
+ */
+err_t
+mqtt_sub_unsub(mqtt_client_t *client, const char *topic, u8_t qos, mqtt_request_cb_t cb, void *arg, u8_t sub)
+{
+ size_t topic_strlen;
+ size_t total_len;
+ u16_t topic_len;
+ u16_t remaining_length;
+ u16_t pkt_id;
+ struct mqtt_request_t *r;
+
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("mqtt_sub_unsub: client != NULL", client);
+ LWIP_ASSERT("mqtt_sub_unsub: topic != NULL", topic);
+
+ topic_strlen = strlen(topic);
+ LWIP_ERROR("mqtt_sub_unsub: topic length overflow", (topic_strlen <= (0xFFFF - 2)), return ERR_ARG);
+ topic_len = (u16_t)topic_strlen;
+ /* Topic string, pkt_id, qos for subscribe */
+ total_len = topic_len + 2 + 2 + (sub != 0);
+ LWIP_ERROR("mqtt_sub_unsub: total length overflow", (total_len <= 0xFFFF), return ERR_ARG);
+ remaining_length = (u16_t)total_len;
+
+ LWIP_ASSERT("mqtt_sub_unsub: qos < 3", qos < 3);
+ if (client->conn_state == TCP_DISCONNECTED) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_sub_unsub: Can not (un)subscribe in disconnected state\n"));
+ return ERR_CONN;
+ }
+
+ pkt_id = msg_generate_packet_id(client);
+ r = mqtt_create_request(client->req_list, LWIP_ARRAYSIZE(client->req_list), pkt_id, cb, arg);
+ if (r == NULL) {
+ return ERR_MEM;
+ }
+
+ if (mqtt_output_check_space(&client->output, remaining_length) == 0) {
+ mqtt_delete_request(r);
+ return ERR_MEM;
+ }
+
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_sub_unsub: Client (un)subscribe to topic \"%s\", id: %d\n", topic, pkt_id));
+
+ mqtt_output_append_fixed_header(&client->output, sub ? MQTT_MSG_TYPE_SUBSCRIBE : MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0, remaining_length);
+ /* Packet id */
+ mqtt_output_append_u16(&client->output, pkt_id);
+ /* Topic */
+ mqtt_output_append_string(&client->output, topic, topic_len);
+ /* QoS */
+ if (sub != 0) {
+ mqtt_output_append_u8(&client->output, LWIP_MIN(qos, 2));
+ }
+
+ mqtt_append_request(&client->pend_req_queue, r);
+ mqtt_output_send(&client->output, client->conn);
+ return ERR_OK;
+}
+
+
+/**
+ * @ingroup mqtt
+ * Set callback to handle incoming publish requests from server
+ * @param client MQTT client
+ * @param pub_cb Callback invoked when publish starts, contain topic and total length of payload
+ * @param data_cb Callback for each fragment of payload that arrives
+ * @param arg User supplied argument to both callbacks
+ */
+void
+mqtt_set_inpub_callback(mqtt_client_t *client, mqtt_incoming_publish_cb_t pub_cb,
+ mqtt_incoming_data_cb_t data_cb, void *arg)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("mqtt_set_inpub_callback: client != NULL", client != NULL);
+ client->data_cb = data_cb;
+ client->pub_cb = pub_cb;
+ client->inpub_arg = arg;
+}
+
+/**
+ * @ingroup mqtt
+ * Create a new MQTT client instance
+ * @return Pointer to instance on success, NULL otherwise
+ */
+mqtt_client_t *
+mqtt_client_new(void)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ return (mqtt_client_t *)mem_calloc(1, sizeof(mqtt_client_t));
+}
+
+/**
+ * @ingroup mqtt
+ * Free MQTT client instance
+ * @param client Pointer to instance to be freed
+ */
+void
+mqtt_client_free(mqtt_client_t *client)
+{
+ mem_free(client);
+}
+
+/**
+ * @ingroup mqtt
+ * Connect to MQTT server
+ * @param client MQTT client
+ * @param ip_addr Server IP
+ * @param port Server port
+ * @param cb Connection state change callback
+ * @param arg User supplied argument to connection callback
+ * @param client_info Client identification and connection options
+ * @return ERR_OK if successful, @see err_t enum for other results
+ */
+err_t
+mqtt_client_connect(mqtt_client_t *client, const ip_addr_t *ip_addr, u16_t port, mqtt_connection_cb_t cb, void *arg,
+ const struct mqtt_connect_client_info_t *client_info)
+{
+ err_t err;
+ size_t len;
+ u16_t client_id_length;
+ /* Length is the sum of 2+"MQTT", protocol level, flags and keep alive */
+ u16_t remaining_length = 2 + 4 + 1 + 1 + 2;
+ u8_t flags = 0, will_topic_len = 0, will_msg_len = 0;
+ u16_t client_user_len = 0, client_pass_len = 0;
+ mqtt_incoming_data_cb_t data_cb;
+ mqtt_incoming_publish_cb_t pub_cb;
+ void *inpub_arg;
+
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("mqtt_client_connect: client != NULL", client != NULL);
+ LWIP_ASSERT("mqtt_client_connect: ip_addr != NULL", ip_addr != NULL);
+ LWIP_ASSERT("mqtt_client_connect: client_info != NULL", client_info != NULL);
+ LWIP_ASSERT("mqtt_client_connect: client_info->client_id != NULL", client_info->client_id != NULL);
+
+ if (client->conn_state != TCP_DISCONNECTED) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_client_connect: Already connected\n"));
+ return ERR_ISCONN;
+ }
+
+ /* Wipe clean, but keep callbacks */
+ data_cb = client->data_cb;
+ pub_cb = client->pub_cb;
+ inpub_arg = client->inpub_arg;
+ memset(client, 0, sizeof(mqtt_client_t));
+ client->data_cb = data_cb;
+ client->pub_cb = pub_cb;
+ client->inpub_arg = inpub_arg;
+
+ client->connect_arg = arg;
+ client->connect_cb = cb;
+ client->keep_alive = client_info->keep_alive;
+ mqtt_init_requests(client->req_list, LWIP_ARRAYSIZE(client->req_list));
+
+ /* Build connect message */
+ if (client_info->will_topic != NULL && client_info->will_msg != NULL) {
+ flags |= MQTT_CONNECT_FLAG_WILL;
+ flags |= (client_info->will_qos & 3) << 3;
+ if (client_info->will_retain) {
+ flags |= MQTT_CONNECT_FLAG_WILL_RETAIN;
+ }
+ len = strlen(client_info->will_topic);
+ LWIP_ERROR("mqtt_client_connect: client_info->will_topic length overflow", len <= 0xFF, return ERR_VAL);
+ LWIP_ERROR("mqtt_client_connect: client_info->will_topic length must be > 0", len > 0, return ERR_VAL);
+ will_topic_len = (u8_t)len;
+ len = strlen(client_info->will_msg);
+ LWIP_ERROR("mqtt_client_connect: client_info->will_msg length overflow", len <= 0xFF, return ERR_VAL);
+ will_msg_len = (u8_t)len;
+ len = remaining_length + 2 + will_topic_len + 2 + will_msg_len;
+ LWIP_ERROR("mqtt_client_connect: remaining_length overflow", len <= 0xFFFF, return ERR_VAL);
+ remaining_length = (u16_t)len;
+ }
+ if (client_info->client_user != NULL) {
+ flags |= MQTT_CONNECT_FLAG_USERNAME;
+ len = strlen(client_info->client_user);
+ LWIP_ERROR("mqtt_client_connect: client_info->client_user length overflow", len <= 0xFFFF, return ERR_VAL);
+ LWIP_ERROR("mqtt_client_connect: client_info->client_user length must be > 0", len > 0, return ERR_VAL);
+ client_user_len = (u16_t)len;
+ len = remaining_length + 2 + client_user_len;
+ LWIP_ERROR("mqtt_client_connect: remaining_length overflow", len <= 0xFFFF, return ERR_VAL);
+ remaining_length = (u16_t)len;
+ }
+ if (client_info->client_pass != NULL) {
+ flags |= MQTT_CONNECT_FLAG_PASSWORD;
+ len = strlen(client_info->client_pass);
+ LWIP_ERROR("mqtt_client_connect: client_info->client_pass length overflow", len <= 0xFFFF, return ERR_VAL);
+ LWIP_ERROR("mqtt_client_connect: client_info->client_pass length must be > 0", len > 0, return ERR_VAL);
+ client_pass_len = (u16_t)len;
+ len = remaining_length + 2 + client_pass_len;
+ LWIP_ERROR("mqtt_client_connect: remaining_length overflow", len <= 0xFFFF, return ERR_VAL);
+ remaining_length = (u16_t)len;
+ }
+
+ /* Don't complicate things, always connect using clean session */
+ flags |= MQTT_CONNECT_FLAG_CLEAN_SESSION;
+
+ len = strlen(client_info->client_id);
+ LWIP_ERROR("mqtt_client_connect: client_info->client_id length overflow", len <= 0xFFFF, return ERR_VAL);
+ client_id_length = (u16_t)len;
+ len = remaining_length + 2 + client_id_length;
+ LWIP_ERROR("mqtt_client_connect: remaining_length overflow", len <= 0xFFFF, return ERR_VAL);
+ remaining_length = (u16_t)len;
+
+ if (mqtt_output_check_space(&client->output, remaining_length) == 0) {
+ return ERR_MEM;
+ }
+
+#if LWIP_ALTCP && LWIP_ALTCP_TLS
+ if (client_info->tls_config) {
+ client->conn = altcp_tls_new(client_info->tls_config, IP_GET_TYPE(ip_addr));
+ } else
+#endif
+ {
+ client->conn = altcp_tcp_new_ip_type(IP_GET_TYPE(ip_addr));
+ }
+ if (client->conn == NULL) {
+ return ERR_MEM;
+ }
+
+ /* Set arg pointer for callbacks */
+ altcp_arg(client->conn, client);
+ /* Any local address, pick random local port number */
+ err = altcp_bind(client->conn, IP_ADDR_ANY, 0);
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(MQTT_DEBUG_WARN, ("mqtt_client_connect: Error binding to local ip/port, %d\n", err));
+ goto tcp_fail;
+ }
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_client_connect: Connecting to host: %s at port:%"U16_F"\n", ipaddr_ntoa(ip_addr), port));
+
+ /* Connect to server */
+ err = altcp_connect(client->conn, ip_addr, port, mqtt_tcp_connect_cb);
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(MQTT_DEBUG_TRACE, ("mqtt_client_connect: Error connecting to remote ip/port, %d\n", err));
+ goto tcp_fail;
+ }
+ /* Set error callback */
+ altcp_err(client->conn, mqtt_tcp_err_cb);
+ client->conn_state = TCP_CONNECTING;
+
+ /* Append fixed header */
+ mqtt_output_append_fixed_header(&client->output, MQTT_MSG_TYPE_CONNECT, 0, 0, 0, remaining_length);
+ /* Append Protocol string */
+ mqtt_output_append_string(&client->output, "MQTT", 4);
+ /* Append Protocol level */
+ mqtt_output_append_u8(&client->output, 4);
+ /* Append connect flags */
+ mqtt_output_append_u8(&client->output, flags);
+ /* Append keep-alive */
+ mqtt_output_append_u16(&client->output, client_info->keep_alive);
+ /* Append client id */
+ mqtt_output_append_string(&client->output, client_info->client_id, client_id_length);
+ /* Append will message if used */
+ if ((flags & MQTT_CONNECT_FLAG_WILL) != 0) {
+ mqtt_output_append_string(&client->output, client_info->will_topic, will_topic_len);
+ mqtt_output_append_string(&client->output, client_info->will_msg, will_msg_len);
+ }
+ /* Append user name if given */
+ if ((flags & MQTT_CONNECT_FLAG_USERNAME) != 0) {
+ mqtt_output_append_string(&client->output, client_info->client_user, client_user_len);
+ }
+ /* Append password if given */
+ if ((flags & MQTT_CONNECT_FLAG_PASSWORD) != 0) {
+ mqtt_output_append_string(&client->output, client_info->client_pass, client_pass_len);
+ }
+ return ERR_OK;
+
+tcp_fail:
+ altcp_abort(client->conn);
+ client->conn = NULL;
+ return err;
+}
+
+
+/**
+ * @ingroup mqtt
+ * Disconnect from MQTT server
+ * @param client MQTT client
+ */
+void
+mqtt_disconnect(mqtt_client_t *client)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("mqtt_disconnect: client != NULL", client);
+ /* If connection in not already closed */
+ if (client->conn_state != TCP_DISCONNECTED) {
+ /* Set conn_state before calling mqtt_close to prevent callback from being called */
+ client->conn_state = TCP_DISCONNECTED;
+ mqtt_close(client, (mqtt_connection_status_t)0);
+ }
+}
+
+/**
+ * @ingroup mqtt
+ * Check connection with server
+ * @param client MQTT client
+ * @return 1 if connected to server, 0 otherwise
+ */
+u8_t
+mqtt_client_is_connected(mqtt_client_t *client)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("mqtt_client_is_connected: client != NULL", client);
+ return client->conn_state == MQTT_CONNECTED;
+}
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API */
diff --git a/src/apps/netbiosns/netbiosns.c b/src/apps/netbiosns/netbiosns.c
new file mode 100644
index 00000000000..479c375b68b
--- /dev/null
+++ b/src/apps/netbiosns/netbiosns.c
@@ -0,0 +1,533 @@
+ /**
+ * @file
+ * NetBIOS name service responder
+ */
+
+/**
+ * @defgroup netbiosns NETBIOS responder
+ * @ingroup apps
+ *
+ * This is an example implementation of a NetBIOS name server.
+ * It responds to name queries for a configurable name.
+ * Name resolving is not supported.
+ *
+ * Note that the device doesn't broadcast it's own name so can't
+ * detect duplicate names!
+ */
+
+/*
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Modifications by Ray Abram to respond to NetBIOS name requests when Incoming name = *
+ * - based on code from "https://github.com/esp8266/Arduino/commit/1f7989b31d26d7df9776a08f36d685eae7ac8f99"
+ * - with permission to relicense to BSD from original author:
+ * http://www.xpablo.cz/?p=751#more-751
+ */
+
+#include "lwip/apps/netbiosns.h"
+
+#if LWIP_IPV4 && LWIP_UDP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/def.h"
+#include "lwip/udp.h"
+#include "lwip/ip.h"
+#include "lwip/netif.h"
+#include "lwip/prot/iana.h"
+
+#include <string.h>
+
+/** size of a NetBIOS name */
+#define NETBIOS_NAME_LEN 16
+
+/** The Time-To-Live for NetBIOS name responds (in seconds)
+ * Default is 300000 seconds (3 days, 11 hours, 20 minutes) */
+#define NETBIOS_NAME_TTL 300000u
+
+/** NetBIOS header flags */
+#define NETB_HFLAG_RESPONSE 0x8000U
+#define NETB_HFLAG_OPCODE 0x7800U
+#define NETB_HFLAG_OPCODE_NAME_QUERY 0x0000U
+#define NETB_HFLAG_AUTHORATIVE 0x0400U
+#define NETB_HFLAG_TRUNCATED 0x0200U
+#define NETB_HFLAG_RECURS_DESIRED 0x0100U
+#define NETB_HFLAG_RECURS_AVAILABLE 0x0080U
+#define NETB_HFLAG_BROADCAST 0x0010U
+#define NETB_HFLAG_REPLYCODE 0x0008U
+#define NETB_HFLAG_REPLYCODE_NOERROR 0x0000U
+
+/* NetBIOS question types */
+#define NETB_QTYPE_NB 0x0020U
+#define NETB_QTYPE_NBSTAT 0x0021U
+
+/** NetBIOS name flags */
+#define NETB_NFLAG_UNIQUE 0x8000U
+#define NETB_NFLAG_NODETYPE 0x6000U
+#define NETB_NFLAG_NODETYPE_HNODE 0x6000U
+#define NETB_NFLAG_NODETYPE_MNODE 0x4000U
+#define NETB_NFLAG_NODETYPE_PNODE 0x2000U
+#define NETB_NFLAG_NODETYPE_BNODE 0x0000U
+
+#define NETB_NFLAG_NAME_IN_CONFLICT 0x0800U /* 1=Yes, 0=No */
+#define NETB_NFLAG_NAME_IS_ACTIVE 0x0400U /* 1=Yes, 0=No */
+#define NETB_NFLAG_NAME_IS_PERMANENT 0x0200U /* 1=Yes (Name is Permanent Node Name), 0=No */
+
+/** NetBIOS message header */
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct netbios_hdr {
+ PACK_STRUCT_FIELD(u16_t trans_id);
+ PACK_STRUCT_FIELD(u16_t flags);
+ PACK_STRUCT_FIELD(u16_t questions);
+ PACK_STRUCT_FIELD(u16_t answerRRs);
+ PACK_STRUCT_FIELD(u16_t authorityRRs);
+ PACK_STRUCT_FIELD(u16_t additionalRRs);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/epstruct.h"
+#endif
+
+/** NetBIOS message question part */
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct netbios_question_hdr {
+ PACK_STRUCT_FLD_8(u8_t nametype);
+ PACK_STRUCT_FLD_8(u8_t encname[(NETBIOS_NAME_LEN * 2) + 1]);
+ PACK_STRUCT_FIELD(u16_t type);
+ PACK_STRUCT_FIELD(u16_t cls);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/epstruct.h"
+#endif
+
+/** NetBIOS message name part */
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct netbios_name_hdr {
+ PACK_STRUCT_FLD_8(u8_t nametype);
+ PACK_STRUCT_FLD_8(u8_t encname[(NETBIOS_NAME_LEN * 2) + 1]);
+ PACK_STRUCT_FIELD(u16_t type);
+ PACK_STRUCT_FIELD(u16_t cls);
+ PACK_STRUCT_FIELD(u32_t ttl);
+ PACK_STRUCT_FIELD(u16_t datalen);
+ PACK_STRUCT_FIELD(u16_t flags);
+ PACK_STRUCT_FLD_S(ip4_addr_p_t addr);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/epstruct.h"
+#endif
+
+/** NetBIOS message */
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct netbios_resp {
+ struct netbios_hdr resp_hdr;
+ struct netbios_name_hdr resp_name;
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/epstruct.h"
+#endif
+
+/** The NBNS Structure Responds to a Name Query */
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct netbios_answer {
+ struct netbios_hdr answer_hdr;
+ /** the length of the next string */
+ PACK_STRUCT_FIELD(u8_t name_size);
+ /** WARNING!!! this item may be of a different length (we use this struct for transmission) */
+ PACK_STRUCT_FLD_8(u8_t query_name[(NETBIOS_NAME_LEN * 2) + 1]);
+ PACK_STRUCT_FIELD(u16_t packet_type);
+ PACK_STRUCT_FIELD(u16_t cls);
+ PACK_STRUCT_FIELD(u32_t ttl);
+ PACK_STRUCT_FIELD(u16_t data_length);
+#define OFFSETOF_STRUCT_NETBIOS_ANSWER_NUMBER_OF_NAMES 56
+ /** number of names */
+ PACK_STRUCT_FLD_8(u8_t number_of_names);
+ /** node name */
+ PACK_STRUCT_FLD_8(u8_t answer_name[NETBIOS_NAME_LEN]);
+ /** node flags */
+ PACK_STRUCT_FIELD(u16_t answer_name_flags);
+ /** Unit ID */
+ PACK_STRUCT_FLD_8(u8_t unit_id[6]);
+ /** Jumpers */
+ PACK_STRUCT_FLD_8(u8_t jumpers);
+ /** Test result */
+ PACK_STRUCT_FLD_8(u8_t test_result);
+ /** Version number */
+ PACK_STRUCT_FIELD(u16_t version_number);
+ /** Period of statistics */
+ PACK_STRUCT_FIELD(u16_t period_of_statistics);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t number_of_crcs);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t number_of_alignment_errors);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t number_of_collisions);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t number_of_send_aborts);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u32_t number_of_good_sends);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u32_t number_of_good_receives);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t number_of_retransmits);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t number_of_no_resource_condition);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t number_of_free_command_blocks);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t total_number_of_command_blocks);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t max_total_number_of_command_blocks);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t number_of_pending_sessions);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t max_number_of_pending_sessions);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t max_total_sessions_possible);
+ /** Statistics */
+ PACK_STRUCT_FIELD(u16_t session_data_packet_size);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/epstruct.h"
+#endif
+
+#ifdef NETBIOS_LWIP_NAME
+#define NETBIOS_LOCAL_NAME NETBIOS_LWIP_NAME
+#else
+static char netbiosns_local_name[NETBIOS_NAME_LEN];
+#define NETBIOS_LOCAL_NAME netbiosns_local_name
+#endif
+
+static struct udp_pcb *netbiosns_pcb;
+
+/** Decode a NetBIOS name (from packet to string) */
+static int
+netbiosns_name_decode(const char *name_enc, char *name_dec, int name_dec_len)
+{
+ const char *pname;
+ char cname;
+ char cnbname;
+ int idx = 0;
+
+ LWIP_UNUSED_ARG(name_dec_len);
+
+ /* Start decoding netbios name. */
+ pname = name_enc;
+ for (;;) {
+ /* Every two characters of the first level-encoded name
+ * turn into one character in the decoded name. */
+ cname = *pname;
+ if (cname == '\0') {
+ break; /* no more characters */
+ }
+ if (cname == '.') {
+ break; /* scope ID follows */
+ }
+ if (!lwip_isupper(cname)) {
+ /* Not legal. */
+ return -1;
+ }
+ cname -= 'A';
+ cnbname = cname << 4;
+ pname++;
+
+ cname = *pname;
+ if (!lwip_isupper(cname)) {
+ /* Not legal. */
+ return -1;
+ }
+ cname -= 'A';
+ cnbname |= cname;
+ pname++;
+
+ /* Do we have room to store the character? */
+ if (idx < NETBIOS_NAME_LEN) {
+ /* Yes - store the character. */
+ name_dec[idx++] = (cnbname != ' ' ? cnbname : '\0');
+ }
+ }
+
+ return 0;
+}
+
+#if 0 /* function currently unused */
+/** Encode a NetBIOS name (from string to packet) - currently unused because
+ we don't ask for names. */
+static int
+netbiosns_name_encode(char *name_enc, char *name_dec, int name_dec_len)
+{
+ char *pname;
+ char cname;
+ unsigned char ucname;
+ int idx = 0;
+
+ /* Start encoding netbios name. */
+ pname = name_enc;
+
+ for (;;) {
+ /* Every two characters of the first level-encoded name
+ * turn into one character in the decoded name. */
+ cname = *pname;
+ if (cname == '\0') {
+ break; /* no more characters */
+ }
+ if (cname == '.') {
+ break; /* scope ID follows */
+ }
+ if ((cname < 'A' || cname > 'Z') && (cname < '0' || cname > '9')) {
+ /* Not legal. */
+ return -1;
+ }
+
+ /* Do we have room to store the character? */
+ if (idx >= name_dec_len) {
+ return -1;
+ }
+
+ /* Yes - store the character. */
+ ucname = cname;
+ name_dec[idx++] = ('A' + ((ucname >> 4) & 0x0F));
+ name_dec[idx++] = ('A' + ( ucname & 0x0F));
+ pname++;
+ }
+
+ /* Fill with "space" coding */
+ for (; idx < name_dec_len - 1;) {
+ name_dec[idx++] = 'C';
+ name_dec[idx++] = 'A';
+ }
+
+ /* Terminate string */
+ name_dec[idx] = '\0';
+
+ return 0;
+}
+#endif /* 0 */
+
+/** NetBIOS Name service recv callback */
+static void
+netbiosns_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+ LWIP_UNUSED_ARG(arg);
+
+ /* if packet is valid */
+ if (p != NULL) {
+ char netbios_name[NETBIOS_NAME_LEN + 1];
+ struct netbios_hdr *netbios_hdr = (struct netbios_hdr *)p->payload;
+ struct netbios_question_hdr *netbios_question_hdr = (struct netbios_question_hdr *)(netbios_hdr + 1);
+
+ /* is the packet long enough (we need the header in one piece) */
+ if (p->len < (sizeof(struct netbios_hdr) + sizeof(struct netbios_question_hdr))) {
+ /* packet too short */
+ pbuf_free(p);
+ return;
+ }
+ /* we only answer if we got a default interface */
+ if (netif_default != NULL) {
+ /* @todo: do we need to check answerRRs/authorityRRs/additionalRRs? */
+ /* if the packet is a NetBIOS name query question */
+ if (((netbios_hdr->flags & PP_NTOHS(NETB_HFLAG_OPCODE)) == PP_NTOHS(NETB_HFLAG_OPCODE_NAME_QUERY)) &&
+ ((netbios_hdr->flags & PP_NTOHS(NETB_HFLAG_RESPONSE)) == 0) &&
+ (netbios_hdr->questions == PP_NTOHS(1))) {
+ /* decode the NetBIOS name */
+ netbiosns_name_decode((char *)(netbios_question_hdr->encname), netbios_name, sizeof(netbios_name));
+ /* check the request type */
+ if (netbios_question_hdr->type == PP_HTONS(NETB_QTYPE_NB)) {
+ /* if the packet is for us */
+ if (lwip_strnicmp(netbios_name, NETBIOS_LOCAL_NAME, sizeof(NETBIOS_LOCAL_NAME)) == 0) {
+ struct pbuf *q;
+ struct netbios_resp *resp;
+
+ q = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct netbios_resp), PBUF_RAM);
+ if (q != NULL) {
+ resp = (struct netbios_resp *)q->payload;
+
+ /* prepare NetBIOS header response */
+ resp->resp_hdr.trans_id = netbios_hdr->trans_id;
+ resp->resp_hdr.flags = PP_HTONS(NETB_HFLAG_RESPONSE |
+ NETB_HFLAG_OPCODE_NAME_QUERY |
+ NETB_HFLAG_AUTHORATIVE |
+ NETB_HFLAG_RECURS_DESIRED);
+ resp->resp_hdr.questions = 0;
+ resp->resp_hdr.answerRRs = PP_HTONS(1);
+ resp->resp_hdr.authorityRRs = 0;
+ resp->resp_hdr.additionalRRs = 0;
+
+ /* prepare NetBIOS header datas */
+ MEMCPY( resp->resp_name.encname, netbios_question_hdr->encname, sizeof(netbios_question_hdr->encname));
+ resp->resp_name.nametype = netbios_question_hdr->nametype;
+ resp->resp_name.type = netbios_question_hdr->type;
+ resp->resp_name.cls = netbios_question_hdr->cls;
+ resp->resp_name.ttl = PP_HTONL(NETBIOS_NAME_TTL);
+ resp->resp_name.datalen = PP_HTONS(sizeof(resp->resp_name.flags) + sizeof(resp->resp_name.addr));
+ resp->resp_name.flags = PP_HTONS(NETB_NFLAG_NODETYPE_BNODE);
+ ip4_addr_copy(resp->resp_name.addr, *netif_ip4_addr(netif_default));
+
+ /* send the NetBIOS response */
+ udp_sendto(upcb, q, addr, port);
+
+ /* free the "reference" pbuf */
+ pbuf_free(q);
+ }
+ }
+#if LWIP_NETBIOS_RESPOND_NAME_QUERY
+ } else if (netbios_question_hdr->type == PP_HTONS(NETB_QTYPE_NBSTAT)) {
+ /* if the packet is for us or general query */
+ if (!lwip_strnicmp(netbios_name, NETBIOS_LOCAL_NAME, sizeof(NETBIOS_LOCAL_NAME)) ||
+ !lwip_strnicmp(netbios_name, "*", sizeof(NETBIOS_LOCAL_NAME))) {
+ /* general query - ask for our IP address */
+ struct pbuf *q;
+ struct netbios_answer *resp;
+
+ q = pbuf_alloc(PBUF_TRANSPORT, sizeof(struct netbios_answer), PBUF_RAM);
+ if (q != NULL) {
+ /* buffer to which a response is compiled */
+ resp = (struct netbios_answer *) q->payload;
+
+ /* Init response to zero, especially the statistics fields */
+ memset(resp, 0, sizeof(*resp));
+
+ /* copy the query to the response ID */
+ resp->answer_hdr.trans_id = netbios_hdr->trans_id;
+ /* acknowledgment of termination */
+ resp->answer_hdr.flags = PP_HTONS(NETB_HFLAG_RESPONSE | NETB_HFLAG_OPCODE_NAME_QUERY | NETB_HFLAG_AUTHORATIVE);
+ /* resp->answer_hdr.questions = PP_HTONS(0); done by memset() */
+ /* serial number of the answer */
+ resp->answer_hdr.answerRRs = PP_HTONS(1);
+ /* resp->answer_hdr.authorityRRs = PP_HTONS(0); done by memset() */
+ /* resp->answer_hdr.additionalRRs = PP_HTONS(0); done by memset() */
+ /* we will copy the length of the station name */
+ resp->name_size = netbios_question_hdr->nametype;
+ /* we will copy the queried name */
+ MEMCPY(resp->query_name, netbios_question_hdr->encname, (NETBIOS_NAME_LEN * 2) + 1);
+ /* NBSTAT */
+ resp->packet_type = PP_HTONS(0x21);
+ /* Internet name */
+ resp->cls = PP_HTONS(1);
+ /* resp->ttl = PP_HTONL(0); done by memset() */
+ resp->data_length = PP_HTONS(sizeof(struct netbios_answer) - offsetof(struct netbios_answer, number_of_names));
+ resp->number_of_names = 1;
+
+ /* make windows see us as workstation, not as a server */
+ memset(resp->answer_name, 0x20, NETBIOS_NAME_LEN - 1);
+ /* strlen is checked to be < NETBIOS_NAME_LEN during initialization */
+ MEMCPY(resp->answer_name, NETBIOS_LOCAL_NAME, strlen(NETBIOS_LOCAL_NAME));
+
+ /* b-node, unique, active */
+ resp->answer_name_flags = PP_HTONS(NETB_NFLAG_NAME_IS_ACTIVE);
+
+ /* Set responder netif MAC address */
+ SMEMCPY(resp->unit_id, ip_current_input_netif()->hwaddr, sizeof(resp->unit_id));
+
+ udp_sendto(upcb, q, addr, port);
+ pbuf_free(q);
+ }
+ }
+#endif /* LWIP_NETBIOS_RESPOND_NAME_QUERY */
+ }
+ }
+ }
+ /* free the pbuf */
+ pbuf_free(p);
+ }
+}
+
+/**
+ * @ingroup netbiosns
+ * Init netbios responder
+ */
+void
+netbiosns_init(void)
+{
+ /* LWIP_ASSERT_CORE_LOCKED(); is checked by udp_new() */
+#ifdef NETBIOS_LWIP_NAME
+ LWIP_ASSERT("NetBIOS name is too long!", strlen(NETBIOS_LWIP_NAME) < NETBIOS_NAME_LEN);
+#endif
+
+ netbiosns_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
+ if (netbiosns_pcb != NULL) {
+ /* we have to be allowed to send broadcast packets! */
+ ip_set_option(netbiosns_pcb, SOF_BROADCAST);
+ udp_bind(netbiosns_pcb, IP_ANY_TYPE, LWIP_IANA_PORT_NETBIOS);
+ udp_recv(netbiosns_pcb, netbiosns_recv, netbiosns_pcb);
+ }
+}
+
+#ifndef NETBIOS_LWIP_NAME
+/**
+ * @ingroup netbiosns
+ * Set netbios name. ATTENTION: the hostname must be less than 15 characters!
+ * the NetBIOS name spec says the name MUST be upper case, so incoming name is forced into uppercase :-)
+ */
+void
+netbiosns_set_name(const char *hostname)
+{
+ size_t i;
+ size_t copy_len = strlen(hostname);
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("NetBIOS name is too long!", copy_len < NETBIOS_NAME_LEN);
+ if (copy_len >= NETBIOS_NAME_LEN) {
+ copy_len = NETBIOS_NAME_LEN - 1;
+ }
+
+ /* make name into upper case */
+ for (i = 0; i < copy_len; i++ ) {
+ netbiosns_local_name[i] = (char)lwip_toupper(hostname[i]);
+ }
+ netbiosns_local_name[copy_len] = '\0';
+}
+#endif /* NETBIOS_LWIP_NAME */
+
+/**
+ * @ingroup netbiosns
+ * Stop netbios responder
+ */
+void
+netbiosns_stop(void)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ if (netbiosns_pcb != NULL) {
+ udp_remove(netbiosns_pcb);
+ netbiosns_pcb = NULL;
+ }
+}
+
+#endif /* LWIP_IPV4 && LWIP_UDP */
diff --git a/src/apps/smtp/smtp.c b/src/apps/smtp/smtp.c
new file mode 100644
index 00000000000..a1780bdabeb
--- /dev/null
+++ b/src/apps/smtp/smtp.c
@@ -0,0 +1,1554 @@
+/**
+ * @file
+ * SMTP client module
+ *
+ * Author: Simon Goldschmidt
+ *
+ * @defgroup smtp SMTP client
+ * @ingroup apps
+ *
+ * This is simple SMTP client for raw API.
+ * It is a minimal implementation of SMTP as specified in RFC 5321.
+ *
+ * Example usage:
+@code{.c}
+ void my_smtp_result_fn(void *arg, u8_t smtp_result, u16_t srv_err, err_t err)
+ {
+ printf("mail (%p) sent with results: 0x%02x, 0x%04x, 0x%08x\n", arg,
+ smtp_result, srv_err, err);
+ }
+ static void my_smtp_test(void)
+ {
+ smtp_set_server_addr("mymailserver.org");
+ -> set both username and password as NULL if no auth needed
+ smtp_set_auth("username", "password");
+ smtp_send_mail("sender", "recipient", "subject", "body", my_smtp_result_fn,
+ some_argument);
+ }
+@endcode
+
+ * When using from any other thread than the tcpip_thread (for NO_SYS==0), use
+ * smtp_send_mail_int()!
+ *
+ * SMTP_BODYDH usage:
+@code{.c}
+ int my_smtp_bodydh_fn(void *arg, struct smtp_bodydh *bdh)
+ {
+ if(bdh->state >= 10) {
+ return BDH_DONE;
+ }
+ sprintf(bdh->buffer,"Line #%2d\r\n",bdh->state);
+ bdh->length = strlen(bdh->buffer);
+ ++bdh->state;
+ return BDH_WORKING;
+ }
+
+ smtp_send_mail_bodycback("sender", "recipient", "subject",
+ my_smtp_bodydh_fn, my_smtp_result_fn, some_argument);
+@endcode
+ *
+ * @todo:
+ * - attachments (the main difficulty here is streaming base64-encoding to
+ * prevent having to allocate a buffer for the whole encoded file at once)
+ * - test with more mail servers...
+ *
+ */
+
+#include "lwip/apps/smtp.h"
+
+#if LWIP_TCP && LWIP_CALLBACK_API
+#include "lwip/sys.h"
+#include "lwip/sockets.h"
+#include "lwip/altcp.h"
+#include "lwip/dns.h"
+#include "lwip/mem.h"
+#include "lwip/altcp_tcp.h"
+#include "lwip/altcp_tls.h"
+
+#include <string.h> /* strlen, memcpy */
+#include <stdlib.h>
+
+/** TCP poll interval. Unit is 0.5 sec. */
+#define SMTP_POLL_INTERVAL 4
+/** TCP poll timeout while sending message body, reset after every
+ * successful write. 3 minutes */
+#define SMTP_TIMEOUT_DATABLOCK ( 3 * 60 * SMTP_POLL_INTERVAL / 2)
+/** TCP poll timeout while waiting for confirmation after sending the body.
+ * 10 minutes */
+#define SMTP_TIMEOUT_DATATERM (10 * 60 * SMTP_POLL_INTERVAL / 2)
+/** TCP poll timeout while not sending the body.
+ * This is somewhat lower than the RFC states (5 minutes for initial, MAIL
+ * and RCPT) but still OK for us here.
+ * 2 minutes */
+#define SMTP_TIMEOUT ( 2 * 60 * SMTP_POLL_INTERVAL / 2)
+
+/* the various debug levels for this file */
+#define SMTP_DEBUG_TRACE (SMTP_DEBUG | LWIP_DBG_TRACE)
+#define SMTP_DEBUG_STATE (SMTP_DEBUG | LWIP_DBG_STATE)
+#define SMTP_DEBUG_WARN (SMTP_DEBUG | LWIP_DBG_LEVEL_WARNING)
+#define SMTP_DEBUG_WARN_STATE (SMTP_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
+#define SMTP_DEBUG_SERIOUS (SMTP_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
+
+
+#define SMTP_RX_BUF_LEN 255
+#define SMTP_TX_BUF_LEN 255
+#define SMTP_CRLF "\r\n"
+#define SMTP_CRLF_LEN 2
+
+#define SMTP_RESP_220 "220"
+#define SMTP_RESP_235 "235"
+#define SMTP_RESP_250 "250"
+#define SMTP_RESP_334 "334"
+#define SMTP_RESP_354 "354"
+#define SMTP_RESP_LOGIN_UNAME "VXNlcm5hbWU6"
+#define SMTP_RESP_LOGIN_PASS "UGFzc3dvcmQ6"
+
+#define SMTP_KEYWORD_AUTH_SP "AUTH "
+#define SMTP_KEYWORD_AUTH_EQ "AUTH="
+#define SMTP_KEYWORD_AUTH_LEN 5
+#define SMTP_AUTH_PARAM_PLAIN "PLAIN"
+#define SMTP_AUTH_PARAM_LOGIN "LOGIN"
+
+#define SMTP_CMD_EHLO_1 "EHLO ["
+#define SMTP_CMD_EHLO_1_LEN 6
+#define SMTP_CMD_EHLO_2 "]\r\n"
+#define SMTP_CMD_EHLO_2_LEN 3
+#define SMTP_CMD_AUTHPLAIN_1 "AUTH PLAIN "
+#define SMTP_CMD_AUTHPLAIN_1_LEN 11
+#define SMTP_CMD_AUTHPLAIN_2 "\r\n"
+#define SMTP_CMD_AUTHPLAIN_2_LEN 2
+#define SMTP_CMD_AUTHLOGIN "AUTH LOGIN\r\n"
+#define SMTP_CMD_AUTHLOGIN_LEN 12
+#define SMTP_CMD_MAIL_1 "MAIL FROM: <"
+#define SMTP_CMD_MAIL_1_LEN 12
+#define SMTP_CMD_MAIL_2 ">\r\n"
+#define SMTP_CMD_MAIL_2_LEN 3
+#define SMTP_CMD_RCPT_1 "RCPT TO: <"
+#define SMTP_CMD_RCPT_1_LEN 10
+#define SMTP_CMD_RCPT_2 ">\r\n"
+#define SMTP_CMD_RCPT_2_LEN 3
+#define SMTP_CMD_DATA "DATA\r\n"
+#define SMTP_CMD_DATA_LEN 6
+#define SMTP_CMD_HEADER_1 "From: <"
+#define SMTP_CMD_HEADER_1_LEN 7
+#define SMTP_CMD_HEADER_2 ">\r\nTo: <"
+#define SMTP_CMD_HEADER_2_LEN 8
+#define SMTP_CMD_HEADER_3 ">\r\nSubject: "
+#define SMTP_CMD_HEADER_3_LEN 12
+#define SMTP_CMD_HEADER_4 "\r\n\r\n"
+#define SMTP_CMD_HEADER_4_LEN 4
+#define SMTP_CMD_BODY_FINISHED "\r\n.\r\n"
+#define SMTP_CMD_BODY_FINISHED_LEN 5
+#define SMTP_CMD_QUIT "QUIT\r\n"
+#define SMTP_CMD_QUIT_LEN 6
+
+#if defined(SMTP_STAT_TX_BUF_MAX) && SMTP_STAT_TX_BUF_MAX
+#define SMTP_TX_BUF_MAX(len) LWIP_MACRO(if((len) > smtp_tx_buf_len_max) smtp_tx_buf_len_max = (len);)
+#else /* SMTP_STAT_TX_BUF_MAX */
+#define SMTP_TX_BUF_MAX(len)
+#endif /* SMTP_STAT_TX_BUF_MAX */
+
+#if SMTP_COPY_AUTHDATA
+#define SMTP_USERNAME(session) (session)->username
+#define SMTP_PASS(session) (session)->pass
+#define SMTP_AUTH_PLAIN_DATA(session) (session)->auth_plain
+#define SMTP_AUTH_PLAIN_LEN(session) (session)->auth_plain_len
+#else /* SMTP_COPY_AUTHDATA */
+#define SMTP_USERNAME(session) smtp_username
+#define SMTP_PASS(session) smtp_pass
+#define SMTP_AUTH_PLAIN_DATA(session) smtp_auth_plain
+#define SMTP_AUTH_PLAIN_LEN(session) smtp_auth_plain_len
+#endif /* SMTP_COPY_AUTHDATA */
+
+#if SMTP_BODYDH
+#ifndef SMTP_BODYDH_MALLOC
+#define SMTP_BODYDH_MALLOC(size) mem_malloc(size)
+#define SMTP_BODYDH_FREE(ptr) mem_free(ptr)
+#endif
+
+/* Some internal state return values */
+#define BDHALLDATASENT 2
+#define BDHSOMEDATASENT 1
+
+enum bdh_handler_state {
+ BDH_SENDING, /* Serving the user function generating body content */
+ BDH_STOP /* User function stopped, closing */
+};
+#endif
+
+/** State for SMTP client state machine */
+enum smtp_session_state {
+ SMTP_NULL,
+ SMTP_HELO,
+ SMTP_AUTH_PLAIN,
+ SMTP_AUTH_LOGIN_UNAME,
+ SMTP_AUTH_LOGIN_PASS,
+ SMTP_AUTH_LOGIN,
+ SMTP_MAIL,
+ SMTP_RCPT,
+ SMTP_DATA,
+ SMTP_BODY,
+ SMTP_QUIT,
+ SMTP_CLOSED
+};
+
+#ifdef LWIP_DEBUG
+/** State-to-string table for debugging */
+static const char *smtp_state_str[] = {
+ "SMTP_NULL",
+ "SMTP_HELO",
+ "SMTP_AUTH_PLAIN",
+ "SMTP_AUTH_LOGIN_UNAME",
+ "SMTP_AUTH_LOGIN_PASS",
+ "SMTP_AUTH_LOGIN",
+ "SMTP_MAIL",
+ "SMTP_RCPT",
+ "SMTP_DATA",
+ "SMTP_BODY",
+ "SMTP_QUIT",
+ "SMTP_CLOSED",
+};
+
+static const char *smtp_result_strs[] = {
+ "SMTP_RESULT_OK",
+ "SMTP_RESULT_ERR_UNKNOWN",
+ "SMTP_RESULT_ERR_CONNECT",
+ "SMTP_RESULT_ERR_HOSTNAME",
+ "SMTP_RESULT_ERR_CLOSED",
+ "SMTP_RESULT_ERR_TIMEOUT",
+ "SMTP_RESULT_ERR_SVR_RESP",
+ "SMTP_RESULT_ERR_MEM"
+};
+#endif /* LWIP_DEBUG */
+
+#if SMTP_BODYDH
+struct smtp_bodydh_state {
+ smtp_bodycback_fn callback_fn; /* The function to call (again) */
+ u16_t state;
+ struct smtp_bodydh exposed; /* the user function structure */
+};
+#endif /* SMTP_BODYDH */
+
+/** struct keeping the body and state of an smtp session */
+struct smtp_session {
+ /** keeping the state of the smtp session */
+ enum smtp_session_state state;
+ /** timeout handling, if this reaches 0, the connection is closed */
+ u16_t timer;
+ /** helper buffer for transmit, not used for sending body */
+ char tx_buf[SMTP_TX_BUF_LEN + 1];
+ struct pbuf* p;
+ /** source email address */
+ const char* from;
+ /** size of the sourceemail address */
+ u16_t from_len;
+ /** target email address */
+ const char* to;
+ /** size of the target email address */
+ u16_t to_len;
+ /** subject of the email */
+ const char *subject;
+ /** length of the subject string */
+ u16_t subject_len;
+ /** this is the body of the mail to be sent */
+ const char* body;
+ /** this is the length of the body to be sent */
+ u16_t body_len;
+ /** amount of data from body already sent */
+ u16_t body_sent;
+ /** callback function to call when closed */
+ smtp_result_fn callback_fn;
+ /** argument for callback function */
+ void *callback_arg;
+#if SMTP_COPY_AUTHDATA
+ /** Username to use for this request */
+ char *username;
+ /** Password to use for this request */
+ char *pass;
+ /** Username and password combined as necessary for PLAIN authentication */
+ char auth_plain[SMTP_MAX_USERNAME_LEN + SMTP_MAX_PASS_LEN + 3];
+ /** Length of smtp_auth_plain string (cannot use strlen since it includes \0) */
+ size_t auth_plain_len;
+#endif /* SMTP_COPY_AUTHDATA */
+#if SMTP_BODYDH
+ struct smtp_bodydh_state *bodydh;
+#endif /* SMTP_BODYDH */
+};
+
+/** IP address or DNS name of the server to use for next SMTP request */
+static char smtp_server[SMTP_MAX_SERVERNAME_LEN + 1];
+/** TCP port of the server to use for next SMTP request */
+static u16_t smtp_server_port = SMTP_DEFAULT_PORT;
+#if LWIP_ALTCP && LWIP_ALTCP_TLS
+/** If this is set, mail is sent using SMTPS */
+static struct altcp_tls_config *smtp_server_tls_config;
+#endif
+/** Username to use for the next SMTP request */
+static char *smtp_username;
+/** Password to use for the next SMTP request */
+static char *smtp_pass;
+/** Username and password combined as necessary for PLAIN authentication */
+static char smtp_auth_plain[SMTP_MAX_USERNAME_LEN + SMTP_MAX_PASS_LEN + 3];
+/** Length of smtp_auth_plain string (cannot use strlen since it includes \0) */
+static size_t smtp_auth_plain_len;
+
+#if SMTP_CHECK_DATA
+static err_t smtp_verify(const char *data, size_t data_len, u8_t linebreaks_allowed);
+#endif /* SMTP_CHECK_DATA */
+static err_t smtp_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err);
+static void smtp_tcp_err(void *arg, err_t err);
+static err_t smtp_tcp_poll(void *arg, struct altcp_pcb *pcb);
+static err_t smtp_tcp_sent(void *arg, struct altcp_pcb *pcb, u16_t len);
+static err_t smtp_tcp_connected(void *arg, struct altcp_pcb *pcb, err_t err);
+#if LWIP_DNS
+static void smtp_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg);
+#endif /* LWIP_DNS */
+#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
+static size_t smtp_base64_encode(char* target, size_t target_len, const char* source, size_t source_len);
+#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
+static enum smtp_session_state smtp_prepare_mail(struct smtp_session *s, u16_t *tx_buf_len);
+static void smtp_send_body(struct smtp_session *s, struct altcp_pcb *pcb);
+static void smtp_process(void *arg, struct altcp_pcb *pcb, struct pbuf *p);
+#if SMTP_BODYDH
+static void smtp_send_body_data_handler(struct smtp_session *s, struct altcp_pcb *pcb);
+#endif /* SMTP_BODYDH */
+
+
+#ifdef LWIP_DEBUG
+/** Convert an smtp result to a string */
+const char*
+smtp_result_str(u8_t smtp_result)
+{
+ if (smtp_result >= LWIP_ARRAYSIZE(smtp_result_strs)) {
+ return "UNKNOWN";
+ }
+ return smtp_result_strs[smtp_result];
+}
+
+/** Null-terminates the payload of p for printing out messages.
+ * WARNING: use this only if p is not needed any more as the last byte of
+ * payload is deleted!
+ */
+static const char*
+smtp_pbuf_str(struct pbuf* p)
+{
+ if ((p == NULL) || (p->len == 0)) {
+ return "";
+ }
+ ((char*)p->payload)[p->len] = 0;
+ return (const char*)p->payload;
+}
+#endif /* LWIP_DEBUG */
+
+/** @ingroup smtp
+ * Set IP address or DNS name for next SMTP connection
+ *
+ * @param server IP address (in ASCII representation) or DNS name of the server
+ */
+err_t
+smtp_set_server_addr(const char* server)
+{
+ size_t len = 0;
+
+ LWIP_ASSERT_CORE_LOCKED();
+
+ if (server != NULL) {
+ /* strlen: returns length WITHOUT terminating 0 byte */
+ len = strlen(server);
+ }
+ if (len > SMTP_MAX_SERVERNAME_LEN) {
+ return ERR_MEM;
+ }
+ if (len != 0) {
+ MEMCPY(smtp_server, server, len);
+ }
+ smtp_server[len] = 0; /* always OK because of smtp_server[SMTP_MAX_SERVERNAME_LEN + 1] */
+ return ERR_OK;
+}
+
+/** @ingroup smtp
+ * Set TCP port for next SMTP connection
+ *
+ * @param port TCP port
+ */
+void
+smtp_set_server_port(u16_t port)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ smtp_server_port = port;
+}
+
+#if LWIP_ALTCP && LWIP_ALTCP_TLS
+/** @ingroup smtp
+ * Set TLS configuration for next SMTP connection
+ *
+ * @param tls_config TLS configuration
+ */
+void
+smtp_set_tls_config(struct altcp_tls_config *tls_config)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ smtp_server_tls_config = tls_config;
+}
+#endif
+
+/** @ingroup smtp
+ * Set authentication parameters for next SMTP connection
+ *
+ * @param username login name as passed to the server
+ * @param pass password passed to the server together with username
+ */
+err_t
+smtp_set_auth(const char* username, const char* pass)
+{
+ size_t uname_len = 0;
+ size_t pass_len = 0;
+
+ LWIP_ASSERT_CORE_LOCKED();
+
+ memset(smtp_auth_plain, 0xfa, 64);
+ if (username != NULL) {
+ uname_len = strlen(username);
+ if (uname_len > SMTP_MAX_USERNAME_LEN) {
+ LWIP_DEBUGF(SMTP_DEBUG_SERIOUS, ("Username is too long, %d instead of %d\n",
+ (int)uname_len, SMTP_MAX_USERNAME_LEN));
+ return ERR_ARG;
+ }
+ }
+ if (pass != NULL) {
+#if SMTP_SUPPORT_AUTH_LOGIN || SMTP_SUPPORT_AUTH_PLAIN
+ pass_len = strlen(pass);
+ if (pass_len > SMTP_MAX_PASS_LEN) {
+ LWIP_DEBUGF(SMTP_DEBUG_SERIOUS, ("Password is too long, %d instead of %d\n",
+ (int)uname_len, SMTP_MAX_USERNAME_LEN));
+ return ERR_ARG;
+ }
+#else /* SMTP_SUPPORT_AUTH_LOGIN || SMTP_SUPPORT_AUTH_PLAIN */
+ LWIP_DEBUGF(SMTP_DEBUG_WARN, ("Password not supported as no authentication methods are activated\n"));
+#endif /* SMTP_SUPPORT_AUTH_LOGIN || SMTP_SUPPORT_AUTH_PLAIN */
+ }
+ *smtp_auth_plain = 0;
+ if (username != NULL) {
+ smtp_username = smtp_auth_plain + 1;
+ strcpy(smtp_username, username);
+ }
+ if (pass != NULL) {
+ smtp_pass = smtp_auth_plain + uname_len + 2;
+ strcpy(smtp_pass, pass);
+ }
+ smtp_auth_plain_len = uname_len + pass_len + 2;
+
+ return ERR_OK;
+}
+
+#if SMTP_BODYDH
+static void smtp_free_struct(struct smtp_session *s)
+{
+ if (s->bodydh != NULL) {
+ SMTP_BODYDH_FREE(s->bodydh);
+ }
+ SMTP_STATE_FREE(s);
+}
+#else /* SMTP_BODYDH */
+#define smtp_free_struct(x) SMTP_STATE_FREE(x)
+#endif /* SMTP_BODYDH */
+
+static struct altcp_pcb*
+smtp_setup_pcb(struct smtp_session *s, const ip_addr_t* remote_ip)
+{
+ struct altcp_pcb* pcb;
+ LWIP_UNUSED_ARG(remote_ip);
+
+#if LWIP_ALTCP && LWIP_ALTCP_TLS
+ if (smtp_server_tls_config) {
+ pcb = altcp_tls_new(smtp_server_tls_config, IP_GET_TYPE(remote_ip));
+ } else
+#endif
+ {
+ pcb = altcp_tcp_new_ip_type(IP_GET_TYPE(remote_ip));
+ }
+ if (pcb != NULL) {
+ altcp_arg(pcb, s);
+ altcp_recv(pcb, smtp_tcp_recv);
+ altcp_err(pcb, smtp_tcp_err);
+ altcp_poll(pcb, smtp_tcp_poll, SMTP_POLL_INTERVAL);
+ altcp_sent(pcb, smtp_tcp_sent);
+ }
+ return pcb;
+}
+
+/** The actual mail-sending function, called by smtp_send_mail and
+ * smtp_send_mail_static after setting up the struct smtp_session.
+ */
+static err_t
+smtp_send_mail_alloced(struct smtp_session *s)
+{
+ err_t err;
+ struct altcp_pcb* pcb = NULL;
+ ip_addr_t addr;
+
+ LWIP_ASSERT("no smtp_session supplied", s != NULL);
+
+#if SMTP_CHECK_DATA
+ /* check that body conforms to RFC:
+ * - convert all single-CR or -LF in body to CRLF
+ * - only 7-bit ASCII is allowed
+ */
+ if (smtp_verify(s->to, s->to_len, 0) != ERR_OK) {
+ err = ERR_ARG;
+ goto leave;
+ }
+ if (smtp_verify(s->from, s->from_len, 0) != ERR_OK) {
+ err = ERR_ARG;
+ goto leave;
+ }
+ if (smtp_verify(s->subject, s->subject_len, 0) != ERR_OK) {
+ err = ERR_ARG;
+ goto leave;
+ }
+#if SMTP_BODYDH
+ if (s->bodydh == NULL)
+#endif /* SMTP_BODYDH */
+ {
+ if (smtp_verify(s->body, s->body_len, 0) != ERR_OK) {
+ err = ERR_ARG;
+ goto leave;
+ }
+ }
+#endif /* SMTP_CHECK_DATA */
+
+#if SMTP_COPY_AUTHDATA
+ /* copy auth data, ensuring the first byte is always zero */
+ MEMCPY(s->auth_plain + 1, smtp_auth_plain + 1, smtp_auth_plain_len - 1);
+ s->auth_plain_len = smtp_auth_plain_len;
+ /* default username and pass is empty string */
+ s->username = s->auth_plain;
+ s->pass = s->auth_plain;
+ if (smtp_username != NULL) {
+ s->username += smtp_username - smtp_auth_plain;
+ }
+ if (smtp_pass != NULL) {
+ s->pass += smtp_pass - smtp_auth_plain;
+ }
+#endif /* SMTP_COPY_AUTHDATA */
+
+ s->state = SMTP_NULL;
+ s->timer = SMTP_TIMEOUT;
+
+#if LWIP_DNS
+ err = dns_gethostbyname(smtp_server, &addr, smtp_dns_found, s);
+#else /* LWIP_DNS */
+ err = ipaddr_aton(smtp_server, &addr) ? ERR_OK : ERR_ARG;
+#endif /* LWIP_DNS */
+ if (err == ERR_OK) {
+ pcb = smtp_setup_pcb(s, &addr);
+ if (pcb == NULL) {
+ err = ERR_MEM;
+ goto leave;
+ }
+ err = altcp_connect(pcb, &addr, smtp_server_port, smtp_tcp_connected);
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err));
+ goto deallocate_and_leave;
+ }
+ } else if (err != ERR_INPROGRESS) {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("dns_gethostbyname failed: %d\n", (int)err));
+ goto deallocate_and_leave;
+ }
+ return ERR_OK;
+
+deallocate_and_leave:
+ if (pcb != NULL) {
+ altcp_arg(pcb, NULL);
+ altcp_close(pcb);
+ }
+leave:
+ smtp_free_struct(s);
+ /* no need to call the callback here since we return != ERR_OK */
+ return err;
+}
+
+/** @ingroup smtp
+ * Send an email via the currently selected server, username and password.
+ *
+ * @param from source email address (must be NULL-terminated)
+ * @param to target email address (must be NULL-terminated)
+ * @param subject email subject (must be NULL-terminated)
+ * @param body email body (must be NULL-terminated)
+ * @param callback_fn callback function
+ * @param callback_arg user argument to callback_fn
+ * @returns - ERR_OK if structures were allocated and no error occurred starting the connection
+ * (this does not mean the email has been successfully sent!)
+ * - another err_t on error.
+ */
+err_t
+smtp_send_mail(const char* from, const char* to, const char* subject, const char* body,
+ smtp_result_fn callback_fn, void* callback_arg)
+{
+ struct smtp_session* s;
+ size_t from_len = strlen(from);
+ size_t to_len = strlen(to);
+ size_t subject_len = strlen(subject);
+ size_t body_len = strlen(body);
+ size_t mem_len = sizeof(struct smtp_session);
+ char *sfrom, *sto, *ssubject, *sbody;
+
+ LWIP_ASSERT_CORE_LOCKED();
+
+ mem_len += from_len + to_len + subject_len + body_len + 4;
+ if (mem_len > 0xffff) {
+ /* too long! */
+ return ERR_MEM;
+ }
+
+ /* Allocate memory to keep this email's session state */
+ s = (struct smtp_session *)SMTP_STATE_MALLOC((mem_size_t)mem_len);
+ if (s == NULL) {
+ return ERR_MEM;
+ }
+ /* initialize the structure */
+ memset(s, 0, mem_len);
+ s->from = sfrom = (char*)s + sizeof(struct smtp_session);
+ s->from_len = (u16_t)from_len;
+ s->to = sto = sfrom + from_len + 1;
+ s->to_len = (u16_t)to_len;
+ s->subject = ssubject = sto + to_len + 1;
+ s->subject_len = (u16_t)subject_len;
+ s->body = sbody = ssubject + subject_len + 1;
+ s->body_len = (u16_t)body_len;
+ /* copy source and target email address */
+ /* cast to size_t is a hack to cast away constness */
+ MEMCPY(sfrom, from, from_len + 1);
+ MEMCPY(sto, to, to_len + 1);
+ MEMCPY(ssubject, subject, subject_len + 1);
+ MEMCPY(sbody, body, body_len + 1);
+
+ s->callback_fn = callback_fn;
+ s->callback_arg = callback_arg;
+
+ /* call the actual implementation of this function */
+ return smtp_send_mail_alloced(s);
+}
+
+/** @ingroup smtp
+ * Same as smtp_send_mail, but doesn't copy from, to, subject and body into
+ * an internal buffer to save memory.
+ * WARNING: the above data must stay untouched until the callback function is
+ * called (unless the function returns != ERR_OK)
+ */
+err_t
+smtp_send_mail_static(const char *from, const char* to, const char* subject,
+ const char* body, smtp_result_fn callback_fn, void* callback_arg)
+{
+ struct smtp_session* s;
+ size_t len;
+
+ LWIP_ASSERT_CORE_LOCKED();
+
+ s = (struct smtp_session*)SMTP_STATE_MALLOC(sizeof(struct smtp_session));
+ if (s == NULL) {
+ return ERR_MEM;
+ }
+ memset(s, 0, sizeof(struct smtp_session));
+ /* initialize the structure */
+ s->from = from;
+ len = strlen(from);
+ LWIP_ASSERT("string is too long", len <= 0xffff);
+ s->from_len = (u16_t)len;
+ s->to = to;
+ len = strlen(to);
+ LWIP_ASSERT("string is too long", len <= 0xffff);
+ s->to_len = (u16_t)len;
+ s->subject = subject;
+ len = strlen(subject);
+ LWIP_ASSERT("string is too long", len <= 0xffff);
+ s->subject_len = (u16_t)len;
+ s->body = body;
+ len = strlen(body);
+ LWIP_ASSERT("string is too long", len <= 0xffff);
+ s->body_len = (u16_t)len;
+ s->callback_fn = callback_fn;
+ s->callback_arg = callback_arg;
+ /* call the actual implementation of this function */
+ return smtp_send_mail_alloced(s);
+}
+
+
+/** @ingroup smtp
+ * Same as smtp_send_mail but takes a struct smtp_send_request as single
+ * parameter which contains all the other parameters.
+ * To be used with tcpip_callback to send mail from interrupt context or from
+ * another thread.
+ *
+ * WARNING: server and authentication must stay untouched until this function has run!
+ *
+ * Usage example:
+ * - allocate a struct smtp_send_request (in a way that is allowed in interrupt context)
+ * - fill the members of the struct as if calling smtp_send_mail
+ * - specify a callback_function
+ * - set callback_arg to the structure itself
+ * - call this function
+ * - wait for the callback function to be called
+ * - in the callback function, deallocate the structure (passed as arg)
+ */
+void
+smtp_send_mail_int(void *arg)
+{
+ struct smtp_send_request *req = (struct smtp_send_request*)arg;
+ err_t err;
+
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("smtp_send_mail_int: no argument given", arg != NULL);
+
+ if (req->static_data) {
+ err = smtp_send_mail_static(req->from, req->to, req->subject, req->body,
+ req->callback_fn, req->callback_arg);
+ } else {
+ err = smtp_send_mail(req->from, req->to, req->subject, req->body,
+ req->callback_fn, req->callback_arg);
+ }
+ if ((err != ERR_OK) && (req->callback_fn != NULL)) {
+ req->callback_fn(req->callback_arg, SMTP_RESULT_ERR_UNKNOWN, 0, err);
+ }
+}
+
+#if SMTP_CHECK_DATA
+/** Verify that a given string conforms to the SMTP rules
+ * (7-bit only, no single CR or LF,
+ * @todo: no line consisting of a single dot only)
+ */
+static err_t
+smtp_verify(const char *data, size_t data_len, u8_t linebreaks_allowed)
+{
+ size_t i;
+ u8_t last_was_cr = 0;
+ for (i = 0; i < data_len; i++) {
+ char current = data[i];
+ if ((current & 0x80) != 0) {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: no 8-bit data supported: %s\n", data));
+ return ERR_ARG;
+ }
+ if (current == '\r') {
+ if (!linebreaks_allowed) {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: found CR where no linebreaks allowed: %s\n", data));
+ return ERR_ARG;
+ }
+ if (last_was_cr) {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: found double CR: %s\n", data));
+ return ERR_ARG;
+ }
+ last_was_cr = 1;
+ } else {
+ if (current == '\n') {
+ if (!last_was_cr) {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_verify: found LF without CR before: %s\n", data));
+ return ERR_ARG;
+ }
+ }
+ last_was_cr = 0;
+ }
+ }
+ return ERR_OK;
+}
+#endif /* SMTP_CHECK_DATA */
+
+/** Frees the smtp_session and calls the callback function */
+static void
+smtp_free(struct smtp_session *s, u8_t result, u16_t srv_err, err_t err)
+{
+ smtp_result_fn fn = s->callback_fn;
+ void *arg = s->callback_arg;
+ if (s->p != NULL) {
+ pbuf_free(s->p);
+ }
+ smtp_free_struct(s);
+ if (fn != NULL) {
+ fn(arg, result, srv_err, err);
+ }
+}
+
+/** Try to close a pcb and free the arg if successful */
+static void
+smtp_close(struct smtp_session *s, struct altcp_pcb *pcb, u8_t result,
+ u16_t srv_err, err_t err)
+{
+ if (pcb != NULL) {
+ altcp_arg(pcb, NULL);
+ if (altcp_close(pcb) == ERR_OK) {
+ if (s != NULL) {
+ smtp_free(s, result, srv_err, err);
+ }
+ } else {
+ /* close failed, set back arg */
+ altcp_arg(pcb, s);
+ }
+ } else {
+ if (s != NULL) {
+ smtp_free(s, result, srv_err, err);
+ }
+ }
+}
+
+/** Raw API TCP err callback: pcb is already deallocated */
+static void
+smtp_tcp_err(void *arg, err_t err)
+{
+ LWIP_UNUSED_ARG(err);
+ if (arg != NULL) {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_tcp_err: connection reset by remote host\n"));
+ smtp_free((struct smtp_session*)arg, SMTP_RESULT_ERR_CLOSED, 0, err);
+ }
+}
+
+/** Raw API TCP poll callback */
+static err_t
+smtp_tcp_poll(void *arg, struct altcp_pcb *pcb)
+{
+ if (arg != NULL) {
+ struct smtp_session *s = (struct smtp_session*)arg;
+ if (s->timer != 0) {
+ s->timer--;
+ }
+ }
+ smtp_process(arg, pcb, NULL);
+ return ERR_OK;
+}
+
+/** Raw API TCP sent callback */
+static err_t
+smtp_tcp_sent(void *arg, struct altcp_pcb *pcb, u16_t len)
+{
+ LWIP_UNUSED_ARG(len);
+
+ smtp_process(arg, pcb, NULL);
+
+ return ERR_OK;
+}
+
+/** Raw API TCP recv callback */
+static err_t
+smtp_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err)
+{
+ LWIP_UNUSED_ARG(err);
+ if (p != NULL) {
+ altcp_recved(pcb, p->tot_len);
+ smtp_process(arg, pcb, p);
+ } else {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_tcp_recv: connection closed by remote host\n"));
+ smtp_close((struct smtp_session*)arg, pcb, SMTP_RESULT_ERR_CLOSED, 0, err);
+ }
+ return ERR_OK;
+}
+
+static err_t
+smtp_tcp_connected(void *arg, struct altcp_pcb *pcb, err_t err)
+{
+ LWIP_UNUSED_ARG(arg);
+
+ if (err == ERR_OK) {
+ LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_connected: Waiting for 220\n"));
+ } else {
+ /* shouldn't happen, but we still check 'err', only to be sure */
+ LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_connected: %d\n", (int)err));
+ smtp_close((struct smtp_session*)arg, pcb, SMTP_RESULT_ERR_CONNECT, 0, err);
+ }
+ return ERR_OK;
+}
+
+#if LWIP_DNS
+/** DNS callback
+ * If ipaddr is non-NULL, resolving succeeded, otherwise it failed.
+ */
+static void
+smtp_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg)
+{
+ struct smtp_session *s = (struct smtp_session*)arg;
+ struct altcp_pcb *pcb;
+ err_t err;
+ u8_t result;
+
+ LWIP_UNUSED_ARG(hostname);
+
+ if (ipaddr != NULL) {
+ pcb = smtp_setup_pcb(s, ipaddr);
+ if (pcb != NULL) {
+ LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_dns_found: hostname resolved, connecting\n"));
+ err = altcp_connect(pcb, ipaddr, smtp_server_port, smtp_tcp_connected);
+ if (err == ERR_OK) {
+ return;
+ }
+ LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("tcp_connect failed: %d\n", (int)err));
+ result = SMTP_RESULT_ERR_CONNECT;
+ } else {
+ LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_dns_found: failed to allocate tcp pcb\n"));
+ result = SMTP_RESULT_ERR_MEM;
+ err = ERR_MEM;
+ }
+ } else {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_dns_found: failed to resolve hostname: %s\n",
+ hostname));
+ pcb = NULL;
+ result = SMTP_RESULT_ERR_HOSTNAME;
+ err = ERR_ARG;
+ }
+ smtp_close(s, pcb, result, 0, err);
+}
+#endif /* LWIP_DNS */
+
+#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
+
+/** Table 6-bit-index-to-ASCII used for base64-encoding */
+static const char base64_table[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '+', '/'
+};
+
+/** Base64 encoding */
+static size_t
+smtp_base64_encode(char* target, size_t target_len, const char* source, size_t source_len)
+{
+ size_t i;
+ s8_t j;
+ size_t target_idx = 0;
+ size_t longer = (source_len % 3) ? (3 - (source_len % 3)) : 0;
+ size_t source_len_b64 = source_len + longer;
+ size_t len = (((source_len_b64) * 4) / 3);
+ u8_t x = 5;
+ u8_t current = 0;
+ LWIP_UNUSED_ARG(target_len);
+
+ LWIP_ASSERT("target_len is too short", target_len >= len);
+
+ for (i = 0; i < source_len_b64; i++) {
+ u8_t b = (i < source_len ? (u8_t)source[i] : 0);
+ for (j = 7; j >= 0; j--, x--) {
+ if ((b & (1 << j)) != 0) {
+ current = (u8_t)(current | (1U << x));
+ }
+ if (x == 0) {
+ target[target_idx++] = base64_table[current];
+ x = 6;
+ current = 0;
+ }
+ }
+ }
+ for (i = len - longer; i < len; i++) {
+ target[i] = '=';
+ }
+ return len;
+}
+#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
+
+/** Parse pbuf to see if it contains the beginning of an answer.
+ * If so, it returns the contained response code as number between 1 and 999.
+ * If not, zero is returned.
+ *
+ * @param s smtp session struct
+ */
+static u16_t
+smtp_is_response(struct smtp_session *s)
+{
+ char digits[4];
+ long num;
+
+ if (s->p == NULL) {
+ return 0;
+ }
+ /* copy three digits and convert them to int */
+ if (pbuf_copy_partial(s->p, digits, 3, 0) != 3) {
+ /* pbuf was too short */
+ return 0;
+ }
+ digits[3] = 0;
+ num = strtol(digits, NULL, 10);
+ if ((num <= 0) || (num >= 1000)) {
+ /* failed to find response code at start of line */
+ return 0;
+ }
+ return (u16_t)num;
+}
+
+/** Parse pbuf to see if it contains a fully received answer.
+ * If one is found, ERR_OK is returned.
+ * If none is found, ERR_VAL is returned.
+ *
+ * A fully received answer is a 3-digit number followed by a space,
+ * some string and a CRLF as line ending.
+ *
+ * @param s smtp session struct
+ */
+static err_t
+smtp_is_response_finished(struct smtp_session *s)
+{
+ u8_t sp;
+ u16_t crlf;
+ u16_t offset;
+
+ if (s->p == NULL) {
+ return ERR_VAL;
+ }
+ offset = 0;
+again:
+ /* We could check the response number here, but we trust the
+ * protocol definition which says the client can rely on it being
+ * the same on every line. */
+
+ /* find CRLF */
+ if (offset > 0xFFFF - 4) {
+ /* would overflow */
+ return ERR_VAL;
+ }
+ crlf = pbuf_memfind(s->p, SMTP_CRLF, SMTP_CRLF_LEN, (u16_t)(offset + 4));
+ if (crlf == 0xFFFF) {
+ /* no CRLF found */
+ return ERR_VAL;
+ }
+ sp = pbuf_get_at(s->p, (u16_t)(offset + 3));
+ if (sp == '-') {
+ /* no space after response code -> try next line */
+ offset = (u16_t)(crlf + 2);
+ if (offset < crlf) {
+ /* overflow */
+ return ERR_VAL;
+ }
+ goto again;
+ } else if (sp == ' ') {
+ /* CRLF found after response code + space -> valid response */
+ return ERR_OK;
+ }
+ /* sp contains invalid character */
+ return ERR_VAL;
+}
+
+/** Prepare HELO/EHLO message */
+static enum smtp_session_state
+smtp_prepare_helo(struct smtp_session *s, u16_t *tx_buf_len, struct altcp_pcb *pcb)
+{
+ size_t ipa_len;
+ const char *ipa = ipaddr_ntoa(altcp_get_ip(pcb, 1));
+ LWIP_ASSERT("ipaddr_ntoa returned NULL", ipa != NULL);
+ ipa_len = strlen(ipa);
+ LWIP_ASSERT("string too long", ipa_len <= (SMTP_TX_BUF_LEN-SMTP_CMD_EHLO_1_LEN-SMTP_CMD_EHLO_2_LEN));
+
+ *tx_buf_len = (u16_t)(SMTP_CMD_EHLO_1_LEN + (u16_t)ipa_len + SMTP_CMD_EHLO_2_LEN);
+ LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
+
+ SMEMCPY(s->tx_buf, SMTP_CMD_EHLO_1, SMTP_CMD_EHLO_1_LEN);
+ MEMCPY(&s->tx_buf[SMTP_CMD_EHLO_1_LEN], ipa, ipa_len);
+ SMEMCPY(&s->tx_buf[SMTP_CMD_EHLO_1_LEN + ipa_len], SMTP_CMD_EHLO_2, SMTP_CMD_EHLO_2_LEN);
+ return SMTP_HELO;
+}
+
+#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
+/** Parse last server response (in rx_buf) for supported authentication method,
+ * create data to send out (to tx_buf), set tx_data_len correctly
+ * and return the next state.
+ */
+static enum smtp_session_state
+smtp_prepare_auth_or_mail(struct smtp_session *s, u16_t *tx_buf_len)
+{
+ /* check response for supported authentication method */
+ u16_t auth = pbuf_strstr(s->p, SMTP_KEYWORD_AUTH_SP);
+ if (auth == 0xFFFF) {
+ auth = pbuf_strstr(s->p, SMTP_KEYWORD_AUTH_EQ);
+ }
+ if (auth != 0xFFFF) {
+ u16_t crlf = pbuf_memfind(s->p, SMTP_CRLF, SMTP_CRLF_LEN, auth);
+ if ((crlf != 0xFFFF) && (crlf > auth)) {
+ /* use tx_buf temporarily */
+ u16_t copied = pbuf_copy_partial(s->p, s->tx_buf, (u16_t)(crlf - auth), auth);
+ if (copied != 0) {
+ char *sep = s->tx_buf + SMTP_KEYWORD_AUTH_LEN;
+ s->tx_buf[copied] = 0;
+#if SMTP_SUPPORT_AUTH_PLAIN
+ /* favour PLAIN over LOGIN since it involves less requests */
+ if (strstr(sep, SMTP_AUTH_PARAM_PLAIN) != NULL) {
+ size_t auth_len;
+ /* server supports AUTH PLAIN */
+ SMEMCPY(s->tx_buf, SMTP_CMD_AUTHPLAIN_1, SMTP_CMD_AUTHPLAIN_1_LEN);
+
+ /* add base64-encoded string "\0username\0password" */
+ auth_len = smtp_base64_encode(&s->tx_buf[SMTP_CMD_AUTHPLAIN_1_LEN],
+ SMTP_TX_BUF_LEN - SMTP_CMD_AUTHPLAIN_1_LEN, SMTP_AUTH_PLAIN_DATA(s),
+ SMTP_AUTH_PLAIN_LEN(s));
+ LWIP_ASSERT("string too long", auth_len <= (SMTP_TX_BUF_LEN-SMTP_CMD_AUTHPLAIN_1_LEN-SMTP_CMD_AUTHPLAIN_2_LEN));
+ *tx_buf_len = (u16_t)(SMTP_CMD_AUTHPLAIN_1_LEN + SMTP_CMD_AUTHPLAIN_2_LEN + (u16_t)auth_len);
+ SMEMCPY(&s->tx_buf[SMTP_CMD_AUTHPLAIN_1_LEN + auth_len], SMTP_CMD_AUTHPLAIN_2,
+ SMTP_CMD_AUTHPLAIN_2_LEN);
+ return SMTP_AUTH_PLAIN;
+ } else
+#endif /* SMTP_SUPPORT_AUTH_PLAIN */
+ {
+#if SMTP_SUPPORT_AUTH_LOGIN
+ if (strstr(sep, SMTP_AUTH_PARAM_LOGIN) != NULL) {
+ /* server supports AUTH LOGIN */
+ *tx_buf_len = SMTP_CMD_AUTHLOGIN_LEN;
+ SMEMCPY(s->tx_buf, SMTP_CMD_AUTHLOGIN, SMTP_CMD_AUTHLOGIN_LEN);
+ return SMTP_AUTH_LOGIN_UNAME;
+ }
+#endif /* SMTP_SUPPORT_AUTH_LOGIN */
+ }
+ }
+ }
+ }
+ /* server didnt's send correct keywords for AUTH, try sending directly */
+ return smtp_prepare_mail(s, tx_buf_len);
+}
+#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
+
+#if SMTP_SUPPORT_AUTH_LOGIN
+/** Send base64-encoded username */
+static enum smtp_session_state
+smtp_prepare_auth_login_uname(struct smtp_session *s, u16_t *tx_buf_len)
+{
+ size_t base64_len = smtp_base64_encode(s->tx_buf, SMTP_TX_BUF_LEN,
+ SMTP_USERNAME(s), strlen(SMTP_USERNAME(s)));
+ /* @todo: support base64-encoded longer than 64k */
+ LWIP_ASSERT("string too long", base64_len <= 0xffff);
+ LWIP_ASSERT("tx_buf overflow detected", base64_len <= SMTP_TX_BUF_LEN - SMTP_CRLF_LEN);
+ *tx_buf_len = (u16_t)(base64_len + SMTP_CRLF_LEN);
+
+ SMEMCPY(&s->tx_buf[base64_len], SMTP_CRLF, SMTP_CRLF_LEN);
+ s->tx_buf[*tx_buf_len] = 0;
+ return SMTP_AUTH_LOGIN_PASS;
+}
+
+/** Send base64-encoded password */
+static enum smtp_session_state
+smtp_prepare_auth_login_pass(struct smtp_session *s, u16_t *tx_buf_len)
+{
+ size_t base64_len = smtp_base64_encode(s->tx_buf, SMTP_TX_BUF_LEN,
+ SMTP_PASS(s), strlen(SMTP_PASS(s)));
+ /* @todo: support base64-encoded longer than 64k */
+ LWIP_ASSERT("string too long", base64_len <= 0xffff);
+ LWIP_ASSERT("tx_buf overflow detected", base64_len <= SMTP_TX_BUF_LEN - SMTP_CRLF_LEN);
+ *tx_buf_len = (u16_t)(base64_len + SMTP_CRLF_LEN);
+
+ SMEMCPY(&s->tx_buf[base64_len], SMTP_CRLF, SMTP_CRLF_LEN);
+ s->tx_buf[*tx_buf_len] = 0;
+ return SMTP_AUTH_LOGIN;
+}
+#endif /* SMTP_SUPPORT_AUTH_LOGIN */
+
+/** Prepare MAIL message */
+static enum smtp_session_state
+smtp_prepare_mail(struct smtp_session *s, u16_t *tx_buf_len)
+{
+ char *target = s->tx_buf;
+ LWIP_ASSERT("tx_buf overflow detected", s->from_len <= (SMTP_TX_BUF_LEN - SMTP_CMD_MAIL_1_LEN - SMTP_CMD_MAIL_2_LEN));
+ *tx_buf_len = (u16_t)(SMTP_CMD_MAIL_1_LEN + SMTP_CMD_MAIL_2_LEN + s->from_len);
+ target[*tx_buf_len] = 0;
+
+ SMEMCPY(target, SMTP_CMD_MAIL_1, SMTP_CMD_MAIL_1_LEN);
+ target += SMTP_CMD_MAIL_1_LEN;
+ MEMCPY(target, s->from, s->from_len);
+ target += s->from_len;
+ SMEMCPY(target, SMTP_CMD_MAIL_2, SMTP_CMD_MAIL_2_LEN);
+ return SMTP_MAIL;
+}
+
+/** Prepare RCPT message */
+static enum smtp_session_state
+smtp_prepare_rcpt(struct smtp_session *s, u16_t *tx_buf_len)
+{
+ char *target = s->tx_buf;
+ LWIP_ASSERT("tx_buf overflow detected", s->to_len <= (SMTP_TX_BUF_LEN - SMTP_CMD_RCPT_1_LEN - SMTP_CMD_RCPT_2_LEN));
+ *tx_buf_len = (u16_t)(SMTP_CMD_RCPT_1_LEN + SMTP_CMD_RCPT_2_LEN + s->to_len);
+ target[*tx_buf_len] = 0;
+
+ SMEMCPY(target, SMTP_CMD_RCPT_1, SMTP_CMD_RCPT_1_LEN);
+ target += SMTP_CMD_RCPT_1_LEN;
+ MEMCPY(target, s->to, s->to_len);
+ target += s->to_len;
+ SMEMCPY(target, SMTP_CMD_RCPT_2, SMTP_CMD_RCPT_2_LEN);
+ return SMTP_RCPT;
+}
+
+/** Prepare header of body */
+static enum smtp_session_state
+smtp_prepare_header(struct smtp_session *s, u16_t *tx_buf_len)
+{
+ char *target = s->tx_buf;
+ int len = SMTP_CMD_HEADER_1_LEN + SMTP_CMD_HEADER_2_LEN +
+ SMTP_CMD_HEADER_3_LEN + SMTP_CMD_HEADER_4_LEN + s->from_len + s->to_len +
+ s->subject_len;
+ LWIP_ASSERT("tx_buf overflow detected", len > 0 && len <= SMTP_TX_BUF_LEN);
+ *tx_buf_len = (u16_t)len;
+ target[*tx_buf_len] = 0;
+
+ SMEMCPY(target, SMTP_CMD_HEADER_1, SMTP_CMD_HEADER_1_LEN);
+ target += SMTP_CMD_HEADER_1_LEN;
+ MEMCPY(target, s->from, s->from_len);
+ target += s->from_len;
+ SMEMCPY(target, SMTP_CMD_HEADER_2, SMTP_CMD_HEADER_2_LEN);
+ target += SMTP_CMD_HEADER_2_LEN;
+ MEMCPY(target, s->to, s->to_len);
+ target += s->to_len;
+ SMEMCPY(target, SMTP_CMD_HEADER_3, SMTP_CMD_HEADER_3_LEN);
+ target += SMTP_CMD_HEADER_3_LEN;
+ MEMCPY(target, s->subject, s->subject_len);
+ target += s->subject_len;
+ SMEMCPY(target, SMTP_CMD_HEADER_4, SMTP_CMD_HEADER_4_LEN);
+
+ return SMTP_BODY;
+}
+
+/** Prepare QUIT message */
+static enum smtp_session_state
+smtp_prepare_quit(struct smtp_session *s, u16_t *tx_buf_len)
+{
+ *tx_buf_len = SMTP_CMD_QUIT_LEN;
+ s->tx_buf[*tx_buf_len] = 0;
+ SMEMCPY(s->tx_buf, SMTP_CMD_QUIT, SMTP_CMD_QUIT_LEN);
+ LWIP_ASSERT("tx_buf overflow detected", *tx_buf_len <= SMTP_TX_BUF_LEN);
+ return SMTP_CLOSED;
+}
+
+/** If in state SMTP_BODY, try to send more body data */
+static void
+smtp_send_body(struct smtp_session *s, struct altcp_pcb *pcb)
+{
+ err_t err;
+
+ if (s->state == SMTP_BODY) {
+#if SMTP_BODYDH
+ if (s->bodydh) {
+ smtp_send_body_data_handler(s, pcb);
+ } else
+#endif /* SMTP_BODYDH */
+ {
+ u16_t send_len = (u16_t)(s->body_len - s->body_sent);
+ if (send_len > 0) {
+ u16_t snd_buf = altcp_sndbuf(pcb);
+ if (send_len > snd_buf) {
+ send_len = snd_buf;
+ }
+ if (send_len > 0) {
+ /* try to send something out */
+ err = altcp_write(pcb, &s->body[s->body_sent], (u16_t)send_len, TCP_WRITE_FLAG_COPY);
+ if (err == ERR_OK) {
+ s->timer = SMTP_TIMEOUT_DATABLOCK;
+ s->body_sent = (u16_t)(s->body_sent + send_len);
+ if (s->body_sent < s->body_len) {
+ LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_send_body: %d of %d bytes written\n",
+ s->body_sent, s->body_len));
+ }
+ }
+ }
+ }
+ }
+ if (s->body_sent == s->body_len) {
+ /* the whole body has been written, write last line */
+ LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_send_body: body completely written (%d bytes), appending end-of-body\n",
+ s->body_len));
+ err = altcp_write(pcb, SMTP_CMD_BODY_FINISHED, SMTP_CMD_BODY_FINISHED_LEN, 0);
+ if (err == ERR_OK) {
+ s->timer = SMTP_TIMEOUT_DATATERM;
+ LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_send_body: end-of-body written, changing state to %s\n",
+ smtp_state_str[SMTP_QUIT]));
+ /* last line written, change state, wait for confirmation */
+ s->state = SMTP_QUIT;
+ }
+ }
+ }
+}
+
+/** State machine-like implementation of an SMTP client.
+ */
+static void
+smtp_process(void *arg, struct altcp_pcb *pcb, struct pbuf *p)
+{
+ struct smtp_session* s = (struct smtp_session*)arg;
+ u16_t response_code = 0;
+ u16_t tx_buf_len = 0;
+ enum smtp_session_state next_state;
+
+ if (arg == NULL) {
+ /* already closed SMTP connection */
+ if (p != NULL) {
+ LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("Received %d bytes after closing: %s\n",
+ p->tot_len, smtp_pbuf_str(p)));
+ pbuf_free(p);
+ }
+ return;
+ }
+
+ next_state = s->state;
+
+ if (p != NULL) {
+ /* received data */
+ if (s->p == NULL) {
+ s->p = p;
+ } else {
+ pbuf_cat(s->p, p);
+ }
+ } else {
+ /* idle timer, close connection if timed out */
+ if (s->timer == 0) {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_process: connection timed out, closing\n"));
+ smtp_close(s, pcb, SMTP_RESULT_ERR_TIMEOUT, 0, ERR_TIMEOUT);
+ return;
+ }
+ if (s->state == SMTP_BODY) {
+ smtp_send_body(s, pcb);
+ return;
+ }
+ }
+ response_code = smtp_is_response(s);
+ if (response_code) {
+ LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process: received response code: %d\n", response_code));
+ if (smtp_is_response_finished(s) != ERR_OK) {
+ LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process: partly received response code: %d\n", response_code));
+ /* wait for next packet to complete the respone */
+ return;
+ }
+ } else {
+ if (s->p != NULL) {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN, ("smtp_process: unknown data received (%s)\n",
+ smtp_pbuf_str(s->p)));
+ pbuf_free(s->p);
+ s->p = NULL;
+ }
+ return;
+ }
+
+ switch(s->state)
+ {
+ case(SMTP_NULL):
+ /* wait for 220 */
+ if (response_code == 220) {
+ /* then send EHLO */
+ next_state = smtp_prepare_helo(s, &tx_buf_len, pcb);
+ }
+ break;
+ case(SMTP_HELO):
+ /* wait for 250 */
+ if (response_code == 250) {
+#if SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN
+ /* then send AUTH or MAIL */
+ next_state = smtp_prepare_auth_or_mail(s, &tx_buf_len);
+ }
+ break;
+ case(SMTP_AUTH_LOGIN):
+ case(SMTP_AUTH_PLAIN):
+ /* wait for 235 */
+ if (response_code == 235) {
+#endif /* SMTP_SUPPORT_AUTH_PLAIN || SMTP_SUPPORT_AUTH_LOGIN */
+ /* send MAIL */
+ next_state = smtp_prepare_mail(s, &tx_buf_len);
+ }
+ break;
+#if SMTP_SUPPORT_AUTH_LOGIN
+ case(SMTP_AUTH_LOGIN_UNAME):
+ /* wait for 334 Username */
+ if (response_code == 334) {
+ if (pbuf_strstr(s->p, SMTP_RESP_LOGIN_UNAME) != 0xFFFF) {
+ /* send username */
+ next_state = smtp_prepare_auth_login_uname(s, &tx_buf_len);
+ }
+ }
+ break;
+ case(SMTP_AUTH_LOGIN_PASS):
+ /* wait for 334 Password */
+ if (response_code == 334) {
+ if (pbuf_strstr(s->p, SMTP_RESP_LOGIN_PASS) != 0xFFFF) {
+ /* send username */
+ next_state = smtp_prepare_auth_login_pass(s, &tx_buf_len);
+ }
+ }
+ break;
+#endif /* SMTP_SUPPORT_AUTH_LOGIN */
+ case(SMTP_MAIL):
+ /* wait for 250 */
+ if (response_code == 250) {
+ /* send RCPT */
+ next_state = smtp_prepare_rcpt(s, &tx_buf_len);
+ }
+ break;
+ case(SMTP_RCPT):
+ /* wait for 250 */
+ if (response_code == 250) {
+ /* send DATA */
+ SMEMCPY(s->tx_buf, SMTP_CMD_DATA, SMTP_CMD_DATA_LEN);
+ tx_buf_len = SMTP_CMD_DATA_LEN;
+ next_state = SMTP_DATA;
+ }
+ break;
+ case(SMTP_DATA):
+ /* wait for 354 */
+ if (response_code == 354) {
+ /* send email header */
+ next_state = smtp_prepare_header(s, &tx_buf_len);
+ }
+ break;
+ case(SMTP_BODY):
+ /* nothing to be done here, handled somewhere else */
+ break;
+ case(SMTP_QUIT):
+ /* wait for 250 */
+ if (response_code == 250) {
+ /* send QUIT */
+ next_state = smtp_prepare_quit(s, &tx_buf_len);
+ }
+ break;
+ case(SMTP_CLOSED):
+ /* nothing to do, wait for connection closed from server */
+ return;
+ default:
+ LWIP_DEBUGF(SMTP_DEBUG_SERIOUS, ("Invalid state: %d/%s\n", (int)s->state,
+ smtp_state_str[s->state]));
+ break;
+ }
+ if (s->state == next_state) {
+ LWIP_DEBUGF(SMTP_DEBUG_WARN_STATE, ("smtp_process[%s]: unexpected response_code, closing: %d (%s)\n",
+ smtp_state_str[s->state], response_code, smtp_pbuf_str(s->p)));
+ /* close connection */
+ smtp_close(s, pcb, SMTP_RESULT_ERR_SVR_RESP, response_code, ERR_OK);
+ return;
+ }
+ if (tx_buf_len > 0) {
+ SMTP_TX_BUF_MAX(tx_buf_len);
+ if (altcp_write(pcb, s->tx_buf, tx_buf_len, TCP_WRITE_FLAG_COPY) == ERR_OK) {
+ LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process[%s]: received command %d (%s)\n",
+ smtp_state_str[s->state], response_code, smtp_pbuf_str(s->p)));
+ LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_process[%s]: sent %"U16_F" bytes: \"%s\"\n",
+ smtp_state_str[s->state], tx_buf_len, s->tx_buf));
+ s->timer = SMTP_TIMEOUT;
+ pbuf_free(s->p);
+ s->p = NULL;
+ LWIP_DEBUGF(SMTP_DEBUG_STATE, ("smtp_process: changing state from %s to %s\n",
+ smtp_state_str[s->state], smtp_state_str[next_state]));
+ s->state = next_state;
+ if (next_state == SMTP_BODY) {
+ /* try to stream-send body data right now */
+ smtp_send_body(s, pcb);
+ } else if (next_state == SMTP_CLOSED) {
+ /* sent out all data, delete structure */
+ altcp_arg(pcb, NULL);
+ smtp_free(s, SMTP_RESULT_OK, 0, ERR_OK);
+ }
+ }
+ }
+}
+
+#if SMTP_BODYDH
+/** Elementary sub-function to send data
+ *
+ * @returns: BDHALLDATASENT all data has been written
+ * BDHSOMEDATASENT some data has been written
+ * 0 no data has been written
+ */
+static int
+smtp_send_bodyh_data(struct altcp_pcb *pcb, const char **from, u16_t *howmany)
+{
+ err_t err;
+ u16_t len = *howmany;
+
+ len = (u16_t)LWIP_MIN(len, altcp_sndbuf(pcb));
+ err = altcp_write(pcb, *from, len, TCP_WRITE_FLAG_COPY);
+ if (err == ERR_OK) {
+ *from += len;
+ if ((*howmany -= len) > 0) {
+ return BDHSOMEDATASENT;
+ }
+ return BDHALLDATASENT;
+ }
+ return 0;
+}
+
+/** Same as smtp_send_mail_static, but uses a callback function to send body data
+ */
+err_t
+smtp_send_mail_bodycback(const char *from, const char* to, const char* subject,
+ smtp_bodycback_fn bodycback_fn, smtp_result_fn callback_fn, void* callback_arg)
+{
+ struct smtp_session* s;
+ size_t len;
+
+ LWIP_ASSERT_CORE_LOCKED();
+
+ s = (struct smtp_session*)SMTP_STATE_MALLOC(sizeof(struct smtp_session));
+ if (s == NULL) {
+ return ERR_MEM;
+ }
+ memset(s, 0, sizeof(struct smtp_session));
+ s->bodydh = (struct smtp_bodydh_state*)SMTP_BODYDH_MALLOC(sizeof(struct smtp_bodydh_state));
+ if (s->bodydh == NULL) {
+ SMTP_STATE_FREE(s);
+ return ERR_MEM;
+ }
+ memset(s->bodydh, 0, sizeof(struct smtp_bodydh_state));
+ /* initialize the structure */
+ s->from = from;
+ len = strlen(from);
+ LWIP_ASSERT("string is too long", len <= 0xffff);
+ s->from_len = (u16_t)len;
+ s->to = to;
+ len = strlen(to);
+ LWIP_ASSERT("string is too long", len <= 0xffff);
+ s->to_len = (u16_t)len;
+ s->subject = subject;
+ len = strlen(subject);
+ LWIP_ASSERT("string is too long", len <= 0xffff);
+ s->subject_len = (u16_t)len;
+ s->body = NULL;
+ s->callback_fn = callback_fn;
+ s->callback_arg = callback_arg;
+ s->bodydh->callback_fn = bodycback_fn;
+ s->bodydh->state = BDH_SENDING;
+ /* call the actual implementation of this function */
+ return smtp_send_mail_alloced(s);
+}
+
+static void
+smtp_send_body_data_handler(struct smtp_session *s, struct altcp_pcb *pcb)
+{
+ struct smtp_bodydh_state *bdh;
+ int res = 0, ret;
+ LWIP_ASSERT("s != NULL", s != NULL);
+ bdh = s->bodydh;
+ LWIP_ASSERT("bodydh != NULL", bdh != NULL);
+
+ /* resume any leftovers from prior memory constraints */
+ if (s->body_len) {
+ LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: resume\n"));
+ if((res = smtp_send_bodyh_data(pcb, (const char **)&s->body, &s->body_len))
+ != BDHALLDATASENT) {
+ s->body_sent = s->body_len - 1;
+ return;
+ }
+ }
+ ret = res;
+ /* all data on buffer has been queued, resume execution */
+ if (bdh->state == BDH_SENDING) {
+ LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: run\n"));
+ do {
+ ret |= res; /* remember if we once queued something to send */
+ bdh->exposed.length = 0;
+ if (bdh->callback_fn(s->callback_arg, &bdh->exposed) == BDH_DONE) {
+ bdh->state = BDH_STOP;
+ }
+ s->body = bdh->exposed.buffer;
+ s->body_len = bdh->exposed.length;
+ LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: trying to send %u bytes\n", (unsigned int)s->body_len));
+ } while (s->body_len &&
+ ((res = smtp_send_bodyh_data(pcb, (const char **)&s->body, &s->body_len)) == BDHALLDATASENT)
+ && (bdh->state != BDH_STOP));
+ }
+ if ((bdh->state != BDH_SENDING) && (ret != BDHSOMEDATASENT)) {
+ LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: stop\n"));
+ s->body_sent = s->body_len;
+ } else {
+ LWIP_DEBUGF(SMTP_DEBUG_TRACE, ("smtp_send_body_data_handler: pause\n"));
+ s->body_sent = s->body_len - 1;
+ }
+}
+#endif /* SMTP_BODYDH */
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API */
diff --git a/src/apps/snmp/snmp_asn1.c b/src/apps/snmp/snmp_asn1.c
new file mode 100644
index 00000000000..6df34dbd605
--- /dev/null
+++ b/src/apps/snmp/snmp_asn1.c
@@ -0,0 +1,704 @@
+/**
+ * @file
+ * Abstract Syntax Notation One (ISO 8824, 8825) encoding
+ *
+ * @todo not optimised (yet), favor correctness over speed, favor speed over size
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Christiaan Simons <[email protected]>
+ * Martin Hentschel <[email protected]>
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "snmp_asn1.h"
+
+#define PBUF_OP_EXEC(code) \
+ if ((code) != ERR_OK) { \
+ return ERR_BUF; \
+ }
+
+/**
+ * Encodes a TLV into a pbuf stream.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param tlv TLV to encode
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) encode
+ */
+err_t
+snmp_ans1_enc_tlv(struct snmp_pbuf_stream *pbuf_stream, struct snmp_asn1_tlv *tlv)
+{
+ u8_t data;
+ u8_t length_bytes_required;
+
+ /* write type */
+ if ((tlv->type & SNMP_ASN1_DATATYPE_MASK) == SNMP_ASN1_DATATYPE_EXTENDED) {
+ /* extended format is not used by SNMP so we do not accept those values */
+ return ERR_ARG;
+ }
+ if (tlv->type_len != 0) {
+ /* any other value as auto is not accepted for type (we always use one byte because extended syntax is prohibited) */
+ return ERR_ARG;
+ }
+
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, tlv->type));
+ tlv->type_len = 1;
+
+ /* write length */
+ if (tlv->value_len <= 127) {
+ length_bytes_required = 1;
+ } else if (tlv->value_len <= 255) {
+ length_bytes_required = 2;
+ } else {
+ length_bytes_required = 3;
+ }
+
+ /* check for forced min length */
+ if (tlv->length_len > 0) {
+ if (tlv->length_len < length_bytes_required) {
+ /* unable to code requested length in requested number of bytes */
+ return ERR_ARG;
+ }
+
+ length_bytes_required = tlv->length_len;
+ } else {
+ tlv->length_len = length_bytes_required;
+ }
+
+ if (length_bytes_required > 1) {
+ /* multi byte representation required */
+ length_bytes_required--;
+ data = 0x80 | length_bytes_required; /* extended length definition, 1 length byte follows */
+
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, data));
+
+ while (length_bytes_required > 1) {
+ if (length_bytes_required == 2) {
+ /* append high byte */
+ data = (u8_t)(tlv->value_len >> 8);
+ } else {
+ /* append leading 0x00 */
+ data = 0x00;
+ }
+
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, data));
+ length_bytes_required--;
+ }
+ }
+
+ /* append low byte */
+ data = (u8_t)(tlv->value_len & 0xFF);
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, data));
+
+ return ERR_OK;
+}
+
+/**
+ * Encodes raw data (octet string, opaque) into a pbuf chained ASN1 msg.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param raw_len raw data length
+ * @param raw points raw data
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) encode
+ */
+err_t
+snmp_asn1_enc_raw(struct snmp_pbuf_stream *pbuf_stream, const u8_t *raw, u16_t raw_len)
+{
+ PBUF_OP_EXEC(snmp_pbuf_stream_writebuf(pbuf_stream, raw, raw_len));
+
+ return ERR_OK;
+}
+
+/**
+ * Encodes u32_t (counter, gauge, timeticks) into a pbuf chained ASN1 msg.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param octets_needed encoding length (from snmp_asn1_enc_u32t_cnt())
+ * @param value is the host order u32_t value to be encoded
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) encode
+ *
+ * @see snmp_asn1_enc_u32t_cnt()
+ */
+err_t
+snmp_asn1_enc_u32t(struct snmp_pbuf_stream *pbuf_stream, u16_t octets_needed, u32_t value)
+{
+ if (octets_needed > 5) {
+ return ERR_ARG;
+ }
+ if (octets_needed == 5) {
+ /* not enough bits in 'value' add leading 0x00 */
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, 0x00));
+ octets_needed--;
+ }
+
+ while (octets_needed > 1) {
+ octets_needed--;
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)(value >> (octets_needed << 3))));
+ }
+
+ /* (only) one least significant octet */
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)value));
+
+ return ERR_OK;
+}
+/**
+ * Encodes s32_t integer into a pbuf chained ASN1 msg.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param octets_needed encoding length (from snmp_asn1_enc_s32t_cnt())
+ * @param value is the host order s32_t value to be encoded
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) encode
+ *
+ * @see snmp_asn1_enc_s32t_cnt()
+ */
+err_t
+snmp_asn1_enc_s32t(struct snmp_pbuf_stream *pbuf_stream, u16_t octets_needed, s32_t value)
+{
+ while (octets_needed > 1) {
+ octets_needed--;
+
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)(value >> (octets_needed << 3))));
+ }
+
+ /* (only) one least significant octet */
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)value));
+
+ return ERR_OK;
+}
+
+/**
+ * Encodes object identifier into a pbuf chained ASN1 msg.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param oid points to object identifier array
+ * @param oid_len object identifier array length
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) encode
+ */
+err_t
+snmp_asn1_enc_oid(struct snmp_pbuf_stream *pbuf_stream, const u32_t *oid, u16_t oid_len)
+{
+ if (oid_len > 1) {
+ /* write compressed first two sub id's */
+ u32_t compressed_byte = ((oid[0] * 40) + oid[1]);
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)compressed_byte));
+ oid_len -= 2;
+ oid += 2;
+ } else {
+ /* @bug: allow empty varbinds for symmetry (we must decode them for getnext), allow partial compression?? */
+ /* ident_len <= 1, at least we need zeroDotZero (0.0) (ident_len == 2) */
+ return ERR_ARG;
+ }
+
+ while (oid_len > 0) {
+ u32_t sub_id;
+ u8_t shift, tail;
+
+ oid_len--;
+ sub_id = *oid;
+ tail = 0;
+ shift = 28;
+ while (shift > 0) {
+ u8_t code;
+
+ code = (u8_t)(sub_id >> shift);
+ if ((code != 0) || (tail != 0)) {
+ tail = 1;
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, code | 0x80));
+ }
+ shift -= 7;
+ }
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)sub_id & 0x7F));
+
+ /* proceed to next sub-identifier */
+ oid++;
+ }
+ return ERR_OK;
+}
+
+/**
+ * Returns octet count for length.
+ *
+ * @param length parameter length
+ * @param octets_needed points to the return value
+ */
+void
+snmp_asn1_enc_length_cnt(u16_t length, u8_t *octets_needed)
+{
+ if (length < 0x80U) {
+ *octets_needed = 1;
+ } else if (length < 0x100U) {
+ *octets_needed = 2;
+ } else {
+ *octets_needed = 3;
+ }
+}
+
+/**
+ * Returns octet count for an u32_t.
+ *
+ * @param value value to be encoded
+ * @param octets_needed points to the return value
+ *
+ * @note ASN coded integers are _always_ signed. E.g. +0xFFFF is coded
+ * as 0x00,0xFF,0xFF. Note the leading sign octet. A positive value
+ * of 0xFFFFFFFF is preceded with 0x00 and the length is 5 octets!!
+ */
+void
+snmp_asn1_enc_u32t_cnt(u32_t value, u16_t *octets_needed)
+{
+ if (value < 0x80UL) {
+ *octets_needed = 1;
+ } else if (value < 0x8000UL) {
+ *octets_needed = 2;
+ } else if (value < 0x800000UL) {
+ *octets_needed = 3;
+ } else if (value < 0x80000000UL) {
+ *octets_needed = 4;
+ } else {
+ *octets_needed = 5;
+ }
+}
+
+/**
+ * Returns octet count for an s32_t.
+ *
+ * @param value value to be encoded
+ * @param octets_needed points to the return value
+ *
+ * @note ASN coded integers are _always_ signed.
+ */
+void
+snmp_asn1_enc_s32t_cnt(s32_t value, u16_t *octets_needed)
+{
+ if (value < 0) {
+ value = ~value;
+ }
+ if (value < 0x80L) {
+ *octets_needed = 1;
+ } else if (value < 0x8000L) {
+ *octets_needed = 2;
+ } else if (value < 0x800000L) {
+ *octets_needed = 3;
+ } else {
+ *octets_needed = 4;
+ }
+}
+
+/**
+ * Returns octet count for an object identifier.
+ *
+ * @param oid points to object identifier array
+ * @param oid_len object identifier array length
+ * @param octets_needed points to the return value
+ */
+void
+snmp_asn1_enc_oid_cnt(const u32_t *oid, u16_t oid_len, u16_t *octets_needed)
+{
+ u32_t sub_id;
+
+ *octets_needed = 0;
+ if (oid_len > 1) {
+ /* compressed prefix in one octet */
+ (*octets_needed)++;
+ oid_len -= 2;
+ oid += 2;
+ }
+ while (oid_len > 0) {
+ oid_len--;
+ sub_id = *oid;
+
+ sub_id >>= 7;
+ (*octets_needed)++;
+ while (sub_id > 0) {
+ sub_id >>= 7;
+ (*octets_needed)++;
+ }
+ oid++;
+ }
+}
+
+/**
+ * Decodes a TLV from a pbuf stream.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param tlv returns decoded TLV
+ * @return ERR_OK if successful, ERR_VAL if we can't decode
+ */
+err_t
+snmp_asn1_dec_tlv(struct snmp_pbuf_stream *pbuf_stream, struct snmp_asn1_tlv *tlv)
+{
+ u8_t data;
+
+ /* decode type first */
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+ tlv->type = data;
+
+ if ((tlv->type & SNMP_ASN1_DATATYPE_MASK) == SNMP_ASN1_DATATYPE_EXTENDED) {
+ /* extended format is not used by SNMP so we do not accept those values */
+ return ERR_VAL;
+ }
+ tlv->type_len = 1;
+
+ /* now, decode length */
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+
+ if (data < 0x80) { /* short form */
+ tlv->length_len = 1;
+ tlv->value_len = data;
+ } else if (data > 0x80) { /* long form */
+ u8_t length_bytes = data - 0x80;
+ if (length_bytes > pbuf_stream->length) {
+ return ERR_VAL;
+ }
+ tlv->length_len = length_bytes + 1; /* this byte + defined number of length bytes following */
+ tlv->value_len = 0;
+
+ while (length_bytes > 0) {
+ /* we only support up to u16.maxvalue-1 (2 bytes) but have to accept leading zero bytes */
+ if (tlv->value_len > 0xFF) {
+ return ERR_VAL;
+ }
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+ tlv->value_len <<= 8;
+ tlv->value_len |= data;
+
+ /* take care for special value used for indefinite length */
+ if (tlv->value_len == 0xFFFF) {
+ return ERR_VAL;
+ }
+
+ length_bytes--;
+ }
+ } else { /* data == 0x80 indefinite length form */
+ /* (not allowed for SNMP; RFC 1157, 3.2.2) */
+ return ERR_VAL;
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * Decodes positive integer (counter, gauge, timeticks) into u32_t.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param len length of the coded integer field
+ * @param value return host order integer
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
+ *
+ * @note ASN coded integers are _always_ signed. E.g. +0xFFFF is coded
+ * as 0x00,0xFF,0xFF. Note the leading sign octet. A positive value
+ * of 0xFFFFFFFF is preceded with 0x00 and the length is 5 octets!!
+ */
+err_t
+snmp_asn1_dec_u32t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u32_t *value)
+{
+ u8_t data;
+
+ if ((len > 0) && (len <= 5)) {
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+
+ /* expecting sign bit to be zero, only unsigned please! */
+ if (((len == 5) && (data == 0x00)) || ((len < 5) && ((data & 0x80) == 0))) {
+ *value = data;
+ len--;
+
+ while (len > 0) {
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+ len--;
+
+ *value <<= 8;
+ *value |= data;
+ }
+
+ return ERR_OK;
+ }
+ }
+
+ return ERR_VAL;
+}
+
+/**
+ * Decodes integer into s32_t.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param len length of the coded integer field
+ * @param value return host order integer
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
+ *
+ * @note ASN coded integers are _always_ signed!
+ */
+err_t
+snmp_asn1_dec_s32t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, s32_t *value)
+{
+ u8_t data;
+
+ if ((len > 0) && (len < 5)) {
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+
+ if (data & 0x80) {
+ /* negative, start from -1 */
+ *value = -1;
+ *value = (*value << 8) | data;
+ } else {
+ /* positive, start from 0 */
+ *value = data;
+ }
+ len--;
+ /* shift in the remaining value */
+ while (len > 0) {
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+ *value = (*value << 8) | data;
+ len--;
+ }
+ return ERR_OK;
+ }
+
+ return ERR_VAL;
+}
+
+/**
+ * Decodes object identifier from incoming message into array of u32_t.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param len length of the coded object identifier
+ * @param oid return decoded object identifier
+ * @param oid_len return decoded object identifier length
+ * @param oid_max_len size of oid buffer
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
+ */
+err_t
+snmp_asn1_dec_oid(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u32_t *oid, u8_t *oid_len, u8_t oid_max_len)
+{
+ u32_t *oid_ptr;
+ u8_t data;
+
+ *oid_len = 0;
+ oid_ptr = oid;
+ if (len > 0) {
+ if (oid_max_len < 2) {
+ return ERR_MEM;
+ }
+
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+ len--;
+
+ /* first compressed octet */
+ if (data == 0x2B) {
+ /* (most) common case 1.3 (iso.org) */
+ *oid_ptr = 1;
+ oid_ptr++;
+ *oid_ptr = 3;
+ oid_ptr++;
+ } else if (data < 40) {
+ *oid_ptr = 0;
+ oid_ptr++;
+ *oid_ptr = data;
+ oid_ptr++;
+ } else if (data < 80) {
+ *oid_ptr = 1;
+ oid_ptr++;
+ *oid_ptr = data - 40;
+ oid_ptr++;
+ } else {
+ *oid_ptr = 2;
+ oid_ptr++;
+ *oid_ptr = data - 80;
+ oid_ptr++;
+ }
+ *oid_len = 2;
+ } else {
+ /* accepting zero length identifiers e.g. for getnext operation. uncommon but valid */
+ return ERR_OK;
+ }
+
+ while ((len > 0) && (*oid_len < oid_max_len)) {
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+ len--;
+
+ if ((data & 0x80) == 0x00) {
+ /* sub-identifier uses single octet */
+ *oid_ptr = data;
+ } else {
+ /* sub-identifier uses multiple octets */
+ u32_t sub_id = (data & ~0x80);
+ while ((len > 0) && ((data & 0x80) != 0)) {
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+ len--;
+
+ sub_id = (sub_id << 7) + (data & ~0x80);
+ }
+
+ if ((data & 0x80) != 0) {
+ /* "more bytes following" bit still set at end of len */
+ return ERR_VAL;
+ }
+ *oid_ptr = sub_id;
+ }
+ oid_ptr++;
+ (*oid_len)++;
+ }
+
+ if (len > 0) {
+ /* OID to long to fit in our buffer */
+ return ERR_MEM;
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * Decodes (copies) raw data (ip-addresses, octet strings, opaque encoding)
+ * from incoming message into array.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param len length of the coded raw data (zero is valid, e.g. empty string!)
+ * @param buf return raw bytes
+ * @param buf_len returns length of the raw return value
+ * @param buf_max_len buffer size
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
+ */
+err_t
+snmp_asn1_dec_raw(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u8_t *buf, u16_t *buf_len, u16_t buf_max_len)
+{
+ if (len > buf_max_len) {
+ /* not enough dst space */
+ return ERR_MEM;
+ }
+ *buf_len = len;
+
+ while (len > 0) {
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, buf));
+ buf++;
+ len--;
+ }
+
+ return ERR_OK;
+}
+
+#if LWIP_HAVE_INT64
+/**
+ * Returns octet count for an u64_t.
+ *
+ * @param value value to be encoded
+ * @param octets_needed points to the return value
+ *
+ * @note ASN coded integers are _always_ signed. E.g. +0xFFFF is coded
+ * as 0x00,0xFF,0xFF. Note the leading sign octet. A positive value
+ * of 0xFFFFFFFFFFFFFFFF is preceded with 0x00 and the length is 9 octets!!
+ */
+void
+snmp_asn1_enc_u64t_cnt(u64_t value, u16_t *octets_needed)
+{
+ /* check if high u32 is 0 */
+ if ((value >> 32) == 0) {
+ /* only low u32 is important */
+ snmp_asn1_enc_u32t_cnt((u32_t)value, octets_needed);
+ } else {
+ /* low u32 does not matter for length determination */
+ snmp_asn1_enc_u32t_cnt((u32_t)(value >> 32), octets_needed);
+ *octets_needed = *octets_needed + 4; /* add the 4 bytes of low u32 */
+ }
+}
+
+/**
+ * Decodes large positive integer (counter64) into 2x u32_t.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param len length of the coded integer field
+ * @param value return 64 bit integer
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
+ *
+ * @note ASN coded integers are _always_ signed. E.g. +0xFFFF is coded
+ * as 0x00,0xFF,0xFF. Note the leading sign octet. A positive value
+ * of 0xFFFFFFFFFFFFFFFF is preceded with 0x00 and the length is 9 octets!!
+ */
+err_t
+snmp_asn1_dec_u64t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u64_t *value)
+{
+ u8_t data;
+
+ if ((len > 0) && (len <= 9)) {
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+
+ /* expecting sign bit to be zero, only unsigned please! */
+ if (((len == 9) && (data == 0x00)) || ((len < 9) && ((data & 0x80) == 0))) {
+ *value = data;
+ len--;
+
+ while (len > 0) {
+ PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, &data));
+ *value <<= 8;
+ *value |= data;
+ len--;
+ }
+
+ return ERR_OK;
+ }
+ }
+
+ return ERR_VAL;
+}
+
+/**
+ * Encodes u64_t (counter64) into a pbuf chained ASN1 msg.
+ *
+ * @param pbuf_stream points to a pbuf stream
+ * @param octets_needed encoding length (from snmp_asn1_enc_u32t_cnt())
+ * @param value is the value to be encoded
+ * @return ERR_OK if successful, ERR_ARG if we can't (or won't) encode
+ *
+ * @see snmp_asn1_enc_u64t_cnt()
+ */
+err_t
+snmp_asn1_enc_u64t(struct snmp_pbuf_stream *pbuf_stream, u16_t octets_needed, u64_t value)
+{
+ if (octets_needed > 9) {
+ return ERR_ARG;
+ }
+ if (octets_needed == 9) {
+ /* not enough bits in 'value' add leading 0x00 */
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, 0x00));
+ octets_needed--;
+ }
+
+ while (octets_needed > 1) {
+ octets_needed--;
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)(value >> (octets_needed << 3))));
+ }
+
+ /* always write at least one octet (also in case of value == 0) */
+ PBUF_OP_EXEC(snmp_pbuf_stream_write(pbuf_stream, (u8_t)(value)));
+
+ return ERR_OK;
+}
+#endif
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_asn1.h b/src/apps/snmp/snmp_asn1.h
new file mode 100644
index 00000000000..87c0973fdf6
--- /dev/null
+++ b/src/apps/snmp/snmp_asn1.h
@@ -0,0 +1,113 @@
+/**
+ * @file
+ * Abstract Syntax Notation One (ISO 8824, 8825) codec.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * Copyright (c) 2016 Elias Oenal.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Christiaan Simons <[email protected]>
+ * Martin Hentschel <[email protected]>
+ * Elias Oenal <[email protected]>
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_ASN1_H
+#define LWIP_HDR_APPS_SNMP_ASN1_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP
+
+#include "lwip/err.h"
+#include "lwip/apps/snmp_core.h"
+#include "snmp_pbuf_stream.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SNMP_ASN1_TLV_INDEFINITE_LENGTH 0x80
+
+#define SNMP_ASN1_CLASS_MASK 0xC0
+#define SNMP_ASN1_CONTENTTYPE_MASK 0x20
+#define SNMP_ASN1_DATATYPE_MASK 0x1F
+#define SNMP_ASN1_DATATYPE_EXTENDED 0x1F /* DataType indicating that datatype is encoded in following bytes */
+
+/* context specific (SNMP) tags (from SNMP spec. RFC1157 and RFC1905) */
+#define SNMP_ASN1_CONTEXT_PDU_GET_REQ 0
+#define SNMP_ASN1_CONTEXT_PDU_GET_NEXT_REQ 1
+#define SNMP_ASN1_CONTEXT_PDU_GET_RESP 2
+#define SNMP_ASN1_CONTEXT_PDU_SET_REQ 3
+#define SNMP_ASN1_CONTEXT_PDU_TRAP 4
+#define SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ 5
+#define SNMP_ASN1_CONTEXT_PDU_INFORM_REQ 6
+#define SNMP_ASN1_CONTEXT_PDU_V2_TRAP 7
+#define SNMP_ASN1_CONTEXT_PDU_REPORT 8
+
+#define SNMP_ASN1_CONTEXT_VARBIND_NO_SUCH_OBJECT 0
+#define SNMP_ASN1_CONTEXT_VARBIND_END_OF_MIB_VIEW 2
+
+struct snmp_asn1_tlv {
+ u8_t type; /* only U8 because extended types are not specified by SNMP */
+ u8_t type_len; /* encoded length of 'type' field (normally 1) */
+ u8_t length_len; /* indicates how many bytes are required to encode the 'value_len' field */
+ u16_t value_len; /* encoded length of the value */
+};
+#define SNMP_ASN1_TLV_HDR_LENGTH(tlv) ((tlv).type_len + (tlv).length_len)
+#define SNMP_ASN1_TLV_LENGTH(tlv) ((tlv).type_len + (tlv).length_len + (tlv).value_len)
+#define SNMP_ASN1_SET_TLV_PARAMS(tlv, type_, length_len_, value_len_) do { (tlv).type = (type_); (tlv).type_len = 0; (tlv).length_len = (length_len_); (tlv).value_len = (value_len_); } while (0);
+
+err_t snmp_asn1_dec_tlv(struct snmp_pbuf_stream *pbuf_stream, struct snmp_asn1_tlv *tlv);
+err_t snmp_asn1_dec_u32t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u32_t *value);
+err_t snmp_asn1_dec_s32t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, s32_t *value);
+err_t snmp_asn1_dec_oid(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u32_t *oid, u8_t *oid_len, u8_t oid_max_len);
+err_t snmp_asn1_dec_raw(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u8_t *buf, u16_t *buf_len, u16_t buf_max_len);
+
+err_t snmp_ans1_enc_tlv(struct snmp_pbuf_stream *pbuf_stream, struct snmp_asn1_tlv *tlv);
+
+void snmp_asn1_enc_length_cnt(u16_t length, u8_t *octets_needed);
+void snmp_asn1_enc_u32t_cnt(u32_t value, u16_t *octets_needed);
+void snmp_asn1_enc_s32t_cnt(s32_t value, u16_t *octets_needed);
+void snmp_asn1_enc_oid_cnt(const u32_t *oid, u16_t oid_len, u16_t *octets_needed);
+err_t snmp_asn1_enc_oid(struct snmp_pbuf_stream *pbuf_stream, const u32_t *oid, u16_t oid_len);
+err_t snmp_asn1_enc_s32t(struct snmp_pbuf_stream *pbuf_stream, u16_t octets_needed, s32_t value);
+err_t snmp_asn1_enc_u32t(struct snmp_pbuf_stream *pbuf_stream, u16_t octets_needed, u32_t value);
+err_t snmp_asn1_enc_raw(struct snmp_pbuf_stream *pbuf_stream, const u8_t *raw, u16_t raw_len);
+
+#if LWIP_HAVE_INT64
+err_t snmp_asn1_dec_u64t(struct snmp_pbuf_stream *pbuf_stream, u16_t len, u64_t *value);
+void snmp_asn1_enc_u64t_cnt(u64_t value, u16_t *octets_needed);
+err_t snmp_asn1_enc_u64t(struct snmp_pbuf_stream *pbuf_stream, u16_t octets_needed, u64_t value);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* LWIP_HDR_APPS_SNMP_ASN1_H */
diff --git a/src/apps/snmp/snmp_core.c b/src/apps/snmp/snmp_core.c
new file mode 100644
index 00000000000..04e0c317c7e
--- /dev/null
+++ b/src/apps/snmp/snmp_core.c
@@ -0,0 +1,1356 @@
+/**
+ * @file
+ * MIB tree access/construction functions.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Christiaan Simons <[email protected]>
+ * Martin Hentschel <[email protected]>
+*/
+
+/**
+ * @defgroup snmp SNMPv2c/v3 agent
+ * @ingroup apps
+ * SNMPv2c and SNMPv3 compatible agent<br>
+ * There is also a MIB compiler and a MIB viewer in lwIP/contrib subdir
+ * (lwip/contrib/apps/LwipMibCompiler).<br>
+ * The agent implements the most important MIB2 MIBs including IPv6 support
+ * (interfaces, UDP, TCP, SNMP, ICMP, SYSTEM). IP MIB is an older version
+ * without IPv6 statistics (TODO).<br>
+ * Rewritten by Martin Hentschel <[email protected]> and
+ * Dirk Ziegelmeier <[email protected]>
+ *
+ * 0 Agent Capabilities
+ * ====================
+ *
+ * Features:
+ * ---------
+ * - SNMPv2c support.
+ * - SNMPv3 support (a port to ARM mbedtls is provided, LWIP_SNMP_V3_MBEDTLS option).
+ * - Low RAM usage - no memory pools, stack only.
+ * - MIB2 implementation is separated from SNMP stack.
+ * - Support for multiple MIBs (snmp_set_mibs() call) - e.g. for private MIB.
+ * - Simple and generic API for MIB implementation.
+ * - Comfortable node types and helper functions for scalar arrays and tables.
+ * - Counter64, bit and truthvalue datatype support.
+ * - Callbacks for SNMP writes e.g. to implement persistency.
+ * - Runs on two APIs: RAW and netconn.
+ * - Async API is gone - the stack now supports netconn API instead,
+ * so blocking operations can be done in MIB calls.
+ * SNMP runs in a worker thread when netconn API is used.
+ * - Simplified thread sync support for MIBs - useful when MIBs
+ * need to access variables shared with other threads where no locking is
+ * possible. Used in MIB2 to access lwIP stats from lwIP thread.
+ *
+ * MIB compiler (code generator):
+ * ------------------------------
+ * - Provided in contrib dir.
+ * - Written in C#. MIB viewer using Windows Forms.
+ * - Developed on Windows with Visual Studio 2010.
+ * - Can be compiled and used on all platforms with http://www.monodevelop.com/.
+ * - Based on a heavily modified version of of SharpSnmpLib (a4bd05c6afb4)
+ * (https://sharpsnmplib.codeplex.com/SourceControl/network/forks/Nemo157/MIBParserUpdate).
+ * This has been the last known revision of that code before being converted to
+ * closed source. The new code on github has completely changed, so we can not
+ * just update :-(
+ * - MIB parser, C file generation framework and LWIP code generation are cleanly
+ * separated, which means the code may be useful as a base for code generation
+ * of other SNMP agents.
+ *
+ * Notes:
+ * ------
+ * - Stack and MIB compiler were used to implement a Profinet device.
+ * Compiled/implemented MIBs: LLDP-MIB, LLDP-EXT-DOT3-MIB, LLDP-EXT-PNO-MIB.
+ *
+ * SNMPv1 per RFC1157 and SNMPv2c per RFC 3416
+ * -------------------------------------------
+ * Note the S in SNMP stands for "Simple". Note that "Simple" is
+ * relative. SNMP is simple compared to the complex ISO network
+ * management protocols CMIP (Common Management Information Protocol)
+ * and CMOT (CMip Over Tcp).
+ *
+ * SNMPv3
+ * ------
+ * When SNMPv3 is used, several functions from snmpv3.h must be implemented
+ * by the user. This is mainly user management and persistence handling.
+ * The sample provided in lwip-contrib is insecure, don't use it in production
+ * systems, especially the missing persistence for engine boots variable
+ * simplifies replay attacks.
+ *
+ * MIB II
+ * ------
+ * The standard lwIP stack management information base.
+ * This is a required MIB, so this is always enabled.
+ * The groups EGP, CMOT and transmission are disabled by default.
+ *
+ * Most mib-2 objects are not writable except:
+ * sysName, sysLocation, sysContact, snmpEnableAuthenTraps.
+ * Writing to or changing the ARP and IP address and route
+ * tables is not possible.
+ *
+ * Note lwIP has a very limited notion of IP routing. It currently
+ * doesn't have a route table and doesn't have a notion of the U,G,H flags.
+ * Instead lwIP uses the interface list with only one default interface
+ * acting as a single gateway interface (G) for the default route.
+ *
+ * The agent returns a "virtual table" with the default route 0.0.0.0
+ * for the default interface and network routes (no H) for each
+ * network interface in the netif_list.
+ * All routes are considered to be up (U).
+ *
+ * Loading additional MIBs
+ * -----------------------
+ * MIBs can only be added in compile-time, not in run-time.
+ *
+ *
+ * 1 Building the Agent
+ * ====================
+ * First of all you'll need to add the following define
+ * to your local lwipopts.h:
+ * \#define LWIP_SNMP 1
+ *
+ * and add the source files your makefile.
+ *
+ * Note you'll might need to adapt you network driver to update
+ * the mib2 variables for your interface.
+ *
+ * 2 Running the Agent
+ * ===================
+ * The following function calls must be made in your program to
+ * actually get the SNMP agent running.
+ *
+ * Before starting the agent you should supply pointers
+ * for sysContact, sysLocation, and snmpEnableAuthenTraps.
+ * You can do this by calling
+ *
+ * - snmp_mib2_set_syscontact()
+ * - snmp_mib2_set_syslocation()
+ * - snmp_set_auth_traps_enabled()
+ *
+ * You can register a callback which is called on successful write access:
+ * snmp_set_write_callback().
+ *
+ * Additionally you may want to set
+ *
+ * - snmp_mib2_set_sysdescr()
+ * - snmp_set_device_enterprise_oid()
+ * - snmp_mib2_set_sysname()
+ *
+ * Also before starting the agent you need to setup
+ * one or more trap destinations using these calls:
+ *
+ * - snmp_trap_dst_enable()
+ * - snmp_trap_dst_ip_set()
+ *
+ * If you need more than MIB2, set the MIBs you want to use
+ * by snmp_set_mibs().
+ *
+ * Finally, enable the agent by calling snmp_init()
+ *
+ * @defgroup snmp_core Core
+ * @ingroup snmp
+ *
+ * @defgroup snmp_traps Traps
+ * @ingroup snmp
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "snmp_core_priv.h"
+#include "lwip/netif.h"
+#include <string.h>
+
+
+#if (LWIP_SNMP && (SNMP_TRAP_DESTINATIONS<=0))
+#error "If you want to use SNMP, you have to define SNMP_TRAP_DESTINATIONS>=1 in your lwipopts.h"
+#endif
+#if (!LWIP_UDP && LWIP_SNMP)
+#error "If you want to use SNMP, you have to define LWIP_UDP=1 in your lwipopts.h"
+#endif
+#if SNMP_MAX_OBJ_ID_LEN > 255
+#error "SNMP_MAX_OBJ_ID_LEN must fit into an u8_t"
+#endif
+
+struct snmp_statistics snmp_stats;
+static const struct snmp_obj_id snmp_device_enterprise_oid_default = {SNMP_DEVICE_ENTERPRISE_OID_LEN, SNMP_DEVICE_ENTERPRISE_OID};
+static const struct snmp_obj_id *snmp_device_enterprise_oid = &snmp_device_enterprise_oid_default;
+
+const u32_t snmp_zero_dot_zero_values[] = { 0, 0 };
+const struct snmp_obj_id_const_ref snmp_zero_dot_zero = { LWIP_ARRAYSIZE(snmp_zero_dot_zero_values), snmp_zero_dot_zero_values };
+
+#if SNMP_LWIP_MIB2 && LWIP_SNMP_V3
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_snmpv2_framework.h"
+#include "lwip/apps/snmp_snmpv2_usm.h"
+static const struct snmp_mib *const default_mibs[] = { &mib2, &snmpframeworkmib, &snmpusmmib };
+static u8_t snmp_num_mibs = LWIP_ARRAYSIZE(default_mibs);
+#elif SNMP_LWIP_MIB2
+#include "lwip/apps/snmp_mib2.h"
+static const struct snmp_mib *const default_mibs[] = { &mib2 };
+static u8_t snmp_num_mibs = LWIP_ARRAYSIZE(default_mibs);
+#else
+static const struct snmp_mib *const default_mibs[] = { NULL };
+static u8_t snmp_num_mibs = 0;
+#endif
+
+/* List of known mibs */
+static struct snmp_mib const *const *snmp_mibs = default_mibs;
+
+/**
+ * @ingroup snmp_core
+ * Sets the MIBs to use.
+ * Example: call snmp_set_mibs() as follows:
+ * static const struct snmp_mib *my_snmp_mibs[] = {
+ * &mib2,
+ * &private_mib
+ * };
+ * snmp_set_mibs(my_snmp_mibs, LWIP_ARRAYSIZE(my_snmp_mibs));
+ */
+void
+snmp_set_mibs(const struct snmp_mib **mibs, u8_t num_mibs)
+{
+ LWIP_ASSERT_SNMP_LOCKED();
+ LWIP_ASSERT("mibs pointer must be != NULL", (mibs != NULL));
+ LWIP_ASSERT("num_mibs pointer must be != 0", (num_mibs != 0));
+ snmp_mibs = mibs;
+ snmp_num_mibs = num_mibs;
+}
+
+/**
+ * @ingroup snmp_core
+ * 'device enterprise oid' is used for 'device OID' field in trap PDU's (for identification of generating device)
+ * as well as for value returned by MIB-2 'sysObjectID' field (if internal MIB2 implementation is used).
+ * The 'device enterprise oid' shall point to an OID located under 'private-enterprises' branch (1.3.6.1.4.1.XXX). If a vendor
+ * wants to provide a custom object there, he has to get its own enterprise oid from IANA (http://www.iana.org). It
+ * is not allowed to use LWIP enterprise ID!
+ * In order to identify a specific device it is recommended to create a dedicated OID for each device type under its own
+ * enterprise oid.
+ * e.g.
+ * device a > 1.3.6.1.4.1.XXX(ent-oid).1(devices).1(device a)
+ * device b > 1.3.6.1.4.1.XXX(ent-oid).1(devices).2(device b)
+ * for more details see description of 'sysObjectID' field in RFC1213-MIB
+ */
+void snmp_set_device_enterprise_oid(const struct snmp_obj_id *device_enterprise_oid)
+{
+ LWIP_ASSERT_SNMP_LOCKED();
+ if (device_enterprise_oid == NULL) {
+ snmp_device_enterprise_oid = &snmp_device_enterprise_oid_default;
+ } else {
+ snmp_device_enterprise_oid = device_enterprise_oid;
+ }
+}
+
+/**
+ * @ingroup snmp_core
+ * Get 'device enterprise oid'
+ */
+const struct snmp_obj_id *snmp_get_device_enterprise_oid(void)
+{
+ LWIP_ASSERT_SNMP_LOCKED();
+ return snmp_device_enterprise_oid;
+}
+
+#if LWIP_IPV4
+/**
+ * Conversion from InetAddressIPv4 oid to lwIP ip4_addr
+ * @param oid points to u32_t ident[4] input
+ * @param ip points to output struct
+ */
+u8_t
+snmp_oid_to_ip4(const u32_t *oid, ip4_addr_t *ip)
+{
+ if ((oid[0] > 0xFF) ||
+ (oid[1] > 0xFF) ||
+ (oid[2] > 0xFF) ||
+ (oid[3] > 0xFF)) {
+ ip4_addr_copy(*ip, *IP4_ADDR_ANY4);
+ return 0;
+ }
+
+ IP4_ADDR(ip, oid[0], oid[1], oid[2], oid[3]);
+ return 1;
+}
+
+/**
+ * Convert ip4_addr to InetAddressIPv4 (no InetAddressType)
+ * @param ip points to input struct
+ * @param oid points to u32_t ident[4] output
+ */
+void
+snmp_ip4_to_oid(const ip4_addr_t *ip, u32_t *oid)
+{
+ oid[0] = ip4_addr1(ip);
+ oid[1] = ip4_addr2(ip);
+ oid[2] = ip4_addr3(ip);
+ oid[3] = ip4_addr4(ip);
+}
+#endif /* LWIP_IPV4 */
+
+#if LWIP_IPV6
+/**
+ * Conversion from InetAddressIPv6 oid to lwIP ip6_addr
+ * @param oid points to u32_t oid[16] input
+ * @param ip points to output struct
+ */
+u8_t
+snmp_oid_to_ip6(const u32_t *oid, ip6_addr_t *ip)
+{
+ if ((oid[0] > 0xFF) ||
+ (oid[1] > 0xFF) ||
+ (oid[2] > 0xFF) ||
+ (oid[3] > 0xFF) ||
+ (oid[4] > 0xFF) ||
+ (oid[5] > 0xFF) ||
+ (oid[6] > 0xFF) ||
+ (oid[7] > 0xFF) ||
+ (oid[8] > 0xFF) ||
+ (oid[9] > 0xFF) ||
+ (oid[10] > 0xFF) ||
+ (oid[11] > 0xFF) ||
+ (oid[12] > 0xFF) ||
+ (oid[13] > 0xFF) ||
+ (oid[14] > 0xFF) ||
+ (oid[15] > 0xFF)) {
+ ip6_addr_set_any(ip);
+ return 0;
+ }
+
+ ip->addr[0] = (oid[0] << 24) | (oid[1] << 16) | (oid[2] << 8) | (oid[3] << 0);
+ ip->addr[1] = (oid[4] << 24) | (oid[5] << 16) | (oid[6] << 8) | (oid[7] << 0);
+ ip->addr[2] = (oid[8] << 24) | (oid[9] << 16) | (oid[10] << 8) | (oid[11] << 0);
+ ip->addr[3] = (oid[12] << 24) | (oid[13] << 16) | (oid[14] << 8) | (oid[15] << 0);
+ return 1;
+}
+
+/**
+ * Convert ip6_addr to InetAddressIPv6 (no InetAddressType)
+ * @param ip points to input struct
+ * @param oid points to u32_t ident[16] output
+ */
+void
+snmp_ip6_to_oid(const ip6_addr_t *ip, u32_t *oid)
+{
+ oid[0] = (ip->addr[0] & 0xFF000000) >> 24;
+ oid[1] = (ip->addr[0] & 0x00FF0000) >> 16;
+ oid[2] = (ip->addr[0] & 0x0000FF00) >> 8;
+ oid[3] = (ip->addr[0] & 0x000000FF) >> 0;
+ oid[4] = (ip->addr[1] & 0xFF000000) >> 24;
+ oid[5] = (ip->addr[1] & 0x00FF0000) >> 16;
+ oid[6] = (ip->addr[1] & 0x0000FF00) >> 8;
+ oid[7] = (ip->addr[1] & 0x000000FF) >> 0;
+ oid[8] = (ip->addr[2] & 0xFF000000) >> 24;
+ oid[9] = (ip->addr[2] & 0x00FF0000) >> 16;
+ oid[10] = (ip->addr[2] & 0x0000FF00) >> 8;
+ oid[11] = (ip->addr[2] & 0x000000FF) >> 0;
+ oid[12] = (ip->addr[3] & 0xFF000000) >> 24;
+ oid[13] = (ip->addr[3] & 0x00FF0000) >> 16;
+ oid[14] = (ip->addr[3] & 0x0000FF00) >> 8;
+ oid[15] = (ip->addr[3] & 0x000000FF) >> 0;
+}
+#endif /* LWIP_IPV6 */
+
+#if LWIP_IPV4 || LWIP_IPV6
+/**
+ * Convert to InetAddressType+InetAddress+InetPortNumber
+ * @param ip IP address
+ * @param port Port
+ * @param oid OID
+ * @return OID length
+ */
+u8_t
+snmp_ip_port_to_oid(const ip_addr_t *ip, u16_t port, u32_t *oid)
+{
+ u8_t idx;
+
+ idx = snmp_ip_to_oid(ip, oid);
+ oid[idx] = port;
+ idx++;
+
+ return idx;
+}
+
+/**
+ * Convert to InetAddressType+InetAddress
+ * @param ip IP address
+ * @param oid OID
+ * @return OID length
+ */
+u8_t
+snmp_ip_to_oid(const ip_addr_t *ip, u32_t *oid)
+{
+ if (IP_IS_ANY_TYPE_VAL(*ip)) {
+ oid[0] = 0; /* any */
+ oid[1] = 0; /* no IP OIDs follow */
+ return 2;
+ } else if (IP_IS_V6(ip)) {
+#if LWIP_IPV6
+ oid[0] = 2; /* ipv6 */
+ oid[1] = 16; /* 16 InetAddressIPv6 OIDs follow */
+ snmp_ip6_to_oid(ip_2_ip6(ip), &oid[2]);
+ return 18;
+#else /* LWIP_IPV6 */
+ return 0;
+#endif /* LWIP_IPV6 */
+ } else {
+#if LWIP_IPV4
+ oid[0] = 1; /* ipv4 */
+ oid[1] = 4; /* 4 InetAddressIPv4 OIDs follow */
+ snmp_ip4_to_oid(ip_2_ip4(ip), &oid[2]);
+ return 6;
+#else /* LWIP_IPV4 */
+ return 0;
+#endif /* LWIP_IPV4 */
+ }
+}
+
+/**
+ * Convert from InetAddressType+InetAddress to ip_addr_t
+ * @param oid OID
+ * @param oid_len OID length
+ * @param ip IP address
+ * @return Parsed OID length
+ */
+u8_t
+snmp_oid_to_ip(const u32_t *oid, u8_t oid_len, ip_addr_t *ip)
+{
+ /* InetAddressType */
+ if (oid_len < 1) {
+ return 0;
+ }
+
+ if (oid[0] == 0) { /* any */
+ /* 1x InetAddressType, 1x OID len */
+ if (oid_len < 2) {
+ return 0;
+ }
+ if (oid[1] != 0) {
+ return 0;
+ }
+
+ memset(ip, 0, sizeof(*ip));
+ IP_SET_TYPE(ip, IPADDR_TYPE_ANY);
+
+ return 2;
+ } else if (oid[0] == 1) { /* ipv4 */
+#if LWIP_IPV4
+ /* 1x InetAddressType, 1x OID len, 4x InetAddressIPv4 */
+ if (oid_len < 6) {
+ return 0;
+ }
+
+ /* 4x ipv4 OID */
+ if (oid[1] != 4) {
+ return 0;
+ }
+
+ IP_SET_TYPE(ip, IPADDR_TYPE_V4);
+ if (!snmp_oid_to_ip4(&oid[2], ip_2_ip4(ip))) {
+ return 0;
+ }
+
+ return 6;
+#else /* LWIP_IPV4 */
+ return 0;
+#endif /* LWIP_IPV4 */
+ } else if (oid[0] == 2) { /* ipv6 */
+#if LWIP_IPV6
+ /* 1x InetAddressType, 1x OID len, 16x InetAddressIPv6 */
+ if (oid_len < 18) {
+ return 0;
+ }
+
+ /* 16x ipv6 OID */
+ if (oid[1] != 16) {
+ return 0;
+ }
+
+ IP_SET_TYPE(ip, IPADDR_TYPE_V6);
+ if (!snmp_oid_to_ip6(&oid[2], ip_2_ip6(ip))) {
+ return 0;
+ }
+
+ return 18;
+#else /* LWIP_IPV6 */
+ return 0;
+#endif /* LWIP_IPV6 */
+ } else { /* unsupported InetAddressType */
+ return 0;
+ }
+}
+
+/**
+ * Convert from InetAddressType+InetAddress+InetPortNumber to ip_addr_t and u16_t
+ * @param oid OID
+ * @param oid_len OID length
+ * @param ip IP address
+ * @param port Port
+ * @return Parsed OID length
+ */
+u8_t
+snmp_oid_to_ip_port(const u32_t *oid, u8_t oid_len, ip_addr_t *ip, u16_t *port)
+{
+ u8_t idx;
+
+ /* InetAddressType + InetAddress */
+ idx = snmp_oid_to_ip(&oid[0], oid_len, ip);
+ if (idx == 0) {
+ return 0;
+ }
+
+ /* InetPortNumber */
+ if (oid_len < (idx + 1)) {
+ return 0;
+ }
+ if (oid[idx] > 0xffff) {
+ return 0;
+ }
+ *port = (u16_t)oid[idx];
+ idx++;
+
+ return idx;
+}
+
+#endif /* LWIP_IPV4 || LWIP_IPV6 */
+
+/**
+ * Assign an OID to struct snmp_obj_id
+ * @param target Assignment target
+ * @param oid OID
+ * @param oid_len OID length
+ */
+void
+snmp_oid_assign(struct snmp_obj_id *target, const u32_t *oid, u8_t oid_len)
+{
+ LWIP_ASSERT("oid_len <= SNMP_MAX_OBJ_ID_LEN", oid_len <= SNMP_MAX_OBJ_ID_LEN);
+
+ target->len = oid_len;
+
+ if (oid_len > 0) {
+ MEMCPY(target->id, oid, oid_len * sizeof(u32_t));
+ }
+}
+
+/**
+ * Prefix an OID to OID in struct snmp_obj_id
+ * @param target Assignment target to prefix
+ * @param oid OID
+ * @param oid_len OID length
+ */
+void
+snmp_oid_prefix(struct snmp_obj_id *target, const u32_t *oid, u8_t oid_len)
+{
+ LWIP_ASSERT("target->len + oid_len <= SNMP_MAX_OBJ_ID_LEN", (target->len + oid_len) <= SNMP_MAX_OBJ_ID_LEN);
+
+ if (oid_len > 0) {
+ /* move existing OID to make room at the beginning for OID to insert */
+ int i;
+ for (i = target->len - 1; i >= 0; i--) {
+ target->id[i + oid_len] = target->id[i];
+ }
+
+ /* paste oid at the beginning */
+ MEMCPY(target->id, oid, oid_len * sizeof(u32_t));
+ }
+}
+
+/**
+ * Combine two OIDs into struct snmp_obj_id
+ * @param target Assignmet target
+ * @param oid1 OID 1
+ * @param oid1_len OID 1 length
+ * @param oid2 OID 2
+ * @param oid2_len OID 2 length
+ */
+void
+snmp_oid_combine(struct snmp_obj_id *target, const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len)
+{
+ snmp_oid_assign(target, oid1, oid1_len);
+ snmp_oid_append(target, oid2, oid2_len);
+}
+
+/**
+ * Append OIDs to struct snmp_obj_id
+ * @param target Assignment target to append to
+ * @param oid OID
+ * @param oid_len OID length
+ */
+void
+snmp_oid_append(struct snmp_obj_id *target, const u32_t *oid, u8_t oid_len)
+{
+ LWIP_ASSERT("offset + oid_len <= SNMP_MAX_OBJ_ID_LEN", (target->len + oid_len) <= SNMP_MAX_OBJ_ID_LEN);
+
+ if (oid_len > 0) {
+ MEMCPY(&target->id[target->len], oid, oid_len * sizeof(u32_t));
+ target->len = (u8_t)(target->len + oid_len);
+ }
+}
+
+/**
+ * Compare two OIDs
+ * @param oid1 OID 1
+ * @param oid1_len OID 1 length
+ * @param oid2 OID 2
+ * @param oid2_len OID 2 length
+ * @return -1: OID1&lt;OID2 1: OID1 &gt;OID2 0: equal
+ */
+s8_t
+snmp_oid_compare(const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len)
+{
+ u8_t level = 0;
+ LWIP_ASSERT("'oid1' param must not be NULL or 'oid1_len' param be 0!", (oid1 != NULL) || (oid1_len == 0));
+ LWIP_ASSERT("'oid2' param must not be NULL or 'oid2_len' param be 0!", (oid2 != NULL) || (oid2_len == 0));
+
+ while ((level < oid1_len) && (level < oid2_len)) {
+ if (*oid1 < *oid2) {
+ return -1;
+ }
+ if (*oid1 > *oid2) {
+ return 1;
+ }
+
+ level++;
+ oid1++;
+ oid2++;
+ }
+
+ /* common part of both OID's is equal, compare length */
+ if (oid1_len < oid2_len) {
+ return -1;
+ }
+ if (oid1_len > oid2_len) {
+ return 1;
+ }
+
+ /* they are equal */
+ return 0;
+}
+
+
+/**
+ * Check of two OIDs are equal
+ * @param oid1 OID 1
+ * @param oid1_len OID 1 length
+ * @param oid2 OID 2
+ * @param oid2_len OID 2 length
+ * @return 1: equal 0: non-equal
+ */
+u8_t
+snmp_oid_equal(const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len)
+{
+ return (snmp_oid_compare(oid1, oid1_len, oid2, oid2_len) == 0) ? 1 : 0;
+}
+
+/**
+ * Convert netif to interface index
+ * @param netif netif
+ * @return index
+ */
+u8_t
+netif_to_num(const struct netif *netif)
+{
+ return netif_get_index(netif);
+}
+
+static const struct snmp_mib *
+snmp_get_mib_from_oid(const u32_t *oid, u8_t oid_len)
+{
+ const u32_t *list_oid;
+ const u32_t *searched_oid;
+ u8_t i, l;
+
+ u8_t max_match_len = 0;
+ const struct snmp_mib *matched_mib = NULL;
+
+ LWIP_ASSERT("'oid' param must not be NULL!", (oid != NULL));
+
+ if (oid_len == 0) {
+ return NULL;
+ }
+
+ for (i = 0; i < snmp_num_mibs; i++) {
+ LWIP_ASSERT("MIB array not initialized correctly", (snmp_mibs[i] != NULL));
+ LWIP_ASSERT("MIB array not initialized correctly - base OID is NULL", (snmp_mibs[i]->base_oid != NULL));
+
+ if (oid_len >= snmp_mibs[i]->base_oid_len) {
+ l = snmp_mibs[i]->base_oid_len;
+ list_oid = snmp_mibs[i]->base_oid;
+ searched_oid = oid;
+
+ while (l > 0) {
+ if (*list_oid != *searched_oid) {
+ break;
+ }
+
+ l--;
+ list_oid++;
+ searched_oid++;
+ }
+
+ if ((l == 0) && (snmp_mibs[i]->base_oid_len > max_match_len)) {
+ max_match_len = snmp_mibs[i]->base_oid_len;
+ matched_mib = snmp_mibs[i];
+ }
+ }
+ }
+
+ return matched_mib;
+}
+
+static const struct snmp_mib *
+snmp_get_next_mib(const u32_t *oid, u8_t oid_len)
+{
+ u8_t i;
+ const struct snmp_mib *next_mib = NULL;
+
+ LWIP_ASSERT("'oid' param must not be NULL!", (oid != NULL));
+
+ if (oid_len == 0) {
+ return NULL;
+ }
+
+ for (i = 0; i < snmp_num_mibs; i++) {
+ if (snmp_mibs[i]->base_oid != NULL) {
+ /* check if mib is located behind starting point */
+ if (snmp_oid_compare(snmp_mibs[i]->base_oid, snmp_mibs[i]->base_oid_len, oid, oid_len) > 0) {
+ if ((next_mib == NULL) ||
+ (snmp_oid_compare(snmp_mibs[i]->base_oid, snmp_mibs[i]->base_oid_len,
+ next_mib->base_oid, next_mib->base_oid_len) < 0)) {
+ next_mib = snmp_mibs[i];
+ }
+ }
+ }
+ }
+
+ return next_mib;
+}
+
+static const struct snmp_mib *
+snmp_get_mib_between(const u32_t *oid1, u8_t oid1_len, const u32_t *oid2, u8_t oid2_len)
+{
+ const struct snmp_mib *next_mib = snmp_get_next_mib(oid1, oid1_len);
+
+ LWIP_ASSERT("'oid2' param must not be NULL!", (oid2 != NULL));
+ LWIP_ASSERT("'oid2_len' param must be greater than 0!", (oid2_len > 0));
+
+ if (next_mib != NULL) {
+ if (snmp_oid_compare(next_mib->base_oid, next_mib->base_oid_len, oid2, oid2_len) < 0) {
+ return next_mib;
+ }
+ }
+
+ return NULL;
+}
+
+u8_t
+snmp_get_node_instance_from_oid(const u32_t *oid, u8_t oid_len, struct snmp_node_instance *node_instance)
+{
+ u8_t result = SNMP_ERR_NOSUCHOBJECT;
+ const struct snmp_mib *mib;
+ const struct snmp_node *mn = NULL;
+
+ mib = snmp_get_mib_from_oid(oid, oid_len);
+ if (mib != NULL) {
+ u8_t oid_instance_len;
+
+ mn = snmp_mib_tree_resolve_exact(mib, oid, oid_len, &oid_instance_len);
+ if ((mn != NULL) && (mn->node_type != SNMP_NODE_TREE)) {
+ /* get instance */
+ const struct snmp_leaf_node *leaf_node = (const struct snmp_leaf_node *)(const void *)mn;
+
+ node_instance->node = mn;
+ snmp_oid_assign(&node_instance->instance_oid, oid + (oid_len - oid_instance_len), oid_instance_len);
+
+ result = leaf_node->get_instance(
+ oid,
+ oid_len - oid_instance_len,
+ node_instance);
+
+#ifdef LWIP_DEBUG
+ if (result == SNMP_ERR_NOERROR) {
+ if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_READ) != 0) && (node_instance->get_value == NULL)) {
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP inconsistent access: node is readable but no get_value function is specified\n"));
+ }
+ if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_WRITE) != 0) && (node_instance->set_value == NULL)) {
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP inconsistent access: node is writable but no set_value and/or set_test function is specified\n"));
+ }
+ }
+#endif
+ }
+ }
+
+ return result;
+}
+
+u8_t
+snmp_get_next_node_instance_from_oid(const u32_t *oid, u8_t oid_len, snmp_validate_node_instance_method validate_node_instance_method, void *validate_node_instance_arg, struct snmp_obj_id *node_oid, struct snmp_node_instance *node_instance)
+{
+ const struct snmp_mib *mib;
+ const struct snmp_node *mn = NULL;
+ const u32_t *start_oid = NULL;
+ u8_t start_oid_len = 0;
+
+ /* resolve target MIB from passed OID */
+ mib = snmp_get_mib_from_oid(oid, oid_len);
+ if (mib == NULL) {
+ /* passed OID does not reference any known MIB, start at the next closest MIB */
+ mib = snmp_get_next_mib(oid, oid_len);
+
+ if (mib != NULL) {
+ start_oid = mib->base_oid;
+ start_oid_len = mib->base_oid_len;
+ }
+ } else {
+ start_oid = oid;
+ start_oid_len = oid_len;
+ }
+
+ /* resolve target node from MIB, skip to next MIB if no suitable node is found in current MIB */
+ while ((mib != NULL) && (mn == NULL)) {
+ u8_t oid_instance_len;
+
+ /* check if OID directly references a node inside current MIB, in this case we have to ask this node for the next instance */
+ mn = snmp_mib_tree_resolve_exact(mib, start_oid, start_oid_len, &oid_instance_len);
+ if (mn != NULL) {
+ snmp_oid_assign(node_oid, start_oid, start_oid_len - oid_instance_len); /* set oid to node */
+ snmp_oid_assign(&node_instance->instance_oid, start_oid + (start_oid_len - oid_instance_len), oid_instance_len); /* set (relative) instance oid */
+ } else {
+ /* OID does not reference a node, search for the next closest node inside MIB; set instance_oid.len to zero because we want the first instance of this node */
+ mn = snmp_mib_tree_resolve_next(mib, start_oid, start_oid_len, node_oid);
+ node_instance->instance_oid.len = 0;
+ }
+
+ /* validate the node; if the node has no further instance or the returned instance is invalid, search for the next in MIB and validate again */
+ node_instance->node = mn;
+ while (mn != NULL) {
+ u8_t result;
+
+ /* clear fields which may have values from previous loops */
+ node_instance->asn1_type = 0;
+ node_instance->access = SNMP_NODE_INSTANCE_NOT_ACCESSIBLE;
+ node_instance->get_value = NULL;
+ node_instance->set_test = NULL;
+ node_instance->set_value = NULL;
+ node_instance->release_instance = NULL;
+ node_instance->reference.ptr = NULL;
+ node_instance->reference_len = 0;
+
+ result = ((const struct snmp_leaf_node *)(const void *)mn)->get_next_instance(
+ node_oid->id,
+ node_oid->len,
+ node_instance);
+
+ if (result == SNMP_ERR_NOERROR) {
+#ifdef LWIP_DEBUG
+ if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_READ) != 0) && (node_instance->get_value == NULL)) {
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP inconsistent access: node is readable but no get_value function is specified\n"));
+ }
+ if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_WRITE) != 0) && (node_instance->set_value == NULL)) {
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP inconsistent access: node is writable but no set_value function is specified\n"));
+ }
+#endif
+
+ /* validate node because the node may be not accessible for example (but let the caller decide what is valid */
+ if ((validate_node_instance_method == NULL) ||
+ (validate_node_instance_method(node_instance, validate_node_instance_arg) == SNMP_ERR_NOERROR)) {
+ /* node_oid "returns" the full result OID (including the instance part) */
+ snmp_oid_append(node_oid, node_instance->instance_oid.id, node_instance->instance_oid.len);
+ break;
+ }
+
+ if (node_instance->release_instance != NULL) {
+ node_instance->release_instance(node_instance);
+ }
+ /*
+ the instance itself is not valid, ask for next instance from same node.
+ we don't have to change any variables because node_instance->instance_oid is used as input (starting point)
+ as well as output (resulting next OID), so we have to simply call get_next_instance method again
+ */
+ } else {
+ if (node_instance->release_instance != NULL) {
+ node_instance->release_instance(node_instance);
+ }
+
+ /* the node has no further instance, skip to next node */
+ mn = snmp_mib_tree_resolve_next(mib, node_oid->id, node_oid->len, &node_instance->instance_oid); /* misuse node_instance->instance_oid as tmp buffer */
+ if (mn != NULL) {
+ /* prepare for next loop */
+ snmp_oid_assign(node_oid, node_instance->instance_oid.id, node_instance->instance_oid.len);
+ node_instance->instance_oid.len = 0;
+ node_instance->node = mn;
+ }
+ }
+ }
+
+ if (mn != NULL) {
+ /*
+ we found a suitable next node,
+ now we have to check if a inner MIB is located between the searched OID and the resulting OID.
+ this is possible because MIB's may be located anywhere in the global tree, that means also in
+ the subtree of another MIB (e.g. if searched OID is .2 and resulting OID is .4, then another
+ MIB having .3 as root node may exist)
+ */
+ const struct snmp_mib *intermediate_mib;
+ intermediate_mib = snmp_get_mib_between(start_oid, start_oid_len, node_oid->id, node_oid->len);
+
+ if (intermediate_mib != NULL) {
+ /* search for first node inside intermediate mib in next loop */
+ if (node_instance->release_instance != NULL) {
+ node_instance->release_instance(node_instance);
+ }
+
+ mn = NULL;
+ mib = intermediate_mib;
+ start_oid = mib->base_oid;
+ start_oid_len = mib->base_oid_len;
+ }
+ /* else { we found out target node } */
+ } else {
+ /*
+ there is no further (suitable) node inside this MIB, search for the next MIB with following priority
+ 1. search for inner MIB's (whose root is located inside tree of current MIB)
+ 2. search for surrounding MIB's (where the current MIB is the inner MIB) and continue there if any
+ 3. take the next closest MIB (not being related to the current MIB)
+ */
+ const struct snmp_mib *next_mib;
+ next_mib = snmp_get_next_mib(start_oid, start_oid_len); /* returns MIB's related to point 1 and 3 */
+
+ /* is the found MIB an inner MIB? (point 1) */
+ if ((next_mib != NULL) && (next_mib->base_oid_len > mib->base_oid_len) &&
+ (snmp_oid_compare(next_mib->base_oid, mib->base_oid_len, mib->base_oid, mib->base_oid_len) == 0)) {
+ /* yes it is -> continue at inner MIB */
+ mib = next_mib;
+ start_oid = mib->base_oid;
+ start_oid_len = mib->base_oid_len;
+ } else {
+ /* check if there is a surrounding mib where to continue (point 2) (only possible if OID length > 1) */
+ if (mib->base_oid_len > 1) {
+ mib = snmp_get_mib_from_oid(mib->base_oid, mib->base_oid_len - 1);
+
+ if (mib == NULL) {
+ /* no surrounding mib, use next mib encountered above (point 3) */
+ mib = next_mib;
+
+ if (mib != NULL) {
+ start_oid = mib->base_oid;
+ start_oid_len = mib->base_oid_len;
+ }
+ }
+ /* else { start_oid stays the same because we want to continue from current offset in surrounding mib (point 2) } */
+ }
+ }
+ }
+ }
+
+ if (mib == NULL) {
+ /* loop is only left when mib == null (error) or mib_node != NULL (success) */
+ return SNMP_ERR_ENDOFMIBVIEW;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+/**
+ * Searches tree for the supplied object identifier.
+ *
+ */
+const struct snmp_node *
+snmp_mib_tree_resolve_exact(const struct snmp_mib *mib, const u32_t *oid, u8_t oid_len, u8_t *oid_instance_len)
+{
+ const struct snmp_node *const *node = &mib->root_node;
+ u8_t oid_offset = mib->base_oid_len;
+
+ while ((oid_offset < oid_len) && ((*node)->node_type == SNMP_NODE_TREE)) {
+ /* search for matching sub node */
+ u32_t subnode_oid = *(oid + oid_offset);
+
+ u32_t i = (*(const struct snmp_tree_node * const *)node)->subnode_count;
+ node = (*(const struct snmp_tree_node * const *)node)->subnodes;
+ while ((i > 0) && ((*node)->oid != subnode_oid)) {
+ node++;
+ i--;
+ }
+
+ if (i == 0) {
+ /* no matching subnode found */
+ return NULL;
+ }
+
+ oid_offset++;
+ }
+
+ if ((*node)->node_type != SNMP_NODE_TREE) {
+ /* we found a leaf node */
+ *oid_instance_len = oid_len - oid_offset;
+ return (*node);
+ }
+
+ return NULL;
+}
+
+const struct snmp_node *
+snmp_mib_tree_resolve_next(const struct snmp_mib *mib, const u32_t *oid, u8_t oid_len, struct snmp_obj_id *oidret)
+{
+ u8_t oid_offset = mib->base_oid_len;
+ const struct snmp_node *const *node;
+ const struct snmp_tree_node *node_stack[SNMP_MAX_OBJ_ID_LEN];
+ s32_t nsi = 0; /* NodeStackIndex */
+ u32_t subnode_oid;
+
+ if (mib->root_node->node_type != SNMP_NODE_TREE) {
+ /* a next operation on a mib with only a leaf node will always return NULL because there is no other node */
+ return NULL;
+ }
+
+ /* first build node stack related to passed oid (as far as possible), then go backwards to determine the next node */
+ node_stack[nsi] = (const struct snmp_tree_node *)(const void *)mib->root_node;
+ while (oid_offset < oid_len) {
+ /* search for matching sub node */
+ u32_t i = node_stack[nsi]->subnode_count;
+ node = node_stack[nsi]->subnodes;
+
+ subnode_oid = *(oid + oid_offset);
+
+ while ((i > 0) && ((*node)->oid != subnode_oid)) {
+ node++;
+ i--;
+ }
+
+ if ((i == 0) || ((*node)->node_type != SNMP_NODE_TREE)) {
+ /* no (matching) tree-subnode found */
+ break;
+ }
+ nsi++;
+ node_stack[nsi] = (const struct snmp_tree_node *)(const void *)(*node);
+
+ oid_offset++;
+ }
+
+
+ if (oid_offset >= oid_len) {
+ /* passed oid references a tree node -> return first usable sub node of it */
+ subnode_oid = 0;
+ } else {
+ subnode_oid = *(oid + oid_offset) + 1;
+ }
+
+ while (nsi >= 0) {
+ const struct snmp_node *subnode = NULL;
+
+ /* find next node on current level */
+ s32_t i = node_stack[nsi]->subnode_count;
+ node = node_stack[nsi]->subnodes;
+ while (i > 0) {
+ if ((*node)->oid == subnode_oid) {
+ subnode = *node;
+ break;
+ } else if (((*node)->oid > subnode_oid) && ((subnode == NULL) || ((*node)->oid < subnode->oid))) {
+ subnode = *node;
+ }
+
+ node++;
+ i--;
+ }
+
+ if (subnode == NULL) {
+ /* no further node found on this level, go one level up and start searching with index of current node*/
+ subnode_oid = node_stack[nsi]->node.oid + 1;
+ nsi--;
+ } else {
+ if (subnode->node_type == SNMP_NODE_TREE) {
+ /* next is a tree node, go into it and start searching */
+ nsi++;
+ node_stack[nsi] = (const struct snmp_tree_node *)(const void *)subnode;
+ subnode_oid = 0;
+ } else {
+ /* we found a leaf node -> fill oidret and return it */
+ snmp_oid_assign(oidret, mib->base_oid, mib->base_oid_len);
+ i = 1;
+ while (i <= nsi) {
+ oidret->id[oidret->len] = node_stack[i]->node.oid;
+ oidret->len++;
+ i++;
+ }
+
+ oidret->id[oidret->len] = subnode->oid;
+ oidret->len++;
+
+ return subnode;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/** initialize struct next_oid_state using this function before passing it to next_oid_check */
+void
+snmp_next_oid_init(struct snmp_next_oid_state *state,
+ const u32_t *start_oid, u8_t start_oid_len,
+ u32_t *next_oid_buf, u8_t next_oid_max_len)
+{
+ state->start_oid = start_oid;
+ state->start_oid_len = start_oid_len;
+ state->next_oid = next_oid_buf;
+ state->next_oid_len = 0;
+ state->next_oid_max_len = next_oid_max_len;
+ state->status = SNMP_NEXT_OID_STATUS_NO_MATCH;
+}
+
+/** checks if the passed incomplete OID may be a possible candidate for snmp_next_oid_check();
+this method is intended if the complete OID is not yet known but it is very expensive to build it up,
+so it is possible to test the starting part before building up the complete oid and pass it to snmp_next_oid_check()*/
+u8_t
+snmp_next_oid_precheck(struct snmp_next_oid_state *state, const u32_t *oid, u8_t oid_len)
+{
+ if (state->status != SNMP_NEXT_OID_STATUS_BUF_TO_SMALL) {
+ u8_t start_oid_len = (oid_len < state->start_oid_len) ? oid_len : state->start_oid_len;
+
+ /* check passed OID is located behind start offset */
+ if (snmp_oid_compare(oid, oid_len, state->start_oid, start_oid_len) >= 0) {
+ /* check if new oid is located closer to start oid than current closest oid */
+ if ((state->status == SNMP_NEXT_OID_STATUS_NO_MATCH) ||
+ (snmp_oid_compare(oid, oid_len, state->next_oid, state->next_oid_len) < 0)) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/** checks the passed OID if it is a candidate to be the next one (get_next); returns !=0 if passed oid is currently closest, otherwise 0 */
+u8_t
+snmp_next_oid_check(struct snmp_next_oid_state *state, const u32_t *oid, u8_t oid_len, void *reference)
+{
+ /* do not overwrite a fail result */
+ if (state->status != SNMP_NEXT_OID_STATUS_BUF_TO_SMALL) {
+ /* check passed OID is located behind start offset */
+ if (snmp_oid_compare(oid, oid_len, state->start_oid, state->start_oid_len) > 0) {
+ /* check if new oid is located closer to start oid than current closest oid */
+ if ((state->status == SNMP_NEXT_OID_STATUS_NO_MATCH) ||
+ (snmp_oid_compare(oid, oid_len, state->next_oid, state->next_oid_len) < 0)) {
+ if (oid_len <= state->next_oid_max_len) {
+ MEMCPY(state->next_oid, oid, oid_len * sizeof(u32_t));
+ state->next_oid_len = oid_len;
+ state->status = SNMP_NEXT_OID_STATUS_SUCCESS;
+ state->reference = reference;
+ return 1;
+ } else {
+ state->status = SNMP_NEXT_OID_STATUS_BUF_TO_SMALL;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+u8_t
+snmp_oid_in_range(const u32_t *oid_in, u8_t oid_len, const struct snmp_oid_range *oid_ranges, u8_t oid_ranges_len)
+{
+ u8_t i;
+
+ if (oid_len != oid_ranges_len) {
+ return 0;
+ }
+
+ for (i = 0; i < oid_ranges_len; i++) {
+ if ((oid_in[i] < oid_ranges[i].min) || (oid_in[i] > oid_ranges[i].max)) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+snmp_err_t
+snmp_set_test_ok(struct snmp_node_instance *instance, u16_t value_len, void *value)
+{
+ LWIP_UNUSED_ARG(instance);
+ LWIP_UNUSED_ARG(value_len);
+ LWIP_UNUSED_ARG(value);
+
+ return SNMP_ERR_NOERROR;
+}
+
+/**
+ * Decodes BITS pseudotype value from ASN.1 OctetString.
+ *
+ * @note Because BITS pseudo type is encoded as OCTET STRING, it cannot directly
+ * be encoded/decoded by the agent. Instead call this function as required from
+ * get/test/set methods.
+ *
+ * @param buf points to a buffer holding the ASN1 octet string
+ * @param buf_len length of octet string
+ * @param bit_value decoded Bit value with Bit0 == LSB
+ * @return ERR_OK if successful, ERR_ARG if bit value contains more than 32 bit
+ */
+err_t
+snmp_decode_bits(const u8_t *buf, u32_t buf_len, u32_t *bit_value)
+{
+ u8_t b;
+ u8_t bits_processed = 0;
+ *bit_value = 0;
+
+ while (buf_len > 0) {
+ /* any bit set in this byte? */
+ if (*buf != 0x00) {
+ if (bits_processed >= 32) {
+ /* accept more than 4 bytes, but only when no bits are set */
+ return ERR_VAL;
+ }
+
+ b = *buf;
+ do {
+ if (b & 0x80) {
+ *bit_value |= (1 << bits_processed);
+ }
+ bits_processed++;
+ b <<= 1;
+ } while ((bits_processed & 0x07) != 0); /* &0x07 -> % 8 */
+ } else {
+ bits_processed += 8;
+ }
+
+ buf_len--;
+ buf++;
+ }
+
+ return ERR_OK;
+}
+
+err_t
+snmp_decode_truthvalue(const s32_t *asn1_value, u8_t *bool_value)
+{
+ /* defined by RFC1443:
+ TruthValue ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "Represents a boolean value."
+ SYNTAX INTEGER { true(1), false(2) }
+ */
+
+ if ((asn1_value == NULL) || (bool_value == NULL)) {
+ return ERR_ARG;
+ }
+
+ if (*asn1_value == 1) {
+ *bool_value = 1;
+ } else if (*asn1_value == 2) {
+ *bool_value = 0;
+ } else {
+ return ERR_VAL;
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * Encodes BITS pseudotype value into ASN.1 OctetString.
+ *
+ * @note Because BITS pseudo type is encoded as OCTET STRING, it cannot directly
+ * be encoded/decoded by the agent. Instead call this function as required from
+ * get/test/set methods.
+ *
+ * @param buf points to a buffer where the resulting ASN1 octet string is stored to
+ * @param buf_len max length of the buffer
+ * @param bit_value Bit value to encode with Bit0 == LSB
+ * @param bit_count Number of possible bits for the bit value (according to rfc we have to send all bits independent from their truth value)
+ * @return number of bytes used from buffer to store the resulting OctetString
+ */
+u8_t
+snmp_encode_bits(u8_t *buf, u32_t buf_len, u32_t bit_value, u8_t bit_count)
+{
+ u8_t len = 0;
+ u8_t min_bytes = (bit_count + 7) >> 3; /* >>3 -> / 8 */
+
+ while ((buf_len > 0) && (bit_value != 0x00)) {
+ s8_t i = 7;
+ *buf = 0x00;
+ while (i >= 0) {
+ if (bit_value & 0x01) {
+ *buf |= 0x01;
+ }
+
+ if (i > 0) {
+ *buf <<= 1;
+ }
+
+ bit_value >>= 1;
+ i--;
+ }
+
+ buf++;
+ buf_len--;
+ len++;
+ }
+
+ if (len < min_bytes) {
+ buf += len;
+ buf_len -= len;
+
+ while ((len < min_bytes) && (buf_len > 0)) {
+ *buf = 0x00;
+ buf++;
+ buf_len--;
+ len++;
+ }
+ }
+
+ return len;
+}
+
+u8_t
+snmp_encode_truthvalue(s32_t *asn1_value, u32_t bool_value)
+{
+ /* defined by RFC1443:
+ TruthValue ::= TEXTUAL-CONVENTION
+ STATUS current
+ DESCRIPTION
+ "Represents a boolean value."
+ SYNTAX INTEGER { true(1), false(2) }
+ */
+
+ if (asn1_value == NULL) {
+ return 0;
+ }
+
+ if (bool_value) {
+ *asn1_value = 1; /* defined by RFC1443 */
+ } else {
+ *asn1_value = 2; /* defined by RFC1443 */
+ }
+
+ return sizeof(s32_t);
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_core_priv.h b/src/apps/snmp/snmp_core_priv.h
new file mode 100644
index 00000000000..e4323180cc2
--- /dev/null
+++ b/src/apps/snmp/snmp_core_priv.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Martin Hentschel <[email protected]>
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_CORE_PRIV_H
+#define LWIP_HDR_APPS_SNMP_CORE_PRIV_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp_core.h"
+#include "snmp_asn1.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if !defined LWIP_ASSERT_SNMP_LOCKED
+#if SNMP_USE_RAW
+#define LWIP_ASSERT_SNMP_LOCKED() LWIP_ASSERT_CORE_LOCKED()
+#else
+#define LWIP_ASSERT_SNMP_LOCKED()
+#endif
+#endif
+
+/* (outdated) SNMPv1 error codes
+ * shall not be used by MIBS anymore, nevertheless required from core for properly answering a v1 request
+ */
+#define SNMP_ERR_NOSUCHNAME 2
+#define SNMP_ERR_BADVALUE 3
+#define SNMP_ERR_READONLY 4
+/* error codes which are internal and shall not be used by MIBS
+ * shall not be used by MIBS anymore, nevertheless required from core for properly answering a v1 request
+ */
+#define SNMP_ERR_TOOBIG 1
+#define SNMP_ERR_AUTHORIZATIONERROR 16
+
+#define SNMP_ERR_UNKNOWN_ENGINEID 30
+#define SNMP_ERR_UNKNOWN_SECURITYNAME 31
+#define SNMP_ERR_UNSUPPORTED_SECLEVEL 32
+#define SNMP_ERR_NOTINTIMEWINDOW 33
+#define SNMP_ERR_DECRYIPTION_ERROR 34
+
+#define SNMP_ERR_NOSUCHOBJECT SNMP_VARBIND_EXCEPTION_OFFSET + SNMP_ASN1_CONTEXT_VARBIND_NO_SUCH_OBJECT
+#define SNMP_ERR_ENDOFMIBVIEW SNMP_VARBIND_EXCEPTION_OFFSET + SNMP_ASN1_CONTEXT_VARBIND_END_OF_MIB_VIEW
+
+
+const struct snmp_node *snmp_mib_tree_resolve_exact(const struct snmp_mib *mib, const u32_t *oid, u8_t oid_len, u8_t *oid_instance_len);
+const struct snmp_node *snmp_mib_tree_resolve_next(const struct snmp_mib *mib, const u32_t *oid, u8_t oid_len, struct snmp_obj_id *oidret);
+
+typedef u8_t (*snmp_validate_node_instance_method)(struct snmp_node_instance *, void *);
+
+u8_t snmp_get_node_instance_from_oid(const u32_t *oid, u8_t oid_len, struct snmp_node_instance *node_instance);
+u8_t snmp_get_next_node_instance_from_oid(const u32_t *oid, u8_t oid_len, snmp_validate_node_instance_method validate_node_instance_method, void *validate_node_instance_arg, struct snmp_obj_id *node_oid, struct snmp_node_instance *node_instance);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* LWIP_HDR_APPS_SNMP_CORE_PRIV_H */
diff --git a/src/apps/snmp/snmp_mib2.c b/src/apps/snmp/snmp_mib2.c
new file mode 100644
index 00000000000..3383e44e929
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2.c
@@ -0,0 +1,116 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) objects and functions.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ * Christiaan Simons <[email protected]>
+ */
+
+/**
+ * @defgroup snmp_mib2 MIB2
+ * @ingroup snmp
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2 /* don't build if not configured for use in lwipopts.h */
+
+#if !LWIP_STATS
+#error LWIP_SNMP MIB2 needs LWIP_STATS (for MIB2)
+#endif
+#if !MIB2_STATS
+#error LWIP_SNMP MIB2 needs MIB2_STATS (for MIB2)
+#endif
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_scalar.h"
+
+#if SNMP_USE_NETCONN
+#include "lwip/tcpip.h"
+#include "lwip/priv/tcpip_priv.h"
+void
+snmp_mib2_lwip_synchronizer(snmp_threadsync_called_fn fn, void *arg)
+{
+#if LWIP_TCPIP_CORE_LOCKING
+ LOCK_TCPIP_CORE();
+ fn(arg);
+ UNLOCK_TCPIP_CORE();
+#else
+ tcpip_callback(fn, arg);
+#endif
+}
+
+struct snmp_threadsync_instance snmp_mib2_lwip_locks;
+#endif
+
+/* dot3 and EtherLike MIB not planned. (transmission .1.3.6.1.2.1.10) */
+/* historical (some say hysterical). (cmot .1.3.6.1.2.1.9) */
+/* lwIP has no EGP, thus may not implement it. (egp .1.3.6.1.2.1.8) */
+
+/* --- mib-2 .1.3.6.1.2.1 ----------------------------------------------------- */
+extern const struct snmp_scalar_array_node snmp_mib2_snmp_root;
+extern const struct snmp_tree_node snmp_mib2_udp_root;
+extern const struct snmp_tree_node snmp_mib2_tcp_root;
+extern const struct snmp_scalar_array_node snmp_mib2_icmp_root;
+extern const struct snmp_tree_node snmp_mib2_interface_root;
+extern const struct snmp_scalar_array_node snmp_mib2_system_node;
+extern const struct snmp_tree_node snmp_mib2_at_root;
+extern const struct snmp_tree_node snmp_mib2_ip_root;
+
+static const struct snmp_node *const mib2_nodes[] = {
+ &snmp_mib2_system_node.node.node,
+ &snmp_mib2_interface_root.node,
+#if LWIP_ARP && LWIP_IPV4
+ &snmp_mib2_at_root.node,
+#endif /* LWIP_ARP && LWIP_IPV4 */
+#if LWIP_IPV4
+ &snmp_mib2_ip_root.node,
+#endif /* LWIP_IPV4 */
+#if LWIP_ICMP
+ &snmp_mib2_icmp_root.node.node,
+#endif /* LWIP_ICMP */
+#if LWIP_TCP
+ &snmp_mib2_tcp_root.node,
+#endif /* LWIP_TCP */
+#if LWIP_UDP
+ &snmp_mib2_udp_root.node,
+#endif /* LWIP_UDP */
+ &snmp_mib2_snmp_root.node.node
+};
+
+static const struct snmp_tree_node mib2_root = SNMP_CREATE_TREE_NODE(1, mib2_nodes);
+
+static const u32_t mib2_base_oid_arr[] = { 1, 3, 6, 1, 2, 1 };
+const struct snmp_mib mib2 = SNMP_MIB_CREATE(mib2_base_oid_arr, &mib2_root.node);
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 */
diff --git a/src/apps/snmp/snmp_mib2_icmp.c b/src/apps/snmp/snmp_mib2_icmp.c
new file mode 100644
index 00000000000..033d229eee4
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_icmp.c
@@ -0,0 +1,182 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) ICMP objects and functions.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ * Christiaan Simons <[email protected]>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/icmp.h"
+#include "lwip/stats.h"
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_ICMP
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+ static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+/* --- icmp .1.3.6.1.2.1.5 ----------------------------------------------------- */
+
+static s16_t
+icmp_get_value(const struct snmp_scalar_array_node_def *node, void *value)
+{
+ u32_t *uint_ptr = (u32_t *)value;
+
+ switch (node->oid) {
+ case 1: /* icmpInMsgs */
+ *uint_ptr = STATS_GET(mib2.icmpinmsgs);
+ return sizeof(*uint_ptr);
+ case 2: /* icmpInErrors */
+ *uint_ptr = STATS_GET(mib2.icmpinerrors);
+ return sizeof(*uint_ptr);
+ case 3: /* icmpInDestUnreachs */
+ *uint_ptr = STATS_GET(mib2.icmpindestunreachs);
+ return sizeof(*uint_ptr);
+ case 4: /* icmpInTimeExcds */
+ *uint_ptr = STATS_GET(mib2.icmpintimeexcds);
+ return sizeof(*uint_ptr);
+ case 5: /* icmpInParmProbs */
+ *uint_ptr = STATS_GET(mib2.icmpinparmprobs);
+ return sizeof(*uint_ptr);
+ case 6: /* icmpInSrcQuenchs */
+ *uint_ptr = STATS_GET(mib2.icmpinsrcquenchs);
+ return sizeof(*uint_ptr);
+ case 7: /* icmpInRedirects */
+ *uint_ptr = STATS_GET(mib2.icmpinredirects);
+ return sizeof(*uint_ptr);
+ case 8: /* icmpInEchos */
+ *uint_ptr = STATS_GET(mib2.icmpinechos);
+ return sizeof(*uint_ptr);
+ case 9: /* icmpInEchoReps */
+ *uint_ptr = STATS_GET(mib2.icmpinechoreps);
+ return sizeof(*uint_ptr);
+ case 10: /* icmpInTimestamps */
+ *uint_ptr = STATS_GET(mib2.icmpintimestamps);
+ return sizeof(*uint_ptr);
+ case 11: /* icmpInTimestampReps */
+ *uint_ptr = STATS_GET(mib2.icmpintimestampreps);
+ return sizeof(*uint_ptr);
+ case 12: /* icmpInAddrMasks */
+ *uint_ptr = STATS_GET(mib2.icmpinaddrmasks);
+ return sizeof(*uint_ptr);
+ case 13: /* icmpInAddrMaskReps */
+ *uint_ptr = STATS_GET(mib2.icmpinaddrmaskreps);
+ return sizeof(*uint_ptr);
+ case 14: /* icmpOutMsgs */
+ *uint_ptr = STATS_GET(mib2.icmpoutmsgs);
+ return sizeof(*uint_ptr);
+ case 15: /* icmpOutErrors */
+ *uint_ptr = STATS_GET(mib2.icmpouterrors);
+ return sizeof(*uint_ptr);
+ case 16: /* icmpOutDestUnreachs */
+ *uint_ptr = STATS_GET(mib2.icmpoutdestunreachs);
+ return sizeof(*uint_ptr);
+ case 17: /* icmpOutTimeExcds */
+ *uint_ptr = STATS_GET(mib2.icmpouttimeexcds);
+ return sizeof(*uint_ptr);
+ case 18: /* icmpOutParmProbs: not supported -> always 0 */
+ *uint_ptr = 0;
+ return sizeof(*uint_ptr);
+ case 19: /* icmpOutSrcQuenchs: not supported -> always 0 */
+ *uint_ptr = 0;
+ return sizeof(*uint_ptr);
+ case 20: /* icmpOutRedirects: not supported -> always 0 */
+ *uint_ptr = 0;
+ return sizeof(*uint_ptr);
+ case 21: /* icmpOutEchos */
+ *uint_ptr = STATS_GET(mib2.icmpoutechos);
+ return sizeof(*uint_ptr);
+ case 22: /* icmpOutEchoReps */
+ *uint_ptr = STATS_GET(mib2.icmpoutechoreps);
+ return sizeof(*uint_ptr);
+ case 23: /* icmpOutTimestamps: not supported -> always 0 */
+ *uint_ptr = 0;
+ return sizeof(*uint_ptr);
+ case 24: /* icmpOutTimestampReps: not supported -> always 0 */
+ *uint_ptr = 0;
+ return sizeof(*uint_ptr);
+ case 25: /* icmpOutAddrMasks: not supported -> always 0 */
+ *uint_ptr = 0;
+ return sizeof(*uint_ptr);
+ case 26: /* icmpOutAddrMaskReps: not supported -> always 0 */
+ *uint_ptr = 0;
+ return sizeof(*uint_ptr);
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("icmp_get_value(): unknown id: %"S32_F"\n", node->oid));
+ break;
+ }
+
+ return 0;
+}
+
+
+static const struct snmp_scalar_array_node_def icmp_nodes[] = {
+ { 1, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ { 2, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ { 3, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ { 4, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ { 5, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ { 6, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ { 7, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ { 8, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ { 9, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {10, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {11, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {12, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {13, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {14, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {15, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {16, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {17, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {18, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {19, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {20, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {21, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {22, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {23, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {24, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {25, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY},
+ {26, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}
+};
+
+const struct snmp_scalar_array_node snmp_mib2_icmp_root = SNMP_SCALAR_CREATE_ARRAY_NODE(5, icmp_nodes, icmp_get_value, NULL, NULL);
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_ICMP */
diff --git a/src/apps/snmp/snmp_mib2_interfaces.c b/src/apps/snmp/snmp_mib2_interfaces.c
new file mode 100644
index 00000000000..5f12dd552d3
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_interfaces.c
@@ -0,0 +1,368 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) INTERFACES objects and functions.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ * Christiaan Simons <[email protected]>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/netif.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+ static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+
+/* --- interfaces .1.3.6.1.2.1.2 ----------------------------------------------------- */
+
+static s16_t
+interfaces_get_value(struct snmp_node_instance *instance, void *value)
+{
+ if (instance->node->oid == 1) {
+ s32_t *sint_ptr = (s32_t *)value;
+ s32_t num_netifs = 0;
+
+ struct netif *netif;
+ NETIF_FOREACH(netif) {
+ num_netifs++;
+ }
+
+ *sint_ptr = num_netifs;
+ return sizeof(*sint_ptr);
+ }
+
+ return 0;
+}
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range interfaces_Table_oid_ranges[] = {
+ { 1, 0xff } /* netif->num is u8_t */
+};
+
+static const u8_t iftable_ifOutQLen = 0;
+
+static const u8_t iftable_ifOperStatus_up = 1;
+static const u8_t iftable_ifOperStatus_down = 2;
+
+static const u8_t iftable_ifAdminStatus_up = 1;
+static const u8_t iftable_ifAdminStatus_lowerLayerDown = 7;
+static const u8_t iftable_ifAdminStatus_down = 2;
+
+static snmp_err_t
+interfaces_Table_get_cell_instance(const u32_t *column, const u32_t *row_oid, u8_t row_oid_len, struct snmp_node_instance *cell_instance)
+{
+ u32_t ifIndex;
+ struct netif *netif;
+
+ LWIP_UNUSED_ARG(column);
+
+ /* check if incoming OID length and if values are in plausible range */
+ if (!snmp_oid_in_range(row_oid, row_oid_len, interfaces_Table_oid_ranges, LWIP_ARRAYSIZE(interfaces_Table_oid_ranges))) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* get netif index from incoming OID */
+ ifIndex = row_oid[0];
+
+ /* find netif with index */
+ NETIF_FOREACH(netif) {
+ if (netif_to_num(netif) == ifIndex) {
+ /* store netif pointer for subsequent operations (get/test/set) */
+ cell_instance->reference.ptr = netif;
+ return SNMP_ERR_NOERROR;
+ }
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+interfaces_Table_get_next_cell_instance(const u32_t *column, struct snmp_obj_id *row_oid, struct snmp_node_instance *cell_instance)
+{
+ struct netif *netif;
+ struct snmp_next_oid_state state;
+ u32_t result_temp[LWIP_ARRAYSIZE(interfaces_Table_oid_ranges)];
+
+ LWIP_UNUSED_ARG(column);
+
+ /* init struct to search next oid */
+ snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(interfaces_Table_oid_ranges));
+
+ /* iterate over all possible OIDs to find the next one */
+ NETIF_FOREACH(netif) {
+ u32_t test_oid[LWIP_ARRAYSIZE(interfaces_Table_oid_ranges)];
+ test_oid[0] = netif_to_num(netif);
+
+ /* check generated OID: is it a candidate for the next one? */
+ snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(interfaces_Table_oid_ranges), netif);
+ }
+
+ /* did we find a next one? */
+ if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+ snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+ /* store netif pointer for subsequent operations (get/test/set) */
+ cell_instance->reference.ptr = /* (struct netif*) */state.reference;
+ return SNMP_ERR_NOERROR;
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static s16_t
+interfaces_Table_get_value(struct snmp_node_instance *instance, void *value)
+{
+ struct netif *netif = (struct netif *)instance->reference.ptr;
+ u32_t *value_u32 = (u32_t *)value;
+ s32_t *value_s32 = (s32_t *)value;
+ u16_t value_len;
+
+ switch (SNMP_TABLE_GET_COLUMN_FROM_OID(instance->instance_oid.id)) {
+ case 1: /* ifIndex */
+ *value_s32 = netif_to_num(netif);
+ value_len = sizeof(*value_s32);
+ break;
+ case 2: /* ifDescr */
+ value_len = sizeof(netif->name);
+ MEMCPY(value, netif->name, value_len);
+ break;
+ case 3: /* ifType */
+ *value_s32 = netif->link_type;
+ value_len = sizeof(*value_s32);
+ break;
+ case 4: /* ifMtu */
+ *value_s32 = netif->mtu;
+ value_len = sizeof(*value_s32);
+ break;
+ case 5: /* ifSpeed */
+ *value_u32 = netif->link_speed;
+ value_len = sizeof(*value_u32);
+ break;
+ case 6: /* ifPhysAddress */
+ value_len = sizeof(netif->hwaddr);
+ MEMCPY(value, &netif->hwaddr, value_len);
+ break;
+ case 7: /* ifAdminStatus */
+ if (netif_is_up(netif)) {
+ *value_s32 = iftable_ifOperStatus_up;
+ } else {
+ *value_s32 = iftable_ifOperStatus_down;
+ }
+ value_len = sizeof(*value_s32);
+ break;
+ case 8: /* ifOperStatus */
+ if (netif_is_up(netif)) {
+ if (netif_is_link_up(netif)) {
+ *value_s32 = iftable_ifAdminStatus_up;
+ } else {
+ *value_s32 = iftable_ifAdminStatus_lowerLayerDown;
+ }
+ } else {
+ *value_s32 = iftable_ifAdminStatus_down;
+ }
+ value_len = sizeof(*value_s32);
+ break;
+ case 9: /* ifLastChange */
+ *value_u32 = netif->ts;
+ value_len = sizeof(*value_u32);
+ break;
+ case 10: /* ifInOctets */
+ *value_u32 = netif->mib2_counters.ifinoctets;
+ value_len = sizeof(*value_u32);
+ break;
+ case 11: /* ifInUcastPkts */
+ *value_u32 = netif->mib2_counters.ifinucastpkts;
+ value_len = sizeof(*value_u32);
+ break;
+ case 12: /* ifInNUcastPkts */
+ *value_u32 = netif->mib2_counters.ifinnucastpkts;
+ value_len = sizeof(*value_u32);
+ break;
+ case 13: /* ifInDiscards */
+ *value_u32 = netif->mib2_counters.ifindiscards;
+ value_len = sizeof(*value_u32);
+ break;
+ case 14: /* ifInErrors */
+ *value_u32 = netif->mib2_counters.ifinerrors;
+ value_len = sizeof(*value_u32);
+ break;
+ case 15: /* ifInUnkownProtos */
+ *value_u32 = netif->mib2_counters.ifinunknownprotos;
+ value_len = sizeof(*value_u32);
+ break;
+ case 16: /* ifOutOctets */
+ *value_u32 = netif->mib2_counters.ifoutoctets;
+ value_len = sizeof(*value_u32);
+ break;
+ case 17: /* ifOutUcastPkts */
+ *value_u32 = netif->mib2_counters.ifoutucastpkts;
+ value_len = sizeof(*value_u32);
+ break;
+ case 18: /* ifOutNUcastPkts */
+ *value_u32 = netif->mib2_counters.ifoutnucastpkts;
+ value_len = sizeof(*value_u32);
+ break;
+ case 19: /* ifOutDiscarts */
+ *value_u32 = netif->mib2_counters.ifoutdiscards;
+ value_len = sizeof(*value_u32);
+ break;
+ case 20: /* ifOutErrors */
+ *value_u32 = netif->mib2_counters.ifouterrors;
+ value_len = sizeof(*value_u32);
+ break;
+ case 21: /* ifOutQLen */
+ *value_u32 = iftable_ifOutQLen;
+ value_len = sizeof(*value_u32);
+ break;
+ /** @note returning zeroDotZero (0.0) no media specific MIB support */
+ case 22: /* ifSpecific */
+ value_len = snmp_zero_dot_zero.len * sizeof(u32_t);
+ MEMCPY(value, snmp_zero_dot_zero.id, value_len);
+ break;
+ default:
+ return 0;
+ }
+
+ return value_len;
+}
+
+#if !SNMP_SAFE_REQUESTS
+
+static snmp_err_t
+interfaces_Table_set_test(struct snmp_node_instance *instance, u16_t len, void *value)
+{
+ s32_t *sint_ptr = (s32_t *)value;
+
+ /* stack should never call this method for another column,
+ because all other columns are set to readonly */
+ LWIP_ASSERT("Invalid column", (SNMP_TABLE_GET_COLUMN_FROM_OID(instance->instance_oid.id) == 7));
+ LWIP_UNUSED_ARG(len);
+
+ if (*sint_ptr == 1 || *sint_ptr == 2) {
+ return SNMP_ERR_NOERROR;
+ }
+
+ return SNMP_ERR_WRONGVALUE;
+}
+
+static snmp_err_t
+interfaces_Table_set_value(struct snmp_node_instance *instance, u16_t len, void *value)
+{
+ struct netif *netif = (struct netif *)instance->reference.ptr;
+ s32_t *sint_ptr = (s32_t *)value;
+
+ /* stack should never call this method for another column,
+ because all other columns are set to readonly */
+ LWIP_ASSERT("Invalid column", (SNMP_TABLE_GET_COLUMN_FROM_OID(instance->instance_oid.id) == 7));
+ LWIP_UNUSED_ARG(len);
+
+ if (*sint_ptr == 1) {
+ netif_set_up(netif);
+ } else if (*sint_ptr == 2) {
+ netif_set_down(netif);
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+#endif /* SNMP_SAFE_REQUESTS */
+
+static const struct snmp_scalar_node interfaces_Number = SNMP_SCALAR_CREATE_NODE_READONLY(1, SNMP_ASN1_TYPE_INTEGER, interfaces_get_value);
+
+static const struct snmp_table_col_def interfaces_Table_columns[] = {
+ { 1, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifIndex */
+ { 2, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifDescr */
+ { 3, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifType */
+ { 4, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifMtu */
+ { 5, SNMP_ASN1_TYPE_GAUGE, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifSpeed */
+ { 6, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifPhysAddress */
+#if !SNMP_SAFE_REQUESTS
+ { 7, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_WRITE }, /* ifAdminStatus */
+#else
+ { 7, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifAdminStatus */
+#endif
+ { 8, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOperStatus */
+ { 9, SNMP_ASN1_TYPE_TIMETICKS, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifLastChange */
+ { 10, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInOctets */
+ { 11, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInUcastPkts */
+ { 12, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInNUcastPkts */
+ { 13, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInDiscarts */
+ { 14, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInErrors */
+ { 15, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifInUnkownProtos */
+ { 16, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutOctets */
+ { 17, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutUcastPkts */
+ { 18, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutNUcastPkts */
+ { 19, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutDiscarts */
+ { 20, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutErrors */
+ { 21, SNMP_ASN1_TYPE_GAUGE, SNMP_NODE_INSTANCE_READ_ONLY }, /* ifOutQLen */
+ { 22, SNMP_ASN1_TYPE_OBJECT_ID, SNMP_NODE_INSTANCE_READ_ONLY } /* ifSpecific */
+};
+
+#if !SNMP_SAFE_REQUESTS
+static const struct snmp_table_node interfaces_Table = SNMP_TABLE_CREATE(
+ 2, interfaces_Table_columns,
+ interfaces_Table_get_cell_instance, interfaces_Table_get_next_cell_instance,
+ interfaces_Table_get_value, interfaces_Table_set_test, interfaces_Table_set_value);
+#else
+static const struct snmp_table_node interfaces_Table = SNMP_TABLE_CREATE(
+ 2, interfaces_Table_columns,
+ interfaces_Table_get_cell_instance, interfaces_Table_get_next_cell_instance,
+ interfaces_Table_get_value, NULL, NULL);
+#endif
+
+/* the following nodes access variables in LWIP stack from SNMP worker thread and must therefore be synced to LWIP (TCPIP) thread */
+CREATE_LWIP_SYNC_NODE(1, interfaces_Number)
+CREATE_LWIP_SYNC_NODE(2, interfaces_Table)
+
+static const struct snmp_node *const interface_nodes[] = {
+ &SYNC_NODE_NAME(interfaces_Number).node.node,
+ &SYNC_NODE_NAME(interfaces_Table).node.node
+};
+
+const struct snmp_tree_node snmp_mib2_interface_root = SNMP_CREATE_TREE_NODE(2, interface_nodes);
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 */
diff --git a/src/apps/snmp/snmp_mib2_ip.c b/src/apps/snmp/snmp_mib2_ip.c
new file mode 100644
index 00000000000..57cf0f5ad6f
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_ip.c
@@ -0,0 +1,731 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) IP objects and functions.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ * Christiaan Simons <[email protected]>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/stats.h"
+#include "lwip/netif.h"
+#include "lwip/ip.h"
+#include "lwip/etharp.h"
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+ static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+#if LWIP_IPV4
+/* --- ip .1.3.6.1.2.1.4 ----------------------------------------------------- */
+
+static s16_t
+ip_get_value(struct snmp_node_instance *instance, void *value)
+{
+ s32_t *sint_ptr = (s32_t *)value;
+ u32_t *uint_ptr = (u32_t *)value;
+
+ switch (instance->node->oid) {
+ case 1: /* ipForwarding */
+#if IP_FORWARD
+ /* forwarding */
+ *sint_ptr = 1;
+#else
+ /* not-forwarding */
+ *sint_ptr = 2;
+#endif
+ return sizeof(*sint_ptr);
+ case 2: /* ipDefaultTTL */
+ *sint_ptr = IP_DEFAULT_TTL;
+ return sizeof(*sint_ptr);
+ case 3: /* ipInReceives */
+ *uint_ptr = STATS_GET(mib2.ipinreceives);
+ return sizeof(*uint_ptr);
+ case 4: /* ipInHdrErrors */
+ *uint_ptr = STATS_GET(mib2.ipinhdrerrors);
+ return sizeof(*uint_ptr);
+ case 5: /* ipInAddrErrors */
+ *uint_ptr = STATS_GET(mib2.ipinaddrerrors);
+ return sizeof(*uint_ptr);
+ case 6: /* ipForwDatagrams */
+ *uint_ptr = STATS_GET(mib2.ipforwdatagrams);
+ return sizeof(*uint_ptr);
+ case 7: /* ipInUnknownProtos */
+ *uint_ptr = STATS_GET(mib2.ipinunknownprotos);
+ return sizeof(*uint_ptr);
+ case 8: /* ipInDiscards */
+ *uint_ptr = STATS_GET(mib2.ipindiscards);
+ return sizeof(*uint_ptr);
+ case 9: /* ipInDelivers */
+ *uint_ptr = STATS_GET(mib2.ipindelivers);
+ return sizeof(*uint_ptr);
+ case 10: /* ipOutRequests */
+ *uint_ptr = STATS_GET(mib2.ipoutrequests);
+ return sizeof(*uint_ptr);
+ case 11: /* ipOutDiscards */
+ *uint_ptr = STATS_GET(mib2.ipoutdiscards);
+ return sizeof(*uint_ptr);
+ case 12: /* ipOutNoRoutes */
+ *uint_ptr = STATS_GET(mib2.ipoutnoroutes);
+ return sizeof(*uint_ptr);
+ case 13: /* ipReasmTimeout */
+#if IP_REASSEMBLY
+ *sint_ptr = IP_REASS_MAXAGE;
+#else
+ *sint_ptr = 0;
+#endif
+ return sizeof(*sint_ptr);
+ case 14: /* ipReasmReqds */
+ *uint_ptr = STATS_GET(mib2.ipreasmreqds);
+ return sizeof(*uint_ptr);
+ case 15: /* ipReasmOKs */
+ *uint_ptr = STATS_GET(mib2.ipreasmoks);
+ return sizeof(*uint_ptr);
+ case 16: /* ipReasmFails */
+ *uint_ptr = STATS_GET(mib2.ipreasmfails);
+ return sizeof(*uint_ptr);
+ case 17: /* ipFragOKs */
+ *uint_ptr = STATS_GET(mib2.ipfragoks);
+ return sizeof(*uint_ptr);
+ case 18: /* ipFragFails */
+ *uint_ptr = STATS_GET(mib2.ipfragfails);
+ return sizeof(*uint_ptr);
+ case 19: /* ipFragCreates */
+ *uint_ptr = STATS_GET(mib2.ipfragcreates);
+ return sizeof(*uint_ptr);
+ case 23: /* ipRoutingDiscards: not supported -> always 0 */
+ *uint_ptr = 0;
+ return sizeof(*uint_ptr);
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("ip_get_value(): unknown id: %"S32_F"\n", instance->node->oid));
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * Test ip object value before setting.
+ *
+ * @param instance node instance
+ * @param len return value space (in bytes)
+ * @param value points to (varbind) space to copy value from.
+ *
+ * @note we allow set if the value matches the hardwired value,
+ * otherwise return badvalue.
+ */
+static snmp_err_t
+ip_set_test(struct snmp_node_instance *instance, u16_t len, void *value)
+{
+ snmp_err_t ret = SNMP_ERR_WRONGVALUE;
+ s32_t *sint_ptr = (s32_t *)value;
+
+ LWIP_UNUSED_ARG(len);
+ switch (instance->node->oid) {
+ case 1: /* ipForwarding */
+#if IP_FORWARD
+ /* forwarding */
+ if (*sint_ptr == 1)
+#else
+ /* not-forwarding */
+ if (*sint_ptr == 2)
+#endif
+ {
+ ret = SNMP_ERR_NOERROR;
+ }
+ break;
+ case 2: /* ipDefaultTTL */
+ if (*sint_ptr == IP_DEFAULT_TTL) {
+ ret = SNMP_ERR_NOERROR;
+ }
+ break;
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("ip_set_test(): unknown id: %"S32_F"\n", instance->node->oid));
+ break;
+ }
+
+ return ret;
+}
+
+static snmp_err_t
+ip_set_value(struct snmp_node_instance *instance, u16_t len, void *value)
+{
+ LWIP_UNUSED_ARG(instance);
+ LWIP_UNUSED_ARG(len);
+ LWIP_UNUSED_ARG(value);
+ /* nothing to do here because in set_test we only accept values being the same as our own stored value -> no need to store anything */
+ return SNMP_ERR_NOERROR;
+}
+
+/* --- ipAddrTable --- */
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range ip_AddrTable_oid_ranges[] = {
+ { 0, 0xff }, /* IP A */
+ { 0, 0xff }, /* IP B */
+ { 0, 0xff }, /* IP C */
+ { 0, 0xff } /* IP D */
+};
+
+static snmp_err_t
+ip_AddrTable_get_cell_value_core(struct netif *netif, const u32_t *column, union snmp_variant_value *value, u32_t *value_len)
+{
+ LWIP_UNUSED_ARG(value_len);
+
+ switch (*column) {
+ case 1: /* ipAdEntAddr */
+ value->u32 = netif_ip4_addr(netif)->addr;
+ break;
+ case 2: /* ipAdEntIfIndex */
+ value->u32 = netif_to_num(netif);
+ break;
+ case 3: /* ipAdEntNetMask */
+ value->u32 = netif_ip4_netmask(netif)->addr;
+ break;
+ case 4: /* ipAdEntBcastAddr */
+ /* lwIP oddity, there's no broadcast
+ address in the netif we can rely on */
+ value->u32 = IPADDR_BROADCAST & 1;
+ break;
+ case 5: /* ipAdEntReasmMaxSize */
+#if IP_REASSEMBLY
+ /* @todo The theoretical maximum is IP_REASS_MAX_PBUFS * size of the pbufs,
+ * but only if receiving one fragmented packet at a time.
+ * The current solution is to calculate for 2 simultaneous packets...
+ */
+ value->u32 = (IP_HLEN + ((IP_REASS_MAX_PBUFS / 2) *
+ (PBUF_POOL_BUFSIZE - PBUF_LINK_ENCAPSULATION_HLEN - PBUF_LINK_HLEN - IP_HLEN)));
+#else
+ /** @todo returning MTU would be a bad thing and
+ returning a wild guess like '576' isn't good either */
+ value->u32 = 0;
+#endif
+ break;
+ default:
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+ip_AddrTable_get_cell_value(const u32_t *column, const u32_t *row_oid, u8_t row_oid_len, union snmp_variant_value *value, u32_t *value_len)
+{
+ ip4_addr_t ip;
+ struct netif *netif;
+
+ /* check if incoming OID length and if values are in plausible range */
+ if (!snmp_oid_in_range(row_oid, row_oid_len, ip_AddrTable_oid_ranges, LWIP_ARRAYSIZE(ip_AddrTable_oid_ranges))) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* get IP from incoming OID */
+ snmp_oid_to_ip4(&row_oid[0], &ip); /* we know it succeeds because of oid_in_range check above */
+
+ /* find netif with requested ip */
+ NETIF_FOREACH(netif) {
+ if (ip4_addr_eq(&ip, netif_ip4_addr(netif))) {
+ /* fill in object properties */
+ return ip_AddrTable_get_cell_value_core(netif, column, value, value_len);
+ }
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+ip_AddrTable_get_next_cell_instance_and_value(const u32_t *column, struct snmp_obj_id *row_oid, union snmp_variant_value *value, u32_t *value_len)
+{
+ struct netif *netif;
+ struct snmp_next_oid_state state;
+ u32_t result_temp[LWIP_ARRAYSIZE(ip_AddrTable_oid_ranges)];
+
+ /* init struct to search next oid */
+ snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(ip_AddrTable_oid_ranges));
+
+ /* iterate over all possible OIDs to find the next one */
+ NETIF_FOREACH(netif) {
+ u32_t test_oid[LWIP_ARRAYSIZE(ip_AddrTable_oid_ranges)];
+ snmp_ip4_to_oid(netif_ip4_addr(netif), &test_oid[0]);
+
+ /* check generated OID: is it a candidate for the next one? */
+ snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(ip_AddrTable_oid_ranges), netif);
+ }
+
+ /* did we find a next one? */
+ if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+ snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+ /* fill in object properties */
+ return ip_AddrTable_get_cell_value_core((struct netif *)state.reference, column, value, value_len);
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+/* --- ipRouteTable --- */
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range ip_RouteTable_oid_ranges[] = {
+ { 0, 0xff }, /* IP A */
+ { 0, 0xff }, /* IP B */
+ { 0, 0xff }, /* IP C */
+ { 0, 0xff }, /* IP D */
+};
+
+static snmp_err_t
+ip_RouteTable_get_cell_value_core(struct netif *netif, u8_t default_route, const u32_t *column, union snmp_variant_value *value, u32_t *value_len)
+{
+ switch (*column) {
+ case 1: /* ipRouteDest */
+ if (default_route) {
+ /* default rte has 0.0.0.0 dest */
+ value->u32 = IP4_ADDR_ANY4->addr;
+ } else {
+ /* netifs have netaddress dest */
+ ip4_addr_t tmp;
+ ip4_addr_get_network(&tmp, netif_ip4_addr(netif), netif_ip4_netmask(netif));
+ value->u32 = tmp.addr;
+ }
+ break;
+ case 2: /* ipRouteIfIndex */
+ value->u32 = netif_to_num(netif);
+ break;
+ case 3: /* ipRouteMetric1 */
+ if (default_route) {
+ value->s32 = 1; /* default */
+ } else {
+ value->s32 = 0; /* normal */
+ }
+ break;
+ case 4: /* ipRouteMetric2 */
+ case 5: /* ipRouteMetric3 */
+ case 6: /* ipRouteMetric4 */
+ value->s32 = -1; /* none */
+ break;
+ case 7: /* ipRouteNextHop */
+ if (default_route) {
+ /* default rte: gateway */
+ value->u32 = netif_ip4_gw(netif)->addr;
+ } else {
+ /* other rtes: netif ip_addr */
+ value->u32 = netif_ip4_addr(netif)->addr;
+ }
+ break;
+ case 8: /* ipRouteType */
+ if (default_route) {
+ /* default rte is indirect */
+ value->u32 = 4; /* indirect */
+ } else {
+ /* other rtes are direct */
+ value->u32 = 3; /* direct */
+ }
+ break;
+ case 9: /* ipRouteProto */
+ /* locally defined routes */
+ value->u32 = 2; /* local */
+ break;
+ case 10: /* ipRouteAge */
+ /* @todo (sysuptime - timestamp last change) / 100 */
+ value->u32 = 0;
+ break;
+ case 11: /* ipRouteMask */
+ if (default_route) {
+ /* default rte use 0.0.0.0 mask */
+ value->u32 = IP4_ADDR_ANY4->addr;
+ } else {
+ /* other rtes use netmask */
+ value->u32 = netif_ip4_netmask(netif)->addr;
+ }
+ break;
+ case 12: /* ipRouteMetric5 */
+ value->s32 = -1; /* none */
+ break;
+ case 13: /* ipRouteInfo */
+ value->const_ptr = snmp_zero_dot_zero.id;
+ *value_len = snmp_zero_dot_zero.len * sizeof(u32_t);
+ break;
+ default:
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+ip_RouteTable_get_cell_value(const u32_t *column, const u32_t *row_oid, u8_t row_oid_len, union snmp_variant_value *value, u32_t *value_len)
+{
+ ip4_addr_t test_ip;
+ struct netif *netif;
+
+ /* check if incoming OID length and if values are in plausible range */
+ if (!snmp_oid_in_range(row_oid, row_oid_len, ip_RouteTable_oid_ranges, LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges))) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* get IP and port from incoming OID */
+ snmp_oid_to_ip4(&row_oid[0], &test_ip); /* we know it succeeds because of oid_in_range check above */
+
+ /* default route is on default netif */
+ if (ip4_addr_isany_val(test_ip) && (netif_default != NULL)) {
+ /* fill in object properties */
+ return ip_RouteTable_get_cell_value_core(netif_default, 1, column, value, value_len);
+ }
+
+ /* find netif with requested route */
+ NETIF_FOREACH(netif) {
+ ip4_addr_t dst;
+ ip4_addr_get_network(&dst, netif_ip4_addr(netif), netif_ip4_netmask(netif));
+
+ if (ip4_addr_eq(&dst, &test_ip)) {
+ /* fill in object properties */
+ return ip_RouteTable_get_cell_value_core(netif, 0, column, value, value_len);
+ }
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+ip_RouteTable_get_next_cell_instance_and_value(const u32_t *column, struct snmp_obj_id *row_oid, union snmp_variant_value *value, u32_t *value_len)
+{
+ struct netif *netif;
+ struct snmp_next_oid_state state;
+ u32_t result_temp[LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges)];
+ u32_t test_oid[LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges)];
+
+ /* init struct to search next oid */
+ snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges));
+
+ /* check default route */
+ if (netif_default != NULL) {
+ snmp_ip4_to_oid(IP4_ADDR_ANY4, &test_oid[0]);
+ snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges), netif_default);
+ }
+
+ /* iterate over all possible OIDs to find the next one */
+ NETIF_FOREACH(netif) {
+ ip4_addr_t dst;
+ ip4_addr_get_network(&dst, netif_ip4_addr(netif), netif_ip4_netmask(netif));
+
+ /* check generated OID: is it a candidate for the next one? */
+ if (!ip4_addr_isany_val(dst)) {
+ snmp_ip4_to_oid(&dst, &test_oid[0]);
+ snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(ip_RouteTable_oid_ranges), netif);
+ }
+ }
+
+ /* did we find a next one? */
+ if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+ ip4_addr_t dst;
+ snmp_oid_to_ip4(&result_temp[0], &dst);
+ snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+ /* fill in object properties */
+ return ip_RouteTable_get_cell_value_core((struct netif *)state.reference, ip4_addr_isany_val(dst), column, value, value_len);
+ } else {
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+}
+
+#if LWIP_ARP && LWIP_IPV4
+/* --- ipNetToMediaTable --- */
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range ip_NetToMediaTable_oid_ranges[] = {
+ { 1, 0xff }, /* IfIndex */
+ { 0, 0xff }, /* IP A */
+ { 0, 0xff }, /* IP B */
+ { 0, 0xff }, /* IP C */
+ { 0, 0xff } /* IP D */
+};
+
+static snmp_err_t
+ip_NetToMediaTable_get_cell_value_core(size_t arp_table_index, const u32_t *column, union snmp_variant_value *value, u32_t *value_len)
+{
+ ip4_addr_t *ip;
+ struct netif *netif;
+ struct eth_addr *ethaddr;
+
+ etharp_get_entry(arp_table_index, &ip, &netif, &ethaddr);
+
+ /* value */
+ switch (*column) {
+ case 1: /* atIfIndex / ipNetToMediaIfIndex */
+ value->u32 = netif_to_num(netif);
+ break;
+ case 2: /* atPhysAddress / ipNetToMediaPhysAddress */
+ value->ptr = ethaddr;
+ *value_len = sizeof(*ethaddr);
+ break;
+ case 3: /* atNetAddress / ipNetToMediaNetAddress */
+ value->u32 = ip->addr;
+ break;
+ case 4: /* ipNetToMediaType */
+ value->u32 = 3; /* dynamic*/
+ break;
+ default:
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+ip_NetToMediaTable_get_cell_value(const u32_t *column, const u32_t *row_oid, u8_t row_oid_len, union snmp_variant_value *value, u32_t *value_len)
+{
+ ip4_addr_t ip_in;
+ u8_t netif_index;
+ size_t i;
+
+ /* check if incoming OID length and if values are in plausible range */
+ if (!snmp_oid_in_range(row_oid, row_oid_len, ip_NetToMediaTable_oid_ranges, LWIP_ARRAYSIZE(ip_NetToMediaTable_oid_ranges))) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* get IP from incoming OID */
+ netif_index = (u8_t)row_oid[0];
+ snmp_oid_to_ip4(&row_oid[1], &ip_in); /* we know it succeeds because of oid_in_range check above */
+
+ /* find requested entry */
+ for (i = 0; i < ARP_TABLE_SIZE; i++) {
+ ip4_addr_t *ip;
+ struct netif *netif;
+ struct eth_addr *ethaddr;
+
+ if (etharp_get_entry(i, &ip, &netif, &ethaddr)) {
+ if ((netif_index == netif_to_num(netif)) && ip4_addr_eq(&ip_in, ip)) {
+ /* fill in object properties */
+ return ip_NetToMediaTable_get_cell_value_core(i, column, value, value_len);
+ }
+ }
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+ip_NetToMediaTable_get_next_cell_instance_and_value(const u32_t *column, struct snmp_obj_id *row_oid, union snmp_variant_value *value, u32_t *value_len)
+{
+ size_t i;
+ struct snmp_next_oid_state state;
+ u32_t result_temp[LWIP_ARRAYSIZE(ip_NetToMediaTable_oid_ranges)];
+
+ /* init struct to search next oid */
+ snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(ip_NetToMediaTable_oid_ranges));
+
+ /* iterate over all possible OIDs to find the next one */
+ for (i = 0; i < ARP_TABLE_SIZE; i++) {
+ ip4_addr_t *ip;
+ struct netif *netif;
+ struct eth_addr *ethaddr;
+
+ if (etharp_get_entry(i, &ip, &netif, &ethaddr)) {
+ u32_t test_oid[LWIP_ARRAYSIZE(ip_NetToMediaTable_oid_ranges)];
+
+ test_oid[0] = netif_to_num(netif);
+ snmp_ip4_to_oid(ip, &test_oid[1]);
+
+ /* check generated OID: is it a candidate for the next one? */
+ snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(ip_NetToMediaTable_oid_ranges), LWIP_PTR_NUMERIC_CAST(void *, i));
+ }
+ }
+
+ /* did we find a next one? */
+ if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+ snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+ /* fill in object properties */
+ return ip_NetToMediaTable_get_cell_value_core(LWIP_PTR_NUMERIC_CAST(size_t, state.reference), column, value, value_len);
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+#endif /* LWIP_ARP && LWIP_IPV4 */
+
+static const struct snmp_scalar_node ip_Forwarding = SNMP_SCALAR_CREATE_NODE(1, SNMP_NODE_INSTANCE_READ_WRITE, SNMP_ASN1_TYPE_INTEGER, ip_get_value, ip_set_test, ip_set_value);
+static const struct snmp_scalar_node ip_DefaultTTL = SNMP_SCALAR_CREATE_NODE(2, SNMP_NODE_INSTANCE_READ_WRITE, SNMP_ASN1_TYPE_INTEGER, ip_get_value, ip_set_test, ip_set_value);
+static const struct snmp_scalar_node ip_InReceives = SNMP_SCALAR_CREATE_NODE_READONLY(3, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_InHdrErrors = SNMP_SCALAR_CREATE_NODE_READONLY(4, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_InAddrErrors = SNMP_SCALAR_CREATE_NODE_READONLY(5, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_ForwDatagrams = SNMP_SCALAR_CREATE_NODE_READONLY(6, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_InUnknownProtos = SNMP_SCALAR_CREATE_NODE_READONLY(7, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_InDiscards = SNMP_SCALAR_CREATE_NODE_READONLY(8, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_InDelivers = SNMP_SCALAR_CREATE_NODE_READONLY(9, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_OutRequests = SNMP_SCALAR_CREATE_NODE_READONLY(10, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_OutDiscards = SNMP_SCALAR_CREATE_NODE_READONLY(11, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_OutNoRoutes = SNMP_SCALAR_CREATE_NODE_READONLY(12, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_ReasmTimeout = SNMP_SCALAR_CREATE_NODE_READONLY(13, SNMP_ASN1_TYPE_INTEGER, ip_get_value);
+static const struct snmp_scalar_node ip_ReasmReqds = SNMP_SCALAR_CREATE_NODE_READONLY(14, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_ReasmOKs = SNMP_SCALAR_CREATE_NODE_READONLY(15, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_ReasmFails = SNMP_SCALAR_CREATE_NODE_READONLY(16, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_FragOKs = SNMP_SCALAR_CREATE_NODE_READONLY(17, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_FragFails = SNMP_SCALAR_CREATE_NODE_READONLY(18, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_FragCreates = SNMP_SCALAR_CREATE_NODE_READONLY(19, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+static const struct snmp_scalar_node ip_RoutingDiscards = SNMP_SCALAR_CREATE_NODE_READONLY(23, SNMP_ASN1_TYPE_COUNTER, ip_get_value);
+
+static const struct snmp_table_simple_col_def ip_AddrTable_columns[] = {
+ { 1, SNMP_ASN1_TYPE_IPADDR, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipAdEntAddr */
+ { 2, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipAdEntIfIndex */
+ { 3, SNMP_ASN1_TYPE_IPADDR, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipAdEntNetMask */
+ { 4, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipAdEntBcastAddr */
+ { 5, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 } /* ipAdEntReasmMaxSize */
+};
+
+static const struct snmp_table_simple_node ip_AddrTable = SNMP_TABLE_CREATE_SIMPLE(20, ip_AddrTable_columns, ip_AddrTable_get_cell_value, ip_AddrTable_get_next_cell_instance_and_value);
+
+static const struct snmp_table_simple_col_def ip_RouteTable_columns[] = {
+ { 1, SNMP_ASN1_TYPE_IPADDR, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteDest */
+ { 2, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteIfIndex */
+ { 3, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_S32 }, /* ipRouteMetric1 */
+ { 4, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_S32 }, /* ipRouteMetric2 */
+ { 5, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_S32 }, /* ipRouteMetric3 */
+ { 6, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_S32 }, /* ipRouteMetric4 */
+ { 7, SNMP_ASN1_TYPE_IPADDR, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteNextHop */
+ { 8, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteType */
+ { 9, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteProto */
+ { 10, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteAge */
+ { 11, SNMP_ASN1_TYPE_IPADDR, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipRouteMask */
+ { 12, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_S32 }, /* ipRouteMetric5 */
+ { 13, SNMP_ASN1_TYPE_OBJECT_ID, SNMP_VARIANT_VALUE_TYPE_PTR } /* ipRouteInfo */
+};
+
+static const struct snmp_table_simple_node ip_RouteTable = SNMP_TABLE_CREATE_SIMPLE(21, ip_RouteTable_columns, ip_RouteTable_get_cell_value, ip_RouteTable_get_next_cell_instance_and_value);
+#endif /* LWIP_IPV4 */
+
+#if LWIP_ARP && LWIP_IPV4
+static const struct snmp_table_simple_col_def ip_NetToMediaTable_columns[] = {
+ { 1, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipNetToMediaIfIndex */
+ { 2, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_VARIANT_VALUE_TYPE_PTR }, /* ipNetToMediaPhysAddress */
+ { 3, SNMP_ASN1_TYPE_IPADDR, SNMP_VARIANT_VALUE_TYPE_U32 }, /* ipNetToMediaNetAddress */
+ { 4, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 } /* ipNetToMediaType */
+};
+
+static const struct snmp_table_simple_node ip_NetToMediaTable = SNMP_TABLE_CREATE_SIMPLE(22, ip_NetToMediaTable_columns, ip_NetToMediaTable_get_cell_value, ip_NetToMediaTable_get_next_cell_instance_and_value);
+#endif /* LWIP_ARP && LWIP_IPV4 */
+
+#if LWIP_IPV4
+/* the following nodes access variables in LWIP stack from SNMP worker thread and must therefore be synced to LWIP (TCPIP) thread */
+CREATE_LWIP_SYNC_NODE( 1, ip_Forwarding)
+CREATE_LWIP_SYNC_NODE( 2, ip_DefaultTTL)
+CREATE_LWIP_SYNC_NODE( 3, ip_InReceives)
+CREATE_LWIP_SYNC_NODE( 4, ip_InHdrErrors)
+CREATE_LWIP_SYNC_NODE( 5, ip_InAddrErrors)
+CREATE_LWIP_SYNC_NODE( 6, ip_ForwDatagrams)
+CREATE_LWIP_SYNC_NODE( 7, ip_InUnknownProtos)
+CREATE_LWIP_SYNC_NODE( 8, ip_InDiscards)
+CREATE_LWIP_SYNC_NODE( 9, ip_InDelivers)
+CREATE_LWIP_SYNC_NODE(10, ip_OutRequests)
+CREATE_LWIP_SYNC_NODE(11, ip_OutDiscards)
+CREATE_LWIP_SYNC_NODE(12, ip_OutNoRoutes)
+CREATE_LWIP_SYNC_NODE(13, ip_ReasmTimeout)
+CREATE_LWIP_SYNC_NODE(14, ip_ReasmReqds)
+CREATE_LWIP_SYNC_NODE(15, ip_ReasmOKs)
+CREATE_LWIP_SYNC_NODE(15, ip_ReasmFails)
+CREATE_LWIP_SYNC_NODE(17, ip_FragOKs)
+CREATE_LWIP_SYNC_NODE(18, ip_FragFails)
+CREATE_LWIP_SYNC_NODE(19, ip_FragCreates)
+CREATE_LWIP_SYNC_NODE(20, ip_AddrTable)
+CREATE_LWIP_SYNC_NODE(21, ip_RouteTable)
+#if LWIP_ARP
+CREATE_LWIP_SYNC_NODE(22, ip_NetToMediaTable)
+#endif /* LWIP_ARP */
+CREATE_LWIP_SYNC_NODE(23, ip_RoutingDiscards)
+
+static const struct snmp_node *const ip_nodes[] = {
+ &SYNC_NODE_NAME(ip_Forwarding).node.node,
+ &SYNC_NODE_NAME(ip_DefaultTTL).node.node,
+ &SYNC_NODE_NAME(ip_InReceives).node.node,
+ &SYNC_NODE_NAME(ip_InHdrErrors).node.node,
+ &SYNC_NODE_NAME(ip_InAddrErrors).node.node,
+ &SYNC_NODE_NAME(ip_ForwDatagrams).node.node,
+ &SYNC_NODE_NAME(ip_InUnknownProtos).node.node,
+ &SYNC_NODE_NAME(ip_InDiscards).node.node,
+ &SYNC_NODE_NAME(ip_InDelivers).node.node,
+ &SYNC_NODE_NAME(ip_OutRequests).node.node,
+ &SYNC_NODE_NAME(ip_OutDiscards).node.node,
+ &SYNC_NODE_NAME(ip_OutNoRoutes).node.node,
+ &SYNC_NODE_NAME(ip_ReasmTimeout).node.node,
+ &SYNC_NODE_NAME(ip_ReasmReqds).node.node,
+ &SYNC_NODE_NAME(ip_ReasmOKs).node.node,
+ &SYNC_NODE_NAME(ip_ReasmFails).node.node,
+ &SYNC_NODE_NAME(ip_FragOKs).node.node,
+ &SYNC_NODE_NAME(ip_FragFails).node.node,
+ &SYNC_NODE_NAME(ip_FragCreates).node.node,
+ &SYNC_NODE_NAME(ip_AddrTable).node.node,
+ &SYNC_NODE_NAME(ip_RouteTable).node.node,
+#if LWIP_ARP
+ &SYNC_NODE_NAME(ip_NetToMediaTable).node.node,
+#endif /* LWIP_ARP */
+ &SYNC_NODE_NAME(ip_RoutingDiscards).node.node
+};
+
+const struct snmp_tree_node snmp_mib2_ip_root = SNMP_CREATE_TREE_NODE(4, ip_nodes);
+#endif /* LWIP_IPV4 */
+
+/* --- at .1.3.6.1.2.1.3 ----------------------------------------------------- */
+
+#if LWIP_ARP && LWIP_IPV4
+/* at node table is a subset of ip_nettomedia table (same rows but less columns) */
+static const struct snmp_table_simple_col_def at_Table_columns[] = {
+ { 1, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* atIfIndex */
+ { 2, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_VARIANT_VALUE_TYPE_PTR }, /* atPhysAddress */
+ { 3, SNMP_ASN1_TYPE_IPADDR, SNMP_VARIANT_VALUE_TYPE_U32 } /* atNetAddress */
+};
+
+static const struct snmp_table_simple_node at_Table = SNMP_TABLE_CREATE_SIMPLE(1, at_Table_columns, ip_NetToMediaTable_get_cell_value, ip_NetToMediaTable_get_next_cell_instance_and_value);
+
+/* the following nodes access variables in LWIP stack from SNMP worker thread and must therefore be synced to LWIP (TCPIP) thread */
+CREATE_LWIP_SYNC_NODE(1, at_Table)
+
+static const struct snmp_node *const at_nodes[] = {
+ &SYNC_NODE_NAME(at_Table).node.node
+};
+
+const struct snmp_tree_node snmp_mib2_at_root = SNMP_CREATE_TREE_NODE(3, at_nodes);
+#endif /* LWIP_ARP && LWIP_IPV4 */
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 */
diff --git a/src/apps/snmp/snmp_mib2_snmp.c b/src/apps/snmp/snmp_mib2_snmp.c
new file mode 100644
index 00000000000..ca22e416365
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_snmp.c
@@ -0,0 +1,227 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) SNMP objects and functions.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ * Christiaan Simons <[email protected]>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_scalar.h"
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2
+
+#define MIB2_AUTH_TRAPS_ENABLED 1
+#define MIB2_AUTH_TRAPS_DISABLED 2
+
+/* --- snmp .1.3.6.1.2.1.11 ----------------------------------------------------- */
+static s16_t
+snmp_get_value(const struct snmp_scalar_array_node_def *node, void *value)
+{
+ u32_t *uint_ptr = (u32_t *)value;
+ switch (node->oid) {
+ case 1: /* snmpInPkts */
+ *uint_ptr = snmp_stats.inpkts;
+ break;
+ case 2: /* snmpOutPkts */
+ *uint_ptr = snmp_stats.outpkts;
+ break;
+ case 3: /* snmpInBadVersions */
+ *uint_ptr = snmp_stats.inbadversions;
+ break;
+ case 4: /* snmpInBadCommunityNames */
+ *uint_ptr = snmp_stats.inbadcommunitynames;
+ break;
+ case 5: /* snmpInBadCommunityUses */
+ *uint_ptr = snmp_stats.inbadcommunityuses;
+ break;
+ case 6: /* snmpInASNParseErrs */
+ *uint_ptr = snmp_stats.inasnparseerrs;
+ break;
+ case 8: /* snmpInTooBigs */
+ *uint_ptr = snmp_stats.intoobigs;
+ break;
+ case 9: /* snmpInNoSuchNames */
+ *uint_ptr = snmp_stats.innosuchnames;
+ break;
+ case 10: /* snmpInBadValues */
+ *uint_ptr = snmp_stats.inbadvalues;
+ break;
+ case 11: /* snmpInReadOnlys */
+ *uint_ptr = snmp_stats.inreadonlys;
+ break;
+ case 12: /* snmpInGenErrs */
+ *uint_ptr = snmp_stats.ingenerrs;
+ break;
+ case 13: /* snmpInTotalReqVars */
+ *uint_ptr = snmp_stats.intotalreqvars;
+ break;
+ case 14: /* snmpInTotalSetVars */
+ *uint_ptr = snmp_stats.intotalsetvars;
+ break;
+ case 15: /* snmpInGetRequests */
+ *uint_ptr = snmp_stats.ingetrequests;
+ break;
+ case 16: /* snmpInGetNexts */
+ *uint_ptr = snmp_stats.ingetnexts;
+ break;
+ case 17: /* snmpInSetRequests */
+ *uint_ptr = snmp_stats.insetrequests;
+ break;
+ case 18: /* snmpInGetResponses */
+ *uint_ptr = snmp_stats.ingetresponses;
+ break;
+ case 19: /* snmpInTraps */
+ *uint_ptr = snmp_stats.intraps;
+ break;
+ case 20: /* snmpOutTooBigs */
+ *uint_ptr = snmp_stats.outtoobigs;
+ break;
+ case 21: /* snmpOutNoSuchNames */
+ *uint_ptr = snmp_stats.outnosuchnames;
+ break;
+ case 22: /* snmpOutBadValues */
+ *uint_ptr = snmp_stats.outbadvalues;
+ break;
+ case 24: /* snmpOutGenErrs */
+ *uint_ptr = snmp_stats.outgenerrs;
+ break;
+ case 25: /* snmpOutGetRequests */
+ *uint_ptr = snmp_stats.outgetrequests;
+ break;
+ case 26: /* snmpOutGetNexts */
+ *uint_ptr = snmp_stats.outgetnexts;
+ break;
+ case 27: /* snmpOutSetRequests */
+ *uint_ptr = snmp_stats.outsetrequests;
+ break;
+ case 28: /* snmpOutGetResponses */
+ *uint_ptr = snmp_stats.outgetresponses;
+ break;
+ case 29: /* snmpOutTraps */
+ *uint_ptr = snmp_stats.outtraps;
+ break;
+ case 30: /* snmpEnableAuthenTraps */
+ if (snmp_get_auth_traps_enabled() == SNMP_AUTH_TRAPS_DISABLED) {
+ *uint_ptr = MIB2_AUTH_TRAPS_DISABLED;
+ } else {
+ *uint_ptr = MIB2_AUTH_TRAPS_ENABLED;
+ }
+ break;
+ case 31: /* snmpSilentDrops */
+ *uint_ptr = 0; /* not supported */
+ break;
+ case 32: /* snmpProxyDrops */
+ *uint_ptr = 0; /* not supported */
+ break;
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("snmp_get_value(): unknown id: %"S32_F"\n", node->oid));
+ return 0;
+ }
+
+ return sizeof(*uint_ptr);
+}
+
+static snmp_err_t
+snmp_set_test(const struct snmp_scalar_array_node_def *node, u16_t len, void *value)
+{
+ snmp_err_t ret = SNMP_ERR_WRONGVALUE;
+ LWIP_UNUSED_ARG(len);
+
+ if (node->oid == 30) {
+ /* snmpEnableAuthenTraps */
+ s32_t *sint_ptr = (s32_t *)value;
+
+ /* we should have writable non-volatile mem here */
+ if ((*sint_ptr == MIB2_AUTH_TRAPS_DISABLED) || (*sint_ptr == MIB2_AUTH_TRAPS_ENABLED)) {
+ ret = SNMP_ERR_NOERROR;
+ }
+ }
+ return ret;
+}
+
+static snmp_err_t
+snmp_set_value(const struct snmp_scalar_array_node_def *node, u16_t len, void *value)
+{
+ LWIP_UNUSED_ARG(len);
+
+ if (node->oid == 30) {
+ /* snmpEnableAuthenTraps */
+ s32_t *sint_ptr = (s32_t *)value;
+ if (*sint_ptr == MIB2_AUTH_TRAPS_DISABLED) {
+ snmp_set_auth_traps_enabled(SNMP_AUTH_TRAPS_DISABLED);
+ } else {
+ snmp_set_auth_traps_enabled(SNMP_AUTH_TRAPS_ENABLED);
+ }
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+/* the following nodes access variables in SNMP stack (snmp_stats) from SNMP worker thread -> OK, no sync needed */
+static const struct snmp_scalar_array_node_def snmp_nodes[] = {
+ { 1, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInPkts */
+ { 2, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpOutPkts */
+ { 3, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInBadVersions */
+ { 4, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInBadCommunityNames */
+ { 5, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInBadCommunityUses */
+ { 6, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInASNParseErrs */
+ { 8, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInTooBigs */
+ { 9, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInNoSuchNames */
+ {10, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInBadValues */
+ {11, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInReadOnlys */
+ {12, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInGenErrs */
+ {13, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInTotalReqVars */
+ {14, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInTotalSetVars */
+ {15, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInGetRequests */
+ {16, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInGetNexts */
+ {17, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInSetRequests */
+ {18, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInGetResponses */
+ {19, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpInTraps */
+ {20, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpOutTooBigs */
+ {21, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpOutNoSuchNames */
+ {22, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpOutBadValues */
+ {24, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpOutGenErrs */
+ {25, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpOutGetRequests */
+ {26, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpOutGetNexts */
+ {27, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpOutSetRequests */
+ {28, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpOutGetResponses */
+ {29, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpOutTraps */
+ {30, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_WRITE}, /* snmpEnableAuthenTraps */
+ {31, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpSilentDrops */
+ {32, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY} /* snmpProxyDrops */
+};
+
+const struct snmp_scalar_array_node snmp_mib2_snmp_root = SNMP_SCALAR_CREATE_ARRAY_NODE(11, snmp_nodes, snmp_get_value, snmp_set_test, snmp_set_value);
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 */
diff --git a/src/apps/snmp/snmp_mib2_system.c b/src/apps/snmp/snmp_mib2_system.c
new file mode 100644
index 00000000000..71c1c29c07d
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_system.c
@@ -0,0 +1,376 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) SYSTEM objects and functions.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ * Christiaan Simons <[email protected]>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/sys.h"
+
+#include <string.h>
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+ static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+/* --- system .1.3.6.1.2.1.1 ----------------------------------------------------- */
+
+/** mib-2.system.sysDescr */
+static const u8_t sysdescr_default[] = SNMP_LWIP_MIB2_SYSDESC;
+static const u8_t *sysdescr = sysdescr_default;
+static const u16_t *sysdescr_len = NULL; /* use strlen for determining len */
+
+/** mib-2.system.sysContact */
+static const u8_t syscontact_default[] = SNMP_LWIP_MIB2_SYSCONTACT;
+static const u8_t *syscontact = syscontact_default;
+static const u16_t *syscontact_len = NULL; /* use strlen for determining len */
+static u8_t *syscontact_wr = NULL; /* if writable, points to the same buffer as syscontact (required for correct constness) */
+static u16_t *syscontact_wr_len = NULL; /* if writable, points to the same buffer as syscontact_len (required for correct constness) */
+static u16_t syscontact_bufsize = 0; /* 0=not writable */
+
+/** mib-2.system.sysName */
+static const u8_t sysname_default[] = SNMP_LWIP_MIB2_SYSNAME;
+static const u8_t *sysname = sysname_default;
+static const u16_t *sysname_len = NULL; /* use strlen for determining len */
+static u8_t *sysname_wr = NULL; /* if writable, points to the same buffer as sysname (required for correct constness) */
+static u16_t *sysname_wr_len = NULL; /* if writable, points to the same buffer as sysname_len (required for correct constness) */
+static u16_t sysname_bufsize = 0; /* 0=not writable */
+
+/** mib-2.system.sysLocation */
+static const u8_t syslocation_default[] = SNMP_LWIP_MIB2_SYSLOCATION;
+static const u8_t *syslocation = syslocation_default;
+static const u16_t *syslocation_len = NULL; /* use strlen for determining len */
+static u8_t *syslocation_wr = NULL; /* if writable, points to the same buffer as syslocation (required for correct constness) */
+static u16_t *syslocation_wr_len = NULL; /* if writable, points to the same buffer as syslocation_len (required for correct constness) */
+static u16_t syslocation_bufsize = 0; /* 0=not writable */
+
+/**
+ * @ingroup snmp_mib2
+ * Initializes sysDescr pointers.
+ *
+ * @param str if non-NULL then copy str pointer
+ * @param len points to string length, excluding zero terminator
+ */
+void
+snmp_mib2_set_sysdescr(const u8_t *str, const u16_t *len)
+{
+ if (str != NULL) {
+ sysdescr = str;
+ sysdescr_len = len;
+ }
+}
+
+/**
+ * @ingroup snmp_mib2
+ * Initializes sysContact pointers
+ *
+ * @param ocstr if non-NULL then copy str pointer
+ * @param ocstrlen points to string length, excluding zero terminator.
+ * if set to NULL it is assumed that ocstr is NULL-terminated.
+ * @param bufsize size of the buffer in bytes.
+ * (this is required because the buffer can be overwritten by snmp-set)
+ * if ocstrlen is NULL buffer needs space for terminating 0 byte.
+ * otherwise complete buffer is used for string.
+ * if bufsize is set to 0, the value is regarded as read-only.
+ */
+void
+snmp_mib2_set_syscontact(u8_t *ocstr, u16_t *ocstrlen, u16_t bufsize)
+{
+ if (ocstr != NULL) {
+ syscontact = ocstr;
+ syscontact_wr = ocstr;
+ syscontact_len = ocstrlen;
+ syscontact_wr_len = ocstrlen;
+ syscontact_bufsize = bufsize;
+ }
+}
+
+/**
+ * @ingroup snmp_mib2
+ * see \ref snmp_mib2_set_syscontact but set pointer to readonly memory
+ */
+void
+snmp_mib2_set_syscontact_readonly(const u8_t *ocstr, const u16_t *ocstrlen)
+{
+ if (ocstr != NULL) {
+ syscontact = ocstr;
+ syscontact_len = ocstrlen;
+ syscontact_wr = NULL;
+ syscontact_wr_len = NULL;
+ syscontact_bufsize = 0;
+ }
+}
+
+
+/**
+ * @ingroup snmp_mib2
+ * Initializes sysName pointers
+ *
+ * @param ocstr if non-NULL then copy str pointer
+ * @param ocstrlen points to string length, excluding zero terminator.
+ * if set to NULL it is assumed that ocstr is NULL-terminated.
+ * @param bufsize size of the buffer in bytes.
+ * (this is required because the buffer can be overwritten by snmp-set)
+ * if ocstrlen is NULL buffer needs space for terminating 0 byte.
+ * otherwise complete buffer is used for string.
+ * if bufsize is set to 0, the value is regarded as read-only.
+ */
+void
+snmp_mib2_set_sysname(u8_t *ocstr, u16_t *ocstrlen, u16_t bufsize)
+{
+ if (ocstr != NULL) {
+ sysname = ocstr;
+ sysname_wr = ocstr;
+ sysname_len = ocstrlen;
+ sysname_wr_len = ocstrlen;
+ sysname_bufsize = bufsize;
+ }
+}
+
+/**
+ * @ingroup snmp_mib2
+ * see \ref snmp_mib2_set_sysname but set pointer to readonly memory
+ */
+void
+snmp_mib2_set_sysname_readonly(const u8_t *ocstr, const u16_t *ocstrlen)
+{
+ if (ocstr != NULL) {
+ sysname = ocstr;
+ sysname_len = ocstrlen;
+ sysname_wr = NULL;
+ sysname_wr_len = NULL;
+ sysname_bufsize = 0;
+ }
+}
+
+/**
+ * @ingroup snmp_mib2
+ * Initializes sysLocation pointers
+ *
+ * @param ocstr if non-NULL then copy str pointer
+ * @param ocstrlen points to string length, excluding zero terminator.
+ * if set to NULL it is assumed that ocstr is NULL-terminated.
+ * @param bufsize size of the buffer in bytes.
+ * (this is required because the buffer can be overwritten by snmp-set)
+ * if ocstrlen is NULL buffer needs space for terminating 0 byte.
+ * otherwise complete buffer is used for string.
+ * if bufsize is set to 0, the value is regarded as read-only.
+ */
+void
+snmp_mib2_set_syslocation(u8_t *ocstr, u16_t *ocstrlen, u16_t bufsize)
+{
+ if (ocstr != NULL) {
+ syslocation = ocstr;
+ syslocation_wr = ocstr;
+ syslocation_len = ocstrlen;
+ syslocation_wr_len = ocstrlen;
+ syslocation_bufsize = bufsize;
+ }
+}
+
+/**
+ * @ingroup snmp_mib2
+ * see \ref snmp_mib2_set_syslocation but set pointer to readonly memory
+ */
+void
+snmp_mib2_set_syslocation_readonly(const u8_t *ocstr, const u16_t *ocstrlen)
+{
+ if (ocstr != NULL) {
+ syslocation = ocstr;
+ syslocation_len = ocstrlen;
+ syslocation_wr = NULL;
+ syslocation_wr_len = NULL;
+ syslocation_bufsize = 0;
+ }
+}
+
+
+static s16_t
+system_get_value(const struct snmp_scalar_array_node_def *node, void *value)
+{
+ const u8_t *var = NULL;
+ const s16_t *var_len;
+ u16_t result;
+
+ switch (node->oid) {
+ case 1: /* sysDescr */
+ var = sysdescr;
+ var_len = (const s16_t *)sysdescr_len;
+ break;
+ case 2: { /* sysObjectID */
+ const struct snmp_obj_id *dev_enterprise_oid = snmp_get_device_enterprise_oid();
+ MEMCPY(value, dev_enterprise_oid->id, dev_enterprise_oid->len * sizeof(u32_t));
+ return dev_enterprise_oid->len * sizeof(u32_t);
+ }
+ case 3: /* sysUpTime */
+ MIB2_COPY_SYSUPTIME_TO((u32_t *)value);
+ return sizeof(u32_t);
+ case 4: /* sysContact */
+ var = syscontact;
+ var_len = (const s16_t *)syscontact_len;
+ break;
+ case 5: /* sysName */
+ var = sysname;
+ var_len = (const s16_t *)sysname_len;
+ break;
+ case 6: /* sysLocation */
+ var = syslocation;
+ var_len = (const s16_t *)syslocation_len;
+ break;
+ case 7: /* sysServices */
+ *(s32_t *)value = SNMP_SYSSERVICES;
+ return sizeof(s32_t);
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("system_get_value(): unknown id: %"S32_F"\n", node->oid));
+ return 0;
+ }
+
+ /* handle string values (OID 1,4,5 and 6) */
+ LWIP_ASSERT("", (value != NULL));
+ if (var_len == NULL) {
+ result = (s16_t)strlen((const char *)var);
+ } else {
+ result = *var_len;
+ }
+ MEMCPY(value, var, result);
+ return result;
+}
+
+static snmp_err_t
+system_set_test(const struct snmp_scalar_array_node_def *node, u16_t len, void *value)
+{
+ snmp_err_t ret = SNMP_ERR_WRONGVALUE;
+ const u16_t *var_bufsize = NULL;
+ const u16_t *var_wr_len;
+
+ LWIP_UNUSED_ARG(value);
+
+ switch (node->oid) {
+ case 4: /* sysContact */
+ var_bufsize = &syscontact_bufsize;
+ var_wr_len = syscontact_wr_len;
+ break;
+ case 5: /* sysName */
+ var_bufsize = &sysname_bufsize;
+ var_wr_len = sysname_wr_len;
+ break;
+ case 6: /* sysLocation */
+ var_bufsize = &syslocation_bufsize;
+ var_wr_len = syslocation_wr_len;
+ break;
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("system_set_test(): unknown id: %"S32_F"\n", node->oid));
+ return ret;
+ }
+
+ /* check if value is writable at all */
+ if (*var_bufsize > 0) {
+ if (var_wr_len == NULL) {
+ /* we have to take the terminating 0 into account */
+ if (len < *var_bufsize) {
+ ret = SNMP_ERR_NOERROR;
+ }
+ } else {
+ if (len <= *var_bufsize) {
+ ret = SNMP_ERR_NOERROR;
+ }
+ }
+ } else {
+ ret = SNMP_ERR_NOTWRITABLE;
+ }
+
+ return ret;
+}
+
+static snmp_err_t
+system_set_value(const struct snmp_scalar_array_node_def *node, u16_t len, void *value)
+{
+ u8_t *var_wr = NULL;
+ u16_t *var_wr_len;
+
+ switch (node->oid) {
+ case 4: /* sysContact */
+ var_wr = syscontact_wr;
+ var_wr_len = syscontact_wr_len;
+ break;
+ case 5: /* sysName */
+ var_wr = sysname_wr;
+ var_wr_len = sysname_wr_len;
+ break;
+ case 6: /* sysLocation */
+ var_wr = syslocation_wr;
+ var_wr_len = syslocation_wr_len;
+ break;
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("system_set_value(): unknown id: %"S32_F"\n", node->oid));
+ return SNMP_ERR_GENERROR;
+ }
+
+ /* no need to check size of target buffer, this was already done in set_test method */
+ LWIP_ASSERT("", var_wr != NULL);
+ MEMCPY(var_wr, value, len);
+
+ if (var_wr_len == NULL) {
+ /* add terminating 0 */
+ var_wr[len] = 0;
+ } else {
+ *var_wr_len = len;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+static const struct snmp_scalar_array_node_def system_nodes[] = {
+ {1, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY}, /* sysDescr */
+ {2, SNMP_ASN1_TYPE_OBJECT_ID, SNMP_NODE_INSTANCE_READ_ONLY}, /* sysObjectID */
+ {3, SNMP_ASN1_TYPE_TIMETICKS, SNMP_NODE_INSTANCE_READ_ONLY}, /* sysUpTime */
+ {4, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_WRITE}, /* sysContact */
+ {5, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_WRITE}, /* sysName */
+ {6, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_WRITE}, /* sysLocation */
+ {7, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY} /* sysServices */
+};
+
+const struct snmp_scalar_array_node snmp_mib2_system_node = SNMP_SCALAR_CREATE_ARRAY_NODE(1, system_nodes, system_get_value, system_set_test, system_set_value);
+
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 */
diff --git a/src/apps/snmp/snmp_mib2_tcp.c b/src/apps/snmp/snmp_mib2_tcp.c
new file mode 100644
index 00000000000..a23143eda21
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_tcp.c
@@ -0,0 +1,607 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) TCP objects and functions.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ * Christiaan Simons <[email protected]>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/tcp.h"
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_TCP
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+ static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+/* --- tcp .1.3.6.1.2.1.6 ----------------------------------------------------- */
+
+static s16_t
+tcp_get_value(struct snmp_node_instance *instance, void *value)
+{
+ u32_t *uint_ptr = (u32_t *)value;
+ s32_t *sint_ptr = (s32_t *)value;
+
+ switch (instance->node->oid) {
+ case 1: /* tcpRtoAlgorithm, vanj(4) */
+ *sint_ptr = 4;
+ return sizeof(*sint_ptr);
+ case 2: /* tcpRtoMin */
+ /* @todo not the actual value, a guess,
+ needs to be calculated */
+ *sint_ptr = 1000;
+ return sizeof(*sint_ptr);
+ case 3: /* tcpRtoMax */
+ /* @todo not the actual value, a guess,
+ needs to be calculated */
+ *sint_ptr = 60000;
+ return sizeof(*sint_ptr);
+ case 4: /* tcpMaxConn */
+ *sint_ptr = MEMP_NUM_TCP_PCB;
+ return sizeof(*sint_ptr);
+ case 5: /* tcpActiveOpens */
+ *uint_ptr = STATS_GET(mib2.tcpactiveopens);
+ return sizeof(*uint_ptr);
+ case 6: /* tcpPassiveOpens */
+ *uint_ptr = STATS_GET(mib2.tcppassiveopens);
+ return sizeof(*uint_ptr);
+ case 7: /* tcpAttemptFails */
+ *uint_ptr = STATS_GET(mib2.tcpattemptfails);
+ return sizeof(*uint_ptr);
+ case 8: /* tcpEstabResets */
+ *uint_ptr = STATS_GET(mib2.tcpestabresets);
+ return sizeof(*uint_ptr);
+ case 9: { /* tcpCurrEstab */
+ u16_t tcpcurrestab = 0;
+ struct tcp_pcb *pcb = tcp_active_pcbs;
+ while (pcb != NULL) {
+ if ((pcb->state == ESTABLISHED) ||
+ (pcb->state == CLOSE_WAIT)) {
+ tcpcurrestab++;
+ }
+ pcb = pcb->next;
+ }
+ *uint_ptr = tcpcurrestab;
+ }
+ return sizeof(*uint_ptr);
+ case 10: /* tcpInSegs */
+ *uint_ptr = STATS_GET(mib2.tcpinsegs);
+ return sizeof(*uint_ptr);
+ case 11: /* tcpOutSegs */
+ *uint_ptr = STATS_GET(mib2.tcpoutsegs);
+ return sizeof(*uint_ptr);
+ case 12: /* tcpRetransSegs */
+ *uint_ptr = STATS_GET(mib2.tcpretranssegs);
+ return sizeof(*uint_ptr);
+ case 14: /* tcpInErrs */
+ *uint_ptr = STATS_GET(mib2.tcpinerrs);
+ return sizeof(*uint_ptr);
+ case 15: /* tcpOutRsts */
+ *uint_ptr = STATS_GET(mib2.tcpoutrsts);
+ return sizeof(*uint_ptr);
+#if LWIP_HAVE_INT64
+ case 17: { /* tcpHCInSegs */
+ /* use the 32 bit counter for now... */
+ u64_t val64 = STATS_GET(mib2.tcpinsegs);
+ *((u64_t *)value) = val64;
+ }
+ return sizeof(u64_t);
+ case 18: { /* tcpHCOutSegs */
+ /* use the 32 bit counter for now... */
+ u64_t val64 = STATS_GET(mib2.tcpoutsegs);
+ *((u64_t *)value) = val64;
+ }
+ return sizeof(u64_t);
+#endif
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("tcp_get_value(): unknown id: %"S32_F"\n", instance->node->oid));
+ break;
+ }
+
+ return 0;
+}
+
+/* --- tcpConnTable --- */
+
+#if LWIP_IPV4
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range tcp_ConnTable_oid_ranges[] = {
+ { 0, 0xff }, /* IP A */
+ { 0, 0xff }, /* IP B */
+ { 0, 0xff }, /* IP C */
+ { 0, 0xff }, /* IP D */
+ { 0, 0xffff }, /* Port */
+ { 0, 0xff }, /* IP A */
+ { 0, 0xff }, /* IP B */
+ { 0, 0xff }, /* IP C */
+ { 0, 0xff }, /* IP D */
+ { 0, 0xffff } /* Port */
+};
+
+static snmp_err_t
+tcp_ConnTable_get_cell_value_core(struct tcp_pcb *pcb, const u32_t *column, union snmp_variant_value *value, u32_t *value_len)
+{
+ LWIP_UNUSED_ARG(value_len);
+
+ /* value */
+ switch (*column) {
+ case 1: /* tcpConnState */
+ value->u32 = pcb->state + 1;
+ break;
+ case 2: /* tcpConnLocalAddress */
+ value->u32 = ip_2_ip4(&pcb->local_ip)->addr;
+ break;
+ case 3: /* tcpConnLocalPort */
+ value->u32 = pcb->local_port;
+ break;
+ case 4: /* tcpConnRemAddress */
+ if (pcb->state == LISTEN) {
+ value->u32 = IP4_ADDR_ANY4->addr;
+ } else {
+ value->u32 = ip_2_ip4(&pcb->remote_ip)->addr;
+ }
+ break;
+ case 5: /* tcpConnRemPort */
+ if (pcb->state == LISTEN) {
+ value->u32 = 0;
+ } else {
+ value->u32 = pcb->remote_port;
+ }
+ break;
+ default:
+ LWIP_ASSERT("invalid id", 0);
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+tcp_ConnTable_get_cell_value(const u32_t *column, const u32_t *row_oid, u8_t row_oid_len, union snmp_variant_value *value, u32_t *value_len)
+{
+ u8_t i;
+ ip4_addr_t local_ip;
+ ip4_addr_t remote_ip;
+ u16_t local_port;
+ u16_t remote_port;
+ struct tcp_pcb *pcb;
+
+ /* check if incoming OID length and if values are in plausible range */
+ if (!snmp_oid_in_range(row_oid, row_oid_len, tcp_ConnTable_oid_ranges, LWIP_ARRAYSIZE(tcp_ConnTable_oid_ranges))) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* get IPs and ports from incoming OID */
+ snmp_oid_to_ip4(&row_oid[0], &local_ip); /* we know it succeeds because of oid_in_range check above */
+ local_port = (u16_t)row_oid[4];
+ snmp_oid_to_ip4(&row_oid[5], &remote_ip); /* we know it succeeds because of oid_in_range check above */
+ remote_port = (u16_t)row_oid[9];
+
+ /* find tcp_pcb with requested ips and ports */
+ for (i = 0; i < LWIP_ARRAYSIZE(tcp_pcb_lists); i++) {
+ pcb = *tcp_pcb_lists[i];
+
+ while (pcb != NULL) {
+ /* do local IP and local port match? */
+ if (IP_IS_V4_VAL(pcb->local_ip) &&
+ ip4_addr_eq(&local_ip, ip_2_ip4(&pcb->local_ip)) && (local_port == pcb->local_port)) {
+
+ /* PCBs in state LISTEN are not connected and have no remote_ip or remote_port */
+ if (pcb->state == LISTEN) {
+ if (ip4_addr_eq(&remote_ip, IP4_ADDR_ANY4) && (remote_port == 0)) {
+ /* fill in object properties */
+ return tcp_ConnTable_get_cell_value_core(pcb, column, value, value_len);
+ }
+ } else {
+ if (IP_IS_V4_VAL(pcb->remote_ip) &&
+ ip4_addr_eq(&remote_ip, ip_2_ip4(&pcb->remote_ip)) && (remote_port == pcb->remote_port)) {
+ /* fill in object properties */
+ return tcp_ConnTable_get_cell_value_core(pcb, column, value, value_len);
+ }
+ }
+ }
+
+ pcb = pcb->next;
+ }
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+tcp_ConnTable_get_next_cell_instance_and_value(const u32_t *column, struct snmp_obj_id *row_oid, union snmp_variant_value *value, u32_t *value_len)
+{
+ u8_t i;
+ struct tcp_pcb *pcb;
+ struct snmp_next_oid_state state;
+ u32_t result_temp[LWIP_ARRAYSIZE(tcp_ConnTable_oid_ranges)];
+
+ /* init struct to search next oid */
+ snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(tcp_ConnTable_oid_ranges));
+
+ /* iterate over all possible OIDs to find the next one */
+ for (i = 0; i < LWIP_ARRAYSIZE(tcp_pcb_lists); i++) {
+ pcb = *tcp_pcb_lists[i];
+ while (pcb != NULL) {
+ u32_t test_oid[LWIP_ARRAYSIZE(tcp_ConnTable_oid_ranges)];
+
+ if (IP_IS_V4_VAL(pcb->local_ip)) {
+ snmp_ip4_to_oid(ip_2_ip4(&pcb->local_ip), &test_oid[0]);
+ test_oid[4] = pcb->local_port;
+
+ /* PCBs in state LISTEN are not connected and have no remote_ip or remote_port */
+ if (pcb->state == LISTEN) {
+ snmp_ip4_to_oid(IP4_ADDR_ANY4, &test_oid[5]);
+ test_oid[9] = 0;
+ } else {
+ if (IP_IS_V6_VAL(pcb->remote_ip)) { /* should never happen */
+ continue;
+ }
+ snmp_ip4_to_oid(ip_2_ip4(&pcb->remote_ip), &test_oid[5]);
+ test_oid[9] = pcb->remote_port;
+ }
+
+ /* check generated OID: is it a candidate for the next one? */
+ snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(tcp_ConnTable_oid_ranges), pcb);
+ }
+
+ pcb = pcb->next;
+ }
+ }
+
+ /* did we find a next one? */
+ if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+ snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+ /* fill in object properties */
+ return tcp_ConnTable_get_cell_value_core((struct tcp_pcb *)state.reference, column, value, value_len);
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+#endif /* LWIP_IPV4 */
+
+/* --- tcpConnectionTable --- */
+
+static snmp_err_t
+tcp_ConnectionTable_get_cell_value_core(const u32_t *column, struct tcp_pcb *pcb, union snmp_variant_value *value)
+{
+ /* all items except tcpConnectionState and tcpConnectionProcess are declared as not-accessible */
+ switch (*column) {
+ case 7: /* tcpConnectionState */
+ value->u32 = pcb->state + 1;
+ break;
+ case 8: /* tcpConnectionProcess */
+ value->u32 = 0; /* not supported */
+ break;
+ default:
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+tcp_ConnectionTable_get_cell_value(const u32_t *column, const u32_t *row_oid, u8_t row_oid_len, union snmp_variant_value *value, u32_t *value_len)
+{
+ ip_addr_t local_ip, remote_ip;
+ u16_t local_port, remote_port;
+ struct tcp_pcb *pcb;
+ u8_t idx = 0;
+ u8_t i;
+ struct tcp_pcb **const tcp_pcb_nonlisten_lists[] = {&tcp_bound_pcbs, &tcp_active_pcbs, &tcp_tw_pcbs};
+
+ LWIP_UNUSED_ARG(value_len);
+
+ /* tcpConnectionLocalAddressType + tcpConnectionLocalAddress + tcpConnectionLocalPort */
+ idx += snmp_oid_to_ip_port(&row_oid[idx], row_oid_len - idx, &local_ip, &local_port);
+ if (idx == 0) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* tcpConnectionRemAddressType + tcpConnectionRemAddress + tcpConnectionRemPort */
+ idx += snmp_oid_to_ip_port(&row_oid[idx], row_oid_len - idx, &remote_ip, &remote_port);
+ if (idx == 0) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* find tcp_pcb with requested ip and port*/
+ for (i = 0; i < LWIP_ARRAYSIZE(tcp_pcb_nonlisten_lists); i++) {
+ pcb = *tcp_pcb_nonlisten_lists[i];
+
+ while (pcb != NULL) {
+ if (ip_addr_eq(&local_ip, &pcb->local_ip) &&
+ (local_port == pcb->local_port) &&
+ ip_addr_eq(&remote_ip, &pcb->remote_ip) &&
+ (remote_port == pcb->remote_port)) {
+ /* fill in object properties */
+ return tcp_ConnectionTable_get_cell_value_core(column, pcb, value);
+ }
+ pcb = pcb->next;
+ }
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+tcp_ConnectionTable_get_next_cell_instance_and_value(const u32_t *column, struct snmp_obj_id *row_oid, union snmp_variant_value *value, u32_t *value_len)
+{
+ struct tcp_pcb *pcb;
+ struct snmp_next_oid_state state;
+ /* 1x tcpConnectionLocalAddressType + 1x OID len + 16x tcpConnectionLocalAddress + 1x tcpConnectionLocalPort
+ * 1x tcpConnectionRemAddressType + 1x OID len + 16x tcpConnectionRemAddress + 1x tcpConnectionRemPort */
+ u32_t result_temp[38];
+ u8_t i;
+ struct tcp_pcb **const tcp_pcb_nonlisten_lists[] = {&tcp_bound_pcbs, &tcp_active_pcbs, &tcp_tw_pcbs};
+
+ LWIP_UNUSED_ARG(value_len);
+
+ /* init struct to search next oid */
+ snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(result_temp));
+
+ /* iterate over all possible OIDs to find the next one */
+ for (i = 0; i < LWIP_ARRAYSIZE(tcp_pcb_nonlisten_lists); i++) {
+ pcb = *tcp_pcb_nonlisten_lists[i];
+
+ while (pcb != NULL) {
+ u8_t idx = 0;
+ u32_t test_oid[LWIP_ARRAYSIZE(result_temp)];
+
+ /* tcpConnectionLocalAddressType + tcpConnectionLocalAddress + tcpConnectionLocalPort */
+ idx += snmp_ip_port_to_oid(&pcb->local_ip, pcb->local_port, &test_oid[idx]);
+
+ /* tcpConnectionRemAddressType + tcpConnectionRemAddress + tcpConnectionRemPort */
+ idx += snmp_ip_port_to_oid(&pcb->remote_ip, pcb->remote_port, &test_oid[idx]);
+
+ /* check generated OID: is it a candidate for the next one? */
+ snmp_next_oid_check(&state, test_oid, idx, pcb);
+
+ pcb = pcb->next;
+ }
+ }
+
+ /* did we find a next one? */
+ if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+ snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+ /* fill in object properties */
+ return tcp_ConnectionTable_get_cell_value_core(column, (struct tcp_pcb *)state.reference, value);
+ } else {
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+}
+
+/* --- tcpListenerTable --- */
+
+static snmp_err_t
+tcp_ListenerTable_get_cell_value_core(const u32_t *column, union snmp_variant_value *value)
+{
+ /* all items except tcpListenerProcess are declared as not-accessible */
+ switch (*column) {
+ case 4: /* tcpListenerProcess */
+ value->u32 = 0; /* not supported */
+ break;
+ default:
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+tcp_ListenerTable_get_cell_value(const u32_t *column, const u32_t *row_oid, u8_t row_oid_len, union snmp_variant_value *value, u32_t *value_len)
+{
+ ip_addr_t local_ip;
+ u16_t local_port;
+ struct tcp_pcb_listen *pcb;
+ u8_t idx = 0;
+
+ LWIP_UNUSED_ARG(value_len);
+
+ /* tcpListenerLocalAddressType + tcpListenerLocalAddress + tcpListenerLocalPort */
+ idx += snmp_oid_to_ip_port(&row_oid[idx], row_oid_len - idx, &local_ip, &local_port);
+ if (idx == 0) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* find tcp_pcb with requested ip and port*/
+ pcb = tcp_listen_pcbs.listen_pcbs;
+ while (pcb != NULL) {
+ if (ip_addr_eq(&local_ip, &pcb->local_ip) &&
+ (local_port == pcb->local_port)) {
+ /* fill in object properties */
+ return tcp_ListenerTable_get_cell_value_core(column, value);
+ }
+ pcb = pcb->next;
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+tcp_ListenerTable_get_next_cell_instance_and_value(const u32_t *column, struct snmp_obj_id *row_oid, union snmp_variant_value *value, u32_t *value_len)
+{
+ struct tcp_pcb_listen *pcb;
+ struct snmp_next_oid_state state;
+ /* 1x tcpListenerLocalAddressType + 1x OID len + 16x tcpListenerLocalAddress + 1x tcpListenerLocalPort */
+ u32_t result_temp[19];
+
+ LWIP_UNUSED_ARG(value_len);
+
+ /* init struct to search next oid */
+ snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(result_temp));
+
+ /* iterate over all possible OIDs to find the next one */
+ pcb = tcp_listen_pcbs.listen_pcbs;
+ while (pcb != NULL) {
+ u8_t idx = 0;
+ u32_t test_oid[LWIP_ARRAYSIZE(result_temp)];
+
+ /* tcpListenerLocalAddressType + tcpListenerLocalAddress + tcpListenerLocalPort */
+ idx += snmp_ip_port_to_oid(&pcb->local_ip, pcb->local_port, &test_oid[idx]);
+
+ /* check generated OID: is it a candidate for the next one? */
+ snmp_next_oid_check(&state, test_oid, idx, NULL);
+
+ pcb = pcb->next;
+ }
+
+ /* did we find a next one? */
+ if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+ snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+ /* fill in object properties */
+ return tcp_ListenerTable_get_cell_value_core(column, value);
+ } else {
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+}
+
+static const struct snmp_scalar_node tcp_RtoAlgorithm = SNMP_SCALAR_CREATE_NODE_READONLY(1, SNMP_ASN1_TYPE_INTEGER, tcp_get_value);
+static const struct snmp_scalar_node tcp_RtoMin = SNMP_SCALAR_CREATE_NODE_READONLY(2, SNMP_ASN1_TYPE_INTEGER, tcp_get_value);
+static const struct snmp_scalar_node tcp_RtoMax = SNMP_SCALAR_CREATE_NODE_READONLY(3, SNMP_ASN1_TYPE_INTEGER, tcp_get_value);
+static const struct snmp_scalar_node tcp_MaxConn = SNMP_SCALAR_CREATE_NODE_READONLY(4, SNMP_ASN1_TYPE_INTEGER, tcp_get_value);
+static const struct snmp_scalar_node tcp_ActiveOpens = SNMP_SCALAR_CREATE_NODE_READONLY(5, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_PassiveOpens = SNMP_SCALAR_CREATE_NODE_READONLY(6, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_AttemptFails = SNMP_SCALAR_CREATE_NODE_READONLY(7, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_EstabResets = SNMP_SCALAR_CREATE_NODE_READONLY(8, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_CurrEstab = SNMP_SCALAR_CREATE_NODE_READONLY(9, SNMP_ASN1_TYPE_GAUGE, tcp_get_value);
+static const struct snmp_scalar_node tcp_InSegs = SNMP_SCALAR_CREATE_NODE_READONLY(10, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_OutSegs = SNMP_SCALAR_CREATE_NODE_READONLY(11, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_RetransSegs = SNMP_SCALAR_CREATE_NODE_READONLY(12, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_InErrs = SNMP_SCALAR_CREATE_NODE_READONLY(14, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+static const struct snmp_scalar_node tcp_OutRsts = SNMP_SCALAR_CREATE_NODE_READONLY(15, SNMP_ASN1_TYPE_COUNTER, tcp_get_value);
+#if LWIP_HAVE_INT64
+static const struct snmp_scalar_node tcp_HCInSegs = SNMP_SCALAR_CREATE_NODE_READONLY(17, SNMP_ASN1_TYPE_COUNTER64, tcp_get_value);
+static const struct snmp_scalar_node tcp_HCOutSegs = SNMP_SCALAR_CREATE_NODE_READONLY(18, SNMP_ASN1_TYPE_COUNTER64, tcp_get_value);
+#endif
+
+#if LWIP_IPV4
+static const struct snmp_table_simple_col_def tcp_ConnTable_columns[] = {
+ { 1, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* tcpConnState */
+ { 2, SNMP_ASN1_TYPE_IPADDR, SNMP_VARIANT_VALUE_TYPE_U32 }, /* tcpConnLocalAddress */
+ { 3, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* tcpConnLocalPort */
+ { 4, SNMP_ASN1_TYPE_IPADDR, SNMP_VARIANT_VALUE_TYPE_U32 }, /* tcpConnRemAddress */
+ { 5, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 } /* tcpConnRemPort */
+};
+
+static const struct snmp_table_simple_node tcp_ConnTable = SNMP_TABLE_CREATE_SIMPLE(13, tcp_ConnTable_columns, tcp_ConnTable_get_cell_value, tcp_ConnTable_get_next_cell_instance_and_value);
+#endif /* LWIP_IPV4 */
+
+static const struct snmp_table_simple_col_def tcp_ConnectionTable_columns[] = {
+ /* all items except tcpConnectionState and tcpConnectionProcess are declared as not-accessible */
+ { 7, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 }, /* tcpConnectionState */
+ { 8, SNMP_ASN1_TYPE_UNSIGNED32, SNMP_VARIANT_VALUE_TYPE_U32 } /* tcpConnectionProcess */
+};
+
+static const struct snmp_table_simple_node tcp_ConnectionTable = SNMP_TABLE_CREATE_SIMPLE(19, tcp_ConnectionTable_columns, tcp_ConnectionTable_get_cell_value, tcp_ConnectionTable_get_next_cell_instance_and_value);
+
+
+static const struct snmp_table_simple_col_def tcp_ListenerTable_columns[] = {
+ /* all items except tcpListenerProcess are declared as not-accessible */
+ { 4, SNMP_ASN1_TYPE_UNSIGNED32, SNMP_VARIANT_VALUE_TYPE_U32 } /* tcpListenerProcess */
+};
+
+static const struct snmp_table_simple_node tcp_ListenerTable = SNMP_TABLE_CREATE_SIMPLE(20, tcp_ListenerTable_columns, tcp_ListenerTable_get_cell_value, tcp_ListenerTable_get_next_cell_instance_and_value);
+
+/* the following nodes access variables in LWIP stack from SNMP worker thread and must therefore be synced to LWIP (TCPIP) thread */
+CREATE_LWIP_SYNC_NODE( 1, tcp_RtoAlgorithm)
+CREATE_LWIP_SYNC_NODE( 2, tcp_RtoMin)
+CREATE_LWIP_SYNC_NODE( 3, tcp_RtoMax)
+CREATE_LWIP_SYNC_NODE( 4, tcp_MaxConn)
+CREATE_LWIP_SYNC_NODE( 5, tcp_ActiveOpens)
+CREATE_LWIP_SYNC_NODE( 6, tcp_PassiveOpens)
+CREATE_LWIP_SYNC_NODE( 7, tcp_AttemptFails)
+CREATE_LWIP_SYNC_NODE( 8, tcp_EstabResets)
+CREATE_LWIP_SYNC_NODE( 9, tcp_CurrEstab)
+CREATE_LWIP_SYNC_NODE(10, tcp_InSegs)
+CREATE_LWIP_SYNC_NODE(11, tcp_OutSegs)
+CREATE_LWIP_SYNC_NODE(12, tcp_RetransSegs)
+#if LWIP_IPV4
+CREATE_LWIP_SYNC_NODE(13, tcp_ConnTable)
+#endif /* LWIP_IPV4 */
+CREATE_LWIP_SYNC_NODE(14, tcp_InErrs)
+CREATE_LWIP_SYNC_NODE(15, tcp_OutRsts)
+#if LWIP_HAVE_INT64
+CREATE_LWIP_SYNC_NODE(17, tcp_HCInSegs)
+CREATE_LWIP_SYNC_NODE(18, tcp_HCOutSegs)
+#endif
+CREATE_LWIP_SYNC_NODE(19, tcp_ConnectionTable)
+CREATE_LWIP_SYNC_NODE(20, tcp_ListenerTable)
+
+static const struct snmp_node *const tcp_nodes[] = {
+ &SYNC_NODE_NAME(tcp_RtoAlgorithm).node.node,
+ &SYNC_NODE_NAME(tcp_RtoMin).node.node,
+ &SYNC_NODE_NAME(tcp_RtoMax).node.node,
+ &SYNC_NODE_NAME(tcp_MaxConn).node.node,
+ &SYNC_NODE_NAME(tcp_ActiveOpens).node.node,
+ &SYNC_NODE_NAME(tcp_PassiveOpens).node.node,
+ &SYNC_NODE_NAME(tcp_AttemptFails).node.node,
+ &SYNC_NODE_NAME(tcp_EstabResets).node.node,
+ &SYNC_NODE_NAME(tcp_CurrEstab).node.node,
+ &SYNC_NODE_NAME(tcp_InSegs).node.node,
+ &SYNC_NODE_NAME(tcp_OutSegs).node.node,
+ &SYNC_NODE_NAME(tcp_RetransSegs).node.node,
+#if LWIP_IPV4
+ &SYNC_NODE_NAME(tcp_ConnTable).node.node,
+#endif /* LWIP_IPV4 */
+ &SYNC_NODE_NAME(tcp_InErrs).node.node,
+ &SYNC_NODE_NAME(tcp_OutRsts).node.node,
+ &SYNC_NODE_NAME(tcp_HCInSegs).node.node,
+#if LWIP_HAVE_INT64
+ &SYNC_NODE_NAME(tcp_HCOutSegs).node.node,
+ &SYNC_NODE_NAME(tcp_ConnectionTable).node.node,
+#endif
+ &SYNC_NODE_NAME(tcp_ListenerTable).node.node
+};
+
+const struct snmp_tree_node snmp_mib2_tcp_root = SNMP_CREATE_TREE_NODE(6, tcp_nodes);
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_TCP */
diff --git a/src/apps/snmp/snmp_mib2_udp.c b/src/apps/snmp/snmp_mib2_udp.c
new file mode 100644
index 00000000000..2b572778e4c
--- /dev/null
+++ b/src/apps/snmp/snmp_mib2_udp.c
@@ -0,0 +1,372 @@
+/**
+ * @file
+ * Management Information Base II (RFC1213) UDP objects and functions.
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ * Christiaan Simons <[email protected]>
+ */
+
+#include "lwip/snmp.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_mib2.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/udp.h"
+#include "lwip/stats.h"
+
+#include <string.h>
+
+#if LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_UDP
+
+#if SNMP_USE_NETCONN
+#define SYNC_NODE_NAME(node_name) node_name ## _synced
+#define CREATE_LWIP_SYNC_NODE(oid, node_name) \
+ static const struct snmp_threadsync_node node_name ## _synced = SNMP_CREATE_THREAD_SYNC_NODE(oid, &node_name.node, &snmp_mib2_lwip_locks);
+#else
+#define SYNC_NODE_NAME(node_name) node_name
+#define CREATE_LWIP_SYNC_NODE(oid, node_name)
+#endif
+
+/* --- udp .1.3.6.1.2.1.7 ----------------------------------------------------- */
+
+static s16_t
+udp_get_value(struct snmp_node_instance *instance, void *value)
+{
+ u32_t *uint_ptr = (u32_t *)value;
+
+ switch (instance->node->oid) {
+ case 1: /* udpInDatagrams */
+ *uint_ptr = STATS_GET(mib2.udpindatagrams);
+ return sizeof(*uint_ptr);
+ case 2: /* udpNoPorts */
+ *uint_ptr = STATS_GET(mib2.udpnoports);
+ return sizeof(*uint_ptr);
+ case 3: /* udpInErrors */
+ *uint_ptr = STATS_GET(mib2.udpinerrors);
+ return sizeof(*uint_ptr);
+ case 4: /* udpOutDatagrams */
+ *uint_ptr = STATS_GET(mib2.udpoutdatagrams);
+ return sizeof(*uint_ptr);
+#if LWIP_HAVE_INT64
+ case 8: { /* udpHCInDatagrams */
+ /* use the 32 bit counter for now... */
+ u64_t val64 = STATS_GET(mib2.udpindatagrams);
+ *((u64_t *)value) = val64;
+ }
+ return sizeof(u64_t);
+ case 9: { /* udpHCOutDatagrams */
+ /* use the 32 bit counter for now... */
+ u64_t val64 = STATS_GET(mib2.udpoutdatagrams);
+ *((u64_t *)value) = val64;
+ }
+ return sizeof(u64_t);
+#endif
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("udp_get_value(): unknown id: %"S32_F"\n", instance->node->oid));
+ break;
+ }
+
+ return 0;
+}
+
+/* --- udpEndpointTable --- */
+
+static snmp_err_t
+udp_endpointTable_get_cell_value_core(const u32_t *column, union snmp_variant_value *value)
+{
+ /* all items except udpEndpointProcess are declared as not-accessible */
+ switch (*column) {
+ case 8: /* udpEndpointProcess */
+ value->u32 = 0; /* not supported */
+ break;
+ default:
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+udp_endpointTable_get_cell_value(const u32_t *column, const u32_t *row_oid, u8_t row_oid_len, union snmp_variant_value *value, u32_t *value_len)
+{
+ ip_addr_t local_ip, remote_ip;
+ u16_t local_port, remote_port;
+ struct udp_pcb *pcb;
+ u8_t idx = 0;
+
+ LWIP_UNUSED_ARG(value_len);
+
+ /* udpEndpointLocalAddressType + udpEndpointLocalAddress + udpEndpointLocalPort */
+ idx += snmp_oid_to_ip_port(&row_oid[idx], row_oid_len - idx, &local_ip, &local_port);
+ if (idx == 0) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* udpEndpointRemoteAddressType + udpEndpointRemoteAddress + udpEndpointRemotePort */
+ idx += snmp_oid_to_ip_port(&row_oid[idx], row_oid_len - idx, &remote_ip, &remote_port);
+ if (idx == 0) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* udpEndpointInstance */
+ if (row_oid_len < (idx + 1)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+ if (row_oid[idx] != 0) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* find udp_pcb with requested ip and port*/
+ pcb = udp_pcbs;
+ while (pcb != NULL) {
+ if (ip_addr_eq(&local_ip, &pcb->local_ip) &&
+ (local_port == pcb->local_port) &&
+ ip_addr_eq(&remote_ip, &pcb->remote_ip) &&
+ (remote_port == pcb->remote_port)) {
+ /* fill in object properties */
+ return udp_endpointTable_get_cell_value_core(column, value);
+ }
+ pcb = pcb->next;
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+udp_endpointTable_get_next_cell_instance_and_value(const u32_t *column, struct snmp_obj_id *row_oid, union snmp_variant_value *value, u32_t *value_len)
+{
+ struct udp_pcb *pcb;
+ struct snmp_next_oid_state state;
+ /* 1x udpEndpointLocalAddressType + 1x OID len + 16x udpEndpointLocalAddress + 1x udpEndpointLocalPort +
+ * 1x udpEndpointRemoteAddressType + 1x OID len + 16x udpEndpointRemoteAddress + 1x udpEndpointRemotePort +
+ * 1x udpEndpointInstance = 39
+ */
+ u32_t result_temp[39];
+
+ LWIP_UNUSED_ARG(value_len);
+
+ /* init struct to search next oid */
+ snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(result_temp));
+
+ /* iterate over all possible OIDs to find the next one */
+ pcb = udp_pcbs;
+ while (pcb != NULL) {
+ u32_t test_oid[LWIP_ARRAYSIZE(result_temp)];
+ u8_t idx = 0;
+
+ /* udpEndpointLocalAddressType + udpEndpointLocalAddress + udpEndpointLocalPort */
+ idx += snmp_ip_port_to_oid(&pcb->local_ip, pcb->local_port, &test_oid[idx]);
+
+ /* udpEndpointRemoteAddressType + udpEndpointRemoteAddress + udpEndpointRemotePort */
+ idx += snmp_ip_port_to_oid(&pcb->remote_ip, pcb->remote_port, &test_oid[idx]);
+
+ test_oid[idx] = 0; /* udpEndpointInstance */
+ idx++;
+
+ /* check generated OID: is it a candidate for the next one? */
+ snmp_next_oid_check(&state, test_oid, idx, NULL);
+
+ pcb = pcb->next;
+ }
+
+ /* did we find a next one? */
+ if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+ snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+ /* fill in object properties */
+ return udp_endpointTable_get_cell_value_core(column, value);
+ } else {
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+}
+
+/* --- udpTable --- */
+
+#if LWIP_IPV4
+
+/* list of allowed value ranges for incoming OID */
+static const struct snmp_oid_range udp_Table_oid_ranges[] = {
+ { 0, 0xff }, /* IP A */
+ { 0, 0xff }, /* IP B */
+ { 0, 0xff }, /* IP C */
+ { 0, 0xff }, /* IP D */
+ { 1, 0xffff } /* Port */
+};
+
+static snmp_err_t
+udp_Table_get_cell_value_core(struct udp_pcb *pcb, const u32_t *column, union snmp_variant_value *value, u32_t *value_len)
+{
+ LWIP_UNUSED_ARG(value_len);
+
+ switch (*column) {
+ case 1: /* udpLocalAddress */
+ /* set reference to PCB local IP and return a generic node that copies IP4 addresses */
+ value->u32 = ip_2_ip4(&pcb->local_ip)->addr;
+ break;
+ case 2: /* udpLocalPort */
+ /* set reference to PCB local port and return a generic node that copies u16_t values */
+ value->u32 = pcb->local_port;
+ break;
+ default:
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ return SNMP_ERR_NOERROR;
+}
+
+static snmp_err_t
+udp_Table_get_cell_value(const u32_t *column, const u32_t *row_oid, u8_t row_oid_len, union snmp_variant_value *value, u32_t *value_len)
+{
+ ip4_addr_t ip;
+ u16_t port;
+ struct udp_pcb *pcb;
+
+ /* check if incoming OID length and if values are in plausible range */
+ if (!snmp_oid_in_range(row_oid, row_oid_len, udp_Table_oid_ranges, LWIP_ARRAYSIZE(udp_Table_oid_ranges))) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* get IP and port from incoming OID */
+ snmp_oid_to_ip4(&row_oid[0], &ip); /* we know it succeeds because of oid_in_range check above */
+ port = (u16_t)row_oid[4];
+
+ /* find udp_pcb with requested ip and port*/
+ pcb = udp_pcbs;
+ while (pcb != NULL) {
+ if (IP_IS_V4_VAL(pcb->local_ip)) {
+ if (ip4_addr_eq(&ip, ip_2_ip4(&pcb->local_ip)) && (port == pcb->local_port)) {
+ /* fill in object properties */
+ return udp_Table_get_cell_value_core(pcb, column, value, value_len);
+ }
+ }
+ pcb = pcb->next;
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static snmp_err_t
+udp_Table_get_next_cell_instance_and_value(const u32_t *column, struct snmp_obj_id *row_oid, union snmp_variant_value *value, u32_t *value_len)
+{
+ struct udp_pcb *pcb;
+ struct snmp_next_oid_state state;
+ u32_t result_temp[LWIP_ARRAYSIZE(udp_Table_oid_ranges)];
+
+ /* init struct to search next oid */
+ snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(udp_Table_oid_ranges));
+
+ /* iterate over all possible OIDs to find the next one */
+ pcb = udp_pcbs;
+ while (pcb != NULL) {
+ u32_t test_oid[LWIP_ARRAYSIZE(udp_Table_oid_ranges)];
+
+ if (IP_IS_V4_VAL(pcb->local_ip)) {
+ snmp_ip4_to_oid(ip_2_ip4(&pcb->local_ip), &test_oid[0]);
+ test_oid[4] = pcb->local_port;
+
+ /* check generated OID: is it a candidate for the next one? */
+ snmp_next_oid_check(&state, test_oid, LWIP_ARRAYSIZE(udp_Table_oid_ranges), pcb);
+ }
+
+ pcb = pcb->next;
+ }
+
+ /* did we find a next one? */
+ if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+ snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+ /* fill in object properties */
+ return udp_Table_get_cell_value_core((struct udp_pcb *)state.reference, column, value, value_len);
+ } else {
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+}
+
+#endif /* LWIP_IPV4 */
+
+static const struct snmp_scalar_node udp_inDatagrams = SNMP_SCALAR_CREATE_NODE_READONLY(1, SNMP_ASN1_TYPE_COUNTER, udp_get_value);
+static const struct snmp_scalar_node udp_noPorts = SNMP_SCALAR_CREATE_NODE_READONLY(2, SNMP_ASN1_TYPE_COUNTER, udp_get_value);
+static const struct snmp_scalar_node udp_inErrors = SNMP_SCALAR_CREATE_NODE_READONLY(3, SNMP_ASN1_TYPE_COUNTER, udp_get_value);
+static const struct snmp_scalar_node udp_outDatagrams = SNMP_SCALAR_CREATE_NODE_READONLY(4, SNMP_ASN1_TYPE_COUNTER, udp_get_value);
+#if LWIP_HAVE_INT64
+static const struct snmp_scalar_node udp_HCInDatagrams = SNMP_SCALAR_CREATE_NODE_READONLY(8, SNMP_ASN1_TYPE_COUNTER64, udp_get_value);
+static const struct snmp_scalar_node udp_HCOutDatagrams = SNMP_SCALAR_CREATE_NODE_READONLY(9, SNMP_ASN1_TYPE_COUNTER64, udp_get_value);
+#endif
+
+#if LWIP_IPV4
+static const struct snmp_table_simple_col_def udp_Table_columns[] = {
+ { 1, SNMP_ASN1_TYPE_IPADDR, SNMP_VARIANT_VALUE_TYPE_U32 }, /* udpLocalAddress */
+ { 2, SNMP_ASN1_TYPE_INTEGER, SNMP_VARIANT_VALUE_TYPE_U32 } /* udpLocalPort */
+};
+static const struct snmp_table_simple_node udp_Table = SNMP_TABLE_CREATE_SIMPLE(5, udp_Table_columns, udp_Table_get_cell_value, udp_Table_get_next_cell_instance_and_value);
+#endif /* LWIP_IPV4 */
+
+static const struct snmp_table_simple_col_def udp_endpointTable_columns[] = {
+ /* all items except udpEndpointProcess are declared as not-accessible */
+ { 8, SNMP_ASN1_TYPE_UNSIGNED32, SNMP_VARIANT_VALUE_TYPE_U32 } /* udpEndpointProcess */
+};
+
+static const struct snmp_table_simple_node udp_endpointTable = SNMP_TABLE_CREATE_SIMPLE(7, udp_endpointTable_columns, udp_endpointTable_get_cell_value, udp_endpointTable_get_next_cell_instance_and_value);
+
+/* the following nodes access variables in LWIP stack from SNMP worker thread and must therefore be synced to LWIP (TCPIP) thread */
+CREATE_LWIP_SYNC_NODE(1, udp_inDatagrams)
+CREATE_LWIP_SYNC_NODE(2, udp_noPorts)
+CREATE_LWIP_SYNC_NODE(3, udp_inErrors)
+CREATE_LWIP_SYNC_NODE(4, udp_outDatagrams)
+#if LWIP_IPV4
+CREATE_LWIP_SYNC_NODE(5, udp_Table)
+#endif /* LWIP_IPV4 */
+CREATE_LWIP_SYNC_NODE(7, udp_endpointTable)
+#if LWIP_HAVE_INT64
+CREATE_LWIP_SYNC_NODE(8, udp_HCInDatagrams)
+CREATE_LWIP_SYNC_NODE(9, udp_HCOutDatagrams)
+#endif
+
+static const struct snmp_node *const udp_nodes[] = {
+ &SYNC_NODE_NAME(udp_inDatagrams).node.node,
+ &SYNC_NODE_NAME(udp_noPorts).node.node,
+ &SYNC_NODE_NAME(udp_inErrors).node.node,
+ &SYNC_NODE_NAME(udp_outDatagrams).node.node,
+#if LWIP_IPV4
+ &SYNC_NODE_NAME(udp_Table).node.node,
+#endif /* LWIP_IPV4 */
+ &SYNC_NODE_NAME(udp_endpointTable).node.node
+#if LWIP_HAVE_INT64
+ ,
+ &SYNC_NODE_NAME(udp_HCInDatagrams).node.node,
+ &SYNC_NODE_NAME(udp_HCOutDatagrams).node.node
+#endif
+};
+
+const struct snmp_tree_node snmp_mib2_udp_root = SNMP_CREATE_TREE_NODE(7, udp_nodes);
+#endif /* LWIP_SNMP && SNMP_LWIP_MIB2 && LWIP_UDP */
diff --git a/src/apps/snmp/snmp_msg.c b/src/apps/snmp/snmp_msg.c
new file mode 100644
index 00000000000..9cd4b071e17
--- /dev/null
+++ b/src/apps/snmp/snmp_msg.c
@@ -0,0 +1,1981 @@
+/**
+ * @file
+ * SNMP message processing (RFC1157).
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * Copyright (c) 2016 Elias Oenal.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Christiaan Simons <[email protected]>
+ * Martin Hentschel <[email protected]>
+ * Elias Oenal <[email protected]>
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "snmp_msg.h"
+#include "snmp_asn1.h"
+#include "snmp_core_priv.h"
+#include "lwip/ip_addr.h"
+#include "lwip/stats.h"
+
+#if LWIP_SNMP_V3
+#include "lwip/apps/snmpv3.h"
+#include "snmpv3_priv.h"
+#ifdef LWIP_HOOK_FILENAME
+#include LWIP_HOOK_FILENAME
+#endif
+#endif
+
+#include <string.h>
+
+#define SNMP_V3_AUTH_FLAG 0x01
+#define SNMP_V3_PRIV_FLAG 0x02
+
+/* Security levels */
+#define SNMP_V3_NOAUTHNOPRIV 0x00
+#define SNMP_V3_AUTHNOPRIV SNMP_V3_AUTH_FLAG
+#define SNMP_V3_AUTHPRIV (SNMP_V3_AUTH_FLAG | SNMP_V3_PRIV_FLAG)
+
+/* public (non-static) constants */
+/** SNMP community string */
+const char *snmp_community = SNMP_COMMUNITY;
+/** SNMP community string for write access */
+const char *snmp_community_write = SNMP_COMMUNITY_WRITE;
+/** SNMP community string for sending traps */
+const char *snmp_community_trap = SNMP_COMMUNITY_TRAP;
+
+snmp_write_callback_fct snmp_write_callback;
+void *snmp_write_callback_arg;
+
+snmp_inform_callback_fct snmp_inform_callback;
+void *snmp_inform_callback_arg;
+
+#if LWIP_SNMP_CONFIGURE_VERSIONS
+
+static u8_t v1_enabled = 1;
+static u8_t v2c_enabled = 1;
+static u8_t v3_enabled = 1;
+
+static u8_t
+snmp_version_enabled(u8_t version)
+{
+ if (version == SNMP_VERSION_1) {
+ return v1_enabled;
+ } else if (version == SNMP_VERSION_2c) {
+ return v2c_enabled;
+ }
+#if LWIP_SNMP_V3
+ else if (version == SNMP_VERSION_3) {
+ return v3_enabled;
+ }
+#endif
+ else {
+ LWIP_ASSERT("Invalid SNMP version", 0);
+ return 0;
+ }
+}
+
+u8_t
+snmp_v1_enabled(void)
+{
+ return snmp_version_enabled(SNMP_VERSION_1);
+}
+
+u8_t
+snmp_v2c_enabled(void)
+{
+ return snmp_version_enabled(SNMP_VERSION_2c);
+}
+
+u8_t
+snmp_v3_enabled(void)
+{
+ return snmp_version_enabled(SNMP_VERSION_3);
+}
+
+static void
+snmp_version_enable(u8_t version, u8_t enable)
+{
+ if (version == SNMP_VERSION_1) {
+ v1_enabled = enable;
+ } else if (version == SNMP_VERSION_2c) {
+ v2c_enabled = enable;
+ }
+#if LWIP_SNMP_V3
+ else if (version == SNMP_VERSION_3) {
+ v3_enabled = enable;
+ }
+#endif
+ else {
+ LWIP_ASSERT("Invalid SNMP version", 0);
+ }
+}
+
+void
+snmp_v1_enable(u8_t enable)
+{
+ snmp_version_enable(SNMP_VERSION_1, enable);
+}
+
+void
+snmp_v2c_enable(u8_t enable)
+{
+ snmp_version_enable(SNMP_VERSION_2c, enable);
+}
+
+void
+snmp_v3_enable(u8_t enable)
+{
+ snmp_version_enable(SNMP_VERSION_3, enable);
+}
+
+#endif
+
+/**
+ * @ingroup snmp_core
+ * Returns current SNMP community string.
+ * @return current SNMP community string
+ */
+const char *
+snmp_get_community(void)
+{
+ return snmp_community;
+}
+
+/**
+ * @ingroup snmp_core
+ * Sets SNMP community string.
+ * The string itself (its storage) must be valid throughout the whole life of
+ * program (or until it is changed to sth else).
+ *
+ * @param community is a pointer to new community string
+ */
+void
+snmp_set_community(const char *const community)
+{
+ LWIP_ASSERT_SNMP_LOCKED();
+ LWIP_ASSERT("community string is too long!", strlen(community) <= SNMP_MAX_COMMUNITY_STR_LEN);
+ snmp_community = community;
+}
+
+/**
+ * @ingroup snmp_core
+ * Returns current SNMP write-access community string.
+ * @return current SNMP write-access community string
+ */
+const char *
+snmp_get_community_write(void)
+{
+ return snmp_community_write;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Returns current SNMP community string used for sending traps.
+ * @return current SNMP community string used for sending traps
+ */
+const char *
+snmp_get_community_trap(void)
+{
+ return snmp_community_trap;
+}
+
+/**
+ * @ingroup snmp_core
+ * Sets SNMP community string for write-access.
+ * The string itself (its storage) must be valid throughout the whole life of
+ * program (or until it is changed to sth else).
+ *
+ * @param community is a pointer to new write-access community string
+ */
+void
+snmp_set_community_write(const char *const community)
+{
+ LWIP_ASSERT_SNMP_LOCKED();
+ LWIP_ASSERT("community string must not be NULL", community != NULL);
+ LWIP_ASSERT("community string is too long!", strlen(community) <= SNMP_MAX_COMMUNITY_STR_LEN);
+ snmp_community_write = community;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Sets SNMP community string used for sending traps.
+ * The string itself (its storage) must be valid throughout the whole life of
+ * program (or until it is changed to sth else).
+ *
+ * @param community is a pointer to new trap community string
+ */
+void
+snmp_set_community_trap(const char *const community)
+{
+ LWIP_ASSERT_SNMP_LOCKED();
+ LWIP_ASSERT("community string is too long!", strlen(community) <= SNMP_MAX_COMMUNITY_STR_LEN);
+ snmp_community_trap = community;
+}
+
+/**
+ * @ingroup snmp_core
+ * Callback fired on every successful write access
+ */
+void
+snmp_set_write_callback(snmp_write_callback_fct write_callback, void *callback_arg)
+{
+ LWIP_ASSERT_SNMP_LOCKED();
+ snmp_write_callback = write_callback;
+ snmp_write_callback_arg = callback_arg;
+}
+
+/**
+ * @ingroup snmp_core
+ * Callback fired on every received INFORM confirmation (get-response)
+ */
+void
+snmp_set_inform_callback(snmp_inform_callback_fct inform_callback, void* callback_arg)
+{
+ snmp_inform_callback = inform_callback;
+ snmp_inform_callback_arg = callback_arg;
+}
+
+/* ----------------------------------------------------------------------- */
+/* forward declarations */
+/* ----------------------------------------------------------------------- */
+
+static err_t snmp_process_get_request(struct snmp_request *request);
+static err_t snmp_process_getnext_request(struct snmp_request *request);
+static err_t snmp_process_getbulk_request(struct snmp_request *request);
+static err_t snmp_process_set_request(struct snmp_request *request);
+
+static err_t snmp_parse_inbound_frame(struct snmp_request *request);
+static err_t snmp_prepare_outbound_frame(struct snmp_request *request);
+static err_t snmp_complete_outbound_frame(struct snmp_request *request);
+static void snmp_execute_write_callbacks(struct snmp_request *request);
+
+
+/* ----------------------------------------------------------------------- */
+/* implementation */
+/* ----------------------------------------------------------------------- */
+
+void
+snmp_receive(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t port)
+{
+ err_t err;
+ struct snmp_request request;
+
+ memset(&request, 0, sizeof(request));
+ request.handle = handle;
+ request.source_ip = source_ip;
+ request.source_port = port;
+ request.inbound_pbuf = p;
+
+ snmp_stats.inpkts++;
+
+ err = snmp_parse_inbound_frame(&request);
+ if (err == ERR_OK) {
+ if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_RESP) {
+ if (request.error_status == SNMP_ERR_NOERROR) {
+ /* If callback function has been defined call it. */
+ if (snmp_inform_callback != NULL) {
+ snmp_inform_callback(&request, snmp_inform_callback_arg);
+ }
+ }
+ /* stop further handling of GET RESP PDU, we are an agent */
+ return;
+ }
+ err = snmp_prepare_outbound_frame(&request);
+ if (err == ERR_OK) {
+
+ if (request.error_status == SNMP_ERR_NOERROR) {
+ /* only process frame if we do not already have an error to return (e.g. all readonly) */
+ if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_REQ) {
+ err = snmp_process_get_request(&request);
+ } else if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_NEXT_REQ) {
+ err = snmp_process_getnext_request(&request);
+ } else if (request.request_type == SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ) {
+ err = snmp_process_getbulk_request(&request);
+ } else if (request.request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
+ err = snmp_process_set_request(&request);
+ }
+ }
+#if LWIP_SNMP_V3
+ else {
+ struct snmp_varbind vb;
+
+ vb.next = NULL;
+ vb.prev = NULL;
+ vb.type = SNMP_ASN1_TYPE_COUNTER32;
+ vb.value_len = sizeof(u32_t);
+
+ switch (request.error_status) {
+ case SNMP_ERR_AUTHORIZATIONERROR: {
+ static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 5, 0 };
+ snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
+ vb.value = &snmp_stats.wrongdigests;
+ }
+ break;
+ case SNMP_ERR_UNKNOWN_ENGINEID: {
+ static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 4, 0 };
+ snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
+ vb.value = &snmp_stats.unknownengineids;
+ }
+ break;
+ case SNMP_ERR_UNKNOWN_SECURITYNAME: {
+ static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 3, 0 };
+ snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
+ vb.value = &snmp_stats.unknownusernames;
+ }
+ break;
+ case SNMP_ERR_UNSUPPORTED_SECLEVEL: {
+ static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 1, 0 };
+ snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
+ vb.value = &snmp_stats.unsupportedseclevels;
+ }
+ break;
+ case SNMP_ERR_NOTINTIMEWINDOW: {
+ static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 2, 0 };
+ snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
+ vb.value = &snmp_stats.notintimewindows;
+ }
+ break;
+ case SNMP_ERR_DECRYIPTION_ERROR: {
+ static const u32_t oid[] = { 1, 3, 6, 1, 6, 3, 15, 1, 1, 6, 0 };
+ snmp_oid_assign(&vb.oid, oid, LWIP_ARRAYSIZE(oid));
+ vb.value = &snmp_stats.decryptionerrors;
+ }
+ break;
+ default:
+ /* Unknown or unhandled error_status */
+ err = ERR_ARG;
+ }
+
+ if (err == ERR_OK) {
+ snmp_append_outbound_varbind(&(request.outbound_pbuf_stream), &vb);
+ request.error_status = SNMP_ERR_NOERROR;
+ }
+
+ request.request_out_type = (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_REPORT);
+ request.request_id = request.msg_id;
+ }
+#endif
+
+ if (err == ERR_OK) {
+ err = snmp_complete_outbound_frame(&request);
+
+ if (err == ERR_OK) {
+ err = snmp_sendto(request.handle, request.outbound_pbuf, request.source_ip, request.source_port);
+
+ if ((request.request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ)
+ && (request.error_status == SNMP_ERR_NOERROR)
+ && (snmp_write_callback != NULL)) {
+ /* raise write notification for all written objects */
+ snmp_execute_write_callbacks(&request);
+ }
+ }
+ }
+ }
+
+ if (request.outbound_pbuf != NULL) {
+ pbuf_free(request.outbound_pbuf);
+ }
+ }
+}
+
+static u8_t
+snmp_msg_getnext_validate_node_inst(struct snmp_node_instance *node_instance, void *validate_arg)
+{
+ if (((node_instance->access & SNMP_NODE_INSTANCE_ACCESS_READ) != SNMP_NODE_INSTANCE_ACCESS_READ) || (node_instance->get_value == NULL)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+#if LWIP_HAVE_INT64
+ if ((node_instance->asn1_type == SNMP_ASN1_TYPE_COUNTER64) && (((struct snmp_request *)validate_arg)->version == SNMP_VERSION_1)) {
+ /* according to RFC 2089 skip Counter64 objects in GetNext requests from v1 clients */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+#endif
+
+ return SNMP_ERR_NOERROR;
+}
+
+static void
+snmp_process_varbind(struct snmp_request *request, struct snmp_varbind *vb, u8_t get_next)
+{
+ err_t err;
+ struct snmp_node_instance node_instance;
+ memset(&node_instance, 0, sizeof(node_instance));
+
+ if (get_next) {
+ struct snmp_obj_id result_oid;
+ request->error_status = snmp_get_next_node_instance_from_oid(vb->oid.id, vb->oid.len, snmp_msg_getnext_validate_node_inst, request, &result_oid, &node_instance);
+
+ if (request->error_status == SNMP_ERR_NOERROR) {
+ snmp_oid_assign(&vb->oid, result_oid.id, result_oid.len);
+ }
+ } else {
+ request->error_status = snmp_get_node_instance_from_oid(vb->oid.id, vb->oid.len, &node_instance);
+
+ if (request->error_status == SNMP_ERR_NOERROR) {
+ /* use 'getnext_validate' method for validation to avoid code duplication (some checks have to be executed here) */
+ request->error_status = snmp_msg_getnext_validate_node_inst(&node_instance, request);
+
+ if (request->error_status != SNMP_ERR_NOERROR) {
+ if (node_instance.release_instance != NULL) {
+ node_instance.release_instance(&node_instance);
+ }
+ }
+ }
+ }
+
+ if (request->error_status != SNMP_ERR_NOERROR) {
+ if (request->error_status >= SNMP_VARBIND_EXCEPTION_OFFSET) {
+ if ((request->version == SNMP_VERSION_2c) || request->version == SNMP_VERSION_3) {
+ /* in SNMP v2c a varbind related exception is stored in varbind and not in frame header */
+ vb->type = (SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_CLASS_CONTEXT | (request->error_status & SNMP_VARBIND_EXCEPTION_MASK));
+ vb->value_len = 0;
+
+ err = snmp_append_outbound_varbind(&(request->outbound_pbuf_stream), vb);
+ if (err == ERR_OK) {
+ /* we stored the exception in varbind -> go on */
+ request->error_status = SNMP_ERR_NOERROR;
+ } else if (err == ERR_BUF) {
+ request->error_status = SNMP_ERR_TOOBIG;
+ } else {
+ request->error_status = SNMP_ERR_GENERROR;
+ }
+ }
+ } else {
+ /* according to RFC 1157/1905, all other errors only return genError */
+ request->error_status = SNMP_ERR_GENERROR;
+ }
+ } else {
+ s16_t len = node_instance.get_value(&node_instance, vb->value);
+
+ if (len >= 0) {
+ vb->value_len = (u16_t)len; /* cast is OK because we checked >= 0 above */
+ vb->type = node_instance.asn1_type;
+
+ LWIP_ASSERT("SNMP_MAX_VALUE_SIZE is configured too low", (vb->value_len & ~SNMP_GET_VALUE_RAW_DATA) <= SNMP_MAX_VALUE_SIZE);
+ err = snmp_append_outbound_varbind(&request->outbound_pbuf_stream, vb);
+
+ if (err == ERR_BUF) {
+ request->error_status = SNMP_ERR_TOOBIG;
+ } else if (err != ERR_OK) {
+ request->error_status = SNMP_ERR_GENERROR;
+ }
+ } else {
+ request->error_status = SNMP_ERR_GENERROR;
+ }
+
+ if (node_instance.release_instance != NULL) {
+ node_instance.release_instance(&node_instance);
+ }
+ }
+}
+
+
+/**
+ * Service an internal or external event for SNMP GET.
+ *
+ * @param request points to the associated message process state
+ */
+static err_t
+snmp_process_get_request(struct snmp_request *request)
+{
+ snmp_vb_enumerator_err_t err;
+ struct snmp_varbind vb;
+ vb.value = request->value_buffer;
+
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP get request\n"));
+
+ while (request->error_status == SNMP_ERR_NOERROR) {
+ err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
+ if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
+ if ((vb.type == SNMP_ASN1_TYPE_NULL) && (vb.value_len == 0)) {
+ snmp_process_varbind(request, &vb, 0);
+ } else {
+ request->error_status = SNMP_ERR_GENERROR;
+ }
+ } else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+ /* no more varbinds in request */
+ break;
+ } else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
+ /* malformed ASN.1, don't answer */
+ return ERR_ARG;
+ } else {
+ request->error_status = SNMP_ERR_GENERROR;
+ }
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * Service an internal or external event for SNMP GET.
+ *
+ * @param request points to the associated message process state
+ */
+static err_t
+snmp_process_getnext_request(struct snmp_request *request)
+{
+ snmp_vb_enumerator_err_t err;
+ struct snmp_varbind vb;
+ vb.value = request->value_buffer;
+
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP get-next request\n"));
+
+ while (request->error_status == SNMP_ERR_NOERROR) {
+ err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
+ if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
+ if ((vb.type == SNMP_ASN1_TYPE_NULL) && (vb.value_len == 0)) {
+ snmp_process_varbind(request, &vb, 1);
+ } else {
+ request->error_status = SNMP_ERR_GENERROR;
+ }
+ } else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+ /* no more varbinds in request */
+ break;
+ } else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
+ /* malformed ASN.1, don't answer */
+ return ERR_ARG;
+ } else {
+ request->error_status = SNMP_ERR_GENERROR;
+ }
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * Service an internal or external event for SNMP GETBULKT.
+ *
+ * @param request points to the associated message process state
+ */
+static err_t
+snmp_process_getbulk_request(struct snmp_request *request)
+{
+ snmp_vb_enumerator_err_t err;
+ s32_t non_repeaters = request->non_repeaters;
+ s32_t repetitions;
+ u16_t repetition_offset = 0;
+ struct snmp_varbind_enumerator repetition_varbind_enumerator;
+ struct snmp_varbind vb;
+ vb.value = request->value_buffer;
+
+ if (SNMP_LWIP_GETBULK_MAX_REPETITIONS > 0) {
+ repetitions = LWIP_MIN(request->max_repetitions, SNMP_LWIP_GETBULK_MAX_REPETITIONS);
+ } else {
+ repetitions = request->max_repetitions;
+ }
+
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP get-bulk request\n"));
+
+ /* process non repeaters and first repetition */
+ while (request->error_status == SNMP_ERR_NOERROR) {
+ if (non_repeaters == 0) {
+ repetition_offset = request->outbound_pbuf_stream.offset;
+
+ if (repetitions == 0) {
+ /* do not resolve repeaters when repetitions is set to 0 */
+ break;
+ }
+ repetitions--;
+ }
+
+ err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
+ if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+ /* no more varbinds in request */
+ break;
+ } else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
+ /* malformed ASN.1, don't answer */
+ return ERR_ARG;
+ } else if ((err != SNMP_VB_ENUMERATOR_ERR_OK) || (vb.type != SNMP_ASN1_TYPE_NULL) || (vb.value_len != 0)) {
+ request->error_status = SNMP_ERR_GENERROR;
+ } else {
+ snmp_process_varbind(request, &vb, 1);
+ non_repeaters--;
+ }
+ }
+
+ /* process repetitions > 1 */
+ while ((request->error_status == SNMP_ERR_NOERROR) && (repetitions > 0) && (request->outbound_pbuf_stream.offset != repetition_offset)) {
+
+ u8_t all_endofmibview = 1;
+
+ snmp_vb_enumerator_init(&repetition_varbind_enumerator, request->outbound_pbuf, repetition_offset, request->outbound_pbuf_stream.offset - repetition_offset);
+ repetition_offset = request->outbound_pbuf_stream.offset; /* for next loop */
+
+ while (request->error_status == SNMP_ERR_NOERROR) {
+ vb.value = NULL; /* do NOT decode value (we enumerate outbound buffer here, so all varbinds have values assigned) */
+ err = snmp_vb_enumerator_get_next(&repetition_varbind_enumerator, &vb);
+ if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
+ vb.value = request->value_buffer;
+ snmp_process_varbind(request, &vb, 1);
+
+ if (request->error_status != SNMP_ERR_NOERROR) {
+ /* already set correct error-index (here it cannot be taken from inbound varbind enumerator) */
+ request->error_index = request->non_repeaters + repetition_varbind_enumerator.varbind_count;
+ } else if (vb.type != (SNMP_ASN1_CONTENTTYPE_PRIMITIVE | SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTEXT_VARBIND_END_OF_MIB_VIEW)) {
+ all_endofmibview = 0;
+ }
+ } else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+ /* no more varbinds in request */
+ break;
+ } else {
+ LWIP_DEBUGF(SNMP_DEBUG, ("Very strange, we cannot parse the varbind output that we created just before!\n"));
+ request->error_status = SNMP_ERR_GENERROR;
+ request->error_index = request->non_repeaters + repetition_varbind_enumerator.varbind_count;
+ }
+ }
+
+ if ((request->error_status == SNMP_ERR_NOERROR) && all_endofmibview) {
+ /* stop when all varbinds in a loop return EndOfMibView */
+ break;
+ }
+
+ repetitions--;
+ }
+
+ if (request->error_status == SNMP_ERR_TOOBIG) {
+ /* for GetBulk it is ok, if not all requested variables fit into the response -> just return the varbinds added so far */
+ request->error_status = SNMP_ERR_NOERROR;
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * Service an internal or external event for SNMP SET.
+ *
+ * @param request points to the associated message process state
+ */
+static err_t
+snmp_process_set_request(struct snmp_request *request)
+{
+ snmp_vb_enumerator_err_t err;
+ struct snmp_varbind vb;
+ vb.value = request->value_buffer;
+
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP set request\n"));
+
+ /* perform set test on all objects */
+ while (request->error_status == SNMP_ERR_NOERROR) {
+ err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
+ if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
+ struct snmp_node_instance node_instance;
+ memset(&node_instance, 0, sizeof(node_instance));
+
+ request->error_status = snmp_get_node_instance_from_oid(vb.oid.id, vb.oid.len, &node_instance);
+ if (request->error_status == SNMP_ERR_NOERROR) {
+ if (node_instance.asn1_type != vb.type) {
+ request->error_status = SNMP_ERR_WRONGTYPE;
+ } else if (((node_instance.access & SNMP_NODE_INSTANCE_ACCESS_WRITE) != SNMP_NODE_INSTANCE_ACCESS_WRITE) || (node_instance.set_value == NULL)) {
+ request->error_status = SNMP_ERR_NOTWRITABLE;
+ } else {
+ if (node_instance.set_test != NULL) {
+ request->error_status = node_instance.set_test(&node_instance, vb.value_len, vb.value);
+ }
+ }
+
+ if (node_instance.release_instance != NULL) {
+ node_instance.release_instance(&node_instance);
+ }
+ }
+ } else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+ /* no more varbinds in request */
+ break;
+ } else if (err == SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH) {
+ request->error_status = SNMP_ERR_WRONGLENGTH;
+ } else if (err == SNMP_VB_ENUMERATOR_ERR_ASN1ERROR) {
+ /* malformed ASN.1, don't answer */
+ return ERR_ARG;
+ } else {
+ request->error_status = SNMP_ERR_GENERROR;
+ }
+ }
+
+ /* perform real set operation on all objects */
+ if (request->error_status == SNMP_ERR_NOERROR) {
+ snmp_vb_enumerator_init(&request->inbound_varbind_enumerator, request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len);
+ while (request->error_status == SNMP_ERR_NOERROR) {
+ err = snmp_vb_enumerator_get_next(&request->inbound_varbind_enumerator, &vb);
+ if (err == SNMP_VB_ENUMERATOR_ERR_OK) {
+ struct snmp_node_instance node_instance;
+ memset(&node_instance, 0, sizeof(node_instance));
+ request->error_status = snmp_get_node_instance_from_oid(vb.oid.id, vb.oid.len, &node_instance);
+ if (request->error_status == SNMP_ERR_NOERROR) {
+ if (node_instance.set_value(&node_instance, vb.value_len, vb.value) != SNMP_ERR_NOERROR) {
+ if (request->inbound_varbind_enumerator.varbind_count == 1) {
+ request->error_status = SNMP_ERR_COMMITFAILED;
+ } else {
+ /* we cannot undo the set operations done so far */
+ request->error_status = SNMP_ERR_UNDOFAILED;
+ }
+ }
+
+ if (node_instance.release_instance != NULL) {
+ node_instance.release_instance(&node_instance);
+ }
+ }
+ } else if (err == SNMP_VB_ENUMERATOR_ERR_EOVB) {
+ /* no more varbinds in request */
+ break;
+ } else {
+ /* first time enumerating varbinds work but second time not, although nothing should have changed in between ??? */
+ request->error_status = SNMP_ERR_GENERROR;
+ }
+ }
+ }
+
+ return ERR_OK;
+}
+
+#define PARSE_EXEC(code, retValue) \
+ if ((code) != ERR_OK) { \
+ LWIP_DEBUGF(SNMP_DEBUG, ("Malformed ASN.1 detected.\n")); \
+ snmp_stats.inasnparseerrs++; \
+ return retValue; \
+ }
+
+#define PARSE_ASSERT(cond, retValue) \
+ if (!(cond)) { \
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP parse assertion failed!: " # cond)); \
+ LWIP_DEBUGF(SNMP_DEBUG, ("\n")); \
+ snmp_stats.inasnparseerrs++; \
+ return retValue; \
+ }
+
+#define BUILD_EXEC(code, retValue) \
+ if ((code) != ERR_OK) { \
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP error during creation of outbound frame!: " # code)); \
+ LWIP_DEBUGF(SNMP_DEBUG, ("\n")); \
+ return retValue; \
+ }
+
+#define IF_PARSE_EXEC(code) PARSE_EXEC(code, ERR_ARG)
+#define IF_PARSE_ASSERT(code) PARSE_ASSERT(code, ERR_ARG)
+
+/**
+ * Checks and decodes incoming SNMP message header, logs header errors.
+ *
+ * @param request points to the current message request state return
+ * @return
+ * - ERR_OK SNMP header is sane and accepted
+ * - ERR_VAL SNMP header is either malformed or rejected
+ */
+static err_t
+snmp_parse_inbound_frame(struct snmp_request *request)
+{
+ struct snmp_pbuf_stream pbuf_stream;
+ struct snmp_asn1_tlv tlv;
+ s32_t parent_tlv_value_len;
+ s32_t s32_value;
+ err_t err;
+#if LWIP_SNMP_V3
+ snmpv3_auth_algo_t auth;
+ snmpv3_priv_algo_t priv;
+#endif
+
+ IF_PARSE_EXEC(snmp_pbuf_stream_init(&pbuf_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
+
+ /* decode main container consisting of version, community and PDU */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_SEQUENCE) && (tlv.value_len == pbuf_stream.length));
+ parent_tlv_value_len = tlv.value_len;
+
+ /* decode version */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+
+ if (((s32_value != SNMP_VERSION_1) &&
+ (s32_value != SNMP_VERSION_2c)
+#if LWIP_SNMP_V3
+ && (s32_value != SNMP_VERSION_3)
+#endif
+ )
+#if LWIP_SNMP_CONFIGURE_VERSIONS
+ || (!snmp_version_enabled(s32_value))
+#endif
+ ) {
+ /* unsupported SNMP version */
+ snmp_stats.inbadversions++;
+ return ERR_ARG;
+ }
+ request->version = (u8_t)s32_value;
+
+#if LWIP_SNMP_V3
+ if (request->version == SNMP_VERSION_3) {
+ u16_t u16_value;
+ u16_t inbound_msgAuthenticationParameters_offset;
+
+ /* SNMPv3 doesn't use communities */
+ /* @todo: Differentiate read/write access */
+ strncpy((char *)request->community, snmp_community, SNMP_MAX_COMMUNITY_STR_LEN);
+ request->community[SNMP_MAX_COMMUNITY_STR_LEN] = 0; /* ensure NULL termination (strncpy does NOT guarantee it!) */
+ request->community_strlen = (u16_t)strlen((char *)request->community);
+
+ /* RFC3414 globalData */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_SEQUENCE);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ /* decode msgID */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+ request->msg_id = s32_value;
+
+ /* decode msgMaxSize */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+ request->msg_max_size = s32_value;
+
+ /* decode msgFlags */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+ request->msg_flags = (u8_t)s32_value;
+
+ /* decode msgSecurityModel */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+ request->msg_security_model = s32_value;
+
+ /* RFC3414 msgSecurityParameters
+ * The User-based Security Model defines the contents of the OCTET
+ * STRING as a SEQUENCE.
+ *
+ * We skip the protective dummy OCTET STRING header
+ * to access the SEQUENCE header.
+ */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ /* msgSecurityParameters SEQUENCE header */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_SEQUENCE);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ /* decode msgAuthoritativeEngineID */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_authoritative_engine_id,
+ &u16_value, SNMP_V3_MAX_ENGINE_ID_LENGTH));
+ request->msg_authoritative_engine_id_len = (u8_t)u16_value;
+
+ /* msgAuthoritativeEngineBoots */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->msg_authoritative_engine_boots));
+
+ /* msgAuthoritativeEngineTime */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->msg_authoritative_engine_time));
+
+ /* msgUserName */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_user_name,
+ &u16_value, SNMP_V3_MAX_USER_LENGTH));
+ request->msg_user_name_len = (u8_t)u16_value;
+
+ /* msgAuthenticationParameters */
+ memset(request->msg_authentication_parameters, 0, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+ /* Remember position */
+ inbound_msgAuthenticationParameters_offset = pbuf_stream.offset;
+ LWIP_UNUSED_ARG(inbound_msgAuthenticationParameters_offset);
+ /* Read auth parameters */
+ /* IF_PARSE_ASSERT(tlv.value_len <= SNMP_V3_MAX_AUTH_PARAM_LENGTH); */
+ IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_authentication_parameters,
+ &u16_value, tlv.value_len));
+ request->msg_authentication_parameters_len = (u8_t)u16_value;
+
+ /* msgPrivacyParameters */
+ memset(request->msg_privacy_parameters, 0, SNMP_V3_MAX_PRIV_PARAM_LENGTH);
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_privacy_parameters,
+ &u16_value, SNMP_V3_MAX_PRIV_PARAM_LENGTH));
+ request->msg_privacy_parameters_len = (u8_t)u16_value;
+
+ /* validate securityParameters here (do this after decoding because we don't want to increase other counters for wrong frames)
+ * 1) securityParameters was correctly serialized if we reach here.
+ * 2) securityParameters are already cached.
+ * 3) if msgAuthoritativeEngineID is unknown, zero-length or too long:
+ b) https://tools.ietf.org/html/rfc3414#section-7
+ */
+ {
+ const char *eid;
+ u8_t eid_len;
+
+ snmpv3_get_engine_id(&eid, &eid_len);
+
+ if ((request->msg_authoritative_engine_id_len == 0) ||
+ (request->msg_authoritative_engine_id_len != eid_len) ||
+ (memcmp(eid, request->msg_authoritative_engine_id, eid_len) != 0)) {
+ snmp_stats.unknownengineids++;
+ request->msg_flags = 0; /* noauthnopriv */
+ request->error_status = SNMP_ERR_UNKNOWN_ENGINEID;
+ return ERR_OK;
+ }
+ }
+
+ /* 4) verify username */
+ if (snmpv3_get_user((char *)request->msg_user_name, &auth, NULL, &priv, NULL)) {
+ snmp_stats.unknownusernames++;
+ request->msg_flags = 0; /* noauthnopriv */
+ request->error_status = SNMP_ERR_UNKNOWN_SECURITYNAME;
+ return ERR_OK;
+ }
+
+ /* 5) verify security level */
+ switch (request->msg_flags & (SNMP_V3_AUTH_FLAG | SNMP_V3_PRIV_FLAG)) {
+ case SNMP_V3_NOAUTHNOPRIV:
+ if ((auth != SNMP_V3_AUTH_ALGO_INVAL) || (priv != SNMP_V3_PRIV_ALGO_INVAL)) {
+ /* Invalid security level for user */
+ snmp_stats.unsupportedseclevels++;
+ request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
+ request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
+ return ERR_OK;
+ }
+ break;
+#if LWIP_SNMP_V3_CRYPTO
+ case SNMP_V3_AUTHNOPRIV:
+ if ((auth == SNMP_V3_AUTH_ALGO_INVAL) || (priv != SNMP_V3_PRIV_ALGO_INVAL)) {
+ /* Invalid security level for user */
+ snmp_stats.unsupportedseclevels++;
+ request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
+ request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
+ return ERR_OK;
+ }
+ break;
+ case SNMP_V3_AUTHPRIV:
+ if ((auth == SNMP_V3_AUTH_ALGO_INVAL) || (priv == SNMP_V3_PRIV_ALGO_INVAL)) {
+ /* Invalid security level for user */
+ snmp_stats.unsupportedseclevels++;
+ request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
+ request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
+ return ERR_OK;
+ }
+ break;
+#endif
+ default:
+ snmp_stats.unsupportedseclevels++;
+ request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
+ request->error_status = SNMP_ERR_UNSUPPORTED_SECLEVEL;
+ return ERR_OK;
+ }
+
+ /* 6) if securitylevel specifies authentication, authenticate message. */
+#if LWIP_SNMP_V3_CRYPTO
+ if (request->msg_flags & SNMP_V3_AUTH_FLAG) {
+ const u8_t zero_arr[SNMP_V3_MAX_AUTH_PARAM_LENGTH] = { 0 };
+ u8_t key[20];
+ u8_t hmac[LWIP_MAX(SNMP_V3_SHA_LEN, SNMP_V3_MD5_LEN)];
+ struct snmp_pbuf_stream auth_stream;
+
+ if (request->msg_authentication_parameters_len > SNMP_V3_MAX_AUTH_PARAM_LENGTH) {
+ snmp_stats.wrongdigests++;
+ request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
+ request->error_status = SNMP_ERR_AUTHORIZATIONERROR;
+ return ERR_OK;
+ }
+
+ /* Rewind stream */
+ IF_PARSE_EXEC(snmp_pbuf_stream_init(&auth_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
+ IF_PARSE_EXEC(snmp_pbuf_stream_seek_abs(&auth_stream, inbound_msgAuthenticationParameters_offset));
+ /* Set auth parameters to zero for verification */
+ IF_PARSE_EXEC(snmp_asn1_enc_raw(&auth_stream, zero_arr, request->msg_authentication_parameters_len));
+
+ /* Verify authentication */
+ IF_PARSE_EXEC(snmp_pbuf_stream_init(&auth_stream, request->inbound_pbuf, 0, request->inbound_pbuf->tot_len));
+
+ IF_PARSE_EXEC(snmpv3_get_user((char *)request->msg_user_name, &auth, key, NULL, NULL));
+ IF_PARSE_EXEC(snmpv3_auth(&auth_stream, request->inbound_pbuf->tot_len, key, auth, hmac));
+
+ if (memcmp(request->msg_authentication_parameters, hmac, SNMP_V3_MAX_AUTH_PARAM_LENGTH)) {
+ snmp_stats.wrongdigests++;
+ request->msg_flags = SNMP_V3_NOAUTHNOPRIV;
+ request->error_status = SNMP_ERR_AUTHORIZATIONERROR;
+ return ERR_OK;
+ }
+
+ /* 7) if securitylevel specifies authentication, verify engineboots, enginetime and lastenginetime */
+ {
+ s32_t boots = snmpv3_get_engine_boots_internal();
+ if ((request->msg_authoritative_engine_boots != boots) || (boots == 2147483647UL)) {
+ snmp_stats.notintimewindows++;
+ request->msg_flags = SNMP_V3_AUTHNOPRIV;
+ request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
+ return ERR_OK;
+ }
+ }
+ {
+ s32_t time = snmpv3_get_engine_time_internal();
+ if (request->msg_authoritative_engine_time > (time + 150)) {
+ snmp_stats.notintimewindows++;
+ request->msg_flags = SNMP_V3_AUTHNOPRIV;
+ request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
+ return ERR_OK;
+ } else if (time > 150) {
+ if (request->msg_authoritative_engine_time < (time - 150)) {
+ snmp_stats.notintimewindows++;
+ request->msg_flags = SNMP_V3_AUTHNOPRIV;
+ request->error_status = SNMP_ERR_NOTINTIMEWINDOW;
+ return ERR_OK;
+ }
+ }
+ }
+ }
+#endif
+
+ /* 8) if securitylevel specifies privacy, decrypt message. */
+#if LWIP_SNMP_V3_CRYPTO
+ if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
+ /* Decrypt message */
+
+ u8_t key[20];
+
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmpv3_get_user((char *)request->msg_user_name, NULL, NULL, &priv, key));
+ if (snmpv3_crypt(&pbuf_stream, tlv.value_len, key,
+ request->msg_privacy_parameters, request->msg_authoritative_engine_boots,
+ request->msg_authoritative_engine_time, priv, SNMP_V3_PRIV_MODE_DECRYPT) != ERR_OK) {
+ snmp_stats.decryptionerrors++;
+ request->msg_flags = SNMP_V3_AUTHNOPRIV;
+ request->error_status = SNMP_ERR_DECRYIPTION_ERROR;
+ return ERR_OK;
+ }
+ }
+#endif
+ /* 9) calculate max size of scoped pdu?
+ * 10) securityname for user is retrieved from usertable?
+ * 11) security data is cached?
+ * 12)
+ */
+
+ /* Scoped PDU
+ * Encryption context
+ */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_SEQUENCE);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_HDR_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ /* contextEngineID */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->context_engine_id,
+ &u16_value, SNMP_V3_MAX_ENGINE_ID_LENGTH));
+ request->context_engine_id_len = (u8_t)u16_value;
+ /* TODO: do we need to verify this contextengineid too? */
+
+ /* contextName */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->context_name,
+ &u16_value, SNMP_V3_MAX_ENGINE_ID_LENGTH));
+ request->context_name_len = (u8_t)u16_value;
+ /* TODO: do we need to verify this contextname too? */
+ } else
+#endif
+ {
+ /* decode community */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ err = snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->community, &request->community_strlen, SNMP_MAX_COMMUNITY_STR_LEN);
+ if (err == ERR_MEM) {
+ /* community string does not fit in our buffer -> its too long -> its invalid */
+ request->community_strlen = 0;
+ snmp_pbuf_stream_seek(&pbuf_stream, tlv.value_len);
+ } else {
+ IF_PARSE_ASSERT(err == ERR_OK);
+ }
+ /* add zero terminator */
+ request->community[request->community_strlen] = 0;
+ }
+
+ /* decode PDU type (next container level) */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.value_len <= pbuf_stream.length);
+ request->inbound_padding_len = pbuf_stream.length - tlv.value_len;
+ parent_tlv_value_len = tlv.value_len;
+
+ /* validate PDU type */
+ switch (tlv.type) {
+ case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_REQ):
+ /* GetRequest PDU */
+ snmp_stats.ingetrequests++;
+ break;
+ case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_NEXT_REQ):
+ /* GetNextRequest PDU */
+ snmp_stats.ingetnexts++;
+ break;
+ case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ):
+ /* GetBulkRequest PDU */
+ if (request->version < SNMP_VERSION_2c) {
+ /* RFC2089: invalid, drop packet */
+ return ERR_ARG;
+ }
+ break;
+ case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_SET_REQ):
+ /* SetRequest PDU */
+ snmp_stats.insetrequests++;
+ break;
+ case (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_RESP):
+ /* GetResponse PDU */
+ snmp_stats.ingetresponses++;
+ break;
+ default:
+ /* unsupported input PDU for this agent (no parse error) */
+ LWIP_DEBUGF(SNMP_DEBUG, ("Unknown/Invalid SNMP PDU type received: %d\n", tlv.type)); \
+ return ERR_ARG;
+ }
+ request->request_type = tlv.type & SNMP_ASN1_DATATYPE_MASK;
+ request->request_out_type = (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_GET_RESP);
+
+ /* validate community (do this after decoding PDU type because we don't want to increase 'inbadcommunitynames' for wrong frame types */
+ if (request->community_strlen == 0) {
+ /* community string was too long or really empty*/
+ snmp_stats.inbadcommunitynames++;
+ snmp_authfail_trap();
+ return ERR_ARG;
+ } else if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
+ if (snmp_community_write[0] == 0) {
+ /* our write community is empty, that means all our objects are readonly */
+ request->error_status = SNMP_ERR_NOTWRITABLE;
+ request->error_index = 1;
+ } else if (strncmp(snmp_community_write, (const char *)request->community, SNMP_MAX_COMMUNITY_STR_LEN) != 0) {
+ /* community name does not match */
+ snmp_stats.inbadcommunitynames++;
+ snmp_authfail_trap();
+ return ERR_ARG;
+ }
+ } else {
+ if (strncmp(snmp_community, (const char *)request->community, SNMP_MAX_COMMUNITY_STR_LEN) != 0) {
+ /* community name does not match */
+ snmp_stats.inbadcommunitynames++;
+ snmp_authfail_trap();
+ return ERR_ARG;
+ }
+ }
+
+ /* decode request ID */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->request_id));
+
+ /* decode error status / non-repeaters */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ if (request->request_type == SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ) {
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->non_repeaters));
+ if (request->non_repeaters < 0) {
+ /* RFC 1905, 4.2.3 */
+ request->non_repeaters = 0;
+ }
+ } else {
+ /* only check valid value, don't touch 'request->error_status', maybe a response error status was already set to above; */
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &s32_value));
+ IF_PARSE_ASSERT(s32_value == SNMP_ERR_NOERROR);
+ }
+
+ /* decode error index / max-repetitions */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_INTEGER);
+ parent_tlv_value_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+ IF_PARSE_ASSERT(parent_tlv_value_len > 0);
+
+ if (request->request_type == SNMP_ASN1_CONTEXT_PDU_GET_BULK_REQ) {
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->max_repetitions));
+ if (request->max_repetitions < 0) {
+ /* RFC 1905, 4.2.3 */
+ request->max_repetitions = 0;
+ }
+ } else {
+ IF_PARSE_EXEC(snmp_asn1_dec_s32t(&pbuf_stream, tlv.value_len, &request->error_index));
+ IF_PARSE_ASSERT(s32_value == 0);
+ }
+
+ /* decode varbind-list type (next container level) */
+ IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
+ IF_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_SEQUENCE) && (tlv.value_len <= pbuf_stream.length));
+
+ request->inbound_varbind_offset = pbuf_stream.offset;
+ request->inbound_varbind_len = pbuf_stream.length - request->inbound_padding_len;
+ snmp_vb_enumerator_init(&(request->inbound_varbind_enumerator), request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len);
+
+ return ERR_OK;
+}
+
+#define OF_BUILD_EXEC(code) BUILD_EXEC(code, ERR_ARG)
+
+static err_t
+snmp_prepare_outbound_frame(struct snmp_request *request)
+{
+ struct snmp_asn1_tlv tlv;
+ struct snmp_pbuf_stream *pbuf_stream = &(request->outbound_pbuf_stream);
+
+ /* try allocating pbuf(s) for maximum response size */
+ request->outbound_pbuf = pbuf_alloc(PBUF_TRANSPORT, 1472, PBUF_RAM);
+ if (request->outbound_pbuf == NULL) {
+ return ERR_MEM;
+ }
+
+ snmp_pbuf_stream_init(pbuf_stream, request->outbound_pbuf, 0, request->outbound_pbuf->tot_len);
+
+ /* 'Message' sequence */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+ /* version */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+ snmp_asn1_enc_s32t_cnt(request->version, &tlv.value_len);
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ OF_BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->version) );
+
+#if LWIP_SNMP_V3
+ if (request->version < SNMP_VERSION_3) {
+#endif
+ /* community */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->community_strlen);
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ OF_BUILD_EXEC( snmp_asn1_enc_raw(pbuf_stream, request->community, request->community_strlen) );
+#if LWIP_SNMP_V3
+ } else {
+ const char *id;
+
+ /* globalData */
+ request->outbound_msg_global_data_offset = pbuf_stream->offset;
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, 0);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+ /* msgID */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+ snmp_asn1_enc_s32t_cnt(request->msg_id, &tlv.value_len);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_id));
+
+ /* msgMaxSize */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+ snmp_asn1_enc_s32t_cnt(request->msg_max_size, &tlv.value_len);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_max_size));
+
+ /* msgFlags */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 1);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, &request->msg_flags, 1));
+
+ /* msgSecurityModel */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+ snmp_asn1_enc_s32t_cnt(request->msg_security_model, &tlv.value_len);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_security_model));
+
+ /* end of msgGlobalData */
+ request->outbound_msg_global_data_end = pbuf_stream->offset;
+
+ /* msgSecurityParameters */
+ request->outbound_msg_security_parameters_str_offset = pbuf_stream->offset;
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, 0);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+ request->outbound_msg_security_parameters_seq_offset = pbuf_stream->offset;
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, 0);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+ /* msgAuthoritativeEngineID */
+ snmpv3_get_engine_id(&id, &request->msg_authoritative_engine_id_len);
+ MEMCPY(request->msg_authoritative_engine_id, id, request->msg_authoritative_engine_id_len);
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->msg_authoritative_engine_id_len);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_authoritative_engine_id, request->msg_authoritative_engine_id_len));
+
+ request->msg_authoritative_engine_time = snmpv3_get_engine_time();
+ request->msg_authoritative_engine_boots = snmpv3_get_engine_boots();
+
+ /* msgAuthoritativeEngineBoots */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+ snmp_asn1_enc_s32t_cnt(request->msg_authoritative_engine_boots, &tlv.value_len);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_authoritative_engine_boots));
+
+ /* msgAuthoritativeEngineTime */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+ snmp_asn1_enc_s32t_cnt(request->msg_authoritative_engine_time, &tlv.value_len);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->msg_authoritative_engine_time));
+
+ /* msgUserName */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->msg_user_name_len);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_user_name, request->msg_user_name_len));
+
+#if LWIP_SNMP_V3_CRYPTO
+ /* msgAuthenticationParameters */
+ if (request->msg_flags & SNMP_V3_AUTH_FLAG) {
+ memset(request->msg_authentication_parameters, 0, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+ request->outbound_msg_authentication_parameters_offset = pbuf_stream->offset;
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_authentication_parameters, SNMP_V3_MAX_AUTH_PARAM_LENGTH));
+ } else
+#endif
+ {
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 0);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ }
+
+#if LWIP_SNMP_V3_CRYPTO
+ /* msgPrivacyParameters */
+ if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
+ snmpv3_build_priv_param(request->msg_privacy_parameters);
+
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, SNMP_V3_MAX_PRIV_PARAM_LENGTH);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->msg_privacy_parameters, SNMP_V3_MAX_PRIV_PARAM_LENGTH));
+ } else
+#endif
+ {
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, 0);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ }
+
+ /* End of msgSecurityParameters, so we can calculate the length of this sequence later */
+ request->outbound_msg_security_parameters_end = pbuf_stream->offset;
+
+#if LWIP_SNMP_V3_CRYPTO
+ /* For encryption we have to encapsulate the payload in an octet string */
+ if (request->msg_flags & SNMP_V3_PRIV_FLAG) {
+ request->outbound_scoped_pdu_string_offset = pbuf_stream->offset;
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 3, 0);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ }
+#endif
+ /* Scoped PDU
+ * Encryption context
+ */
+ request->outbound_scoped_pdu_seq_offset = pbuf_stream->offset;
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+ /* contextEngineID */
+ snmpv3_get_engine_id(&id, &request->context_engine_id_len);
+ MEMCPY(request->context_engine_id, id, request->context_engine_id_len);
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->context_engine_id_len);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->context_engine_id, request->context_engine_id_len));
+
+ /* contextName */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, request->context_name_len);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, request->context_name, request->context_name_len));
+ }
+#endif
+
+ /* 'PDU' sequence */
+ request->outbound_pdu_offset = pbuf_stream->offset;
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, request->request_out_type, 3, 0);
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+ /* request ID */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+ snmp_asn1_enc_s32t_cnt(request->request_id, &tlv.value_len);
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ OF_BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, request->request_id) );
+
+ /* error status */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ request->outbound_error_status_offset = pbuf_stream->offset;
+ OF_BUILD_EXEC( snmp_pbuf_stream_write(pbuf_stream, 0) );
+
+ /* error index */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 1);
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ request->outbound_error_index_offset = pbuf_stream->offset;
+ OF_BUILD_EXEC( snmp_pbuf_stream_write(pbuf_stream, 0) );
+
+ /* 'VarBindList' sequence */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, 0);
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+ request->outbound_varbind_offset = pbuf_stream->offset;
+
+ return ERR_OK;
+}
+
+/** Calculate the length of a varbind list */
+err_t
+snmp_varbind_length(struct snmp_varbind *varbind, struct snmp_varbind_len *len)
+{
+ /* calculate required lengths */
+ snmp_asn1_enc_oid_cnt(varbind->oid.id, varbind->oid.len, &len->oid_value_len);
+ snmp_asn1_enc_length_cnt(len->oid_value_len, &len->oid_len_len);
+
+ if (varbind->value_len == 0) {
+ len->value_value_len = 0;
+ } else if (varbind->value_len & SNMP_GET_VALUE_RAW_DATA) {
+ len->value_value_len = varbind->value_len & (~SNMP_GET_VALUE_RAW_DATA);
+ } else {
+ switch (varbind->type) {
+ case SNMP_ASN1_TYPE_INTEGER:
+ if (varbind->value_len != sizeof (s32_t)) {
+ return ERR_VAL;
+ }
+ snmp_asn1_enc_s32t_cnt(*((s32_t *) varbind->value), &len->value_value_len);
+ break;
+ case SNMP_ASN1_TYPE_COUNTER:
+ case SNMP_ASN1_TYPE_GAUGE:
+ case SNMP_ASN1_TYPE_TIMETICKS:
+ if (varbind->value_len != sizeof (u32_t)) {
+ return ERR_VAL;
+ }
+ snmp_asn1_enc_u32t_cnt(*((u32_t *) varbind->value), &len->value_value_len);
+ break;
+ case SNMP_ASN1_TYPE_OCTET_STRING:
+ case SNMP_ASN1_TYPE_IPADDR:
+ case SNMP_ASN1_TYPE_OPAQUE:
+ len->value_value_len = varbind->value_len;
+ break;
+ case SNMP_ASN1_TYPE_NULL:
+ if (varbind->value_len != 0) {
+ return ERR_VAL;
+ }
+ len->value_value_len = 0;
+ break;
+ case SNMP_ASN1_TYPE_OBJECT_ID:
+ if ((varbind->value_len & 0x03) != 0) {
+ return ERR_VAL;
+ }
+ snmp_asn1_enc_oid_cnt((u32_t *) varbind->value, varbind->value_len >> 2, &len->value_value_len);
+ break;
+#if LWIP_HAVE_INT64
+ case SNMP_ASN1_TYPE_COUNTER64:
+ if (varbind->value_len != sizeof(u64_t)) {
+ return ERR_VAL;
+ }
+ snmp_asn1_enc_u64t_cnt(*(u64_t *)varbind->value, &len->value_value_len);
+ break;
+#endif
+ default:
+ /* unsupported type */
+ return ERR_VAL;
+ }
+ }
+ snmp_asn1_enc_length_cnt(len->value_value_len, &len->value_len_len);
+
+ len->vb_value_len = 1 + len->oid_len_len + len->oid_value_len + 1 + len->value_len_len + len->value_value_len;
+ snmp_asn1_enc_length_cnt(len->vb_value_len, &len->vb_len_len);
+
+ return ERR_OK;
+}
+
+#define OVB_BUILD_EXEC(code) BUILD_EXEC(code, ERR_ARG)
+
+err_t
+snmp_append_outbound_varbind(struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind *varbind)
+{
+ struct snmp_asn1_tlv tlv;
+ struct snmp_varbind_len len;
+ err_t err;
+
+ err = snmp_varbind_length(varbind, &len);
+
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ /* check length already before adding first data because in case of GetBulk,
+ * data added so far is returned and therefore no partial data shall be added
+ */
+ if ((1 + len.vb_len_len + len.vb_value_len) > pbuf_stream->length) {
+ return ERR_BUF;
+ }
+
+ /* 'VarBind' sequence */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, len.vb_len_len, len.vb_value_len);
+ OVB_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+ /* VarBind OID */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OBJECT_ID, len.oid_len_len, len.oid_value_len);
+ OVB_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+ OVB_BUILD_EXEC(snmp_asn1_enc_oid(pbuf_stream, varbind->oid.id, varbind->oid.len));
+
+ /* VarBind value */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, varbind->type, len.value_len_len, len.value_value_len);
+ OVB_BUILD_EXEC(snmp_ans1_enc_tlv(pbuf_stream, &tlv));
+
+ if (len.value_value_len > 0) {
+ if (varbind->value_len & SNMP_GET_VALUE_RAW_DATA) {
+ OVB_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, (u8_t *) varbind->value, len.value_value_len));
+ } else {
+ switch (varbind->type) {
+ case SNMP_ASN1_TYPE_INTEGER:
+ OVB_BUILD_EXEC(snmp_asn1_enc_s32t(pbuf_stream, len.value_value_len, *((s32_t *) varbind->value)));
+ break;
+ case SNMP_ASN1_TYPE_COUNTER:
+ case SNMP_ASN1_TYPE_GAUGE:
+ case SNMP_ASN1_TYPE_TIMETICKS:
+ OVB_BUILD_EXEC(snmp_asn1_enc_u32t(pbuf_stream, len.value_value_len, *((u32_t *) varbind->value)));
+ break;
+ case SNMP_ASN1_TYPE_OCTET_STRING:
+ case SNMP_ASN1_TYPE_IPADDR:
+ case SNMP_ASN1_TYPE_OPAQUE:
+ OVB_BUILD_EXEC(snmp_asn1_enc_raw(pbuf_stream, (u8_t *) varbind->value, len.value_value_len));
+ len.value_value_len = varbind->value_len;
+ break;
+ case SNMP_ASN1_TYPE_OBJECT_ID:
+ OVB_BUILD_EXEC(snmp_asn1_enc_oid(pbuf_stream, (u32_t *) varbind->value, varbind->value_len / sizeof (u32_t)));
+ break;
+#if LWIP_HAVE_INT64
+ case SNMP_ASN1_TYPE_COUNTER64:
+ OVB_BUILD_EXEC(snmp_asn1_enc_u64t(pbuf_stream, len.value_value_len, *(u64_t *) varbind->value));
+ break;
+#endif
+ default:
+ LWIP_ASSERT("Unknown variable type", 0);
+ break;
+ }
+ }
+ }
+
+ return ERR_OK;
+}
+
+static err_t
+snmp_complete_outbound_frame(struct snmp_request *request)
+{
+ struct snmp_asn1_tlv tlv;
+ u16_t frame_size;
+ u8_t outbound_padding = 0;
+
+ if (request->version == SNMP_VERSION_1) {
+ if (request->error_status != SNMP_ERR_NOERROR) {
+ /* map v2c error codes to v1 compliant error code (according to RFC 2089) */
+ switch (request->error_status) {
+ /* mapping of implementation specific "virtual" error codes
+ * (during processing of frame we already stored them in error_status field,
+ * so no need to check all varbinds here for those exceptions as suggested by RFC) */
+ case SNMP_ERR_NOSUCHINSTANCE:
+ case SNMP_ERR_NOSUCHOBJECT:
+ case SNMP_ERR_ENDOFMIBVIEW:
+ request->error_status = SNMP_ERR_NOSUCHNAME;
+ break;
+ /* mapping according to RFC */
+ case SNMP_ERR_WRONGVALUE:
+ case SNMP_ERR_WRONGENCODING:
+ case SNMP_ERR_WRONGTYPE:
+ case SNMP_ERR_WRONGLENGTH:
+ case SNMP_ERR_INCONSISTENTVALUE:
+ request->error_status = SNMP_ERR_BADVALUE;
+ break;
+ case SNMP_ERR_NOACCESS:
+ case SNMP_ERR_NOTWRITABLE:
+ case SNMP_ERR_NOCREATION:
+ case SNMP_ERR_INCONSISTENTNAME:
+ case SNMP_ERR_AUTHORIZATIONERROR:
+ request->error_status = SNMP_ERR_NOSUCHNAME;
+ break;
+ case SNMP_ERR_RESOURCEUNAVAILABLE:
+ case SNMP_ERR_COMMITFAILED:
+ case SNMP_ERR_UNDOFAILED:
+ default:
+ request->error_status = SNMP_ERR_GENERROR;
+ break;
+ }
+ }
+ } else {
+ if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
+ /* map error codes to according to RFC 1905 (4.2.5. The SetRequest-PDU) return 'NotWritable' for unknown OIDs) */
+ switch (request->error_status) {
+ case SNMP_ERR_NOSUCHINSTANCE:
+ case SNMP_ERR_NOSUCHOBJECT:
+ case SNMP_ERR_ENDOFMIBVIEW:
+ request->error_status = SNMP_ERR_NOTWRITABLE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (request->error_status >= SNMP_VARBIND_EXCEPTION_OFFSET) {
+ /* should never occur because v2 frames store exceptions directly inside varbinds and not as frame error_status */
+ LWIP_DEBUGF(SNMP_DEBUG, ("snmp_complete_outbound_frame() > Found v2 request with varbind exception code stored as error status!\n"));
+ return ERR_ARG;
+ }
+ }
+
+ if ((request->error_status != SNMP_ERR_NOERROR) || (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ)) {
+ /* all inbound vars are returned in response without any modification for error responses and successful set requests*/
+ struct snmp_pbuf_stream inbound_stream;
+ OF_BUILD_EXEC( snmp_pbuf_stream_init(&inbound_stream, request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len) );
+ OF_BUILD_EXEC( snmp_pbuf_stream_init(&(request->outbound_pbuf_stream), request->outbound_pbuf, request->outbound_varbind_offset, request->outbound_pbuf->tot_len - request->outbound_varbind_offset) );
+ OF_BUILD_EXEC( snmp_pbuf_stream_writeto(&inbound_stream, &(request->outbound_pbuf_stream), 0) );
+ }
+
+ frame_size = request->outbound_pbuf_stream.offset;
+
+#if LWIP_SNMP_V3 && LWIP_SNMP_V3_CRYPTO
+ /* Calculate padding for encryption */
+ if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_PRIV_FLAG)) {
+ u8_t i;
+ outbound_padding = (8 - (u8_t)((frame_size - request->outbound_scoped_pdu_seq_offset) & 0x07)) & 0x07;
+ for (i = 0; i < outbound_padding; i++) {
+ OF_BUILD_EXEC( snmp_pbuf_stream_write(&request->outbound_pbuf_stream, 0) );
+ }
+ }
+#endif
+
+ /* complete missing length in 'Message' sequence ; 'Message' tlv is located at the beginning (offset 0) */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size + outbound_padding - 1 - 3); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+ OF_BUILD_EXEC( snmp_pbuf_stream_init(&(request->outbound_pbuf_stream), request->outbound_pbuf, 0, request->outbound_pbuf->tot_len) );
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv) );
+
+#if LWIP_SNMP_V3
+ if (request->version == SNMP_VERSION_3) {
+ /* complete missing length in 'globalData' sequence */
+ /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, request->outbound_msg_global_data_end
+ - request->outbound_msg_global_data_offset - 1 - 1);
+ OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_msg_global_data_offset));
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
+
+ /* complete missing length in 'msgSecurityParameters' sequence */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, request->outbound_msg_security_parameters_end
+ - request->outbound_msg_security_parameters_str_offset - 1 - 1);
+ OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_msg_security_parameters_str_offset));
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
+
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 1, request->outbound_msg_security_parameters_end
+ - request->outbound_msg_security_parameters_seq_offset - 1 - 1);
+ OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_msg_security_parameters_seq_offset));
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
+
+ /* complete missing length in scoped PDU sequence */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size - request->outbound_scoped_pdu_seq_offset - 1 - 3);
+ OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_scoped_pdu_seq_offset));
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
+ }
+#endif
+
+ /* complete missing length in 'PDU' sequence */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, request->request_out_type, 3,
+ frame_size - request->outbound_pdu_offset - 1 - 3); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+ OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_pdu_offset) );
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv) );
+
+ /* process and encode final error status */
+ if (request->error_status != 0) {
+ u16_t len;
+ snmp_asn1_enc_s32t_cnt(request->error_status, &len);
+ if (len != 1) {
+ /* error, we only reserved one byte for it */
+ return ERR_ARG;
+ }
+ OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_error_status_offset) );
+ OF_BUILD_EXEC( snmp_asn1_enc_s32t(&(request->outbound_pbuf_stream), len, request->error_status) );
+
+ /* for compatibility to v1, log statistics; in v2 (RFC 1907) these statistics are obsoleted */
+ switch (request->error_status) {
+ case SNMP_ERR_TOOBIG:
+ snmp_stats.outtoobigs++;
+ break;
+ case SNMP_ERR_NOSUCHNAME:
+ snmp_stats.outnosuchnames++;
+ break;
+ case SNMP_ERR_BADVALUE:
+ snmp_stats.outbadvalues++;
+ break;
+ case SNMP_ERR_GENERROR:
+ default:
+ snmp_stats.outgenerrs++;
+ break;
+ }
+
+ if (request->error_status == SNMP_ERR_TOOBIG) {
+ request->error_index = 0; /* defined by RFC 1157 */
+ } else if (request->error_index == 0) {
+ /* set index to varbind where error occurred (if not already set before, e.g. during GetBulk processing) */
+ request->error_index = request->inbound_varbind_enumerator.varbind_count;
+ }
+ } else {
+ if (request->request_type == SNMP_ASN1_CONTEXT_PDU_SET_REQ) {
+ snmp_stats.intotalsetvars += request->inbound_varbind_enumerator.varbind_count;
+ } else {
+ snmp_stats.intotalreqvars += request->inbound_varbind_enumerator.varbind_count;
+ }
+ }
+
+ /* encode final error index*/
+ if (request->error_index != 0) {
+ u16_t len;
+ snmp_asn1_enc_s32t_cnt(request->error_index, &len);
+ if (len != 1) {
+ /* error, we only reserved one byte for it */
+ return ERR_VAL;
+ }
+ OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_error_index_offset) );
+ OF_BUILD_EXEC( snmp_asn1_enc_s32t(&(request->outbound_pbuf_stream), len, request->error_index) );
+ }
+
+ /* complete missing length in 'VarBindList' sequence ; 'VarBindList' tlv is located directly before varbind offset */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 3, frame_size - request->outbound_varbind_offset);
+ OF_BUILD_EXEC( snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_varbind_offset - 1 - 3) ); /* - type - length_len(fixed, see snmp_prepare_outbound_frame()) */
+ OF_BUILD_EXEC( snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv) );
+
+ /* Authenticate response */
+#if LWIP_SNMP_V3 && LWIP_SNMP_V3_CRYPTO
+ /* Encrypt response */
+ if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_PRIV_FLAG)) {
+ u8_t key[20];
+ snmpv3_priv_algo_t algo;
+
+ /* complete missing length in PDU sequence */
+ OF_BUILD_EXEC(snmp_pbuf_stream_init(&request->outbound_pbuf_stream, request->outbound_pbuf, 0, request->outbound_pbuf->tot_len));
+ OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&(request->outbound_pbuf_stream), request->outbound_scoped_pdu_string_offset));
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 3, frame_size + outbound_padding
+ - request->outbound_scoped_pdu_string_offset - 1 - 3);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(&(request->outbound_pbuf_stream), &tlv));
+
+ OF_BUILD_EXEC(snmpv3_get_user((char *)request->msg_user_name, NULL, NULL, &algo, key));
+
+ OF_BUILD_EXEC(snmpv3_crypt(&request->outbound_pbuf_stream, tlv.value_len, key,
+ request->msg_privacy_parameters, request->msg_authoritative_engine_boots,
+ request->msg_authoritative_engine_time, algo, SNMP_V3_PRIV_MODE_ENCRYPT));
+ }
+
+ if (request->version == SNMP_VERSION_3 && (request->msg_flags & SNMP_V3_AUTH_FLAG)) {
+ u8_t key[20];
+ snmpv3_auth_algo_t algo;
+ u8_t hmac[20];
+
+ OF_BUILD_EXEC(snmpv3_get_user((char *)request->msg_user_name, &algo, key, NULL, NULL));
+ OF_BUILD_EXEC(snmp_pbuf_stream_init(&(request->outbound_pbuf_stream),
+ request->outbound_pbuf, 0, request->outbound_pbuf->tot_len));
+ OF_BUILD_EXEC(snmpv3_auth(&request->outbound_pbuf_stream, frame_size + outbound_padding, key, algo, hmac));
+
+ MEMCPY(request->msg_authentication_parameters, hmac, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+ OF_BUILD_EXEC(snmp_pbuf_stream_init(&request->outbound_pbuf_stream,
+ request->outbound_pbuf, 0, request->outbound_pbuf->tot_len));
+ OF_BUILD_EXEC(snmp_pbuf_stream_seek_abs(&request->outbound_pbuf_stream,
+ request->outbound_msg_authentication_parameters_offset));
+
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 1, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
+ OF_BUILD_EXEC(snmp_ans1_enc_tlv(&request->outbound_pbuf_stream, &tlv));
+ OF_BUILD_EXEC(snmp_asn1_enc_raw(&request->outbound_pbuf_stream,
+ request->msg_authentication_parameters, SNMP_V3_MAX_AUTH_PARAM_LENGTH));
+ }
+#endif
+
+ pbuf_realloc(request->outbound_pbuf, frame_size + outbound_padding);
+
+ snmp_stats.outgetresponses++;
+ snmp_stats.outpkts++;
+
+ return ERR_OK;
+}
+
+static void
+snmp_execute_write_callbacks(struct snmp_request *request)
+{
+ struct snmp_varbind_enumerator inbound_varbind_enumerator;
+ struct snmp_varbind vb;
+
+ snmp_vb_enumerator_init(&inbound_varbind_enumerator, request->inbound_pbuf, request->inbound_varbind_offset, request->inbound_varbind_len);
+ vb.value = NULL; /* do NOT decode value (we enumerate outbound buffer here, so all varbinds have values assigned, which we don't need here) */
+
+ while (snmp_vb_enumerator_get_next(&inbound_varbind_enumerator, &vb) == SNMP_VB_ENUMERATOR_ERR_OK) {
+ snmp_write_callback(vb.oid.id, vb.oid.len, snmp_write_callback_arg);
+ }
+}
+
+
+/* ----------------------------------------------------------------------- */
+/* VarBind enumerator methods */
+/* ----------------------------------------------------------------------- */
+
+void
+snmp_vb_enumerator_init(struct snmp_varbind_enumerator *enumerator, struct pbuf *p, u16_t offset, u16_t length)
+{
+ snmp_pbuf_stream_init(&(enumerator->pbuf_stream), p, offset, length);
+ enumerator->varbind_count = 0;
+}
+
+#define VB_PARSE_EXEC(code) PARSE_EXEC(code, SNMP_VB_ENUMERATOR_ERR_ASN1ERROR)
+#define VB_PARSE_ASSERT(code) PARSE_ASSERT(code, SNMP_VB_ENUMERATOR_ERR_ASN1ERROR)
+
+snmp_vb_enumerator_err_t
+snmp_vb_enumerator_get_next(struct snmp_varbind_enumerator *enumerator, struct snmp_varbind *varbind)
+{
+ struct snmp_asn1_tlv tlv;
+ u16_t varbind_len;
+ err_t err;
+
+ if (enumerator->pbuf_stream.length == 0) {
+ return SNMP_VB_ENUMERATOR_ERR_EOVB;
+ }
+ enumerator->varbind_count++;
+
+ /* decode varbind itself (parent container of a varbind) */
+ VB_PARSE_EXEC(snmp_asn1_dec_tlv(&(enumerator->pbuf_stream), &tlv));
+ VB_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_SEQUENCE) && (tlv.value_len <= enumerator->pbuf_stream.length));
+ varbind_len = tlv.value_len;
+
+ /* decode varbind name (object id) */
+ VB_PARSE_EXEC(snmp_asn1_dec_tlv(&(enumerator->pbuf_stream), &tlv));
+ VB_PARSE_ASSERT((tlv.type == SNMP_ASN1_TYPE_OBJECT_ID) && (SNMP_ASN1_TLV_LENGTH(tlv) < varbind_len) && (tlv.value_len < enumerator->pbuf_stream.length));
+
+ VB_PARSE_EXEC(snmp_asn1_dec_oid(&(enumerator->pbuf_stream), tlv.value_len, varbind->oid.id, &(varbind->oid.len), SNMP_MAX_OBJ_ID_LEN));
+ varbind_len -= SNMP_ASN1_TLV_LENGTH(tlv);
+
+ /* decode varbind value (object id) */
+ VB_PARSE_EXEC(snmp_asn1_dec_tlv(&(enumerator->pbuf_stream), &tlv));
+ VB_PARSE_ASSERT((SNMP_ASN1_TLV_LENGTH(tlv) == varbind_len) && (tlv.value_len <= enumerator->pbuf_stream.length));
+ varbind->type = tlv.type;
+
+ /* shall the value be decoded ? */
+ if (varbind->value != NULL) {
+ switch (varbind->type) {
+ case SNMP_ASN1_TYPE_INTEGER:
+ VB_PARSE_EXEC(snmp_asn1_dec_s32t(&(enumerator->pbuf_stream), tlv.value_len, (s32_t *)varbind->value));
+ varbind->value_len = sizeof(s32_t);
+ break;
+ case SNMP_ASN1_TYPE_COUNTER:
+ case SNMP_ASN1_TYPE_GAUGE:
+ case SNMP_ASN1_TYPE_TIMETICKS:
+ VB_PARSE_EXEC(snmp_asn1_dec_u32t(&(enumerator->pbuf_stream), tlv.value_len, (u32_t *)varbind->value));
+ varbind->value_len = sizeof(u32_t);
+ break;
+ case SNMP_ASN1_TYPE_OCTET_STRING:
+ case SNMP_ASN1_TYPE_OPAQUE:
+ err = snmp_asn1_dec_raw(&(enumerator->pbuf_stream), tlv.value_len, (u8_t *)varbind->value, &varbind->value_len, SNMP_MAX_VALUE_SIZE);
+ if (err == ERR_MEM) {
+ return SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH;
+ }
+ VB_PARSE_ASSERT(err == ERR_OK);
+ break;
+ case SNMP_ASN1_TYPE_NULL:
+ varbind->value_len = 0;
+ break;
+ case SNMP_ASN1_TYPE_OBJECT_ID:
+ /* misuse tlv.length_len as OID_length transporter */
+ err = snmp_asn1_dec_oid(&(enumerator->pbuf_stream), tlv.value_len, (u32_t *)varbind->value, &tlv.length_len, SNMP_MAX_OBJ_ID_LEN);
+ if (err == ERR_MEM) {
+ return SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH;
+ }
+ VB_PARSE_ASSERT(err == ERR_OK);
+ varbind->value_len = tlv.length_len * sizeof(u32_t);
+ break;
+ case SNMP_ASN1_TYPE_IPADDR:
+ if (tlv.value_len == 4) {
+ /* must be exactly 4 octets! */
+ VB_PARSE_EXEC(snmp_asn1_dec_raw(&(enumerator->pbuf_stream), tlv.value_len, (u8_t *)varbind->value, &varbind->value_len, SNMP_MAX_VALUE_SIZE));
+ } else {
+ VB_PARSE_ASSERT(0);
+ }
+ break;
+#if LWIP_HAVE_INT64
+ case SNMP_ASN1_TYPE_COUNTER64:
+ VB_PARSE_EXEC(snmp_asn1_dec_u64t(&(enumerator->pbuf_stream), tlv.value_len, (u64_t *)varbind->value));
+ varbind->value_len = sizeof(u64_t);
+ break;
+#endif
+ default:
+ VB_PARSE_ASSERT(0);
+ break;
+ }
+ } else {
+ snmp_pbuf_stream_seek(&(enumerator->pbuf_stream), tlv.value_len);
+ varbind->value_len = tlv.value_len;
+ }
+
+ return SNMP_VB_ENUMERATOR_ERR_OK;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_msg.h b/src/apps/snmp/snmp_msg.h
new file mode 100644
index 00000000000..903f08a2dc8
--- /dev/null
+++ b/src/apps/snmp/snmp_msg.h
@@ -0,0 +1,185 @@
+/**
+ * @file
+ * SNMP Agent message handling structures (internal API, do not use in client code).
+ */
+
+/*
+ * Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
+ * Copyright (c) 2016 Elias Oenal.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Christiaan Simons <[email protected]>
+ * Martin Hentschel <[email protected]>
+ * Elias Oenal <[email protected]>
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_MSG_H
+#define LWIP_HDR_APPS_SNMP_MSG_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP
+
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "snmp_pbuf_stream.h"
+#include "lwip/ip_addr.h"
+#include "lwip/err.h"
+
+#if LWIP_SNMP_V3
+#include "snmpv3_priv.h"
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* version defines used in PDU */
+#define SNMP_VERSION_1 0
+#define SNMP_VERSION_2c 1
+#define SNMP_VERSION_3 3
+
+struct snmp_varbind_enumerator {
+ struct snmp_pbuf_stream pbuf_stream;
+ u16_t varbind_count;
+};
+
+typedef enum {
+ SNMP_VB_ENUMERATOR_ERR_OK = 0,
+ SNMP_VB_ENUMERATOR_ERR_EOVB = 1,
+ SNMP_VB_ENUMERATOR_ERR_ASN1ERROR = 2,
+ SNMP_VB_ENUMERATOR_ERR_INVALIDLENGTH = 3
+} snmp_vb_enumerator_err_t;
+
+void snmp_vb_enumerator_init(struct snmp_varbind_enumerator *enumerator, struct pbuf *p, u16_t offset, u16_t length);
+snmp_vb_enumerator_err_t snmp_vb_enumerator_get_next(struct snmp_varbind_enumerator *enumerator, struct snmp_varbind *varbind);
+
+struct snmp_request {
+ /* Communication handle */
+ void *handle;
+ /* source IP address */
+ const ip_addr_t *source_ip;
+ /* source UDP port */
+ u16_t source_port;
+ /* incoming snmp version */
+ u8_t version;
+ /* community name (zero terminated) */
+ u8_t community[SNMP_MAX_COMMUNITY_STR_LEN + 1];
+ /* community string length (exclusive zero term) */
+ u16_t community_strlen;
+ /* request type */
+ u8_t request_type;
+ /* request ID */
+ s32_t request_id;
+ /* error status */
+ s32_t error_status;
+ /* error index */
+ s32_t error_index;
+ /* non-repeaters (getBulkRequest (SNMPv2c)) */
+ s32_t non_repeaters;
+ /* max-repetitions (getBulkRequest (SNMPv2c)) */
+ s32_t max_repetitions;
+
+ /* Usually response-pdu (2). When snmpv3 errors are detected report-pdu(8) */
+ u8_t request_out_type;
+
+#if LWIP_SNMP_V3
+ s32_t msg_id;
+ s32_t msg_max_size;
+ u8_t msg_flags;
+ s32_t msg_security_model;
+ u8_t msg_authoritative_engine_id[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+ u8_t msg_authoritative_engine_id_len;
+ s32_t msg_authoritative_engine_boots;
+ s32_t msg_authoritative_engine_time;
+ u8_t msg_user_name[SNMP_V3_MAX_USER_LENGTH];
+ u8_t msg_user_name_len;
+ u8_t msg_authentication_parameters[SNMP_V3_MAX_AUTH_PARAM_LENGTH];
+ u8_t msg_authentication_parameters_len;
+ u8_t msg_privacy_parameters[SNMP_V3_MAX_PRIV_PARAM_LENGTH];
+ u8_t msg_privacy_parameters_len;
+ u8_t context_engine_id[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+ u8_t context_engine_id_len;
+ u8_t context_name[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+ u8_t context_name_len;
+#endif
+
+ struct pbuf *inbound_pbuf;
+ struct snmp_varbind_enumerator inbound_varbind_enumerator;
+ u16_t inbound_varbind_offset;
+ u16_t inbound_varbind_len;
+ u16_t inbound_padding_len;
+
+ struct pbuf *outbound_pbuf;
+ struct snmp_pbuf_stream outbound_pbuf_stream;
+ u16_t outbound_pdu_offset;
+ u16_t outbound_error_status_offset;
+ u16_t outbound_error_index_offset;
+ u16_t outbound_varbind_offset;
+#if LWIP_SNMP_V3
+ u16_t outbound_msg_global_data_offset;
+ u16_t outbound_msg_global_data_end;
+ u16_t outbound_msg_security_parameters_str_offset;
+ u16_t outbound_msg_security_parameters_seq_offset;
+ u16_t outbound_msg_security_parameters_end;
+ u16_t outbound_msg_authentication_parameters_offset;
+ u16_t outbound_scoped_pdu_seq_offset;
+ u16_t outbound_scoped_pdu_string_offset;
+#endif
+
+ u8_t value_buffer[SNMP_MAX_VALUE_SIZE];
+};
+
+/** A helper struct keeping length information about varbinds */
+struct snmp_varbind_len {
+ u8_t vb_len_len;
+ u16_t vb_value_len;
+ u8_t oid_len_len;
+ u16_t oid_value_len;
+ u8_t value_len_len;
+ u16_t value_value_len;
+};
+
+/** Agent community string */
+extern const char *snmp_community;
+/** Agent community string for write access */
+extern const char *snmp_community_write;
+/** handle for sending traps */
+extern void *snmp_traps_handle;
+
+void snmp_receive(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t port);
+err_t snmp_sendto(void *handle, struct pbuf *p, const ip_addr_t *dst, u16_t port);
+u8_t snmp_get_local_ip_for_dst(void *handle, const ip_addr_t *dst, ip_addr_t *result);
+err_t snmp_varbind_length(struct snmp_varbind *varbind, struct snmp_varbind_len *len);
+err_t snmp_append_outbound_varbind(struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind *varbind);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* LWIP_HDR_APPS_SNMP_MSG_H */
diff --git a/src/apps/snmp/snmp_netconn.c b/src/apps/snmp/snmp_netconn.c
new file mode 100644
index 00000000000..70f8bfc057c
--- /dev/null
+++ b/src/apps/snmp/snmp_netconn.c
@@ -0,0 +1,122 @@
+/**
+ * @file
+ * SNMP netconn frontend.
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP && SNMP_USE_NETCONN
+
+#include <string.h>
+#include "lwip/api.h"
+#include "lwip/ip.h"
+#include "lwip/udp.h"
+#include "snmp_msg.h"
+#include "lwip/sys.h"
+#include "lwip/prot/iana.h"
+
+/** SNMP netconn API worker thread */
+static void
+snmp_netconn_thread(void *arg)
+{
+ struct netconn *conn;
+ struct netbuf *buf;
+ err_t err;
+ LWIP_UNUSED_ARG(arg);
+
+ /* Bind to SNMP port with default IP address */
+#if LWIP_IPV6
+ conn = netconn_new(NETCONN_UDP_IPV6);
+ netconn_bind(conn, IP6_ADDR_ANY, LWIP_IANA_PORT_SNMP);
+#else /* LWIP_IPV6 */
+ conn = netconn_new(NETCONN_UDP);
+ netconn_bind(conn, IP4_ADDR_ANY, LWIP_IANA_PORT_SNMP);
+#endif /* LWIP_IPV6 */
+ LWIP_ERROR("snmp_netconn: invalid conn", (conn != NULL), return;);
+
+ snmp_traps_handle = conn;
+
+ do {
+ err = netconn_recv(conn, &buf);
+
+ if (err == ERR_OK) {
+ snmp_receive(conn, buf->p, &buf->addr, buf->port);
+ }
+
+ if (buf != NULL) {
+ netbuf_delete(buf);
+ }
+ } while (1);
+}
+
+err_t
+snmp_sendto(void *handle, struct pbuf *p, const ip_addr_t *dst, u16_t port)
+{
+ err_t result;
+ struct netbuf buf;
+
+ memset(&buf, 0, sizeof(buf));
+ buf.p = p;
+ result = netconn_sendto((struct netconn *)handle, &buf, dst, port);
+
+ return result;
+}
+
+u8_t
+snmp_get_local_ip_for_dst(void *handle, const ip_addr_t *dst, ip_addr_t *result)
+{
+ struct netconn *conn = (struct netconn *)handle;
+ struct netif *dst_if;
+ const ip_addr_t *dst_ip;
+
+ LWIP_UNUSED_ARG(conn); /* unused in case of IPV4 only configuration */
+
+ ip_route_get_local_ip(&conn->pcb.udp->local_ip, dst, dst_if, dst_ip);
+
+ if ((dst_if != NULL) && (dst_ip != NULL)) {
+ ip_addr_copy(*result, *dst_ip);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * Starts SNMP Agent.
+ */
+void
+snmp_init(void)
+{
+ sys_thread_new("snmp_netconn", snmp_netconn_thread, NULL, SNMP_STACK_SIZE, SNMP_THREAD_PRIO);
+}
+
+#endif /* LWIP_SNMP && SNMP_USE_NETCONN */
diff --git a/src/apps/snmp/snmp_pbuf_stream.c b/src/apps/snmp/snmp_pbuf_stream.c
new file mode 100644
index 00000000000..a6e319c2f0f
--- /dev/null
+++ b/src/apps/snmp/snmp_pbuf_stream.c
@@ -0,0 +1,156 @@
+/**
+ * @file
+ * SNMP pbuf stream wrapper implementation (internal API, do not use in client code).
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Martin Hentschel <[email protected]>
+ *
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "snmp_pbuf_stream.h"
+#include "lwip/def.h"
+#include <string.h>
+
+err_t
+snmp_pbuf_stream_init(struct snmp_pbuf_stream *pbuf_stream, struct pbuf *p, u16_t offset, u16_t length)
+{
+ pbuf_stream->offset = offset;
+ pbuf_stream->length = length;
+ pbuf_stream->pbuf = p;
+
+ return ERR_OK;
+}
+
+err_t
+snmp_pbuf_stream_read(struct snmp_pbuf_stream *pbuf_stream, u8_t *data)
+{
+ if (pbuf_stream->length == 0) {
+ return ERR_BUF;
+ }
+
+ if (pbuf_copy_partial(pbuf_stream->pbuf, data, 1, pbuf_stream->offset) == 0) {
+ return ERR_BUF;
+ }
+
+ pbuf_stream->offset++;
+ pbuf_stream->length--;
+
+ return ERR_OK;
+}
+
+err_t
+snmp_pbuf_stream_write(struct snmp_pbuf_stream *pbuf_stream, u8_t data)
+{
+ return snmp_pbuf_stream_writebuf(pbuf_stream, &data, 1);
+}
+
+err_t
+snmp_pbuf_stream_writebuf(struct snmp_pbuf_stream *pbuf_stream, const void *buf, u16_t buf_len)
+{
+ if (pbuf_stream->length < buf_len) {
+ return ERR_BUF;
+ }
+
+ if (pbuf_take_at(pbuf_stream->pbuf, buf, buf_len, pbuf_stream->offset) != ERR_OK) {
+ return ERR_BUF;
+ }
+
+ pbuf_stream->offset += buf_len;
+ pbuf_stream->length -= buf_len;
+
+ return ERR_OK;
+}
+
+err_t
+snmp_pbuf_stream_writeto(struct snmp_pbuf_stream *pbuf_stream, struct snmp_pbuf_stream *target_pbuf_stream, u16_t len)
+{
+
+ if ((pbuf_stream == NULL) || (target_pbuf_stream == NULL)) {
+ return ERR_ARG;
+ }
+ if ((len > pbuf_stream->length) || (len > target_pbuf_stream->length)) {
+ return ERR_ARG;
+ }
+
+ if (len == 0) {
+ len = LWIP_MIN(pbuf_stream->length, target_pbuf_stream->length);
+ }
+
+ while (len > 0) {
+ u16_t chunk_len;
+ err_t err;
+ u16_t target_offset;
+ struct pbuf *pbuf = pbuf_skip(pbuf_stream->pbuf, pbuf_stream->offset, &target_offset);
+
+ if ((pbuf == NULL) || (pbuf->len == 0)) {
+ return ERR_BUF;
+ }
+
+ chunk_len = LWIP_MIN(len, pbuf->len);
+ err = snmp_pbuf_stream_writebuf(target_pbuf_stream, &((u8_t *)pbuf->payload)[target_offset], chunk_len);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ pbuf_stream->offset += chunk_len;
+ pbuf_stream->length -= chunk_len;
+ len -= chunk_len;
+ }
+
+ return ERR_OK;
+}
+
+err_t
+snmp_pbuf_stream_seek(struct snmp_pbuf_stream *pbuf_stream, s32_t offset)
+{
+ if (((pbuf_stream->offset + offset) < 0) || (offset > pbuf_stream->length)) {
+ /* we cannot seek backwards or forward behind stream end */
+ return ERR_ARG;
+ }
+
+ pbuf_stream->offset += (u16_t)offset;
+ pbuf_stream->length -= (u16_t)offset;
+
+ return ERR_OK;
+}
+
+err_t
+snmp_pbuf_stream_seek_abs(struct snmp_pbuf_stream *pbuf_stream, u32_t offset)
+{
+ s32_t rel_offset = offset - pbuf_stream->offset;
+ return snmp_pbuf_stream_seek(pbuf_stream, rel_offset);
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_pbuf_stream.h b/src/apps/snmp/snmp_pbuf_stream.h
new file mode 100644
index 00000000000..682713f959e
--- /dev/null
+++ b/src/apps/snmp/snmp_pbuf_stream.h
@@ -0,0 +1,72 @@
+/**
+ * @file
+ * SNMP pbuf stream wrapper (internal API, do not use in client code).
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Martin Hentschel <[email protected]>
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_PBUF_STREAM_H
+#define LWIP_HDR_APPS_SNMP_PBUF_STREAM_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP
+
+#include "lwip/err.h"
+#include "lwip/pbuf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct snmp_pbuf_stream {
+ struct pbuf *pbuf;
+ u16_t offset;
+ u16_t length;
+};
+
+err_t snmp_pbuf_stream_init(struct snmp_pbuf_stream *pbuf_stream, struct pbuf *p, u16_t offset, u16_t length);
+err_t snmp_pbuf_stream_read(struct snmp_pbuf_stream *pbuf_stream, u8_t *data);
+err_t snmp_pbuf_stream_write(struct snmp_pbuf_stream *pbuf_stream, u8_t data);
+err_t snmp_pbuf_stream_writebuf(struct snmp_pbuf_stream *pbuf_stream, const void *buf, u16_t buf_len);
+err_t snmp_pbuf_stream_writeto(struct snmp_pbuf_stream *pbuf_stream, struct snmp_pbuf_stream *target_pbuf_stream, u16_t len);
+err_t snmp_pbuf_stream_seek(struct snmp_pbuf_stream *pbuf_stream, s32_t offset);
+err_t snmp_pbuf_stream_seek_abs(struct snmp_pbuf_stream *pbuf_stream, u32_t offset);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_SNMP */
+
+#endif /* LWIP_HDR_APPS_SNMP_PBUF_STREAM_H */
diff --git a/src/apps/snmp/snmp_raw.c b/src/apps/snmp/snmp_raw.c
new file mode 100644
index 00000000000..e203cb69492
--- /dev/null
+++ b/src/apps/snmp/snmp_raw.c
@@ -0,0 +1,103 @@
+/**
+ * @file
+ * SNMP RAW API frontend.
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ */
+
+#include "lwip/apps/snmp_opts.h"
+#include "lwip/ip_addr.h"
+
+#if LWIP_SNMP && SNMP_USE_RAW
+
+#include "lwip/udp.h"
+#include "lwip/ip.h"
+#include "lwip/prot/iana.h"
+#include "snmp_msg.h"
+
+/* lwIP UDP receive callback function */
+static void
+snmp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+ LWIP_UNUSED_ARG(arg);
+
+ snmp_receive(pcb, p, addr, port);
+
+ pbuf_free(p);
+}
+
+err_t
+snmp_sendto(void *handle, struct pbuf *p, const ip_addr_t *dst, u16_t port)
+{
+ return udp_sendto((struct udp_pcb *)handle, p, dst, port);
+}
+
+u8_t
+snmp_get_local_ip_for_dst(void *handle, const ip_addr_t *dst, ip_addr_t *result)
+{
+ struct udp_pcb *udp_pcb = (struct udp_pcb *)handle;
+ struct netif *dst_if;
+ const ip_addr_t *dst_ip;
+
+ LWIP_UNUSED_ARG(udp_pcb); /* unused in case of IPV4 only configuration */
+
+ ip_route_get_local_ip(&udp_pcb->local_ip, dst, dst_if, dst_ip);
+
+ if ((dst_if != NULL) && (dst_ip != NULL)) {
+ ip_addr_copy(*result, *dst_ip);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * @ingroup snmp_core
+ * Starts SNMP Agent.
+ * Allocates UDP pcb and binds it to IP_ANY_TYPE port 161.
+ */
+void
+snmp_init(void)
+{
+ err_t err;
+
+ struct udp_pcb *snmp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
+ LWIP_ERROR("snmp_raw: no PCB", (snmp_pcb != NULL), return;);
+
+ LWIP_ASSERT_CORE_LOCKED();
+
+ snmp_traps_handle = snmp_pcb;
+
+ udp_recv(snmp_pcb, snmp_recv, NULL);
+ err = udp_bind(snmp_pcb, IP_ANY_TYPE, LWIP_IANA_PORT_SNMP);
+ LWIP_ERROR("snmp_raw: Unable to bind PCB", (err == ERR_OK), return;);
+}
+
+#endif /* LWIP_SNMP && SNMP_USE_RAW */
diff --git a/src/apps/snmp/snmp_scalar.c b/src/apps/snmp/snmp_scalar.c
new file mode 100644
index 00000000000..094f52fe91a
--- /dev/null
+++ b/src/apps/snmp/snmp_scalar.c
@@ -0,0 +1,232 @@
+/**
+ * @file
+ * SNMP scalar node support implementation.
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Martin Hentschel <[email protected]>
+ *
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/apps/snmp_core.h"
+
+static s16_t snmp_scalar_array_get_value(struct snmp_node_instance *instance, void *value);
+static snmp_err_t snmp_scalar_array_set_test(struct snmp_node_instance *instance, u16_t value_len, void *value);
+static snmp_err_t snmp_scalar_array_set_value(struct snmp_node_instance *instance, u16_t value_len, void *value);
+
+snmp_err_t
+snmp_scalar_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
+{
+ const struct snmp_scalar_node *scalar_node = (const struct snmp_scalar_node *)(const void *)instance->node;
+
+ LWIP_UNUSED_ARG(root_oid);
+ LWIP_UNUSED_ARG(root_oid_len);
+
+ /* scalar only has one dedicated instance: .0 */
+ if ((instance->instance_oid.len != 1) || (instance->instance_oid.id[0] != 0)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ instance->access = scalar_node->access;
+ instance->asn1_type = scalar_node->asn1_type;
+ instance->get_value = scalar_node->get_value;
+ instance->set_test = scalar_node->set_test;
+ instance->set_value = scalar_node->set_value;
+ return SNMP_ERR_NOERROR;
+}
+
+snmp_err_t
+snmp_scalar_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
+{
+ /* because our only instance is .0 we can only return a next instance if no instance oid is passed */
+ if (instance->instance_oid.len == 0) {
+ instance->instance_oid.len = 1;
+ instance->instance_oid.id[0] = 0;
+
+ return snmp_scalar_get_instance(root_oid, root_oid_len, instance);
+ }
+
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+
+snmp_err_t
+snmp_scalar_array_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
+{
+ LWIP_UNUSED_ARG(root_oid);
+ LWIP_UNUSED_ARG(root_oid_len);
+
+ if ((instance->instance_oid.len == 2) && (instance->instance_oid.id[1] == 0)) {
+ const struct snmp_scalar_array_node *array_node = (const struct snmp_scalar_array_node *)(const void *)instance->node;
+ const struct snmp_scalar_array_node_def *array_node_def = array_node->array_nodes;
+ u32_t i = 0;
+
+ while (i < array_node->array_node_count) {
+ if (array_node_def->oid == instance->instance_oid.id[0]) {
+ break;
+ }
+
+ array_node_def++;
+ i++;
+ }
+
+ if (i < array_node->array_node_count) {
+ instance->access = array_node_def->access;
+ instance->asn1_type = array_node_def->asn1_type;
+ instance->get_value = snmp_scalar_array_get_value;
+ instance->set_test = snmp_scalar_array_set_test;
+ instance->set_value = snmp_scalar_array_set_value;
+ instance->reference.const_ptr = array_node_def;
+
+ return SNMP_ERR_NOERROR;
+ }
+ }
+
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+snmp_err_t
+snmp_scalar_array_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
+{
+ const struct snmp_scalar_array_node *array_node = (const struct snmp_scalar_array_node *)(const void *)instance->node;
+ const struct snmp_scalar_array_node_def *array_node_def = array_node->array_nodes;
+ const struct snmp_scalar_array_node_def *result = NULL;
+
+ LWIP_UNUSED_ARG(root_oid);
+ LWIP_UNUSED_ARG(root_oid_len);
+
+ if ((instance->instance_oid.len == 0) && (array_node->array_node_count > 0)) {
+ /* return node with lowest OID */
+ u16_t i = 0;
+
+ result = array_node_def;
+ array_node_def++;
+
+ for (i = 1; i < array_node->array_node_count; i++) {
+ if (array_node_def->oid < result->oid) {
+ result = array_node_def;
+ }
+ array_node_def++;
+ }
+ } else if (instance->instance_oid.len >= 1) {
+ if (instance->instance_oid.len == 1) {
+ /* if we have the requested OID we return its instance, otherwise we search for the next available */
+ u16_t i = 0;
+ while (i < array_node->array_node_count) {
+ if (array_node_def->oid == instance->instance_oid.id[0]) {
+ result = array_node_def;
+ break;
+ }
+
+ array_node_def++;
+ i++;
+ }
+ }
+ if (result == NULL) {
+ u32_t oid_dist = 0xFFFFFFFFUL;
+ u16_t i = 0;
+ array_node_def = array_node->array_nodes; /* may be already at the end when if case before was executed without result -> reinitialize to start */
+ while (i < array_node->array_node_count) {
+ if ((array_node_def->oid > instance->instance_oid.id[0]) &&
+ ((u32_t)(array_node_def->oid - instance->instance_oid.id[0]) < oid_dist)) {
+ result = array_node_def;
+ oid_dist = array_node_def->oid - instance->instance_oid.id[0];
+ }
+
+ array_node_def++;
+ i++;
+ }
+ }
+ }
+
+ if (result == NULL) {
+ /* nothing to return */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ instance->instance_oid.len = 2;
+ instance->instance_oid.id[0] = result->oid;
+ instance->instance_oid.id[1] = 0;
+
+ instance->access = result->access;
+ instance->asn1_type = result->asn1_type;
+ instance->get_value = snmp_scalar_array_get_value;
+ instance->set_test = snmp_scalar_array_set_test;
+ instance->set_value = snmp_scalar_array_set_value;
+ instance->reference.const_ptr = result;
+
+ return SNMP_ERR_NOERROR;
+}
+
+static s16_t
+snmp_scalar_array_get_value(struct snmp_node_instance *instance, void *value)
+{
+ s16_t result = -1;
+ const struct snmp_scalar_array_node *array_node = (const struct snmp_scalar_array_node *)(const void *)instance->node;
+ const struct snmp_scalar_array_node_def *array_node_def = (const struct snmp_scalar_array_node_def *)instance->reference.const_ptr;
+
+ if (array_node->get_value != NULL) {
+ result = array_node->get_value(array_node_def, value);
+ }
+ return result;
+}
+
+static snmp_err_t
+snmp_scalar_array_set_test(struct snmp_node_instance *instance, u16_t value_len, void *value)
+{
+ snmp_err_t result = SNMP_ERR_NOTWRITABLE;
+ const struct snmp_scalar_array_node *array_node = (const struct snmp_scalar_array_node *)(const void *)instance->node;
+ const struct snmp_scalar_array_node_def *array_node_def = (const struct snmp_scalar_array_node_def *)instance->reference.const_ptr;
+
+ if (array_node->set_test != NULL) {
+ result = array_node->set_test(array_node_def, value_len, value);
+ }
+ return result;
+}
+
+static snmp_err_t
+snmp_scalar_array_set_value(struct snmp_node_instance *instance, u16_t value_len, void *value)
+{
+ snmp_err_t result = SNMP_ERR_NOTWRITABLE;
+ const struct snmp_scalar_array_node *array_node = (const struct snmp_scalar_array_node *)(const void *)instance->node;
+ const struct snmp_scalar_array_node_def *array_node_def = (const struct snmp_scalar_array_node_def *)instance->reference.const_ptr;
+
+ if (array_node->set_value != NULL) {
+ result = array_node->set_value(array_node_def, value_len, value);
+ }
+ return result;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_snmpv2_framework.c b/src/apps/snmp/snmp_snmpv2_framework.c
new file mode 100644
index 00000000000..a5611dbf7fa
--- /dev/null
+++ b/src/apps/snmp/snmp_snmpv2_framework.c
@@ -0,0 +1,90 @@
+/*
+Generated by LwipMibCompiler
+*/
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP && LWIP_SNMP_V3 /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp_snmpv2_framework.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmpv3.h"
+#include "snmpv3_priv.h"
+
+#include "lwip/sys.h"
+
+#include <string.h>
+
+const struct snmp_obj_id usmNoAuthProtocol = { 10, { 1, 3, 6, 1, 6, 3, 10, 1, 1, 1 } };
+const struct snmp_obj_id usmHMACMD5AuthProtocol = { 10, { 1, 3, 6, 1, 6, 3, 10, 1, 1, 2 } };
+const struct snmp_obj_id usmHMACSHAAuthProtocol = { 10, { 1, 3, 6, 1, 6, 3, 10, 1, 1, 3 } };
+/* .4 sha-224
+ * .5 sha-256
+ * .6 sha-384
+ * .7 sha-512
+ */
+
+const struct snmp_obj_id usmNoPrivProtocol = { 10, { 1, 3, 6, 1, 6, 3, 10, 1, 2, 1 } };
+const struct snmp_obj_id usmDESPrivProtocol = { 10, { 1, 3, 6, 1, 6, 3, 10, 1, 2, 2 } };
+/* .3 3des-ede */
+const struct snmp_obj_id usmAESPrivProtocol = { 10, { 1, 3, 6, 1, 6, 3, 10, 1, 2, 4 } };
+/* .5 unknown
+ * .6 unknown
+ * .7 unknown
+ */
+
+/* TODO: where should this value come from? */
+#define SNMP_FRAMEWORKMIB_SNMPENGINEMAXMESSAGESIZE 1500
+
+/* --- snmpFrameworkMIBObjects 1.3.6.1.6.3.10.2 ----------------------------------------------------- */
+static s16_t snmpengine_scalars_get_value(const struct snmp_scalar_array_node_def *node, void *value)
+{
+ const char *engineid;
+ u8_t engineid_len;
+
+ switch (node->oid) {
+ case 1: /* snmpEngineID */
+ snmpv3_get_engine_id(&engineid, &engineid_len);
+ MEMCPY(value, engineid, engineid_len);
+ return engineid_len;
+ case 2: /* snmpEngineBoots */
+ *(s32_t *)value = snmpv3_get_engine_boots_internal();
+ return sizeof(s32_t);
+ case 3: /* snmpEngineTime */
+ *(s32_t *)value = snmpv3_get_engine_time_internal();
+ return sizeof(s32_t);
+ case 4: /* snmpEngineMaxMessageSize */
+ *(s32_t *)value = SNMP_FRAMEWORKMIB_SNMPENGINEMAXMESSAGESIZE;
+ return sizeof(s32_t);
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("snmpengine_scalars_get_value(): unknown id: %"S32_F"\n", node->oid));
+ return 0;
+ }
+}
+
+static const struct snmp_scalar_array_node_def snmpengine_scalars_nodes[] = {
+ {1, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpEngineID */
+ {2, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpEngineBoots */
+ {3, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpEngineTime */
+ {4, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY}, /* snmpEngineMaxMessageSize */
+};
+static const struct snmp_scalar_array_node snmpengine_scalars = SNMP_SCALAR_CREATE_ARRAY_NODE(1, snmpengine_scalars_nodes, snmpengine_scalars_get_value, NULL, NULL);
+
+static const struct snmp_node *const snmpframeworkmibobjects_subnodes[] = {
+ &snmpengine_scalars.node.node
+};
+static const struct snmp_tree_node snmpframeworkmibobjects_treenode = SNMP_CREATE_TREE_NODE(2, snmpframeworkmibobjects_subnodes);
+
+/* --- snmpFrameworkMIB ----------------------------------------------------- */
+static const struct snmp_node *const snmpframeworkmib_subnodes[] = {
+ &snmpframeworkmibobjects_treenode.node
+};
+static const struct snmp_tree_node snmpframeworkmib_root = SNMP_CREATE_TREE_NODE(10, snmpframeworkmib_subnodes);
+static const u32_t snmpframeworkmib_base_oid[] = {1, 3, 6, 1, 6, 3, 10};
+const struct snmp_mib snmpframeworkmib = {snmpframeworkmib_base_oid, LWIP_ARRAYSIZE(snmpframeworkmib_base_oid), &snmpframeworkmib_root.node};
+
+/* --- snmpFrameworkMIB ----------------------------------------------------- */
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_snmpv2_usm.c b/src/apps/snmp/snmp_snmpv2_usm.c
new file mode 100644
index 00000000000..4cc73d39588
--- /dev/null
+++ b/src/apps/snmp/snmp_snmpv2_usm.c
@@ -0,0 +1,410 @@
+/*
+Generated by LwipMibCompiler
+*/
+
+#include "lwip/apps/snmp_opts.h"
+#if LWIP_SNMP && LWIP_SNMP_V3
+
+#include "lwip/apps/snmp_snmpv2_usm.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_scalar.h"
+#include "lwip/apps/snmp_table.h"
+#include "lwip/apps/snmpv3.h"
+#include "snmpv3_priv.h"
+
+#include "lwip/apps/snmp_snmpv2_framework.h"
+
+#include <string.h>
+
+/* --- usmUser 1.3.6.1.6.3.15.1.2 ----------------------------------------------------- */
+
+static const struct snmp_oid_range usmUserTable_oid_ranges[] = {
+ { 0, 0xff }, { 0, 0xff }, { 0, 0xff }, { 0, 0xff },
+ { 0, 0xff }, { 0, 0xff }, { 0, 0xff }, { 0, 0xff },
+ { 0, 0xff }, { 0, 0xff }, { 0, 0xff }, { 0, 0xff },
+ { 0, 0xff }, { 0, 0xff }, { 0, 0xff }, { 0, 0xff },
+ { 0, 0xff }, { 0, 0xff }, { 0, 0xff }, { 0, 0xff },
+ { 0, 0xff }, { 0, 0xff }, { 0, 0xff }, { 0, 0xff },
+ { 0, 0xff }, { 0, 0xff }, { 0, 0xff }, { 0, 0xff },
+ { 0, 0xff }, { 0, 0xff }, { 0, 0xff }, { 0, 0xff }
+};
+
+static void snmp_engineid_to_oid(const char *engineid, u32_t *oid, u32_t len)
+{
+ u8_t i;
+
+ for (i = 0; i < len; i++) {
+ oid[i] = engineid[i];
+ }
+}
+
+static void snmp_oid_to_name(char *name, const u32_t *oid, size_t len)
+{
+ u8_t i;
+
+ for (i = 0; i < len; i++) {
+ name[i] = (char)oid[i];
+ }
+}
+
+static void snmp_name_to_oid(const char *name, u32_t *oid, size_t len)
+{
+ u8_t i;
+
+ for (i = 0; i < len; i++) {
+ oid[i] = name[i];
+ }
+}
+
+static const struct snmp_obj_id *snmp_auth_algo_to_oid(snmpv3_auth_algo_t algo)
+{
+ if (algo == SNMP_V3_AUTH_ALGO_MD5) {
+ return &usmHMACMD5AuthProtocol;
+ } else if (algo == SNMP_V3_AUTH_ALGO_SHA) {
+ return &usmHMACSHAAuthProtocol;
+ }
+
+ return &usmNoAuthProtocol;
+}
+
+static const struct snmp_obj_id *snmp_priv_algo_to_oid(snmpv3_priv_algo_t algo)
+{
+ if (algo == SNMP_V3_PRIV_ALGO_DES) {
+ return &usmDESPrivProtocol;
+ } else if (algo == SNMP_V3_PRIV_ALGO_AES) {
+ return &usmAESPrivProtocol;
+ }
+
+ return &usmNoPrivProtocol;
+}
+
+char username[32];
+
+static snmp_err_t usmusertable_get_instance(const u32_t *column, const u32_t *row_oid, u8_t row_oid_len, struct snmp_node_instance *cell_instance)
+{
+ const char *engineid;
+ u8_t eid_len;
+
+ u32_t engineid_oid[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+
+ u8_t name_len;
+ u8_t engineid_len;
+
+ u8_t name_start;
+ u8_t engineid_start;
+
+ LWIP_UNUSED_ARG(column);
+
+ snmpv3_get_engine_id(&engineid, &eid_len);
+
+ engineid_len = (u8_t)row_oid[0];
+ engineid_start = 1;
+
+ if (engineid_len != eid_len) {
+ /* EngineID length does not match! */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ if (engineid_len > row_oid_len) {
+ /* row OID doesn't contain enough data according to engineid_len.*/
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* check if incoming OID length and if values are in plausible range */
+ if (!snmp_oid_in_range(&row_oid[engineid_start], engineid_len, usmUserTable_oid_ranges, engineid_len)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ snmp_engineid_to_oid(engineid, engineid_oid, engineid_len);
+
+ /* Verify EngineID */
+ if (snmp_oid_equal(&row_oid[engineid_start], engineid_len, engineid_oid, engineid_len)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ name_len = (u8_t)row_oid[engineid_start + engineid_len];
+ name_start = engineid_start + engineid_len + 1;
+
+ if (name_len > SNMP_V3_MAX_USER_LENGTH) {
+ /* specified name is too long */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ if (1 + engineid_len + 1 + name_len != row_oid_len) {
+ /* Length of EngineID and name does not match row oid length. (+2 for length fields)*/
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* check if incoming OID length and if values are in plausible range */
+ if (!snmp_oid_in_range(&row_oid[name_start], name_len, usmUserTable_oid_ranges, name_len)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* Verify if user exists */
+ memset(username, 0, sizeof(username));
+ snmp_oid_to_name(username, &row_oid[name_start], name_len);
+ if (snmpv3_get_user(username, NULL, NULL, NULL, NULL) != ERR_OK) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ /* Save name in reference pointer to make it easier to handle later on */
+ cell_instance->reference.ptr = username;
+ cell_instance->reference_len = name_len;
+
+ /* user was found */
+ return SNMP_ERR_NOERROR;
+}
+
+/*
+ * valid oid options
+ * <oid>
+ * <oid>.<EngineID length>
+ * <oid>.<EngineID length>.<partial EngineID>
+ * <oid>.<EngineID length>.<EngineID>
+ * <oid>.<EngineID length>.<EngineID>.<UserName length>
+ * <oid>.<EngineID length>.<EngineID>.<UserName length>.<partial UserName>
+ * <oid>.<EngineID length>.<EngineID>.<UserName length>.<UserName>
+ *
+ */
+static snmp_err_t usmusertable_get_next_instance(const u32_t *column, struct snmp_obj_id *row_oid, struct snmp_node_instance *cell_instance)
+{
+ const char *engineid;
+ u8_t eid_len;
+
+ u32_t engineid_oid[SNMP_V3_MAX_ENGINE_ID_LENGTH];
+
+ u8_t name_len;
+ u8_t engineid_len;
+
+ u8_t name_start;
+ u8_t engineid_start = 1;
+ u8_t i;
+
+ struct snmp_next_oid_state state;
+
+ u32_t result_temp[LWIP_ARRAYSIZE(usmUserTable_oid_ranges)];
+
+ LWIP_UNUSED_ARG(column);
+
+ snmpv3_get_engine_id(&engineid, &eid_len);
+
+ /* If EngineID might be given */
+ if (row_oid->len > 0) {
+ engineid_len = (u8_t)row_oid->id[0];
+ engineid_start = 1;
+
+ if (engineid_len != eid_len) {
+ /* EngineID length does not match! */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ if (engineid_len > row_oid->len) {
+ /* Verify partial EngineID */
+ snmp_engineid_to_oid(engineid, engineid_oid, row_oid->len - 1);
+ if (!snmp_oid_equal(&row_oid->id[engineid_start], row_oid->len - 1, engineid_oid, row_oid->len - 1)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+ } else {
+ /* Verify complete EngineID */
+ snmp_engineid_to_oid(engineid, engineid_oid, engineid_len);
+ if (!snmp_oid_equal(&row_oid->id[engineid_start], engineid_len, engineid_oid, engineid_len)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+ }
+
+ /* At this point, the given EngineID (partially) matches the local EngineID.*/
+
+ /* If name might also be given */
+ if (row_oid->len > engineid_start + engineid_len) {
+ name_len = (u8_t)row_oid->id[engineid_start + engineid_len];
+ name_start = engineid_start + engineid_len + 1;
+
+ if (name_len > SNMP_V3_MAX_USER_LENGTH) {
+ /* specified name is too long, max length is 32 according to mib file.*/
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ if (row_oid->len < engineid_len + name_len + 2) {
+ /* Partial name given according to oid.*/
+ u8_t tmplen = row_oid->len - engineid_len - 2;
+ if (!snmp_oid_in_range(&row_oid->id[name_start], tmplen, usmUserTable_oid_ranges, tmplen)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+ } else {
+ /* Full name given according to oid. Also test for too much data.*/
+ u8_t tmplen = row_oid->len - engineid_len - 2;
+ if (!snmp_oid_in_range(&row_oid->id[name_start], name_len, usmUserTable_oid_ranges, tmplen)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+ }
+
+ /* At this point the EngineID and (partial) UserName match the local EngineID and UserName.*/
+ }
+ }
+
+ /* init struct to search next oid */
+ snmp_next_oid_init(&state, row_oid->id, row_oid->len, result_temp, LWIP_ARRAYSIZE(usmUserTable_oid_ranges));
+
+ for (i = 0; i < snmpv3_get_amount_of_users(); i++) {
+ u32_t test_oid[LWIP_ARRAYSIZE(usmUserTable_oid_ranges)];
+
+ test_oid[0] = eid_len;
+ snmp_engineid_to_oid(engineid, &test_oid[1], eid_len);
+
+ snmpv3_get_username(username, i);
+
+ test_oid[1 + eid_len] = strlen(username);
+ snmp_name_to_oid(username, &test_oid[2 + eid_len], strlen(username));
+
+ /* check generated OID: is it a candidate for the next one? */
+ snmp_next_oid_check(&state, test_oid, (u8_t)(1 + eid_len + 1 + strlen(username)), LWIP_PTR_NUMERIC_CAST(void *, i));
+ }
+
+ /* did we find a next one? */
+ if (state.status == SNMP_NEXT_OID_STATUS_SUCCESS) {
+ snmp_oid_assign(row_oid, state.next_oid, state.next_oid_len);
+ /* store username for subsequent operations (get/test/set) */
+ memset(username, 0, sizeof(username));
+ snmpv3_get_username(username, LWIP_PTR_NUMERIC_CAST(u8_t, state.reference));
+ cell_instance->reference.ptr = username;
+ cell_instance->reference_len = strlen(username);
+ return SNMP_ERR_NOERROR;
+ }
+
+ /* not found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+}
+
+static s16_t usmusertable_get_value(struct snmp_node_instance *cell_instance, void *value)
+{
+ snmpv3_user_storagetype_t storage_type;
+
+ switch (SNMP_TABLE_GET_COLUMN_FROM_OID(cell_instance->instance_oid.id)) {
+ case 3: /* usmUserSecurityName */
+ MEMCPY(value, cell_instance->reference.ptr, cell_instance->reference_len);
+ return (s16_t)cell_instance->reference_len;
+ case 4: /* usmUserCloneFrom */
+ MEMCPY(value, snmp_zero_dot_zero.id, snmp_zero_dot_zero.len * sizeof(u32_t));
+ return snmp_zero_dot_zero.len * sizeof(u32_t);
+ case 5: { /* usmUserAuthProtocol */
+ const struct snmp_obj_id *auth_algo;
+ snmpv3_auth_algo_t auth_algo_val;
+ snmpv3_get_user((const char *)cell_instance->reference.ptr, &auth_algo_val, NULL, NULL, NULL);
+ auth_algo = snmp_auth_algo_to_oid(auth_algo_val);
+ MEMCPY(value, auth_algo->id, auth_algo->len * sizeof(u32_t));
+ return auth_algo->len * sizeof(u32_t);
+ }
+ case 6: /* usmUserAuthKeyChange */
+ return 0;
+ case 7: /* usmUserOwnAuthKeyChange */
+ return 0;
+ case 8: { /* usmUserPrivProtocol */
+ const struct snmp_obj_id *priv_algo;
+ snmpv3_priv_algo_t priv_algo_val;
+ snmpv3_get_user((const char *)cell_instance->reference.ptr, NULL, NULL, &priv_algo_val, NULL);
+ priv_algo = snmp_priv_algo_to_oid(priv_algo_val);
+ MEMCPY(value, priv_algo->id, priv_algo->len * sizeof(u32_t));
+ return priv_algo->len * sizeof(u32_t);
+ }
+ case 9: /* usmUserPrivKeyChange */
+ return 0;
+ case 10: /* usmUserOwnPrivKeyChange */
+ return 0;
+ case 11: /* usmUserPublic */
+ /* TODO: Implement usmUserPublic */
+ return 0;
+ case 12: /* usmUserStorageType */
+ snmpv3_get_user_storagetype((const char *)cell_instance->reference.ptr, &storage_type);
+ *(s32_t *)value = storage_type;
+ return sizeof(s32_t);
+ case 13: /* usmUserStatus */
+ *(s32_t *)value = 1; /* active */
+ return sizeof(s32_t);
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("usmusertable_get_value(): unknown id: %"S32_F"\n", SNMP_TABLE_GET_COLUMN_FROM_OID(cell_instance->instance_oid.id)));
+ return 0;
+ }
+}
+
+/* --- usmMIBObjects 1.3.6.1.6.3.15.1 ----------------------------------------------------- */
+static s16_t usmstats_scalars_get_value(const struct snmp_scalar_array_node_def *node, void *value)
+{
+ u32_t *uint_ptr = (u32_t *)value;
+ switch (node->oid) {
+ case 1: /* usmStatsUnsupportedSecLevels */
+ *uint_ptr = snmp_stats.unsupportedseclevels;
+ break;
+ case 2: /* usmStatsNotInTimeWindows */
+ *uint_ptr = snmp_stats.notintimewindows;
+ break;
+ case 3: /* usmStatsUnknownUserNames */
+ *uint_ptr = snmp_stats.unknownusernames;
+ break;
+ case 4: /* usmStatsUnknownEngineIDs */
+ *uint_ptr = snmp_stats.unknownengineids;
+ break;
+ case 5: /* usmStatsWrongDigests */
+ *uint_ptr = snmp_stats.wrongdigests;
+ break;
+ case 6: /* usmStatsDecryptionErrors */
+ *uint_ptr = snmp_stats.decryptionerrors;
+ break;
+ default:
+ LWIP_DEBUGF(SNMP_MIB_DEBUG, ("usmstats_scalars_get_value(): unknown id: %"S32_F"\n", node->oid));
+ return 0;
+ }
+
+ return sizeof(*uint_ptr);
+}
+
+/* --- snmpUsmMIB ----------------------------------------------------- */
+
+/* --- usmUser 1.3.6.1.6.3.15.1.2 ----------------------------------------------------- */
+
+static const struct snmp_table_col_def usmusertable_columns[] = {
+ {3, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserSecurityName */
+ {4, SNMP_ASN1_TYPE_OBJECT_ID, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserCloneFrom */
+ {5, SNMP_ASN1_TYPE_OBJECT_ID, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserAuthProtocol */
+ {6, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserAuthKeyChange */
+ {7, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserOwnAuthKeyChange */
+ {8, SNMP_ASN1_TYPE_OBJECT_ID, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserPrivProtocol */
+ {9, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserPrivKeyChange */
+ {10, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserOwnPrivKeyChange */
+ {11, SNMP_ASN1_TYPE_OCTET_STRING, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserPublic */
+ {12, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserStorageType */
+ {13, SNMP_ASN1_TYPE_INTEGER, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmUserStatus */
+};
+static const struct snmp_table_node usmusertable = SNMP_TABLE_CREATE(2, usmusertable_columns, usmusertable_get_instance, usmusertable_get_next_instance, usmusertable_get_value, NULL, NULL);
+
+static const struct snmp_node *const usmuser_subnodes[] = {
+ &usmusertable.node.node
+};
+static const struct snmp_tree_node usmuser_treenode = SNMP_CREATE_TREE_NODE(2, usmuser_subnodes);
+
+/* --- usmMIBObjects 1.3.6.1.6.3.15.1 ----------------------------------------------------- */
+static const struct snmp_scalar_array_node_def usmstats_scalars_nodes[] = {
+ {1, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmStatsUnsupportedSecLevels */
+ {2, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmStatsNotInTimeWindows */
+ {3, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmStatsUnknownUserNames */
+ {4, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmStatsUnknownEngineIDs */
+ {5, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmStatsWrongDigests */
+ {6, SNMP_ASN1_TYPE_COUNTER, SNMP_NODE_INSTANCE_READ_ONLY}, /* usmStatsDecryptionErrors */
+};
+static const struct snmp_scalar_array_node usmstats_scalars = SNMP_SCALAR_CREATE_ARRAY_NODE(1, usmstats_scalars_nodes, usmstats_scalars_get_value, NULL, NULL);
+
+static const struct snmp_node *const usmmibobjects_subnodes[] = {
+ &usmstats_scalars.node.node,
+ &usmuser_treenode.node
+};
+static const struct snmp_tree_node usmmibobjects_treenode = SNMP_CREATE_TREE_NODE(1, usmmibobjects_subnodes);
+
+/* --- snmpUsmMIB ----------------------------------------------------- */
+static const struct snmp_node *const snmpusmmib_subnodes[] = {
+ &usmmibobjects_treenode.node
+};
+static const struct snmp_tree_node snmpusmmib_root = SNMP_CREATE_TREE_NODE(15, snmpusmmib_subnodes);
+static const u32_t snmpusmmib_base_oid[] = {1, 3, 6, 1, 6, 3, 15};
+const struct snmp_mib snmpusmmib = {snmpusmmib_base_oid, LWIP_ARRAYSIZE(snmpusmmib_base_oid), &snmpusmmib_root.node};
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_table.c b/src/apps/snmp/snmp_table.c
new file mode 100644
index 00000000000..4b77a47466f
--- /dev/null
+++ b/src/apps/snmp/snmp_table.c
@@ -0,0 +1,342 @@
+/**
+ * @file
+ * SNMP table support implementation.
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Martin Hentschel <[email protected]>
+ *
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp_core.h"
+#include "lwip/apps/snmp_table.h"
+#include <string.h>
+
+snmp_err_t snmp_table_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
+{
+ snmp_err_t ret = SNMP_ERR_NOSUCHINSTANCE;
+ const struct snmp_table_node *table_node = (const struct snmp_table_node *)(const void *)instance->node;
+
+ LWIP_UNUSED_ARG(root_oid);
+ LWIP_UNUSED_ARG(root_oid_len);
+
+ /* check min. length (fixed row entry definition, column, row instance oid with at least one entry */
+ /* fixed row entry always has oid 1 */
+ if ((instance->instance_oid.len >= 3) && (instance->instance_oid.id[0] == 1)) {
+ /* search column */
+ const struct snmp_table_col_def *col_def = table_node->columns;
+ u16_t i = table_node->column_count;
+ while (i > 0) {
+ if (col_def->index == instance->instance_oid.id[1]) {
+ break;
+ }
+
+ col_def++;
+ i--;
+ }
+
+ if (i > 0) {
+ /* everything may be overwritten by get_cell_instance_method() in order to implement special handling for single columns/cells */
+ instance->asn1_type = col_def->asn1_type;
+ instance->access = col_def->access;
+ instance->get_value = table_node->get_value;
+ instance->set_test = table_node->set_test;
+ instance->set_value = table_node->set_value;
+
+ ret = table_node->get_cell_instance(
+ &(instance->instance_oid.id[1]),
+ &(instance->instance_oid.id[2]),
+ instance->instance_oid.len - 2,
+ instance);
+ }
+ }
+
+ return ret;
+}
+
+snmp_err_t snmp_table_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
+{
+ const struct snmp_table_node *table_node = (const struct snmp_table_node *)(const void *)instance->node;
+ const struct snmp_table_col_def *col_def;
+ struct snmp_obj_id row_oid;
+ u32_t column = 0;
+ snmp_err_t result;
+
+ LWIP_UNUSED_ARG(root_oid);
+ LWIP_UNUSED_ARG(root_oid_len);
+
+ /* check that first part of id is 0 or 1, referencing fixed row entry */
+ if ((instance->instance_oid.len > 0) && (instance->instance_oid.id[0] > 1)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+ if (instance->instance_oid.len > 1) {
+ column = instance->instance_oid.id[1];
+ }
+ if (instance->instance_oid.len > 2) {
+ snmp_oid_assign(&row_oid, &(instance->instance_oid.id[2]), instance->instance_oid.len - 2);
+ } else {
+ row_oid.len = 0;
+ }
+
+ instance->get_value = table_node->get_value;
+ instance->set_test = table_node->set_test;
+ instance->set_value = table_node->set_value;
+
+ /* resolve column and value */
+ do {
+ u16_t i;
+ const struct snmp_table_col_def *next_col_def = NULL;
+ col_def = table_node->columns;
+
+ for (i = 0; i < table_node->column_count; i++) {
+ if (col_def->index == column) {
+ next_col_def = col_def;
+ break;
+ } else if ((col_def->index > column) && ((next_col_def == NULL) || (col_def->index < next_col_def->index))) {
+ next_col_def = col_def;
+ }
+ col_def++;
+ }
+
+ if (next_col_def == NULL) {
+ /* no further column found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ instance->asn1_type = next_col_def->asn1_type;
+ instance->access = next_col_def->access;
+
+ result = table_node->get_next_cell_instance(
+ &next_col_def->index,
+ &row_oid,
+ instance);
+
+ if (result == SNMP_ERR_NOERROR) {
+ col_def = next_col_def;
+ break;
+ }
+
+ row_oid.len = 0; /* reset row_oid because we switch to next column and start with the first entry there */
+ column = next_col_def->index + 1;
+ } while (1);
+
+ /* build resulting oid */
+ instance->instance_oid.len = 2;
+ instance->instance_oid.id[0] = 1;
+ instance->instance_oid.id[1] = col_def->index;
+ snmp_oid_append(&instance->instance_oid, row_oid.id, row_oid.len);
+
+ return SNMP_ERR_NOERROR;
+}
+
+
+snmp_err_t snmp_table_simple_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
+{
+ snmp_err_t ret = SNMP_ERR_NOSUCHINSTANCE;
+ const struct snmp_table_simple_node *table_node = (const struct snmp_table_simple_node *)(const void *)instance->node;
+
+ LWIP_UNUSED_ARG(root_oid);
+ LWIP_UNUSED_ARG(root_oid_len);
+
+ /* check min. length (fixed row entry definition, column, row instance oid with at least one entry */
+ /* fixed row entry always has oid 1 */
+ if ((instance->instance_oid.len >= 3) && (instance->instance_oid.id[0] == 1)) {
+ ret = table_node->get_cell_value(
+ &(instance->instance_oid.id[1]),
+ &(instance->instance_oid.id[2]),
+ instance->instance_oid.len - 2,
+ &instance->reference,
+ &instance->reference_len);
+
+ if (ret == SNMP_ERR_NOERROR) {
+ /* search column */
+ const struct snmp_table_simple_col_def *col_def = table_node->columns;
+ u32_t i = table_node->column_count;
+ while (i > 0) {
+ if (col_def->index == instance->instance_oid.id[1]) {
+ break;
+ }
+
+ col_def++;
+ i--;
+ }
+
+ if (i > 0) {
+ instance->asn1_type = col_def->asn1_type;
+ instance->access = SNMP_NODE_INSTANCE_READ_ONLY;
+ instance->set_test = NULL;
+ instance->set_value = NULL;
+
+ switch (col_def->data_type) {
+ case SNMP_VARIANT_VALUE_TYPE_U32:
+ instance->get_value = snmp_table_extract_value_from_u32ref;
+ break;
+ case SNMP_VARIANT_VALUE_TYPE_S32:
+ instance->get_value = snmp_table_extract_value_from_s32ref;
+ break;
+ case SNMP_VARIANT_VALUE_TYPE_PTR: /* fall through */
+ case SNMP_VARIANT_VALUE_TYPE_CONST_PTR:
+ instance->get_value = snmp_table_extract_value_from_refconstptr;
+ break;
+ default:
+ LWIP_DEBUGF(SNMP_DEBUG, ("snmp_table_simple_get_instance(): unknown column data_type: %d\n", col_def->data_type));
+ return SNMP_ERR_GENERROR;
+ }
+
+ ret = SNMP_ERR_NOERROR;
+ } else {
+ ret = SNMP_ERR_NOSUCHINSTANCE;
+ }
+ }
+ }
+
+ return ret;
+}
+
+snmp_err_t snmp_table_simple_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
+{
+ const struct snmp_table_simple_node *table_node = (const struct snmp_table_simple_node *)(const void *)instance->node;
+ const struct snmp_table_simple_col_def *col_def;
+ struct snmp_obj_id row_oid;
+ u32_t column = 0;
+ snmp_err_t result;
+
+ LWIP_UNUSED_ARG(root_oid);
+ LWIP_UNUSED_ARG(root_oid_len);
+
+ /* check that first part of id is 0 or 1, referencing fixed row entry */
+ if ((instance->instance_oid.len > 0) && (instance->instance_oid.id[0] > 1)) {
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+ if (instance->instance_oid.len > 1) {
+ column = instance->instance_oid.id[1];
+ }
+ if (instance->instance_oid.len > 2) {
+ snmp_oid_assign(&row_oid, &(instance->instance_oid.id[2]), instance->instance_oid.len - 2);
+ } else {
+ row_oid.len = 0;
+ }
+
+ /* resolve column and value */
+ do {
+ u32_t i;
+ const struct snmp_table_simple_col_def *next_col_def = NULL;
+ col_def = table_node->columns;
+
+ for (i = 0; i < table_node->column_count; i++) {
+ if (col_def->index == column) {
+ next_col_def = col_def;
+ break;
+ } else if ((col_def->index > column) && ((next_col_def == NULL) ||
+ (col_def->index < next_col_def->index))) {
+ next_col_def = col_def;
+ }
+ col_def++;
+ }
+
+ if (next_col_def == NULL) {
+ /* no further column found */
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ result = table_node->get_next_cell_instance_and_value(
+ &next_col_def->index,
+ &row_oid,
+ &instance->reference,
+ &instance->reference_len);
+
+ if (result == SNMP_ERR_NOERROR) {
+ col_def = next_col_def;
+ break;
+ }
+
+ row_oid.len = 0; /* reset row_oid because we switch to next column and start with the first entry there */
+ column = next_col_def->index + 1;
+ } while (1);
+
+ instance->asn1_type = col_def->asn1_type;
+ instance->access = SNMP_NODE_INSTANCE_READ_ONLY;
+ instance->set_test = NULL;
+ instance->set_value = NULL;
+
+ switch (col_def->data_type) {
+ case SNMP_VARIANT_VALUE_TYPE_U32:
+ instance->get_value = snmp_table_extract_value_from_u32ref;
+ break;
+ case SNMP_VARIANT_VALUE_TYPE_S32:
+ instance->get_value = snmp_table_extract_value_from_s32ref;
+ break;
+ case SNMP_VARIANT_VALUE_TYPE_PTR: /* fall through */
+ case SNMP_VARIANT_VALUE_TYPE_CONST_PTR:
+ instance->get_value = snmp_table_extract_value_from_refconstptr;
+ break;
+ default:
+ LWIP_DEBUGF(SNMP_DEBUG, ("snmp_table_simple_get_instance(): unknown column data_type: %d\n", col_def->data_type));
+ return SNMP_ERR_GENERROR;
+ }
+
+ /* build resulting oid */
+ instance->instance_oid.len = 2;
+ instance->instance_oid.id[0] = 1;
+ instance->instance_oid.id[1] = col_def->index;
+ snmp_oid_append(&instance->instance_oid, row_oid.id, row_oid.len);
+
+ return SNMP_ERR_NOERROR;
+}
+
+
+s16_t
+snmp_table_extract_value_from_s32ref(struct snmp_node_instance *instance, void *value)
+{
+ s32_t *dst = (s32_t *)value;
+ *dst = instance->reference.s32;
+ return sizeof(*dst);
+}
+
+s16_t
+snmp_table_extract_value_from_u32ref(struct snmp_node_instance *instance, void *value)
+{
+ u32_t *dst = (u32_t *)value;
+ *dst = instance->reference.u32;
+ return sizeof(*dst);
+}
+
+s16_t
+snmp_table_extract_value_from_refconstptr(struct snmp_node_instance *instance, void *value)
+{
+ MEMCPY(value, instance->reference.const_ptr, instance->reference_len);
+ return (u16_t)instance->reference_len;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_threadsync.c b/src/apps/snmp/snmp_threadsync.c
new file mode 100644
index 00000000000..927c62a087d
--- /dev/null
+++ b/src/apps/snmp/snmp_threadsync.c
@@ -0,0 +1,231 @@
+/**
+ * @file
+ * SNMP thread synchronization implementation.
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Dirk Ziegelmeier <[email protected]>
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP && (NO_SYS == 0) /* don't build if not configured for use in lwipopts.h */
+
+#include "lwip/apps/snmp_threadsync.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/sys.h"
+#include <string.h>
+
+static void
+call_synced_function(struct threadsync_data *call_data, snmp_threadsync_called_fn fn)
+{
+ sys_mutex_lock(&call_data->threadsync_node->instance->sem_usage_mutex);
+ call_data->threadsync_node->instance->sync_fn(fn, call_data);
+ sys_sem_wait(&call_data->threadsync_node->instance->sem);
+ sys_mutex_unlock(&call_data->threadsync_node->instance->sem_usage_mutex);
+}
+
+static void
+threadsync_get_value_synced(void *ctx)
+{
+ struct threadsync_data *call_data = (struct threadsync_data *)ctx;
+
+ if (call_data->proxy_instance.get_value != NULL) {
+ call_data->retval.s16 = call_data->proxy_instance.get_value(&call_data->proxy_instance, call_data->arg1.value);
+ } else {
+ call_data->retval.s16 = -1;
+ }
+
+ sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static s16_t
+threadsync_get_value(struct snmp_node_instance *instance, void *value)
+{
+ struct threadsync_data *call_data = (struct threadsync_data *)instance->reference.ptr;
+
+ call_data->arg1.value = value;
+ call_synced_function(call_data, threadsync_get_value_synced);
+
+ return call_data->retval.s16;
+}
+
+static void
+threadsync_set_test_synced(void *ctx)
+{
+ struct threadsync_data *call_data = (struct threadsync_data *)ctx;
+
+ if (call_data->proxy_instance.set_test != NULL) {
+ call_data->retval.err = call_data->proxy_instance.set_test(&call_data->proxy_instance, call_data->arg2.len, call_data->arg1.value);
+ } else {
+ call_data->retval.err = SNMP_ERR_NOTWRITABLE;
+ }
+
+ sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static snmp_err_t
+threadsync_set_test(struct snmp_node_instance *instance, u16_t len, void *value)
+{
+ struct threadsync_data *call_data = (struct threadsync_data *)instance->reference.ptr;
+
+ call_data->arg1.value = value;
+ call_data->arg2.len = len;
+ call_synced_function(call_data, threadsync_set_test_synced);
+
+ return call_data->retval.err;
+}
+
+static void
+threadsync_set_value_synced(void *ctx)
+{
+ struct threadsync_data *call_data = (struct threadsync_data *)ctx;
+
+ if (call_data->proxy_instance.set_value != NULL) {
+ call_data->retval.err = call_data->proxy_instance.set_value(&call_data->proxy_instance, call_data->arg2.len, call_data->arg1.value);
+ } else {
+ call_data->retval.err = SNMP_ERR_NOTWRITABLE;
+ }
+
+ sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static snmp_err_t
+threadsync_set_value(struct snmp_node_instance *instance, u16_t len, void *value)
+{
+ struct threadsync_data *call_data = (struct threadsync_data *)instance->reference.ptr;
+
+ call_data->arg1.value = value;
+ call_data->arg2.len = len;
+ call_synced_function(call_data, threadsync_set_value_synced);
+
+ return call_data->retval.err;
+}
+
+static void
+threadsync_release_instance_synced(void *ctx)
+{
+ struct threadsync_data *call_data = (struct threadsync_data *)ctx;
+
+ call_data->proxy_instance.release_instance(&call_data->proxy_instance);
+
+ sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static void
+threadsync_release_instance(struct snmp_node_instance *instance)
+{
+ struct threadsync_data *call_data = (struct threadsync_data *)instance->reference.ptr;
+
+ if (call_data->proxy_instance.release_instance != NULL) {
+ call_synced_function(call_data, threadsync_release_instance_synced);
+ }
+}
+
+static void
+get_instance_synced(void *ctx)
+{
+ struct threadsync_data *call_data = (struct threadsync_data *)ctx;
+ const struct snmp_leaf_node *leaf = (const struct snmp_leaf_node *)(const void *)call_data->proxy_instance.node;
+
+ call_data->retval.err = leaf->get_instance(call_data->arg1.root_oid, call_data->arg2.root_oid_len, &call_data->proxy_instance);
+
+ sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static void
+get_next_instance_synced(void *ctx)
+{
+ struct threadsync_data *call_data = (struct threadsync_data *)ctx;
+ const struct snmp_leaf_node *leaf = (const struct snmp_leaf_node *)(const void *)call_data->proxy_instance.node;
+
+ call_data->retval.err = leaf->get_next_instance(call_data->arg1.root_oid, call_data->arg2.root_oid_len, &call_data->proxy_instance);
+
+ sys_sem_signal(&call_data->threadsync_node->instance->sem);
+}
+
+static snmp_err_t
+do_sync(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance, snmp_threadsync_called_fn fn)
+{
+ const struct snmp_threadsync_node *threadsync_node = (const struct snmp_threadsync_node *)(const void *)instance->node;
+ struct threadsync_data *call_data = &threadsync_node->instance->data;
+
+ if (threadsync_node->node.node.oid != threadsync_node->target->node.oid) {
+ LWIP_DEBUGF(SNMP_DEBUG, ("Sync node OID does not match target node OID\n"));
+ return SNMP_ERR_NOSUCHINSTANCE;
+ }
+
+ memset(&call_data->proxy_instance, 0, sizeof(call_data->proxy_instance));
+
+ instance->reference.ptr = call_data;
+ snmp_oid_assign(&call_data->proxy_instance.instance_oid, instance->instance_oid.id, instance->instance_oid.len);
+
+ call_data->proxy_instance.node = &threadsync_node->target->node;
+ call_data->threadsync_node = threadsync_node;
+
+ call_data->arg1.root_oid = root_oid;
+ call_data->arg2.root_oid_len = root_oid_len;
+ call_synced_function(call_data, fn);
+
+ if (call_data->retval.err == SNMP_ERR_NOERROR) {
+ instance->access = call_data->proxy_instance.access;
+ instance->asn1_type = call_data->proxy_instance.asn1_type;
+ instance->release_instance = threadsync_release_instance;
+ instance->get_value = (call_data->proxy_instance.get_value != NULL) ? threadsync_get_value : NULL;
+ instance->set_value = (call_data->proxy_instance.set_value != NULL) ? threadsync_set_value : NULL;
+ instance->set_test = (call_data->proxy_instance.set_test != NULL) ? threadsync_set_test : NULL;
+ snmp_oid_assign(&instance->instance_oid, call_data->proxy_instance.instance_oid.id, call_data->proxy_instance.instance_oid.len);
+ }
+
+ return call_data->retval.err;
+}
+
+snmp_err_t
+snmp_threadsync_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
+{
+ return do_sync(root_oid, root_oid_len, instance, get_instance_synced);
+}
+
+snmp_err_t
+snmp_threadsync_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
+{
+ return do_sync(root_oid, root_oid_len, instance, get_next_instance_synced);
+}
+
+/** Initializes thread synchronization instance */
+void snmp_threadsync_init(struct snmp_threadsync_instance *instance, snmp_threadsync_synchronizer_fn sync_fn)
+{
+ err_t err = sys_mutex_new(&instance->sem_usage_mutex);
+ LWIP_ASSERT("Failed to set up mutex", err == ERR_OK);
+ err = sys_sem_new(&instance->sem, 0);
+ LWIP_UNUSED_ARG(err); /* in case of LWIP_NOASSERT */
+ LWIP_ASSERT("Failed to set up semaphore", err == ERR_OK);
+ instance->sync_fn = sync_fn;
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmp_traps.c b/src/apps/snmp/snmp_traps.c
new file mode 100644
index 00000000000..4acbe60479e
--- /dev/null
+++ b/src/apps/snmp/snmp_traps.c
@@ -0,0 +1,900 @@
+/**
+ * @file
+ * SNMPv1 and SNMPv2 traps implementation.
+ */
+
+/*
+ * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Martin Hentschel
+ * Christiaan Simons <[email protected]>
+ *
+ */
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
+
+#include <string.h>
+
+#include "lwip/snmp.h"
+#include "lwip/sys.h"
+#include "lwip/apps/snmp.h"
+#include "lwip/apps/snmp_core.h"
+#include "lwip/prot/iana.h"
+#include "snmp_msg.h"
+#include "snmp_asn1.h"
+#include "snmp_core_priv.h"
+
+#define SNMP_IS_INFORM 1
+#define SNMP_IS_TRAP 0
+
+struct snmp_msg_trap
+{
+ /* source enterprise ID (sysObjectID) */
+ const struct snmp_obj_id *enterprise;
+ /* source IP address, raw network order format */
+ ip_addr_t sip;
+ /* generic trap code */
+ u32_t gen_trap;
+ /* specific trap code */
+ u32_t spc_trap;
+ /* timestamp */
+ u32_t ts;
+ /* snmp_version */
+ u32_t snmp_version;
+
+ /* output trap lengths used in ASN encoding */
+ /* encoding pdu length */
+ u16_t pdulen;
+ /* encoding community length */
+ u16_t comlen;
+ /* encoding sequence length */
+ u16_t seqlen;
+ /* encoding varbinds sequence length */
+ u16_t vbseqlen;
+
+ /* error status */
+ s32_t error_status;
+ /* error index */
+ s32_t error_index;
+ /* trap or inform? */
+ u8_t trap_or_inform;
+};
+
+static u16_t snmp_trap_varbind_sum(struct snmp_msg_trap *trap, struct snmp_varbind *varbinds);
+static u16_t snmp_trap_header_sum(struct snmp_msg_trap *trap, u16_t vb_len);
+static err_t snmp_trap_header_enc(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream);
+static err_t snmp_trap_varbind_enc(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind *varbinds);
+static u16_t snmp_trap_header_sum_v1_specific(struct snmp_msg_trap *trap);
+static u16_t snmp_trap_header_sum_v2c_specific(struct snmp_msg_trap *trap);
+static err_t snmp_trap_header_enc_v1_specific(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream);
+static err_t snmp_trap_header_enc_v2c_specific(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream);
+static err_t snmp_prepare_trap_oid(struct snmp_obj_id *dest_snmp_trap_oid, const struct snmp_obj_id *eoid, s32_t generic_trap, s32_t specific_trap);
+static void snmp_prepare_necessary_msg_fields(struct snmp_msg_trap *trap_msg, const struct snmp_obj_id *eoid, s32_t generic_trap, s32_t specific_trap, struct snmp_varbind *varbinds);
+static err_t snmp_send_msg(struct snmp_msg_trap *trap_msg, struct snmp_varbind *varbinds, u16_t tot_len, ip_addr_t *dip);
+
+#define BUILD_EXEC(code) \
+ if ((code) != ERR_OK) { \
+ LWIP_DEBUGF(SNMP_DEBUG, ("SNMP error during creation of outbound trap frame!\n")); \
+ return ERR_ARG; \
+ }
+
+/** Agent community string for sending traps */
+extern const char *snmp_community_trap;
+
+void *snmp_traps_handle;
+
+/**
+ * @ingroup snmp_traps
+ * @struct snmp_trap_dst
+ */
+struct snmp_trap_dst
+{
+ /* destination IP address in network order */
+ ip_addr_t dip;
+ /* set to 0 when disabled, >0 when enabled */
+ u8_t enable;
+};
+static struct snmp_trap_dst trap_dst[SNMP_TRAP_DESTINATIONS];
+
+static u8_t snmp_auth_traps_enabled = 0;
+
+/* This is used in functions like snmp_coldstart_trap where user didn't specify which version of trap to use */
+static u8_t snmp_default_trap_version = SNMP_VERSION_1;
+
+/* This is used in trap messages v2c */
+static s32_t req_id = 1;
+
+/**
+ * @ingroup snmp_traps
+ * Sets enable switch for this trap destination.
+ * @param dst_idx index in 0 .. SNMP_TRAP_DESTINATIONS-1
+ * @param enable switch if 0 destination is disabled >0 enabled.
+ *
+ * @retval void
+ */
+void
+snmp_trap_dst_enable(u8_t dst_idx, u8_t enable)
+{
+ LWIP_ASSERT_SNMP_LOCKED();
+ if (dst_idx < SNMP_TRAP_DESTINATIONS) {
+ trap_dst[dst_idx].enable = enable;
+ }
+}
+
+/**
+ * @ingroup snmp_traps
+ * Sets IPv4 address for this trap destination.
+ * @param dst_idx index in 0 .. SNMP_TRAP_DESTINATIONS-1
+ * @param dst IPv4 address in host order.
+ *
+ * @retval void
+ */
+void
+snmp_trap_dst_ip_set(u8_t dst_idx, const ip_addr_t *dst)
+{
+ LWIP_ASSERT_SNMP_LOCKED();
+ if (dst_idx < SNMP_TRAP_DESTINATIONS) {
+ ip_addr_set(&trap_dst[dst_idx].dip, dst);
+ }
+}
+
+/**
+ * @ingroup snmp_traps
+ * Enable/disable authentication traps
+ *
+ * @param enable enable SNMP traps
+ *
+ * @retval void
+ */
+void
+snmp_set_auth_traps_enabled(u8_t enable)
+{
+ snmp_auth_traps_enabled = enable;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Get authentication traps enabled state
+ *
+ * @return TRUE if traps are enabled, FALSE if they aren't
+ */
+u8_t
+snmp_get_auth_traps_enabled(void)
+{
+ return snmp_auth_traps_enabled;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Choose default SNMP version for sending traps (if not specified, default version is SNMP_VERSION_1)
+ * SNMP_VERSION_1 0
+ * SNMP_VERSION_2c 1
+ * SNMP_VERSION_3 3
+ *
+ * @param snmp_version version that will be used for sending traps
+ *
+ * @retval void
+ */
+void
+snmp_set_default_trap_version(u8_t snmp_version)
+{
+ snmp_default_trap_version = snmp_version;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Get default SNMP version for sending traps
+ *
+ * @return selected default version:
+ * 0 - SNMP_VERSION_1
+ * 1 - SNMP_VERSION_2c
+ * 3 - SNMP_VERSION_3
+ */
+u8_t
+snmp_get_default_trap_version(void)
+{
+ return snmp_default_trap_version;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Prepares snmpTrapOID for SNMP v2c
+ * @param dest_snmp_trap_oid pointer to destination snmpTrapOID
+ * @param eoid enterprise oid (can be NULL)
+ * @param generic_trap SNMP v1 generic trap
+ * @param specific_trap SNMP v1 specific trap
+ * @return ERR_OK if completed successfully;
+ * ERR_MEM if there wasn't enough memory allocated for destination;
+ * ERR_VAL if value for generic trap was incorrect;
+ */
+static err_t
+snmp_prepare_trap_oid(struct snmp_obj_id *dest_snmp_trap_oid, const struct snmp_obj_id *eoid, s32_t generic_trap, s32_t specific_trap)
+{
+ err_t err = ERR_OK;
+ const u32_t snmpTrapOID[] = {1, 3, 6, 1, 6, 3, 1, 1, 5}; /* please see rfc3584 */
+
+ if (generic_trap == SNMP_GENTRAP_ENTERPRISE_SPECIFIC) {
+ if (eoid == NULL) {
+ MEMCPY(dest_snmp_trap_oid, snmp_get_device_enterprise_oid(), sizeof(*dest_snmp_trap_oid));
+ } else {
+ MEMCPY(dest_snmp_trap_oid, eoid, sizeof(*dest_snmp_trap_oid));
+ }
+ if (dest_snmp_trap_oid->len + 2 < SNMP_MAX_OBJ_ID_LEN) {
+ dest_snmp_trap_oid->id[dest_snmp_trap_oid->len++] = 0;
+ dest_snmp_trap_oid->id[dest_snmp_trap_oid->len++] = specific_trap;
+ } else {
+ err = ERR_MEM;
+ }
+ } else if ((generic_trap >= SNMP_GENTRAP_COLDSTART) && (generic_trap < SNMP_GENTRAP_ENTERPRISE_SPECIFIC)) {
+ if (sizeof(dest_snmp_trap_oid->id) >= sizeof(snmpTrapOID)) {
+ MEMCPY(&dest_snmp_trap_oid->id, snmpTrapOID , sizeof(snmpTrapOID));
+ dest_snmp_trap_oid->len = LWIP_ARRAYSIZE(snmpTrapOID);
+ dest_snmp_trap_oid->id[dest_snmp_trap_oid->len++] = specific_trap + 1;
+ } else {
+ err = ERR_MEM;
+ }
+ } else {
+ err = ERR_VAL;
+ }
+ return err;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Prepare the rest of the necessary fields for trap/notification/inform message.
+ * @param trap_msg message that should be set
+ * @param eoid enterprise oid (can be NULL)
+ * @param generic_trap SNMP v1 generic trap
+ * @param specific_trap SNMP v1 specific trap
+ * @param varbinds list of varbinds
+ * @retval void
+ */
+static void
+snmp_prepare_necessary_msg_fields(struct snmp_msg_trap *trap_msg, const struct snmp_obj_id *eoid, s32_t generic_trap, s32_t specific_trap, struct snmp_varbind *varbinds)
+{
+ if (trap_msg->snmp_version == SNMP_VERSION_1) {
+ trap_msg->enterprise = (eoid == NULL) ? snmp_get_device_enterprise_oid() : eoid;
+ trap_msg->gen_trap = generic_trap;
+ trap_msg->spc_trap = (generic_trap == SNMP_GENTRAP_ENTERPRISE_SPECIFIC) ? specific_trap : 0;
+ MIB2_COPY_SYSUPTIME_TO(&trap_msg->ts);
+ } else if (trap_msg->snmp_version == SNMP_VERSION_2c) {
+ /* Copy sysUpTime into the first varbind */
+ MIB2_COPY_SYSUPTIME_TO((u32_t *)varbinds[0].value);
+ }
+}
+
+/**
+ * @ingroup snmp_traps
+ * Copy trap message structure to pbuf and sends it
+ * @param trap_msg contains the data that should be sent
+ * @param varbinds list of varbinds
+ * @param tot_len total length of encoded data
+ * @param dip destination IP address
+ * @return ERR_OK if sending was successful
+ */
+static err_t
+snmp_send_msg(struct snmp_msg_trap *trap_msg, struct snmp_varbind *varbinds, u16_t tot_len, ip_addr_t *dip)
+{
+ err_t err = ERR_OK;
+ struct pbuf *p = NULL;
+ /* allocate pbuf(s) */
+ p = pbuf_alloc(PBUF_TRANSPORT, tot_len, PBUF_RAM);
+ if (p != NULL) {
+ struct snmp_pbuf_stream pbuf_stream;
+ snmp_pbuf_stream_init(&pbuf_stream, p, 0, tot_len);
+
+ /* pass 1, encode packet ino the pbuf(s) */
+ BUILD_EXEC( snmp_trap_header_enc(trap_msg, &pbuf_stream) );
+ BUILD_EXEC( snmp_trap_varbind_enc(trap_msg, &pbuf_stream, varbinds) );
+
+ snmp_stats.outtraps++;
+ snmp_stats.outpkts++;
+
+ /** send to the TRAP destination */
+ err = snmp_sendto(snmp_traps_handle, p, dip, LWIP_IANA_PORT_SNMP_TRAP);
+ pbuf_free(p);
+ } else {
+ err = ERR_MEM;
+ }
+ return err;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Prepare and sends a generic or enterprise specific trap message, notification or inform.
+ *
+ * @param trap_msg defines msg type
+ * @param eoid points to enterprise object identifier
+ * @param generic_trap is the trap code
+ * @param specific_trap used for enterprise traps when generic_trap == 6
+ * @param varbinds linked list of varbinds to be sent
+ * @return ERR_OK when success, ERR_MEM if we're out of memory
+ *
+ * @note the use of the enterprise identifier field
+ * is per RFC1215.
+ * Use .iso.org.dod.internet.mgmt.mib-2.snmp for generic traps
+ * and .iso.org.dod.internet.private.enterprises.yourenterprise
+ * (sysObjectID) for specific traps.
+ */
+static err_t
+snmp_send_trap_or_notification_or_inform_generic(struct snmp_msg_trap *trap_msg, const struct snmp_obj_id *eoid, s32_t generic_trap, s32_t specific_trap, struct snmp_varbind *varbinds)
+{
+ struct snmp_trap_dst *td = NULL;
+ u16_t i = 0;
+ u16_t tot_len = 0;
+ err_t err = ERR_OK;
+ u32_t timestamp = 0;
+ struct snmp_varbind *original_varbinds = varbinds;
+ struct snmp_varbind *original_prev = NULL;
+ struct snmp_varbind snmp_v2_special_varbinds[] = {
+ /* First varbind is used to store sysUpTime */
+ {
+ NULL, /* *next */
+ NULL, /* *prev */
+ { /* oid */
+ 8, /* oid len */
+ {1, 3, 6, 1, 2, 1, 1, 3} /* oid for sysUpTime */
+ },
+ SNMP_ASN1_TYPE_TIMETICKS, /* type */
+ sizeof(u32_t), /* value_len */
+ NULL /* value */
+ },
+ /* Second varbind is used to store snmpTrapOID */
+ {
+ NULL, /* *next */
+ NULL, /* *prev */
+ { /* oid */
+ 10, /* oid len */
+ {1, 3, 6, 1, 6, 3, 1, 1, 4, 1} /* oid for snmpTrapOID */
+ },
+ SNMP_ASN1_TYPE_OBJECT_ID, /* type */
+ 0, /* value_len */
+ NULL /* value */
+ }
+ };
+
+ LWIP_ASSERT_SNMP_LOCKED();
+
+ snmp_v2_special_varbinds[0].next = &snmp_v2_special_varbinds[1];
+ snmp_v2_special_varbinds[1].prev = &snmp_v2_special_varbinds[0];
+
+ snmp_v2_special_varbinds[0].value = &timestamp;
+
+ snmp_v2_special_varbinds[1].next = varbinds;
+
+ /* see rfc3584 */
+ if (trap_msg->snmp_version == SNMP_VERSION_2c) {
+ struct snmp_obj_id snmp_trap_oid = { 0 }; /* used for converting SNMPv1 generic/specific trap parameter to SNMPv2 snmpTrapOID */
+ err = snmp_prepare_trap_oid(&snmp_trap_oid, eoid, generic_trap, specific_trap);
+ if (err == ERR_OK) {
+ snmp_v2_special_varbinds[1].value_len = snmp_trap_oid.len * sizeof(snmp_trap_oid.id[0]);
+ snmp_v2_special_varbinds[1].value = snmp_trap_oid.id;
+ if (varbinds != NULL) {
+ original_prev = varbinds->prev;
+ varbinds->prev = &snmp_v2_special_varbinds[1];
+ }
+ varbinds = snmp_v2_special_varbinds; /* After inserting two varbinds at the beginning of the list, make sure that pointer is pointing to the first element */
+ }
+ }
+
+ for (i = 0, td = &trap_dst[0]; (i < SNMP_TRAP_DESTINATIONS) && (err == ERR_OK); i++, td++) {
+ if ((td->enable != 0) && !ip_addr_isany(&td->dip)) {
+ /* lookup current source address for this dst */
+ if (snmp_get_local_ip_for_dst(snmp_traps_handle, &td->dip, &trap_msg->sip)) {
+ snmp_prepare_necessary_msg_fields(trap_msg, eoid, generic_trap, specific_trap, varbinds);
+
+ /* pass 0, calculate length fields */
+ tot_len = snmp_trap_varbind_sum(trap_msg, varbinds);
+ tot_len = snmp_trap_header_sum(trap_msg, tot_len);
+
+ /* allocate pbuf, fill it and send it */
+ err = snmp_send_msg(trap_msg, varbinds, tot_len, &td->dip);
+ } else {
+ /* routing error */
+ err = ERR_RTE;
+ }
+ }
+ }
+ if ((trap_msg->snmp_version == SNMP_VERSION_2c) && (original_varbinds != NULL)) {
+ original_varbinds->prev = original_prev;
+ }
+ req_id++;
+ return err;
+}
+
+/**
+ * @ingroup snmp_traps
+ * This function is a wrapper function for preparing and sending generic or specific traps.
+ *
+ * @param oid points to enterprise object identifier
+ * @param generic_trap is the trap code
+ * @param specific_trap used for enterprise traps when generic_trap == 6
+ * @param varbinds linked list of varbinds to be sent
+ * @return ERR_OK when success, ERR_MEM if we're out of memory
+ *
+ * @note the use of the enterprise identifier field
+ * is per RFC1215.
+ * Use .iso.org.dod.internet.mgmt.mib-2.snmp for generic traps
+ * and .iso.org.dod.internet.private.enterprises.yourenterprise
+ * (sysObjectID) for specific traps.
+ */
+err_t
+snmp_send_trap(const struct snmp_obj_id* oid, s32_t generic_trap, s32_t specific_trap, struct snmp_varbind *varbinds)
+{
+ struct snmp_msg_trap trap_msg = {0};
+ trap_msg.snmp_version = snmp_default_trap_version;
+ trap_msg.trap_or_inform = SNMP_IS_TRAP;
+ return snmp_send_trap_or_notification_or_inform_generic(&trap_msg, oid, generic_trap, specific_trap, varbinds);
+}
+
+/**
+ * @ingroup snmp_traps
+ * Send generic SNMP trap
+ * @param generic_trap is the trap code
+ * return ERR_OK when success
+ */
+err_t
+snmp_send_trap_generic(s32_t generic_trap)
+{
+ err_t err = ERR_OK;
+ struct snmp_msg_trap trap_msg = {0};
+ trap_msg.snmp_version = snmp_default_trap_version;
+ trap_msg.trap_or_inform = SNMP_IS_TRAP;
+
+ if(snmp_default_trap_version == SNMP_VERSION_1) {
+ static const struct snmp_obj_id oid = { 7, { 1, 3, 6, 1, 2, 1, 11 } };
+ err = snmp_send_trap_or_notification_or_inform_generic(&trap_msg, &oid, generic_trap, 0, NULL);
+ } else if (snmp_default_trap_version == SNMP_VERSION_2c) {
+ err = snmp_send_trap_or_notification_or_inform_generic(&trap_msg, NULL, generic_trap, 0, NULL);
+ } else {
+ err = ERR_VAL;
+ }
+ return err;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Send specific SNMP trap with variable bindings
+ * @param specific_trap used for enterprise traps (generic_trap = 6)
+ * @param varbinds linked list of varbinds to be sent
+ * @return ERR_OK when success
+ */
+err_t
+snmp_send_trap_specific(s32_t specific_trap, struct snmp_varbind *varbinds)
+{
+ struct snmp_msg_trap trap_msg = {0};
+ trap_msg.snmp_version = snmp_default_trap_version;
+ trap_msg.trap_or_inform = SNMP_IS_TRAP;
+ return snmp_send_trap_or_notification_or_inform_generic(&trap_msg, NULL, SNMP_GENTRAP_ENTERPRISE_SPECIFIC, specific_trap, varbinds);
+}
+
+/**
+ * @ingroup snmp_traps
+ * Send coldstart trap
+ * @retval void
+ */
+void
+snmp_coldstart_trap(void)
+{
+ snmp_send_trap_generic(SNMP_GENTRAP_COLDSTART);
+}
+
+/**
+ * @ingroup snmp_traps
+ * Send authentication failure trap (used internally by agent)
+ * @retval void
+ */
+void
+snmp_authfail_trap(void)
+{
+ if (snmp_auth_traps_enabled != 0) {
+ snmp_send_trap_generic(SNMP_GENTRAP_AUTH_FAILURE);
+ }
+}
+
+/**
+ * @ingroup snmp_traps
+ * Sums trap varbinds
+ *
+ * @param trap Trap message
+ * @param varbinds linked list of varbinds
+ * @return the required length for encoding of this part of the trap header
+ */
+static u16_t
+snmp_trap_varbind_sum(struct snmp_msg_trap *trap, struct snmp_varbind *varbinds)
+{
+ struct snmp_varbind *varbind;
+ u16_t tot_len;
+ u8_t tot_len_len;
+
+ tot_len = 0;
+ varbind = varbinds;
+ while (varbind != NULL) {
+ struct snmp_varbind_len len;
+
+ if (snmp_varbind_length(varbind, &len) == ERR_OK) {
+ tot_len += 1 + len.vb_len_len + len.vb_value_len;
+ }
+
+ varbind = varbind->next;
+ }
+
+ trap->vbseqlen = tot_len;
+ snmp_asn1_enc_length_cnt(trap->vbseqlen, &tot_len_len);
+ tot_len += 1 + tot_len_len;
+
+ return tot_len;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Sums trap header fields that are specific for SNMP v1
+ *
+ * @param trap Trap message
+ * @return the required length for encoding of this part of the trap header
+ */
+static u16_t
+snmp_trap_header_sum_v1_specific(struct snmp_msg_trap *trap)
+{
+ u16_t tot_len = 0;
+ u16_t len = 0;
+ u8_t lenlen = 0;
+
+ snmp_asn1_enc_u32t_cnt(trap->ts, &len);
+ snmp_asn1_enc_length_cnt(len, &lenlen);
+ tot_len += 1 + len + lenlen;
+
+ snmp_asn1_enc_s32t_cnt(trap->spc_trap, &len);
+ snmp_asn1_enc_length_cnt(len, &lenlen);
+ tot_len += 1 + len + lenlen;
+
+ snmp_asn1_enc_s32t_cnt(trap->gen_trap, &len);
+ snmp_asn1_enc_length_cnt(len, &lenlen);
+ tot_len += 1 + len + lenlen;
+
+ if (IP_IS_V6_VAL(trap->sip)) {
+#if LWIP_IPV6
+ len = sizeof(ip_2_ip6(&trap->sip)->addr);
+#endif
+ } else {
+#if LWIP_IPV4
+ len = sizeof(ip_2_ip4(&trap->sip)->addr);
+#endif
+ }
+ snmp_asn1_enc_length_cnt(len, &lenlen);
+ tot_len += 1 + len + lenlen;
+
+ snmp_asn1_enc_oid_cnt(trap->enterprise->id, trap->enterprise->len, &len);
+ snmp_asn1_enc_length_cnt(len, &lenlen);
+ tot_len += 1 + len + lenlen;
+
+ return tot_len;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Sums trap header fields that are specific for SNMP v2c
+ *
+ * @param trap Trap message
+ * @return the required length for encoding of this part of the trap header
+ */
+static u16_t
+snmp_trap_header_sum_v2c_specific(struct snmp_msg_trap *trap)
+{
+ u16_t tot_len = 0;
+ u16_t len = 0;
+ u8_t lenlen = 0;
+
+ snmp_asn1_enc_u32t_cnt(req_id, &len);
+ snmp_asn1_enc_length_cnt(len, &lenlen);
+ tot_len += 1 + len + lenlen;
+ snmp_asn1_enc_u32t_cnt(trap->error_status, &len);
+ snmp_asn1_enc_length_cnt(len, &lenlen);
+ tot_len += 1 + len + lenlen;
+ snmp_asn1_enc_u32t_cnt(trap->error_index, &len);
+ snmp_asn1_enc_length_cnt(len, &lenlen);
+ tot_len += 1 + len + lenlen;
+
+ return tot_len;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Sums trap header field lengths from tail to head and
+ * returns trap_header_lengths for second encoding pass.
+ *
+ * @param trap Trap message
+ * @param vb_len varbind-list length
+ * @return the required length for encoding the trap header
+ */
+static u16_t
+snmp_trap_header_sum(struct snmp_msg_trap *trap, u16_t vb_len)
+{
+ u16_t tot_len = vb_len;
+ u16_t len = 0;
+ u8_t lenlen = 0;
+
+ if (trap->snmp_version == SNMP_VERSION_1) {
+ tot_len += snmp_trap_header_sum_v1_specific(trap);
+ } else if (trap->snmp_version == SNMP_VERSION_2c) {
+ tot_len += snmp_trap_header_sum_v2c_specific(trap);
+ }
+ trap->pdulen = tot_len;
+ snmp_asn1_enc_length_cnt(trap->pdulen, &lenlen);
+ tot_len += 1 + lenlen;
+
+ trap->comlen = (u16_t)LWIP_MIN(strlen(snmp_community_trap), 0xFFFF);
+ snmp_asn1_enc_length_cnt(trap->comlen, &lenlen);
+ tot_len += 1 + lenlen + trap->comlen;
+
+ snmp_asn1_enc_s32t_cnt(trap->snmp_version, &len);
+ snmp_asn1_enc_length_cnt(len, &lenlen);
+ tot_len += 1 + len + lenlen;
+
+ trap->seqlen = tot_len;
+ snmp_asn1_enc_length_cnt(trap->seqlen, &lenlen);
+ tot_len += 1 + lenlen;
+
+ return tot_len;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Encodes varbinds.
+ * @param trap Trap message
+ * @param pbuf_stream stream used for storing data inside pbuf
+ * @param varbinds linked list of varbinds
+ * @retval err_t ERR_OK if successful, ERR_ARG otherwise
+ */
+static err_t
+snmp_trap_varbind_enc(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream, struct snmp_varbind *varbinds)
+{
+ struct snmp_asn1_tlv tlv;
+ struct snmp_varbind *varbind;
+
+ varbind = varbinds;
+
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 0, trap->vbseqlen);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+ while (varbind != NULL) {
+ BUILD_EXEC( snmp_append_outbound_varbind(pbuf_stream, varbind) );
+
+ varbind = varbind->next;
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Encodes trap header PDU part.
+ * @param trap Trap message
+ * @param pbuf_stream stream used for storing data inside pbuf
+ * @retval err_t ERR_OK if successful, ERR_ARG otherwise
+ */
+static err_t
+snmp_trap_header_enc_pdu(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream)
+{
+ struct snmp_asn1_tlv tlv;
+ /* 'PDU' sequence */
+ if (trap->snmp_version == SNMP_VERSION_1) {
+ /* TRAP V1 */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_TRAP), 0, trap->pdulen);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ } else if ((trap->snmp_version == SNMP_VERSION_2c) && (trap->trap_or_inform == SNMP_IS_INFORM)) {
+ /* TRAP v2 - INFORM */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_INFORM_REQ), 0, trap->pdulen);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ } else if (trap->snmp_version == SNMP_VERSION_2c) {
+ /* TRAP v2 - NOTIFICATION*/
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, (SNMP_ASN1_CLASS_CONTEXT | SNMP_ASN1_CONTENTTYPE_CONSTRUCTED | SNMP_ASN1_CONTEXT_PDU_V2_TRAP), 0, trap->pdulen);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Encodes trap header part that is SNMP v1 header specific.
+ * @param trap Trap message
+ * @param pbuf_stream stream used for storing data inside pbuf
+ * @retval void
+ */
+static err_t
+snmp_trap_header_enc_v1_specific(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream)
+{
+ struct snmp_asn1_tlv tlv;
+ /* object ID */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OBJECT_ID, 0, 0);
+ snmp_asn1_enc_oid_cnt(trap->enterprise->id, trap->enterprise->len, &tlv.value_len);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_oid(pbuf_stream, trap->enterprise->id, trap->enterprise->len) );
+
+ /* IP addr */
+ if (IP_IS_V6_VAL(trap->sip)) {
+#if LWIP_IPV6
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_IPADDR, 0, sizeof(ip_2_ip6(&trap->sip)->addr));
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_raw(pbuf_stream, (const u8_t *)&ip_2_ip6(&trap->sip)->addr, sizeof(ip_2_ip6(&trap->sip)->addr)) );
+#endif
+ } else {
+#if LWIP_IPV4
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_IPADDR, 0, sizeof(ip_2_ip4(&trap->sip)->addr));
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_raw(pbuf_stream, (const u8_t *)&ip_2_ip4(&trap->sip)->addr, sizeof(ip_2_ip4(&trap->sip)->addr)) );
+#endif
+ }
+
+ /* generic trap */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+ snmp_asn1_enc_s32t_cnt(trap->gen_trap, &tlv.value_len);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, trap->gen_trap) );
+
+ /* specific trap */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+ snmp_asn1_enc_s32t_cnt(trap->spc_trap, &tlv.value_len);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, trap->spc_trap) );
+
+ /* timestamp */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_TIMETICKS, 0, 0);
+ snmp_asn1_enc_s32t_cnt(trap->ts, &tlv.value_len);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, trap->ts) );
+
+ return ERR_OK;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Encodes trap header part that is SNMP v2c header specific.
+ *
+ * @param trap Trap message
+ * @param pbuf_stream stream used for storing data inside pbuf
+ * @retval void
+ */
+static err_t
+snmp_trap_header_enc_v2c_specific(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream)
+{
+ struct snmp_asn1_tlv tlv;
+ /* request id */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+ snmp_asn1_enc_s32t_cnt(req_id, &tlv.value_len);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, req_id) );
+
+ /* error status */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+ snmp_asn1_enc_s32t_cnt(trap->error_status, &tlv.value_len);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, trap->error_status) );
+
+ /* error index */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+ snmp_asn1_enc_s32t_cnt(trap->error_index, &tlv.value_len);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, trap->error_index) );
+
+ return ERR_OK;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Encodes trap header from head to tail.
+ *
+ * @param trap Trap message
+ * @param pbuf_stream stream used for storing data inside pbuf
+ * @retval void
+ */
+static err_t
+snmp_trap_header_enc(struct snmp_msg_trap *trap, struct snmp_pbuf_stream *pbuf_stream)
+{
+ struct snmp_asn1_tlv tlv;
+
+ /* 'Message' sequence */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_SEQUENCE, 0, trap->seqlen);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+
+ /* version */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_INTEGER, 0, 0);
+ snmp_asn1_enc_s32t_cnt(trap->snmp_version, &tlv.value_len);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_s32t(pbuf_stream, tlv.value_len, trap->snmp_version) );
+
+ /* community */
+ SNMP_ASN1_SET_TLV_PARAMS(tlv, SNMP_ASN1_TYPE_OCTET_STRING, 0, trap->comlen);
+ BUILD_EXEC( snmp_ans1_enc_tlv(pbuf_stream, &tlv) );
+ BUILD_EXEC( snmp_asn1_enc_raw(pbuf_stream, (const u8_t *)snmp_community_trap, trap->comlen) );
+
+ /* PDU */
+ BUILD_EXEC( snmp_trap_header_enc_pdu(trap, pbuf_stream) );
+ if (trap->snmp_version == SNMP_VERSION_1) {
+ /* object ID, IP addr, generic trap, specific trap, timestamp */
+ BUILD_EXEC( snmp_trap_header_enc_v1_specific(trap, pbuf_stream) );
+ } else if (SNMP_VERSION_2c == trap->snmp_version) {
+ /* request id, error status, error index */
+ BUILD_EXEC( snmp_trap_header_enc_v2c_specific(trap, pbuf_stream) );
+ }
+
+ return ERR_OK;
+}
+
+/**
+ * @ingroup snmp_traps
+ * Wrapper function for sending informs
+ * @param specific_trap will be appended to enterprise oid [see RFC 3584]
+ * @param varbinds linked list of varbinds (at the beginning of this list function will insert 2 special purpose varbinds [see RFC 3584])
+ * @param ptr_request_id [out] variable in which to store request_id needed to verify acknowledgement
+ * @return ERR_OK if successful
+ */
+err_t
+snmp_send_inform_specific(s32_t specific_trap, struct snmp_varbind *varbinds, s32_t *ptr_request_id)
+{
+ return snmp_send_inform(NULL, SNMP_GENTRAP_ENTERPRISE_SPECIFIC, specific_trap, varbinds, ptr_request_id);
+}
+
+/**
+ * @ingroup snmp_traps
+ * Wrapper function for sending informs
+ * @param generic_trap is the trap code
+ * @param varbinds linked list of varbinds (at the beginning of this list function will insert 2 special purpose varbinds [see RFC 3584])
+ * @param ptr_request_id [out] variable in which to store request_id needed to verify acknowledgement
+ * @return ERR_OK if successful
+ */
+err_t
+snmp_send_inform_generic(s32_t generic_trap, struct snmp_varbind *varbinds, s32_t *ptr_request_id)
+{
+ return snmp_send_inform(NULL, generic_trap, 0, varbinds, ptr_request_id);
+}
+
+/**
+ * @ingroup snmp_traps
+ * Generic function for sending informs
+ * @param oid points to object identifier
+ * @param generic_trap is the trap code
+ * @param specific_trap used for enterprise traps when generic_trap == 6
+ * @param varbinds linked list of varbinds (at the beginning of this list function will insert 2 special purpose varbinds [see RFC 3584])
+ * @param ptr_request_id [out] variable in which to store request_id needed to verify acknowledgement
+ * @return ERR_OK if successful
+ */
+err_t
+snmp_send_inform(const struct snmp_obj_id* oid, s32_t generic_trap, s32_t specific_trap, struct snmp_varbind *varbinds, s32_t *ptr_request_id)
+{
+ struct snmp_msg_trap trap_msg = {0};
+ trap_msg.snmp_version = SNMP_VERSION_2c;
+ trap_msg.trap_or_inform = SNMP_IS_INFORM;
+ *ptr_request_id = req_id;
+ return snmp_send_trap_or_notification_or_inform_generic(&trap_msg, oid, generic_trap, specific_trap, varbinds);
+}
+
+#endif /* LWIP_SNMP */
diff --git a/src/apps/snmp/snmpv3.c b/src/apps/snmp/snmpv3.c
new file mode 100644
index 00000000000..ed5a076c2ed
--- /dev/null
+++ b/src/apps/snmp/snmpv3.c
@@ -0,0 +1,136 @@
+/**
+ * @file
+ * Additional SNMPv3 functionality RFC3414 and RFC3826.
+ */
+
+/*
+ * Copyright (c) 2016 Elias Oenal.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Elias Oenal <[email protected]>
+ */
+
+#include "snmpv3_priv.h"
+#include "lwip/apps/snmpv3.h"
+#include "lwip/sys.h"
+#include <string.h>
+
+#if LWIP_SNMP && LWIP_SNMP_V3
+
+#ifdef LWIP_SNMPV3_INCLUDE_ENGINE
+#include LWIP_SNMPV3_INCLUDE_ENGINE
+#endif
+
+#define SNMP_MAX_TIME_BOOT 2147483647UL
+
+/** Call this if engine has been changed. Has to reset boots, see below */
+void
+snmpv3_engine_id_changed(void)
+{
+ snmpv3_set_engine_boots(0);
+}
+
+/** According to RFC3414 2.2.2.
+ *
+ * The number of times that the SNMP engine has
+ * (re-)initialized itself since snmpEngineID
+ * was last configured.
+ */
+s32_t
+snmpv3_get_engine_boots_internal(void)
+{
+ if (snmpv3_get_engine_boots() == 0 ||
+ snmpv3_get_engine_boots() < SNMP_MAX_TIME_BOOT) {
+ return snmpv3_get_engine_boots();
+ }
+
+ snmpv3_set_engine_boots(SNMP_MAX_TIME_BOOT);
+ return snmpv3_get_engine_boots();
+}
+
+/** RFC3414 2.2.2.
+ *
+ * Once the timer reaches 2147483647 it gets reset to zero and the
+ * engine boot ups get incremented.
+ */
+s32_t
+snmpv3_get_engine_time_internal(void)
+{
+ if (snmpv3_get_engine_time() >= SNMP_MAX_TIME_BOOT) {
+ snmpv3_reset_engine_time();
+
+ if (snmpv3_get_engine_boots() < SNMP_MAX_TIME_BOOT - 1) {
+ snmpv3_set_engine_boots(snmpv3_get_engine_boots() + 1);
+ } else {
+ snmpv3_set_engine_boots(SNMP_MAX_TIME_BOOT);
+ }
+ }
+
+ return snmpv3_get_engine_time();
+}
+
+#if LWIP_SNMP_V3_CRYPTO
+
+/* This function ignores the byte order suggestion in RFC3414
+ * since it simply doesn't influence the effectiveness of an IV.
+ *
+ * Implementing RFC3826 priv param algorithm if LWIP_RAND is available.
+ *
+ * @todo: This is a potential thread safety issue.
+ */
+err_t
+snmpv3_build_priv_param(u8_t *priv_param)
+{
+#ifdef LWIP_RAND /* Based on RFC3826 */
+ static u8_t init;
+ static u32_t priv1, priv2;
+
+ /* Lazy initialisation */
+ if (init == 0) {
+ init = 1;
+ priv1 = LWIP_RAND();
+ priv2 = LWIP_RAND();
+ }
+
+ SMEMCPY(&priv_param[0], &priv1, sizeof(priv1));
+ SMEMCPY(&priv_param[4], &priv2, sizeof(priv2));
+
+ /* Emulate 64bit increment */
+ priv1++;
+ if (!priv1) { /* Overflow */
+ priv2++;
+ }
+#else /* Based on RFC3414 */
+ static u32_t ctr;
+ u32_t boots = snmpv3_get_engine_boots_internal();
+ SMEMCPY(&priv_param[0], &boots, 4);
+ SMEMCPY(&priv_param[4], &ctr, 4);
+ ctr++;
+#endif
+ return ERR_OK;
+}
+#endif /* LWIP_SNMP_V3_CRYPTO */
+
+#endif
diff --git a/src/apps/snmp/snmpv3_mbedtls.c b/src/apps/snmp/snmpv3_mbedtls.c
new file mode 100644
index 00000000000..48c1a81fef1
--- /dev/null
+++ b/src/apps/snmp/snmpv3_mbedtls.c
@@ -0,0 +1,342 @@
+/**
+ * @file
+ * SNMPv3 crypto/auth functions implemented for ARM mbedtls.
+ */
+
+/*
+ * Copyright (c) 2016 Elias Oenal and Dirk Ziegelmeier.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Elias Oenal <[email protected]>
+ * Dirk Ziegelmeier <[email protected]>
+ */
+
+#include "lwip/apps/snmpv3.h"
+#include "snmpv3_priv.h"
+#include "lwip/arch.h"
+#include "snmp_msg.h"
+#include "lwip/sys.h"
+#include <string.h>
+
+#if LWIP_SNMP && LWIP_SNMP_V3 && LWIP_SNMP_V3_MBEDTLS
+
+#include "mbedtls/md.h"
+#include "mbedtls/cipher.h"
+
+#include "mbedtls/md5.h"
+#include "mbedtls/sha1.h"
+
+err_t
+snmpv3_auth(struct snmp_pbuf_stream *stream, u16_t length,
+ const u8_t *key, snmpv3_auth_algo_t algo, u8_t *hmac_out)
+{
+ u32_t i;
+ u8_t key_len;
+ const mbedtls_md_info_t *md_info;
+ mbedtls_md_context_t ctx;
+ struct snmp_pbuf_stream read_stream;
+ snmp_pbuf_stream_init(&read_stream, stream->pbuf, stream->offset, stream->length);
+
+ if (algo == SNMP_V3_AUTH_ALGO_MD5) {
+ md_info = mbedtls_md_info_from_type(MBEDTLS_MD_MD5);
+ key_len = SNMP_V3_MD5_LEN;
+ } else if (algo == SNMP_V3_AUTH_ALGO_SHA) {
+ md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1);
+ key_len = SNMP_V3_SHA_LEN;
+ } else {
+ return ERR_ARG;
+ }
+
+ mbedtls_md_init(&ctx);
+ if (mbedtls_md_setup(&ctx, md_info, 1) != 0) {
+ return ERR_ARG;
+ }
+
+ if (mbedtls_md_hmac_starts(&ctx, key, key_len) != 0) {
+ goto free_md;
+ }
+
+ for (i = 0; i < length; i++) {
+ u8_t byte;
+
+ if (snmp_pbuf_stream_read(&read_stream, &byte)) {
+ goto free_md;
+ }
+
+ if (mbedtls_md_hmac_update(&ctx, &byte, 1) != 0) {
+ goto free_md;
+ }
+ }
+
+ if (mbedtls_md_hmac_finish(&ctx, hmac_out) != 0) {
+ goto free_md;
+ }
+
+ mbedtls_md_free(&ctx);
+ return ERR_OK;
+
+free_md:
+ mbedtls_md_free(&ctx);
+ return ERR_ARG;
+}
+
+#if LWIP_SNMP_V3_CRYPTO
+
+err_t
+snmpv3_crypt(struct snmp_pbuf_stream *stream, u16_t length,
+ const u8_t *key, const u8_t *priv_param, const u32_t engine_boots,
+ const u32_t engine_time, snmpv3_priv_algo_t algo, snmpv3_priv_mode_t mode)
+{
+ size_t i;
+ mbedtls_cipher_context_t ctx;
+ const mbedtls_cipher_info_t *cipher_info;
+
+ struct snmp_pbuf_stream read_stream;
+ struct snmp_pbuf_stream write_stream;
+ snmp_pbuf_stream_init(&read_stream, stream->pbuf, stream->offset, stream->length);
+ snmp_pbuf_stream_init(&write_stream, stream->pbuf, stream->offset, stream->length);
+ mbedtls_cipher_init(&ctx);
+
+ if (algo == SNMP_V3_PRIV_ALGO_DES) {
+ u8_t iv_local[8];
+ u8_t out_bytes[8];
+ size_t out_len;
+
+ /* RFC 3414 mandates padding for DES */
+ if ((length & 0x07) != 0) {
+ return ERR_ARG;
+ }
+
+ cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_DES_CBC);
+ if (mbedtls_cipher_setup(&ctx, cipher_info) != 0) {
+ return ERR_ARG;
+ }
+ if (mbedtls_cipher_set_padding_mode(&ctx, MBEDTLS_PADDING_NONE) != 0) {
+ return ERR_ARG;
+ }
+ if (mbedtls_cipher_setkey(&ctx, key, 8 * 8, (mode == SNMP_V3_PRIV_MODE_ENCRYPT) ? MBEDTLS_ENCRYPT : MBEDTLS_DECRYPT) != 0) {
+ goto error;
+ }
+
+ /* Prepare IV */
+ for (i = 0; i < LWIP_ARRAYSIZE(iv_local); i++) {
+ iv_local[i] = priv_param[i] ^ key[i + 8];
+ }
+ if (mbedtls_cipher_set_iv(&ctx, iv_local, LWIP_ARRAYSIZE(iv_local)) != 0) {
+ goto error;
+ }
+
+ for (i = 0; i < length; i += 8) {
+ size_t j;
+ u8_t in_bytes[8];
+ out_len = LWIP_ARRAYSIZE(out_bytes) ;
+
+ for (j = 0; j < LWIP_ARRAYSIZE(in_bytes); j++) {
+ if (snmp_pbuf_stream_read(&read_stream, &in_bytes[j]) != ERR_OK) {
+ goto error;
+ }
+ }
+
+ if (mbedtls_cipher_update(&ctx, in_bytes, LWIP_ARRAYSIZE(in_bytes), out_bytes, &out_len) != 0) {
+ goto error;
+ }
+
+ if (snmp_pbuf_stream_writebuf(&write_stream, out_bytes, (u16_t)out_len) != ERR_OK) {
+ goto error;
+ }
+ }
+
+ out_len = LWIP_ARRAYSIZE(out_bytes);
+ if (mbedtls_cipher_finish(&ctx, out_bytes, &out_len) != 0) {
+ goto error;
+ }
+
+ if (snmp_pbuf_stream_writebuf(&write_stream, out_bytes, (u16_t)out_len) != ERR_OK) {
+ goto error;
+ }
+ } else if (algo == SNMP_V3_PRIV_ALGO_AES) {
+ u8_t iv_local[16];
+
+ cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_CFB128);
+ if (mbedtls_cipher_setup(&ctx, cipher_info) != 0) {
+ return ERR_ARG;
+ }
+ if (mbedtls_cipher_setkey(&ctx, key, 16 * 8, (mode == SNMP_V3_PRIV_MODE_ENCRYPT) ? MBEDTLS_ENCRYPT : MBEDTLS_DECRYPT) != 0) {
+ goto error;
+ }
+
+ /*
+ * IV is the big endian concatenation of boots,
+ * uptime and priv param - see RFC3826.
+ */
+ iv_local[0 + 0] = (engine_boots >> 24) & 0xFF;
+ iv_local[0 + 1] = (engine_boots >> 16) & 0xFF;
+ iv_local[0 + 2] = (engine_boots >> 8) & 0xFF;
+ iv_local[0 + 3] = (engine_boots >> 0) & 0xFF;
+ iv_local[4 + 0] = (engine_time >> 24) & 0xFF;
+ iv_local[4 + 1] = (engine_time >> 16) & 0xFF;
+ iv_local[4 + 2] = (engine_time >> 8) & 0xFF;
+ iv_local[4 + 3] = (engine_time >> 0) & 0xFF;
+ SMEMCPY(iv_local + 8, priv_param, 8);
+ if (mbedtls_cipher_set_iv(&ctx, iv_local, LWIP_ARRAYSIZE(iv_local)) != 0) {
+ goto error;
+ }
+
+ for (i = 0; i < length; i++) {
+ u8_t in_byte;
+ u8_t out_byte;
+ size_t out_len = sizeof(out_byte);
+
+ if (snmp_pbuf_stream_read(&read_stream, &in_byte) != ERR_OK) {
+ goto error;
+ }
+ if (mbedtls_cipher_update(&ctx, &in_byte, sizeof(in_byte), &out_byte, &out_len) != 0) {
+ goto error;
+ }
+ if (snmp_pbuf_stream_write(&write_stream, out_byte) != ERR_OK) {
+ goto error;
+ }
+ }
+ } else {
+ return ERR_ARG;
+ }
+
+ mbedtls_cipher_free(&ctx);
+ return ERR_OK;
+
+error:
+ mbedtls_cipher_free(&ctx);
+ return ERR_OK;
+}
+
+#endif /* LWIP_SNMP_V3_CRYPTO */
+
+/* A.2.1. Password to Key Sample Code for MD5 */
+void
+snmpv3_password_to_key_md5(
+ const u8_t *password, /* IN */
+ size_t passwordlen, /* IN */
+ const u8_t *engineID, /* IN - pointer to snmpEngineID */
+ u8_t engineLength,/* IN - length of snmpEngineID */
+ u8_t *key) /* OUT - pointer to caller 16-octet buffer */
+{
+ mbedtls_md5_context MD;
+ u8_t *cp, password_buf[64];
+ u32_t password_index = 0;
+ u8_t i;
+ u32_t count = 0;
+
+ mbedtls_md5_init(&MD); /* initialize MD5 */
+ mbedtls_md5_starts(&MD);
+
+ /**********************************************/
+ /* Use while loop until we've done 1 Megabyte */
+ /**********************************************/
+ while (count < 1048576) {
+ cp = password_buf;
+ for (i = 0; i < 64; i++) {
+ /*************************************************/
+ /* Take the next octet of the password, wrapping */
+ /* to the beginning of the password as necessary.*/
+ /*************************************************/
+ *cp++ = password[password_index++ % passwordlen];
+ }
+ mbedtls_md5_update(&MD, password_buf, 64);
+ count += 64;
+ }
+ mbedtls_md5_finish(&MD, key); /* tell MD5 we're done */
+
+ /*****************************************************/
+ /* Now localize the key with the engineID and pass */
+ /* through MD5 to produce final key */
+ /* May want to ensure that engineLength <= 32, */
+ /* otherwise need to use a buffer larger than 64 */
+ /*****************************************************/
+ SMEMCPY(password_buf, key, 16);
+ MEMCPY(password_buf + 16, engineID, engineLength);
+ SMEMCPY(password_buf + 16 + engineLength, key, 16);
+
+ mbedtls_md5_starts(&MD);
+ mbedtls_md5_update(&MD, password_buf, 32 + engineLength);
+ mbedtls_md5_finish(&MD, key);
+
+ mbedtls_md5_free(&MD);
+ return;
+}
+
+/* A.2.2. Password to Key Sample Code for SHA */
+void
+snmpv3_password_to_key_sha(
+ const u8_t *password, /* IN */
+ size_t passwordlen, /* IN */
+ const u8_t *engineID, /* IN - pointer to snmpEngineID */
+ u8_t engineLength,/* IN - length of snmpEngineID */
+ u8_t *key) /* OUT - pointer to caller 20-octet buffer */
+{
+ mbedtls_sha1_context SH;
+ u8_t *cp, password_buf[72];
+ u32_t password_index = 0;
+ u8_t i;
+ u32_t count = 0;
+
+ mbedtls_sha1_init(&SH); /* initialize SHA */
+ mbedtls_sha1_starts(&SH);
+
+ /**********************************************/
+ /* Use while loop until we've done 1 Megabyte */
+ /**********************************************/
+ while (count < 1048576) {
+ cp = password_buf;
+ for (i = 0; i < 64; i++) {
+ /*************************************************/
+ /* Take the next octet of the password, wrapping */
+ /* to the beginning of the password as necessary.*/
+ /*************************************************/
+ *cp++ = password[password_index++ % passwordlen];
+ }
+ mbedtls_sha1_update(&SH, password_buf, 64);
+ count += 64;
+ }
+ mbedtls_sha1_finish(&SH, key); /* tell SHA we're done */
+
+ /*****************************************************/
+ /* Now localize the key with the engineID and pass */
+ /* through SHA to produce final key */
+ /* May want to ensure that engineLength <= 32, */
+ /* otherwise need to use a buffer larger than 72 */
+ /*****************************************************/
+ SMEMCPY(password_buf, key, 20);
+ MEMCPY(password_buf + 20, engineID, engineLength);
+ SMEMCPY(password_buf + 20 + engineLength, key, 20);
+
+ mbedtls_sha1_starts(&SH);
+ mbedtls_sha1_update(&SH, password_buf, 40 + engineLength);
+ mbedtls_sha1_finish(&SH, key);
+
+ mbedtls_sha1_free(&SH);
+ return;
+}
+
+#endif /* LWIP_SNMP && LWIP_SNMP_V3 && LWIP_SNMP_V3_MBEDTLS */
diff --git a/src/apps/snmp/snmpv3_priv.h b/src/apps/snmp/snmpv3_priv.h
new file mode 100644
index 00000000000..323364c30d5
--- /dev/null
+++ b/src/apps/snmp/snmpv3_priv.h
@@ -0,0 +1,69 @@
+/**
+ * @file
+ * Additional SNMPv3 functionality RFC3414 and RFC3826 (internal API, do not use in client code).
+ */
+
+/*
+ * Copyright (c) 2016 Elias Oenal.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * Author: Elias Oenal <[email protected]>
+ */
+
+#ifndef LWIP_HDR_APPS_SNMP_V3_PRIV_H
+#define LWIP_HDR_APPS_SNMP_V3_PRIV_H
+
+#include "lwip/apps/snmp_opts.h"
+
+#if LWIP_SNMP && LWIP_SNMP_V3
+
+#include "lwip/apps/snmpv3.h"
+#include "snmp_pbuf_stream.h"
+
+/* According to RFC 3411 */
+#define SNMP_V3_MAX_ENGINE_ID_LENGTH 32
+#define SNMP_V3_MAX_USER_LENGTH 32
+
+#define SNMP_V3_MAX_AUTH_PARAM_LENGTH 12
+#define SNMP_V3_MAX_PRIV_PARAM_LENGTH 8
+
+#define SNMP_V3_MD5_LEN 16
+#define SNMP_V3_SHA_LEN 20
+
+typedef enum {
+ SNMP_V3_PRIV_MODE_DECRYPT = 0,
+ SNMP_V3_PRIV_MODE_ENCRYPT = 1
+} snmpv3_priv_mode_t;
+
+s32_t snmpv3_get_engine_boots_internal(void);
+err_t snmpv3_auth(struct snmp_pbuf_stream *stream, u16_t length, const u8_t *key, snmpv3_auth_algo_t algo, u8_t *hmac_out);
+err_t snmpv3_crypt(struct snmp_pbuf_stream *stream, u16_t length, const u8_t *key,
+ const u8_t *priv_param, const u32_t engine_boots, const u32_t engine_time, snmpv3_priv_algo_t algo, snmpv3_priv_mode_t mode);
+err_t snmpv3_build_priv_param(u8_t *priv_param);
+void snmpv3_enginetime_timer(void *arg);
+
+#endif
+
+#endif /* LWIP_HDR_APPS_SNMP_V3_PRIV_H */
diff --git a/src/apps/sntp/sntp.c b/src/apps/sntp/sntp.c
new file mode 100644
index 00000000000..0e7f36520fa
--- /dev/null
+++ b/src/apps/sntp/sntp.c
@@ -0,0 +1,948 @@
+/**
+ * @file
+ * SNTP client module
+ */
+
+/*
+ * Copyright (c) 2007-2009 Frédéric Bernon, Simon Goldschmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Frédéric Bernon, Simon Goldschmidt
+ */
+
+
+/**
+ * @defgroup sntp SNTP
+ * @ingroup apps
+ *
+ * This is simple "SNTP" client for the lwIP raw API.
+ * It is a minimal implementation of SNTPv4 as specified in RFC 4330.
+ *
+ * You need to increase MEMP_NUM_SYS_TIMEOUT by one if you use SNTP!
+ *
+ * For a list of some public NTP servers, see this link:
+ * http://support.ntp.org/bin/view/Servers/NTPPoolServers
+ *
+ * @todo:
+ * - complete SNTP_CHECK_RESPONSE checks 3 and 4
+ */
+
+#include "lwip/apps/sntp.h"
+
+#include "lwip/opt.h"
+#include "lwip/timeouts.h"
+#include "lwip/udp.h"
+#include "lwip/dns.h"
+#include "lwip/ip_addr.h"
+#include "lwip/pbuf.h"
+#include "lwip/dhcp.h"
+
+#include <string.h>
+#include <time.h>
+
+#if LWIP_UDP
+
+/* Handle support for more than one server via SNTP_MAX_SERVERS */
+#if SNTP_MAX_SERVERS > 1
+#define SNTP_SUPPORT_MULTIPLE_SERVERS 1
+#else /* NTP_MAX_SERVERS > 1 */
+#define SNTP_SUPPORT_MULTIPLE_SERVERS 0
+#endif /* NTP_MAX_SERVERS > 1 */
+
+#ifndef SNTP_SUPPRESS_DELAY_CHECK
+#if SNTP_UPDATE_DELAY < 15000
+#error "SNTPv4 RFC 4330 enforces a minimum update time of 15 seconds (define SNTP_SUPPRESS_DELAY_CHECK to disable this error)!"
+#endif
+#endif
+
+/* the various debug levels for this file */
+#define SNTP_DEBUG_TRACE (SNTP_DEBUG | LWIP_DBG_TRACE)
+#define SNTP_DEBUG_STATE (SNTP_DEBUG | LWIP_DBG_STATE)
+#define SNTP_DEBUG_WARN (SNTP_DEBUG | LWIP_DBG_LEVEL_WARNING)
+#define SNTP_DEBUG_WARN_STATE (SNTP_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
+#define SNTP_DEBUG_SERIOUS (SNTP_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
+
+#define SNTP_ERR_KOD 1
+
+/* SNTP protocol defines */
+#define SNTP_MSG_LEN 48
+
+#define SNTP_OFFSET_LI_VN_MODE 0
+#define SNTP_LI_MASK 0xC0
+#define SNTP_LI_NO_WARNING (0x00 << 6)
+#define SNTP_LI_LAST_MINUTE_61_SEC (0x01 << 6)
+#define SNTP_LI_LAST_MINUTE_59_SEC (0x02 << 6)
+#define SNTP_LI_ALARM_CONDITION (0x03 << 6) /* (clock not synchronized) */
+
+#define SNTP_VERSION_MASK 0x38
+#define SNTP_VERSION (4/* NTP Version 4*/<<3)
+
+#define SNTP_MODE_MASK 0x07
+#define SNTP_MODE_CLIENT 0x03
+#define SNTP_MODE_SERVER 0x04
+#define SNTP_MODE_BROADCAST 0x05
+
+#define SNTP_OFFSET_STRATUM 1
+#define SNTP_STRATUM_KOD 0x00
+
+#define SNTP_OFFSET_ORIGINATE_TIME 24
+#define SNTP_OFFSET_RECEIVE_TIME 32
+#define SNTP_OFFSET_TRANSMIT_TIME 40
+
+/* Number of seconds between 1970 and Feb 7, 2036 06:28:16 UTC (epoch 1) */
+#define DIFF_SEC_1970_2036 ((u32_t)2085978496L)
+
+/** Convert NTP timestamp fraction to microseconds.
+ */
+#ifndef SNTP_FRAC_TO_US
+# if LWIP_HAVE_INT64
+# define SNTP_FRAC_TO_US(f) ((u32_t)(((u64_t)(f) * 1000000UL) >> 32))
+# else
+# define SNTP_FRAC_TO_US(f) ((u32_t)(f) / 4295)
+# endif
+#endif /* !SNTP_FRAC_TO_US */
+
+/* Configure behaviour depending on native, microsecond or second precision.
+ * Treat NTP timestamps as signed two's-complement integers. This way,
+ * timestamps that have the MSB set simply become negative offsets from
+ * the epoch (Feb 7, 2036 06:28:16 UTC). Representable dates range from
+ * 1968 to 2104.
+ */
+#ifndef SNTP_SET_SYSTEM_TIME_NTP
+# ifdef SNTP_SET_SYSTEM_TIME_US
+# define SNTP_SET_SYSTEM_TIME_NTP(s, f) \
+ SNTP_SET_SYSTEM_TIME_US((u32_t)((s) + DIFF_SEC_1970_2036), SNTP_FRAC_TO_US(f))
+# else
+# define SNTP_SET_SYSTEM_TIME_NTP(s, f) \
+ SNTP_SET_SYSTEM_TIME((u32_t)((s) + DIFF_SEC_1970_2036))
+# endif
+#endif /* !SNTP_SET_SYSTEM_TIME_NTP */
+
+/* Get the system time either natively as NTP timestamp or convert from
+ * Unix time in seconds and microseconds. Take care to avoid overflow if the
+ * microsecond value is at the maximum of 999999. Also add 0.5 us fudge to
+ * avoid special values like 0, and to mask round-off errors that would
+ * otherwise break round-trip conversion identity.
+ */
+#ifndef SNTP_GET_SYSTEM_TIME_NTP
+# define SNTP_GET_SYSTEM_TIME_NTP(s, f) do { \
+ u32_t sec_, usec_; \
+ SNTP_GET_SYSTEM_TIME(sec_, usec_); \
+ (s) = (s32_t)(sec_ - DIFF_SEC_1970_2036); \
+ (f) = usec_ * 4295 - ((usec_ * 2143) >> 16) + 2147; \
+ } while (0)
+#endif /* !SNTP_GET_SYSTEM_TIME_NTP */
+
+/* Start offset of the timestamps to extract from the SNTP packet */
+#define SNTP_OFFSET_TIMESTAMPS \
+ (SNTP_OFFSET_TRANSMIT_TIME + 8 - sizeof(struct sntp_timestamps))
+
+/* Round-trip delay arithmetic helpers */
+#if SNTP_COMP_ROUNDTRIP
+# if !LWIP_HAVE_INT64
+# error "SNTP round-trip delay compensation requires 64-bit arithmetic"
+# endif
+# define SNTP_SEC_FRAC_TO_S64(s, f) \
+ ((s64_t)(((u64_t)(s) << 32) | (u32_t)(f)))
+# define SNTP_TIMESTAMP_TO_S64(t) \
+ SNTP_SEC_FRAC_TO_S64(lwip_ntohl((t).sec), lwip_ntohl((t).frac))
+#endif /* SNTP_COMP_ROUNDTRIP */
+
+/**
+ * 64-bit NTP timestamp, in network byte order.
+ */
+struct sntp_time {
+ u32_t sec;
+ u32_t frac;
+};
+
+/**
+ * Timestamps to be extracted from the NTP header.
+ */
+struct sntp_timestamps {
+#if SNTP_COMP_ROUNDTRIP || SNTP_CHECK_RESPONSE >= 2
+ struct sntp_time orig;
+ struct sntp_time recv;
+#endif
+ struct sntp_time xmit;
+};
+
+/**
+ * SNTP packet format (without optional fields)
+ * Timestamps are coded as 64 bits:
+ * - signed 32 bits seconds since Feb 07, 2036, 06:28:16 UTC (epoch 1)
+ * - unsigned 32 bits seconds fraction (2^32 = 1 second)
+ */
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/bpstruct.h"
+#endif
+PACK_STRUCT_BEGIN
+struct sntp_msg {
+ PACK_STRUCT_FLD_8(u8_t li_vn_mode);
+ PACK_STRUCT_FLD_8(u8_t stratum);
+ PACK_STRUCT_FLD_8(u8_t poll);
+ PACK_STRUCT_FLD_8(u8_t precision);
+ PACK_STRUCT_FIELD(u32_t root_delay);
+ PACK_STRUCT_FIELD(u32_t root_dispersion);
+ PACK_STRUCT_FIELD(u32_t reference_identifier);
+ PACK_STRUCT_FIELD(u32_t reference_timestamp[2]);
+ PACK_STRUCT_FIELD(u32_t originate_timestamp[2]);
+ PACK_STRUCT_FIELD(u32_t receive_timestamp[2]);
+ PACK_STRUCT_FIELD(u32_t transmit_timestamp[2]);
+} PACK_STRUCT_STRUCT;
+PACK_STRUCT_END
+#ifdef PACK_STRUCT_USE_INCLUDES
+# include "arch/epstruct.h"
+#endif
+
+/* function prototypes */
+static void sntp_request(void *arg);
+
+/** The operating mode */
+static u8_t sntp_opmode;
+
+/** The UDP pcb used by the SNTP client */
+static struct udp_pcb *sntp_pcb;
+/** Names/Addresses of servers */
+struct sntp_server {
+#if SNTP_SERVER_DNS
+ const char *name;
+#endif /* SNTP_SERVER_DNS */
+ ip_addr_t addr;
+#if SNTP_MONITOR_SERVER_REACHABILITY
+ /** Reachability shift register as described in RFC 5905 */
+ u8_t reachability;
+#endif /* SNTP_MONITOR_SERVER_REACHABILITY */
+#if SNTP_SUPPORT_MULTIPLE_SERVERS
+ u8_t kod_received;
+#endif
+};
+static struct sntp_server sntp_servers[SNTP_MAX_SERVERS];
+
+#if SNTP_GET_SERVERS_FROM_DHCP || SNTP_GET_SERVERS_FROM_DHCPV6
+static u8_t sntp_set_servers_from_dhcp;
+#endif /* SNTP_GET_SERVERS_FROM_DHCP || SNTP_GET_SERVERS_FROM_DHCPV6 */
+#if SNTP_SUPPORT_MULTIPLE_SERVERS
+/** The currently used server (initialized to 0) */
+static u8_t sntp_current_server;
+#else /* SNTP_SUPPORT_MULTIPLE_SERVERS */
+#define sntp_current_server 0
+#endif /* SNTP_SUPPORT_MULTIPLE_SERVERS */
+
+#if SNTP_RETRY_TIMEOUT_EXP
+#define SNTP_RESET_RETRY_TIMEOUT() sntp_retry_timeout = SNTP_RETRY_TIMEOUT
+/** Retry time, initialized with SNTP_RETRY_TIMEOUT and doubled with each retry. */
+static u32_t sntp_retry_timeout;
+#else /* SNTP_RETRY_TIMEOUT_EXP */
+#define SNTP_RESET_RETRY_TIMEOUT()
+#define sntp_retry_timeout SNTP_RETRY_TIMEOUT
+#endif /* SNTP_RETRY_TIMEOUT_EXP */
+
+#if SNTP_CHECK_RESPONSE >= 1
+/** Saves the last server address to compare with response */
+static ip_addr_t sntp_last_server_address;
+#endif /* SNTP_CHECK_RESPONSE >= 1 */
+
+#if SNTP_CHECK_RESPONSE >= 2
+/** Saves the last timestamp sent (which is sent back by the server)
+ * to compare against in response. Stored in network byte order. */
+static struct sntp_time sntp_last_timestamp_sent;
+#endif /* SNTP_CHECK_RESPONSE >= 2 */
+
+#if defined(LWIP_DEBUG) && !defined(sntp_format_time)
+/* Debug print helper. */
+static const char *
+sntp_format_time(s32_t sec)
+{
+ time_t ut;
+ ut = (u32_t)((u32_t)sec + DIFF_SEC_1970_2036);
+ return ctime(&ut);
+}
+#endif /* LWIP_DEBUG && !sntp_format_time */
+
+/**
+ * SNTP processing of received timestamp
+ */
+static void
+sntp_process(const struct sntp_timestamps *timestamps)
+{
+ s32_t sec;
+ u32_t frac;
+
+ sec = (s32_t)lwip_ntohl(timestamps->xmit.sec);
+ frac = lwip_ntohl(timestamps->xmit.frac);
+
+#if SNTP_COMP_ROUNDTRIP
+# if SNTP_CHECK_RESPONSE >= 2
+ if (timestamps->recv.sec != 0 || timestamps->recv.frac != 0)
+# endif
+ {
+ s32_t dest_sec;
+ u32_t dest_frac;
+ u32_t step_sec;
+
+ /* Get the destination time stamp, i.e. the current system time */
+ SNTP_GET_SYSTEM_TIME_NTP(dest_sec, dest_frac);
+
+ step_sec = (dest_sec < sec) ? ((u32_t)sec - (u32_t)dest_sec)
+ : ((u32_t)dest_sec - (u32_t)sec);
+ /* In order to avoid overflows, skip the compensation if the clock step
+ * is larger than about 34 years. */
+ if ((step_sec >> 30) == 0) {
+ s64_t t1, t2, t3, t4;
+
+ t4 = SNTP_SEC_FRAC_TO_S64(dest_sec, dest_frac);
+ t3 = SNTP_SEC_FRAC_TO_S64(sec, frac);
+ t1 = SNTP_TIMESTAMP_TO_S64(timestamps->orig);
+ t2 = SNTP_TIMESTAMP_TO_S64(timestamps->recv);
+ /* Clock offset calculation according to RFC 4330 */
+ t4 += ((t2 - t1) + (t3 - t4)) / 2;
+
+ sec = (s32_t)((u64_t)t4 >> 32);
+ frac = (u32_t)((u64_t)t4);
+ }
+ }
+#endif /* SNTP_COMP_ROUNDTRIP */
+
+ SNTP_SET_SYSTEM_TIME_NTP(sec, frac);
+ LWIP_UNUSED_ARG(frac); /* might be unused if only seconds are set */
+ LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_process: %s, %" U32_F " us\n",
+ sntp_format_time(sec), SNTP_FRAC_TO_US(frac)));
+}
+
+/**
+ * Initialize request struct to be sent to server.
+ */
+static void
+sntp_initialize_request(struct sntp_msg *req)
+{
+ memset(req, 0, SNTP_MSG_LEN);
+ req->li_vn_mode = SNTP_LI_NO_WARNING | SNTP_VERSION | SNTP_MODE_CLIENT;
+
+#if SNTP_CHECK_RESPONSE >= 2 || SNTP_COMP_ROUNDTRIP
+ {
+ s32_t secs;
+ u32_t sec, frac;
+ /* Get the transmit timestamp */
+ SNTP_GET_SYSTEM_TIME_NTP(secs, frac);
+ sec = lwip_htonl((u32_t)secs);
+ frac = lwip_htonl(frac);
+
+# if SNTP_CHECK_RESPONSE >= 2
+ sntp_last_timestamp_sent.sec = sec;
+ sntp_last_timestamp_sent.frac = frac;
+# endif
+ req->transmit_timestamp[0] = sec;
+ req->transmit_timestamp[1] = frac;
+ }
+#endif /* SNTP_CHECK_RESPONSE >= 2 || SNTP_COMP_ROUNDTRIP */
+}
+
+/**
+ * Retry: send a new request (and increase retry timeout).
+ *
+ * @param arg is unused (only necessary to conform to sys_timeout)
+ */
+static void
+sntp_retry(void *arg)
+{
+ LWIP_UNUSED_ARG(arg);
+
+ LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_retry: Next request will be sent in %"U32_F" ms\n",
+ sntp_retry_timeout));
+
+ /* set up a timer to send a retry and increase the retry delay */
+ sys_untimeout(sntp_request, NULL);
+ sys_timeout(sntp_retry_timeout, sntp_request, NULL);
+
+#if SNTP_RETRY_TIMEOUT_EXP
+ {
+ u32_t new_retry_timeout;
+ /* increase the timeout for next retry */
+ new_retry_timeout = sntp_retry_timeout << 1;
+ /* limit to maximum timeout and prevent overflow */
+ if ((new_retry_timeout <= SNTP_RETRY_TIMEOUT_MAX) &&
+ (new_retry_timeout > sntp_retry_timeout)) {
+ sntp_retry_timeout = new_retry_timeout;
+ } else {
+ sntp_retry_timeout = SNTP_RETRY_TIMEOUT_MAX;
+ }
+ }
+#endif /* SNTP_RETRY_TIMEOUT_EXP */
+}
+
+#if SNTP_SUPPORT_MULTIPLE_SERVERS
+/**
+ * If Kiss-of-Death is received (or another packet parsing error),
+ * try the next server or retry the current server and increase the retry
+ * timeout if only one server is available.
+ * (implicitly, SNTP_MAX_SERVERS > 1)
+ *
+ * @param arg is unused (only necessary to conform to sys_timeout)
+ */
+static void
+sntp_try_next_server(void *arg)
+{
+ u8_t old_server, i;
+ LWIP_UNUSED_ARG(arg);
+
+ old_server = sntp_current_server;
+ for (i = 0; i < SNTP_MAX_SERVERS - 1; i++) {
+ sntp_current_server++;
+ if (sntp_current_server >= SNTP_MAX_SERVERS) {
+ sntp_current_server = 0;
+ }
+ if (sntp_servers[sntp_current_server].kod_received) {
+ /* KOD received, don't use this server */
+ continue;
+ }
+ if (!ip_addr_isany(&sntp_servers[sntp_current_server].addr)
+#if SNTP_SERVER_DNS
+ || (sntp_servers[sntp_current_server].name != NULL)
+#endif
+ ) {
+ LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_try_next_server: Sending request to server %"U16_F"\n",
+ (u16_t)sntp_current_server));
+ /* new server: reset retry timeout */
+ SNTP_RESET_RETRY_TIMEOUT();
+ /* instantly send a request to the next server */
+ sntp_request(NULL);
+ return;
+ }
+ }
+ /* no other valid server found */
+ sntp_current_server = old_server;
+ sntp_retry(NULL);
+}
+
+static void
+sntp_kod_try_next_server(void *arg)
+{
+ sntp_servers[sntp_current_server].kod_received = 1;
+ sntp_try_next_server(arg);
+}
+
+#else /* SNTP_SUPPORT_MULTIPLE_SERVERS */
+/* Always retry on error if only one server is supported */
+#define sntp_try_next_server sntp_retry
+#define sntp_kod_try_next_server sntp_retry
+#endif /* SNTP_SUPPORT_MULTIPLE_SERVERS */
+
+/** UDP recv callback for the sntp pcb */
+static void
+sntp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+ struct sntp_timestamps timestamps;
+ u8_t mode;
+ u8_t stratum;
+ err_t err;
+
+ LWIP_UNUSED_ARG(arg);
+ LWIP_UNUSED_ARG(pcb);
+
+ err = ERR_ARG;
+#if SNTP_CHECK_RESPONSE >= 1
+ /* check server address and port */
+ if (((sntp_opmode != SNTP_OPMODE_POLL) || ip_addr_eq(addr, &sntp_last_server_address)) &&
+ (port == SNTP_PORT))
+#else /* SNTP_CHECK_RESPONSE >= 1 */
+ LWIP_UNUSED_ARG(addr);
+ LWIP_UNUSED_ARG(port);
+#endif /* SNTP_CHECK_RESPONSE >= 1 */
+ {
+ /* process the response */
+ if (p->tot_len == SNTP_MSG_LEN) {
+ mode = pbuf_get_at(p, SNTP_OFFSET_LI_VN_MODE) & SNTP_MODE_MASK;
+ /* if this is a SNTP response... */
+ if (((sntp_opmode == SNTP_OPMODE_POLL) && (mode == SNTP_MODE_SERVER)) ||
+ ((sntp_opmode == SNTP_OPMODE_LISTENONLY) && (mode == SNTP_MODE_BROADCAST))) {
+ stratum = pbuf_get_at(p, SNTP_OFFSET_STRATUM);
+
+ if (stratum == SNTP_STRATUM_KOD) {
+ /* Kiss-of-death packet. Use another server or increase UPDATE_DELAY. */
+ err = SNTP_ERR_KOD;
+ LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_recv: Received Kiss-of-Death\n"));
+ } else {
+ pbuf_copy_partial(p, &timestamps, sizeof(timestamps), SNTP_OFFSET_TIMESTAMPS);
+#if SNTP_CHECK_RESPONSE >= 2
+ /* check originate_timetamp against sntp_last_timestamp_sent */
+ if (timestamps.orig.sec != sntp_last_timestamp_sent.sec ||
+ timestamps.orig.frac != sntp_last_timestamp_sent.frac) {
+ LWIP_DEBUGF(SNTP_DEBUG_WARN,
+ ("sntp_recv: Invalid originate timestamp in response\n"));
+ } else
+#endif /* SNTP_CHECK_RESPONSE >= 2 */
+ /* @todo: add code for SNTP_CHECK_RESPONSE >= 3 and >= 4 here */
+ {
+ /* correct answer */
+ err = ERR_OK;
+ }
+ }
+ } else {
+ LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid mode in response: %"U16_F"\n", (u16_t)mode));
+ /* wait for correct response */
+ err = ERR_TIMEOUT;
+ }
+ } else {
+ LWIP_DEBUGF(SNTP_DEBUG_WARN, ("sntp_recv: Invalid packet length: %"U16_F"\n", p->tot_len));
+ }
+ }
+#if SNTP_CHECK_RESPONSE >= 1
+ else {
+ /* packet from wrong remote address or port, wait for correct response */
+ err = ERR_TIMEOUT;
+ }
+#endif /* SNTP_CHECK_RESPONSE >= 1 */
+
+ pbuf_free(p);
+
+ if (err == ERR_OK) {
+ /* correct packet received: process it it */
+ sntp_process(&timestamps);
+
+#if SNTP_MONITOR_SERVER_REACHABILITY
+ /* indicate that server responded */
+ sntp_servers[sntp_current_server].reachability |= 1;
+#endif /* SNTP_MONITOR_SERVER_REACHABILITY */
+ /* Set up timeout for next request (only if poll response was received)*/
+ if (sntp_opmode == SNTP_OPMODE_POLL) {
+ u32_t sntp_update_delay;
+ sys_untimeout(sntp_try_next_server, NULL);
+ sys_untimeout(sntp_request, NULL);
+
+ /* Correct response, reset retry timeout */
+ SNTP_RESET_RETRY_TIMEOUT();
+
+ sntp_update_delay = (u32_t)SNTP_UPDATE_DELAY;
+ sys_timeout(sntp_update_delay, sntp_request, NULL);
+ LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_recv: Scheduled next time request: %"U32_F" ms\n",
+ sntp_update_delay));
+ }
+ } else if (err == SNTP_ERR_KOD) {
+ /* KOD errors are only processed in case of an explicit poll response */
+ if (sntp_opmode == SNTP_OPMODE_POLL) {
+ /* Kiss-of-death packet. Use another server or increase UPDATE_DELAY. */
+ sntp_kod_try_next_server(NULL);
+ }
+ } else {
+ /* ignore any broken packet, poll mode: retry after timeout to avoid flooding */
+ }
+}
+
+/** Actually send an sntp request to a server.
+ *
+ * @param server_addr resolved IP address of the SNTP server
+ */
+static void
+sntp_send_request(const ip_addr_t *server_addr)
+{
+ struct pbuf *p;
+
+ LWIP_ASSERT("server_addr != NULL", server_addr != NULL);
+
+ p = pbuf_alloc(PBUF_TRANSPORT, SNTP_MSG_LEN, PBUF_RAM);
+ if (p != NULL) {
+ struct sntp_msg *sntpmsg = (struct sntp_msg *)p->payload;
+ LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_send_request: Sending request to server\n"));
+ /* initialize request message */
+ sntp_initialize_request(sntpmsg);
+ /* send request */
+ udp_sendto(sntp_pcb, p, server_addr, SNTP_PORT);
+ /* free the pbuf after sending it */
+ pbuf_free(p);
+#if SNTP_MONITOR_SERVER_REACHABILITY
+ /* indicate new packet has been sent */
+ sntp_servers[sntp_current_server].reachability <<= 1;
+#endif /* SNTP_MONITOR_SERVER_REACHABILITY */
+ /* set up receive timeout: try next server or retry on timeout */
+ sys_untimeout(sntp_try_next_server, NULL);
+ sys_timeout((u32_t)SNTP_RECV_TIMEOUT, sntp_try_next_server, NULL);
+#if SNTP_CHECK_RESPONSE >= 1
+ /* save server address to verify it in sntp_recv */
+ ip_addr_copy(sntp_last_server_address, *server_addr);
+#endif /* SNTP_CHECK_RESPONSE >= 1 */
+ } else {
+ LWIP_DEBUGF(SNTP_DEBUG_SERIOUS, ("sntp_send_request: Out of memory, trying again in %"U32_F" ms\n",
+ (u32_t)SNTP_RETRY_TIMEOUT));
+ /* out of memory: set up a timer to send a retry */
+ sys_untimeout(sntp_request, NULL);
+ sys_timeout((u32_t)SNTP_RETRY_TIMEOUT, sntp_request, NULL);
+ }
+}
+
+#if SNTP_SERVER_DNS
+/**
+ * DNS found callback when using DNS names as server address.
+ */
+static void
+sntp_dns_found(const char *hostname, const ip_addr_t *ipaddr, void *arg)
+{
+ LWIP_UNUSED_ARG(hostname);
+ LWIP_UNUSED_ARG(arg);
+
+ if (ipaddr != NULL) {
+ /* Address resolved, send request */
+ LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_dns_found: Server address resolved, sending request\n"));
+ sntp_servers[sntp_current_server].addr = *ipaddr;
+ sntp_send_request(ipaddr);
+ } else {
+ /* DNS resolving failed -> try another server */
+ LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_dns_found: Failed to resolve server address resolved, trying next server\n"));
+ sntp_try_next_server(NULL);
+ }
+}
+#endif /* SNTP_SERVER_DNS */
+
+/**
+ * Send out an sntp request.
+ *
+ * @param arg is unused (only necessary to conform to sys_timeout)
+ */
+static void
+sntp_request(void *arg)
+{
+ ip_addr_t sntp_server_address;
+ err_t err;
+
+ LWIP_UNUSED_ARG(arg);
+
+ /* initialize SNTP server address */
+#if SNTP_SERVER_DNS
+ if (sntp_servers[sntp_current_server].name) {
+ /* always resolve the name and rely on dns-internal caching & timeout */
+ ip_addr_set_zero(&sntp_servers[sntp_current_server].addr);
+ err = dns_gethostbyname(sntp_servers[sntp_current_server].name, &sntp_server_address,
+ sntp_dns_found, NULL);
+ if (err == ERR_INPROGRESS) {
+ /* DNS request sent, wait for sntp_dns_found being called */
+ LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_request: Waiting for server address to be resolved.\n"));
+ return;
+ } else if (err == ERR_OK) {
+ sntp_servers[sntp_current_server].addr = sntp_server_address;
+ }
+ } else
+#endif /* SNTP_SERVER_DNS */
+ {
+ sntp_server_address = sntp_servers[sntp_current_server].addr;
+ err = (ip_addr_isany_val(sntp_server_address)) ? ERR_ARG : ERR_OK;
+ }
+
+ if (err == ERR_OK) {
+ LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_request: current server address is %s\n",
+ ipaddr_ntoa(&sntp_server_address)));
+ sntp_send_request(&sntp_server_address);
+ } else {
+ /* address conversion failed, try another server */
+ LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_request: Invalid server address, trying next server.\n"));
+ sys_untimeout(sntp_try_next_server, NULL);
+ sys_timeout((u32_t)SNTP_RETRY_TIMEOUT, sntp_try_next_server, NULL);
+ }
+}
+
+/**
+ * @ingroup sntp
+ * Initialize this module.
+ * Send out request instantly or after SNTP_STARTUP_DELAY(_FUNC).
+ */
+void
+sntp_init(void)
+{
+ /* LWIP_ASSERT_CORE_LOCKED(); is checked by udp_new() */
+ LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_init: SNTP initialised\n"));
+
+#ifdef SNTP_SERVER_ADDRESS
+#if SNTP_SERVER_DNS
+ sntp_setservername(0, SNTP_SERVER_ADDRESS);
+#else
+#error SNTP_SERVER_ADDRESS string not supported SNTP_SERVER_DNS==0
+#endif
+#endif /* SNTP_SERVER_ADDRESS */
+
+ if (sntp_pcb == NULL) {
+ sntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
+ LWIP_ASSERT("Failed to allocate udp pcb for sntp client", sntp_pcb != NULL);
+ if (sntp_pcb != NULL) {
+ udp_recv(sntp_pcb, sntp_recv, NULL);
+
+ if (sntp_opmode == SNTP_OPMODE_POLL) {
+ SNTP_RESET_RETRY_TIMEOUT();
+#if SNTP_STARTUP_DELAY
+ sys_timeout((u32_t)SNTP_STARTUP_DELAY_FUNC, sntp_request, NULL);
+#else
+ sntp_request(NULL);
+#endif
+ } else if (sntp_opmode == SNTP_OPMODE_LISTENONLY) {
+ ip_set_option(sntp_pcb, SOF_BROADCAST);
+ udp_bind(sntp_pcb, IP_ANY_TYPE, SNTP_PORT);
+ }
+ }
+ }
+}
+
+/**
+ * @ingroup sntp
+ * Stop this module.
+ */
+void
+sntp_stop(void)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ if (sntp_pcb != NULL) {
+#if SNTP_MONITOR_SERVER_REACHABILITY
+ u8_t i;
+ for (i = 0; i < SNTP_MAX_SERVERS; i++) {
+ sntp_servers[i].reachability = 0;
+ }
+#endif /* SNTP_MONITOR_SERVER_REACHABILITY */
+ sys_untimeout(sntp_request, NULL);
+ sys_untimeout(sntp_try_next_server, NULL);
+ udp_remove(sntp_pcb);
+ sntp_pcb = NULL;
+ }
+}
+
+/**
+ * @ingroup sntp
+ * Get enabled state.
+ */
+u8_t sntp_enabled(void)
+{
+ return (sntp_pcb != NULL) ? 1 : 0;
+}
+
+/**
+ * @ingroup sntp
+ * Sets the operating mode.
+ * @param operating_mode one of the available operating modes
+ */
+void
+sntp_setoperatingmode(u8_t operating_mode)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ LWIP_ASSERT("Invalid operating mode", operating_mode <= SNTP_OPMODE_LISTENONLY);
+ LWIP_ASSERT("Operating mode must not be set while SNTP client is running", sntp_pcb == NULL);
+ sntp_opmode = operating_mode;
+}
+
+/**
+ * @ingroup sntp
+ * Gets the operating mode.
+ */
+u8_t
+sntp_getoperatingmode(void)
+{
+ return sntp_opmode;
+}
+
+#if SNTP_MONITOR_SERVER_REACHABILITY
+/**
+ * @ingroup sntp
+ * Gets the server reachability shift register as described in RFC 5905.
+ *
+ * @param idx the index of the NTP server
+ */
+u8_t
+sntp_getreachability(u8_t idx)
+{
+ if (idx < SNTP_MAX_SERVERS) {
+ return sntp_servers[idx].reachability;
+ }
+ return 0;
+}
+#endif /* SNTP_MONITOR_SERVER_REACHABILITY */
+
+#if SNTP_GET_SERVERS_FROM_DHCP || SNTP_GET_SERVERS_FROM_DHCPV6
+/**
+ * Config SNTP server handling by IP address, name, or DHCP; clear table
+ * @param set_servers_from_dhcp enable or disable getting server addresses from dhcp
+ */
+void
+sntp_servermode_dhcp(int set_servers_from_dhcp)
+{
+ u8_t new_mode = set_servers_from_dhcp ? 1 : 0;
+ LWIP_ASSERT_CORE_LOCKED();
+ if (sntp_set_servers_from_dhcp != new_mode) {
+ sntp_set_servers_from_dhcp = new_mode;
+ }
+}
+#endif /* SNTP_GET_SERVERS_FROM_DHCP || SNTP_GET_SERVERS_FROM_DHCPV6 */
+
+/**
+ * @ingroup sntp
+ * Initialize one of the NTP servers by IP address
+ *
+ * @param idx the index of the NTP server to set must be < SNTP_MAX_SERVERS
+ * @param server IP address of the NTP server to set
+ */
+void
+sntp_setserver(u8_t idx, const ip_addr_t *server)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ if (idx < SNTP_MAX_SERVERS) {
+ if (server != NULL) {
+ sntp_servers[idx].addr = (*server);
+#if SNTP_SUPPORT_MULTIPLE_SERVERS
+ sntp_servers[idx].kod_received = 0;
+#endif
+ } else {
+ ip_addr_set_zero(&sntp_servers[idx].addr);
+ }
+#if SNTP_SERVER_DNS
+ sntp_servers[idx].name = NULL;
+#endif
+ }
+}
+
+#if LWIP_DHCP && SNTP_GET_SERVERS_FROM_DHCP
+/**
+ * Initialize one of the NTP servers by IP address, required by DHCP
+ *
+ * @param num the index of the NTP server to set must be < SNTP_MAX_SERVERS
+ * @param server IP address of the NTP server to set
+ */
+void
+dhcp_set_ntp_servers(u8_t num, const ip4_addr_t *server)
+{
+ LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp: %s %u.%u.%u.%u as NTP server #%u via DHCP\n",
+ (sntp_set_servers_from_dhcp ? "Got" : "Rejected"),
+ ip4_addr1(server), ip4_addr2(server), ip4_addr3(server), ip4_addr4(server), num));
+ if (sntp_set_servers_from_dhcp && num) {
+ u8_t i;
+ for (i = 0; (i < num) && (i < SNTP_MAX_SERVERS); i++) {
+ ip_addr_t addr;
+ ip_addr_copy_from_ip4(addr, server[i]);
+ sntp_setserver(i, &addr);
+ }
+ for (i = num; i < SNTP_MAX_SERVERS; i++) {
+ sntp_setserver(i, NULL);
+ }
+ }
+}
+#endif /* LWIP_DHCP && SNTP_GET_SERVERS_FROM_DHCP */
+
+#if LWIP_IPV6_DHCP6 && SNTP_GET_SERVERS_FROM_DHCPV6
+/**
+ * Initialize one of the NTP servers by IP address, required by DHCPV6
+ *
+ * @param num the number of NTP server addresses to set must be < SNTP_MAX_SERVERS
+ * @param server array of IP address of the NTP servers to set
+ */
+void
+dhcp6_set_ntp_servers(u8_t num_ntp_servers, ip_addr_t* ntp_server_addrs)
+{
+ LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp: %s %u NTP server(s) via DHCPv6\n",
+ (sntp_set_servers_from_dhcp ? "Got" : "Rejected"),
+ num_ntp_servers));
+ if (sntp_set_servers_from_dhcp && num_ntp_servers) {
+ u8_t i;
+ for (i = 0; (i < num_ntp_servers) && (i < SNTP_MAX_SERVERS); i++) {
+ LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp: NTP server %u: %s\n",
+ i, ipaddr_ntoa(&ntp_server_addrs[i])));
+ sntp_setserver(i, &ntp_server_addrs[i]);
+ }
+ for (i = num_ntp_servers; i < SNTP_MAX_SERVERS; i++) {
+ sntp_setserver(i, NULL);
+ }
+ }
+}
+#endif /* LWIP_DHCPv6 && SNTP_GET_SERVERS_FROM_DHCPV6 */
+
+/**
+ * @ingroup sntp
+ * Obtain one of the currently configured by IP address (or DHCP) NTP servers
+ *
+ * @param idx the index of the NTP server
+ * @return IP address of the indexed NTP server or "ip_addr_any" if the NTP
+ * server has not been configured by address (or at all).
+ */
+const ip_addr_t *
+sntp_getserver(u8_t idx)
+{
+ if (idx < SNTP_MAX_SERVERS) {
+ return &sntp_servers[idx].addr;
+ }
+ return IP_ADDR_ANY;
+}
+
+/**
+ * @ingroup sntp
+ * Check if a Kiss-of-Death has been received from this server (only valid for
+ * SNTP_MAX_SERVERS > 1).
+ *
+ * @param idx the index of the NTP server
+ * @return 1 if a KoD has been received, 0 if not.
+ */
+u8_t
+sntp_getkodreceived(u8_t idx)
+{
+#if SNTP_SUPPORT_MULTIPLE_SERVERS
+ if (idx < SNTP_MAX_SERVERS) {
+ return sntp_servers[idx].kod_received;
+ }
+#else
+ LWIP_UNUSED_ARG(idx);
+#endif
+ return 0;
+}
+
+#if SNTP_SERVER_DNS
+/**
+ * Initialize one of the NTP servers by name
+ *
+ * @param idx the index of the NTP server to set must be < SNTP_MAX_SERVERS
+ * @param server DNS name of the NTP server to set, to be resolved at contact time
+ */
+void
+sntp_setservername(u8_t idx, const char *server)
+{
+ LWIP_ASSERT_CORE_LOCKED();
+ if (idx < SNTP_MAX_SERVERS) {
+ sntp_servers[idx].name = server;
+#if SNTP_SUPPORT_MULTIPLE_SERVERS
+ sntp_servers[idx].kod_received = 0;
+#endif
+ }
+}
+
+/**
+ * Obtain one of the currently configured by name NTP servers.
+ *
+ * @param idx the index of the NTP server
+ * @return IP address of the indexed NTP server or NULL if the NTP
+ * server has not been configured by name (or at all)
+ */
+const char *
+sntp_getservername(u8_t idx)
+{
+ if (idx < SNTP_MAX_SERVERS) {
+ return sntp_servers[idx].name;
+ }
+ return NULL;
+}
+#endif /* SNTP_SERVER_DNS */
+
+#endif /* LWIP_UDP */
diff --git a/src/apps/tftp/tftp.c b/src/apps/tftp/tftp.c
new file mode 100644
index 00000000000..ddfdbfc0c1b
--- /dev/null
+++ b/src/apps/tftp/tftp.c
@@ -0,0 +1,548 @@
+/**
+ *
+ * @file tftp.c
+ *
+ * @author Logan Gunthorpe <[email protected]>
+ * Dirk Ziegelmeier <[email protected]>
+ *
+ * @brief Trivial File Transfer Protocol (RFC 1350)
+ *
+ * Copyright (c) Deltatee Enterprises Ltd. 2013
+ * All rights reserved.
+ *
+ */
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification,are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Author: Logan Gunthorpe <[email protected]>
+ * Dirk Ziegelmeier <[email protected]>
+ *
+ */
+
+/**
+ * @defgroup tftp TFTP client/server
+ * @ingroup apps
+ *
+ * This is simple TFTP client/server for the lwIP raw API.
+ * You need to increase MEMP_NUM_SYS_TIMEOUT by one if you use TFTP!
+ */
+
+#include "lwip/apps/tftp_client.h"
+#include "lwip/apps/tftp_server.h"
+
+#if LWIP_UDP
+
+#include "lwip/udp.h"
+#include "lwip/timeouts.h"
+#include "lwip/debug.h"
+
+#define TFTP_MAX_PAYLOAD_SIZE 512
+#define TFTP_HEADER_LENGTH 4
+
+#define TFTP_RRQ 1
+#define TFTP_WRQ 2
+#define TFTP_DATA 3
+#define TFTP_ACK 4
+#define TFTP_ERROR 5
+
+enum tftp_error {
+ TFTP_ERROR_FILE_NOT_FOUND = 1,
+ TFTP_ERROR_ACCESS_VIOLATION = 2,
+ TFTP_ERROR_DISK_FULL = 3,
+ TFTP_ERROR_ILLEGAL_OPERATION = 4,
+ TFTP_ERROR_UNKNOWN_TRFR_ID = 5,
+ TFTP_ERROR_FILE_EXISTS = 6,
+ TFTP_ERROR_NO_SUCH_USER = 7
+};
+
+#include <string.h>
+
+struct tftp_state {
+ const struct tftp_context *ctx;
+ void *handle;
+ struct pbuf *last_data;
+ struct udp_pcb *upcb;
+ ip_addr_t addr;
+ u16_t port;
+ int timer;
+ int last_pkt;
+ u16_t blknum;
+ u8_t retries;
+ u8_t mode_write;
+ u8_t tftp_mode;
+};
+
+static struct tftp_state tftp_state;
+
+static void tftp_tmr(void *arg);
+
+static void
+close_handle(void)
+{
+ tftp_state.port = 0;
+ ip_addr_set_any(0, &tftp_state.addr);
+
+ if (tftp_state.last_data != NULL) {
+ pbuf_free(tftp_state.last_data);
+ tftp_state.last_data = NULL;
+ }
+
+ sys_untimeout(tftp_tmr, NULL);
+
+ if (tftp_state.handle) {
+ tftp_state.ctx->close(tftp_state.handle);
+ tftp_state.handle = NULL;
+ LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: closing\n"));
+ }
+}
+
+static struct pbuf*
+init_packet(u16_t opcode, u16_t extra, size_t size)
+{
+ struct pbuf* p = pbuf_alloc(PBUF_TRANSPORT, (u16_t)(TFTP_HEADER_LENGTH + size), PBUF_RAM);
+ u16_t* payload;
+
+ if (p != NULL) {
+ payload = (u16_t*) p->payload;
+ payload[0] = PP_HTONS(opcode);
+ payload[1] = lwip_htons(extra);
+ }
+
+ return p;
+}
+
+static err_t
+send_request(const ip_addr_t *addr, u16_t port, u16_t opcode, const char* fname, const char* mode)
+{
+ size_t fname_length = strlen(fname)+1;
+ size_t mode_length = strlen(mode)+1;
+ struct pbuf* p = init_packet(opcode, 0, fname_length + mode_length - 2);
+ char* payload;
+ err_t ret;
+
+ if (p == NULL) {
+ return ERR_MEM;
+ }
+
+ payload = (char*) p->payload;
+ MEMCPY(payload+2, fname, fname_length);
+ MEMCPY(payload+2+fname_length, mode, mode_length);
+
+ ret = udp_sendto(tftp_state.upcb, p, addr, port);
+ pbuf_free(p);
+ return ret;
+}
+
+static err_t
+send_error(const ip_addr_t *addr, u16_t port, enum tftp_error code, const char *str)
+{
+ int str_length = strlen(str);
+ struct pbuf *p;
+ u16_t *payload;
+ err_t ret;
+
+ p = init_packet(TFTP_ERROR, code, str_length + 1);
+ if (p == NULL) {
+ return ERR_MEM;
+ }
+
+ payload = (u16_t *) p->payload;
+ MEMCPY(&payload[2], str, str_length + 1);
+
+ ret = udp_sendto(tftp_state.upcb, p, addr, port);
+ pbuf_free(p);
+ return ret;
+}
+
+static err_t
+send_ack(const ip_addr_t *addr, u16_t port, u16_t blknum)
+{
+ struct pbuf *p;
+ err_t ret;
+
+ p = init_packet(TFTP_ACK, blknum, 0);
+ if (p == NULL) {
+ return ERR_MEM;
+ }
+
+ ret = udp_sendto(tftp_state.upcb, p, addr, port);
+ pbuf_free(p);
+ return ret;
+}
+
+static err_t
+resend_data(const ip_addr_t *addr, u16_t port)
+{
+ err_t ret;
+ struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, tftp_state.last_data->len, PBUF_RAM);
+ if (p == NULL) {
+ return ERR_MEM;
+ }
+
+ ret = pbuf_copy(p, tftp_state.last_data);
+ if (ret != ERR_OK) {
+ pbuf_free(p);
+ return ret;
+ }
+
+ ret = udp_sendto(tftp_state.upcb, p, addr, port);
+ pbuf_free(p);
+ return ret;
+}
+
+static void
+send_data(const ip_addr_t *addr, u16_t port)
+{
+ u16_t *payload;
+ int ret;
+
+ if (tftp_state.last_data != NULL) {
+ pbuf_free(tftp_state.last_data);
+ }
+
+ tftp_state.last_data = init_packet(TFTP_DATA, tftp_state.blknum, TFTP_MAX_PAYLOAD_SIZE);
+ if (tftp_state.last_data == NULL) {
+ return;
+ }
+
+ payload = (u16_t *) tftp_state.last_data->payload;
+
+ ret = tftp_state.ctx->read(tftp_state.handle, &payload[2], TFTP_MAX_PAYLOAD_SIZE);
+ if (ret < 0) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Error occurred while reading the file.");
+ close_handle();
+ return;
+ }
+
+ pbuf_realloc(tftp_state.last_data, (u16_t)(TFTP_HEADER_LENGTH + ret));
+ resend_data(addr, port);
+}
+
+static void
+tftp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
+{
+ u16_t *sbuf = (u16_t *) p->payload;
+ int opcode;
+
+ LWIP_UNUSED_ARG(arg);
+ LWIP_UNUSED_ARG(upcb);
+
+ if (((tftp_state.port != 0) && (port != tftp_state.port)) ||
+ (!ip_addr_isany_val(tftp_state.addr) && !ip_addr_eq(&tftp_state.addr, addr))) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Only one connection at a time is supported");
+ pbuf_free(p);
+ return;
+ }
+
+ opcode = sbuf[0];
+
+ tftp_state.last_pkt = tftp_state.timer;
+ tftp_state.retries = 0;
+
+ switch (opcode) {
+ case PP_HTONS(TFTP_RRQ): /* fall through */
+ case PP_HTONS(TFTP_WRQ): {
+ const char tftp_null = 0;
+ char filename[TFTP_MAX_FILENAME_LEN + 1];
+ char mode[TFTP_MAX_MODE_LEN + 1];
+ u16_t filename_end_offset;
+ u16_t mode_end_offset;
+
+ if (tftp_state.handle != NULL) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Only one connection at a time is supported");
+ break;
+ }
+
+ if ((tftp_state.tftp_mode & LWIP_TFTP_MODE_SERVER) == 0) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "TFTP server not enabled");
+ break;
+ }
+
+ sys_timeout(TFTP_TIMER_MSECS, tftp_tmr, NULL);
+
+ /* find \0 in pbuf -> end of filename string */
+ filename_end_offset = pbuf_memfind(p, &tftp_null, sizeof(tftp_null), 2);
+ if ((u16_t)(filename_end_offset - 1) > sizeof(filename)) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Filename too long/not NULL terminated");
+ break;
+ }
+ pbuf_copy_partial(p, filename, filename_end_offset - 1, 2);
+
+ /* find \0 in pbuf -> end of mode string */
+ mode_end_offset = pbuf_memfind(p, &tftp_null, sizeof(tftp_null), filename_end_offset + 1);
+ if ((u16_t)(mode_end_offset - filename_end_offset) > sizeof(mode)) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Mode too long/not NULL terminated");
+ break;
+ }
+ pbuf_copy_partial(p, mode, mode_end_offset - filename_end_offset, filename_end_offset + 1);
+
+ tftp_state.handle = tftp_state.ctx->open(filename, mode, opcode == PP_HTONS(TFTP_WRQ));
+ tftp_state.blknum = 1;
+
+ if (!tftp_state.handle) {
+ send_error(addr, port, TFTP_ERROR_FILE_NOT_FOUND, "Unable to open requested file.");
+ break;
+ }
+
+ LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: %s request from ", (opcode == PP_HTONS(TFTP_WRQ)) ? "write" : "read"));
+ ip_addr_debug_print(TFTP_DEBUG | LWIP_DBG_STATE, addr);
+ LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, (" for '%s' mode '%s'\n", filename, mode));
+
+ ip_addr_copy(tftp_state.addr, *addr);
+ tftp_state.port = port;
+
+ if (opcode == PP_HTONS(TFTP_WRQ)) {
+ tftp_state.mode_write = 1;
+ send_ack(addr, port, 0);
+ } else {
+ tftp_state.mode_write = 0;
+ send_data(addr, port);
+ }
+
+ break;
+ }
+
+ case PP_HTONS(TFTP_DATA): {
+ int ret;
+ u16_t blknum;
+
+ if (tftp_state.handle == NULL) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "No connection");
+ break;
+ }
+
+ if (tftp_state.mode_write != 1) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Not a write connection");
+ break;
+ }
+
+ blknum = lwip_ntohs(sbuf[1]);
+ if (blknum == tftp_state.blknum) {
+ pbuf_remove_header(p, TFTP_HEADER_LENGTH);
+
+ ret = tftp_state.ctx->write(tftp_state.handle, p);
+ if (ret < 0) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "error writing file");
+ close_handle();
+ } else {
+ send_ack(addr, port, blknum);
+ }
+
+ if (p->tot_len < TFTP_MAX_PAYLOAD_SIZE) {
+ close_handle();
+ } else {
+ tftp_state.blknum++;
+ }
+ } else if ((u16_t)(blknum + 1) == tftp_state.blknum) {
+ /* retransmit of previous block, ack again (casting to u16_t to care for overflow) */
+ send_ack(addr, port, blknum);
+ } else {
+ send_error(addr, port, TFTP_ERROR_UNKNOWN_TRFR_ID, "Wrong block number");
+ }
+ break;
+ }
+
+ case PP_HTONS(TFTP_ACK): {
+ u16_t blknum;
+ int lastpkt;
+
+ if (tftp_state.handle == NULL) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "No connection");
+ break;
+ }
+
+ if (tftp_state.mode_write != 0) {
+ send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Not a read connection");
+ break;
+ }
+
+ blknum = lwip_ntohs(sbuf[1]);
+ if (blknum != tftp_state.blknum) {
+ send_error(addr, port, TFTP_ERROR_UNKNOWN_TRFR_ID, "Wrong block number");
+ break;
+ }
+
+ lastpkt = 0;
+
+ if (tftp_state.last_data != NULL) {
+ lastpkt = tftp_state.last_data->tot_len != (TFTP_MAX_PAYLOAD_SIZE + TFTP_HEADER_LENGTH);
+ }
+
+ if (!lastpkt) {
+ tftp_state.blknum++;
+ send_data(addr, port);
+ } else {
+ close_handle();
+ }
+
+ break;
+ }
+ case PP_HTONS(TFTP_ERROR):
+ if (tftp_state.handle != NULL) {
+ pbuf_remove_header(p, TFTP_HEADER_LENGTH);
+ tftp_state.ctx->error(tftp_state.handle, sbuf[1], (const char*)p->payload, p->len);
+ close_handle();
+ }
+ break;
+ default:
+ send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "Unknown operation");
+ break;
+ }
+
+ pbuf_free(p);
+}
+
+static void
+tftp_tmr(void *arg)
+{
+ LWIP_UNUSED_ARG(arg);
+
+ tftp_state.timer++;
+
+ if (tftp_state.handle == NULL) {
+ return;
+ }
+
+ sys_timeout(TFTP_TIMER_MSECS, tftp_tmr, NULL);
+
+ if ((tftp_state.timer - tftp_state.last_pkt) > (TFTP_TIMEOUT_MSECS / TFTP_TIMER_MSECS)) {
+ if ((tftp_state.last_data != NULL) && (tftp_state.retries < TFTP_MAX_RETRIES)) {
+ LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: timeout, retrying\n"));
+ resend_data(&tftp_state.addr, tftp_state.port);
+ tftp_state.retries++;
+ } else {
+ LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: timeout\n"));
+ close_handle();
+ }
+ }
+}
+
+/**
+ * Initialize TFTP client/server.
+ * @param mode TFTP mode (client/server)
+ * @param ctx TFTP callback struct
+ */
+err_t
+tftp_init_common(u8_t mode, const struct tftp_context *ctx)
+{
+ err_t ret;
+
+ /* LWIP_ASSERT_CORE_LOCKED(); is checked by udp_new() */
+ struct udp_pcb *pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
+ if (pcb == NULL) {
+ return ERR_MEM;
+ }
+
+ ret = udp_bind(pcb, IP_ANY_TYPE, TFTP_PORT);
+ if (ret != ERR_OK) {
+ udp_remove(pcb);
+ return ret;
+ }
+
+ tftp_state.handle = NULL;
+ tftp_state.port = 0;
+ tftp_state.ctx = ctx;
+ tftp_state.timer = 0;
+ tftp_state.last_data = NULL;
+ tftp_state.upcb = pcb;
+ tftp_state.tftp_mode = mode;
+
+ udp_recv(pcb, tftp_recv, NULL);
+
+ return ERR_OK;
+}
+
+/** @ingroup tftp
+ * Initialize TFTP server.
+ * @param ctx TFTP callback struct
+ */
+err_t
+tftp_init_server(const struct tftp_context *ctx)
+{
+ return tftp_init_common(LWIP_TFTP_MODE_SERVER, ctx);
+}
+
+/** @ingroup tftp
+ * Initialize TFTP client.
+ * @param ctx TFTP callback struct
+ */
+err_t
+tftp_init_client(const struct tftp_context *ctx)
+{
+ return tftp_init_common(LWIP_TFTP_MODE_CLIENT, ctx);
+}
+
+/** @ingroup tftp
+ * Deinitialize ("turn off") TFTP client/server.
+ */
+void tftp_cleanup(void)
+{
+ LWIP_ASSERT("Cleanup called on non-initialized TFTP", tftp_state.upcb != NULL);
+ udp_remove(tftp_state.upcb);
+ close_handle();
+ memset(&tftp_state, 0, sizeof(tftp_state));
+}
+
+static const char *
+mode_to_string(enum tftp_transfer_mode mode)
+{
+ if (mode == TFTP_MODE_OCTET) {
+ return "octet";
+ }
+ if (mode == TFTP_MODE_NETASCII) {
+ return "netascii";
+ }
+ if (mode == TFTP_MODE_BINARY) {
+ return "binary";
+ }
+ return NULL;
+}
+
+err_t
+tftp_get(void* handle, const ip_addr_t *addr, u16_t port, const char* fname, enum tftp_transfer_mode mode)
+{
+ LWIP_ERROR("TFTP client is not enabled (tftp_init)", (tftp_state.tftp_mode & LWIP_TFTP_MODE_CLIENT) != 0, return ERR_VAL);
+ LWIP_ERROR("tftp_get: invalid file name", fname != NULL, return ERR_VAL);
+ LWIP_ERROR("tftp_get: invalid mode", mode <= TFTP_MODE_BINARY, return ERR_VAL);
+
+ tftp_state.handle = handle;
+ tftp_state.blknum = 1;
+ tftp_state.mode_write = 1; /* We want to receive data */
+ return send_request(addr, port, TFTP_RRQ, fname, mode_to_string(mode));
+}
+
+err_t
+tftp_put(void* handle, const ip_addr_t *addr, u16_t port, const char* fname, enum tftp_transfer_mode mode)
+{
+ LWIP_ERROR("TFTP client is not enabled (tftp_init)", (tftp_state.tftp_mode & LWIP_TFTP_MODE_CLIENT) != 0, return ERR_VAL);
+ LWIP_ERROR("tftp_put: invalid file name", fname != NULL, return ERR_VAL);
+ LWIP_ERROR("tftp_put: invalid mode", mode <= TFTP_MODE_BINARY, return ERR_VAL);
+
+ tftp_state.handle = handle;
+ tftp_state.blknum = 1;
+ tftp_state.mode_write = 0; /* We want to send data */
+ return send_request(addr, port, TFTP_WRQ, fname, mode_to_string(mode));
+}
+
+#endif /* LWIP_UDP */