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