///|
fn read_u8(
  data : Bytes,
  pos : Int32,
  end : Int32,
) -> (Byte, Int32) raise MsfError {
  if pos < 0 || pos >= end || pos >= data.length() {
    raise Truncated(pos)
  }
  (data.at(pos), pos + 1)
}

///|
fn read_u32_le(
  data : Bytes,
  pos : Int32,
  end : Int32,
) -> (UInt32, Int32) raise MsfError {
  let (b0, pos) = read_u8(data, pos, end)
  let (b1, pos) = read_u8(data, pos, end)
  let (b2, pos) = read_u8(data, pos, end)
  let (b3, pos) = read_u8(data, pos, end)
  (
    ((b3.to_int() << 24) |
    (b2.to_int() << 16) |
    (b1.to_int() << 8) |
    b0.to_int()).reinterpret_as_uint(),
    pos,
  )
}

///|
fn read_u32_le_at(data : Bytes, offset : Int32) -> UInt32 raise MsfError {
  let (value, _) = read_u32_le(data, offset, data.length())
  value
}

///|
fn uint_to_int(value : UInt32, field : String) -> Int32 raise MsfError {
  let wide = value.to_int64()
  if wide > 0x7FFFFFFFL {
    raise InvalidValue("\{field} exceeds Int range")
  }
  wide.to_int()
}

///|
fn be_div_ceil(value : Int32, divisor : Int32) -> Int32 {
  if value == 0 {
    0
  } else {
    (value + divisor - 1) / divisor
  }
}

///|
fn checked_mul(
  left : Int32,
  right : Int32,
  field : String,
) -> Int32 raise MsfError {
  if left < 0 || right < 0 || (left != 0 && right > 0x7FFFFFFF / left) {
    raise InvalidValue("\{field} overflows Int range")
  }
  left * right
}

///|
fn checked_add(
  left : Int32,
  right : Int32,
  field : String,
) -> Int32 raise MsfError {
  if left < 0 || right < 0 || left > 0x7FFFFFFF - right {
    raise InvalidValue("\{field} overflows Int range")
  }
  left + right
}