From fca86d4aa4eb48e2c0f6bc896175ef561dab6dce Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 23 Mar 2026 22:34:01 -0700 Subject: [PATCH 1/2] fix(dart): use BigInt for int64 serialization on web MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ByteData.getInt64() and ByteData.setInt64() throw UnsupportedError on Dart web because JavaScript numbers are 64-bit doubles with no native 64-bit integer support. deserializeInt64() now uses _bytesToBigInt() (the same approach already used by deserializeUint64()). serializeInt64() now uses manual byte-splitting (matching serializeUint64()). This was discovered via rinf (Rust-Flutter bridge) where any signal containing i64 fields silently failed deserialization on web — the signal was sent successfully from Rust but Dart never received it because the bincode deserialization threw an uncaught UnsupportedError. --- .../runtime/dart/serde/binary_deserializer.dart | 6 ++++-- .../runtime/dart/serde/binary_serializer.dart | 10 +++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/serde-generate/runtime/dart/serde/binary_deserializer.dart b/serde-generate/runtime/dart/serde/binary_deserializer.dart index 44eeee4cc..b03b5be50 100644 --- a/serde-generate/runtime/dart/serde/binary_deserializer.dart +++ b/serde-generate/runtime/dart/serde/binary_deserializer.dart @@ -79,9 +79,11 @@ abstract class BinaryDeserializer { } int deserializeInt64() { - final result = input.getInt64(_offset, Endian.little); + // ByteData.getInt64 throws UnsupportedError on Dart web (JS numbers are + // 64-bit doubles). Use the manual BigInt path which works on all platforms. + final number = _bytesToBigInt(8, signed: true); _offset += 8; - return result; + return number.toInt(); } double deserializeFloat32() { diff --git a/serde-generate/runtime/dart/serde/binary_serializer.dart b/serde-generate/runtime/dart/serde/binary_serializer.dart index 0d2d5b89b..16ffd6686 100644 --- a/serde-generate/runtime/dart/serde/binary_serializer.dart +++ b/serde-generate/runtime/dart/serde/binary_serializer.dart @@ -101,7 +101,15 @@ abstract class BinarySerializer { } void serializeInt64(int value) { - final bdata = ByteData(8)..setInt64(0, value, Endian.little); + // ByteData.setInt64 throws UnsupportedError on Dart web (JS numbers are + // 64-bit doubles). Use manual byte-splitting which works on all platforms. + BigInt number = BigInt.from(value); + final byteMask = BigInt.from(0xFF); + var bdata = Uint8List(8); + for (int i = 0; i < 8; i++) { + bdata[i] = (number & byteMask).toInt(); + number = number >> 8; + } output.addAll(bdata.buffer.asUint8List()); } From 6d87c577d08b2e0244bc9dbd75f304f553d641ef Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 23 Mar 2026 23:13:59 -0700 Subject: [PATCH 2/2] add tests --- .../dart/serde/binary_deserializer.dart | 2 - .../runtime/dart/serde/binary_serializer.dart | 2 - .../runtime/dart/test/bincode_test.dart | 46 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/serde-generate/runtime/dart/serde/binary_deserializer.dart b/serde-generate/runtime/dart/serde/binary_deserializer.dart index b03b5be50..661140272 100644 --- a/serde-generate/runtime/dart/serde/binary_deserializer.dart +++ b/serde-generate/runtime/dart/serde/binary_deserializer.dart @@ -79,8 +79,6 @@ abstract class BinaryDeserializer { } int deserializeInt64() { - // ByteData.getInt64 throws UnsupportedError on Dart web (JS numbers are - // 64-bit doubles). Use the manual BigInt path which works on all platforms. final number = _bytesToBigInt(8, signed: true); _offset += 8; return number.toInt(); diff --git a/serde-generate/runtime/dart/serde/binary_serializer.dart b/serde-generate/runtime/dart/serde/binary_serializer.dart index 16ffd6686..899c49063 100644 --- a/serde-generate/runtime/dart/serde/binary_serializer.dart +++ b/serde-generate/runtime/dart/serde/binary_serializer.dart @@ -101,8 +101,6 @@ abstract class BinarySerializer { } void serializeInt64(int value) { - // ByteData.setInt64 throws UnsupportedError on Dart web (JS numbers are - // 64-bit doubles). Use manual byte-splitting which works on all platforms. BigInt number = BigInt.from(value); final byteMask = BigInt.from(0xFF); var bdata = Uint8List(8); diff --git a/serde-generate/runtime/dart/test/bincode_test.dart b/serde-generate/runtime/dart/test/bincode_test.dart index 0ee29da2f..8f932afa7 100644 --- a/serde-generate/runtime/dart/test/bincode_test.dart +++ b/serde-generate/runtime/dart/test/bincode_test.dart @@ -95,6 +95,52 @@ void main() { expect(() => serializer.serializeInt32(-2147483649), throwsException); }); + test('serializeInt64', () { + final serializer = BincodeSerializer(); + serializer.serializeInt64(1); + expect(serializer.bytes, + Uint8List.fromList([1, 0, 0, 0, 0, 0, 0, 0])); + final deserializer = BincodeDeserializer(serializer.bytes); + expect(deserializer.deserializeInt64(), 1); + }); + + test('serializeInt64 negative', () { + final serializer = BincodeSerializer(); + serializer.serializeInt64(-1); + expect(serializer.bytes, + Uint8List.fromList([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])); + final deserializer = BincodeDeserializer(serializer.bytes); + expect(deserializer.deserializeInt64(), -1); + }); + + test('serializeInt64 max safe integer', () { + // 2^53 - 1 (JS MAX_SAFE_INTEGER) — largest value portable across all platforms + final serializer = BincodeSerializer(); + serializer.serializeInt64(9007199254740991); + expect(serializer.bytes, + Uint8List.fromList([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00])); + final deserializer = BincodeDeserializer(serializer.bytes); + expect(deserializer.deserializeInt64(), 9007199254740991); + }); + + test('serializeInt64 min safe integer', () { + // -(2^53 - 1) (JS MIN_SAFE_INTEGER) + final serializer = BincodeSerializer(); + serializer.serializeInt64(-9007199254740991); + expect(serializer.bytes, + Uint8List.fromList([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF])); + final deserializer = BincodeDeserializer(serializer.bytes); + expect(deserializer.deserializeInt64(), -9007199254740991); + }); + + test('serializeInt64 typical timestamp', () { + // A realistic epoch-millis value (2026-03-23T00:00:00Z) + final serializer = BincodeSerializer(); + serializer.serializeInt64(1774243200000); + final deserializer = BincodeDeserializer(serializer.bytes); + expect(deserializer.deserializeInt64(), 1774243200000); + }); + test('serializeString', () { final serializer = BincodeSerializer(); serializer.serializeString('dummy text / ダミーテキスト');