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