echo_server.c revision 9a690580
1/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ 2/* 3 * echo_server.c -- QUIC server that echoes back input line by line 4 */ 5 6#include <assert.h> 7#include <netinet/in.h> 8#include <signal.h> 9#include <stdio.h> 10#include <stdlib.h> 11#include <string.h> 12#include <sys/queue.h> 13#include <time.h> 14#include <unistd.h> 15 16#include "lsquic.h" 17#include "test_common.h" 18#include "prog.h" 19 20#include "../src/liblsquic/lsquic_logger.h" 21 22 23struct lsquic_conn_ctx; 24 25struct echo_server_ctx { 26 TAILQ_HEAD(, lsquic_conn_ctx) conn_ctxs; 27 unsigned max_reqs; 28 int n_conn; 29 struct sport_head sports; 30 struct prog *prog; 31}; 32 33struct lsquic_conn_ctx { 34 TAILQ_ENTRY(lsquic_conn_ctx) next_connh; 35 lsquic_conn_t *conn; 36 struct echo_server_ctx *server_ctx; 37}; 38 39 40static lsquic_conn_ctx_t * 41echo_server_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn) 42{ 43 struct echo_server_ctx *server_ctx = stream_if_ctx; 44 lsquic_conn_ctx_t *conn_h = calloc(1, sizeof(*conn_h)); 45 conn_h->conn = conn; 46 conn_h->server_ctx = server_ctx; 47 TAILQ_INSERT_TAIL(&server_ctx->conn_ctxs, conn_h, next_connh); 48 LSQ_NOTICE("New connection!"); 49 print_conn_info(conn); 50 return conn_h; 51} 52 53 54static void 55echo_server_on_conn_closed (lsquic_conn_t *conn) 56{ 57 lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn); 58 if (conn_h->server_ctx->n_conn) 59 { 60 --conn_h->server_ctx->n_conn; 61 LSQ_NOTICE("Connection closed, remaining: %d", conn_h->server_ctx->n_conn); 62 if (0 == conn_h->server_ctx->n_conn) 63 prog_stop(conn_h->server_ctx->prog); 64 } 65 else 66 LSQ_NOTICE("Connection closed"); 67 TAILQ_REMOVE(&conn_h->server_ctx->conn_ctxs, conn_h, next_connh); 68 free(conn_h); 69} 70 71 72struct lsquic_stream_ctx { 73 lsquic_stream_t *stream; 74 struct echo_server_ctx *server_ctx; 75 char buf[0x100]; 76 size_t buf_off; 77}; 78 79 80static lsquic_stream_ctx_t * 81echo_server_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream) 82{ 83 lsquic_stream_ctx_t *st_h = malloc(sizeof(*st_h)); 84 st_h->stream = stream; 85 st_h->server_ctx = stream_if_ctx; 86 st_h->buf_off = 0; 87 lsquic_stream_wantread(stream, 1); 88 return st_h; 89} 90 91 92static struct lsquic_conn_ctx * 93find_conn_h (const struct echo_server_ctx *server_ctx, lsquic_stream_t *stream) 94{ 95 struct lsquic_conn_ctx *conn_h; 96 lsquic_conn_t *conn; 97 98 conn = lsquic_stream_conn(stream); 99 TAILQ_FOREACH(conn_h, &server_ctx->conn_ctxs, next_connh) 100 if (conn_h->conn == conn) 101 return conn_h; 102 return NULL; 103} 104 105 106static void 107echo_server_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 108{ 109 struct lsquic_conn_ctx *conn_h; 110 size_t nr; 111 112 nr = lsquic_stream_read(stream, st_h->buf + st_h->buf_off++, 1); 113 if (0 == nr) 114 { 115 LSQ_NOTICE("EOF: closing connection"); 116 lsquic_stream_shutdown(stream, 2); 117 conn_h = find_conn_h(st_h->server_ctx, stream); 118 lsquic_conn_close(conn_h->conn); 119 } 120 else if ('\n' == st_h->buf[ st_h->buf_off - 1 ]) 121 { 122 /* Found end of line: echo it back */ 123 lsquic_stream_wantwrite(stream, 1); 124 lsquic_stream_wantread(stream, 0); 125 } 126 else if (st_h->buf_off == sizeof(st_h->buf)) 127 { 128 /* Out of buffer space: line too long */ 129 LSQ_NOTICE("run out of buffer space"); 130 lsquic_stream_shutdown(stream, 2); 131 } 132 else 133 { 134 /* Keep reading */; 135 } 136} 137 138 139static void 140echo_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 141{ 142 lsquic_stream_write(stream, st_h->buf, st_h->buf_off); 143 st_h->buf_off = 0; 144 lsquic_stream_flush(stream); 145 lsquic_stream_wantwrite(stream, 0); 146 lsquic_stream_wantread(stream, 1); 147} 148 149 150static void 151echo_server_on_stream_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h) 152{ 153 struct lsquic_conn_ctx *conn_h; 154 LSQ_NOTICE("%s called", __func__); 155 conn_h = find_conn_h(st_h->server_ctx, stream); 156 LSQ_WARN("%s: TODO: free connection handler %p", __func__, conn_h); 157 free(st_h); 158} 159 160 161const struct lsquic_stream_if server_echo_stream_if = { 162 .on_new_conn = echo_server_on_new_conn, 163 .on_conn_closed = echo_server_on_conn_closed, 164 .on_new_stream = echo_server_on_new_stream, 165 .on_read = echo_server_on_read, 166 .on_write = echo_server_on_write, 167 .on_close = echo_server_on_stream_close, 168}; 169 170 171static void 172usage (const char *prog) 173{ 174 const char *const slash = strrchr(prog, '/'); 175 if (slash) 176 prog = slash + 1; 177 printf( 178"Usage: %s [opts]\n" 179"\n" 180"Options:\n" 181 , prog); 182} 183 184 185int 186main (int argc, char **argv) 187{ 188 int opt, s; 189 struct prog prog; 190 struct echo_server_ctx server_ctx; 191 192 memset(&server_ctx, 0, sizeof(server_ctx)); 193 server_ctx.prog = &prog; 194 TAILQ_INIT(&server_ctx.sports); 195 TAILQ_INIT(&server_ctx.conn_ctxs); 196 197 prog_init(&prog, LSENG_SERVER, &server_ctx.sports, 198 &server_echo_stream_if, &server_ctx); 199 200 while (-1 != (opt = getopt(argc, argv, PROG_OPTS "hn:"))) 201 { 202 switch (opt) { 203 case 'n': 204 server_ctx.n_conn = atoi(optarg); 205 break; 206 case 'h': 207 usage(argv[0]); 208 prog_print_common_options(&prog, stdout); 209 exit(0); 210 default: 211 if (0 != prog_set_opt(&prog, opt, optarg)) 212 exit(1); 213 } 214 } 215 216 if (0 != prog_prep(&prog)) 217 { 218 LSQ_ERROR("could not prep"); 219 exit(EXIT_FAILURE); 220 } 221 222 LSQ_DEBUG("entering event loop"); 223 224 s = prog_run(&prog); 225 prog_cleanup(&prog); 226 227 exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE); 228} 229