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