Skip to content

Commit 4b71c1a

Browse files
authored
Merge pull request #2700 from SatoshiPortal/fix/urqr-fountain-seqnum
fix(qr): accept fountain parts with seqnum above sequence count
2 parents b3b9f4e + 3418c22 commit 4b71c1a

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

lib/core/urqr/urqr.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,12 @@ class UrQrReader {
138138

139139
final sequenceNumber = int.parse(match.group(1)!);
140140
final sequenceCount = int.parse(match.group(2)!);
141+
// Note: sequenceNumber may legitimately exceed sequenceCount. Animated
142+
// URs are fountain-coded (BCR-2020-005): once the pure fragments 1..N
143+
// have played, the stream keeps emitting mixed parts N+1, N+2, ... so a
144+
// decoder that missed frames can still recover. Rejecting those parts
145+
// breaks scanning of any stream the camera joins mid-animation.
141146
if (sequenceNumber < 1 ||
142-
sequenceNumber > sequenceCount ||
143147
sequenceCount > maxMultipartParts ||
144148
data.length > maxMultipartMessageSize) {
145149
throw UrSequenceLimitExceeded();
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'dart:convert';
2+
import 'dart:typed_data';
3+
4+
import 'package:bb_mobile/core/urqr/urqr.dart';
5+
import 'package:cbor/cbor.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
import 'package:ur/ur.dart';
8+
import 'package:ur/ur_encoder.dart';
9+
10+
void main() {
11+
group('UrQrReader', () {
12+
// Animated URs are fountain-coded (BCR-2020-005): once the pure
13+
// fragments 1..N have played, the stream keeps emitting mixed parts
14+
// N+1, N+2, ... The reader must accept them, or any scan that joins
15+
// the animation mid-stream fails immediately.
16+
test('decodes a stream the camera joins mid-animation', () {
17+
final payload = cbor.encode(CborBytes(utf8.encode('x' * 100)));
18+
final encoder = UREncoder(UR('bytes', Uint8List.fromList(payload)), 20);
19+
final pureParts = <String>[];
20+
while (!encoder.isComplete) {
21+
pureParts.add(encoder.nextPart());
22+
}
23+
final mixedPart = encoder.nextPart(); // seqNum N+1 of N
24+
25+
final reader = UrQrReader();
26+
reader.receive(mixedPart); // first captured frame is a mixed part
27+
for (final part in pureParts) {
28+
reader.receive(part);
29+
}
30+
31+
expect(reader.isComplete, isTrue);
32+
});
33+
});
34+
}

0 commit comments

Comments
 (0)