📄 str_net.c
字号:
/* Buffered transport-layer read function. This sits on top of the
transport-layer read function and performs speculative read-ahead
buffering to improve performance in protocols such as HTTP that have to
read a byte at a time in places:
bPos bEnd
| |
v v
+-------+-----------+-------+
| |///////////| |
+-------+-----------+-------+
-- Read -->
We fill the buffer to bEnd, then empty it advancing bPos until there isn't
enough data left to satisfy the read, whereupon we move the data down and
refill from bEnd:
bPos bEnd
| |
v v
+-----------+---------------+
|///////////| |
+-----------+---------------+
-- Write --> */
static int bufferedTransportReadFunction( STREAM *stream, BYTE *buffer,
const int length, const int flags )
{
const int bytesLeft = stream->bufEnd - stream->bufPos;
int bytesToRead, status;
assert( isWritePtr( buffer, length ) );
assert( length > 0 );
assert( bytesLeft >= 0 );
/* If there's enough data in the buffer to satisfy the request, return it
directly */
if( length <= bytesLeft )
{
if( length == 1 )
/* Optimisation for HTTP header reads */
*buffer = stream->buffer[ stream->bufPos++ ];
else
{
memcpy( buffer, stream->buffer + stream->bufPos, length );
stream->bufPos += length;
}
assert( stream->bufPos <= stream->bufEnd );
return( length );
}
/* We're about to refill the buffer, if there's a gap at the start move
everything down to make room for the new data */
if( stream->bufPos > 0 )
{
if( bytesLeft > 0 )
memmove( stream->buffer, stream->buffer + stream->bufPos,
bytesLeft );
stream->bufEnd = bytesLeft;
stream->bufPos = 0;
}
assert( stream->bufPos == 0 );
assert( length > bytesLeft );
/* If there's more room in the buffer, refill it */
if( stream->bufEnd < stream->bufSize )
{
int bytesRead;
/* Perform an explicitly blocking read of as many bytes as we can/are
asked for. Since there may be data already present from an
earlier speculative read, we only read as much as we need to
fulfill the request */
bytesRead = stream->transportReadFunction( stream,
stream->buffer + stream->bufEnd,
min( length - bytesLeft, \
stream->bufSize - stream->bufEnd ),
TRANSPORT_FLAG_BLOCKING );
if( cryptStatusError( bytesRead ) )
return( bytesRead );
stream->bufEnd += bytesRead;
/* If there's room for more, perform a second, nonblocking read for
whatever might still be there. An error at this point isn't
fatal since this was only a speculative read */
if( stream->bufEnd < stream->bufSize )
{
bytesRead = stream->transportReadFunction( stream,
stream->buffer + stream->bufEnd,
stream->bufSize - stream->bufEnd,
TRANSPORT_FLAG_NONBLOCKING );
if( !cryptStatusError( bytesRead ) )
stream->bufEnd += bytesRead;
}
}
assert( stream->bufEnd <= stream->bufSize );
/* Read as much as we can from the buffer */
bytesToRead = min( length, stream->bufEnd );
memcpy( buffer, stream->buffer, bytesToRead );
stream->bufPos += bytesToRead;
assert( stream->bufPos <= stream->bufEnd );
/* If we could satisfy the read from the buffer, we're done */
if( length <= bytesToRead )
return( length );
/* We're drained the stream buffer and there's more to go, read it
directly into the caller's buffer */
status = stream->transportReadFunction( stream,
buffer + bytesToRead, length - bytesToRead,
TRANSPORT_FLAG_BLOCKING );
return( cryptStatusError( status ) ? status : status + bytesToRead );
}
/* Buffered transport-layer write function. This sits on top of the
transport-layer write function and combines two (or more, although in
practice only two ever occur) writes into a single write. The reason for
this is that when using TCP transport the delayed-ACK handling means
that performing two writes followed by a read (typical for HTTP and CMP
messages) leads to very poor performance, usually made even worse by TCP
slow-start.
The reason for this is that the TCP MSS is typically 1460 bytes on a LAN
(Ethernet) or 512/536 bytes on a WAN, while HTTP headers are ~200-300
bytes, far less than the MSS. When an HTTP message is first sent, the
TCP congestion window begins at one segment, with the TCP slow-start then
doubling its size for each ACK. Sending the headers separately will
send one short segment and a second MSS-size segment, whereupon the TCP
stack will wait for the responder's ACK before continuing. The responder
gets both segments, then delays its ACK for 200ms in the hopes of
piggybacking it on responder data, which is never sent since it's still
waiting for the rest of the HTTP body from the initiator. As a result,
this results in a 200ms (+ assorted RTT) delay in each message sent.
There is a somewhat related situation that occurs as a result of TCP
slow-start and that can't be avoided programmatically in which we can't
send more than a single request initially, however most BSD-derived
implementations set the server's congestion window to two segments in
response to receiving the TCP handshake ACK, so for the initial message
exchange the client can send a request of 1MSS and the server a response
of 2MSS without running into congestion-control problems.
A related problem is the fact that many TCP implementations will reset the
congestion window after one retransmission timeout period if all data sent
at that point has been acked, which means that both sides now restart with
a congestion window of size 1. Unfortunately there's nothing that can be
done about this, however hopefully at some point TCP implementations will
start to fall into line with RFC 3390 and allow initial windows of ~4K,
which will fix this particular problem */
static int bufferedTransportWriteFunction( STREAM *stream, const BYTE *buffer,
const int length, const int flags )
{
const BYTE *bufPtr = buffer;
int byteCount = length;
assert( isReadPtr( buffer, length ) );
assert( length > 0 );
/* If it's not a flush and the buffer can absorb the data, copy it in and
exit */
if( !( flags & TRANSPORT_FLAG_FLUSH ) && \
stream->writeBufEnd + length <= stream->writeBufSize )
{
memcpy( stream->writeBuffer + stream->writeBufEnd, buffer, length );
stream->writeBufEnd += length;
assert( stream->writeBufEnd <= stream->writeBufSize );
return( CRYPT_OK );
}
/* It's a flush or too much data to buffer, assemble a complete buffer
and write it */
if( stream->writeBufEnd > 0 )
{
const int bytesToCopy = min( byteCount, \
stream->writeBufSize - stream->writeBufEnd );
int status;
if( bytesToCopy > 0 )
memcpy( stream->writeBuffer + stream->writeBufEnd, buffer,
bytesToCopy );
status = stream->transportWriteFunction( stream, stream->writeBuffer,
stream->writeBufEnd + \
bytesToCopy,
TRANSPORT_FLAG_FLUSH );
stream->writeBufEnd = 0;
bufPtr += bytesToCopy;
byteCount -= bytesToCopy;
if( byteCount <= 0 )
return( CRYPT_OK );
}
/* Write anything that's left directly */
return( stream->transportWriteFunction( stream, bufPtr, byteCount,
TRANSPORT_FLAG_FLUSH ) );
}
/****************************************************************************
* *
* Transport-layer Functions *
* *
****************************************************************************/
/* Map the upper-layer I/O functions directly to the transport-layer
equivalent. This is used if we're performing raw I/O without any
intermediate protocol layers or buffering */
static int transportDirectReadFunction( STREAM *stream, void *buffer,
const int length )
{
return( stream->transportReadFunction( stream, buffer, length,
TRANSPORT_FLAG_NONE ) );
}
static int transportDirectWriteFunction( STREAM *stream, const void *buffer,
const int length )
{
return( stream->transportWriteFunction( stream, buffer, length,
TRANSPORT_FLAG_NONE ) );
}
static int setStreamLayerDirect( STREAM *stream )
{
stream->writeFunction = transportDirectWriteFunction;
stream->readFunction = transportDirectReadFunction;
return( CRYPT_OK );
}
/* Send and receive data with a cryptlib session as the transport layer */
static int transportSessionConnectFunction( STREAM *stream,
const char *server,
const int port )
{
int isActive, status;
assert( server == NULL );
assert( port == 0 );
/* If the transport session hasn't been activated yet, activate it now */
status = krnlSendMessage( stream->iTransportSession,
IMESSAGE_GETATTRIBUTE, &isActive,
CRYPT_SESSINFO_ACTIVE );
if( cryptStatusOK( status ) && isActive )
return( CRYPT_OK );
status = krnlSendMessage( stream->iTransportSession,
IMESSAGE_SETATTRIBUTE, MESSAGE_VALUE_TRUE,
CRYPT_SESSINFO_ACTIVE );
if( cryptStatusError( status ) )
return( getSessionErrorInfo( stream, status ) );
return( CRYPT_OK );
}
static void transportSessionDisconnectFunction( STREAM *stream,
const BOOLEAN fullDisconnect )
{
krnlSendNotifier( stream->iTransportSession, IMESSAGE_DECREFCOUNT );
}
static BOOLEAN transportSessionOKFunction( void )
{
return( TRUE );
}
static int transportSessionReadFunction( STREAM *stream, BYTE *buffer,
const int length, const int flags )
{
RESOURCE_DATA msgData;
int newTimeout = CRYPT_UNUSED, status;
/* Read data from the session, overriding the timeout handling if
requested */
if( ( flags & TRANSPORT_FLAG_NONBLOCKING ) && stream->timeout > 0 )
newTimeout = 0;
else
if( ( flags & TRANSPORT_FLAG_BLOCKING ) && stream->timeout == 0 )
newTimeout = 30;
if( newTimeout != CRYPT_UNUSED )
krnlSendMessage( stream->iTransportSession, IMESSAGE_SETATTRIBUTE,
&newTimeout, CRYPT_OPTION_NET_TIMEOUT );
setMessageData( &msgData, buffer, length );
status = krnlSendMessage( stream->iTransportSession, IMESSAGE_ENV_POPDATA,
&msgData, 0 );
if( newTimeout != CRYPT_UNUSED )
krnlSendMessage( stream->iTransportSession, IMESSAGE_SETATTRIBUTE,
&stream->timeout, CRYPT_OPTION_NET_TIMEOUT );
if( cryptStatusError( status ) )
return( getSessionErrorInfo( stream, status ) );
if( msgData.length < length )
retExtStream( stream, CRYPT_ERROR_READ,
"Only read %d out of %d bytes via cryptlib session "
"object", msgData.length, length );
return( length );
}
static int transportSessionWriteFunction( STREAM *stream, const BYTE *buffer,
const int length, const int flags )
{
RESOURCE_DATA msgData;
int status;
setMessageData( &msgData, ( void * ) buffer, length );
status = krnlSendMessage( stream->iTransportSession,
IMESSAGE_ENV_PUSHDATA, &msgData, 0 );
if( cryptStatusOK( status ) )
{
setMessageData( &msgData, NULL, 0 );
status = krnlSendMessage( stream->iTransportSession,
IMESSAGE_ENV_PUSHDATA, &msgData, 0 );
}
if( cryptStatusError( status ) )
return( getSessionErrorInfo( stream, status ) );
return( CRYPT_OK );
}
/****************************************************************************
* *
* Network Stream Functions *
* *
****************************************************************************/
/* Clean up a stream to shut it down, optionally closing the underlying
network connection */
static void cleanupStream( STREAM *stream, const BOOLEAN cleanupTransport )
{
assert( stream != NULL && stream->type == STREAM_TYPE_NETWORK );
assert( stream->errorMessage != NULL );
if( cleanupTransport && !( stream->flags & STREAM_NFLAG_USERSOCKET ) )
stream->transportDisconnectFunction( stream, TRUE );
if( stream->bufSize )
{
zeroise( stream->buffer, stream->bufSize );
clFree( "cleanupStream", stream->buffer );
}
if( stream->writeBufSize )
{
zeroise( stream->writeBuffer, stream->writeBufSize );
clFree( "cleanupStream", stream->writeBuffer );
}
if( stream->host != NULL )
clFree( "cleanupStream", stream->host );
if( stream->path != NULL )
clFree( "cleanupStream", stream->path );
if( stream->query != NULL )
clFree( "cleanupStream", stream->query );
clFree( "cleanupStream", stream->errorMessage );
zeroise( stream, sizeof( STREAM ) );
}
/* Complete a network connection after the client- or server-specific
portions have been handled */
static int completeConnect( STREAM *stream,
const STREAM_PROTOCOL_TYPE protocol,
const NET_OPTION_TYPE options,
const char *proxyURL,
const CRYPT_USER iUserObject,
const int netTimeout, char *errorMessage,
int *errorCode )
{
const BOOLEAN useTransportBuffering = \
( options == NET_OPTION_TRANSPORTSESSION || \
protocol == STREAM_PROTOCOL_TCPIP ) ? \
FALSE : TRUE;
int timeout, status = CRYPT_OK;
/* Set up the access method pointers. We can use either direct TCP/IP
access or a cryptlib stream for transport, and layered over that
either HTTP, the CMP socket protocol, or direct access to the
transport layer */
if( options == NET_OPTION_TRANSPORTSESSION )
{
stream->transportConnectFunction = transportSessionConnectFunction;
stream->transportDisconnectFunction = transportSessionDisconnectFunction;
stream->transportWriteFunction = transportSessionWriteFunction;
stream->transportReadFunction = transportSessionReadFunction;
stream->transportOKFunction = transportSessionOKFunction;
}
else
setAccessMethodTCP( stream );
switch( protocol )
{
case STREAM_PROTOCOL_HTTP:
case STREAM_PROTOCOL_HTTP_TRANSACTION:
setStreamLayerHTTP( stream );
break;
case STREAM_PROTOCOL_CMP:
setStreamLayerCMP( stream );
break;
case STREAM_PROTOCOL_TCPIP:
setStreamLayerDirect( stream );
break;
default:
assert( NOTREACHED );
}
if( useTransportBuffering )
{
stream->bufferedTransportReadFunction = bufferedTransportReadFunction;
stream->bufferedTransportWriteFunction = bufferedTransportWriteFunction;
}
else
{
stream->bufferedTransportReadFunction = stream->transportReadFunction;
stream->bufferedTransportWriteFunction = stream->transportWriteFunction;
}
/* If we're running over a cryptlib session, make sure that we wait around
for a minimum amount of time during network comms in case the user has
specified nonblocking behaviour or quick timeouts */
if( options == NET_OPTION_TRANSPORTSESSION )
{
static const int fixedTimeout = 30;
status = krnlSendMessage( iUserObject, IMESSAGE_GETATTRIBUTE,
&timeout, CRYPT_OPTION_NET_CONNECTTIMEOUT );
if( cryptStatusOK( status ) && timeout < fixedTimeout )
krnlSendMessage( stream->iTransportSession,
IMESSAGE_SETATTRIBUTE, ( void * ) &fixedTimeout,
CRYPT_OPTION_NET_CONNECTTIMEOUT );
status = krnlSendMessage( iUserObject, IMESSAGE_GETATTRIBUTE,
&timeout, CRYPT_OPTION_NET_TIMEOUT );
if( cryptStatusOK( status ) && timeout < fixedTimeout )
krnlSendMessage( stream->iTransportSession, IMESSAGE_SETATTRIBUTE,
( void * ) &fixedTimeout, CRYPT_OPTION_NET_TIMEOUT );
status = CRYPT_OK; /* Reset status from above checks */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -