diff --git a/Makefile.am b/Makefile.am index 9c9fd6c1cdd8b7e1b3176b4af26b0b48b5f18bb0..f697f6ddfc5bc4075d54aeac795e30c1cff9d211 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,7 +20,7 @@ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -SUBDIRS = lib tests +SUBDIRS = lib tests examples ACLOCAL_AMFLAGS = -I m4 @@ -30,4 +30,5 @@ ACLOCAL_AMFLAGS = -I m4 clang-format: CLANGFORMAT=`git config --get clangformat.binary`; \ test -z $${CLANGFORMAT} && CLANGFORMAT="clang-format"; \ - $${CLANGFORMAT} -i lib/*.{c,h} lib/includes/ngtcp2/*.h + $${CLANGFORMAT} -i lib/*.{c,h} lib/includes/ngtcp2/*.h \ + examples/*.{cc,h} diff --git a/configure.ac b/configure.ac index 5cf28e7552092529be0f731ac42bb9af3e6c9f4d..17523c15766f0f3fcc84a7c153b8fd971eab3ae5 100644 --- a/configure.ac +++ b/configure.ac @@ -67,6 +67,8 @@ AC_ARG_ENABLE([debug], # Checks for programs AC_PROG_CC +AC_PROG_CXX +AC_PROG_CPP AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET @@ -74,6 +76,8 @@ AC_PROG_MKDIR_P PKG_PROG_PKG_CONFIG([0.20]) +AX_CXX_COMPILE_STDCXX([14], [noext], [optional]) + # Checks for libraries. # cunit @@ -105,6 +109,29 @@ fi AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ]) +# openssl (for examples) +PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.1.1], + [have_openssl=yes], [have_openssl=no]) +if test "x${have_openssl}" = "xno"; then + AC_MSG_NOTICE($OPENSSL_PKG_ERRORS) +fi + +# libev (for examples) +# libev does not have pkg-config file. Check it in an old way. +save_LIBS=$LIBS +# android requires -lm for floor +AC_CHECK_LIB([ev], [ev_time], [have_libev=yes], [have_libev=no], [-lm]) +if test "x${have_libev}" = "xyes"; then + AC_CHECK_HEADER([ev.h], [have_libev=yes], [have_libev=no]) + if test "x${have_libev}" = "xyes"; then + LIBEV_LIBS=-lev + LIBEV_CFLAGS= + AC_SUBST([LIBEV_LIBS]) + AC_SUBST([LIBEV_CFLAGS]) + fi +fi +LIBS=$save_LIBS + # Checks for header files. AC_CHECK_HEADERS([ \ arpa/inet.h \ @@ -222,6 +249,7 @@ AC_CONFIG_FILES([ lib/includes/Makefile lib/includes/ngtcp2/version.h tests/Makefile + examples/Makefile ]) AC_OUTPUT @@ -250,4 +278,7 @@ AC_MSG_NOTICE([summary of build options: CUnit: ${have_cunit} (CFLAGS='${CUNIT_CFLAGS}' LIBS='${CUNIT_LIBS}') Debug: Debug: ${debug} + Libs: + OpenSSL: ${have_openssl} (CFLAGS='${OPENSSL_CFLAGS}' LIBS='${OPENSSL_LIBS}') + Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}') ]) diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..f2ad85300ebd7641c887c7ed39b2422037f0557a --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,2 @@ +client +server diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..43123e8f97e076f9dc604fcae7d205d1efdc177d --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,41 @@ +# ngtcp2 + +# Copyright (c) 2017 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +AM_CFLAGS = $(WARNCFLAGS) +AM_CPPFLAGS = \ + -I$(top_srcdir)/lib/includes \ + -I$(top_builddir)/lib/includes \ + @OPENSSL_CFLAGS@ \ + @LIBEV_CFLAGS@ \ + @DEFS@ +LDADD = $(top_builddir)/lib/libngtcp2.la \ + @OPENSSL_LIBS@ \ + @LIBEV_LIBS@ + +noinst_PROGRAMS = client server + +client_SOURCES = client.cc \ + template.h + +server_SOURCES = server.cc \ + template.h diff --git a/examples/client.cc b/examples/client.cc new file mode 100644 index 0000000000000000000000000000000000000000..08a6e32ea722431ca3acfe525783183608b360a3 --- /dev/null +++ b/examples/client.cc @@ -0,0 +1,490 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include <cstdlib> +#include <cassert> +#include <iostream> +#include <algorithm> + +#include <unistd.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +#include <openssl/bio.h> + +#include "client.h" +#include "template.h" +#include "network.h" + +using namespace ngtcp2; + +namespace { +void *BIO_get_data(BIO *bio) { return bio->ptr; } +void BIO_set_data(BIO *bio, void *ptr) { bio->ptr = ptr; } +void BIO_set_init(BIO *bio, int init) { bio->init = init; } +} // namespace + +namespace { +int bio_write(BIO *b, const char *buf, int len) { + BIO_clear_retry_flags(b); + + auto c = static_cast<Client *>(BIO_get_data(b)); + + c->write_client_handshake(reinterpret_cast<const uint8_t *>(buf), len); + + std::cerr << "ClientHello: " << len << " bytes" << std::endl; + ; + return len; +} +} // namespace + +namespace { +int bio_read(BIO *b, char *buf, int len) { + BIO_clear_retry_flags(b); + + auto c = static_cast<Client *>(BIO_get_data(b)); + + len = c->read_server_handshake(reinterpret_cast<uint8_t *>(buf), len); + if (len == 0) { + BIO_set_retry_read(b); + return -1; + } + + std::cerr << "ServerHello: " << len << " bytes" << std::endl; + + return len; +} +} // namespace + +namespace { +int bio_puts(BIO *b, const char *str) { return bio_write(b, str, strlen(str)); } +} // namespace + +namespace { +int bio_gets(BIO *b, char *buf, int len) { return -1; } +} // namespace + +namespace { +long bio_ctrl(BIO *b, int cmd, long num, void *ptr) { + switch (cmd) { + case BIO_CTRL_FLUSH: + return 1; + } + + return 0; +} +} // namespace + +namespace { +int bio_create(BIO *b) { + BIO_set_init(b, 1); + return 1; +} +} // namespace + +namespace { +int bio_destroy(BIO *b) { + if (b == nullptr) { + return 0; + } + + return 1; +} +} // namespace + +namespace { +BIO_METHOD *create_bio_method() { + static auto meth = new BIO_METHOD{ + BIO_TYPE_FD, "bio", bio_write, bio_read, bio_puts, + bio_gets, bio_ctrl, bio_create, bio_destroy, + }; + + return meth; +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) {} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto c = static_cast<Client *>(w->data); + + if (c->on_read() != 0) { + c->disconnect(); + } +} +} // namespace + +Client::Client(struct ev_loop *loop, SSL_CTX *ssl_ctx) + : loop_(loop), + ssl_ctx_(ssl_ctx), + ssl_(nullptr), + fd_(-1), + ncread_(0), + nsread_(0), + conn_(nullptr) { + ev_io_init(&wev_, writecb, 0, EV_WRITE); + ev_io_init(&rev_, readcb, 0, EV_READ); + wev_.data = this; + rev_.data = this; +} + +Client::~Client() { + disconnect(); +} + +void Client::disconnect() { + std::cerr << "disconnecting" << std::endl; + + ev_io_stop(loop_, &rev_); + ev_io_stop(loop_, &wev_); + + if (conn_) { + ngtcp2_conn_del(conn_); + conn_ = nullptr; + } + + if (ssl_) { + SSL_free(ssl_); + ssl_ = nullptr; + } + + if (fd_ != -1) { + close(fd_); + fd_ = -1; + } +} + +namespace { +ssize_t send_client_initial(ngtcp2_conn *conn, uint32_t flags, + uint64_t *ppkt_num, const uint8_t **pdest, + size_t maxdestlen, void *user_data) { + auto c = static_cast<Client *>(user_data); + + if (c->tls_handshake() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + *ppkt_num = 1; + + auto len = c->read_client_handshake(pdest, maxdestlen); + + std::cerr << "Client Initial: " << len << " bytes" << std::endl; + + return len; +} +} // namespace + +namespace { +ssize_t send_client_cleartext(ngtcp2_conn *conn, uint32_t flags, + const uint8_t **pdest, size_t maxdestlen, + void *user_data) { + auto c = static_cast<Client *>(user_data); + + if (c->tls_handshake() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + auto len = c->read_client_handshake(pdest, maxdestlen); + + std::cerr << "Client Cleartext: " << len << " bytes" << std::endl; + + return len; +} +} // namespace + +namespace { +int recv_handshake_data(ngtcp2_conn *conn, const uint8_t *data, size_t datalen, + void *user_data) { + auto c = static_cast<Client *>(user_data); + + c->write_server_handshake(data, datalen); + + if (c->tls_handshake() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::init(int fd) { + int rv; + + fd_ = fd; + ssl_ = SSL_new(ssl_ctx_); + auto bio = BIO_new(create_bio_method()); + BIO_set_data(bio, this); + SSL_set_bio(ssl_, bio, bio); + SSL_set_app_data(ssl_, this); + SSL_set_connect_state(ssl_); + + auto callbacks = ngtcp2_conn_callbacks{ + send_client_initial, send_client_cleartext, nullptr, recv_handshake_data, + }; + + rv = ngtcp2_conn_client_new(&conn_, 1, 1, &callbacks, this); + if (rv != 0) { + std::cerr << "ngtcp2_conn_client_new: " << rv << std::endl; + return -1; + } + + ev_io_set(&wev_, fd_, EV_WRITE); + ev_io_set(&rev_, fd_, EV_READ); + + ev_io_start(loop_, &rev_); + + return 0; +} + +int Client::tls_handshake() { + ERR_clear_error(); + + auto rv = SSL_do_handshake(ssl_); + if (rv <= 0) { + auto err = SSL_get_error(ssl_, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_SSL: + std::cerr << "TLS handshake error: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + default: + std::cerr << "TLS handshake error: " << err << std::endl; + return -1; + } + } + + return 0; +} + +int Client::feed_data(const uint8_t *data, size_t datalen) { + int rv; + + rv = ngtcp2_conn_recv(conn_, data, datalen); + if (rv != 0) { + std::cerr << "ngtcp2_conn_recv: " << rv << std::endl; + return -1; + } + + return 0; +} + +int Client::on_read() { + std::array<uint8_t, 65536> buf; + + auto nread = + recvfrom(fd_, buf.data(), buf.size(), MSG_DONTWAIT, nullptr, nullptr); + + if (nread == -1) { + std::cerr << "recvfrom: " << strerror(errno) << std::endl; + return 0; + } + + std::cerr << "Read " << nread << " from socket " << fd_ << std::endl; + + if (feed_data(buf.data(), nread) != 0) { + return -1; + } + + return on_write(); +} + +int Client::on_write() { + std::array<uint8_t, 1280> buf; + auto n = ngtcp2_conn_send(conn_, buf.data(), buf.size()); + if (n < 0) { + return -1; + } + if (n == 0) { + return 0; + } + + std::cerr << "Write " << n << " bytes of UDP payload" << std::endl; + + auto nwrite = write(fd_, buf.data(), n); + if (nwrite == -1) { + std::cerr << "write: " << strerror(errno) << std::endl; + return -1; + } + + std::cerr << "Wrote " << nwrite << " bytes" << std::endl; + + return 0; +} + +void Client::write_client_handshake(const uint8_t *data, size_t datalen) { + std::copy_n(data, datalen, std::back_inserter(chandshake_)); +} + +size_t Client::read_client_handshake(const uint8_t **pdest, size_t maxdestlen) { + auto n = std::min(maxdestlen, chandshake_.size() - ncread_); + *pdest = chandshake_.data() + ncread_; + ncread_ += n; + return n; +} + +size_t Client::read_server_handshake(uint8_t *buf, size_t buflen) { + auto n = std::min(buflen, shandshake_.size() - nsread_); + std::copy_n(std::begin(shandshake_) + nsread_, n, buf); + nsread_ += n; + return n; +} + +void Client::write_server_handshake(const uint8_t *data, size_t datalen) { + std::copy_n(data, datalen, std::back_inserter(shandshake_)); +} + +namespace { +SSL_CTX *create_ssl_ctx() { + auto ssl_ctx = SSL_CTX_new(TLS_method()); + + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); + + SSL_CTX_set_default_verify_paths(ssl_ctx); + + return ssl_ctx; +} +} // namespace + +namespace { +int create_sock(const char *addr, const char *port) { + addrinfo hints{}; + addrinfo *res, *rp; + int rv; + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + rv = getaddrinfo(addr, port, &hints, &res); + if (rv != 0) { + std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + int fd = -1; + + for (rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + continue; + } + + if (connect(fd, rp->ai_addr, rp->ai_addrlen) == -1) { + goto next; + } + + break; + + next: + close(fd); + } + + if (!rp) { + std::cerr << "Could not connect" << std::endl; + return -1; + } + + auto val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + return -1; + } + + return fd; +} + +} // namespace + +namespace { +int run(Client &c, const char *addr, const char *port) { + int rv; + + auto fd = create_sock(addr, port); + if (fd == -1) { + return -1; + } + + if (c.init(fd) != 0) { + return -1; + } + + c.on_write(); + + ev_run(EV_DEFAULT, 0); + + return 0; +} +} // namespace + +namespace { +void print_usage() { std::cerr << "Usage: client ADDR PORT" << std::endl; } +} // namespace + +int main(int argc, char **argv) { + for (;;) { + static int flag = 0; + constexpr static option long_opts[] = {{nullptr, 0, nullptr, 0}}; + + auto optidx = 0; + auto c = getopt_long(argc, argv, "", long_opts, &optidx); + if (c == -1) { + break; + } + switch (c) { + case '?': + print_usage(); + exit(EXIT_FAILURE); + default: + break; + }; + } + + if (argc - optind < 2) { + std::cerr << "Too few arguments" << std::endl; + print_usage(); + exit(EXIT_FAILURE); + } + + auto addr = argv[optind++]; + auto port = argv[optind++]; + + auto ssl_ctx = create_ssl_ctx(); + auto ssl_ctx_d = defer(SSL_CTX_free, ssl_ctx); + + Client c(EV_DEFAULT, ssl_ctx); + + if (run(c, addr, port) != 0) { + exit(EXIT_FAILURE); + } +} diff --git a/examples/client.h b/examples/client.h new file mode 100644 index 0000000000000000000000000000000000000000..d8d4843fb6776cc71299f06bc95da9af38f62606 --- /dev/null +++ b/examples/client.h @@ -0,0 +1,73 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef CLIENT_H +#define CLIENT_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif // HAVE_CONFIG_H + +#include <vector> + +#include <ngtcp2/ngtcp2.h> + +#include <openssl/ssl.h> + +#include <ev.h> + +class Client { +public: + Client(struct ev_loop *loop, SSL_CTX *ssl_ctx); + ~Client(); + + int init(int fd); + void disconnect(); + + int tls_handshake(); + int on_read(); + int on_write(); + int feed_data(const uint8_t *data, size_t datalen); + + void write_client_handshake(const uint8_t *data, size_t datalen); + size_t read_client_handshake(const uint8_t **pdest, size_t maxdestlen); + + size_t read_server_handshake(uint8_t *buf, size_t buflen); + void write_server_handshake(const uint8_t *data, size_t datalen); + +private: + ev_io wev_; + ev_io rev_; + struct ev_loop *loop_; + SSL_CTX *ssl_ctx_; + SSL *ssl_; + int fd_; + std::vector<uint8_t> chandshake_; + size_t ncread_; + std::vector<uint8_t> shandshake_; + size_t nsread_; + ngtcp2_conn *conn_; +}; + +#endif // CLIENT_H diff --git a/examples/network.h b/examples/network.h new file mode 100644 index 0000000000000000000000000000000000000000..4e9a44deed37e6c9ff4996494c6fd74b5c5c4158 --- /dev/null +++ b/examples/network.h @@ -0,0 +1,61 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2016 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NETWORK_H +#define NETWORK_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif // HAVE_CONFIG_H + +#include <sys/types.h> +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif // HAVE_SYS_SOCKET_H +#include <sys/un.h> +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif // HAVE_NETINET_IN_H +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif // HAVE_ARPA_INET_H + +namespace ngtcp2 { + +union sockaddr_union { + sockaddr_storage storage; + sockaddr sa; + sockaddr_in6 in6; + sockaddr_in in; +}; + +struct Address { + size_t len; + union sockaddr_union su; +}; + +} // namespace ngtcp2 + +#endif // NETWORK_H diff --git a/examples/server.cc b/examples/server.cc new file mode 100644 index 0000000000000000000000000000000000000000..110db1d380c5d8ebff99e9f68fce835cde8392bb --- /dev/null +++ b/examples/server.cc @@ -0,0 +1,611 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include <cstdlib> +#include <cassert> +#include <iostream> +#include <algorithm> + +#include <unistd.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +#include <openssl/bio.h> + +#include "server.h" +#include "template.h" +#include "network.h" + +using namespace ngtcp2; + +namespace { +void *BIO_get_data(BIO *bio) { return bio->ptr; } +void BIO_set_data(BIO *bio, void *ptr) { bio->ptr = ptr; } +void BIO_set_init(BIO *bio, int init) { bio->init = init; } +} // namespace + +namespace { +int bio_write(BIO *b, const char *buf, int len) { + BIO_clear_retry_flags(b); + + auto h = static_cast<Handler *>(BIO_get_data(b)); + + h->write_server_handshake(reinterpret_cast<const uint8_t *>(buf), len); + + std::cerr << "ServerHello: " << len << " bytes" << std::endl; + ; + return len; +} +} // namespace + +namespace { +int bio_read(BIO *b, char *buf, int len) { + BIO_clear_retry_flags(b); + + auto h = static_cast<Handler *>(BIO_get_data(b)); + + len = h->read_client_handshake(reinterpret_cast<uint8_t *>(buf), len); + if (len == 0) { + BIO_set_retry_read(b); + return -1; + } + + std::cerr << "ClientHello: " << len << " bytes" << std::endl; + ; + + return len; +} +} // namespace + +namespace { +int bio_puts(BIO *b, const char *str) { return bio_write(b, str, strlen(str)); } +} // namespace + +namespace { +int bio_gets(BIO *b, char *buf, int len) { return -1; } +} // namespace + +namespace { +long bio_ctrl(BIO *b, int cmd, long num, void *ptr) { + switch (cmd) { + case BIO_CTRL_FLUSH: + return 1; + } + + return 0; +} +} // namespace + +namespace { +int bio_create(BIO *b) { + BIO_set_init(b, 1); + return 1; +} +} // namespace + +namespace { +int bio_destroy(BIO *b) { + if (b == nullptr) { + return 0; + } + + return 1; +} +} // namespace + +namespace { +BIO_METHOD *create_bio_method() { + static auto meth = new BIO_METHOD{ + BIO_TYPE_FD, "bio", bio_write, bio_read, bio_puts, + bio_gets, bio_ctrl, bio_create, bio_destroy, + }; + + return meth; +} +} // namespace + +namespace { +void hwritecb(struct ev_loop *loop, ev_io *w, int revents) { + auto h = static_cast<Handler *>(w->data); + + if (h->on_write() != 0) { + delete h; + } +} +} // namespace + +namespace { +void hreadcb(struct ev_loop *loop, ev_io *w, int revents) { + auto h = static_cast<Handler *>(w->data); + + if (h->on_read() != 0) { + delete h; + } +} +} // namespace + +Handler::Handler(struct ev_loop *loop, SSL_CTX *ssl_ctx) + : loop_(loop), + ssl_ctx_(ssl_ctx), + ssl_(nullptr), + fd_(-1), + ncread_(0), + nsread_(0), + conn_(nullptr) { + ev_io_init(&wev_, hwritecb, 0, EV_WRITE); + ev_io_init(&rev_, hreadcb, 0, EV_READ); + wev_.data = this; + rev_.data = this; +} + +Handler::~Handler() { + ev_io_stop(loop_, &rev_); + ev_io_stop(loop_, &wev_); + + if (conn_) { + ngtcp2_conn_del(conn_); + } + + if (ssl_) { + SSL_free(ssl_); + } + + if (fd_ != -1) { + close(fd_); + } + assert(0); +} + +namespace { +ssize_t send_server_cleartext(ngtcp2_conn *conn, uint32_t flags, + uint64_t *ppkt_num, const uint8_t **pdest, + size_t maxdestlen, void *user_data) { + auto h = static_cast<Handler *>(user_data); + + if (h->tls_handshake() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (ppkt_num) { + *ppkt_num = 1; + } + + auto len = h->read_server_handshake(pdest, maxdestlen); + + std::cerr << "Server Cleartext: " << len << " bytes" << std::endl; + + return len; +} +} // namespace + +namespace { +int recv_handshake_data(ngtcp2_conn *conn, const uint8_t *data, size_t datalen, + void *user_data) { + auto h = static_cast<Handler *>(user_data); + + h->write_client_handshake(data, datalen); + + if (h->tls_handshake() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Handler::init(int fd) { + int rv; + + fd_ = fd; + ssl_ = SSL_new(ssl_ctx_); + auto bio = BIO_new(create_bio_method()); + BIO_set_data(bio, this); + SSL_set_bio(ssl_, bio, bio); + SSL_set_app_data(ssl_, this); + SSL_set_accept_state(ssl_); + + auto callbacks = ngtcp2_conn_callbacks{ + nullptr, nullptr, send_server_cleartext, recv_handshake_data, + }; + + rv = ngtcp2_conn_server_new(&conn_, 2, 1, &callbacks, this); + if (rv != 0) { + std::cerr << "ngtcp2_conn_server_new: " << rv << std::endl; + return -1; + } + + ev_io_set(&wev_, fd_, EV_WRITE); + ev_io_set(&rev_, fd_, EV_READ); + + ev_io_start(loop_, &rev_); + + return 0; +} + +int Handler::tls_handshake() { + ERR_clear_error(); + + auto rv = SSL_do_handshake(ssl_); + if (rv <= 0) { + auto err = SSL_get_error(ssl_, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + break; + case SSL_ERROR_SSL: + std::cerr << "TLS handshake error: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + default: + std::cerr << "TLS handshake error: " << err << std::endl; + return -1; + } + } + + return 0; +} + +void Handler::write_server_handshake(const uint8_t *data, size_t datalen) { + std::copy_n(data, datalen, std::back_inserter(chandshake_)); +} + +size_t Handler::read_server_handshake(const uint8_t **pdest, + size_t maxdestlen) { + auto n = std::min(maxdestlen, chandshake_.size() - ncread_); + *pdest = chandshake_.data() + ncread_; + ncread_ += n; + return n; +} + +size_t Handler::read_client_handshake(uint8_t *buf, size_t buflen) { + auto n = std::min(buflen, shandshake_.size() - nsread_); + std::copy_n(std::begin(shandshake_) + nsread_, n, buf); + nsread_ += n; + return n; +} + +void Handler::write_client_handshake(const uint8_t *data, size_t datalen) { + std::copy_n(data, datalen, std::back_inserter(shandshake_)); +} + +int Handler::feed_data(const uint8_t *data, size_t datalen) { + int rv; + + rv = ngtcp2_conn_recv(conn_, data, datalen); + if (rv != 0) { + std::cerr << "ngtcp2_conn_recv: " << rv << std::endl; + return -1; + } + + return 0; +} + +int Handler::on_read() { + sockaddr_union su; + socklen_t addrlen = sizeof(su); + std::array<uint8_t, 1280> buf; + + auto nread = + recvfrom(fd_, buf.data(), buf.size(), MSG_DONTWAIT, &su.sa, &addrlen); + if (nread == -1) { + std::cerr << "recvfrom: " << strerror(errno) << std::endl; + return 0; + } + + std::cerr << "Read " << nread << " from socket " << fd_ << std::endl; + + if (feed_data(buf.data(), nread) != 0) { + return -1; + } + + return on_write(); +} + +int Handler::on_write() { + std::array<uint8_t, 1280> buf; + std::cerr << "on_write" << std::endl; + + for (;;) { + auto n = ngtcp2_conn_send(conn_, buf.data(), buf.size()); + if (n < 0) { + return -1; + } + if (n == 0) { + return 0; + } + + std::cerr << "Write " << n << " bytes of UDP payload" << std::endl; + + auto nwrite = write(fd_, buf.data(), n); + if (nwrite == -1) { + std::cerr << "write: " << strerror(errno) << std::endl; + return -1; + } + + std::cerr << "Wrote " << nwrite << " bytes" << std::endl; + } +} + +void Handler::signal_write() { ev_feed_event(loop_, &wev_, EV_WRITE); } + +namespace { +void swritecb(struct ev_loop *loop, ev_io *w, int revents) {} +} // namespace + +namespace { +void sreadcb(struct ev_loop *loop, ev_io *w, int revents) { + auto s = static_cast<Server *>(w->data); + + s->on_read(); +} +} // namespace + +Server::Server(struct ev_loop *loop, SSL_CTX *ssl_ctx) + : loop_(loop), ssl_ctx_(ssl_ctx), fd_(-1) { + ev_io_init(&wev_, swritecb, 0, EV_WRITE); + ev_io_init(&rev_, sreadcb, 0, EV_READ); + wev_.data = this; + rev_.data = this; +} + +Server::~Server() { + ev_io_stop(loop_, &rev_); + ev_io_stop(loop_, &wev_); + + if (fd_ != -1) { + close(fd_); + } +} + +int Server::init(int fd) { + fd_ = fd; + + ev_io_set(&wev_, fd_, EV_WRITE); + ev_io_set(&rev_, fd_, EV_READ); + + ev_io_start(loop_, &rev_); + + return 0; +} + +int Server::on_read() { + sockaddr_union su; + socklen_t addrlen = sizeof(su); + std::array<uint8_t, 1280> buf; + + auto nread = + recvfrom(fd_, buf.data(), buf.size(), MSG_DONTWAIT, &su.sa, &addrlen); + if (nread == -1) { + std::cerr << "recvfrom: " << strerror(errno) << std::endl; + // TODO Handle running out of fd + return 0; + } + + auto fd = socket(su.storage.ss_family, SOCK_DGRAM, 0); + if (fd == -1) { + std::cerr << "socket: " << strerror(errno) << std::endl; + return 0; + } + + auto val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + return 0; + } + + { + sockaddr_union su; + socklen_t addrlen = sizeof(su); + + if (getsockname(fd_, &su.sa, &addrlen) == -1) { + std::cerr << "getsockname: " << strerror(errno) << std::endl; + } + + if (bind(fd, &su.sa, addrlen) == -1) { + std::cerr << "bind: " << strerror(errno) << std::endl; + } + } + + if (connect(fd, &su.sa, addrlen) == -1) { + std::cerr << "connect: " << strerror(errno) << std::endl; + close(fd); + return 0; + } + + auto h = std::make_unique<Handler>(loop_, ssl_ctx_); + h->init(fd); + if (h->feed_data(buf.data(), nread) != 0) { + return 0; + } + h->signal_write(); + h.release(); + + return 0; +} + +namespace { +SSL_CTX *create_ssl_ctx(const char *private_key_file, const char *cert_file) { + auto ssl_ctx = SSL_CTX_new(TLS_method()); + + constexpr auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_SINGLE_ECDH_USE | + SSL_OP_CIPHER_SERVER_PREFERENCE; + + SSL_CTX_set_options(ssl_ctx, ssl_opts); + SSL_CTX_set1_curves_list(ssl_ctx, "p-256"); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS); + + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); + + SSL_CTX_set_default_verify_paths(ssl_ctx); + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file, + SSL_FILETYPE_PEM) != 1) { + std::cerr << "SSL_CTX_use_PrivateKey_file: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + goto fail; + } + + if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { + std::cerr << "SSL_CTX_use_certificate_file: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + goto fail; + } + + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + std::cerr << "SSL_CTX_check_private_key: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + goto fail; + } + + return ssl_ctx; + +fail: + SSL_CTX_free(ssl_ctx); + return nullptr; +} +} // namespace + +namespace { +int create_sock(const char *addr, const char *port) { + addrinfo hints{}; + addrinfo *res, *rp; + int rv; + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + rv = getaddrinfo(addr, port, &hints, &res); + if (rv != 0) { + std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + int fd = -1; + + for (rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + continue; + } + + if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; + } + + close(fd); + } + + if (!rp) { + std::cerr << "Could not bind" << std::endl; + return -1; + } + + auto val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + return -1; + } + + return fd; +} + +} // namespace + +namespace { +int serve(Server &s, const char *addr, const char *port) { + int rv; + + auto fd = create_sock(addr, port); + if (fd == -1) { + return -1; + } + + if (s.init(fd) != 0) { + return -1; + } + + ev_run(EV_DEFAULT, 0); + + return 0; +} +} // namespace + +namespace { +void print_usage() { + std::cerr << "Usage: server ADDR PORT PRIVATE_KEY_FILE CERTIFICATE_FILE" + << std::endl; +} +} // namespace + +int main(int argc, char **argv) { + for (;;) { + static int flag = 0; + constexpr static option long_opts[] = {{nullptr, 0, nullptr, 0}}; + + auto optidx = 0; + auto c = getopt_long(argc, argv, "", long_opts, &optidx); + if (c == -1) { + break; + } + switch (c) { + case '?': + print_usage(); + exit(EXIT_FAILURE); + default: + break; + }; + } + + if (argc - optind < 4) { + std::cerr << "Too few arguments" << std::endl; + print_usage(); + exit(EXIT_FAILURE); + } + + auto addr = argv[optind++]; + auto port = argv[optind++]; + auto private_key_file = argv[optind++]; + auto cert_file = argv[optind++]; + + auto ssl_ctx = create_ssl_ctx(private_key_file, cert_file); + if (ssl_ctx == nullptr) { + exit(EXIT_FAILURE); + } + + auto ssl_ctx_d = defer(SSL_CTX_free, ssl_ctx); + + Server s(EV_DEFAULT, ssl_ctx); + + if (serve(s, addr, port) != 0) { + exit(EXIT_FAILURE); + } +} diff --git a/examples/server.h b/examples/server.h new file mode 100644 index 0000000000000000000000000000000000000000..532555b1168cfeae1f7ff5023cd5cd712f58fb4b --- /dev/null +++ b/examples/server.h @@ -0,0 +1,88 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SERVER_H +#define SERVER_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif // HAVE_CONFIG_H + +#include <vector> + +#include <ngtcp2/ngtcp2.h> + +#include <openssl/ssl.h> +#include <ev.h> + +class Handler { +public: + Handler(struct ev_loop *loop, SSL_CTX *ssl_ctx); + ~Handler(); + + int init(int fd); + int tls_handshake(); + int on_read(); + int on_write(); + int feed_data(const uint8_t *data, size_t datalen); + void signal_write(); + + void write_server_handshake(const uint8_t *data, size_t datalen); + size_t read_server_handshake(const uint8_t **pdest, size_t maxdestlen); + + size_t read_client_handshake(uint8_t *buf, size_t buflen); + void write_client_handshake(const uint8_t *data, size_t datalen); + +private: + struct ev_loop *loop_; + SSL_CTX *ssl_ctx_; + SSL *ssl_; + int fd_; + ev_io wev_; + ev_io rev_; + std::vector<uint8_t> chandshake_; + size_t ncread_; + std::vector<uint8_t> shandshake_; + size_t nsread_; + ngtcp2_conn *conn_; +}; + +class Server { +public: + Server(struct ev_loop *loop, SSL_CTX *ssl_ctx); + ~Server(); + + int init(int fd); + int on_read(); + +private: + struct ev_loop *loop_; + SSL_CTX *ssl_ctx_; + int fd_; + ev_io wev_; + ev_io rev_; +}; + + +#endif // SERVER_H diff --git a/examples/template.h b/examples/template.h new file mode 100644 index 0000000000000000000000000000000000000000..39f6ef0932bfcea06a8bf9655a34bcd76a395f50 --- /dev/null +++ b/examples/template.h @@ -0,0 +1,50 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2015 ngttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TEMPLATE_H +#define TEMPLATE_H + +#include <functional> +#include <utility> +#include <type_traits> + +// inspired by <http://blog.korfuri.fr/post/go-defer-in-cpp/>, but our +// template can take functions returning other than void. +template <typename F, typename... T> struct Defer { + Defer(F &&f, T &&... t) + : f(std::bind(std::forward<F>(f), std::forward<T>(t)...)) {} + Defer(Defer &&o) noexcept : f(std::move(o.f)) {} + ~Defer() { f(); } + + using ResultType = typename std::result_of<typename std::decay<F>::type( + typename std::decay<T>::type...)>::type; + std::function<ResultType()> f; +}; + +template <typename F, typename... T> Defer<F, T...> defer(F &&f, T &&... t) { + return Defer<F, T...>(std::forward<F>(f), std::forward<T>(t)...); +} + +#endif // TEMPLATE_H diff --git a/lib/Makefile.am b/lib/Makefile.am index f930ea463494d4054a0ee92eaf3e42ea49b667e8..d955dca00ebf380695047dc5e02c9eb2601bbe86 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -23,7 +23,7 @@ SUBDIRS = includes AM_CFLAGS = $(WARNCFLAGS) $(EXTRACFLAG) -AM_CPPFLAGS = -I$(srcdir)/includes -I$(builddir)/includes +AM_CPPFLAGS = -I$(srcdir)/includes -I$(builddir)/includes -DBUILDING_NGTCP2 pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libngtcp2.pc diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c index d55b1392778356f3df80345303d642ca756cd235..29c92778f80bb7096a34151c9c6bed140ce0508a 100644 --- a/lib/ngtcp2_conn.c +++ b/lib/ngtcp2_conn.c @@ -116,7 +116,8 @@ static ssize_t ngtcp2_conn_send_client_initial(ngtcp2_conn *conn, uint8_t *dest, return NGTCP2_ERR_INVALID_ARGUMENT; } - maxpayloadlen = destlen - NGTCP2_LONG_HEADERLEN - NGTCP2_PKT_MDLEN; + maxpayloadlen = destlen - NGTCP2_LONG_HEADERLEN - NGTCP2_STREAM_OVERHEAD - + NGTCP2_PKT_MDLEN; payloadlen = conn->callbacks.send_client_initial( conn, NGTCP2_CONN_FLAG_NONE, &pkt_num, &payload, maxpayloadlen, @@ -153,7 +154,7 @@ static ssize_t ngtcp2_conn_send_client_initial(ngtcp2_conn *conn, uint8_t *dest, ngtcp2_upe_padding(&upe); - conn->strm0.offset += (size_t)payloadlen; + conn->strm0.tx_offset += (size_t)payloadlen; return (ssize_t)ngtcp2_upe_final(&upe, NULL); } @@ -173,13 +174,17 @@ static ssize_t ngtcp2_conn_send_client_cleartext(ngtcp2_conn *conn, return NGTCP2_ERR_INVALID_ARGUMENT; } - maxpayloadlen = destlen - NGTCP2_LONG_HEADERLEN - NGTCP2_PKT_MDLEN; + maxpayloadlen = destlen - NGTCP2_LONG_HEADERLEN - NGTCP2_STREAM_OVERHEAD - + NGTCP2_PKT_MDLEN; payloadlen = conn->callbacks.send_client_cleartext( conn, NGTCP2_CONN_FLAG_NONE, &payload, maxpayloadlen, conn->user_data); - if (payloadlen <= 0) { + if (payloadlen < 0) { return NGTCP2_ERR_CALLBACK_FAILURE; } + if (payloadlen == 0) { + return 0; + } ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_CLIENT_INITIAL, conn->conn_id, conn->next_out_pkt_num++, conn->version); @@ -195,7 +200,7 @@ static ssize_t ngtcp2_conn_send_client_cleartext(ngtcp2_conn *conn, fr.stream.flags = 0; fr.stream.fin = 0; fr.stream.stream_id = 0; - fr.stream.offset = conn->strm0.offset; + fr.stream.offset = conn->strm0.tx_offset; fr.stream.datalen = (size_t)payloadlen; fr.stream.data = payload; @@ -204,7 +209,7 @@ static ssize_t ngtcp2_conn_send_client_cleartext(ngtcp2_conn *conn, return rv; } - conn->strm0.offset += (size_t)payloadlen; + conn->strm0.tx_offset += (size_t)payloadlen; return (ssize_t)ngtcp2_upe_final(&upe, NULL); } @@ -225,7 +230,8 @@ static ssize_t ngtcp2_conn_send_server_cleartext(ngtcp2_conn *conn, return NGTCP2_ERR_INVALID_ARGUMENT; } - maxpayloadlen = destlen - NGTCP2_LONG_HEADERLEN - NGTCP2_PKT_MDLEN; + maxpayloadlen = destlen - NGTCP2_LONG_HEADERLEN - NGTCP2_STREAM_OVERHEAD - + NGTCP2_PKT_MDLEN; payloadlen = conn->callbacks.send_server_cleartext( conn, NGTCP2_CONN_FLAG_NONE, initial ? &pkt_num : NULL, &payload, @@ -263,7 +269,7 @@ static ssize_t ngtcp2_conn_send_server_cleartext(ngtcp2_conn *conn, fr.stream.flags = 0; fr.stream.fin = 0; fr.stream.stream_id = 0; - fr.stream.offset = conn->strm0.offset; + fr.stream.offset = conn->strm0.tx_offset; fr.stream.datalen = (size_t)payloadlen; fr.stream.data = payload; @@ -272,13 +278,13 @@ static ssize_t ngtcp2_conn_send_server_cleartext(ngtcp2_conn *conn, return rv; } - conn->strm0.offset += (size_t)payloadlen; + conn->strm0.tx_offset += (size_t)payloadlen; return (ssize_t)ngtcp2_upe_final(&upe, NULL); } ssize_t ngtcp2_conn_send(ngtcp2_conn *conn, uint8_t *dest, size_t destlen) { - ssize_t rv; + ssize_t rv = 0; switch (conn->state) { case NGTCP2_CS_CLIENT_INITIAL: @@ -311,7 +317,7 @@ ssize_t ngtcp2_conn_send(ngtcp2_conn *conn, uint8_t *dest, size_t destlen) { break; } - return 0; + return rv; } static int ngtcp2_conn_recv_cleartext(ngtcp2_conn *conn, const uint8_t *pkt, @@ -341,7 +347,7 @@ static int ngtcp2_conn_recv_cleartext(ngtcp2_conn *conn, const uint8_t *pkt, conn->conn_id = hd.conn_id; } - for (;;) { + for (; pktlen;) { nread = ngtcp2_pkt_decode_frame(&fr, pkt, pktlen); if (nread < 0) { return (int)nread; @@ -351,12 +357,12 @@ static int ngtcp2_conn_recv_cleartext(ngtcp2_conn *conn, const uint8_t *pkt, pktlen -= (size_t)nread; if (fr.type != NGTCP2_FRAME_STREAM || fr.stream.stream_id != 0 || - conn->strm0.offset >= fr.stream.offset + fr.stream.datalen) { + conn->strm0.rx_offset >= fr.stream.offset + fr.stream.datalen) { continue; } - if (conn->strm0.offset == fr.stream.offset) { - conn->strm0.offset += fr.stream.datalen; + if (conn->strm0.rx_offset == fr.stream.offset) { + conn->strm0.rx_offset += fr.stream.datalen; rv = conn->callbacks.recv_handshake_data( conn, fr.stream.data, fr.stream.datalen, conn->user_data); @@ -426,7 +432,7 @@ int ngtcp2_conn_recv(ngtcp2_conn *conn, const uint8_t *pkt, size_t pktlen) { break; } - return -1; + return rv; } int ngtcp2_conn_emit_pending_recv_handshake(ngtcp2_conn *conn, @@ -436,11 +442,13 @@ int ngtcp2_conn_emit_pending_recv_handshake(ngtcp2_conn *conn, int rv; for (;;) { - datalen = ngtcp2_rob_data_at(&strm->rob, &data, strm->offset); + datalen = ngtcp2_rob_data_at(&strm->rob, &data, strm->rx_offset); if (datalen == 0) { return 0; } + strm->rx_offset += datalen; + rv = conn->callbacks.recv_handshake_data(conn, data, datalen, conn->user_data); if (rv != 0) { @@ -452,7 +460,8 @@ int ngtcp2_conn_emit_pending_recv_handshake(ngtcp2_conn *conn, } int ngtcp2_strm_init(ngtcp2_strm *strm, ngtcp2_mem *mem) { - strm->offset = 0; + strm->tx_offset = 0; + strm->rx_offset = 0; strm->nbuffered = 0; strm->mem = mem; return ngtcp2_rob_init(&strm->rob, mem); diff --git a/lib/ngtcp2_conn.h b/lib/ngtcp2_conn.h index 07c6021e9a173299840d4044b5ef0a37ba6d7e2a..9242982a62a24a94703067bd13f38a91b128fa5a 100644 --- a/lib/ngtcp2_conn.h +++ b/lib/ngtcp2_conn.h @@ -52,7 +52,8 @@ typedef enum { } ngtcp2_conn_state; typedef struct { - uint64_t offset; + uint64_t rx_offset; + uint64_t tx_offset; ngtcp2_rob rob; ngtcp2_mem *mem; size_t nbuffered; diff --git a/lib/ngtcp2_pkt.h b/lib/ngtcp2_pkt.h index 1eabb24c10f710f18db9425930dff9d80d184186..a940c5f83d5ed60aeb0c3373f1078cb6418482ce 100644 --- a/lib/ngtcp2_pkt.h +++ b/lib/ngtcp2_pkt.h @@ -45,6 +45,11 @@ #define NGTCP2_STREAM_OO_MASK 0x06 #define NGTCP2_STREAM_D_BIT 0x01 +/* NGTCP2_STREAM_OVERHEAD is the maximum number of bytes required + other than payload for STREAM frame. That is from type field to + the beginning of the payload. */ +#define NGTCP2_STREAM_OVERHEAD 15 + #define NGTCP2_ACK_N_BIT 0x10 #define NGTCP2_ACK_LL_MASK 0x0c #define NGTCP2_ACK_MM_MASK 0x03