array.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import {
  2. defineProperties, isError, assert, assertArgument, assertArgumentCount
  3. } from "../../utils/index.js";
  4. import { Typed } from "../typed.js";
  5. import { Coder, Result, WordSize, Writer } from "./abstract-coder.js";
  6. import { AnonymousCoder } from "./anonymous.js";
  7. import type { Reader } from "./abstract-coder.js";
  8. /**
  9. * @_ignore
  10. */
  11. export function pack(writer: Writer, coders: ReadonlyArray<Coder>, values: Array<any> | { [ name: string ]: any }): number {
  12. let arrayValues: Array<any> = [ ];
  13. if (Array.isArray(values)) {
  14. arrayValues = values;
  15. } else if (values && typeof(values) === "object") {
  16. let unique: { [ name: string ]: boolean } = { };
  17. arrayValues = coders.map((coder) => {
  18. const name = coder.localName;
  19. assert(name, "cannot encode object for signature with missing names",
  20. "INVALID_ARGUMENT", { argument: "values", info: { coder }, value: values });
  21. assert(!unique[name], "cannot encode object for signature with duplicate names",
  22. "INVALID_ARGUMENT", { argument: "values", info: { coder }, value: values });
  23. unique[name] = true;
  24. return values[name];
  25. });
  26. } else {
  27. assertArgument(false, "invalid tuple value", "tuple", values);
  28. }
  29. assertArgument(coders.length === arrayValues.length, "types/value length mismatch", "tuple", values);
  30. let staticWriter = new Writer();
  31. let dynamicWriter = new Writer();
  32. let updateFuncs: Array<(baseOffset: number) => void> = [];
  33. coders.forEach((coder, index) => {
  34. let value = arrayValues[index];
  35. if (coder.dynamic) {
  36. // Get current dynamic offset (for the future pointer)
  37. let dynamicOffset = dynamicWriter.length;
  38. // Encode the dynamic value into the dynamicWriter
  39. coder.encode(dynamicWriter, value);
  40. // Prepare to populate the correct offset once we are done
  41. let updateFunc = staticWriter.writeUpdatableValue();
  42. updateFuncs.push((baseOffset: number) => {
  43. updateFunc(baseOffset + dynamicOffset);
  44. });
  45. } else {
  46. coder.encode(staticWriter, value);
  47. }
  48. });
  49. // Backfill all the dynamic offsets, now that we know the static length
  50. updateFuncs.forEach((func) => { func(staticWriter.length); });
  51. let length = writer.appendWriter(staticWriter);
  52. length += writer.appendWriter(dynamicWriter);
  53. return length;
  54. }
  55. /**
  56. * @_ignore
  57. */
  58. export function unpack(reader: Reader, coders: ReadonlyArray<Coder>): Result {
  59. let values: Array<any> = [];
  60. let keys: Array<null | string> = [ ];
  61. // A reader anchored to this base
  62. let baseReader = reader.subReader(0);
  63. coders.forEach((coder) => {
  64. let value: any = null;
  65. if (coder.dynamic) {
  66. let offset = reader.readIndex();
  67. let offsetReader = baseReader.subReader(offset);
  68. try {
  69. value = coder.decode(offsetReader);
  70. } catch (error: any) {
  71. // Cannot recover from this
  72. if (isError(error, "BUFFER_OVERRUN")) {
  73. throw error;
  74. }
  75. value = error;
  76. value.baseType = coder.name;
  77. value.name = coder.localName;
  78. value.type = coder.type;
  79. }
  80. } else {
  81. try {
  82. value = coder.decode(reader);
  83. } catch (error: any) {
  84. // Cannot recover from this
  85. if (isError(error, "BUFFER_OVERRUN")) {
  86. throw error;
  87. }
  88. value = error;
  89. value.baseType = coder.name;
  90. value.name = coder.localName;
  91. value.type = coder.type;
  92. }
  93. }
  94. if (value == undefined) {
  95. throw new Error("investigate");
  96. }
  97. values.push(value);
  98. keys.push(coder.localName || null);
  99. });
  100. return Result.fromItems(values, keys);
  101. }
  102. /**
  103. * @_ignore
  104. */
  105. export class ArrayCoder extends Coder {
  106. readonly coder!: Coder;
  107. readonly length!: number;
  108. constructor(coder: Coder, length: number, localName: string) {
  109. const type = (coder.type + "[" + (length >= 0 ? length: "") + "]");
  110. const dynamic = (length === -1 || coder.dynamic);
  111. super("array", type, localName, dynamic);
  112. defineProperties<ArrayCoder>(this, { coder, length });
  113. }
  114. defaultValue(): Array<any> {
  115. // Verifies the child coder is valid (even if the array is dynamic or 0-length)
  116. const defaultChild = this.coder.defaultValue();
  117. const result: Array<any> = [];
  118. for (let i = 0; i < this.length; i++) {
  119. result.push(defaultChild);
  120. }
  121. return result;
  122. }
  123. encode(writer: Writer, _value: Array<any> | Typed): number {
  124. const value = Typed.dereference(_value, "array");
  125. if(!Array.isArray(value)) {
  126. this._throwError("expected array value", value);
  127. }
  128. let count = this.length;
  129. if (count === -1) {
  130. count = value.length;
  131. writer.writeValue(value.length);
  132. }
  133. assertArgumentCount(value.length, count, "coder array" + (this.localName? (" "+ this.localName): ""));
  134. let coders: Array<Coder> = [ ];
  135. for (let i = 0; i < value.length; i++) { coders.push(this.coder); }
  136. return pack(writer, coders, value);
  137. }
  138. decode(reader: Reader): any {
  139. let count = this.length;
  140. if (count === -1) {
  141. count = reader.readIndex();
  142. // Check that there is *roughly* enough data to ensure
  143. // stray random data is not being read as a length. Each
  144. // slot requires at least 32 bytes for their value (or 32
  145. // bytes as a link to the data). This could use a much
  146. // tighter bound, but we are erroring on the side of safety.
  147. assert(count * WordSize <= reader.dataLength, "insufficient data length",
  148. "BUFFER_OVERRUN", { buffer: reader.bytes, offset: count * WordSize, length: reader.dataLength });
  149. }
  150. let coders: Array<Coder> = [];
  151. for (let i = 0; i < count; i++) { coders.push(new AnonymousCoder(this.coder)); }
  152. return unpack(reader, coders);
  153. }
  154. }