echo_client.c revision 9a690580
1/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc.  See LICENSE. */
2/*
3 * echo_client.c -- This is really a "line client:" it connects to QUIC server
4 * and sends it stuff, line by line.  It works in tandem with echo_server.
5 */
6
7#include <assert.h>
8#include <errno.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <unistd.h>
13#include <sys/queue.h>
14#include <sys/types.h>
15#include <sys/stat.h>
16#include <fcntl.h>
17
18#include <event2/event.h>
19
20#include "lsquic.h"
21#include "test_common.h"
22#include "prog.h"
23
24#include "../src/liblsquic/lsquic_logger.h"
25
26struct lsquic_conn_ctx;
27
28struct echo_client_ctx {
29    struct lsquic_conn_ctx  *conn_h;
30    struct prog                 *prog;
31};
32
33struct lsquic_conn_ctx {
34    lsquic_conn_t       *conn;
35    struct echo_client_ctx   *client_ctx;
36};
37
38
39static lsquic_conn_ctx_t *
40echo_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
41{
42    struct echo_client_ctx *client_ctx = stream_if_ctx;
43    lsquic_conn_ctx_t *conn_h = malloc(sizeof(*conn_h));
44    conn_h->conn = conn;
45    conn_h->client_ctx = client_ctx;
46    client_ctx->conn_h = conn_h;
47    lsquic_conn_make_stream(conn);
48    return conn_h;
49}
50
51
52static void
53echo_client_on_conn_closed (lsquic_conn_t *conn)
54{
55    lsquic_conn_ctx_t *conn_h = lsquic_conn_get_ctx(conn);
56    LSQ_NOTICE("Connection closed");
57    prog_stop(conn_h->client_ctx->prog);
58    free(conn_h);
59}
60
61
62struct lsquic_stream_ctx {
63    lsquic_stream_t     *stream;
64    struct echo_client_ctx   *client_ctx;
65    struct event        *read_stdin_ev;
66    char                 buf[0x100];
67    size_t               buf_off;
68};
69
70
71static void
72read_stdin (int fd, short what, void *ctx)
73{
74    ssize_t nr;
75    lsquic_stream_ctx_t *st_h = ctx;
76
77    nr = read(fd, st_h->buf + st_h->buf_off++, 1);
78    LSQ_DEBUG("read %zd bytes from stdin", nr);
79    if (0 == nr)
80    {
81        lsquic_stream_shutdown(st_h->stream, 2);
82    }
83    else if (-1 == nr)
84    {
85        perror("read");
86        exit(1);
87    }
88    else if ('\n' == st_h->buf[ st_h->buf_off - 1 ])
89    {
90        LSQ_DEBUG("read newline: wantwrite");
91        lsquic_stream_wantwrite(st_h->stream, 1);
92        lsquic_engine_process_conns(st_h->client_ctx->prog->prog_engine);
93    }
94    else if (st_h->buf_off == sizeof(st_h->buf))
95    {
96        LSQ_NOTICE("line too long");
97        exit(2);
98    }
99    else
100        event_add(st_h->read_stdin_ev, NULL);
101}
102
103
104static lsquic_stream_ctx_t *
105echo_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
106{
107    lsquic_stream_ctx_t *st_h = calloc(1, sizeof(*st_h));
108    st_h->stream = stream;
109    st_h->client_ctx = stream_if_ctx;
110    st_h->buf_off = 0;
111    st_h->read_stdin_ev = event_new(prog_eb(st_h->client_ctx->prog),
112                                    STDIN_FILENO, EV_READ, read_stdin, st_h);
113    event_add(st_h->read_stdin_ev, NULL);
114    return st_h;
115}
116
117
118static void
119echo_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
120{
121    char c;
122    size_t nr;
123
124    nr = lsquic_stream_read(stream, &c, 1);
125    if (0 == nr)
126    {
127        lsquic_stream_shutdown(stream, 2);
128        return;
129    }
130    printf("%c", c);
131    fflush(stdout);
132    if ('\n' == c)
133    {
134        event_add(st_h->read_stdin_ev, NULL);
135        lsquic_stream_wantread(stream, 0);
136    }
137}
138
139
140static void
141echo_client_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
142{
143    /* Here we make an assumption that we can write the whole buffer.
144     * Don't do it in a real program.
145     */
146    lsquic_stream_write(stream, st_h->buf, st_h->buf_off);
147    st_h->buf_off = 0;
148
149    lsquic_stream_flush(stream);
150    lsquic_stream_wantwrite(stream, 0);
151    lsquic_stream_wantread(stream, 1);
152}
153
154
155static void
156echo_client_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
157{
158    LSQ_NOTICE("%s called", __func__);
159    if (st_h->read_stdin_ev)
160    {
161        event_del(st_h->read_stdin_ev);
162        event_free(st_h->read_stdin_ev);
163    }
164    free(st_h);
165    lsquic_conn_close(lsquic_stream_conn(stream));
166}
167
168
169const struct lsquic_stream_if client_echo_stream_if = {
170    .on_new_conn            = echo_client_on_new_conn,
171    .on_conn_closed         = echo_client_on_conn_closed,
172    .on_new_stream          = echo_client_on_new_stream,
173    .on_read                = echo_client_on_read,
174    .on_write               = echo_client_on_write,
175    .on_close               = echo_client_on_close,
176};
177
178
179static void
180usage (const char *prog)
181{
182    const char *const slash = strrchr(prog, '/');
183    if (slash)
184        prog = slash + 1;
185    LSQ_NOTICE(
186"Usage: %s [opts]\n"
187"\n"
188"Options:\n"
189            , prog);
190}
191
192
193int
194main (int argc, char **argv)
195{
196    int opt, s;
197    struct sport_head sports;
198    struct prog prog;
199    struct echo_client_ctx client_ctx;
200
201    memset(&client_ctx, 0, sizeof(client_ctx));
202    client_ctx.prog = &prog;
203
204    TAILQ_INIT(&sports);
205    prog_init(&prog, 0, &sports, &client_echo_stream_if, &client_ctx);
206
207    while (-1 != (opt = getopt(argc, argv, PROG_OPTS "h")))
208    {
209        switch (opt) {
210        case 'h':
211            usage(argv[0]);
212            prog_print_common_options(&prog, stdout);
213            exit(0);
214        default:
215            if (0 != prog_set_opt(&prog, opt, optarg))
216                exit(1);
217        }
218    }
219
220    int flags = fcntl(STDIN_FILENO, F_GETFL);
221    flags |= O_NONBLOCK;
222    if (0 != fcntl(STDIN_FILENO, F_SETFL, flags))
223    {
224        perror("fcntl");
225        exit(1);
226    }
227
228    if (0 != prog_prep(&prog))
229    {
230        LSQ_ERROR("could not prep");
231        exit(EXIT_FAILURE);
232    }
233    if (0 != prog_connect(&prog, NULL, 0))
234    {
235        LSQ_ERROR("could not connect");
236        exit(EXIT_FAILURE);
237    }
238
239    LSQ_DEBUG("entering event loop");
240
241    s = prog_run(&prog);
242    prog_cleanup(&prog);
243
244    exit(0 == s ? EXIT_SUCCESS : EXIT_FAILURE);
245}
246