read method
Reads data from the connection.
If length
is provided, reads exactly that many bytes.
Otherwise, reads whatever is available.
Implementation
@override
Future<Uint8List> read([int? length]) async {
final readId = Uuid().v4().substring(0, 8);
_log.finer('TCPConnection($id) - Read($readId) START. Requested: $length. Buffer: ${_receiveBuffer.length} bytes.');
_assertNotClosed();
if (length != null && length < 0) throw ArgumentError('Length cannot be negative');
if (length == 0) {
_log.finer('TCPConnection($id) - Read($readId) END (requested 0 bytes). Returning 0 bytes.');
return Uint8List(0);
}
while (true) { // Loop until enough data is available or EOF/error
if (length != null) { // Specific length requested
if (_receiveBuffer.length >= length) {
final result = Uint8List.fromList(_receiveBuffer.toBytes().sublist(0, length));
final remainingBytes = _receiveBuffer.toBytes().sublist(length);
_receiveBuffer.clear();
_receiveBuffer.add(remainingBytes);
_log.finer('TCPConnection($id) - Read($readId) END (from buffer). Returning ${result.length} bytes: ${hex.encode(result)}. Buffer after: ${_receiveBuffer.length} bytes: ${hex.encode(remainingBytes)}');
return result;
}
} else { // length is null, read any available data
if (_receiveBuffer.isNotEmpty) {
final result = _receiveBuffer.toBytes();
_receiveBuffer.clear();
_log.finer('TCPConnection($id) - Read($readId) END (all from buffer, length null). Returning ${result.length} bytes: ${hex.encode(result)}. Buffer after: 0');
return result;
}
}
// If not enough data, check for EOF or closed state
if (_socketIsDone) {
if (_receiveBuffer.isEmpty) {
_log.finer('TCPConnection($id) - Read($readId) END (socket done, buffer empty -> EOF). Returning 0 bytes.');
return Uint8List(0); // Clean EOF if buffer is empty
}
// Socket is done, buffer is NOT empty. Return what's available from buffer.
Uint8List resultToReturn;
if (length == null || _receiveBuffer.length <= length) {
// Requested all, or specific length but buffer has less than or equal to what's requested.
resultToReturn = _receiveBuffer.toBytes();
_receiveBuffer.clear();
_log.finer('TCPConnection($id) - Read($readId) END (socket done, returning all/partial from buffer: ${resultToReturn.length} bytes).');
} else { // length != null && _receiveBuffer.length > length
// Buffer has more than the specific length requested.
resultToReturn = Uint8List.fromList(_receiveBuffer.toBytes().sublist(0, length));
final remainingBytesInInternalBuffer = _receiveBuffer.toBytes().sublist(length);
_receiveBuffer.clear();
_receiveBuffer.add(remainingBytesInInternalBuffer);
_log.finer('TCPConnection($id) - Read($readId) END (socket done, returning specific length from buffer: ${resultToReturn.length} bytes, remaining in buffer: ${_receiveBuffer.length}).');
}
return resultToReturn;
}
if (_closed) { // Connection explicitly closed via this.close() by the application
_log.warning('TCPConnection($id) - Read($readId) ERROR: Connection explicitly closed by API call.');
throw StateError('Connection closed.');
}
// Not enough data, socket not done, connection not closed: wait for more data
_log.finer('TCPConnection($id) - Read($readId) ASYNC WAIT. Requested: $length. Buffer: ${_receiveBuffer.length}');
_pendingReadCompleter = Completer<void>();
Timer? timeoutTimer;
try {
if (_currentReadTimeout != null && _currentReadTimeout! > Duration.zero) {
timeoutTimer = Timer(_currentReadTimeout!, () {
if (_pendingReadCompleter != null && !_pendingReadCompleter!.isCompleted) {
_log.warning('TCPConnection($id) - Read($readId) TIMEOUT after $_currentReadTimeout.');
_pendingReadCompleter!.completeError(TimeoutException('Raw read timed out after $_currentReadTimeout'));
}
});
}
await _pendingReadCompleter!.future;
} finally {
timeoutTimer?.cancel();
// _pendingReadCompleter = null; // Do not nullify here, a new one is made if loop continues
}
_log.finer('TCPConnection($id) - Read($readId) ASYNC AWOKE. Re-checking buffer.');
// Loop continues
}
}