diff --git a/lib/cbor.dart b/lib/cbor.dart index 12b8d95..8aa94d7 100644 --- a/lib/cbor.dart +++ b/lib/cbor.dart @@ -78,6 +78,7 @@ /// * [CborRegex] /// * [CborDateTimeFloat] /// * [CborDecimalFraction] +/// * [CborRationalNumber] /// * [CborBigFloat] library cbor; diff --git a/lib/src/decoder/stage3.dart b/lib/src/decoder/stage3.dart index 2368ac2..49c1dd7 100644 --- a/lib/src/decoder/stage3.dart +++ b/lib/src/decoder/stage3.dart @@ -234,6 +234,26 @@ CborList _createList( CborLengthType type, ) { switch (raw.tags.lastWhereOrNull(isHintSubtype)) { + case CborTag.rationalNumber: + if (items.length != 2) { + break; + } + + final numerator = items[0]; + final denominator = items[1]; + + if (numerator is! CborInt || + denominator is! CborInt || + denominator.toInt() == 0) { + break; + } + + return CborRationalNumber( + numerator: numerator, + denominator: denominator, + tags: raw.tags, + type: type, + ); case CborTag.decimalFraction: if (items.length != 2) { break; diff --git a/lib/src/value/list.dart b/lib/src/value/list.dart index b1a4861..d8e7547 100644 --- a/lib/src/value/list.dart +++ b/lib/src/value/list.dart @@ -238,9 +238,88 @@ class _CborDecimalFractionImpl extends DelegatingList @override void encode(EncodeSink sink) { sink.addTags(tags); - sink.addHeaderInfo(4, const Arg.int(2)); + sink.addHeaderInfo( + 4, + switch (type) { + CborLengthType.definite || CborLengthType.auto => const Arg.int(2), + CborLengthType.indefinite => Arg.indefiniteLength, + }, + ); exponent.encode(sink); mantissa.encode(sink); + + if (type == CborLengthType.indefinite) { + (const Break()).encode(sink); + } + } +} + +/// A CBOR rational number (m / n). +/// https://peteroupc.github.io/CBOR/rational.html +abstract class CborRationalNumber extends CborList { + factory CborRationalNumber({ + required CborInt numerator, + required CborInt denominator, + List tags, + CborLengthType type, + }) = _CborRationalNumberImpl; + + CborInt get numerator; + + CborInt get denominator; +} + +class _CborRationalNumberImpl extends DelegatingList + with CborValueMixin + implements CborRationalNumber { + _CborRationalNumberImpl({ + required this.numerator, + required this.denominator, + this.tags = const [CborTag.rationalNumber], + this.type = CborLengthType.auto, + }) : assert(denominator.toInt() != 0), + super(List.of([numerator, denominator], growable: false)); + + @override + final CborInt numerator; + @override + final CborInt denominator; + + @override + final List tags; + + @override + final CborLengthType type; + + @override + Object? toObjectInternal(Set cyclicCheck, ToObjectOptions o) { + return [numerator.toBigInt(), denominator.toBigInt()]; + } + + @override + Object? toJsonInternal(Set cyclicCheck, ToJsonOptions o) { + return [ + numerator.toJsonInternal(cyclicCheck, o), + denominator.toJsonInternal(cyclicCheck, o), + ]; + } + + @override + void encode(EncodeSink sink) { + sink.addTags(tags); + sink.addHeaderInfo( + 4, + switch (type) { + CborLengthType.definite || CborLengthType.auto => const Arg.int(2), + CborLengthType.indefinite => Arg.indefiniteLength, + }, + ); + numerator.encode(sink); + denominator.encode(sink); + + if (type == CborLengthType.indefinite) { + (const Break()).encode(sink); + } } } @@ -286,9 +365,19 @@ class _CborBigFloatImpl extends DelegatingList @override void encode(EncodeSink sink) { sink.addTags(tags); - sink.addHeaderInfo(4, const Arg.int(2)); + sink.addHeaderInfo( + 4, + switch (type) { + CborLengthType.definite || CborLengthType.auto => const Arg.int(2), + CborLengthType.indefinite => Arg.indefiniteLength, + }, + ); exponent.encode(sink); mantissa.encode(sink); + + if (type == CborLengthType.indefinite) { + (const Break()).encode(sink); + } } @override diff --git a/lib/src/value/value.dart b/lib/src/value/value.dart index 5d68af5..35db8e5 100644 --- a/lib/src/value/value.dart +++ b/lib/src/value/value.dart @@ -36,6 +36,7 @@ class CborTag { static const int expectedConversionToBase64 = 22; static const int expectedConversionToBase64Url = 21; static const int expectedConversionToBase16 = 23; + static const int rationalNumber = 30; static const int uri = 32; static const int base64Url = 33; static const int base64 = 34; diff --git a/test/rational_number_test.dart b/test/rational_number_test.dart new file mode 100644 index 0000000..c7efed7 --- /dev/null +++ b/test/rational_number_test.dart @@ -0,0 +1,103 @@ +/* + * Package : Cbor + * Author : Alex Dochioiu + * Date : 19/06/2024 + * Copyright : Alex Dochioiu + */ + +import 'package:cbor/cbor.dart'; +import 'package:test/test.dart'; + +void main() { + group( + "RationalNumber", + () => { + group('encoding tests', () { + test('encode auto length', () { + expect( + cbor.encode( + CborRationalNumber( + numerator: CborInt(BigInt.from(1)), + denominator: CborSmallInt(2), + type: CborLengthType.auto, + ), + ), + [0xd8, 0x1e, 0x82, 0x1, 0x2], + ); + }); + + test('encode definite length', () { + expect( + cbor.encode( + CborRationalNumber( + numerator: CborInt(BigInt.from(1)), + denominator: CborSmallInt(2), + type: CborLengthType.definite, + ), + ), + [0xd8, 0x1e, 0x82, 0x1, 0x2], + ); + }); + + test('encode indefinite length', () { + expect( + cbor.encode( + CborRationalNumber( + numerator: CborInt(BigInt.from(1)), + denominator: CborSmallInt(2), + type: CborLengthType.indefinite, + ), + ), + [0xd8, 0x1e, 0x9f, 0x1, 0x2, 0xff], + ); + }); + }), + group('decoding tests', () { + test('auto length', () { + expect( + cbor.decode([0xd8, 0x1e, 0x82, 0x1, 0x2]), + CborRationalNumber( + numerator: CborInt(BigInt.from(1)), + denominator: CborSmallInt(2), + type: CborLengthType.auto, + ), + ); + }); + + test('definite length', () { + expect( + cbor.decode([0xd8, 0x1e, 0x82, 0x1, 0x2]), + CborRationalNumber( + numerator: CborInt(BigInt.from(1)), + denominator: CborSmallInt(2), + type: CborLengthType.definite, + ), + ); + }); + + test('indefinite length', () { + expect( + cbor.decode([0xd8, 0x1e, 0x9f, 0x1, 0x2, 0xff]), + CborRationalNumber( + numerator: CborInt(BigInt.from(1)), + denominator: CborSmallInt(2), + type: CborLengthType.indefinite, + ), + ); + }); + + test('not CborRationalNumber when denominator is 0', () { + expect( + cbor.decode([0xd8, 0x1e, 0x9f, 0x1, 0x0, 0xff]) + is CborRationalNumber, + false, + ); + + expect( + cbor.decode([0xd8, 0x1e, 0x9f, 0x1, 0x0, 0xff]) is CborList, + true, + ); + }); + }), + }); +}