static ssize_t process_input_ssl()

in c/src/ssl/schannel.cpp [1627:1766]


static ssize_t process_input_ssl(pn_transport_t *transport, unsigned int layer, const char *input_data, size_t available)
{
  pni_ssl_t *ssl = transport->ssl;
  ssl_log( transport, PN_LEVEL_TRACE, "process_input_ssl( data size=%zu )",available );
  ssize_t consumed = 0;
  ssize_t forwarded = 0;
  bool new_app_input;

  if (available == 0) {
    // No more inbound network data
    read_closed(transport, layer, 0);
    return 0;
  }

  do {
    if (ssl->sc_input_shutdown) {
      // TLS protocol shutdown detected on input, so we are done.
      read_closed(transport, layer, 0);
      return PN_EOS;
    }

    // sc_inbuf should be ready for new or additional network encrypted bytes.
    // i.e. no straggling decrypted bytes pending.
    assert(ssl->in_data_count == 0 && ssl->decrypting);
    new_app_input = false;
    size_t count;

    if (ssl->state != RUNNING) {
      count = _pni_min(ssl->sc_in_size - ssl->sc_in_count, available);
    } else {
      // look for TLS record boundaries
      if (ssl->sc_in_count < 5) {
        ssl->sc_in_incomplete = true;
        size_t hc = _pni_min(available, 5 - ssl->sc_in_count);
        memmove(ssl->sc_inbuf + ssl->sc_in_count, input_data, hc);
        ssl->sc_in_count += hc;
        input_data += hc;
        available -= hc;
        consumed += hc;
        if (ssl->sc_in_count < 5 || available == 0)
          break;
      }

      // Top up sc_inbuf from network input_data hoping for a complete TLS Record
      // We try to guess the length as an optimization, but let SChannel
      // ultimately decide if there is spoofing going on.
      unsigned char low = (unsigned char) ssl->sc_inbuf[4];
      unsigned char high = (unsigned char) ssl->sc_inbuf[3];
      size_t rec_len = high * 256 + low + 5;
      if (rec_len < 5 || rec_len == ssl->sc_in_count || rec_len > ssl->sc_in_size)
        rec_len = ssl->sc_in_size;

      count = _pni_min(rec_len - ssl->sc_in_count, available);
    }

    if (count > 0) {
      memmove(ssl->sc_inbuf + ssl->sc_in_count, input_data, count);
      ssl->sc_in_count += count;
      input_data += count;
      available -= count;
      consumed += count;
      ssl->sc_in_incomplete = false;
    }

    // Try to decrypt another TLS Record.

    if (ssl->sc_in_count > 0 && ssl->state <= SHUTTING_DOWN) {
      if (ssl->state == NEGOTIATING) {
        ssl_handshake(transport);
      } else {
        if (ssl_decrypt(transport)) {
          // Ignore TLS Record with 0 length data (does not mean EOS)
          if (ssl->in_data_size > 0) {
            new_app_input = true;
            app_inbytes_add(transport);
          } else {
            assert(ssl->decrypting == false);
            rewind_sc_inbuf(ssl);
          }
        }
        ssl_log(transport, PN_LEVEL_TRACE, "Next decryption, %zu left over", available);
      }
    }

    if (ssl->state == SHUTTING_DOWN) {
      if (ssl->network_out_pending == 0 && !ssl->queued_shutdown) {
        start_ssl_shutdown(transport);
      }
    } else if (ssl->state == SSL_CLOSED) {
      return PN_EOS;
    }

    // Consume or discard the decrypted bytes
    if (new_app_input && (ssl->state == RUNNING || ssl->state == SHUTTING_DOWN)) {
      // present app_inbytes to io_next only if it has new content
      while (ssl->app_inbytes.size > 0) {
        if (!ssl->app_input_closed) {
          ssize_t count = transport->io_layers[layer+1]->process_input(transport, layer+1, ssl->app_inbytes.start, ssl->app_inbytes.size);
          if (count > 0) {
            forwarded += count;
            // advance() can increase app_inbytes.size if double buffered
            app_inbytes_advance(transport, count);
            ssl_log(transport, PN_LEVEL_TRACE, "Application consumed %d bytes from peer", (int) count);
          } else if (count == 0) {
            size_t old_size = ssl->app_inbytes.size;
            app_inbytes_advance(transport, 0);
            if (ssl->app_inbytes.size == old_size) {
              break;  // no additional contiguous decrypted data available, get more network data
            }
          } else {
            // count < 0
            ssl_log(transport, PN_LEVEL_WARNING, "Application layer closed its input, error=%d (discarding %d bytes)",
                 (int) count, (int)ssl->app_inbytes.size);
            app_inbytes_advance(transport, ssl->app_inbytes.size);    // discard
            read_closed(transport, layer, count);
          }
        } else {
          ssl_log(transport, PN_LEVEL_WARNING, "Input closed discard %d bytes",
               (int)ssl->app_inbytes.size);
          app_inbytes_advance(transport, ssl->app_inbytes.size);      // discard
        }
      }
    }
  } while (available || (ssl->sc_in_count && !ssl->sc_in_incomplete));

  if (ssl->state >= SHUTTING_DOWN) {
    if (ssl->app_input_closed || ssl->sc_input_shutdown) {
      // Next layer doesn't want more bytes, or it can't process without more data than it has seen so far
      // but the ssl stream has ended
      consumed = ssl->app_input_closed ? ssl->app_input_closed : PN_EOS;
      if (transport->io_layers[layer]==&ssl_output_closed_layer) {
        transport->io_layers[layer] = &ssl_closed_layer;
      } else {
        transport->io_layers[layer] = &ssl_input_closed_layer;
      }
    }
  }
  ssl_log(transport, PN_LEVEL_TRACE, "process_input_ssl() returning %d, forwarded %d", (int) consumed, (int) forwarded);
  return consumed;
}