/* * HTTPSConnection.cpp * * Created on: Dec 6, 2017 * Author: frank */ #include "HTTPSConnection.hpp" namespace httpsserver { HTTPSConnection::HTTPSConnection(ResourceResolver * resResolver): _resResolver(resResolver) { _socket = -1; _addrLen = 0; _ssl = NULL; _bufferProcessed = 0; _bufferUnusedIdx = 0; _connectionState = STATE_UNDEFINED; _clientState = CSTATE_UNDEFINED; _httpHeaders = NULL; _defaultHeaders = NULL; _isKeepAlive = false; _lastTransmissionTS = millis(); _shutdownTS = 0; } HTTPSConnection::~HTTPSConnection() { // Close the socket closeConnection(); } /** * Initializes the connection from a server socket. * * The call WILL BLOCK if accept(serverSocketID) blocks. So use select() to check for that in advance. */ int HTTPSConnection::initialize(int serverSocketID, SSL_CTX * sslCtx, HTTPHeaders *defaultHeaders) { if (_connectionState == STATE_UNDEFINED) { _defaultHeaders = defaultHeaders; _socket = accept(serverSocketID, (struct sockaddr * )&_sockAddr, &_addrLen); // Build up SSL Connection context if the socket has been created successfully if (_socket >= 0) { HTTPS_DLOGHEX("[-->] New connection. Socket fid is: ", _socket); _ssl = SSL_new(sslCtx); if (_ssl) { // Bind SSL to the socket int success = SSL_set_fd(_ssl, _socket); if (success) { // Perform the handshake success = SSL_accept(_ssl); if (success) { _connectionState = STATE_INITIAL; _httpHeaders = new HTTPHeaders(); refreshTimeout(); return _socket; } else { HTTPS_DLOG("[ERR] SSL_accept failed. Aborting handshake."); } } else { HTTPS_DLOG("[ERR] SSL_set_fd failed. Aborting handshake."); } } else { HTTPS_DLOG("[ERR] SSL_new failed. Aborting handshake."); } } else { HTTPS_DLOG("[ERR] Could not accept() new connection"); } _connectionState = STATE_ERROR; _clientState = CSTATE_ACTIVE; // This will only be called if the connection could not be established and cleanup // variables like _ssl etc. closeConnection(); } // Error: The connection has already been established or could not be established return -1; } /** * True if the connection is timed out. * * (Should be checkd in the loop and transition should go to CONNECTION_CLOSE if exceeded) */ bool HTTPSConnection::isTimeoutExceeded() { return _lastTransmissionTS + HTTPS_CONNECTION_TIMEOUT < millis(); } /** * Resets the timeout to allow again the full HTTPS_CONNECTION_TIMEOUT milliseconds */ void HTTPSConnection::refreshTimeout() { _lastTransmissionTS = millis(); } /** * Returns true, if the connection has been closed. */ bool HTTPSConnection::isClosed() { return (_connectionState == STATE_ERROR || _connectionState == STATE_CLOSED); } /** * Returns true, if the connection has been closed due to error */ bool HTTPSConnection::isError() { return (_connectionState == STATE_ERROR); } void HTTPSConnection::closeConnection() { // TODO: Call an event handler here, maybe? if (_connectionState != STATE_ERROR && _connectionState != STATE_CLOSED) { // First call to closeConnection - set the timestamp to calculate the timeout later on if (_connectionState != STATE_CLOSING) { _shutdownTS = millis(); } // Set the connection state to closing. We stay in closing as long as SSL has not been shutdown // correctly _connectionState = STATE_CLOSING; } // Try to tear down SSL while we are in the _shutdownTS timeout period or if an error occurred if (_ssl) { if(_connectionState == STATE_ERROR || SSL_shutdown(_ssl) == 0) { // SSL_shutdown will return 1 as soon as the client answered with close notify // This means we are safe to close the socket SSL_free(_ssl); _ssl = NULL; } else if (_shutdownTS + HTTPS_SHUTDOWN_TIMEOUT < millis()) { // The timeout has been hit, we force SSL shutdown now by freeing the context SSL_free(_ssl); _ssl = NULL; HTTPS_DLOG("[ERR] SSL_shutdown did not receive close notification from the client"); _connectionState = STATE_ERROR; } } // If SSL has been brought down, close the socket if (!_ssl) { // Tear down the socket if (_socket >= 0) { HTTPS_DLOGHEX("[<--] Connection has been closed. fid = ", _socket); close(_socket); _socket = -1; _addrLen = 0; } if (_connectionState != STATE_ERROR) { _connectionState = STATE_CLOSED; } if (_httpHeaders != NULL) { delete _httpHeaders; _httpHeaders = NULL; } } } /** * This method will try to fill up the buffer with data from */ int HTTPSConnection::updateBuffer() { if (!isClosed()) { // If there is buffer data that has been marked as processed. // Some example is shown here: // // Previous configuration: // GET / HTTP/1.1\\Host: test\\Foo: bar\\\\[some uninitialized memory] // ^ processed ^ unusedIdx // // New configuration after shifting: // Host: test\\Foo: bar\\\\[some uninitialized memory] // ^ processed ^ unusedIdx if (_bufferProcessed > 0) { for(int i = 0; i < HTTPS_CONNECTION_DATA_CHUNK_SIZE; i++) { int copyFrom = i + _bufferProcessed; if (copyFrom < _bufferUnusedIdx) { _receiveBuffer[i] = _receiveBuffer[copyFrom]; } else { break; } } _bufferUnusedIdx -= _bufferProcessed; _bufferProcessed = 0; } if (_bufferUnusedIdx < HTTPS_CONNECTION_DATA_CHUNK_SIZE) { // Check only this socket for data fd_set sockfds; FD_ZERO( &sockfds ); FD_SET(_socket, &sockfds); // We define an immediate timeout (return immediately, if there's no data) timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; // Check for input // As by 2017-12-14, it seems that FD_SETSIZE is defined as 0x40, but socket IDs now // start at 0x1000, so we need to use _socket+1 here select(_socket + 1, &sockfds, NULL, NULL, &timeout); if (FD_ISSET(_socket, &sockfds) || SSL_pending(_ssl) > 0) { HTTPS_DLOGHEX("[ ] There is data on the connection socket. fid=", _socket) // The return code of SSL_read means: // > 0 : Length of the data that has been read // < 0 : Error // = 0 : Connection closed int readReturnCode = SSL_read( _ssl, // Only after the part of the buffer that has not been processed yet _receiveBuffer + sizeof(char) * _bufferUnusedIdx, // Only append up to the end of the buffer HTTPS_CONNECTION_DATA_CHUNK_SIZE - _bufferUnusedIdx ); if (readReturnCode > 0) { _bufferUnusedIdx += readReturnCode; refreshTimeout(); return readReturnCode; } else if (readReturnCode == 0) { // The connection has been closed by the client _clientState = CSTATE_CLOSED; HTTPS_DLOGHEX("[ x ] Client closed connection, fid=", _socket); // TODO: If we are in state websocket, we might need to do something here return 0; } else { // An error occured _connectionState = STATE_ERROR; HTTPS_DLOGHEX("[ERR] An SSL error occured, fid=", _socket); closeConnection(); return -1; } } // data pending } // buffer can read more } return 0; } size_t HTTPSConnection::readBuffer(byte* buffer, size_t length) { updateBuffer(); size_t bufferSize = _bufferUnusedIdx - _bufferProcessed; if (length > bufferSize) { length = bufferSize; } // Write until length is reached (either by param of by empty buffer for(int i = 0; i < length; i++) { buffer[i] = _receiveBuffer[_bufferProcessed++]; } return length; } size_t HTTPSConnection::pendingBufferSize() { updateBuffer(); return _bufferUnusedIdx - _bufferProcessed + SSL_pending(_ssl); } void HTTPSConnection::serverError() { _connectionState = STATE_ERROR; // TODO: Write 500 Serial.println("Server error"); char staticResponse[] = "HTTP/1.1 500 Internal Server Error\r\nServer: esp32https\r\nConnection:close\r\nContent-Type: text/html\r\nContent-Length:34\r\n\r\n