///|
/// Every cursor has a local end; a message cannot consume its neighbour.
priv struct Cursor {
data : Bytes
mut pos : Int32
end : Int32
}
///|
fn cursor(data : Bytes, start : Int32, size : Int32) -> Cursor raise H5Error {
if start < 0 ||
size < 0 ||
start > data.length() ||
size > data.length() - start {
raise Invalid("truncated byte range", start)
}
{ data, pos: start, end: start + size, }
}
///|
fn Cursor::need(self : Cursor, n : Int32) -> Unit raise H5Error {
if n < 0 || n > self.end - self.pos {
raise Invalid("truncated field", self.pos)
}
}
///|
fn Cursor::u8(self : Cursor) -> Int32 raise H5Error {
self.need(1)
let v = self.data[self.pos].to_int()
self.pos += 1
v
}
///|
fn Cursor::skip(self : Cursor, n : Int32) -> Unit raise H5Error {
self.need(n)
self.pos += n
}
///|
fn Cursor::bytes(self : Cursor, n : Int32) -> Bytes raise H5Error {
self.need(n)
let out = self.data[self.pos:self.pos + n].to_owned()
self.pos += n
out
}
///|
fn Cursor::uint(self : Cursor, width : Int32) -> UInt64 raise H5Error {
if width < 1 || width > 8 {
raise Unsupported("integer field width", self.pos)
}
self.need(width)
let mut out = 0UL
for i = 0; i < width; i = i + 1 {
out = out | (self.u8().to_uint64() << (i * 8))
}
out
}
///|
/// Host indices are deliberately bounded; never truncate 64-bit addresses.
fn Cursor::index(self : Cursor, width : Int32) -> Int32 raise H5Error {
let at = self.pos
let n = self.uint(width)
if n > 2147483647UL {
raise Limit("file offset/extent exceeds Int range at \{at}")
}
n.to_int()
}
///|
/// HDF5's all-ones address is an absent address, not a huge positive index.
fn Cursor::address(
self : Cursor,
width : Int32,
base : Int32,
) -> Int32? raise H5Error {
let n = self.uint(width)
let absent = if width == 8 {
0xffffffffffffffffUL
} else {
(1UL << (8 * width)) - 1UL
}
if n == absent {
return None
}
if n > 2147483647UL {
raise Limit("file address exceeds Int range")
}
Some(checked_add(base, n.to_int()))
}
///|
fn Cursor::signature(self : Cursor, expected : Bytes) -> Unit raise H5Error {
let start = self.pos
if self.bytes(expected.length()) != expected {
raise Invalid("bad signature", start)
}
}
///|
priv struct Buffer {
items : Array[Byte]
}
///|
fn buffer() -> Buffer {
{ items: [], }
}
///|
fn Buffer::length(self : Buffer) -> Int32 {
self.items.length()
}
///|
fn Buffer::u8(self : Buffer, n : Int32) -> Unit {
self.items.push(n.to_byte())
}
///|
fn Buffer::uint(self : Buffer, n : UInt64, width : Int32) -> Unit {
for i = 0; i < width; i = i + 1 {
self.items.push((n >> (i * 8)).to_byte())
}
}
///|
fn Buffer::bytes(self : Buffer, data : Bytes) -> Unit {
for byte in data {
self.items.push(byte)
}
}
///|
fn Buffer::zeros(self : Buffer, count : Int32) -> Unit {
for i = 0; i < count; i = i + 1 {
self.items.push(0)
}
}
///|
fn Buffer::finish(self : Buffer) -> Bytes {
Bytes::from_array(self.items)
}
///|
fn bytes_at(data : Bytes, start : Int32, length : Int32) -> Bytes raise H5Error {
cursor(data, start, length).bytes(length)
}
///|
test "bounded cursor rejects truncation and 64-bit host overflow" {
let c = cursor(b"\x01\x02\x03\x04", 0, 4)
assert_eq(c.uint(2), 513UL)
assert_eq(c.bytes(2), b"\x03\x04")
let bad = try {
ignore(c.u8())
false
} catch {
_ => true
}
assert_true(bad)
assert_eq(cursor(b"\xff\xff\xff\xff", 0, 4).address(4, 0), None)
assert_true(
try {
ignore(cursor(b"\x00\x00\x00\x80", 0, 4).index(4))
false
} catch {
_ => true
},
)
}
///|
test "little endian buffer preserves exact unsigned bits" {
let b = buffer()
b.uint(0xfedcba9876543210UL, 8)
assert_eq(cursor(b.finish(), 0, 8).uint(8), 0xfedcba9876543210UL)
}