📄 sess_rw.c
字号:
we haven't already returned existing/earlier data. This ensures
that the caller can drain out any remaining data from the session
buffer before they start getting error returns */
if( *bytesCopied <= 0 )
{
if( readInfo == READINFO_FATAL )
sessionInfoPtr->readErrorState = status;
return( status );
}
/* We got some data before encountering the error, if it's fatal
save the pending error state for later while returning the read
byte count to the caller. Note that this results in non-fatal
errors being quietly dropped if data is otherwise available, the
alternative would be to save it as a pending (specially-marked)
non-fatal error, however since this error type by definition can
be resumed it may already have resolved itself by the next time
that we're called, so this is safe to do */
if( readInfo == READINFO_FATAL )
sessionInfoPtr->pendingReadErrorState = status;
return( OK_SPECIAL );
}
/* If we got nothing, exit */
if( status == 0 )
return( OK_SPECIAL );
/* If we read a partial packet and there's room for the rest of the
packet in the buffer, set a minimum timeout to try and get the rest
of the packet. This is safe because tryRead() could have behaved in
only one of two ways:
1. Blocking read, in which case we waited for the full timeout
period anyway and a small additional timeout won't be noticed.
2. Nonblocking read, in which case waiting for a nonzero time could
potentially have retrieved more data */
if( status == OK_SPECIAL )
{
assert( readInfo == READINFO_PARTIAL || \
readInfo == READINFO_NOOP );
if( readInfo == READINFO_PARTIAL && \
sessionInfoPtr->pendingPacketRemaining <= \
sessionInfoPtr->receiveBufSize - sessionInfoPtr->receiveBufEnd )
sioctl( &sessionInfoPtr->stream, STREAM_IOCTL_READTIMEOUT, NULL, 1 );
return( CRYPT_OK );
}
/* Make the stream nonblocking if it was blocking before. This is
necessary to avoid having the stream always block for the set timeout
value on the last read */
assert( status > 0 );
sioctl( &sessionInfoPtr->stream, STREAM_IOCTL_READTIMEOUT, NULL, 0 );
return( CRYPT_OK );
}
int getSessionData( SESSION_INFO *sessionInfoPtr, void *data,
const int length, int *bytesCopied )
{
BYTE *dataPtr = data;
int dataLength = length, status = CRYPT_OK;
/* Clear return value */
*bytesCopied = 0;
/* If there's an error pending (which will always be fatal, see the
comment after the tryRead() call in getData()), set the current error
state to the pending state and return */
if( cryptStatusError( sessionInfoPtr->pendingReadErrorState ) )
{
assert( sessionInfoPtr->receiveBufPos == 0 );
status = sessionInfoPtr->readErrorState = \
sessionInfoPtr->pendingReadErrorState;
sessionInfoPtr->pendingReadErrorState = CRYPT_OK;
return( status );
}
/* Update the stream read timeout to the current user-selected read
timeout in case the user has changed the timeout setting */
sioctl( &sessionInfoPtr->stream, STREAM_IOCTL_READTIMEOUT, NULL,
sessionInfoPtr->readTimeout );
while( cryptStatusOK( status ) && dataLength > 0 )
{
int count;
/* Get the next packets-worth of data. This can return one of three
classes of values:
1. An error code.
2. OK_SPECIAL to indicate that some data was read but no more is
available.
3. CRYPT_OK to indicate that data was read and more may be
available.
Note that we can have data available even if an error status is
returned since it can successfully read data before encountering
the error, so we update the byte count no matter what the return
status */
status = getData( sessionInfoPtr, dataPtr, dataLength, &count );
if( count > 0 )
{
*bytesCopied += count;
dataPtr += count;
dataLength -= count;
}
assert( sessionInfoPtr->receiveBufEnd <= \
sessionInfoPtr->receiveBufSize );
assert( sessionInfoPtr->receiveBufPos <= \
sessionInfoPtr->receiveBufEnd );
}
/* If we got at least some data or encountered a soft timeout, the
operation was (nominally) successful, otherwise it's an error */
return( ( *bytesCopied > 0 || status == OK_SPECIAL ) ? \
CRYPT_OK : status );
}
/* Read a fixed-size packet header, called by the secure data session
routines to read the fixed header on a data packet. This is an atomic
read of out-of-band data that isn't part of the packet payload, so we
have to make sure that we've got the entire header before we can
continue:
| <- hdrSize -> |
----+---------------+--------
////| |
----+---------------+--------
^ ^
| |
bEnd partialHdr
The data is read into the read buffer starting at the end of the last
payload packet bEnd, this is safe because this function causes a
pipeline stall so no more data can be read until the header has been
read. The function then returns CRYPT_ERROR_TIMEOUT until partialHdr
reaches the full header size */
int readFixedHeader( SESSION_INFO *sessionInfoPtr, const int headerSize )
{
BYTE *bufPtr = sessionInfoPtr->receiveBuffer + \
sessionInfoPtr->receiveBufEnd;
int status;
/* If it's the first attempt at reading the header, set the total byte
count */
if( sessionInfoPtr->partialHeaderLength <= 0 )
sessionInfoPtr->partialHeaderLength = headerSize;
else
bufPtr += headerSize - sessionInfoPtr->partialHeaderLength;
assert( sessionInfoPtr->partialHeaderLength > 0 && \
sessionInfoPtr->partialHeaderLength <= headerSize );
/* Clear the first few bytes of returned data to make sure that the
higher-level code always bails out if the read fails for some reason
without returning an error status */
memset( bufPtr, 0, min( headerSize, 8 ) );
/* Try and read the remaining header bytes */
status = sread( &sessionInfoPtr->stream, bufPtr,
sessionInfoPtr->partialHeaderLength );
if( cryptStatusError( status ) )
{
/* We could be trying to read an ack for a close packet sent in
response to an earlier error, in which case we don't want the
already-present error information overwritten by network
error info, so if the no-report-error flag is set we don't
update the extended error info */
if( sessionInfoPtr->flags & SESSION_NOREPORTERROR )
return( status );
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
&sessionInfoPtr->errorCode );
return( status );
}
/* If we didn't get the whole header, treat it as a timeout error */
if( status < sessionInfoPtr->partialHeaderLength )
{
/* If we timed out during the handshake phase, treat it as a hard
timeout error */
if( !( sessionInfoPtr->flags & SESSION_ISOPEN ) )
{
if( sessionInfoPtr->flags & SESSION_NOREPORTERROR )
return( status );
retExt( sessionInfoPtr, CRYPT_ERROR_TIMEOUT,
"Timeout during packet header read, only got %d of %d "
"bytes", status, headerSize );
}
/* We're in the data-processing stage, it's a soft timeout error */
sessionInfoPtr->partialHeaderLength -= status;
return( 0 );
}
/* We've got the whole header ready to process */
assert( sessionInfoPtr->partialHeaderLength == status );
sessionInfoPtr->partialHeaderLength = 0;
return( headerSize );
}
/****************************************************************************
* *
* Secure Session Data Write Functions *
* *
****************************************************************************/
/* Send data to the remote system. There are two strategies for handling
buffer filling and partial writes, either to fill the buffer as full as
possible and write it all at once, or to write complete packets as soon
as they're available. We use the latter strategy here, both because it
considerably simplifies buffer management and because interleaving
(asynchronous) writes and packet processing increases the chances that
the current packet will be successfully dispatched across the network
while the next one is being encrypted - trying to asynchronously write a
large amount of data in one go practically guarantees that the write
won't complete.
Session buffer management is handled as follows: The startOfs index
points to the start of the payload space in the buffer (everything before
this is header data). The maxPacketSize value indicates the end of the
payload space relative to the startOfs:
<- hdr->|<-- payload -->|
+-------+---------------+---+
| |///////////////| |
+-------+---------------+---+
^ ^
| |
startOfs maxPacketSize
The bPos index moves from startsOfs to maxPacketSize, after which the
data is wrapped up by the protocol-specific code. At this point bPos
usually points past the end of maxPacketSize due to the addition of
trailer data such as encryption block padding and a MAC. Once the
packet is assembled, the data is flushed and the bPos index reset:
startOfs maxPacketSize
| |
v v
+-------+-------+-------+---+
|.......|.......|///////|///|
+-------+-------+-------+---+
^<--- to -->^
| write |
partialBufPos bufPos
As with reads, writes can be non-atomic, although on a more restrictive
scale than reads: Once an encrypted packet has been assembled in the
write buffer, the entire contents must be written before a new packet can
be assembled. This guarantees that when the caller flushes data through
to the other side, all of the data will be sent (and the other side will
have a chance to react to it) before the next load of data can be flushed
through.
Once we have partial data in the send buffer, all further attempts to
add more data fail until the remainder of the partially-written data
has been flushed. This is handled by setting sendBufPartialBufPos to
point to the first byte of unwritten data, so that
sendBufPartialBufPos ... sendBufPos remains to be written */
static int flushData( SESSION_INFO *sessionInfoPtr )
{
int length, status;
/* If there's no data to flush, exit */
if( sessionInfoPtr->sendBufPos <= sessionInfoPtr->sendBufStartOfs )
return( CRYPT_OK );
/* If there's no unwritten data from a previous write attempt still
present, prepare to send the new data */
if( !sessionInfoPtr->partialWrite )
{
assert( sessionInfoPtr->sendBufPartialBufPos == 0 );
status = length = \
sessionInfoPtr->preparePacketFunction( sessionInfoPtr );
if( cryptStatusError( status ) )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -