+namespace {
+
+void flush_pending_data(int sock)
+{
+ // Flush pending data, which would otherwise wait for the 200ms TCP_CORK timer
+ // to elapsed; does not cancel out TCP_CORK (since that still takes priority),
+ // but does a one-off flush.
+ int one = 1;
+ if (setsockopt(sock, SOL_TCP, TCP_NODELAY, &one, sizeof(one)) == -1) {
+ log_perror("setsockopt(TCP_NODELAY)");
+ // Can still continue.
+ }
+}
+
+} // namespace
+
+bool Server::send_pending_tls_data(Client *client)
+{
+ // See if there's data from the TLS library to write.
+ if (client->tls_data_to_send == nullptr) {
+ client->tls_data_to_send = tls_get_write_buffer(client->tls_context, &client->tls_data_left_to_send);
+ if (client->tls_data_to_send == nullptr) {
+ // Really no data to send.
+ return false;
+ }
+ }
+
+send_data_again:
+ int ret;
+ do {
+ ret = write(client->sock, client->tls_data_to_send, client->tls_data_left_to_send);
+ } while (ret == -1 && errno == EINTR);
+ assert(ret < 0 || size_t(ret) <= client->tls_data_left_to_send);
+
+ if (ret == -1 && errno == EAGAIN) {
+ // We're out of socket space, so now we're at the “low edge” of epoll's
+ // edge triggering. epoll will tell us when there is more room, so for now,
+ // just return.
+ // This is postcondition #4.
+ return true;
+ }
+ if (ret == -1) {
+ // Error! Postcondition #1.
+ log_perror("write");
+ close_client(client);
+ return true;
+ }
+ if (ret > 0 && size_t(ret) == client->tls_data_left_to_send) {
+ // All data has been sent, so we don't need to go to sleep
+ // (although we are likely to do so immediately afterwards,
+ // due to lack of client data).
+ tls_buffer_clear(client->tls_context);
+ client->tls_data_to_send = nullptr;
+
+ // Flush the data we just wrote, since the client probably
+ // is waiting for it.
+ flush_pending_data(client->sock);
+ return false;
+ }
+
+ // More data to send, so try again.
+ client->tls_data_to_send += ret;
+ client->tls_data_left_to_send -= ret;
+ goto send_data_again;
+}
+
+int Server::read_plain_data(Client *client, char *buf, size_t max_size)
+{
+ int ret;
+ do {
+ ret = read(client->sock, buf, max_size);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret == -1 && errno == EAGAIN) {
+ // No more data right now. Nothing to do.
+ // This is postcondition #2.
+ return -1;
+ }
+ if (ret == -1) {
+ log_perror("read");
+ close_client(client);
+ return -1;
+ }
+ if (ret == 0) {
+ // OK, the socket is closed.
+ close_client(client);
+ return -1;
+ }
+
+ return ret;
+}
+
+int Server::read_tls_data(Client *client, char *buf, size_t max_size)
+{
+read_again:
+ assert(!client->in_ktls_mode);
+
+ int ret;
+ do {
+ ret = read(client->sock, buf, max_size);
+ } while (ret == -1 && errno == EINTR);
+
+ if (ret == -1 && errno == EAGAIN) {
+ // No more data right now. Nothing to do.
+ // This is postcondition #2.
+ return -1;
+ }
+ if (ret == -1) {
+ log_perror("read");
+ close_client(client);
+ return -1;
+ }
+ if (ret == 0) {
+ // OK, the socket is closed.
+ close_client(client);
+ return -1;
+ }
+
+ // Give it to the TLS library.
+ int err = tls_consume_stream(client->tls_context, reinterpret_cast<const unsigned char *>(buf), ret, nullptr);
+ if (err < 0) {
+ log_tls_error("tls_consume_stream", err);
+ close_client(client);
+ return -1;
+ }
+ if (err == 0) {
+ // Not consumed any data. See if we can read more.
+ goto read_again;
+ }
+
+ // Read any decrypted data available for us. (We can reuse buf, since it's free now.)
+ ret = tls_read(client->tls_context, reinterpret_cast<unsigned char *>(buf), max_size);
+ if (ret == 0) {
+ // No decrypted data for us yet, but there might be some more handshaking
+ // to send. Do that if needed, then look for more data.
+ if (send_pending_tls_data(client)) {
+ // send_pending_tls_data() hit postconditions #1 or #4.
+ return -1;
+ }
+ goto read_again;
+ }
+ if (ret < 0) {
+ log_tls_error("tls_read", ret);
+ close_client(client);
+ return -1;
+ }
+
+ if (tls_established(client->tls_context)) {
+ // We're ready to enter kTLS mode, unless we still have some
+ // handshake data to send (which then must be sent as non-kTLS).
+ if (send_pending_tls_data(client)) {
+ // send_pending_tls_data() hit postconditions #1 or #4.
+ return -1;
+ }
+ int err = tls_make_ktls(client->tls_context, client->sock); // Don't overwrite ret.
+ if (err < 0) {
+ log_tls_error("tls_make_ktls", ret);
+ close_client(client);
+ return -1;
+ }
+ client->in_ktls_mode = true;
+ }
+
+ assert(ret > 0);
+ return ret;
+}
+