Skip to content

Commit 199f7c2

Browse files
committed
fix: Unsigned interpretation
1 parent d84d7d0 commit 199f7c2

File tree

5 files changed

+59
-23
lines changed

5 files changed

+59
-23
lines changed

src/pybag/bag/record_parser.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def _parse_header_field(cls, data: bytes, offset: int) -> tuple[int, str, bytes]
8080
Returns:
8181
Tuple of (new_offset, field_name, field_value).
8282
"""
83-
field_len = struct.unpack_from('<i', data, offset)[0]
83+
field_len = struct.unpack_from('<I', data, offset)[0]
8484
offset += 4
8585
field_data = data[offset:offset + field_len]
8686
offset += field_len
@@ -135,7 +135,7 @@ def parse_record(cls, file: BaseReader) -> tuple[int, Any] | None:
135135
header_len_bytes = file.read(4)
136136
if len(header_len_bytes) < 4:
137137
return None # EOF
138-
header_len = struct.unpack('<i', header_len_bytes)[0]
138+
header_len = struct.unpack('<I', header_len_bytes)[0]
139139
header = cls._parse_header(file, header_len)
140140

141141
# Get the operation type
@@ -147,7 +147,7 @@ def parse_record(cls, file: BaseReader) -> tuple[int, Any] | None:
147147
data_len_bytes = file.read(4)
148148
if len(data_len_bytes) < 4:
149149
return None # EOF
150-
data_len = struct.unpack('<i', data_len_bytes)[0]
150+
data_len = struct.unpack('<I', data_len_bytes)[0]
151151
data = cls._parse_data(file, data_len)
152152

153153
return op, cls._parse_record_by_type(op, header, data)
@@ -183,9 +183,9 @@ def _parse_bag_header(
183183
data: bytes
184184
) -> BagHeaderRecord:
185185
"""Parse a bag header record."""
186-
index_pos = struct.unpack('<q', header['index_pos'])[0]
187-
conn_count = struct.unpack('<i', header['conn_count'])[0]
188-
chunk_count = struct.unpack('<i', header['chunk_count'])[0]
186+
index_pos = struct.unpack('<Q', header['index_pos'])[0]
187+
conn_count = struct.unpack('<I', header['conn_count'])[0]
188+
chunk_count = struct.unpack('<I', header['chunk_count'])[0]
189189
return BagHeaderRecord(index_pos, conn_count, chunk_count, data)
190190

191191
@classmethod
@@ -196,7 +196,7 @@ def _parse_chunk(
196196
) -> ChunkRecord:
197197
"""Parse a chunk record."""
198198
compression = header['compression'].decode('ascii')
199-
size = struct.unpack('<i', header['size'])[0]
199+
size = struct.unpack('<I', header['size'])[0]
200200
return ChunkRecord(compression, size, data)
201201

202202
@classmethod
@@ -210,7 +210,7 @@ def _parse_connection(
210210
The connection header contains basic info, and the data section
211211
contains the full connection header from the original publisher.
212212
"""
213-
conn = struct.unpack('<i', header['conn'])[0]
213+
conn = struct.unpack('<I', header['conn'])[0]
214214
topic = header['topic'].decode('utf-8')
215215
return ConnectionRecord(conn, topic, data)
216216

@@ -221,7 +221,7 @@ def _parse_message_data(
221221
data: bytes
222222
) -> MessageDataRecord:
223223
"""Parse a message data record."""
224-
conn = struct.unpack('<i', header['conn'])[0]
224+
conn = struct.unpack('<I', header['conn'])[0]
225225
time = _decode_ros_time(header['time'])
226226
return MessageDataRecord(conn, time, data)
227227

@@ -232,9 +232,9 @@ def _parse_index_data(
232232
data: bytes
233233
) -> IndexDataRecord:
234234
"""Parse an index data record."""
235-
ver = struct.unpack('<i', header['ver'])[0]
236-
conn = struct.unpack('<i', header['conn'])[0]
237-
count = struct.unpack('<i', header['count'])[0]
235+
ver = struct.unpack('<I', header['ver'])[0]
236+
conn = struct.unpack('<I', header['conn'])[0]
237+
count = struct.unpack('<I', header['count'])[0]
238238
return IndexDataRecord(ver, conn, count, data)
239239

240240
@classmethod
@@ -244,11 +244,11 @@ def _parse_chunk_info(
244244
data: bytes
245245
) -> ChunkInfoRecord:
246246
"""Parse a chunk info record."""
247-
ver = struct.unpack('<i', header['ver'])[0]
248-
chunk_pos = struct.unpack('<q', header['chunk_pos'])[0]
247+
ver = struct.unpack('<I', header['ver'])[0]
248+
chunk_pos = struct.unpack('<Q', header['chunk_pos'])[0]
249249
start_time = _decode_ros_time(header['start_time'])
250250
end_time = _decode_ros_time(header['end_time'])
251-
count = struct.unpack('<i', header['count'])[0]
251+
count = struct.unpack('<I', header['count'])[0]
252252
return ChunkInfoRecord(ver, chunk_pos, start_time, end_time, count, data)
253253

254254
@classmethod

src/pybag/bag/records.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def connection_header(self) -> ConnectionHeader:
8383
fields: dict[str, bytes] = {}
8484
offset = 0
8585
while offset < len(self.data):
86-
field_len = struct.unpack_from('<i', self.data, offset)[0]
86+
field_len = struct.unpack_from('<I', self.data, offset)[0]
8787
offset += 4
8888
field_data = self.data[offset:offset + field_len]
8989
offset += field_len
@@ -192,7 +192,7 @@ def connection_counts(self) -> dict[int, int]:
192192
num_entries = len(self.data) // 8
193193
for i in range(num_entries):
194194
offset = i * 8
195-
conn_id, msg_count = struct.unpack_from('<ii', self.data, offset)
195+
conn_id, msg_count = struct.unpack_from('<II', self.data, offset)
196196
counts[conn_id] = msg_count
197197
return counts
198198

@@ -224,8 +224,8 @@ def entries(self) -> list[tuple[int, int]]:
224224
result: list[tuple[int, int]] = []
225225
for i in range(self.count):
226226
offset = i * 12
227-
# ROS time format: two uint32 (secs, nsecs) + int32 offset
228-
secs, nsecs, chunk_offset = struct.unpack_from('<IIi', self.data, offset)
227+
# ROS time format: two uint32 (secs, nsecs) + uint32 offset
228+
secs, nsecs, chunk_offset = struct.unpack_from('<III', self.data, offset)
229229
timestamp_ns = secs * NSEC_PER_SEC + nsecs
230230
result.append((timestamp_ns, chunk_offset))
231231
return result

src/pybag/bag_writer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def _encode_header_field(name: str, value: bytes) -> bytes:
146146
Format: field_len (4 bytes) | name=value
147147
"""
148148
field_data = name.encode('ascii') + b'=' + value
149-
return struct.pack('<i', len(field_data)) + field_data
149+
return struct.pack('<I', len(field_data)) + field_data
150150

151151
def _write_header(self) -> None:
152152
"""Write the initial file header.
@@ -510,7 +510,7 @@ def _flush_chunk(self) -> None:
510510
for time_ns, offset in entries:
511511
secs = time_ns // NSEC_PER_SEC
512512
nsecs = time_ns % NSEC_PER_SEC
513-
index_data_buffer.write(struct.pack('<IIi', secs, nsecs, offset))
513+
index_data_buffer.write(struct.pack('<III', secs, nsecs, offset))
514514
# Write the index records
515515
index_record = IndexDataRecord(
516516
ver=1,

tests/bag/test_bag_records.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import struct
2+
3+
import pytest
4+
5+
from pybag.bag.records import ChunkInfoRecord, IndexDataRecord
6+
7+
8+
def test_connection_counts_max_uint32(self):
9+
"""Test connection counts with maximum uint32 values."""
10+
max_uint32 = 2**32 - 1 # 4294967295
11+
12+
data = struct.pack('<II', max_uint32, max_uint32)
13+
14+
record = ChunkInfoRecord(
15+
ver=1,
16+
chunk_pos=0,
17+
start_time=0,
18+
end_time=0,
19+
count=1,
20+
data=data,
21+
)
22+
23+
counts = record.connection_counts
24+
assert max_uint32 in counts
25+
assert counts[max_uint32] == max_uint32
26+
27+
28+
def test_index_entries_max_offset(self):
29+
"""Test index entries with maximum uint32 offset."""
30+
max_offset = 2**32 - 1 # 4294967295
31+
32+
data = struct.pack('<III', 0, 0, max_offset)
33+
34+
record = IndexDataRecord(ver=1, conn=0, count=1, data=data)
35+
entries = record.entries
36+
37+
timestamp_ns, offset = entries[0]
38+
assert offset == max_offset

tests/bag/test_rosmsg.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Tests for ROS 1 message serialization (rosmsg format)."""
22

3-
import struct
4-
53
import pytest
64

75
from pybag.encoding.rosmsg import RosMsgDecoder, RosMsgEncoder

0 commit comments

Comments
 (0)