@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.
Files changed (229) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +996 -0
  3. package/dist/commonjs/classes/FountainDecoder.d.ts +125 -0
  4. package/dist/commonjs/classes/FountainDecoder.js +453 -0
  5. package/dist/commonjs/classes/FountainDecoder.js.map +1 -0
  6. package/dist/commonjs/classes/FountainEncoder.d.ts +63 -0
  7. package/dist/commonjs/classes/FountainEncoder.js +168 -0
  8. package/dist/commonjs/classes/FountainEncoder.js.map +1 -0
  9. package/dist/commonjs/classes/RegistryItem.d.ts +104 -0
  10. package/dist/commonjs/classes/RegistryItem.js +172 -0
  11. package/dist/commonjs/classes/RegistryItem.js.map +1 -0
  12. package/dist/commonjs/classes/UR.d.ts +89 -0
  13. package/dist/commonjs/classes/UR.js +243 -0
  14. package/dist/commonjs/classes/UR.js.map +1 -0
  15. package/dist/commonjs/classes/UrFountainDecoder.d.ts +15 -0
  16. package/dist/commonjs/classes/UrFountainDecoder.js +127 -0
  17. package/dist/commonjs/classes/UrFountainDecoder.js.map +1 -0
  18. package/dist/commonjs/classes/UrFountainEncoder.d.ts +42 -0
  19. package/dist/commonjs/classes/UrFountainEncoder.js +92 -0
  20. package/dist/commonjs/classes/UrFountainEncoder.js.map +1 -0
  21. package/dist/commonjs/classes/key.helper.d.ts +27 -0
  22. package/dist/commonjs/classes/key.helper.js +70 -0
  23. package/dist/commonjs/classes/key.helper.js.map +1 -0
  24. package/dist/commonjs/encodingMethods/BytewordEncoding.d.ts +11 -0
  25. package/dist/commonjs/encodingMethods/BytewordEncoding.js +23 -0
  26. package/dist/commonjs/encodingMethods/BytewordEncoding.js.map +1 -0
  27. package/dist/commonjs/encodingMethods/CborEncoding.d.ts +44 -0
  28. package/dist/commonjs/encodingMethods/CborEncoding.js +151 -0
  29. package/dist/commonjs/encodingMethods/CborEncoding.js.map +1 -0
  30. package/dist/commonjs/encodingMethods/HexEncoding.d.ts +8 -0
  31. package/dist/commonjs/encodingMethods/HexEncoding.js +24 -0
  32. package/dist/commonjs/encodingMethods/HexEncoding.js.map +1 -0
  33. package/dist/commonjs/encodingMethods/UrEncoding.d.ts +10 -0
  34. package/dist/commonjs/encodingMethods/UrEncoding.js +19 -0
  35. package/dist/commonjs/encodingMethods/UrEncoding.js.map +1 -0
  36. package/dist/commonjs/encodingMethods/bytewords.d.ts +27 -0
  37. package/dist/commonjs/encodingMethods/bytewords.js +152 -0
  38. package/dist/commonjs/encodingMethods/bytewords.js.map +1 -0
  39. package/dist/commonjs/encodingMethods/index.d.ts +19 -0
  40. package/dist/commonjs/encodingMethods/index.js +27 -0
  41. package/dist/commonjs/encodingMethods/index.js.map +1 -0
  42. package/dist/commonjs/encodingMethods/pipeline.d.ts +19 -0
  43. package/dist/commonjs/encodingMethods/pipeline.js +80 -0
  44. package/dist/commonjs/encodingMethods/pipeline.js.map +1 -0
  45. package/dist/commonjs/enums/EncodingMethodName.d.ts +10 -0
  46. package/dist/commonjs/enums/EncodingMethodName.js +15 -0
  47. package/dist/commonjs/enums/EncodingMethodName.js.map +1 -0
  48. package/dist/commonjs/errors.d.ts +15 -0
  49. package/dist/commonjs/errors.js +39 -0
  50. package/dist/commonjs/errors.js.map +1 -0
  51. package/dist/commonjs/helpers/aliasSampling.d.ts +6 -0
  52. package/dist/commonjs/helpers/aliasSampling.js +73 -0
  53. package/dist/commonjs/helpers/aliasSampling.js.map +1 -0
  54. package/dist/commonjs/helpers/crc32.d.ts +1 -0
  55. package/dist/commonjs/helpers/crc32.js +19 -0
  56. package/dist/commonjs/helpers/crc32.js.map +1 -0
  57. package/dist/commonjs/helpers/fountainUtils.d.ts +40 -0
  58. package/dist/commonjs/helpers/fountainUtils.js +124 -0
  59. package/dist/commonjs/helpers/fountainUtils.js.map +1 -0
  60. package/dist/commonjs/helpers/sha256.d.ts +1 -0
  61. package/dist/commonjs/helpers/sha256.js +78 -0
  62. package/dist/commonjs/helpers/sha256.js.map +1 -0
  63. package/dist/commonjs/helpers/type.helper.d.ts +3 -0
  64. package/dist/commonjs/helpers/type.helper.js +3 -0
  65. package/dist/commonjs/helpers/type.helper.js.map +1 -0
  66. package/dist/commonjs/helpers/uintArrayHelper.d.ts +287 -0
  67. package/dist/commonjs/helpers/uintArrayHelper.js +545 -0
  68. package/dist/commonjs/helpers/uintArrayHelper.js.map +1 -0
  69. package/dist/commonjs/helpers/utils.d.ts +55 -0
  70. package/dist/commonjs/helpers/utils.js +123 -0
  71. package/dist/commonjs/helpers/utils.js.map +1 -0
  72. package/dist/commonjs/index-react-native.d.ts +9 -0
  73. package/dist/commonjs/index-react-native.js +15 -0
  74. package/dist/commonjs/index-react-native.js.map +1 -0
  75. package/dist/commonjs/index.d.ts +21 -0
  76. package/dist/commonjs/index.js +34 -0
  77. package/dist/commonjs/index.js.map +1 -0
  78. package/dist/commonjs/interfaces/IEncodingMethod.d.ts +9 -0
  79. package/dist/commonjs/interfaces/IEncodingMethod.js +3 -0
  80. package/dist/commonjs/interfaces/IEncodingMethod.js.map +1 -0
  81. package/dist/commonjs/package.json +3 -0
  82. package/dist/commonjs/registry.d.ts +26 -0
  83. package/dist/commonjs/registry.js +118 -0
  84. package/dist/commonjs/registry.js.map +1 -0
  85. package/dist/commonjs/test.utils.d.ts +31 -0
  86. package/dist/commonjs/test.utils.js +88 -0
  87. package/dist/commonjs/test.utils.js.map +1 -0
  88. package/dist/commonjs/wrappers/cbor2-cjs.cjs.map +1 -0
  89. package/dist/commonjs/wrappers/cbor2.d.ts +2 -0
  90. package/dist/commonjs/wrappers/cbor2.js +14 -0
  91. package/dist/commonjs/wrappers/cbor2Wrapper.d.ts +14 -0
  92. package/dist/commonjs/wrappers/cbor2Wrapper.js +49 -0
  93. package/dist/commonjs/wrappers/cbor2Wrapper.js.map +1 -0
  94. package/dist/commonjs/xoshiro.d.ts +12 -0
  95. package/dist/commonjs/xoshiro.js +52 -0
  96. package/dist/commonjs/xoshiro.js.map +1 -0
  97. package/dist/esm/classes/FountainDecoder.d.ts +125 -0
  98. package/dist/esm/classes/FountainDecoder.js +447 -0
  99. package/dist/esm/classes/FountainDecoder.js.map +1 -0
  100. package/dist/esm/classes/FountainEncoder.d.ts +63 -0
  101. package/dist/esm/classes/FountainEncoder.js +164 -0
  102. package/dist/esm/classes/FountainEncoder.js.map +1 -0
  103. package/dist/esm/classes/RegistryItem.d.ts +104 -0
  104. package/dist/esm/classes/RegistryItem.js +166 -0
  105. package/dist/esm/classes/RegistryItem.js.map +1 -0
  106. package/dist/esm/classes/UR.d.ts +89 -0
  107. package/dist/esm/classes/UR.js +239 -0
  108. package/dist/esm/classes/UR.js.map +1 -0
  109. package/dist/esm/classes/UrFountainDecoder.d.ts +15 -0
  110. package/dist/esm/classes/UrFountainDecoder.js +123 -0
  111. package/dist/esm/classes/UrFountainDecoder.js.map +1 -0
  112. package/dist/esm/classes/UrFountainEncoder.d.ts +42 -0
  113. package/dist/esm/classes/UrFountainEncoder.js +88 -0
  114. package/dist/esm/classes/UrFountainEncoder.js.map +1 -0
  115. package/dist/esm/classes/key.helper.d.ts +27 -0
  116. package/dist/esm/classes/key.helper.js +66 -0
  117. package/dist/esm/classes/key.helper.js.map +1 -0
  118. package/dist/esm/encodingMethods/BytewordEncoding.d.ts +11 -0
  119. package/dist/esm/encodingMethods/BytewordEncoding.js +19 -0
  120. package/dist/esm/encodingMethods/BytewordEncoding.js.map +1 -0
  121. package/dist/esm/encodingMethods/CborEncoding.d.ts +44 -0
  122. package/dist/esm/encodingMethods/CborEncoding.js +147 -0
  123. package/dist/esm/encodingMethods/CborEncoding.js.map +1 -0
  124. package/dist/esm/encodingMethods/HexEncoding.d.ts +8 -0
  125. package/dist/esm/encodingMethods/HexEncoding.js +20 -0
  126. package/dist/esm/encodingMethods/HexEncoding.js.map +1 -0
  127. package/dist/esm/encodingMethods/UrEncoding.d.ts +10 -0
  128. package/dist/esm/encodingMethods/UrEncoding.js +15 -0
  129. package/dist/esm/encodingMethods/UrEncoding.js.map +1 -0
  130. package/dist/esm/encodingMethods/bytewords.d.ts +27 -0
  131. package/dist/esm/encodingMethods/bytewords.js +147 -0
  132. package/dist/esm/encodingMethods/bytewords.js.map +1 -0
  133. package/dist/esm/encodingMethods/index.d.ts +19 -0
  134. package/dist/esm/encodingMethods/index.js +24 -0
  135. package/dist/esm/encodingMethods/index.js.map +1 -0
  136. package/dist/esm/encodingMethods/pipeline.d.ts +19 -0
  137. package/dist/esm/encodingMethods/pipeline.js +76 -0
  138. package/dist/esm/encodingMethods/pipeline.js.map +1 -0
  139. package/dist/esm/enums/EncodingMethodName.d.ts +10 -0
  140. package/dist/esm/enums/EncodingMethodName.js +12 -0
  141. package/dist/esm/enums/EncodingMethodName.js.map +1 -0
  142. package/dist/esm/errors.d.ts +15 -0
  143. package/dist/esm/errors.js +31 -0
  144. package/dist/esm/errors.js.map +1 -0
  145. package/dist/esm/helpers/aliasSampling.d.ts +6 -0
  146. package/dist/esm/helpers/aliasSampling.js +70 -0
  147. package/dist/esm/helpers/aliasSampling.js.map +1 -0
  148. package/dist/esm/helpers/crc32.d.ts +1 -0
  149. package/dist/esm/helpers/crc32.js +16 -0
  150. package/dist/esm/helpers/crc32.js.map +1 -0
  151. package/dist/esm/helpers/fountainUtils.d.ts +40 -0
  152. package/dist/esm/helpers/fountainUtils.js +114 -0
  153. package/dist/esm/helpers/fountainUtils.js.map +1 -0
  154. package/dist/esm/helpers/sha256.d.ts +1 -0
  155. package/dist/esm/helpers/sha256.js +75 -0
  156. package/dist/esm/helpers/sha256.js.map +1 -0
  157. package/dist/esm/helpers/type.helper.d.ts +3 -0
  158. package/dist/esm/helpers/type.helper.js +2 -0
  159. package/dist/esm/helpers/type.helper.js.map +1 -0
  160. package/dist/esm/helpers/uintArrayHelper.d.ts +287 -0
  161. package/dist/esm/helpers/uintArrayHelper.js +526 -0
  162. package/dist/esm/helpers/uintArrayHelper.js.map +1 -0
  163. package/dist/esm/helpers/utils.d.ts +55 -0
  164. package/dist/esm/helpers/utils.js +102 -0
  165. package/dist/esm/helpers/utils.js.map +1 -0
  166. package/dist/esm/index-react-native.d.ts +9 -0
  167. package/dist/esm/index-react-native.js +12 -0
  168. package/dist/esm/index-react-native.js.map +1 -0
  169. package/dist/esm/index.d.ts +21 -0
  170. package/dist/esm/index.js +16 -0
  171. package/dist/esm/index.js.map +1 -0
  172. package/dist/esm/interfaces/IEncodingMethod.d.ts +9 -0
  173. package/dist/esm/interfaces/IEncodingMethod.js +2 -0
  174. package/dist/esm/interfaces/IEncodingMethod.js.map +1 -0
  175. package/dist/esm/package.json +3 -0
  176. package/dist/esm/registry.d.ts +26 -0
  177. package/dist/esm/registry.js +114 -0
  178. package/dist/esm/registry.js.map +1 -0
  179. package/dist/esm/test.utils.d.ts +31 -0
  180. package/dist/esm/test.utils.js +83 -0
  181. package/dist/esm/test.utils.js.map +1 -0
  182. package/dist/esm/wrappers/cbor2-deno.d.mts +2 -0
  183. package/dist/esm/wrappers/cbor2-deno.mjs +5 -0
  184. package/dist/esm/wrappers/cbor2-deno.mjs.map +1 -0
  185. package/dist/esm/wrappers/cbor2.d.ts +2 -0
  186. package/dist/esm/wrappers/cbor2.js +5 -0
  187. package/dist/esm/wrappers/cbor2.js.map +1 -0
  188. package/dist/esm/wrappers/cbor2Wrapper.d.ts +5 -0
  189. package/dist/esm/wrappers/cbor2Wrapper.js +6 -0
  190. package/dist/esm/wrappers/cbor2Wrapper.js.map +1 -0
  191. package/dist/esm/xoshiro.d.ts +12 -0
  192. package/dist/esm/xoshiro.js +47 -0
  193. package/dist/esm/xoshiro.js.map +1 -0
  194. package/dist/web/bytewords.js +335 -0
  195. package/index.html +98 -0
  196. package/package.json +94 -0
  197. package/src/classes/FountainDecoder.ts +539 -0
  198. package/src/classes/FountainEncoder.ts +211 -0
  199. package/src/classes/RegistryItem.ts +240 -0
  200. package/src/classes/UR.ts +308 -0
  201. package/src/classes/UrFountainDecoder.ts +142 -0
  202. package/src/classes/UrFountainEncoder.ts +103 -0
  203. package/src/classes/key.helper.ts +85 -0
  204. package/src/encodingMethods/BytewordEncoding.ts +23 -0
  205. package/src/encodingMethods/CborEncoding.ts +196 -0
  206. package/src/encodingMethods/HexEncoding.ts +23 -0
  207. package/src/encodingMethods/UrEncoding.ts +19 -0
  208. package/src/encodingMethods/bytewords.ts +215 -0
  209. package/src/encodingMethods/index.ts +26 -0
  210. package/src/encodingMethods/pipeline.ts +103 -0
  211. package/src/enums/EncodingMethodName.ts +10 -0
  212. package/src/errors.ts +34 -0
  213. package/src/helpers/aliasSampling.ts +87 -0
  214. package/src/helpers/crc32.ts +19 -0
  215. package/src/helpers/fountainUtils.ts +157 -0
  216. package/src/helpers/sha256.ts +88 -0
  217. package/src/helpers/type.helper.ts +1 -0
  218. package/src/helpers/uintArrayHelper.ts +611 -0
  219. package/src/helpers/utils.ts +135 -0
  220. package/src/index-react-native.ts +12 -0
  221. package/src/index.ts +44 -0
  222. package/src/interfaces/IEncodingMethod.ts +10 -0
  223. package/src/registry.ts +146 -0
  224. package/src/test.utils.ts +105 -0
  225. package/src/wrappers/cbor2-cjs.cts +6 -0
  226. package/src/wrappers/cbor2-deno.mts +6 -0
  227. package/src/wrappers/cbor2.ts +7 -0
  228. package/src/wrappers/cbor2Wrapper.ts +14 -0
  229. 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
+ }