@qrkit/bc-ur 2.0.0-beta.9-qrkit.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +996 -0
- package/dist/commonjs/classes/FountainDecoder.d.ts +125 -0
- package/dist/commonjs/classes/FountainDecoder.js +453 -0
- package/dist/commonjs/classes/FountainDecoder.js.map +1 -0
- package/dist/commonjs/classes/FountainEncoder.d.ts +63 -0
- package/dist/commonjs/classes/FountainEncoder.js +168 -0
- package/dist/commonjs/classes/FountainEncoder.js.map +1 -0
- package/dist/commonjs/classes/RegistryItem.d.ts +104 -0
- package/dist/commonjs/classes/RegistryItem.js +172 -0
- package/dist/commonjs/classes/RegistryItem.js.map +1 -0
- package/dist/commonjs/classes/UR.d.ts +89 -0
- package/dist/commonjs/classes/UR.js +243 -0
- package/dist/commonjs/classes/UR.js.map +1 -0
- package/dist/commonjs/classes/UrFountainDecoder.d.ts +15 -0
- package/dist/commonjs/classes/UrFountainDecoder.js +127 -0
- package/dist/commonjs/classes/UrFountainDecoder.js.map +1 -0
- package/dist/commonjs/classes/UrFountainEncoder.d.ts +42 -0
- package/dist/commonjs/classes/UrFountainEncoder.js +92 -0
- package/dist/commonjs/classes/UrFountainEncoder.js.map +1 -0
- package/dist/commonjs/classes/key.helper.d.ts +27 -0
- package/dist/commonjs/classes/key.helper.js +70 -0
- package/dist/commonjs/classes/key.helper.js.map +1 -0
- package/dist/commonjs/encodingMethods/BytewordEncoding.d.ts +11 -0
- package/dist/commonjs/encodingMethods/BytewordEncoding.js +23 -0
- package/dist/commonjs/encodingMethods/BytewordEncoding.js.map +1 -0
- package/dist/commonjs/encodingMethods/CborEncoding.d.ts +44 -0
- package/dist/commonjs/encodingMethods/CborEncoding.js +151 -0
- package/dist/commonjs/encodingMethods/CborEncoding.js.map +1 -0
- package/dist/commonjs/encodingMethods/HexEncoding.d.ts +8 -0
- package/dist/commonjs/encodingMethods/HexEncoding.js +24 -0
- package/dist/commonjs/encodingMethods/HexEncoding.js.map +1 -0
- package/dist/commonjs/encodingMethods/UrEncoding.d.ts +10 -0
- package/dist/commonjs/encodingMethods/UrEncoding.js +19 -0
- package/dist/commonjs/encodingMethods/UrEncoding.js.map +1 -0
- package/dist/commonjs/encodingMethods/bytewords.d.ts +27 -0
- package/dist/commonjs/encodingMethods/bytewords.js +152 -0
- package/dist/commonjs/encodingMethods/bytewords.js.map +1 -0
- package/dist/commonjs/encodingMethods/index.d.ts +19 -0
- package/dist/commonjs/encodingMethods/index.js +27 -0
- package/dist/commonjs/encodingMethods/index.js.map +1 -0
- package/dist/commonjs/encodingMethods/pipeline.d.ts +19 -0
- package/dist/commonjs/encodingMethods/pipeline.js +80 -0
- package/dist/commonjs/encodingMethods/pipeline.js.map +1 -0
- package/dist/commonjs/enums/EncodingMethodName.d.ts +10 -0
- package/dist/commonjs/enums/EncodingMethodName.js +15 -0
- package/dist/commonjs/enums/EncodingMethodName.js.map +1 -0
- package/dist/commonjs/errors.d.ts +15 -0
- package/dist/commonjs/errors.js +39 -0
- package/dist/commonjs/errors.js.map +1 -0
- package/dist/commonjs/helpers/aliasSampling.d.ts +6 -0
- package/dist/commonjs/helpers/aliasSampling.js +73 -0
- package/dist/commonjs/helpers/aliasSampling.js.map +1 -0
- package/dist/commonjs/helpers/crc32.d.ts +1 -0
- package/dist/commonjs/helpers/crc32.js +19 -0
- package/dist/commonjs/helpers/crc32.js.map +1 -0
- package/dist/commonjs/helpers/fountainUtils.d.ts +40 -0
- package/dist/commonjs/helpers/fountainUtils.js +124 -0
- package/dist/commonjs/helpers/fountainUtils.js.map +1 -0
- package/dist/commonjs/helpers/sha256.d.ts +1 -0
- package/dist/commonjs/helpers/sha256.js +78 -0
- package/dist/commonjs/helpers/sha256.js.map +1 -0
- package/dist/commonjs/helpers/type.helper.d.ts +3 -0
- package/dist/commonjs/helpers/type.helper.js +3 -0
- package/dist/commonjs/helpers/type.helper.js.map +1 -0
- package/dist/commonjs/helpers/uintArrayHelper.d.ts +287 -0
- package/dist/commonjs/helpers/uintArrayHelper.js +545 -0
- package/dist/commonjs/helpers/uintArrayHelper.js.map +1 -0
- package/dist/commonjs/helpers/utils.d.ts +55 -0
- package/dist/commonjs/helpers/utils.js +123 -0
- package/dist/commonjs/helpers/utils.js.map +1 -0
- package/dist/commonjs/index-react-native.d.ts +9 -0
- package/dist/commonjs/index-react-native.js +15 -0
- package/dist/commonjs/index-react-native.js.map +1 -0
- package/dist/commonjs/index.d.ts +21 -0
- package/dist/commonjs/index.js +34 -0
- package/dist/commonjs/index.js.map +1 -0
- package/dist/commonjs/interfaces/IEncodingMethod.d.ts +9 -0
- package/dist/commonjs/interfaces/IEncodingMethod.js +3 -0
- package/dist/commonjs/interfaces/IEncodingMethod.js.map +1 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/commonjs/registry.d.ts +26 -0
- package/dist/commonjs/registry.js +118 -0
- package/dist/commonjs/registry.js.map +1 -0
- package/dist/commonjs/test.utils.d.ts +31 -0
- package/dist/commonjs/test.utils.js +88 -0
- package/dist/commonjs/test.utils.js.map +1 -0
- package/dist/commonjs/wrappers/cbor2-cjs.cjs.map +1 -0
- package/dist/commonjs/wrappers/cbor2.d.ts +2 -0
- package/dist/commonjs/wrappers/cbor2.js +14 -0
- package/dist/commonjs/wrappers/cbor2Wrapper.d.ts +14 -0
- package/dist/commonjs/wrappers/cbor2Wrapper.js +49 -0
- package/dist/commonjs/wrappers/cbor2Wrapper.js.map +1 -0
- package/dist/commonjs/xoshiro.d.ts +12 -0
- package/dist/commonjs/xoshiro.js +52 -0
- package/dist/commonjs/xoshiro.js.map +1 -0
- package/dist/esm/classes/FountainDecoder.d.ts +125 -0
- package/dist/esm/classes/FountainDecoder.js +447 -0
- package/dist/esm/classes/FountainDecoder.js.map +1 -0
- package/dist/esm/classes/FountainEncoder.d.ts +63 -0
- package/dist/esm/classes/FountainEncoder.js +164 -0
- package/dist/esm/classes/FountainEncoder.js.map +1 -0
- package/dist/esm/classes/RegistryItem.d.ts +104 -0
- package/dist/esm/classes/RegistryItem.js +166 -0
- package/dist/esm/classes/RegistryItem.js.map +1 -0
- package/dist/esm/classes/UR.d.ts +89 -0
- package/dist/esm/classes/UR.js +239 -0
- package/dist/esm/classes/UR.js.map +1 -0
- package/dist/esm/classes/UrFountainDecoder.d.ts +15 -0
- package/dist/esm/classes/UrFountainDecoder.js +123 -0
- package/dist/esm/classes/UrFountainDecoder.js.map +1 -0
- package/dist/esm/classes/UrFountainEncoder.d.ts +42 -0
- package/dist/esm/classes/UrFountainEncoder.js +88 -0
- package/dist/esm/classes/UrFountainEncoder.js.map +1 -0
- package/dist/esm/classes/key.helper.d.ts +27 -0
- package/dist/esm/classes/key.helper.js +66 -0
- package/dist/esm/classes/key.helper.js.map +1 -0
- package/dist/esm/encodingMethods/BytewordEncoding.d.ts +11 -0
- package/dist/esm/encodingMethods/BytewordEncoding.js +19 -0
- package/dist/esm/encodingMethods/BytewordEncoding.js.map +1 -0
- package/dist/esm/encodingMethods/CborEncoding.d.ts +44 -0
- package/dist/esm/encodingMethods/CborEncoding.js +147 -0
- package/dist/esm/encodingMethods/CborEncoding.js.map +1 -0
- package/dist/esm/encodingMethods/HexEncoding.d.ts +8 -0
- package/dist/esm/encodingMethods/HexEncoding.js +20 -0
- package/dist/esm/encodingMethods/HexEncoding.js.map +1 -0
- package/dist/esm/encodingMethods/UrEncoding.d.ts +10 -0
- package/dist/esm/encodingMethods/UrEncoding.js +15 -0
- package/dist/esm/encodingMethods/UrEncoding.js.map +1 -0
- package/dist/esm/encodingMethods/bytewords.d.ts +27 -0
- package/dist/esm/encodingMethods/bytewords.js +147 -0
- package/dist/esm/encodingMethods/bytewords.js.map +1 -0
- package/dist/esm/encodingMethods/index.d.ts +19 -0
- package/dist/esm/encodingMethods/index.js +24 -0
- package/dist/esm/encodingMethods/index.js.map +1 -0
- package/dist/esm/encodingMethods/pipeline.d.ts +19 -0
- package/dist/esm/encodingMethods/pipeline.js +76 -0
- package/dist/esm/encodingMethods/pipeline.js.map +1 -0
- package/dist/esm/enums/EncodingMethodName.d.ts +10 -0
- package/dist/esm/enums/EncodingMethodName.js +12 -0
- package/dist/esm/enums/EncodingMethodName.js.map +1 -0
- package/dist/esm/errors.d.ts +15 -0
- package/dist/esm/errors.js +31 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/helpers/aliasSampling.d.ts +6 -0
- package/dist/esm/helpers/aliasSampling.js +70 -0
- package/dist/esm/helpers/aliasSampling.js.map +1 -0
- package/dist/esm/helpers/crc32.d.ts +1 -0
- package/dist/esm/helpers/crc32.js +16 -0
- package/dist/esm/helpers/crc32.js.map +1 -0
- package/dist/esm/helpers/fountainUtils.d.ts +40 -0
- package/dist/esm/helpers/fountainUtils.js +114 -0
- package/dist/esm/helpers/fountainUtils.js.map +1 -0
- package/dist/esm/helpers/sha256.d.ts +1 -0
- package/dist/esm/helpers/sha256.js +75 -0
- package/dist/esm/helpers/sha256.js.map +1 -0
- package/dist/esm/helpers/type.helper.d.ts +3 -0
- package/dist/esm/helpers/type.helper.js +2 -0
- package/dist/esm/helpers/type.helper.js.map +1 -0
- package/dist/esm/helpers/uintArrayHelper.d.ts +287 -0
- package/dist/esm/helpers/uintArrayHelper.js +526 -0
- package/dist/esm/helpers/uintArrayHelper.js.map +1 -0
- package/dist/esm/helpers/utils.d.ts +55 -0
- package/dist/esm/helpers/utils.js +102 -0
- package/dist/esm/helpers/utils.js.map +1 -0
- package/dist/esm/index-react-native.d.ts +9 -0
- package/dist/esm/index-react-native.js +12 -0
- package/dist/esm/index-react-native.js.map +1 -0
- package/dist/esm/index.d.ts +21 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/interfaces/IEncodingMethod.d.ts +9 -0
- package/dist/esm/interfaces/IEncodingMethod.js +2 -0
- package/dist/esm/interfaces/IEncodingMethod.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/registry.d.ts +26 -0
- package/dist/esm/registry.js +114 -0
- package/dist/esm/registry.js.map +1 -0
- package/dist/esm/test.utils.d.ts +31 -0
- package/dist/esm/test.utils.js +83 -0
- package/dist/esm/test.utils.js.map +1 -0
- package/dist/esm/wrappers/cbor2-deno.d.mts +2 -0
- package/dist/esm/wrappers/cbor2-deno.mjs +5 -0
- package/dist/esm/wrappers/cbor2-deno.mjs.map +1 -0
- package/dist/esm/wrappers/cbor2.d.ts +2 -0
- package/dist/esm/wrappers/cbor2.js +5 -0
- package/dist/esm/wrappers/cbor2.js.map +1 -0
- package/dist/esm/wrappers/cbor2Wrapper.d.ts +5 -0
- package/dist/esm/wrappers/cbor2Wrapper.js +6 -0
- package/dist/esm/wrappers/cbor2Wrapper.js.map +1 -0
- package/dist/esm/xoshiro.d.ts +12 -0
- package/dist/esm/xoshiro.js +47 -0
- package/dist/esm/xoshiro.js.map +1 -0
- package/dist/web/bytewords.js +335 -0
- package/index.html +98 -0
- package/package.json +94 -0
- package/src/classes/FountainDecoder.ts +539 -0
- package/src/classes/FountainEncoder.ts +211 -0
- package/src/classes/RegistryItem.ts +240 -0
- package/src/classes/UR.ts +308 -0
- package/src/classes/UrFountainDecoder.ts +142 -0
- package/src/classes/UrFountainEncoder.ts +103 -0
- package/src/classes/key.helper.ts +85 -0
- package/src/encodingMethods/BytewordEncoding.ts +23 -0
- package/src/encodingMethods/CborEncoding.ts +196 -0
- package/src/encodingMethods/HexEncoding.ts +23 -0
- package/src/encodingMethods/UrEncoding.ts +19 -0
- package/src/encodingMethods/bytewords.ts +215 -0
- package/src/encodingMethods/index.ts +26 -0
- package/src/encodingMethods/pipeline.ts +103 -0
- package/src/enums/EncodingMethodName.ts +10 -0
- package/src/errors.ts +34 -0
- package/src/helpers/aliasSampling.ts +87 -0
- package/src/helpers/crc32.ts +19 -0
- package/src/helpers/fountainUtils.ts +157 -0
- package/src/helpers/sha256.ts +88 -0
- package/src/helpers/type.helper.ts +1 -0
- package/src/helpers/uintArrayHelper.ts +611 -0
- package/src/helpers/utils.ts +135 -0
- package/src/index-react-native.ts +12 -0
- package/src/index.ts +44 -0
- package/src/interfaces/IEncodingMethod.ts +10 -0
- package/src/registry.ts +146 -0
- package/src/test.utils.ts +105 -0
- package/src/wrappers/cbor2-cjs.cts +6 -0
- package/src/wrappers/cbor2-deno.mts +6 -0
- package/src/wrappers/cbor2.ts +7 -0
- package/src/wrappers/cbor2Wrapper.ts +14 -0
- package/src/xoshiro.ts +66 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import { difference, isSubset } from "../helpers/utils.js";
|
|
2
|
+
import { concatUint8Arrays, isUint8Array } from "../helpers/uintArrayHelper.js";
|
|
3
|
+
|
|
4
|
+
import { InvalidChecksumError, InvalidSchemeError } from "../errors.js";
|
|
5
|
+
import { chooseFragments } from "../helpers/fountainUtils.js";
|
|
6
|
+
import { arrayContains, arraysEqual, bufferXOR, getCRC, setDifference } from "../helpers/utils.js";
|
|
7
|
+
|
|
8
|
+
import { CborEncoding } from "../encodingMethods/CborEncoding.js";
|
|
9
|
+
|
|
10
|
+
const cborEncoder = new CborEncoding();
|
|
11
|
+
|
|
12
|
+
export type MultipartPayload = {
|
|
13
|
+
seqNum: number;
|
|
14
|
+
seqLength: number;
|
|
15
|
+
messageLength: number;
|
|
16
|
+
checksum: number;
|
|
17
|
+
fragment: Uint8Array;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
class FountainBlock {
|
|
21
|
+
constructor(protected _indexes: number[], protected _fragment: Uint8Array) {}
|
|
22
|
+
/** What blocks is mixed in this block */
|
|
23
|
+
get indexes() {
|
|
24
|
+
return this._indexes;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Data */
|
|
28
|
+
get fragment() {
|
|
29
|
+
return this._fragment;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public isSimple() {
|
|
33
|
+
return this.indexes.length === 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public isSubsetOf(other: FountainBlock) {
|
|
37
|
+
// Dont use set use array
|
|
38
|
+
// return isSubset(new Set(this.indexes), new Set(other.indexes));
|
|
39
|
+
return arrayContains(other.indexes, this.indexes);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public isSupersetOf(other: FountainBlock) {
|
|
43
|
+
// return isSubset(new Set(other.indexes), new Set(this.indexes));
|
|
44
|
+
return arrayContains(this.indexes, other.indexes);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public reduceBy(other: FountainBlock): FountainBlock {
|
|
48
|
+
// Check if the other block is a subset of this block
|
|
49
|
+
if (!this.isSupersetOf(other)) {
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
// Otherwise reduce the other block from this block
|
|
53
|
+
// const newIndexes = difference(new Set(this.indexes), new Set(other.indexes));
|
|
54
|
+
const newIndexes = setDifference(this.indexes, other.indexes);
|
|
55
|
+
const newFragment = bufferXOR(this.fragment, other.fragment);
|
|
56
|
+
|
|
57
|
+
return new FountainBlock([...newIndexes], newFragment);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
export class FountainDecoder {
|
|
63
|
+
/** Did we received any parts and started decoding */
|
|
64
|
+
public started: boolean = false;
|
|
65
|
+
public done: boolean = false;
|
|
66
|
+
|
|
67
|
+
/** Stores the error if decoding fauls */
|
|
68
|
+
protected error: Error | undefined;
|
|
69
|
+
/** Stores the decoded data*/
|
|
70
|
+
protected resultRaw: Uint8Array | undefined = undefined;
|
|
71
|
+
|
|
72
|
+
/** Stores the expected length of the final message */
|
|
73
|
+
protected expectedMessageLength: number = 0;
|
|
74
|
+
/** Stores the expected checksum of the final message */
|
|
75
|
+
protected expectedChecksum: number = 0;
|
|
76
|
+
/** Stores the expected message length of each fragment */
|
|
77
|
+
protected expectedFragmentLength: number = 0;
|
|
78
|
+
/** Total number of simple fragments */
|
|
79
|
+
protected expectedPartCount: number = 0;
|
|
80
|
+
|
|
81
|
+
/** Mixed Parts that we cannot reduce to simple parts yet */
|
|
82
|
+
protected mixedBlocks: FountainBlock[] = [];
|
|
83
|
+
/** Non-mixed single parts */
|
|
84
|
+
protected simpleBlocks: FountainBlock[] = [];
|
|
85
|
+
/** Queue of parts that may take part in reduction */
|
|
86
|
+
protected queuedBlocks: FountainBlock[] = [];
|
|
87
|
+
|
|
88
|
+
// For tracking the progress of decoding we can keep seen indexes and decoded indexes
|
|
89
|
+
/** Bitmap array of seen block */
|
|
90
|
+
public seenBlocks: number[] = [];
|
|
91
|
+
/** Bitmap array of decoded blocks */
|
|
92
|
+
public decodedBlocks: number[] = [];
|
|
93
|
+
/** Keeps track of the how many parts have been processed */
|
|
94
|
+
protected processedPartsCount: number = 0;
|
|
95
|
+
|
|
96
|
+
get result(): Uint8Array | Error | undefined {
|
|
97
|
+
return this.resultRaw || this.error;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
isSuccessful(): boolean {
|
|
101
|
+
return this.done && !this.error;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
isComplete(): boolean {
|
|
105
|
+
return this.done;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
constructor(parts: Uint8Array[] = []) {
|
|
109
|
+
parts.forEach((part) => this.receivePart(part));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public reset(): void {
|
|
113
|
+
this.error = undefined;
|
|
114
|
+
this.resultRaw = undefined;
|
|
115
|
+
this.expectedMessageLength = 0;
|
|
116
|
+
this.expectedChecksum = 0;
|
|
117
|
+
this.expectedFragmentLength = 0;
|
|
118
|
+
|
|
119
|
+
this.mixedBlocks = [];
|
|
120
|
+
this.simpleBlocks = [];
|
|
121
|
+
this.queuedBlocks = [];
|
|
122
|
+
|
|
123
|
+
this.seenBlocks = [];
|
|
124
|
+
this.decodedBlocks = [];
|
|
125
|
+
this.processedPartsCount = 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected setExpectedValues(decodedPart: MultipartPayload): void {
|
|
129
|
+
this.expectedPartCount = decodedPart.seqLength;
|
|
130
|
+
this.expectedMessageLength = decodedPart.messageLength;
|
|
131
|
+
this.expectedChecksum = decodedPart.checksum;
|
|
132
|
+
this.expectedFragmentLength = decodedPart.fragment.length;
|
|
133
|
+
|
|
134
|
+
this.seenBlocks = new Array(this.expectedPartCount).fill(0);
|
|
135
|
+
this.decodedBlocks = new Array(this.expectedPartCount).fill(0);
|
|
136
|
+
|
|
137
|
+
// Set started so we know we have expected values
|
|
138
|
+
this.started = true;
|
|
139
|
+
this.done = false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Set the expected values on the initial run the current decoder.
|
|
144
|
+
* And check if the next multipart ur is a 'member' of the originally scanned ur with the current decoder.
|
|
145
|
+
* @param decodedPart received multipart ur
|
|
146
|
+
* @returns boolean indicating if the multipart ur is a 'member' of the originally scanned ur with the current decoder.
|
|
147
|
+
*/
|
|
148
|
+
protected validatePart(decodedPart: MultipartPayload): boolean {
|
|
149
|
+
// If this part's values don't match the first part's values, throw away the part
|
|
150
|
+
if (this.expectedPartCount !== decodedPart.seqLength) {
|
|
151
|
+
// TODO: display proper error message
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (this.expectedMessageLength !== decodedPart.messageLength) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (this.expectedChecksum !== decodedPart.checksum) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
if (this.expectedFragmentLength !== decodedPart.fragment.length) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// This part should be processed
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public finalize() {
|
|
169
|
+
if (this.simpleBlocks.length !== this.expectedPartCount) {
|
|
170
|
+
console.warn("Not all parts have been received");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Sort the simple blocks by their index
|
|
175
|
+
const sortedParts = [...this.simpleBlocks].sort((a, b) => a.indexes[0] - b.indexes[0]);
|
|
176
|
+
|
|
177
|
+
// Get fragments in order and combine them to message
|
|
178
|
+
const message = joinFragments(
|
|
179
|
+
sortedParts.map((p) => p.fragment),
|
|
180
|
+
this.expectedMessageLength
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Get the final checksum
|
|
184
|
+
const checksum = getCRC(message);
|
|
185
|
+
|
|
186
|
+
if (checksum === this.expectedChecksum) {
|
|
187
|
+
this.resultRaw = message;
|
|
188
|
+
} else {
|
|
189
|
+
this.error = new InvalidChecksumError();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.done = true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
parseInput(input: Uint8Array | MultipartPayload): MultipartPayload {
|
|
196
|
+
// Check the type of input payload
|
|
197
|
+
if (input instanceof Uint8Array) {
|
|
198
|
+
return parseMultipartCbor(input);
|
|
199
|
+
} else if (typeof input === "object") {
|
|
200
|
+
// Check its correct type
|
|
201
|
+
if (!validateMultipartPayload(input)) {
|
|
202
|
+
throw new Error("Invalid multipart payload");
|
|
203
|
+
}
|
|
204
|
+
return input;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
throw new Error("Invalid input type");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
receivePart(encodedPart: Uint8Array | MultipartPayload): boolean {
|
|
211
|
+
// If we already have a result, we're done
|
|
212
|
+
if (this.done) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let decodedPart: MultipartPayload;
|
|
217
|
+
try {
|
|
218
|
+
decodedPart = this.parseInput(encodedPart);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
// Skip receiving invalid parts
|
|
221
|
+
console.warn("Cannot parse part", error);
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
// If this is the first part we've seen then set expected values
|
|
227
|
+
if (!this.started) this.setExpectedValues(decodedPart);
|
|
228
|
+
|
|
229
|
+
// Validate part
|
|
230
|
+
if (!this.validatePart(decodedPart)) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Now we can start processing the part
|
|
235
|
+
// Find which original data fragments are in this fragment what is mixed in this
|
|
236
|
+
let indexes = chooseFragments(decodedPart.seqNum, decodedPart.seqLength, decodedPart.checksum);
|
|
237
|
+
// order the indexes
|
|
238
|
+
indexes = indexes.sort((a, b) => a - b);
|
|
239
|
+
// Get the data
|
|
240
|
+
const fragment = decodedPart.fragment;
|
|
241
|
+
|
|
242
|
+
const block = new FountainBlock(indexes, fragment);
|
|
243
|
+
|
|
244
|
+
this.queuedBlocks.push(block);
|
|
245
|
+
|
|
246
|
+
this.processQueue();
|
|
247
|
+
|
|
248
|
+
this.processedPartsCount += 1;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
// Skip receiving invalid parts
|
|
251
|
+
console.warn("Error receiving part", error);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// http://blog.notdot.net/2012/01/Damn-Cool-Algorithms-Fountain-Codes
|
|
259
|
+
protected processQueue(): void {
|
|
260
|
+
// Process the queued blocks until queue is empty or we're done
|
|
261
|
+
while (!this.done && this.queuedBlocks.length > 0) {
|
|
262
|
+
const block = this.queuedBlocks.shift()!;
|
|
263
|
+
|
|
264
|
+
// Add indexes to seen indexes
|
|
265
|
+
block.indexes.forEach((index) => {
|
|
266
|
+
this.seenBlocks[index] = 1;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (block.isSimple()) {
|
|
270
|
+
this.processSimpleBlock(block);
|
|
271
|
+
} else {
|
|
272
|
+
this.processMixedBlock(block);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Process a "pure" fragment. this is a original fragment that is not mixed with any other fragments.
|
|
279
|
+
* @param block object with the indexes and the fragment payload buffer.
|
|
280
|
+
* @returns
|
|
281
|
+
*/
|
|
282
|
+
protected processSimpleBlock(block: FountainBlock): void {
|
|
283
|
+
if (!block.isSimple()) {
|
|
284
|
+
throw new Error("Part is not simple");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Don't process duplicate blocks
|
|
288
|
+
// if (this.simpleBlocks.has(block.indexes)) return;
|
|
289
|
+
if (this.simpleBlocks.some((b) => arraysEqual(b.indexes, block.indexes))) return;
|
|
290
|
+
|
|
291
|
+
// Add our block to simple blocks
|
|
292
|
+
this.simpleBlocks.push(block);
|
|
293
|
+
|
|
294
|
+
// Keep track of the decoded blocks
|
|
295
|
+
this.decodedBlocks[block.indexes[0]] = 1;
|
|
296
|
+
|
|
297
|
+
// If we've received all the parts
|
|
298
|
+
if (this.simpleBlocks.length == this.expectedPartCount) {
|
|
299
|
+
this.finalize();
|
|
300
|
+
} else {
|
|
301
|
+
// Otherwise try to reduce the all the mixed parts by this simple part
|
|
302
|
+
this.reduceAllMixedBlocksBy(block);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Process the mixed parts
|
|
308
|
+
* @param newPart
|
|
309
|
+
* @returns
|
|
310
|
+
*/
|
|
311
|
+
protected processMixedBlock(newPart: FountainBlock): void {
|
|
312
|
+
// Check if already have this block, if so pass
|
|
313
|
+
// if (this.mixedBlocks.has(newPart.indexes)) {
|
|
314
|
+
// return;
|
|
315
|
+
// }
|
|
316
|
+
|
|
317
|
+
if (this.mixedBlocks.some((b) => arraysEqual(b.indexes, newPart.indexes))) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Get all simple blocks that we have that makes up this part and reduce current block by whatever we have
|
|
322
|
+
let reducedBlock: FountainBlock = this.simpleBlocks.reduce(
|
|
323
|
+
(proccessed, currentSimple) => proccessed.reduceBy(currentSimple),
|
|
324
|
+
newPart
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Now check if we have a simple part if so add it to the queue
|
|
328
|
+
if (reducedBlock.isSimple()) {
|
|
329
|
+
this.queuedBlocks.push(reducedBlock);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// We still have have a mixed block
|
|
334
|
+
// So we will check if there are any subsets of this block that we can reduce
|
|
335
|
+
// So if we have 1x2x3 try to find subparts (1x2, 2x3, 1x3) that will directly reduce this part to simple part
|
|
336
|
+
// For now we will go though all the parts and try to reduce them so if we have 1x2x3x4 XOR 1x2 we will get 3x4
|
|
337
|
+
reducedBlock = this.mixedBlocks.reduce(
|
|
338
|
+
(proccessed, currentMixed) => proccessed.reduceBy(currentMixed),
|
|
339
|
+
reducedBlock
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// If after all the operations we have a simple part add it to the queue
|
|
343
|
+
if (reducedBlock.isSimple()) {
|
|
344
|
+
this.queuedBlocks.push(reducedBlock);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// If we dont have a simple part we will try to reduce all the mixed parts by this part
|
|
349
|
+
this.reduceAllMixedBlocksBy(reducedBlock);
|
|
350
|
+
|
|
351
|
+
// Then add our part to the mixed parts
|
|
352
|
+
this.mixedBlocks.push(reducedBlock);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Process all the mixed blocks by the given block
|
|
357
|
+
* If the mixed part can be reduced to a simple part, add it to the queue
|
|
358
|
+
* If a mixed part is reduced to simpler part add it to the mixed
|
|
359
|
+
* @param block
|
|
360
|
+
*/
|
|
361
|
+
protected reduceAllMixedBlocksBy(block: FountainBlock): void {
|
|
362
|
+
const newMixed: FountainBlock[] = [];
|
|
363
|
+
|
|
364
|
+
// Try to reduce all the mixed parts by this simple part
|
|
365
|
+
this.mixedBlocks
|
|
366
|
+
.map((mixedPart) => mixedPart.reduceBy(block))
|
|
367
|
+
.forEach((reducedPart) => {
|
|
368
|
+
if (reducedPart.isSimple()) {
|
|
369
|
+
// Add to the queue if it is a simple part
|
|
370
|
+
this.queuedBlocks.push(reducedPart);
|
|
371
|
+
} else {
|
|
372
|
+
// Otherwise add it to as a new mixed part
|
|
373
|
+
newMixed.push(reducedPart);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Override the mixed parts with new reduces parts
|
|
378
|
+
this.mixedBlocks = newMixed;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
*
|
|
382
|
+
*
|
|
383
|
+
* The next part it receives is 3: A ⊕ B ⊕ C ⊕ D.
|
|
384
|
+
* Each time a part is received, the decoder checks to see whether the set of fragments it contains
|
|
385
|
+
* is a proper subset or superset of the set of fragments in any part is has received.
|
|
386
|
+
* If so, it can reduce the superset part by the subset part. In this case,
|
|
387
|
+
* it discovers it can reduce the incoming part 3: A ⊕ B ⊕ C ⊕ D by XORing it with part 1: A ⊕ B ⊕ C,
|
|
388
|
+
* yielding the simple part 3: D.
|
|
389
|
+
*/
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
*
|
|
393
|
+
* Try the reduce mixed part A by the part B
|
|
394
|
+
* If B is a subset of A then we can reduce A by B
|
|
395
|
+
* Otherwise return A
|
|
396
|
+
*
|
|
397
|
+
* @param a existing mixedpart
|
|
398
|
+
* @param b newly received mixedpart
|
|
399
|
+
* @returns
|
|
400
|
+
*/
|
|
401
|
+
private reducePartByPart_(a: FountainBlock, b: FountainBlock): FountainBlock {
|
|
402
|
+
// If the fragments mixed into `b` are a strict (proper) subset of those in `a`...
|
|
403
|
+
const aSet = new Set(a.indexes);
|
|
404
|
+
const bSet = new Set(b.indexes);
|
|
405
|
+
|
|
406
|
+
// If B is a subset of A then we can reduce A by B
|
|
407
|
+
if (isSubset(bSet, aSet)) {
|
|
408
|
+
// A - B => new indexes in the mixed part
|
|
409
|
+
const newIndexes = difference(aSet, bSet);
|
|
410
|
+
const newFragment = bufferXOR(a.fragment, b.fragment);
|
|
411
|
+
|
|
412
|
+
return new FountainBlock([...newIndexes], newFragment);
|
|
413
|
+
}
|
|
414
|
+
// If A is a subset of B then we can reduce B by A
|
|
415
|
+
// else if (isSubset(aSet, bSet)) {
|
|
416
|
+
// // B - A => new indexes in the mixed part
|
|
417
|
+
// const newIndexes = difference(bSet, aSet);
|
|
418
|
+
// const newFragment = bufferXOR(b.fragment, a.fragment);
|
|
419
|
+
|
|
420
|
+
// return new FountainEncodedPart([...newIndexes], newFragment);
|
|
421
|
+
// }
|
|
422
|
+
else {
|
|
423
|
+
// If A is not reducable by B then return A
|
|
424
|
+
return a;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private reducePartByPart(a: FountainBlock, b: FountainBlock): FountainBlock {
|
|
429
|
+
// If the fragments mixed into `b` are a strict (proper) subset of those in `a`...
|
|
430
|
+
if (arrayContains(a.indexes, b.indexes)) {
|
|
431
|
+
const newIndexes = setDifference(a.indexes, b.indexes);
|
|
432
|
+
const newFragment = bufferXOR(a.fragment, b.fragment);
|
|
433
|
+
|
|
434
|
+
return new FountainBlock(newIndexes, newFragment);
|
|
435
|
+
} else {
|
|
436
|
+
// `a` is not reducable by `b`, so return a
|
|
437
|
+
return a;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
public estimatedPercentComplete(): number {
|
|
442
|
+
if (this.done) {
|
|
443
|
+
return 1;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const expectedPartCount = this.expectedPartCount;
|
|
447
|
+
|
|
448
|
+
if (expectedPartCount === 0) {
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// We multiply the expectedPartCount by `1.75` as a way to compensate for the facet
|
|
453
|
+
// that `this.processedPartsCount` also tracks the duplicate parts that have been
|
|
454
|
+
// processeed.
|
|
455
|
+
return Math.min(0.99, this.processedPartsCount / (expectedPartCount * 1.75));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
public getProgress(): number {
|
|
459
|
+
if (this.done) {
|
|
460
|
+
return 1;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const expectedPartCount = this.expectedPartCount;
|
|
464
|
+
|
|
465
|
+
if (expectedPartCount === 0) {
|
|
466
|
+
return 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return this.simpleBlocks.length / expectedPartCount;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
getDecodedData(): any {
|
|
473
|
+
if (!this.isSuccessful()) {
|
|
474
|
+
console.log('Fountain decoding was not successful');
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
return cborEncoder.decode(this.resultRaw);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
getError(): Error | undefined {
|
|
481
|
+
return this.error;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export type IMultipartUrPayload = [number, number, number, number, Uint8Array];
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Parse CBOR encoded Multipart Payload
|
|
489
|
+
* @param encodeded
|
|
490
|
+
* @returns
|
|
491
|
+
*/
|
|
492
|
+
export function parseMultipartCbor(encodeded: Uint8Array): MultipartPayload {
|
|
493
|
+
const decoded = cborEncoder.decode(encodeded) as unknown as IMultipartUrPayload;
|
|
494
|
+
return validateDecodedMultipart(decoded);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Validate and convert the decoded multipart payload to MultipartPayload object
|
|
499
|
+
* @param decoded
|
|
500
|
+
* @returns
|
|
501
|
+
*/
|
|
502
|
+
export function validateDecodedMultipart(decoded: IMultipartUrPayload): MultipartPayload {
|
|
503
|
+
const [seqNum, seqLength, messageLength, checksum, fragment] = decoded;
|
|
504
|
+
if (!validateMultipartPayload({ seqNum, seqLength, messageLength, checksum, fragment })) {
|
|
505
|
+
throw new Error("Invalid multipart payload");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return { seqNum, seqLength, messageLength, checksum, fragment };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function validateMultipartPayload(decoded: MultipartPayload): boolean {
|
|
512
|
+
if (
|
|
513
|
+
typeof decoded.seqNum !== "number" ||
|
|
514
|
+
typeof decoded.seqLength !== "number" ||
|
|
515
|
+
typeof decoded.messageLength !== "number" ||
|
|
516
|
+
typeof decoded.checksum !== "number" ||
|
|
517
|
+
!isUint8Array(decoded.fragment) ||
|
|
518
|
+
decoded.fragment.length === 0
|
|
519
|
+
) {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Join the fragments together.
|
|
528
|
+
* @param fragments fragments to join
|
|
529
|
+
* @param messageLength length of the expected message, full if not provided.
|
|
530
|
+
* @returns the concatenated fragments with the expected length.
|
|
531
|
+
*/
|
|
532
|
+
function joinFragments(fragments: Uint8Array[], messageLength?: number): Uint8Array {
|
|
533
|
+
let result = concatUint8Arrays(fragments);
|
|
534
|
+
if (messageLength) {
|
|
535
|
+
// with 'slice', we remove the additionally created buffer parts, needed to achieve the minimum fragment length.
|
|
536
|
+
result = result.slice(0, messageLength);
|
|
537
|
+
}
|
|
538
|
+
return result;
|
|
539
|
+
}
|