decodeAttributeStreamBody function

FourdgsAttributeStream decodeAttributeStreamBody(
  1. FourdgsCursor cursor,
  2. FourdgsStreamHeader header, {
  3. int streamOffset = 0,
  4. int? chunkOffset,
})

Decodes the payload of a stream whose header has already been read.

Implementation

FourdgsAttributeStream decodeAttributeStreamBody(
  FourdgsCursor cursor,
  FourdgsStreamHeader header, {
  int streamOffset = 0,
  int? chunkOffset,
}) {
  final attributeId = header.attributeId;
  final width = header.width;
  final mode = header.mode;
  final codec = header.codec;
  final channels = header.channels;
  final count = header.count;
  final payloadLength = header.payloadLength;

  if (count == 0) {
    cursor.skip(payloadLength);
    return FourdgsAttributeStream(
      attributeId: attributeId,
      channels: channels,
      count: 0,
      values: Int32List(0),
    );
  }
  if (width != 1 && width != 2 && width != 4) {
    throw FourdgsMalformedFile(
      'attribute $attributeId: bad symbol width $width',
    );
  }
  if (channels == 0) {
    throw FourdgsMalformedFile('attribute $attributeId: zero channels');
  }

  final symbols = mode == modeConst ? channels : count * channels;
  final expected = symbols * width;
  if (expected > maxStreamBytes) {
    throw FourdgsMalformedFile(
      'attribute $attributeId declares $expected bytes, past the $maxStreamBytes cap',
    );
  }
  // The cap above bounds what is *decompressed*; this bounds what is
  // *materialized*, and they are not the same number. A constant-mode stream
  // stores `channels` symbols and repeats them `element_count` times, so a
  // handful of payload bytes can name a four-billion-element array — the cheap
  // half of a decompression bomb, and one the size ceiling alone does not see.
  if (count * channels > maxStreamBytes ~/ 4) {
    throw FourdgsMalformedFile(
      'attribute $attributeId would expand to ${count * channels} values, past the cap',
    );
  }

  final context =
      'attribute $attributeId stream header at byte $streamOffset'
      '${chunkOffset == null ? '' : ' in the Chunk at byte $chunkOffset'}';
  final raw = _decompress(cursor.take(payloadLength), codec, expected, context);
  final sym = _unshuffle(raw, width, symbols);

  final values = Int32List(count * channels);
  if (mode == modeConst) {
    // Exactly `channels` symbols, repeated `count` times.
    for (int c = 0; c < channels; c++) {
      final v = _unzigzag(sym[c]);
      for (int i = 0; i < count; i++) {
        values[i * channels + c] = v;
      }
    }
  } else {
    for (int i = 0; i < symbols; i++) {
      values[i] = _unzigzag(sym[i]);
    }
    if (mode == modeDelta) {
      // Delta runs along element order, so a channel accumulates against the
      // same channel of the previous element.
      //
      // The running sum is checked rather than allowed to wrap. Reading an
      // Int32List element yields a 64-bit Dart int, so the addition below
      // happens before any truncation and the overflow is visible here; storing
      // it would silently turn a malformed stream into a plausible wrong
      // number. That matters most for `object_id` (section 6.6), where the code
      // is a label rather than a bin: a wrapped code is a *different object*,
      // and the Rust reader — which accumulates in i64 and then requires the
      // code to fit an i32 — refuses the same file.
      for (int i = channels; i < values.length; i++) {
        final acc = values[i] + values[i - channels];
        if (acc > 2147483647 || acc < -2147483648) {
          throw FourdgsMalformedFile(
            'attribute $attributeId: delta stream leaves the 32-bit range at '
            'element ${i ~/ channels}',
          );
        }
        values[i] = acc;
      }
    } else if (mode != modeRaw) {
      throw FourdgsMalformedFile(
        'attribute $attributeId: unknown stream mode $mode',
      );
    }
  }
  return FourdgsAttributeStream(
    attributeId: attributeId,
    channels: channels,
    count: count,
    values: values,
  );
}