📄 net_trans.c
字号:
| |///////////| |
+-------+-----------+-------+
-- Read -->
We fill the buffer to bEnd and then empty it by 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 --> */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
static int bufferedTransportReadFunction( INOUT STREAM *stream,
OUT_BUFFER( maxLength, length ) \
BYTE *buffer,
IN_LENGTH const int maxLength,
OUT_LENGTH_Z int *length,
IN_FLAGS_Z( TRANSPORT ) \
const int flags )
{
NET_STREAM_INFO *netStream = ( NET_STREAM_INFO * ) stream->netStreamInfo;
const int bytesLeft = stream->bufEnd - stream->bufPos;
int bufferBytesRead, bytesRead, status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( buffer, maxLength ) );
assert( isWritePtr( length, sizeof( int ) ) );
assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
REQUIRES_S( netStream->sanityCheckFunction( stream ) );
REQUIRES_S( maxLength > 0 && maxLength < MAX_INTLENGTH );
REQUIRES_S( bytesLeft >= 0 && bytesLeft < MAX_INTLENGTH_SHORT );
REQUIRES_S( flags >= TRANSPORT_FLAG_NONE && \
flags <= TRANSPORT_FLAG_MAX );
/* Clear return value */
*length = 0;
/* If there's enough data in the buffer to satisfy the request, return it
directly */
if( maxLength <= bytesLeft )
{
if( maxLength == 1 )
{
/* Optimisation for char-at-a-time HTTP header reads */
*buffer = stream->buffer[ stream->bufPos++ ];
}
else
{
memcpy( buffer, stream->buffer + stream->bufPos, maxLength );
stream->bufPos += maxLength;
}
*length = maxLength;
ENSURES_S( netStream->sanityCheckFunction( stream ) );
return( CRYPT_OK );
}
/* 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;
}
ENSURES_S( stream->bufPos == 0 );
ENSURES_S( maxLength > bytesLeft );
/* If there's more room in the buffer, refill it */
if( stream->bufEnd < stream->bufSize )
{
int bytesToRead;
/* Calculate how many bytes we still need to read from the network into
the buffer and how much room there is in it. If the read count is
less than the available buffer space we only read that much, any
further space will be filled (if possible) by the opportunistic
read that follows */
bytesToRead = stream->bufSize - stream->bufEnd;
if( bytesToRead > maxLength )
bytesToRead = maxLength;
/* 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 actually need
in order to fulfill the request */
status = netStream->transportReadFunction( stream,
stream->buffer + stream->bufEnd,
bytesToRead, &bytesRead,
TRANSPORT_FLAG_BLOCKING );
if( cryptStatusError( status ) )
return( status );
stream->bufEnd += bytesRead;
/* If there's room for more, perform an opportunistic 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 )
{
status = netStream->transportReadFunction( stream,
stream->buffer + stream->bufEnd,
stream->bufSize - stream->bufEnd,
&bytesRead, TRANSPORT_FLAG_NONBLOCKING );
if( cryptStatusOK( status ) )
stream->bufEnd += bytesRead;
}
}
ENSURES_S( netStream->sanityCheckFunction( stream ) );
/* Read as much as we can from the buffer */
bufferBytesRead = min( maxLength, stream->bufEnd );
memcpy( buffer, stream->buffer, bufferBytesRead );
stream->bufPos = bufferBytesRead;
*length = bufferBytesRead;
/* If we could satisfy the entire read from the buffer, we're done */
if( maxLength <= bufferBytesRead ) /* Actually length == bufferBytesRead */
{
ENSURES_S( netStream->sanityCheckFunction( stream ) );
return( CRYPT_OK );
}
/* We've drained the stream buffer and there's more to go, read the
remainder directly into the caller's buffer. What to return in case
there's a failure at this point is a bit tricky since we can
successfully return some data from the internal buffer but then fail
when we try and replenish the buffer from the network. For now we
simply force the operation to be atomic since we're reading PKI
datagrams that have to be read in their entirety */
status = netStream->transportReadFunction( stream,
buffer + bufferBytesRead, maxLength - bufferBytesRead,
&bytesRead, TRANSPORT_FLAG_BLOCKING );
if( cryptStatusError( status ) )
return( status );
*length += bytesRead;
ENSURES_S( netStream->sanityCheckFunction( stream ) );
return( CRYPT_OK );
}
/* 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 and 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. This results
in a 200ms (+ assorted RTT) delay in each message sent.
There's 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.
There are other, non-portable workarounds for this as well but they're so
non-portable that they often don't even work across different versions of
the same OS (e.g. different versions of the Linux kernel) let alone
variants of one OS type (e.g. OpenBSD vs. FreeBSD). The least nonportable
one is using writev() to combine a seperate header and body, which exists
in most Unix versions and Win32. Easier-to-use but almost totally non-
portable are facilities like TCP_CORK (newer Linux kernels) and
TCP_NOPUSH (some *BSDs) which delay sending buffer contents until the
flag is reset again (so the use is "set TCP_CORK, write, write, write,
reset TCP_CORK"). Because all of these are far more trouble than they're
worth and in any case we're only sending very small data quantities via
these functions (PKI messages) we just assemble the whole datagram
ourselves, which works across all OSes */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 4 ) ) \
static int processIncompleteWrite( INOUT NET_STREAM_INFO *netStream,
IN_LENGTH const int bytesWritten,
IN_LENGTH_Z const int newDataToWrite,
OUT_LENGTH_Z int *newDataWritten )
{
const int bytesLeftToWrite = netStream->writeBufEnd - bytesWritten;
assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
REQUIRES( bytesWritten > 0 && bytesWritten < netStream->writeBufEnd && \
bytesWritten < MAX_INTLENGTH );
REQUIRES( newDataToWrite >= 0 && newDataToWrite < MAX_INTLENGTH );
/* May be zero if the write buffer was already full */
/* Clear return value */
*newDataWritten = 0;
/* Determine how much was written from what the user gave us. This is
complicated by the fact that the write buffer may already contain
buffered data from a previous write so we want to report to the
caller only what was written from the new data that was supplied:
|<-- newDataToWrite --->|
|<---------------------- bufEnd ------------------->|
+---------------------------+-----------------------+
| Existing data in buffer | New data copied in |
+---------------------------+-----------------------+
|<-- bytesWritten --> ........ <-- bytesLeftToWr -->|
We can tell whether only existing data or newly-copied-in data was
written based on whether bytesLeftToWrite covers only the new data
or whether it reaches back into the existing data in the buffer. If
bytesLeftToWrite reaches back into the existing data then no new data
could be written */
if( bytesLeftToWrite < newDataToWrite )
*newDataWritten = newDataToWrite - bytesLeftToWrite;
/* We couldn't write all of the data in the buffer, move what's left
down to the start. This shouldn't be needed since the caller will
convert the failure to write the full amount into a write timeout but
we do it anyway just to be neat */
ENSURES( bytesLeftToWrite > 0 && \
bytesLeftToWrite <= netStream->writeBufEnd );
memmove( netStream->writeBuffer, netStream->writeBuffer + bytesWritten,
bytesLeftToWrite );
netStream->writeBufEnd = bytesLeftToWrite;
return( CRYPT_OK );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
static int bufferedTransportWriteFunction( INOUT STREAM *stream,
IN_BUFFER( length ) const BYTE *buffer,
IN_LENGTH const int maxLength,
OUT_LENGTH_Z int *length,
IN_FLAGS_Z( TRANSPORT ) \
const int flags )
{
NET_STREAM_INFO *netStream = ( NET_STREAM_INFO * ) stream->netStreamInfo;
const BYTE *bufPtr = buffer;
int byteCount = maxLength, bytesWritten, status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isReadPtr( buffer, maxLength ) );
assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
REQUIRES_S( netStream->sanityCheckFunction( stream ) );
REQUIRES_S( maxLength > 0 && maxLength < MAX_INTLENGTH );
REQUIRES_S( flags >= TRANSPORT_FLAG_NONE && \
flags <= TRANSPORT_FLAG_MAX );
/* Clear return value */
*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 ) && \
netStream->writeBufEnd + byteCount <= netStream->writeBufSize )
{
memcpy( netStream->writeBuffer + netStream->writeBufEnd, buffer,
byteCount );
netStream->writeBufEnd += byteCount;
*length = byteCount;
ENSURES_S( netStream->sanityCheckFunction( stream ) );
return( CRYPT_OK );
}
/* It's a flush or there's too much data to buffer, assemble a complete
buffer and write it */
if( netStream->writeBufEnd > 0 )
{
int bytesToCopy;
/* Calculate how much data we can still add to the buffer. If the write
count is less than the available buffer size we only write that much */
bytesToCopy = netStream->writeBufSize - netStream->writeBufEnd;
if( bytesToCopy > byteCount )
bytesToCopy = byteCount;
if( bytesToCopy > 0 )
{
memcpy( netStream->writeBuffer + netStream->writeBufEnd, buffer,
bytesToCopy );
netStream->writeBufEnd += bytesToCopy;
}
status = netStream->transportWriteFunction( stream,
netStream->writeBuffer, netStream->writeBufEnd,
&bytesWritten, TRANSPORT_FLAG_NONE );
if( cryptStatusError( status ) )
return( status );
if( bytesWritten < netStream->writeBufEnd )
{
status = processIncompleteWrite( netStream, bytesWritten,
bytesToCopy, length );
if( cryptStatusError( status ) )
return( status );
ENSURES_S( netStream->sanityCheckFunction( stream ) );
return( CRYPT_OK );
}
netStream->writeBufEnd = 0;
if( bytesToCopy > 0 )
{
bufPtr += bytesToCopy;
byteCount -= bytesToCopy;
if( byteCount <= 0 )
{
/* We've written everything, exit */
*length = maxLength;
ENSURES_S( netStream->sanityCheckFunction( stream ) );
return( CRYPT_OK );
}
}
}
ENSURES( netStream->writeBufEnd == 0 );
/* Write anything that's left directly */
status = netStream->transportWriteFunction( stream, bufPtr, byteCount,
&bytesWritten,
TRANSPORT_FLAG_NONE );
if( cryptStatusError( status ) )
return( status );
if( bytesWritten < byteCount )
{
/* Calculate how much remains to be written. The overall amount
written was the total amount to write minus what's left
unwritten. We don't have to update the stream buffer
information this time because the write buffer has already been
emptied */
byteCount -= bytesWritten;
*length = maxLength - byteCount;
}
else
{
/* We managed to write everything */
*length = maxLength;
}
ENSURES_S( netStream->sanityCheckFunction( stream ) );
return( CRYPT_OK );
}
STDC_NONNULL_ARG( ( 1 ) ) \
void setStreamLayerBuffering( INOUT NET_STREAM_INFO *netStream,
const BOOLEAN useTransportBuffering )
{
assert( isWritePtr( netStream, sizeof( NET_STREAM_INFO ) ) );
if( useTransportBuffering )
{
netStream->sanityCheckFunction = sanityCheckBufferedFunction;
netStream->bufferedTransportReadFunction = bufferedTransportReadFunction;
netStream->bufferedTransportWriteFunction = bufferedTransportWriteFunction;
}
else
{
netStream->sanityCheckFunction = sanityCheckFunction;
netStream->bufferedTransportReadFunction = netStream->transportReadFunction;
netStream->bufferedTransportWriteFunction = netStream->transportWriteFunction;
}
}
#endif /* USE_TCP */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -