diff --git a/.gitignore b/.gitignore index 650d91dc6..820b22de0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,14 @@ cmake_install.cmake xcuserdata *.xccheckout tmp/ +cli +embedded-probes.h +examples-echo +libquicly.a +picotls-probes.h +quicly-probes.h +quicly-tracer.h +test.t +udpfw +examples-pipeclient +examples-pipeserver diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..662726db6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,54 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "example-pipe-server", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceRoot}/build/examples-pipeserver", + "args": ["-c", "../tmp/server.cert", "-k", "../tmp/server.key"], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + },{ + "name": "example-pipe-client", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceRoot}/build/examples-pipeclient", + "args": [""], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": true, + "MIMode": "lldb" + },{ + "name": "example-echo-client", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceRoot}/build/examples-echo", + "args": [""], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": true, + "MIMode": "lldb" + }, + { + "name": "cli help", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceRoot}/build/cli", + "args": ["-h"], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + "MIMode": "lldb" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..3bd8ac75c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "unistd.h": "c", + "sstream": "c" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a2ed00ec0..58907ae48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11) +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11...3.20) CMAKE_POLICY(SET CMP0003 NEW) PROJECT(quicly) @@ -124,6 +124,12 @@ TARGET_LINK_LIBRARIES(test.t quicly ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) ADD_EXECUTABLE(examples-echo ${PICOTLS_OPENSSL_FILES} examples/echo.c) TARGET_LINK_LIBRARIES(examples-echo quicly ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) +ADD_EXECUTABLE(examples-pipeclient ${PICOTLS_OPENSSL_FILES} examples/pipeclient.c) +TARGET_LINK_LIBRARIES(examples-pipeclient quicly ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) + +ADD_EXECUTABLE(examples-pipeserver ${PICOTLS_OPENSSL_FILES} examples/pipeserver.c) +TARGET_LINK_LIBRARIES(examples-pipeserver quicly ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS}) + ADD_EXECUTABLE(udpfw t/udpfw.c) ADD_CUSTOM_TARGET(check env BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} WITH_DTRACE=${WITH_DTRACE} prove --exec "sh -c" -v ${CMAKE_CURRENT_BINARY_DIR}/*.t t/*.t diff --git a/examples/PIPE-README.md b/examples/PIPE-README.md new file mode 100644 index 000000000..eb391c94a --- /dev/null +++ b/examples/PIPE-README.md @@ -0,0 +1,50 @@ +Send data streams over QUIC (Quicly) +=== + +Using the `pipeclient` and `pipeserver` binaries you can esaly send data streams (ex: live video) over QUIC + +How to test it +--- + +- Install & compile the code, you can follow the [readme](../README.md) instructions + +- Generate the certs in the `/tmp` dir: +``` +mkdir -p tmp +openssl req -nodes -new -x509 -keyout tmp/server.key -out tmp/server.crt +``` + +- To run the server +``` +./examples-pipeserver -c ../tmp/server.crt -k ../tmp/server.key -p 4433 > myReceivedFile.bin +``` + +- To run the client +``` +cat myfile.bin | ./examples-pipeclient +``` + +- Check files +``` +diff myfile.bin myReceivedFile.bin +``` + +Example with video (Assumes `ffmpeg` and `ffplay` installed) +--- + +- To run the server: +``` +./examples-pipeserver -c ../tmp/server.cert -k ../tmp/server.key -p 4433 | ffplay - +``` + +- To run the client: +``` +ffmpeg -re -f lavfi -i smptebars=duration=30:size=320x200:rate=30 -f lavfi -re -i sine=frequency=1000:duration=30:sample_rate=48000 -pix_fmt yuv420p -c:v libx264 -b:v 180k -g 60 -keyint_min 60 -profile:v baseline -preset veryfast -c:a aac -b:a 96k -vf "drawtext=fontfile=/Library/Fonts/Arial.ttf: text=\'Local time %{localtime\: %Y\/%m\/%d %H.%M.%S} (%{n})\': x=10: y=10: fontsize=16: fontcolor=white: box=1: boxcolor=0x00000099" -f mpegts - | ./examples-pipeclient +``` + +Note: The previous command will work on MACos, but if you have problems with `lavfi` filter or with the fonts path you can use the following one: +``` +ffmpeg -re -i myVideoFile.mp4 -c copy -f mpegts - | ./examples-pipeclient +``` + +Now you should see live video!!! \ No newline at end of file diff --git a/examples/pipeclient.c b/examples/pipeclient.c new file mode 100644 index 000000000..1c29734f2 --- /dev/null +++ b/examples/pipeclient.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2021 Jordi Cenzano + * Created from ./echo.c + */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 /* required for glibc to use getaddrinfo, etc. */ +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "picotls.h" +#include "picotls/openssl.h" +#include "quicly.h" +#include "quicly/defaults.h" +#include "quicly/streambuf.h" + +/** + * the QUIC context + */ +static quicly_context_t ctx; +/** + * CID seed + */ +static quicly_cid_plaintext_t next_cid; +/** + * Verbose mode + */ + int is_verbose = 0; + +static int resolve_address(struct sockaddr *sa, socklen_t *salen, const char *host, const char *port, int family, int type, + int proto) +{ + struct addrinfo hints, *res; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = type; + hints.ai_protocol = proto; + hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_PASSIVE; + if ((err = getaddrinfo(host, port, &hints, &res)) != 0 || res == NULL) { + fprintf(stderr, "failed to resolve address:%s:%s:%s\n", host, port, + err != 0 ? gai_strerror(err) : "getaddrinfo returned NULL"); + return -1; + } + + memcpy(sa, res->ai_addr, res->ai_addrlen); + *salen = res->ai_addrlen; + + freeaddrinfo(res); + return 0; +} + +static void usage(const char *progname) +{ + printf("Usage: %s [options] [host]\n" + "Options:\n" + " -v Show messages from server" + " -p specifies the port number (default: 4433)\n" + " -h prints this help\n" + "\n" + "If omitted, host defaults to 127.0.0.1.\n" + "\n" + "Example (sends live video over QUIC):\n" + "ffmpeg -i \"udp://localhost:5000\" -c copy -f mpegts - | %s -p 4433 localhost\n", + progname, progname); + exit(0); +} + +static int forward_stdin(quicly_conn_t *conn) +{ + quicly_stream_t *stream0; + const size_t READ_BLOCK_SIZE = 188 * 6; // Assumed input is transport stream + char buf[READ_BLOCK_SIZE]; + size_t rret; + + if ((stream0 = quicly_get_stream(conn, 0)) == NULL || !quicly_sendstate_is_open(&stream0->sendstate)) + return 0; + + /* Read binary from stdin */ + while ((rret = read(STDIN_FILENO, buf, READ_BLOCK_SIZE)) == -1 && errno == EINTR) + ; + + fprintf(stderr, "Read from stdin: %zu bytes\n", rret); + + // Something wrong! + if (rret < 0) { + // Show error and close the stream + fprintf(stderr, "failed to read from stdin"); + rret = 0; + } + + if (rret == 0) { + fprintf(stderr, "Closing\n"); + /* stdin closed, close the send-side of stream0 */ + quicly_streambuf_egress_shutdown(stream0); + return 0; + } else { + /* write data to send buffer */ + quicly_streambuf_egress_write(stream0, buf, rret); + return 1; + } +} + +static void on_stop_sending(quicly_stream_t *stream, int err) +{ + fprintf(stderr, "received STOP_SENDING: %" PRIu16 "\n", QUICLY_ERROR_GET_ERROR_CODE(err)); + quicly_close(stream->conn, QUICLY_ERROR_FROM_APPLICATION_ERROR_CODE(0), ""); +} + +static void on_receive_reset(quicly_stream_t *stream, int err) +{ + fprintf(stderr, "received RESET_STREAM: %" PRIu16 "\n", QUICLY_ERROR_GET_ERROR_CODE(err)); + quicly_close(stream->conn, QUICLY_ERROR_FROM_APPLICATION_ERROR_CODE(0), ""); +} + +static void on_receive(quicly_stream_t *stream, size_t off, const void *src, size_t len) +{ + /* read input to receive buffer */ + if (quicly_streambuf_ingress_receive(stream, off, src, len) != 0) + return; + + /* obtain contiguous bytes from the receive buffer */ + ptls_iovec_t input = quicly_streambuf_ingress_get(stream); + + /* print to stdout any data re receive from server*/ + if (is_verbose) { + fwrite(input.base, 1, input.len, stdout); + fflush(stdout); + } + /* initiate connection close after receiving all data */ + if (quicly_recvstate_transfer_complete(&stream->recvstate)) + quicly_close(stream->conn, 0, ""); + + /* remove used bytes from receive buffer */ + quicly_streambuf_ingress_shift(stream, input.len); +} + +static void process_msg(quicly_conn_t *client, struct msghdr *msg, size_t dgram_len) +{ + size_t off = 0; + + /* split UDP datagram into multiple QUIC packets */ + while (off < dgram_len) { + quicly_decoded_packet_t decoded; + if (quicly_decode_packet(&ctx, &decoded, msg->msg_iov[0].iov_base, dgram_len, &off) == SIZE_MAX) + return; + + if (client != NULL) { + if (quicly_is_destination(client, NULL, msg->msg_name, &decoded)) + quicly_receive(client, NULL, msg->msg_name, &decoded); + } + } +} + +static int send_one(int fd, struct sockaddr *dest, struct iovec *vec) +{ + struct msghdr mess = {.msg_name = dest, .msg_namelen = quicly_get_socklen(dest), .msg_iov = vec, .msg_iovlen = 1}; + int ret; + + while ((ret = (int)sendmsg(fd, &mess, 0)) == -1 && errno == EINTR) + ; + return ret; +} + +static int run_loop_client(int fd, quicly_conn_t *client) +{ + int read_stdin = 1; + + while (1) { + /* wait for sockets to become readable, or some event in the QUIC stack to fire */ + fd_set readfds; + struct timeval tv; + do { + int64_t first_timeout = INT64_MAX, now = ctx.now->cb(ctx.now); + int64_t conn_timeout = quicly_get_first_timeout(client); + if (conn_timeout < first_timeout) + first_timeout = conn_timeout; + if (now < first_timeout) { + int64_t delta = first_timeout - now; + if (delta > 1000 * 1000) + delta = 1000 * 1000; + tv.tv_sec = delta / 1000; + tv.tv_usec = (delta % 1000) * 1000; + } else { + tv.tv_sec = 1000; + tv.tv_usec = 0; + } + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + if (read_stdin) + FD_SET(STDIN_FILENO, &readfds); + } while (select(fd + 1, &readfds, NULL, NULL, &tv) == -1 && errno == EINTR); + + /* read the QUIC fd */ + if (FD_ISSET(fd, &readfds)) { + uint8_t buf[4096]; + struct sockaddr_storage sa; + struct iovec vec = {.iov_base = buf, .iov_len = sizeof(buf)}; + struct msghdr msg = {.msg_name = &sa, .msg_namelen = sizeof(sa), .msg_iov = &vec, .msg_iovlen = 1}; + ssize_t rret; + while ((rret = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR) + ; + if (rret > 0) + process_msg(client, &msg, rret); + } + + if (FD_ISSET(0, &readfds)) { + assert(client != NULL); + if (!forward_stdin(client)) + read_stdin = 0; + } + + /* send QUIC packets, if any */ + quicly_address_t dest, src; + struct iovec dgrams[10]; + uint8_t dgrams_buf[PTLS_ELEMENTSOF(dgrams) * ctx.transport_params.max_udp_payload_size]; + size_t num_dgrams = PTLS_ELEMENTSOF(dgrams); + int ret = quicly_send(client, &dest, &src, dgrams, &num_dgrams, dgrams_buf, sizeof(dgrams_buf)); + switch (ret) { + case 0: { + size_t j; + for (j = 0; j != num_dgrams; ++j) { + send_one(fd, &dest.sa, &dgrams[j]); + } + } break; + case QUICLY_ERROR_FREE_CONNECTION: + /* connection has been closed, free, and exit when running as a client */ + quicly_free(client); + return 0; + default: + fprintf(stderr, "quicly_send returned %d\n", ret); + return 1; + } + } + + return 0; +} + +static int on_stream_open(quicly_stream_open_t *self, quicly_stream_t *stream) +{ + static const quicly_stream_callbacks_t stream_callbacks = { + quicly_streambuf_destroy, quicly_streambuf_egress_shift, quicly_streambuf_egress_emit, on_stop_sending, on_receive, + on_receive_reset}; + int ret; + + if ((ret = quicly_streambuf_create(stream, sizeof(quicly_streambuf_t))) != 0) + return ret; + stream->callbacks = &stream_callbacks; + return 0; +} + +int main(int argc, char **argv) +{ + ptls_context_t tlsctx = { + .random_bytes = ptls_openssl_random_bytes, + .get_time = &ptls_get_time, + .key_exchanges = ptls_openssl_key_exchanges, + .cipher_suites = ptls_openssl_cipher_suites, + }; + quicly_stream_open_t stream_open = {on_stream_open}; + char *host = "127.0.0.1", *port = "4433"; + struct sockaddr_storage sa; + socklen_t salen; + int ch, fd; + + /* setup quic context */ + ctx = quicly_spec_context; + ctx.tls = &tlsctx; + quicly_amend_ptls_context(ctx.tls); + ctx.stream_open = &stream_open; + + /* resolve command line options and arguments */ + while ((ch = getopt(argc, argv, "p:h:v")) != -1) { + switch (ch) { + case 'p': /* port */ + port = optarg; + break; + case 'v': /* verbose */ + is_verbose = 1; + break; + case 'h': /* help */ + usage(argv[0]); + break; + default: + exit(1); + break; + } + } + argc -= optind; + argv += optind; + if (argc != 0) + host = *argv++; + if (resolve_address((struct sockaddr *)&sa, &salen, host, port, AF_INET, SOCK_DGRAM, 0) != 0) + exit(1); + + /* open socket on any port (as a client) */ + if ((fd = socket(sa.ss_family, SOCK_DGRAM, 0)) == -1) { + perror("socket(2) failed"); + exit(1); + } + // fcntl(fd, F_SETFL, O_NONBLOCK); + struct sockaddr_in local; + memset(&local, 0, sizeof(local)); + if (bind(fd, (struct sockaddr *)&local, sizeof(local)) != 0) { + perror("bind(2) failed"); + exit(1); + } + + quicly_conn_t *client = NULL; + /* initiate a connection, and open a stream */ + int ret; + if ((ret = quicly_connect(&client, &ctx, host, (struct sockaddr *)&sa, NULL, &next_cid, ptls_iovec_init(NULL, 0), NULL, + NULL)) != 0) { + fprintf(stderr, "quicly_connect failed:%d\n", ret); + exit(1); + } + quicly_stream_t *stream; /* we retain the opened stream via the on_stream_open callback */ + quicly_open_stream(client, &stream, 0); + + /* enter the event loop with a connection object */ + return run_loop_client(fd, client); +} diff --git a/examples/pipeserver.c b/examples/pipeserver.c new file mode 100644 index 000000000..9aceff3d5 --- /dev/null +++ b/examples/pipeserver.c @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2021 Jordi Cenzano + * Created from ./echo.c + */ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 /* required for glibc to use getaddrinfo, etc. */ +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "picotls.h" +#include "picotls/openssl.h" +#include "quicly.h" +#include "quicly/defaults.h" +#include "quicly/streambuf.h" + +/** + * the QUIC context + */ +static quicly_context_t ctx; +/** + * CID seed + */ +static quicly_cid_plaintext_t next_cid; +/** + * Verbose mode + */ + int is_verbose = 0; + + +static int resolve_address(struct sockaddr *sa, socklen_t *salen, const char *host, const char *port, int family, int type, + int proto) +{ + struct addrinfo hints, *res; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = type; + hints.ai_protocol = proto; + hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_PASSIVE; + if ((err = getaddrinfo(host, port, &hints, &res)) != 0 || res == NULL) { + fprintf(stderr, "failed to resolve address:%s:%s:%s\n", host, port, + err != 0 ? gai_strerror(err) : "getaddrinfo returned NULL"); + return -1; + } + + memcpy(sa, res->ai_addr, res->ai_addrlen); + *salen = res->ai_addrlen; + + freeaddrinfo(res); + return 0; +} + +static void usage(const char *progname) +{ + printf("Usage: %s [options] [host]\n" + "Options:\n" + " -c specifies the certificate chain file (PEM format)\n" + " -k specifies the private key file (PEM format)\n" + " -p specifies the port number (default: 4433)\n" + " -h prints this help\n" + "\n" + "`-c` and `-k` have to be be specified\n" + "If omitted, host defaults to 127.0.0.1.\n" + "In this case all info received will be output to stdout, so you should have only 1 connection active\n" + "\n" + "Example (receives live video over QUIC):\n" + "%s -c server.crt -k server.key -p 4433 | ffplay -i -\n", + progname, progname); + exit(0); +} + +static void on_stop_sending(quicly_stream_t *stream, int err) +{ + fprintf(stderr, "received STOP_SENDING: %" PRIu16 "\n", QUICLY_ERROR_GET_ERROR_CODE(err)); + quicly_close(stream->conn, QUICLY_ERROR_FROM_APPLICATION_ERROR_CODE(0), ""); +} + +static void on_receive_reset(quicly_stream_t *stream, int err) +{ + fprintf(stderr, "received RESET_STREAM: %" PRIu16 "\n", QUICLY_ERROR_GET_ERROR_CODE(err)); + quicly_close(stream->conn, QUICLY_ERROR_FROM_APPLICATION_ERROR_CODE(0), ""); +} + +static void on_receive(quicly_stream_t *stream, size_t off, const void *src, size_t len) +{ + /* read input to receive buffer */ + if (quicly_streambuf_ingress_receive(stream, off, src, len) != 0) + return; + + /* obtain contiguous bytes from the receive buffer */ + ptls_iovec_t input = quicly_streambuf_ingress_get(stream); + + /* server: echo back to the client */ + if (quicly_sendstate_is_open(&stream->sendstate) && (input.len > 0)) { + // Write received data to stdout + fwrite(input.base, 1, input.len, stdout); + fflush(stdout); + + // Show the received size to logs and send it to the client + char str[128]; + sprintf(str, "Received: %zu bytes\n", input.len); + if (is_verbose) + fprintf(stderr, "%s", str); + quicly_streambuf_egress_write(stream, str, strlen(str)); + // shutdown the stream after echoing all data + if (quicly_recvstate_transfer_complete(&stream->recvstate)) + quicly_streambuf_egress_shutdown(stream); + } + + /* remove used bytes from receive buffer */ + quicly_streambuf_ingress_shift(stream, input.len); +} + +static void process_msg(quicly_conn_t **conn, struct msghdr *msg, size_t dgram_len) +{ + size_t off = 0; + + /* split UDP datagram into multiple QUIC packets */ + while (off < dgram_len) { + quicly_decoded_packet_t decoded; + if (quicly_decode_packet(&ctx, &decoded, msg->msg_iov[0].iov_base, dgram_len, &off) == SIZE_MAX) + return; + /* find the corresponding connection (TODO handle version negotiation, rebinding, retry, etc.) */ + if (*conn != NULL) { + if (quicly_is_destination(*conn, NULL, msg->msg_name, &decoded)) { + quicly_receive(*conn, NULL, msg->msg_name, &decoded); + } else { + fprintf(stderr, "failed to accept new incoming connection, this server only allows 1 concurrent connection\n"); + } + } else { + /* assume that the packet is a new connection */ + quicly_accept(conn, &ctx, NULL, msg->msg_name, &decoded, NULL, &next_cid, NULL); + } + } +} + +static int send_one(int fd, struct sockaddr *dest, struct iovec *vec) +{ + struct msghdr mess = {.msg_name = dest, .msg_namelen = quicly_get_socklen(dest), .msg_iov = vec, .msg_iovlen = 1}; + int ret; + + while ((ret = (int)sendmsg(fd, &mess, 0)) == -1 && errno == EINTR) + ; + return ret; +} + +static int run_loop_server(int fd) +{ + quicly_conn_t *conn = NULL; /* this server only accepts a single connection */ + + while (1) { + + /* wait for sockets to become readable, or some event in the QUIC stack to fire */ + fd_set readfds; + struct timeval tv; + do { + int64_t first_timeout = INT64_MAX, now = ctx.now->cb(ctx.now); + if (conn != NULL) { + int64_t conn_timeout = quicly_get_first_timeout(conn); + if (conn_timeout < first_timeout) + first_timeout = conn_timeout; + } + if (now < first_timeout) { + int64_t delta = first_timeout - now; + if (delta > 1000 * 1000) + delta = 1000 * 1000; + tv.tv_sec = delta / 1000; + tv.tv_usec = (delta % 1000) * 1000; + } else { + tv.tv_sec = 1000; + tv.tv_usec = 0; + } + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + } while (select(fd + 1, &readfds, NULL, NULL, &tv) == -1 && errno == EINTR); + + /* read the QUIC fd */ + if (FD_ISSET(fd, &readfds)) { + uint8_t buf[4096]; + struct sockaddr_storage sa; + struct iovec vec = {.iov_base = buf, .iov_len = sizeof(buf)}; + struct msghdr msg = {.msg_name = &sa, .msg_namelen = sizeof(sa), .msg_iov = &vec, .msg_iovlen = 1}; + ssize_t rret; + while ((rret = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR) + ; + if (rret > 0) + process_msg(&conn, &msg, rret); + } + + /* send QUIC packets, if any */ + if (conn != NULL) { + quicly_address_t dest, src; + struct iovec dgrams[10]; + uint8_t dgrams_buf[PTLS_ELEMENTSOF(dgrams) * ctx.transport_params.max_udp_payload_size]; + size_t num_dgrams = PTLS_ELEMENTSOF(dgrams); + int ret = quicly_send(conn, &dest, &src, dgrams, &num_dgrams, dgrams_buf, sizeof(dgrams_buf)); + switch (ret) { + case 0: { + size_t j; + for (j = 0; j != num_dgrams; ++j) { + send_one(fd, &dest.sa, &dgrams[j]); + } + } break; + case QUICLY_ERROR_FREE_CONNECTION: + /* connection has been closed, free */ + quicly_free(conn); + conn = NULL; + break; + default: + fprintf(stderr, "quicly_send returned %d\n", ret); + return 1; + } + } + } + + return 0; +} + +static int on_stream_open(quicly_stream_open_t *self, quicly_stream_t *stream) +{ + static const quicly_stream_callbacks_t stream_callbacks = { + quicly_streambuf_destroy, quicly_streambuf_egress_shift, quicly_streambuf_egress_emit, on_stop_sending, on_receive, + on_receive_reset}; + int ret; + + if ((ret = quicly_streambuf_create(stream, sizeof(quicly_streambuf_t))) != 0) + return ret; + stream->callbacks = &stream_callbacks; + return 0; +} + +int main(int argc, char **argv) +{ + ptls_openssl_sign_certificate_t sign_certificate; + ptls_context_t tlsctx = { + .random_bytes = ptls_openssl_random_bytes, + .get_time = &ptls_get_time, + .key_exchanges = ptls_openssl_key_exchanges, + .cipher_suites = ptls_openssl_cipher_suites, + }; + quicly_stream_open_t stream_open = {on_stream_open}; + char *host = "127.0.0.1", *port = "4433"; + struct sockaddr_storage sa; + socklen_t salen; + int ch, fd; + + /* setup quic context */ + ctx = quicly_spec_context; + ctx.tls = &tlsctx; + quicly_amend_ptls_context(ctx.tls); + ctx.stream_open = &stream_open; + + /* resolve command line options and arguments */ + while ((ch = getopt(argc, argv, "c:k:p:h:v")) != -1) { + switch (ch) { + case 'c': /* load certificate chain */ { + int ret; + if ((ret = ptls_load_certificates(&tlsctx, optarg)) != 0) { + fprintf(stderr, "failed to load certificates from file %s:%d\n", optarg, ret); + exit(1); + } + } break; + case 'k': /* load private key */ { + FILE *fp; + if ((fp = fopen(optarg, "r")) == NULL) { + fprintf(stderr, "failed to open file:%s:%s\n", optarg, strerror(errno)); + exit(1); + } + EVP_PKEY *pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL); + fclose(fp); + if (pkey == NULL) { + fprintf(stderr, "failed to load private key from file:%s\n", optarg); + exit(1); + } + ptls_openssl_init_sign_certificate(&sign_certificate, pkey); + EVP_PKEY_free(pkey); + tlsctx.sign_certificate = &sign_certificate.super; + } break; + case 'p': /* port */ + port = optarg; + break; + case 'v': /* verbose */ + is_verbose = 1; + break; + case 'h': /* help */ + usage(argv[0]); + break; + default: + exit(1); + break; + } + } + if ((tlsctx.certificates.count <= 0) || (tlsctx.sign_certificate == NULL)) { + fprintf(stderr, "-c and -k options must be used\n"); + exit(1); + } + argc -= optind; + argv += optind; + if (argc != 0) + host = *argv++; + if (resolve_address((struct sockaddr *)&sa, &salen, host, port, AF_INET, SOCK_DGRAM, 0) != 0) + exit(1); + + /* open socket, on the specified port (as a server) */ + if ((fd = socket(sa.ss_family, SOCK_DGRAM, 0)) == -1) { + perror("socket(2) failed"); + exit(1); + } + // fcntl(fd, F_SETFL, O_NONBLOCK); + int reuseaddr = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); + if (bind(fd, (struct sockaddr *)&sa, salen) != 0) { + perror("bind(2) failed"); + exit(1); + } + + /* enter the event loop with a connection object */ + return run_loop_server(fd); +}