Summary
impl From<u16> for WindowMaskCellFormat in src/object_pool/object_attributes.rs never recovers the width byte, so every WindowMask read back via ObjectPool::from_iop/reader.rs reports WindowMaskCellFormat::CF1x1, regardless of what was actually written to the .iop file.
Where
src/object_pool/object_attributes.rs, line 98-101 (main branch):
impl From<u16> for WindowMaskCellFormat {
fn from(value: u16) -> Self {
WindowMaskCellFormat::from_size((value << 8) as u8, value as u8)
}
}
from_size(width: u8, height: u8) (line 61) falls back to CF1x1 for any (width, height) pair it doesn't recognize (line 76: _ => WindowMaskCellFormat::CF1x1). Two things go wrong in the From<u16> impl above:
(value << 8) as u8 is 0 for every possible u16 input -- the high byte is shifted out of the 16-bit value before the cast down to u8 truncates it.
- Even ignoring that, the two bytes are read back in the wrong order.
write_window_mask (src/object_pool/writer.rs, line 62-63) writes cell_format.size().x (width) first, then .y (height), as two separate bytes. read_window_mask (src/object_pool/reader.rs, line 1026) reads them back with Self::read_u16(data)?, whose read_u16 (reader.rs, line 236) does u16::from_le_bytes([a, b]) -- little-endian, so the first byte read (width) ends up as the low byte of value, and the second (height) as the high byte. So value as u8 is actually the width, and (value >> 8) as u8 would be the height -- but the code passes (value << 8) as u8 (always 0) as the width argument and value as u8 (the real width) as the height argument. from_size is therefore always called with width = 0, which matches nothing and falls back to CF1x1.
Reproduction
use ag_iso_stack::object_pool::object_attributes::WindowMaskCellFormat;
// on-wire bytes read_u16 would see for a written CF2x1 (width=2, height=1):
// first byte (width) = 2, second byte (height) = 1 -> le_bytes([2, 1]) = 0x0102
let on_wire: u16 = u16::from_le_bytes([2, 1]);
assert_eq!(WindowMaskCellFormat::from(on_wire), WindowMaskCellFormat::CF2x1); // fails, is CF1x1
More concretely: build any WindowMask object with cell_format: WindowMaskCellFormat::CF2x1, serialize it with ObjectPool::as_iop(), then parse the resulting bytes back with ObjectPool::from_iop(). The round-tripped WindowMask.cell_format comes back as CF1x1.
Impact
Only affects reading a .iop pool back into this library's object model (e.g. round-trip tests, or tools that inspect an existing pool). The bytes written to the .iop file itself are unaffected and ISO 11783-6-correct -- a real Virtual Terminal parses the two raw bytes directly, not through this From<u16> impl, so VT rendering is not affected. Found while writing an external tool that builds a .iop pool with ag-iso-stack and verifies it by reading it back with the same library.
Suggested fix
impl From<u16> for WindowMaskCellFormat {
fn from(value: u16) -> Self {
WindowMaskCellFormat::from_size(value as u8, (value >> 8) as u8)
}
}
i.e. swap which byte gets shifted, and swap the argument order to match from_size(width, height) and the little-endian byte order read_u16 already uses.
Summary
impl From<u16> for WindowMaskCellFormatinsrc/object_pool/object_attributes.rsnever recovers the width byte, so everyWindowMaskread back viaObjectPool::from_iop/reader.rsreportsWindowMaskCellFormat::CF1x1, regardless of what was actually written to the.iopfile.Where
src/object_pool/object_attributes.rs, line 98-101 (mainbranch):from_size(width: u8, height: u8)(line 61) falls back toCF1x1for any(width, height)pair it doesn't recognize (line 76:_ => WindowMaskCellFormat::CF1x1). Two things go wrong in theFrom<u16>impl above:(value << 8) as u8is0for every possibleu16input -- the high byte is shifted out of the 16-bit value before the cast down tou8truncates it.write_window_mask(src/object_pool/writer.rs, line 62-63) writescell_format.size().x(width) first, then.y(height), as two separate bytes.read_window_mask(src/object_pool/reader.rs, line 1026) reads them back withSelf::read_u16(data)?, whoseread_u16(reader.rs, line 236) doesu16::from_le_bytes([a, b])-- little-endian, so the first byte read (width) ends up as the low byte ofvalue, and the second (height) as the high byte. Sovalue as u8is actually the width, and(value >> 8) as u8would be the height -- but the code passes(value << 8) as u8(always 0) as thewidthargument andvalue as u8(the real width) as theheightargument.from_sizeis therefore always called withwidth = 0, which matches nothing and falls back toCF1x1.Reproduction
More concretely: build any
WindowMaskobject withcell_format: WindowMaskCellFormat::CF2x1, serialize it withObjectPool::as_iop(), then parse the resulting bytes back withObjectPool::from_iop(). The round-trippedWindowMask.cell_formatcomes back asCF1x1.Impact
Only affects reading a
.ioppool back into this library's object model (e.g. round-trip tests, or tools that inspect an existing pool). The bytes written to the.iopfile itself are unaffected and ISO 11783-6-correct -- a real Virtual Terminal parses the two raw bytes directly, not through thisFrom<u16>impl, so VT rendering is not affected. Found while writing an external tool that builds a.ioppool withag-iso-stackand verifies it by reading it back with the same library.Suggested fix
i.e. swap which byte gets shifted, and swap the argument order to match
from_size(width, height)and the little-endian byte orderread_u16already uses.