@bcts/uniform-resources 1.0.0-alpha.12 → 1.0.0-alpha.14

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/dist/index.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ import { sha256 } from "@bcts/crypto";
2
+
1
3
  //#region ../dcbor/dist/index.mjs
2
4
  var __create = Object.create;
3
5
  var __defProp = Object.defineProperty;
@@ -4897,7 +4899,13 @@ var TagsStore = class {
4897
4899
  /**
4898
4900
  * Insert a tag into the registry.
4899
4901
  *
4900
- * @param tag - The tag to register
4902
+ * Matches Rust's TagsStore::insert() behavior:
4903
+ * - Throws if the tag name is undefined or empty
4904
+ * - Throws if a tag with the same value exists with a different name
4905
+ * - Allows re-registering the same tag value with the same name
4906
+ *
4907
+ * @param tag - The tag to register (must have a non-empty name)
4908
+ * @throws Error if tag has no name, empty name, or conflicts with existing registration
4901
4909
  *
4902
4910
  * @example
4903
4911
  * ```typescript
@@ -4906,9 +4914,13 @@ var TagsStore = class {
4906
4914
  * ```
4907
4915
  */
4908
4916
  insert(tag) {
4917
+ const name = tag.name;
4918
+ if (name === void 0 || name === "") throw new Error(`Tag ${tag.value} must have a non-empty name`);
4909
4919
  const key = this.#valueKey(tag.value);
4920
+ const existing = this.#tagsByValue.get(key);
4921
+ if (existing?.name !== void 0 && existing.name !== name) throw new Error(`Attempt to register tag: ${tag.value} '${existing.name}' with different name: '${name}'`);
4910
4922
  this.#tagsByValue.set(key, tag);
4911
- if (tag.name !== void 0) this.#tagsByName.set(tag.name, tag);
4923
+ this.#tagsByName.set(name, tag);
4912
4924
  }
4913
4925
  /**
4914
4926
  * Insert multiple tags into the registry.
@@ -4946,21 +4958,6 @@ var TagsStore = class {
4946
4958
  const key = this.#valueKey(tagValue$1);
4947
4959
  this.#summarizers.set(key, summarizer);
4948
4960
  }
4949
- /**
4950
- * Remove a tag from the registry.
4951
- *
4952
- * @param tagValue - The numeric tag value to remove
4953
- * @returns true if a tag was removed, false otherwise
4954
- */
4955
- remove(tagValue$1) {
4956
- const key = this.#valueKey(tagValue$1);
4957
- const tag = this.#tagsByValue.get(key);
4958
- if (tag === void 0) return false;
4959
- this.#tagsByValue.delete(key);
4960
- if (tag.name !== void 0) this.#tagsByName.delete(tag.name);
4961
- this.#summarizers.delete(key);
4962
- return true;
4963
- }
4964
4961
  assignedNameForTag(tag) {
4965
4962
  const key = this.#valueKey(tag.value);
4966
4963
  return this.#tagsByValue.get(key)?.name;
@@ -4984,30 +4981,6 @@ var TagsStore = class {
4984
4981
  return this.#summarizers.get(key);
4985
4982
  }
4986
4983
  /**
4987
- * Get all registered tags.
4988
- *
4989
- * @returns Array of all registered tags
4990
- */
4991
- getAllTags() {
4992
- return Array.from(this.#tagsByValue.values());
4993
- }
4994
- /**
4995
- * Clear all registered tags and summarizers.
4996
- */
4997
- clear() {
4998
- this.#tagsByValue.clear();
4999
- this.#tagsByName.clear();
5000
- this.#summarizers.clear();
5001
- }
5002
- /**
5003
- * Get the number of registered tags.
5004
- *
5005
- * @returns Number of tags in the registry
5006
- */
5007
- get size() {
5008
- return this.#tagsByValue.size;
5009
- }
5010
- /**
5011
4984
  * Create a string key for a numeric tag value.
5012
4985
  * Handles both number and bigint types.
5013
4986
  *
@@ -5370,11 +5343,17 @@ function formatMap(map, opts) {
5370
5343
  }
5371
5344
  /**
5372
5345
  * Format tagged value.
5346
+ *
5347
+ * Matches Rust's diag_item() for Tagged case.
5373
5348
  */
5374
5349
  function formatTagged(tag, content, opts) {
5375
5350
  if (opts.summarize === true) {
5376
5351
  const summarizer = resolveTagsStore(opts.tags)?.summarizer(tag);
5377
- if (summarizer !== void 0) return summarizer(content, opts.flat ?? false);
5352
+ if (summarizer !== void 0) {
5353
+ const result$1 = summarizer(content, opts.flat ?? false);
5354
+ if (result$1.ok) return result$1.value;
5355
+ else return `<error: ${result$1.error.type === "Custom" ? result$1.error.message : result$1.error.type === "WrongTag" ? `expected CBOR tag ${result$1.error.expected.value}, but got ${result$1.error.actual.value}` : result$1.error.type}>`;
5356
+ }
5378
5357
  }
5379
5358
  let comment;
5380
5359
  if (opts.annotate === true) {
@@ -6005,21 +5984,20 @@ let EdgeType = /* @__PURE__ */ function(EdgeType$1) {
6005
5984
  * @param cbor - The CBOR value to traverse
6006
5985
  * @param initialState - Initial state value
6007
5986
  * @param visitor - Function to call for each element
6008
- * @returns Final state after traversal
6009
5987
  *
6010
5988
  * @example
6011
5989
  * ```typescript
6012
- * // Count all text strings in a structure
6013
- * interface CountState { count: number }
5990
+ * // Count all text strings in a structure using RefCell-like pattern
5991
+ * const count = { value: 0 };
6014
5992
  *
6015
5993
  * const structure = cbor({ name: 'Alice', tags: ['urgent', 'draft'] });
6016
- * const result = walk(structure, { count: 0 }, (element, level, edge, state) => {
5994
+ * walk(structure, null, (element, level, edge, state) => {
6017
5995
  * if (element.type === 'single' && element.cbor.type === MajorType.Text) {
6018
- * return [{ count: state.count + 1 }, false];
5996
+ * count.value++;
6019
5997
  * }
6020
5998
  * return [state, false];
6021
5999
  * });
6022
- * console.log(result.count); // 3 (name, urgent, draft)
6000
+ * console.log(count.value); // 3 (name, urgent, draft)
6023
6001
  * ```
6024
6002
  *
6025
6003
  * @example
@@ -6028,7 +6006,7 @@ let EdgeType = /* @__PURE__ */ function(EdgeType$1) {
6028
6006
  * const structure = cbor([1, 2, 3, 'found', 5, 6]);
6029
6007
  * let found = false;
6030
6008
  *
6031
- * walk(structure, null, (element, level, edge) => {
6009
+ * walk(structure, null, (element, level, edge, state) => {
6032
6010
  * if (element.type === 'single' &&
6033
6011
  * element.cbor.type === MajorType.Text &&
6034
6012
  * element.cbor.value === 'found') {
@@ -6040,7 +6018,7 @@ let EdgeType = /* @__PURE__ */ function(EdgeType$1) {
6040
6018
  * ```
6041
6019
  */
6042
6020
  const walk = (cbor$1, initialState, visitor) => {
6043
- return walkInternal(cbor$1, 0, { type: EdgeType.None }, initialState, visitor);
6021
+ walkInternal(cbor$1, 0, { type: EdgeType.None }, initialState, visitor);
6044
6022
  };
6045
6023
  /**
6046
6024
  * Internal recursive walk implementation.
@@ -6524,7 +6502,7 @@ const attachMethods = (obj) => {
6524
6502
  return this.value;
6525
6503
  },
6526
6504
  walk(initialState, visitor) {
6527
- return walk(this, initialState, visitor);
6505
+ walk(this, initialState, visitor);
6528
6506
  },
6529
6507
  validateTag(expectedTags) {
6530
6508
  if (this.type !== MajorType.Tagged) throw new CborError({ type: "WrongType" });
@@ -6660,6 +6638,16 @@ var CBORError = class extends URError {
6660
6638
  }
6661
6639
  };
6662
6640
  /**
6641
+ * Error type for UR decoder errors.
6642
+ * Matches Rust's Error::UR(String) variant.
6643
+ */
6644
+ var URDecodeError = class extends URError {
6645
+ constructor(message) {
6646
+ super(`UR decoder error (${message})`);
6647
+ this.name = "URDecodeError";
6648
+ }
6649
+ };
6650
+ /**
6663
6651
  * Helper function to check if a result is an error.
6664
6652
  */
6665
6653
  function isError(result) {
@@ -6688,13 +6676,6 @@ function isValidURType(urType) {
6688
6676
  return Array.from(urType).every((char) => isURTypeChar(char));
6689
6677
  }
6690
6678
  /**
6691
- * Validates and returns a UR type, or throws an error if invalid.
6692
- */
6693
- function validateURType(urType) {
6694
- if (!isValidURType(urType)) throw new InvalidTypeError();
6695
- return urType;
6696
- }
6697
- /**
6698
6679
  * Bytewords for encoding/decoding bytes as words.
6699
6680
  * See: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-004-bytewords.md
6700
6681
  */
@@ -7265,7 +7246,7 @@ function encodeBytemojisIdentifier(data) {
7265
7246
  let BytewordsStyle = /* @__PURE__ */ function(BytewordsStyle$1) {
7266
7247
  /** Full 4-letter words separated by spaces */
7267
7248
  BytewordsStyle$1["Standard"] = "standard";
7268
- /** Full 4-letter words without separators */
7249
+ /** Full 4-letter words separated by hyphens (URI-safe) */
7269
7250
  BytewordsStyle$1["Uri"] = "uri";
7270
7251
  /** First and last character only (minimal) - used by UR encoding */
7271
7252
  BytewordsStyle$1["Minimal"] = "minimal";
@@ -7341,7 +7322,7 @@ function encodeBytewords(data, style = BytewordsStyle.Minimal) {
7341
7322
  }
7342
7323
  switch (style) {
7343
7324
  case BytewordsStyle.Standard: return words.join(" ");
7344
- case BytewordsStyle.Uri:
7325
+ case BytewordsStyle.Uri: return words.join("-");
7345
7326
  case BytewordsStyle.Minimal: return words.join("");
7346
7327
  }
7347
7328
  }
@@ -7361,14 +7342,11 @@ function decodeBytewords(encoded, style = BytewordsStyle.Minimal) {
7361
7342
  });
7362
7343
  break;
7363
7344
  case BytewordsStyle.Uri:
7364
- if (lowercased.length % 4 !== 0) throw new Error("Invalid URI bytewords length");
7365
- bytes = [];
7366
- for (let i = 0; i < lowercased.length; i += 4) {
7367
- const word = lowercased.slice(i, i + 4);
7345
+ bytes = lowercased.split("-").map((word) => {
7368
7346
  const index = BYTEWORDS_MAP.get(word);
7369
7347
  if (index === void 0) throw new Error(`Invalid byteword: ${word}`);
7370
- bytes.push(index);
7371
- }
7348
+ return index;
7349
+ });
7372
7350
  break;
7373
7351
  case BytewordsStyle.Minimal:
7374
7352
  if (lowercased.length % 2 !== 0) throw new Error("Invalid minimal bytewords length");
@@ -7673,6 +7651,7 @@ function isURCodable(obj) {
7673
7651
  * for deterministic fragment selection in fountain codes.
7674
7652
  *
7675
7653
  * Reference: https://prng.di.unimi.it/
7654
+ * BC-UR Reference: https://github.com/nicklockwood/fountain-codes
7676
7655
  */
7677
7656
  const MAX_UINT64 = BigInt("0xffffffffffffffff");
7678
7657
  /**
@@ -7692,21 +7671,28 @@ function rotl(x, k) {
7692
7671
  var Xoshiro256 = class Xoshiro256 {
7693
7672
  s;
7694
7673
  /**
7695
- * Creates a new Xoshiro256** instance from a seed.
7674
+ * Creates a new Xoshiro256** instance from a 32-byte seed.
7696
7675
  *
7697
- * The seed is hashed using SHA-256 to initialize the state.
7698
- * For consistent results across encoder/decoder, use the same seed.
7676
+ * The seed must be exactly 32 bytes (256 bits). The bytes are interpreted
7677
+ * using the BC-UR reference algorithm: each 8-byte chunk is read as
7678
+ * big-endian then stored as little-endian for the state.
7699
7679
  *
7700
- * @param seed - The seed bytes (any length)
7680
+ * @param seed - The seed bytes (must be exactly 32 bytes)
7701
7681
  */
7702
7682
  constructor(seed) {
7703
- const hash = this.hashSeed(seed);
7704
- this.s = [
7705
- this.bytesToBigInt(hash.slice(0, 8)),
7706
- this.bytesToBigInt(hash.slice(8, 16)),
7707
- this.bytesToBigInt(hash.slice(16, 24)),
7708
- this.bytesToBigInt(hash.slice(24, 32))
7683
+ if (seed.length !== 32) throw new Error(`Seed must be 32 bytes, got ${seed.length}`);
7684
+ const s = [
7685
+ 0n,
7686
+ 0n,
7687
+ 0n,
7688
+ 0n
7709
7689
  ];
7690
+ for (let i = 0; i < 4; i++) {
7691
+ let v = 0n;
7692
+ for (let n = 0; n < 8; n++) v = v << 8n | BigInt(seed[8 * i + n] ?? 0);
7693
+ s[i] = v;
7694
+ }
7695
+ this.s = s;
7710
7696
  }
7711
7697
  /**
7712
7698
  * Creates a Xoshiro256** instance from raw state values.
@@ -7723,33 +7709,6 @@ var Xoshiro256 = class Xoshiro256 {
7723
7709
  return instance;
7724
7710
  }
7725
7711
  /**
7726
- * Simple hash function for seeding.
7727
- * This is a basic implementation - in production use SHA-256.
7728
- */
7729
- hashSeed(seed) {
7730
- const result = new Uint8Array(32);
7731
- if (seed.length === 0) return result;
7732
- for (let i = 0; i < 32; i++) {
7733
- let hash = 0;
7734
- for (const byte of seed) hash = hash * 31 + byte + i >>> 0;
7735
- hash ^= hash >>> 16;
7736
- hash = hash * 2246822507 >>> 0;
7737
- hash ^= hash >>> 13;
7738
- hash = hash * 3266489909 >>> 0;
7739
- hash ^= hash >>> 16;
7740
- result[i] = hash & 255;
7741
- }
7742
- return result;
7743
- }
7744
- /**
7745
- * Converts 8 bytes to a 64-bit BigInt (little-endian).
7746
- */
7747
- bytesToBigInt(bytes) {
7748
- let result = 0n;
7749
- for (let i = 7; i >= 0; i--) result = result << 8n | BigInt(bytes[i] ?? 0);
7750
- return result;
7751
- }
7752
- /**
7753
7712
  * Generates the next 64-bit random value.
7754
7713
  */
7755
7714
  next() {
@@ -7765,17 +7724,19 @@ var Xoshiro256 = class Xoshiro256 {
7765
7724
  }
7766
7725
  /**
7767
7726
  * Generates a random double in [0, 1).
7727
+ * Matches BC-UR reference: self.next() as f64 / (u64::MAX as f64 + 1.0)
7768
7728
  */
7769
7729
  nextDouble() {
7770
7730
  const value = this.next();
7771
- return Number(value >> 11n) / Number(1n << 53n);
7731
+ return Number(value) / 0x10000000000000000;
7772
7732
  }
7773
7733
  /**
7774
- * Generates a random integer in [low, high).
7734
+ * Generates a random integer in [low, high] (inclusive).
7735
+ * Matches BC-UR reference: (self.next_double() * ((high - low + 1) as f64)) as u64 + low
7775
7736
  */
7776
7737
  nextInt(low, high) {
7777
- const range$1 = high - low;
7778
- return low + Math.floor(this.nextDouble() * range$1);
7738
+ const range$1 = high - low + 1;
7739
+ return Math.floor(this.nextDouble() * range$1) + low;
7779
7740
  }
7780
7741
  /**
7781
7742
  * Generates a random byte [0, 255].
@@ -7791,24 +7752,102 @@ var Xoshiro256 = class Xoshiro256 {
7791
7752
  for (let i = 0; i < count; i++) result[i] = this.nextByte();
7792
7753
  return result;
7793
7754
  }
7755
+ /**
7756
+ * Shuffles items by repeatedly picking random indices.
7757
+ * Matches BC-UR reference implementation.
7758
+ */
7759
+ shuffled(items) {
7760
+ const source = [...items];
7761
+ const shuffled = [];
7762
+ while (source.length > 0) {
7763
+ const index = this.nextInt(0, source.length - 1);
7764
+ const item = source.splice(index, 1)[0];
7765
+ if (item !== void 0) shuffled.push(item);
7766
+ }
7767
+ return shuffled;
7768
+ }
7769
+ /**
7770
+ * Chooses the degree (number of fragments to mix) using a weighted sampler.
7771
+ * Uses the robust soliton distribution with weights [1/1, 1/2, 1/3, ..., 1/n].
7772
+ * Matches BC-UR reference implementation.
7773
+ */
7774
+ chooseDegree(seqLen) {
7775
+ const weights = [];
7776
+ for (let i = 1; i <= seqLen; i++) weights.push(1 / i);
7777
+ return new WeightedSampler(weights).next(this) + 1;
7778
+ }
7779
+ };
7780
+ /**
7781
+ * Weighted sampler using Vose's alias method.
7782
+ * Allows O(1) sampling from a discrete probability distribution.
7783
+ */
7784
+ var WeightedSampler = class {
7785
+ aliases;
7786
+ probs;
7787
+ constructor(weights) {
7788
+ const n = weights.length;
7789
+ if (n === 0) throw new Error("Weights array cannot be empty");
7790
+ const sum = weights.reduce((a, b) => a + b, 0);
7791
+ if (sum <= 0) throw new Error("Weights must sum to a positive value");
7792
+ const normalized = weights.map((w) => w * n / sum);
7793
+ this.aliases = Array.from({ length: n }).fill(0);
7794
+ this.probs = Array.from({ length: n }).fill(0);
7795
+ const small = [];
7796
+ const large = [];
7797
+ for (let i = n - 1; i >= 0; i--) if (normalized[i] < 1) small.push(i);
7798
+ else large.push(i);
7799
+ while (small.length > 0 && large.length > 0) {
7800
+ const a = small.pop();
7801
+ const g = large.pop();
7802
+ if (a === void 0 || g === void 0) break;
7803
+ this.probs[a] = normalized[a] ?? 0;
7804
+ this.aliases[a] = g;
7805
+ normalized[g] = (normalized[g] ?? 0) + (normalized[a] ?? 0) - 1;
7806
+ if (normalized[g] !== void 0 && normalized[g] < 1) small.push(g);
7807
+ else large.push(g);
7808
+ }
7809
+ while (large.length > 0) {
7810
+ const g = large.pop();
7811
+ if (g === void 0) break;
7812
+ this.probs[g] = 1;
7813
+ }
7814
+ while (small.length > 0) {
7815
+ const a = small.pop();
7816
+ if (a === void 0) break;
7817
+ this.probs[a] = 1;
7818
+ }
7819
+ }
7820
+ /**
7821
+ * Sample from the distribution.
7822
+ */
7823
+ next(rng) {
7824
+ const r1 = rng.nextDouble();
7825
+ const r2 = rng.nextDouble();
7826
+ const n = this.probs.length;
7827
+ const i = Math.floor(n * r1);
7828
+ if (r2 < this.probs[i]) return i;
7829
+ else return this.aliases[i];
7830
+ }
7794
7831
  };
7795
7832
  /**
7796
- * Creates a seed for the Xoshiro PRNG from message checksum and sequence number.
7833
+ * Creates a Xoshiro256 PRNG instance from message checksum and sequence number.
7834
+ *
7835
+ * This creates an 8-byte seed by concatenating seqNum and checksum (both in
7836
+ * big-endian), then hashes it with SHA-256 to get the 32-byte seed for Xoshiro.
7797
7837
  *
7798
- * This ensures that both encoder and decoder produce the same random sequence
7799
- * for a given message and part number.
7838
+ * This matches the BC-UR reference implementation.
7800
7839
  */
7801
7840
  function createSeed(checksum, seqNum) {
7802
- const seed = new Uint8Array(8);
7803
- seed[0] = checksum >>> 24 & 255;
7804
- seed[1] = checksum >>> 16 & 255;
7805
- seed[2] = checksum >>> 8 & 255;
7806
- seed[3] = checksum & 255;
7807
- seed[4] = seqNum >>> 24 & 255;
7808
- seed[5] = seqNum >>> 16 & 255;
7809
- seed[6] = seqNum >>> 8 & 255;
7810
- seed[7] = seqNum & 255;
7811
- return seed;
7841
+ const seed8 = new Uint8Array(8);
7842
+ seed8[0] = seqNum >>> 24 & 255;
7843
+ seed8[1] = seqNum >>> 16 & 255;
7844
+ seed8[2] = seqNum >>> 8 & 255;
7845
+ seed8[3] = seqNum & 255;
7846
+ seed8[4] = checksum >>> 24 & 255;
7847
+ seed8[5] = checksum >>> 16 & 255;
7848
+ seed8[6] = checksum >>> 8 & 255;
7849
+ seed8[7] = checksum & 255;
7850
+ return sha256(seed8);
7812
7851
  }
7813
7852
 
7814
7853
  //#endregion
@@ -7855,6 +7894,11 @@ function xorBytes(a, b) {
7855
7894
  * This uses a seeded Xoshiro256** PRNG to deterministically select fragments,
7856
7895
  * ensuring encoder and decoder agree without explicit coordination.
7857
7896
  *
7897
+ * The algorithm matches the BC-UR reference implementation:
7898
+ * 1. For pure parts (seqNum <= seqLen), return single fragment index
7899
+ * 2. For mixed parts, use weighted sampling to choose degree
7900
+ * 3. Shuffle all indices and take the first 'degree' indices
7901
+ *
7858
7902
  * @param seqNum - The sequence number (1-based)
7859
7903
  * @param seqLen - Total number of pure fragments
7860
7904
  * @param checksum - CRC32 checksum of the message
@@ -7863,26 +7907,10 @@ function xorBytes(a, b) {
7863
7907
  function chooseFragments(seqNum, seqLen, checksum) {
7864
7908
  if (seqNum <= seqLen) return [seqNum - 1];
7865
7909
  const rng = new Xoshiro256(createSeed(checksum, seqNum));
7866
- const degree = chooseDegree(rng, seqLen);
7867
- const indices = /* @__PURE__ */ new Set();
7868
- while (indices.size < degree) {
7869
- const index = rng.nextInt(0, seqLen);
7870
- indices.add(index);
7871
- }
7872
- return Array.from(indices).sort((a, b) => a - b);
7873
- }
7874
- /**
7875
- * Chooses the degree (number of fragments to mix) using a simplified
7876
- * robust soliton distribution.
7877
- *
7878
- * This ensures good coverage of fragments for efficient decoding.
7879
- */
7880
- function chooseDegree(rng, seqLen) {
7881
- const r = rng.nextDouble();
7882
- if (r < .5) return 1;
7883
- else if (r < .75) return 2;
7884
- else if (r < .9) return Math.min(3, seqLen);
7885
- else return Math.min(rng.nextInt(4, seqLen + 1), seqLen);
7910
+ const degree = rng.chooseDegree(seqLen);
7911
+ const allIndices = [];
7912
+ for (let i = 0; i < seqLen; i++) allIndices.push(i);
7913
+ return rng.shuffled(allIndices).slice(0, degree);
7886
7914
  }
7887
7915
  /**
7888
7916
  * Mixes the selected fragments using XOR.
@@ -8122,14 +8150,6 @@ var MultipartEncoder = class {
8122
8150
  this._fountainEncoder = new FountainEncoder(ur.cbor().toData(), maxFragmentLen);
8123
8151
  }
8124
8152
  /**
8125
- * Returns whether the message fits in a single part.
8126
- *
8127
- * For single-part messages, consider using UR.string() directly.
8128
- */
8129
- isSinglePart() {
8130
- return this._fountainEncoder.isSinglePart();
8131
- }
8132
- /**
8133
8153
  * Gets the next part of the encoding.
8134
8154
  *
8135
8155
  * Parts 1 through seqLen are "pure" fragments containing one piece each.
@@ -8157,20 +8177,17 @@ var MultipartEncoder = class {
8157
8177
  return `ur:${this._ur.urTypeStr()}/${part.seqNum}-${part.seqLen}/${encoded}`;
8158
8178
  }
8159
8179
  /**
8160
- * Encodes part metadata and data into bytes for bytewords encoding.
8180
+ * Encodes part metadata and data as CBOR for bytewords encoding.
8181
+ * Format: CBOR array [seqNum, seqLen, messageLen, checksum, data]
8161
8182
  */
8162
8183
  _encodePartData(part) {
8163
- const result = new Uint8Array(8 + part.data.length);
8164
- result[0] = part.messageLen >>> 24 & 255;
8165
- result[1] = part.messageLen >>> 16 & 255;
8166
- result[2] = part.messageLen >>> 8 & 255;
8167
- result[3] = part.messageLen & 255;
8168
- result[4] = part.checksum >>> 24 & 255;
8169
- result[5] = part.checksum >>> 16 & 255;
8170
- result[6] = part.checksum >>> 8 & 255;
8171
- result[7] = part.checksum & 255;
8172
- result.set(part.data, 8);
8173
- return result;
8184
+ return cbor([
8185
+ part.seqNum,
8186
+ part.seqLen,
8187
+ part.messageLen,
8188
+ part.checksum,
8189
+ part.data
8190
+ ]).toData();
8174
8191
  }
8175
8192
  /**
8176
8193
  * Gets the current part index.
@@ -8187,22 +8204,6 @@ var MultipartEncoder = class {
8187
8204
  partsCount() {
8188
8205
  return this._fountainEncoder.seqLen;
8189
8206
  }
8190
- /**
8191
- * Checks if all pure parts have been emitted.
8192
- *
8193
- * Even after this returns true, you can continue calling nextPart()
8194
- * to generate additional rateless parts for redundancy.
8195
- */
8196
- isComplete() {
8197
- return this._fountainEncoder.isComplete();
8198
- }
8199
- /**
8200
- * Resets the encoder to start from the beginning.
8201
- */
8202
- reset() {
8203
- this._currentIndex = 0;
8204
- this._fountainEncoder.reset();
8205
- }
8206
8207
  };
8207
8208
 
8208
8209
  //#endregion
@@ -8284,16 +8285,23 @@ var MultipartDecoder = class {
8284
8285
  }
8285
8286
  /**
8286
8287
  * Decodes a multipart UR's fountain part data.
8288
+ *
8289
+ * The multipart body is a CBOR array: [seqNum, seqLen, messageLen, checksum, data]
8287
8290
  */
8288
8291
  _decodeFountainPart(partInfo) {
8289
- const rawData = decodeBytewords(partInfo.encodedData, BytewordsStyle.Minimal);
8290
- if (rawData.length < 8) throw new URError("Invalid multipart data: too short");
8291
- const messageLen = (rawData[0] << 24 | rawData[1] << 16 | rawData[2] << 8 | rawData[3]) >>> 0;
8292
- const checksum = (rawData[4] << 24 | rawData[5] << 16 | rawData[6] << 8 | rawData[7]) >>> 0;
8293
- const data = rawData.slice(8);
8292
+ const decoded = decodeCbor(decodeBytewords(partInfo.encodedData, BytewordsStyle.Minimal));
8293
+ if (decoded.type !== MajorType.Array) throw new URError("Invalid multipart data: expected CBOR array");
8294
+ const items = decoded.value;
8295
+ if (items.length !== 5) throw new URError(`Invalid multipart data: expected 5 elements, got ${items.length}`);
8296
+ const seqNum = Number(items[0].value);
8297
+ const seqLen = Number(items[1].value);
8298
+ const messageLen = Number(items[2].value);
8299
+ const checksum = Number(items[3].value);
8300
+ const data = items[4].value;
8301
+ if (seqNum !== partInfo.seqNum || seqLen !== partInfo.seqLen) throw new URError(`Multipart metadata mismatch: URL says ${partInfo.seqNum}-${partInfo.seqLen}, CBOR says ${seqNum}-${seqLen}`);
8294
8302
  return {
8295
- seqNum: partInfo.seqNum,
8296
- seqLen: partInfo.seqLen,
8303
+ seqNum,
8304
+ seqLen,
8297
8305
  messageLen,
8298
8306
  checksum,
8299
8307
  data
@@ -8313,24 +8321,8 @@ var MultipartDecoder = class {
8313
8321
  message() {
8314
8322
  return this._decodedMessage;
8315
8323
  }
8316
- /**
8317
- * Returns the decoding progress as a fraction (0 to 1).
8318
- */
8319
- progress() {
8320
- if (this._decodedMessage !== null) return 1;
8321
- if (this._fountainDecoder === null) return 0;
8322
- return this._fountainDecoder.progress();
8323
- }
8324
- /**
8325
- * Resets the decoder to receive a new message.
8326
- */
8327
- reset() {
8328
- this._urType = null;
8329
- this._fountainDecoder = null;
8330
- this._decodedMessage = null;
8331
- }
8332
8324
  };
8333
8325
 
8334
8326
  //#endregion
8335
- export { BYTEMOJIS, BYTEWORDS, BYTEWORDS_MAP, BytewordsError, BytewordsStyle, CBORError, FountainDecoder, FountainEncoder, InvalidSchemeError, InvalidTypeError, MINIMAL_BYTEWORDS_MAP, MultipartDecoder, MultipartEncoder, NotSinglePartError, TypeUnspecifiedError, UR, URError, URType, UnexpectedTypeError, Xoshiro256, chooseFragments, crc32, createSeed, decodeBytewords, encodeBytemojisIdentifier, encodeBytewords, encodeBytewordsIdentifier, isError, isURCodable, isURDecodable, isUREncodable, isURTypeChar, isValidURType, mixFragments, splitMessage, validateURType, xorBytes };
8327
+ export { BYTEMOJIS, BYTEWORDS, BytewordsError, BytewordsStyle, CBORError, InvalidSchemeError, InvalidTypeError, MultipartDecoder, MultipartEncoder, NotSinglePartError, TypeUnspecifiedError, UR, URDecodeError, URError, URType, UnexpectedTypeError, decodeBytewords, encodeBytemojisIdentifier, encodeBytewords, encodeBytewordsIdentifier, isError, isURCodable, isURDecodable, isUREncodable };
8336
8328
  //# sourceMappingURL=index.mjs.map