2017-02-23 10:57:59 -06:00
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project .
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/.
*/
# ifndef INCLUDED_WEBSOCKETHANDLER_HPP
# define INCLUDED_WEBSOCKETHANDLER_HPP
2017-05-20 12:28:43 -05:00
# include <chrono>
# include <memory>
# include <vector>
# include "common/Common.hpp"
# include "common/Log.hpp"
2017-10-26 03:38:57 -05:00
# include "common/Unit.hpp"
2017-02-23 10:57:59 -06:00
# include "Socket.hpp"
2018-05-01 11:50:13 -05:00
# include <Poco/MemoryStream.h>
2017-03-02 03:38:49 -06:00
# include <Poco/Net/HTTPRequest.h>
2018-05-03 10:32:31 -05:00
# include <Poco/Net/HTTPResponse.h>
2017-03-02 03:38:49 -06:00
# include <Poco/Net/WebSocket.h>
2017-02-23 10:57:59 -06:00
class WebSocketHandler : public SocketHandlerInterface
{
2018-10-24 02:35:46 -05:00
private :
2018-02-11 18:14:21 -06:00
/// The socket that owns us (we can't own it).
2017-02-26 12:37:15 -06:00
std : : weak_ptr < StreamSocket > _socket ;
2017-03-02 03:38:49 -06:00
2018-02-11 18:14:21 -06:00
std : : chrono : : steady_clock : : time_point _lastPingSentTime ;
2017-03-17 17:59:03 -05:00
int _pingTimeUs ;
2017-02-23 10:57:59 -06:00
std : : vector < char > _wsPayload ;
2018-02-11 18:14:21 -06:00
std : : atomic < bool > _shuttingDown ;
2018-05-01 11:50:13 -05:00
bool _isClient ;
2018-06-15 08:18:31 -05:00
bool _isMasking ;
2017-02-23 10:57:59 -06:00
2018-10-24 02:35:46 -05:00
protected :
2018-02-11 18:14:21 -06:00
struct WSFrameMask
2017-02-28 16:31:27 -06:00
{
2018-02-11 18:14:21 -06:00
static const unsigned char Fin = 0x80 ;
static const unsigned char Mask = 0x80 ;
2017-02-28 16:31:27 -06:00
} ;
2018-09-11 03:34:53 -05:00
static const int InitialPingDelayMs ;
static const int PingFrequencyMs ;
2018-02-11 18:14:21 -06:00
2017-02-23 10:57:59 -06:00
public :
2018-05-01 11:50:13 -05:00
/// Perform upgrade ourselves, or select a client web socket.
2018-06-15 08:18:31 -05:00
WebSocketHandler ( bool isClient = false , bool isMasking = true ) :
2018-02-11 18:14:21 -06:00
_lastPingSentTime ( std : : chrono : : steady_clock : : now ( ) ) ,
2017-03-17 17:59:03 -05:00
_pingTimeUs ( 0 ) ,
2018-05-01 11:50:13 -05:00
_shuttingDown ( false ) ,
2018-06-15 08:18:31 -05:00
_isClient ( isClient ) ,
_isMasking ( isClient & & isMasking )
2017-02-23 10:57:59 -06:00
{
}
2017-03-02 03:38:49 -06:00
/// Upgrades itself to a websocket directly.
2017-03-06 13:50:06 -06:00
WebSocketHandler ( const std : : weak_ptr < StreamSocket > & socket ,
const Poco : : Net : : HTTPRequest & request ) :
2017-03-02 03:38:49 -06:00
_socket ( socket ) ,
2018-02-11 18:14:21 -06:00
_lastPingSentTime ( std : : chrono : : steady_clock : : now ( ) -
2017-03-18 09:39:49 -05:00
std : : chrono : : milliseconds ( PingFrequencyMs ) -
std : : chrono : : milliseconds ( InitialPingDelayMs ) ) ,
2017-03-17 17:59:03 -05:00
_pingTimeUs ( 0 ) ,
2018-05-01 11:50:13 -05:00
_shuttingDown ( false ) ,
2018-06-15 08:18:31 -05:00
_isClient ( false ) ,
_isMasking ( false )
2017-03-01 12:49:29 -06:00
{
2017-03-02 03:38:49 -06:00
upgradeToWebSocket ( request ) ;
2017-03-01 12:49:29 -06:00
}
2017-02-23 10:57:59 -06:00
/// Implementation of the SocketHandlerInterface.
2017-03-26 22:06:44 -05:00
void onConnect ( const std : : shared_ptr < StreamSocket > & socket ) override
2017-02-23 10:57:59 -06:00
{
2017-02-25 18:07:22 -06:00
_socket = socket ;
2018-09-19 02:30:19 -05:00
LOG_TRC ( " # " < < socket - > getFD ( ) < < " Connected to WS Handler " < < this ) ;
2017-02-23 10:57:59 -06:00
}
2017-02-28 20:06:29 -06:00
/// Status codes sent to peer on shutdown.
enum class StatusCodes : unsigned short
{
NORMAL_CLOSE = 1000 ,
ENDPOINT_GOING_AWAY = 1001 ,
PROTOCOL_ERROR = 1002 ,
PAYLOAD_NOT_ACCEPTABLE = 1003 ,
RESERVED = 1004 ,
RESERVED_NO_STATUS_CODE = 1005 ,
RESERVED_ABNORMAL_CLOSE = 1006 ,
MALFORMED_PAYLOAD = 1007 ,
POLICY_VIOLATION = 1008 ,
PAYLOAD_TOO_BIG = 1009 ,
EXTENSION_REQUIRED = 1010 ,
UNEXPECTED_CONDITION = 1011 ,
RESERVED_TLS_FAILURE = 1015
} ;
/// Sends WS shutdown message to the peer.
void shutdown ( const StatusCodes statusCode = StatusCodes : : NORMAL_CLOSE , const std : : string & statusMessage = " " )
{
2018-02-07 03:17:35 -06:00
std : : shared_ptr < StreamSocket > socket = _socket . lock ( ) ;
2017-02-28 20:06:29 -06:00
if ( socket = = nullptr )
2017-03-26 22:06:44 -05:00
{
2018-09-19 02:30:19 -05:00
LOG_ERR ( " No socket associated with WebSocketHandler " < < this ) ;
2017-02-28 20:06:29 -06:00
return ;
2017-03-26 22:06:44 -05:00
}
2017-02-28 20:06:29 -06:00
2017-03-25 20:50:24 -05:00
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Shutdown websocket, code: " < <
static_cast < unsigned > ( statusCode ) < < " , message: " < < statusMessage ) ;
_shuttingDown = true ;
2017-03-10 11:58:51 -06:00
2018-09-13 11:16:00 -05:00
# ifndef MOBILEAPP
2017-02-28 20:06:29 -06:00
const size_t len = statusMessage . size ( ) ;
std : : vector < char > buf ( 2 + len ) ;
2017-02-28 21:33:39 -06:00
buf [ 0 ] = ( ( ( ( int ) statusCode ) > > 8 ) & 0xff ) ;
buf [ 1 ] = ( ( ( ( int ) statusCode ) > > 0 ) & 0xff ) ;
2017-03-20 21:55:52 -05:00
std : : copy ( statusMessage . begin ( ) , statusMessage . end ( ) , buf . begin ( ) + 2 ) ;
2018-02-11 18:14:21 -06:00
const unsigned char flags = WSFrameMask : : Fin
2017-03-20 21:55:52 -05:00
| static_cast < char > ( WSOpCode : : Close ) ;
2017-02-28 20:06:29 -06:00
sendFrame ( socket , buf . data ( ) , buf . size ( ) , flags ) ;
2018-09-13 11:16:00 -05:00
# endif
2017-02-28 20:06:29 -06:00
}
2017-04-02 22:27:06 -05:00
bool handleOneIncomingMessage ( const std : : shared_ptr < StreamSocket > & socket )
2017-02-23 10:57:59 -06:00
{
2017-04-02 22:27:06 -05:00
assert ( socket & & " Expected a valid socket instance. " ) ;
2017-02-26 12:37:15 -06:00
2017-02-23 10:57:59 -06:00
// websocket fun !
2018-10-25 09:38:54 -05:00
const size_t len = socket - > getInBuffer ( ) . size ( ) ;
2017-03-10 08:49:19 -06:00
if ( len = = 0 )
return false ; // avoid logging.
2018-09-11 01:30:55 -05:00
# ifndef MOBILEAPP
2017-02-23 10:57:59 -06:00
if ( len < 2 ) // partial read
2018-07-19 03:35:48 -05:00
{
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Still incomplete WebSocket message, have " < < len < < " bytes " ) ;
2017-03-10 08:36:21 -06:00
return false ;
2018-07-19 03:35:48 -05:00
}
2017-02-23 10:57:59 -06:00
2018-10-25 09:38:54 -05:00
unsigned char * p = reinterpret_cast < unsigned char * > ( & socket - > getInBuffer ( ) [ 0 ] ) ;
2017-04-02 22:27:06 -05:00
const bool fin = p [ 0 ] & 0x80 ;
const WSOpCode code = static_cast < WSOpCode > ( p [ 0 ] & 0x0f ) ;
const bool hasMask = p [ 1 ] & 0x80 ;
2017-02-23 10:57:59 -06:00
size_t payloadLen = p [ 1 ] & 0x7f ;
size_t headerLen = 2 ;
// normally - 7 bit length.
if ( payloadLen = = 126 ) // 2 byte length
{
if ( len < 2 + 2 )
2018-07-19 03:35:48 -05:00
{
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Still incomplete WebSocket message, have " < < len < < " bytes " ) ;
2017-03-10 08:36:21 -06:00
return false ;
2018-07-19 03:35:48 -05:00
}
2017-02-23 10:57:59 -06:00
payloadLen = ( ( ( unsigned ) p [ 2 ] ) < < 8 ) | ( ( unsigned ) p [ 3 ] ) ;
headerLen + = 2 ;
}
else if ( payloadLen = = 127 ) // 8 byte length
{
if ( len < 2 + 8 )
2018-07-19 03:35:48 -05:00
{
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Still incomplete WebSocket message, have " < < len < < " bytes " ) ;
2017-03-10 08:36:21 -06:00
return false ;
2018-07-19 03:35:48 -05:00
}
2017-02-28 21:33:39 -06:00
payloadLen = ( ( ( ( uint64_t ) p [ 9 ] ) < < 0 ) + ( ( ( uint64_t ) p [ 8 ] ) < < 8 ) +
( ( ( uint64_t ) p [ 7 ] ) < < 16 ) + ( ( ( uint64_t ) p [ 6 ] ) < < 24 ) +
( ( ( uint64_t ) p [ 5 ] ) < < 32 ) + ( ( ( uint64_t ) p [ 4 ] ) < < 40 ) +
( ( ( uint64_t ) p [ 3 ] ) < < 48 ) + ( ( ( uint64_t ) p [ 2 ] ) < < 56 ) ) ;
2017-02-23 10:57:59 -06:00
// FIXME: crop read length to remove top / sign bits.
headerLen + = 8 ;
}
unsigned char * data , * mask ;
if ( hasMask )
{
mask = p + headerLen ;
headerLen + = 4 ;
}
if ( payloadLen + headerLen > len )
{ // partial read wait for more data.
2018-07-19 03:35:48 -05:00
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Still incomplete WebSocket message, have " < < len < < " bytes, message is " < < payloadLen + headerLen < < " bytes " ) ;
2017-03-10 08:36:21 -06:00
return false ;
2017-02-23 10:57:59 -06:00
}
2018-10-25 09:38:54 -05:00
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Incoming WebSocket data of " < < len < < " bytes: " < < Util : : stringifyHexLine ( socket - > getInBuffer ( ) , 0 , std : : min ( ( size_t ) 32 , len ) ) ) ;
2018-07-19 03:35:48 -05:00
2017-02-23 10:57:59 -06:00
data = p + headerLen ;
if ( hasMask )
{
2017-03-19 17:54:07 -05:00
const size_t end = _wsPayload . size ( ) ;
_wsPayload . resize ( end + payloadLen ) ;
char * wsData = & _wsPayload [ end ] ;
2017-02-23 10:57:59 -06:00
for ( size_t i = 0 ; i < payloadLen ; + + i )
2017-03-19 17:54:07 -05:00
* wsData + + = data [ i ] ^ mask [ i % 4 ] ;
2017-02-23 10:57:59 -06:00
} else
_wsPayload . insert ( _wsPayload . end ( ) , data , data + payloadLen ) ;
2018-09-11 01:30:55 -05:00
# else
2018-10-25 13:33:57 -05:00
unsigned char * const p = reinterpret_cast < unsigned char * > ( & socket - > getInBuffer ( ) [ 0 ] ) ;
2018-09-11 01:30:55 -05:00
_wsPayload . insert ( _wsPayload . end ( ) , p , p + len ) ;
const size_t headerLen = 0 ;
const size_t payloadLen = len ;
const bool hasMask = false ;
# endif
2017-02-23 10:57:59 -06:00
2017-04-09 17:24:51 -05:00
assert ( _wsPayload . size ( ) > = payloadLen ) ;
2018-10-25 09:38:54 -05:00
socket - > getInBuffer ( ) . erase ( socket - > getInBuffer ( ) . begin ( ) , socket - > getInBuffer ( ) . begin ( ) + headerLen + payloadLen ) ;
2017-02-23 10:57:59 -06:00
2018-09-11 01:30:55 -05:00
# ifndef MOBILEAPP
2017-02-23 10:57:59 -06:00
// FIXME: fin, aggregating payloads into _wsPayload etc.
2017-10-26 03:38:57 -05:00
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Incoming WebSocket message code " < < static_cast < unsigned > ( code ) < <
2017-04-09 17:24:51 -05:00
" , fin? " < < fin < < " , mask? " < < hasMask < < " , payload length: " < < _wsPayload . size ( ) < <
2018-10-25 09:38:54 -05:00
" , residual socket data: " < < socket - > getInBuffer ( ) . size ( ) < < " bytes. " ) ;
2017-02-27 08:34:48 -06:00
2018-05-02 09:40:16 -05:00
bool doClose = false ;
2017-03-17 17:59:03 -05:00
switch ( code )
2017-03-01 00:12:12 -06:00
{
2017-03-17 17:59:03 -05:00
case WSOpCode : : Pong :
2018-02-11 18:14:21 -06:00
{
2018-05-02 09:40:16 -05:00
if ( _isClient )
{
LOG_ERR ( " # " < < socket - > getFD ( ) < < " : Servers should not send pongs, only clients " ) ;
doClose = true ;
break ;
}
else
{
_pingTimeUs = std : : chrono : : duration_cast < std : : chrono : : microseconds >
( std : : chrono : : steady_clock : : now ( ) - _lastPingSentTime ) . count ( ) ;
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Pong received: " < < _pingTimeUs < < " microseconds " ) ;
break ;
}
2018-02-11 18:14:21 -06:00
}
2017-03-17 17:59:03 -05:00
case WSOpCode : : Ping :
2018-05-02 09:40:16 -05:00
if ( _isClient )
{
auto now = std : : chrono : : steady_clock : : now ( ) ;
_pingTimeUs = std : : chrono : : duration_cast < std : : chrono : : microseconds >
( now - _lastPingSentTime ) . count ( ) ;
sendPong ( now , & _wsPayload [ 0 ] , payloadLen , socket ) ;
break ;
}
else
{
LOG_ERR ( " # " < < socket - > getFD ( ) < < " : Clients should not send pings, only servers " ) ;
doClose = true ;
}
break ;
2017-03-17 17:59:03 -05:00
case WSOpCode : : Close :
2018-05-02 09:40:16 -05:00
doClose = true ;
break ;
default :
handleMessage ( fin , code , _wsPayload ) ;
break ;
}
2018-09-11 01:30:55 -05:00
# else
handleMessage ( true , WSOpCode : : Binary , _wsPayload ) ;
# endif
# ifndef MOBILEAPP
2018-05-02 09:40:16 -05:00
if ( doClose )
{
2017-03-01 00:12:12 -06:00
if ( ! _shuttingDown )
{
// Peer-initiated shutdown must be echoed.
2017-03-25 20:51:25 -05:00
// Otherwise, this is the echo to _our_ shutdown message, which we should ignore.
const StatusCodes statusCode = static_cast < StatusCodes > ( ( ( ( uint64_t ) ( unsigned char ) _wsPayload [ 0 ] ) < < 8 ) +
( ( ( uint64_t ) ( unsigned char ) _wsPayload [ 1 ] ) < < 0 ) ) ;
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Client initiated socket shutdown. Code: " < < static_cast < int > ( statusCode ) ) ;
2017-03-01 00:12:12 -06:00
if ( _wsPayload . size ( ) > 2 )
{
const std : : string message ( & _wsPayload [ 2 ] , & _wsPayload [ 2 ] + _wsPayload . size ( ) - 2 ) ;
shutdown ( statusCode , message ) ;
}
else
{
shutdown ( statusCode ) ;
}
}
2017-03-25 20:50:24 -05:00
else
{
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Client responded to our shutdown. " ) ;
}
2017-03-01 00:12:12 -06:00
// TCP Close.
2017-03-19 18:07:42 -05:00
socket - > closeConnection ( ) ;
2017-03-01 00:12:12 -06:00
}
2018-09-11 01:30:55 -05:00
# endif
2017-03-01 00:12:12 -06:00
2017-02-23 10:57:59 -06:00
_wsPayload . clear ( ) ;
2017-03-10 08:36:21 -06:00
return true ;
2017-02-23 10:57:59 -06:00
}
2017-03-10 08:36:21 -06:00
/// Implementation of the SocketHandlerInterface.
2017-05-05 05:51:43 -05:00
virtual void handleIncomingMessage ( SocketDisposition & ) override
2017-03-10 08:36:21 -06:00
{
2018-09-13 11:16:00 -05:00
// LOG_TRC("***** WebSocketHandler::handleIncomingMessage()");
2018-02-07 03:17:35 -06:00
std : : shared_ptr < StreamSocket > socket = _socket . lock ( ) ;
2018-09-13 11:16:00 -05:00
# ifdef MOBILEAPP
// No separate "upgrade" is going on
if ( socket ! = nullptr & & ! socket - > isWebSocket ( ) )
socket - > setWebSocket ( ) ;
# endif
2017-04-02 22:27:06 -05:00
if ( socket = = nullptr )
{
2018-09-19 02:30:19 -05:00
LOG_ERR ( " No socket associated with WebSocketHandler " < < this ) ;
2017-04-02 22:27:06 -05:00
}
2018-09-13 11:16:00 -05:00
# ifndef MOBILEAPP
2018-05-01 11:50:13 -05:00
else if ( _isClient & & ! socket - > isWebSocket ( ) )
handleClientUpgrade ( ) ;
2018-09-13 11:16:00 -05:00
# endif
2017-04-02 22:27:06 -05:00
else
{
while ( handleOneIncomingMessage ( socket ) )
2018-07-19 03:39:32 -05:00
; // might have multiple messages in the accumulated buffer.
2017-04-02 22:27:06 -05:00
}
2017-03-10 08:36:21 -06:00
}
2017-03-17 17:59:03 -05:00
int getPollEvents ( std : : chrono : : steady_clock : : time_point now ,
int & timeoutMaxMs ) override
2017-03-06 10:26:52 -06:00
{
2018-05-02 09:40:16 -05:00
if ( ! _isClient )
{
const int timeSincePingMs =
std : : chrono : : duration_cast < std : : chrono : : milliseconds > ( now - _lastPingSentTime ) . count ( ) ;
timeoutMaxMs = std : : min ( timeoutMaxMs , PingFrequencyMs - timeSincePingMs ) ;
}
2017-03-17 16:59:09 -05:00
return POLLIN ;
2017-03-06 10:26:52 -06:00
}
2018-09-13 11:16:00 -05:00
# ifndef MOBILEAPP
2017-03-18 09:39:49 -05:00
/// Send a ping message
2018-05-02 09:40:16 -05:00
void sendPingOrPong ( std : : chrono : : steady_clock : : time_point now ,
const char * data , const size_t len ,
const WSOpCode code ,
const std : : shared_ptr < StreamSocket > & socket )
2017-03-18 09:39:49 -05:00
{
2017-04-09 17:20:52 -05:00
assert ( socket & & " Expected a valid socket instance. " ) ;
2017-03-18 09:39:49 -05:00
// Must not send this before we're upgraded.
2018-02-11 18:14:21 -06:00
if ( ! socket - > isWebSocket ( ) )
2017-03-18 09:39:49 -05:00
{
2018-02-11 18:14:21 -06:00
LOG_WRN ( " Attempted ping on non-upgraded websocket! # " < < socket - > getFD ( ) ) ;
_lastPingSentTime = now ; // Pretend we sent it to avoid timing out immediately.
2017-03-18 09:39:49 -05:00
return ;
}
2017-04-09 17:20:52 -05:00
2018-05-02 09:40:16 -05:00
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Sending " < <
( const char * ) ( code = = WSOpCode : : Ping ? " ping. " : " pong. " ) ) ;
2017-03-18 09:39:49 -05:00
// FIXME: allow an empty payload.
2018-05-02 09:40:16 -05:00
sendMessage ( data , len , code , false ) ;
2018-02-11 18:14:21 -06:00
_lastPingSentTime = now ;
2017-03-18 09:39:49 -05:00
}
2018-05-02 09:40:16 -05:00
void sendPing ( std : : chrono : : steady_clock : : time_point now ,
const std : : shared_ptr < StreamSocket > & socket )
{
assert ( ! _isClient ) ;
sendPingOrPong ( now , " " , 1 , WSOpCode : : Ping , socket ) ;
}
void sendPong ( std : : chrono : : steady_clock : : time_point now ,
const char * data , const size_t len ,
const std : : shared_ptr < StreamSocket > & socket )
{
assert ( _isClient ) ;
sendPingOrPong ( now , data , len , WSOpCode : : Pong , socket ) ;
}
2018-09-13 11:16:00 -05:00
# endif
2018-05-02 09:40:16 -05:00
2017-03-17 17:59:03 -05:00
/// Do we need to handle a timeout ?
void checkTimeout ( std : : chrono : : steady_clock : : time_point now ) override
{
2018-09-13 11:16:00 -05:00
# ifndef MOBILEAPP
2018-05-02 09:40:16 -05:00
if ( _isClient )
return ;
2017-04-09 17:20:52 -05:00
const int timeSincePingMs =
2018-02-11 18:14:21 -06:00
std : : chrono : : duration_cast < std : : chrono : : milliseconds > ( now - _lastPingSentTime ) . count ( ) ;
2017-03-17 17:59:03 -05:00
if ( timeSincePingMs > = PingFrequencyMs )
2017-04-09 17:20:52 -05:00
{
2018-02-11 18:14:21 -06:00
const std : : shared_ptr < StreamSocket > socket = _socket . lock ( ) ;
2017-04-09 17:20:52 -05:00
if ( socket )
sendPing ( now , socket ) ;
}
2018-09-13 11:16:00 -05:00
# endif
2017-03-17 17:59:03 -05:00
}
2017-03-16 12:23:42 -05:00
/// By default rely on the socket buffer.
void performWrites ( ) override { }
2017-03-06 10:26:52 -06:00
2017-03-14 20:13:36 -05:00
/// Sends a WebSocket Text message.
2017-03-29 19:38:41 -05:00
int sendMessage ( const std : : string & msg ) const
2017-03-01 12:49:29 -06:00
{
2017-03-29 19:38:41 -05:00
return sendMessage ( msg . data ( ) , msg . size ( ) , WSOpCode : : Text ) ;
2017-03-01 12:49:29 -06:00
}
2017-02-26 12:37:15 -06:00
/// Sends a WebSocket message of WPOpCode type.
/// Returns the number of bytes written (including frame overhead) on success,
/// 0 for closed/invalid socket, and -1 for other errors.
2017-02-27 00:45:18 -06:00
int sendMessage ( const char * data , const size_t len , const WSOpCode code , const bool flush = true ) const
2017-02-23 10:57:59 -06:00
{
2017-10-26 03:38:57 -05:00
int unitReturn = - 1 ;
2017-12-11 08:27:29 -06:00
if ( UnitBase : : get ( ) . filterSendMessage ( data , len , code , flush , unitReturn ) )
2017-10-26 03:38:57 -05:00
return unitReturn ;
2017-02-28 20:04:44 -06:00
//TODO: Support fragmented messages.
2017-02-26 20:32:16 -06:00
2018-02-07 03:17:35 -06:00
std : : shared_ptr < StreamSocket > socket = _socket . lock ( ) ;
2018-02-11 18:14:21 -06:00
return sendFrame ( socket , data , len , WSFrameMask : : Fin | static_cast < unsigned char > ( code ) , flush ) ;
2017-02-28 20:04:44 -06:00
}
2018-06-15 08:18:31 -05:00
private :
2017-02-28 20:04:44 -06:00
/// Sends a WebSocket frame given the data, length, and flags.
/// Returns the number of bytes written (including frame overhead) on success,
/// 0 for closed/invalid socket, and -1 for other errors.
2018-06-15 08:18:31 -05:00
int sendFrame ( const std : : shared_ptr < StreamSocket > & socket ,
const char * data , const size_t len ,
unsigned char flags , const bool flush = true ) const
2017-02-28 20:04:44 -06:00
{
if ( ! socket | | data = = nullptr | | len = = 0 )
return - 1 ;
2018-01-21 19:34:38 -06:00
if ( socket - > isClosed ( ) )
return 0 ;
2017-04-05 07:48:49 -05:00
socket - > assertCorrectThread ( ) ;
2018-10-25 09:38:54 -05:00
std : : vector < char > & out = socket - > getOutBuffer ( ) ;
2017-04-09 17:24:51 -05:00
const size_t oldSize = out . size ( ) ;
2017-02-28 20:04:44 -06:00
2018-09-13 11:16:00 -05:00
# ifndef MOBILEAPP
2017-02-28 20:04:44 -06:00
out . push_back ( flags ) ;
2017-02-23 10:57:59 -06:00
2018-06-15 08:18:31 -05:00
int maskFlag = _isMasking ? 0x80 : 0 ;
2017-02-23 10:57:59 -06:00
if ( len < 126 )
{
2018-06-15 08:18:31 -05:00
out . push_back ( ( char ) ( len | maskFlag ) ) ;
2017-02-23 10:57:59 -06:00
}
else if ( len < = 0xffff )
{
2018-06-15 08:18:31 -05:00
out . push_back ( ( char ) ( 126 | maskFlag ) ) ;
2017-02-28 16:31:27 -06:00
out . push_back ( static_cast < char > ( ( len > > 8 ) & 0xff ) ) ;
out . push_back ( static_cast < char > ( ( len > > 0 ) & 0xff ) ) ;
2017-02-23 10:57:59 -06:00
}
else
{
2018-06-15 08:18:31 -05:00
out . push_back ( ( char ) ( 127 | maskFlag ) ) ;
2017-02-28 16:31:27 -06:00
out . push_back ( static_cast < char > ( ( len > > 56 ) & 0xff ) ) ;
out . push_back ( static_cast < char > ( ( len > > 48 ) & 0xff ) ) ;
out . push_back ( static_cast < char > ( ( len > > 40 ) & 0xff ) ) ;
out . push_back ( static_cast < char > ( ( len > > 32 ) & 0xff ) ) ;
out . push_back ( static_cast < char > ( ( len > > 24 ) & 0xff ) ) ;
out . push_back ( static_cast < char > ( ( len > > 16 ) & 0xff ) ) ;
out . push_back ( static_cast < char > ( ( len > > 8 ) & 0xff ) ) ;
out . push_back ( static_cast < char > ( ( len > > 0 ) & 0xff ) ) ;
2017-02-23 10:57:59 -06:00
}
2018-06-15 08:18:31 -05:00
if ( _isMasking )
{ // flip some top bits - perhaps it helps.
size_t mask = out . size ( ) ;
out . push_back ( static_cast < char > ( 0x81 ) ) ;
out . push_back ( static_cast < char > ( 0x76 ) ) ;
out . push_back ( static_cast < char > ( 0x81 ) ) ;
out . push_back ( static_cast < char > ( 0x76 ) ) ;
// Copy the data.
out . insert ( out . end ( ) , data , data + len ) ;
// Mask it.
for ( size_t i = 4 ; i < out . size ( ) - mask ; + + i )
out [ mask + i ] = out [ mask + i ] ^ out [ mask + ( i % 4 ) ] ;
}
else
{
// Copy the data.
out . insert ( out . end ( ) , data , data + len ) ;
}
2017-04-09 17:24:51 -05:00
const size_t size = out . size ( ) - oldSize ;
2018-09-13 11:16:00 -05:00
# else
LOG_TRC ( " WebSocketHandle::sendFrame: Writing to # " < < socket - > getFD ( ) < < " " < < len < < " bytes " ) ;
assert ( flush ) ;
assert ( out . size ( ) = = 0 ) ;
2017-02-28 20:04:44 -06:00
2018-09-13 11:16:00 -05:00
out . insert ( out . end ( ) , data , data + len ) ;
const size_t size = out . size ( ) ;
# endif
2017-02-26 10:52:31 -06:00
if ( flush )
2017-02-26 12:37:15 -06:00
socket - > writeOutgoingData ( ) ;
2017-04-09 17:24:51 -05:00
return size ;
2017-02-23 10:57:59 -06:00
}
2018-06-15 08:18:31 -05:00
protected :
2017-03-01 12:49:29 -06:00
/// To be overriden to handle the websocket messages the way you need.
virtual void handleMessage ( bool /*fin*/ , WSOpCode /*code*/ , std : : vector < char > & /*data*/ )
2017-02-26 10:31:52 -06:00
{
}
2017-03-02 03:38:49 -06:00
2018-10-24 02:35:46 -05:00
std : : weak_ptr < StreamSocket > & getSocket ( )
{
return _socket ;
}
void setSocket ( const std : : weak_ptr < StreamSocket > & socket )
{
_socket = socket ;
}
2017-03-18 09:59:09 -05:00
void dumpState ( std : : ostream & os ) override ;
2017-03-02 03:38:49 -06:00
private :
/// To make the protected 'computeAccept' accessible.
class PublicComputeAccept : public Poco : : Net : : WebSocket
{
public :
static std : : string doComputeAccept ( const std : : string & key )
{
return computeAccept ( key ) ;
}
} ;
protected :
/// Upgrade the http(s) connection to a websocket.
void upgradeToWebSocket ( const Poco : : Net : : HTTPRequest & req )
{
2018-02-07 03:17:35 -06:00
std : : shared_ptr < StreamSocket > socket = _socket . lock ( ) ;
2017-03-02 03:38:49 -06:00
if ( socket = = nullptr )
throw std : : runtime_error ( " Invalid socket while upgrading to WebSocket. Request: " + req . getURI ( ) ) ;
2017-03-14 21:14:20 -05:00
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Upgrading to WebSocket. " ) ;
2018-02-11 18:14:21 -06:00
assert ( ! socket - > isWebSocket ( ) ) ;
2017-03-14 21:14:20 -05:00
2018-09-13 11:16:00 -05:00
# ifndef MOBILEAPP
2017-03-02 03:38:49 -06:00
// create our websocket goodness ...
const int wsVersion = std : : stoi ( req . get ( " Sec-WebSocket-Version " , " 13 " ) ) ;
const std : : string wsKey = req . get ( " Sec-WebSocket-Key " , " " ) ;
const std : : string wsProtocol = req . get ( " Sec-WebSocket-Protocol " , " chat " ) ;
// FIXME: other sanity checks ...
2017-04-09 17:20:52 -05:00
LOG_INF ( " # " < < socket - > getFD ( ) < < " : WebSocket version: " < < wsVersion < <
" , key: [ " < < wsKey < < " ], protocol: [ " < < wsProtocol < < " ]. " ) ;
2017-04-19 03:58:18 -05:00
# if ENABLE_DEBUG
if ( std : : getenv ( " LOOL_ZERO_BUFFER_SIZE " ) )
socket - > setSocketBufferSize ( 0 ) ;
# endif
2017-03-02 03:38:49 -06:00
std : : ostringstream oss ;
oss < < " HTTP/1.1 101 Switching Protocols \r \n "
< < " Upgrade: websocket \r \n "
< < " Connection: Upgrade \r \n "
2017-03-02 03:39:24 -06:00
< < " Sec-WebSocket-Accept: " < < PublicComputeAccept : : doComputeAccept ( wsKey ) < < " \r \n "
2017-03-02 03:38:49 -06:00
< < " \r \n " ;
2017-04-09 17:20:52 -05:00
const std : : string res = oss . str ( ) ;
LOG_TRC ( " # " < < socket - > getFD ( ) < < " : Sending WS Upgrade response: " < < res ) ;
socket - > send ( res ) ;
2018-09-13 11:16:00 -05:00
# endif
2018-04-18 13:20:54 -05:00
setWebSocket ( ) ;
}
2017-03-10 03:55:28 -06:00
2018-09-13 11:16:00 -05:00
# ifndef MOBILEAPP
2018-05-01 11:50:13 -05:00
// Handle incoming upgrade to full socket as client WS.
void handleClientUpgrade ( )
{
std : : shared_ptr < StreamSocket > socket = _socket . lock ( ) ;
2018-10-25 09:38:54 -05:00
LOG_TRC ( " Incoming client websocket upgrade response: " < < std : : string ( & socket - > getInBuffer ( ) [ 0 ] , socket - > getInBuffer ( ) . size ( ) ) ) ;
2018-05-01 11:50:13 -05:00
bool bOk = false ;
2018-05-03 10:32:31 -05:00
size_t responseSize = 0 ;
try
2018-05-01 11:50:13 -05:00
{
2018-10-25 09:38:54 -05:00
Poco : : MemoryInputStream message ( & socket - > getInBuffer ( ) [ 0 ] , socket - > getInBuffer ( ) . size ( ) ) ; ;
2018-05-03 10:32:31 -05:00
Poco : : Net : : HTTPResponse response ;
response . read ( message ) ;
2018-05-01 11:50:13 -05:00
{
2018-05-03 10:32:31 -05:00
static const std : : string marker ( " \r \n \r \n " ) ;
2018-10-25 09:38:54 -05:00
auto itBody = std : : search ( socket - > getInBuffer ( ) . begin ( ) ,
socket - > getInBuffer ( ) . end ( ) ,
2018-05-03 10:32:31 -05:00
marker . begin ( ) , marker . end ( ) ) ;
2018-10-25 09:38:54 -05:00
if ( itBody ! = socket - > getInBuffer ( ) . end ( ) )
responseSize = itBody - socket - > getInBuffer ( ) . begin ( ) + marker . size ( ) ;
2018-05-01 11:50:13 -05:00
}
2018-05-03 10:32:31 -05:00
if ( response . getStatus ( ) = = Poco : : Net : : HTTPResponse : : HTTP_SWITCHING_PROTOCOLS & &
response . has ( " Upgrade " ) & & Poco : : icompare ( response . get ( " Upgrade " ) , " websocket " ) = = 0 )
{
#if 0 // SAL_DEBUG ...
const std : : string wsKey = response . get ( " Sec-WebSocket-Accept " , " " ) ;
const std : : string wsProtocol = response . get ( " Sec-WebSocket-Protocol " , " " ) ;
if ( Poco : : icompare ( wsProtocol , " chat " ) ! = 0 )
LOG_ERR ( " Unknown websocket protocol " < < wsProtocol ) ;
else
# endif
{
LOG_TRC ( " Accepted incoming websocket response " ) ;
// FIXME: validate Sec-WebSocket-Accept vs. Sec-WebSocket-Key etc.
bOk = true ;
}
}
}
catch ( const Poco : : Exception & exc )
{
LOG_DBG ( " handleClientUpgrade exception caught: " < < exc . displayText ( ) ) ;
}
catch ( const std : : exception & exc )
{
LOG_DBG ( " handleClientUpgrade exception caught. " ) ;
2018-05-01 11:50:13 -05:00
}
2018-05-03 10:32:31 -05:00
if ( ! bOk | | responseSize = = 0 )
2018-05-01 11:50:13 -05:00
{
2018-05-03 10:32:31 -05:00
LOG_ERR ( " Bad websocker server response. " ) ;
2018-05-01 11:50:13 -05:00
socket - > shutdown ( ) ;
2018-05-03 10:32:31 -05:00
return ;
2018-05-01 11:50:13 -05:00
}
setWebSocket ( ) ;
2018-05-03 10:32:31 -05:00
socket - > eraseFirstInputBytes ( responseSize ) ;
2018-05-01 11:50:13 -05:00
}
2018-09-13 11:16:00 -05:00
# endif
2018-05-01 11:50:13 -05:00
2018-04-18 13:20:54 -05:00
void setWebSocket ( )
{
std : : shared_ptr < StreamSocket > socket = _socket . lock ( ) ;
2018-02-11 18:14:21 -06:00
socket - > setWebSocket ( ) ;
2017-04-09 17:20:52 -05:00
// No need to ping right upon connection/upgrade,
// but do reset the time to avoid pinging immediately after.
2018-02-11 18:14:21 -06:00
_lastPingSentTime = std : : chrono : : steady_clock : : now ( ) ;
2017-03-02 03:38:49 -06:00
}
2017-02-26 10:31:52 -06:00
} ;
2017-02-23 10:57:59 -06:00
# endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */