CoolTerm.py v1.8: LookAhead() / ReadAll() silently truncate large responses — _SendPacket() does a single recv()
Posted: Sat Sep 12, 2026 3:05 am
Hi Roger,
First of all, thank you for CoolTerm — the Data Forwarding + NULL Device
combination let me mirror a live serial session into a second window and read it
from a script without disturbing the original terminal at all. It works
beautifully; in a 60 s test at 921600 baud the mirrored copy was byte-identical
to the source (5,547,776 bytes, matching SHA-256).
While building that I ran into a bug in the bundled Python module.
ENVIRONMENT
CoolTerm 2.4.0 (2.4.0.3.0.1425), Windows 11 Pro 26100, 64-bit
Scripting/Python/CoolTerm.py v1.8, January 2025
Python 3.13.5
WHAT HAPPENS
When a window's receive buffer is large, LookAhead() returns only a prefix of
it, with no error and no indication that anything is missing. Two measurements
from the same session:
BytesAvailable() = 389,188 -> len(LookAhead()) = 61,508
BytesAvailable() = 292,653 -> len(LookAhead()) = 30,509
The same applies to ReadAll() / LookAheadHex() / GetAllParameters(), i.e. any
command with a large response.
CAUSE
_SendPacket() reads the reply with a single recv() and no read loop:
self.skt.sendall(Packet)
data = self.skt.recv(65535)
return data
TCP is free to deliver the response in several segments, so whatever has not
arrived yet at that instant is lost. _getData() then slices Packet[6:6+LEN]
out of the short buffer and returns a silently shortened string. The two
numbers above are not round, and differ between calls, which is consistent
with partial reads rather than a fixed cap.
HOW TO REPRODUCE
1. Let a window accumulate more than ~64 KB in its receive buffer
(RXBufferSize set high, or feed it with Receive()).
2. Compare BytesAvailable(ID) with len(LookAhead(ID)).
SUGGESTED FIX
Read the 6-byte header first, then exactly LEN payload bytes:
With this change every response comes back complete in my tests.
QUESTION ABOUT THE PROTOCOL
The length field in the packet header is 2 bytes, so a single response can
carry at most 65,535 bytes. What does CoolTerm do when the requested data is
larger than that — is the response capped at 65,535, is the data truncated on
your side, or is there a continuation mechanism I have missed? Knowing this
would tell client authors whether they must keep RXBufferSize below 64 KB, or
drain the buffer with Read() in chunks, to be safe. It may be worth a note in
the protocol PDF either way.
TWO SMALLER OBSERVATIONS
1. The remote control socket appears to accept only one client connection at a
time — a second CoolTermSocket() prints "ERROR: Could not connect to
CoolTerm" while the first is still open. Is that by design? It is easy to
work around once you know, but it is not mentioned in the help.
2. Receive(ID, Data) returns True when the target window's port is closed, but
the data does not appear in the receive buffer (BytesAvailable stays 0).
Returning False, or documenting that the port must be open, would make this
less surprising.
Thanks again for the tool, and for keeping the protocol documented — being able
to fix the client myself is exactly why that matters.
--
Ken, with Claude (Anthropic's AI assistant) as investigation partner.
Every number above comes from an actual run against real hardware on the machine
described, not from reading the code alone — happy to re-run anything or test a
patched CoolTerm.py if that would help.
First of all, thank you for CoolTerm — the Data Forwarding + NULL Device
combination let me mirror a live serial session into a second window and read it
from a script without disturbing the original terminal at all. It works
beautifully; in a 60 s test at 921600 baud the mirrored copy was byte-identical
to the source (5,547,776 bytes, matching SHA-256).
While building that I ran into a bug in the bundled Python module.
ENVIRONMENT
CoolTerm 2.4.0 (2.4.0.3.0.1425), Windows 11 Pro 26100, 64-bit
Scripting/Python/CoolTerm.py v1.8, January 2025
Python 3.13.5
WHAT HAPPENS
When a window's receive buffer is large, LookAhead() returns only a prefix of
it, with no error and no indication that anything is missing. Two measurements
from the same session:
BytesAvailable() = 389,188 -> len(LookAhead()) = 61,508
BytesAvailable() = 292,653 -> len(LookAhead()) = 30,509
The same applies to ReadAll() / LookAheadHex() / GetAllParameters(), i.e. any
command with a large response.
CAUSE
_SendPacket() reads the reply with a single recv() and no read loop:
self.skt.sendall(Packet)
data = self.skt.recv(65535)
return data
TCP is free to deliver the response in several segments, so whatever has not
arrived yet at that instant is lost. _getData() then slices Packet[6:6+LEN]
out of the short buffer and returns a silently shortened string. The two
numbers above are not round, and differ between calls, which is consistent
with partial reads rather than a fixed cap.
HOW TO REPRODUCE
1. Let a window accumulate more than ~64 KB in its receive buffer
(RXBufferSize set high, or feed it with Receive()).
2. Compare BytesAvailable(ID) with len(LookAhead(ID)).
SUGGESTED FIX
Read the 6-byte header first, then exactly LEN payload bytes:
Code: Select all
def _recv_exact(self, n):
buf = b""
while len(buf) < n:
chunk = self.skt.recv(n - len(buf))
if not chunk:
raise ConnectionError("socket closed by CoolTerm")
buf += chunk
return buf
def _SendPacket(self, Packet):
self.skt.sendall(Packet)
head = self._recv_exact(6)
LEN = int.from_bytes(head[1:3], byteorder="little")
return head + self._recv_exact(LEN)
QUESTION ABOUT THE PROTOCOL
The length field in the packet header is 2 bytes, so a single response can
carry at most 65,535 bytes. What does CoolTerm do when the requested data is
larger than that — is the response capped at 65,535, is the data truncated on
your side, or is there a continuation mechanism I have missed? Knowing this
would tell client authors whether they must keep RXBufferSize below 64 KB, or
drain the buffer with Read() in chunks, to be safe. It may be worth a note in
the protocol PDF either way.
TWO SMALLER OBSERVATIONS
1. The remote control socket appears to accept only one client connection at a
time — a second CoolTermSocket() prints "ERROR: Could not connect to
CoolTerm" while the first is still open. Is that by design? It is easy to
work around once you know, but it is not mentioned in the help.
2. Receive(ID, Data) returns True when the target window's port is closed, but
the data does not appear in the receive buffer (BytesAvailable stays 0).
Returning False, or documenting that the port must be open, would make this
less surprising.
Thanks again for the tool, and for keeping the protocol documented — being able
to fix the client myself is exactly why that matters.
--
Ken, with Claude (Anthropic's AI assistant) as investigation partner.
Every number above comes from an actual run against real hardware on the machine
described, not from reading the code alone — happy to re-run anything or test a
patched CoolTerm.py if that would help.