--- /dev/null
+/*
+ * cmd_echoserver.c -- Simple echoserver
+ *
+ * Copyright (C) 2014 Czech Technical University in Prague
+ *
+ * This echoserver is capable of running multiple instances each running
+ * in the background thread.
+ * Each instance listens on a predefined port, when a connection is
+ * established and some data are received, the same data are then sent
+ * back to the client.
+ * There is support for basic thread management -- it is possible to
+ * either list all the background threads or kill them all.
+ *
+ * The code uses LwIP netconn API.
+ *
+ * Nomenclature:
+ * es_ prefix is used as the "echoserver" specific identifier
+ */
+
+#include "cmd_echoserver.h"
+
+#ifndef DOCGEN
+
+#include <string.h>
+#include <ctype.h>
+
+#include "rpp/rpp.h"
+#include "lwip/udp.h"
+#include "lwip/api.h" /* netconn */
+
+#include "os/FreeRTOS.h"
+#include "os/task.h"
+
+/* Ethernet interface instance nr. */
+#define INTERFACE_INSTANCE_NUMBER 0
+
+/* Number of statically allocated structs es_thread_context.
+ * This limits the number of concurrently running threads.
+ */
+#define MAX_THREADS_CNT 12
+
+/* Run in the foreground, in the context of the command-processor thread */
+#define ES_COMMAND_RUN_FOREGROUND 1
+/* Create new background thread */
+#define ES_COMMAND_NEW_THREAD 2
+/* List all the background ES threads */
+#define ES_COMMAND_LIST_THREADS 3
+/* Kill all the background ES threads */
+#define ES_COMMAND_KILL_ALL_THREADS 4
+
+/* Forward declarations */
+void es_print_help(void);
+
+struct es_thread_context {
+ /* Thread handle; Useful when killing all the threads */
+ xTaskHandle handle;
+
+ /* listening connection */
+ struct netconn *conn_listen;
+ /* used for the netconn_accept() */
+ struct netconn *conn;
+
+#define ES_THREAD_TERMINATED 0
+#define ES_THREAD_RUNNING 1
+ /* Status of the thread represented with this
+ * particular struct es_thread_context
+ */
+ int status;
+
+ /*
+ * Options passed when executed from the command processor
+ */
+
+ /* Number of the port used to listen to */
+ unsigned int port_no;
+
+ /* Return code */
+ int err;
+} es_threads[MAX_THREADS_CNT];
+
+static int es_thread_handle_find_first_free(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_THREADS_CNT; i++) {
+ if (es_threads[i].status == ES_THREAD_TERMINATED)
+ return i;
+ }
+
+ return -1;
+}
+
+static void es_thread_terminate(struct es_thread_context *thread_context)
+{
+ int err = thread_context->err;
+
+ /* FIXME maybe not necessary at all */
+ thread_context->port_no = 0;
+
+ thread_context->err = 0;
+ thread_context->status = ES_THREAD_TERMINATED;
+
+ if (thread_context->conn_listen) {
+ netconn_close(thread_context->conn_listen);
+ netconn_delete(thread_context->conn_listen);
+ thread_context->conn_listen = NULL;
+ }
+ if (thread_context->conn) {
+ netconn_close(thread_context->conn);
+ netconn_delete(thread_context->conn);
+ thread_context->conn = NULL;
+ }
+
+ rpp_sci_printf("Terminating thread %s retcode: %d\n",
+ pcTaskGetTaskName(thread_context->handle), err);
+ vTaskDelete(thread_context->handle);
+}
+
+
+static void es_logic(cmd_io_t *cmd_io, void *data)
+{
+ struct es_thread_context *thread_context = (struct es_thread_context*)data;
+ err_t err = ERR_OK;
+
+ thread_context->conn_listen = netconn_new(NETCONN_TCP);
+ if (thread_context->conn_listen == NULL) {
+ rpp_sci_printf("netconn_new() failed\n");
+ thread_context->err = ERR_MEM;
+ return;
+ }
+ err = netconn_bind(thread_context->conn_listen, NULL, thread_context->port_no);
+ if (err != ERR_OK) {
+ thread_context->err = err;
+ goto leave_clean;
+ }
+
+ err = netconn_listen(thread_context->conn_listen);
+ if (err != ERR_OK) {
+ thread_context->err = err;
+ goto leave_clean;
+ }
+
+ while (cmd_io == NULL || cmd_io->getc(cmd_io) < 0) { /* keep-alive */
+ err = netconn_accept(thread_context->conn_listen, &thread_context->conn);
+ if (err == ERR_OK) {
+ struct netbuf *netbuf;
+ void *data;
+ u16_t len;
+
+ rpp_sci_printf("accepted new connection %p\n",
+ thread_context->conn);
+ while ((err = netconn_recv(thread_context->conn, &netbuf)) == ERR_OK) {
+ /*printf("Recved\n");*/
+ do {
+ netbuf_data(netbuf, &data, &len);
+ //rpp_sci_printf("netbuf len: %d\n", (unsigned int)len);
+ err = netconn_write(thread_context->conn,
+ data, len, NETCONN_COPY);
+ /* FIXME NOCOPY or COPY? What if we free the netbuf before its
+ payload is transmitted? */
+ if (err != ERR_OK) {
+ rpp_sci_printf(
+ "tcpecho: netconn_write: error \"%s\" (%d)\n",
+ lwip_strerr(err), err);
+ break; /* FIXME is this necessary?
+ Is it possible to continue? */
+ }
+ } while (netbuf_next(netbuf) >= 0);
+ netbuf_delete(netbuf);
+ }
+ rpp_sci_printf("Got EOF, looping\n");
+ netconn_close(thread_context->conn);
+ netconn_delete(thread_context->conn);
+ /* Necessary for generic resource freeing */
+ thread_context->conn = NULL;
+ } else {
+ thread_context->err = err;
+ return;
+ }
+ }
+
+leave_clean:
+ netconn_close(thread_context->conn_listen);
+ netconn_delete(thread_context->conn_listen);
+ /* Necessary for generic resource freeing */
+ thread_context->conn_listen = NULL;
+ return;
+}
+
+static void es_thread(void *data)
+{
+ struct es_thread_context *thread_context = (struct es_thread_context*)data;
+
+ /* Run the echoserver logic */
+ es_logic(NULL, data);
+
+ /* It is mandatory to destroy the thread when leaving;
+ * Be aware of the fact that we are terminating ourselves --
+ * in our thread context */
+ es_thread_terminate(thread_context);
+
+ /* We should never ever get here */
+}
+
+int cmd_do_init_es(cmd_io_t *cmd_io, const struct cmd_des *des, char *param[])
+{
+ struct netif *netif = rpp_eth_get_netif(INTERFACE_INSTANCE_NUMBER);
+ struct es_thread_context *thread_context;
+ unsigned char thread_name[5] = "es"; /* thread name, used for debugging only */
+ err_t err = ERR_OK;
+ uint8_t pindex;
+ int thread_nr;
+ int command = ES_COMMAND_RUN_FOREGROUND;
+ unsigned int port_no = 0;
+ int i;
+
+ if (!isPostInitialized()) {
+ rpp_sci_printf("Eth not initialized run 'ethinit' command first.\n");
+ return FAILURE;
+ }
+
+ /* Parse the "command line" arguments */
+ for (pindex = 1; param[pindex] != 0; pindex++) {
+ if (strncmp(param[pindex], "-c", 3) == 0) {
+ if (command != ES_COMMAND_RUN_FOREGROUND) {
+ rpp_sci_printf("More than single command are used\n");
+ return FAILURE;
+ }
+ command = ES_COMMAND_KILL_ALL_THREADS;
+ } else if (strncmp(param[pindex], "-l", 3) == 0) {
+ if (command != ES_COMMAND_RUN_FOREGROUND) {
+ rpp_sci_printf("More than single command are used\n");
+ return FAILURE;
+ }
+ command = ES_COMMAND_LIST_THREADS;
+ } else if (strncmp(param[pindex], "-t", 3) == 0) {
+ if (command != ES_COMMAND_RUN_FOREGROUND) {
+ rpp_sci_printf("More than single command are used\n");
+ return FAILURE;
+ }
+ command = ES_COMMAND_NEW_THREAD;
+ } else if (strncmp(param[pindex], "-p", 3) == 0) {
+ /* The validity (!= 0) check is done later */
+ port_no = rpp_eth_portStrToInt((uint8_t *)param[++pindex]);
+ } else {
+ es_print_help();
+ return FAILURE;
+ }
+ }
+
+ /* Port number is mandatory for interactive or background-thread mode */
+ if (command == ES_COMMAND_RUN_FOREGROUND || command == ES_COMMAND_NEW_THREAD)
+ {
+ if (!port_no)
+ {
+ rpp_sci_printf("Port number not set\n");
+ return FAILURE;
+ }
+
+ /* Is there any free preallocated thread handle? */
+ thread_nr = es_thread_handle_find_first_free();
+ if (thread_nr < 0) {
+ rpp_sci_printf("Unable to create new thread."
+ "Maximum number of simultaneously running threads is reached\n");
+ return FAILURE;
+ }
+ thread_context = &es_threads[thread_nr];
+ thread_context->port_no = port_no;
+ }
+
+ /* Execute the particular command */
+ if (command == ES_COMMAND_NEW_THREAD) {
+ unsigned int thread_nr_tmp;
+ thread_nr_tmp = (thread_nr % 100);
+ thread_name[2] = (thread_nr_tmp/10) + '0';
+ thread_name[3] = (thread_nr_tmp%10) + '0';
+ thread_name[4] = '\0';
+ rpp_sci_printf("Starting thread: %s\n", thread_name);
+ err = xTaskCreate(es_thread,
+ thread_name,
+ esTaskStackSize,
+ thread_context,
+ esTaskPriority,
+ &thread_context->handle);
+ if (err != pdPASS) {
+ rpp_sci_printf("xTaskCreate() failed\n");
+ return FAILURE;
+ } else {
+ thread_context->status = ES_THREAD_RUNNING;
+ }
+
+ } else if (command == ES_COMMAND_RUN_FOREGROUND) {
+ /* Run in the foreground */
+ es_logic(cmd_io, (void *)thread_context);
+ } else if (command == ES_COMMAND_LIST_THREADS) {
+ for (i = 0; i < MAX_THREADS_CNT; i++) {
+ if (es_threads[i].status == ES_THREAD_RUNNING) {
+ rpp_sci_printf("Thread %s exists state: %s\n",
+ pcTaskGetTaskName(es_threads[i].handle),
+ "unknown");
+ }
+ }
+ } else if (command == ES_COMMAND_KILL_ALL_THREADS) {
+ for (i = 0; i < MAX_THREADS_CNT; i++) {
+ if (es_threads[i].status == ES_THREAD_RUNNING) {
+ es_thread_terminate(&es_threads[i]);
+ }
+ }
+ }
+ return ERR_OK;
+}
+
+#endif /* DOCGEN */
+
+#define ES_COMMAND_NAME "ethes"
+cmd_des_t const cmd_des_es = {
+ 0, CDESM_SPACE_SEP,
+ ES_COMMAND_NAME, "Start very simple TCP echoserver",
+ "Syntax:\n"
+ ES_COMMAND_NAME " -p NUMBER [-l|-c|-t]\n\n"
+ " -p\tdefine port number to listen to\n"
+ "Optional command:\n"
+ " -l\tlist all running threads\n"
+ " -c\tkill all running threads\n"
+ " -t\tcreate new background thread\n"
+ "If no command is given, the application is run in interactive mode"
+ " (in foreground)\n",
+ CMD_HANDLER(cmd_do_init_es), (void *)&cmd_list_es
+};
+
+#ifndef DOCGEN
+void es_print_help(void)
+{
+ rpp_sci_printf("%s\n", cmd_des_es.long_help);
+}
+#endif /* DOCGEN */
+
+/** List of commands for lwip, defined as external */
+cmd_des_t const *cmd_list_es[] = {
+ &cmd_des_es,
+ NULL
+};