///|
/// Byte level helpers shared by every format in this library.
///
/// The formats documented in `docs/design.md` are written with big endian fixed
/// width integers. That decision lives here and nowhere else. Every reader is
/// bounds checked and reports `None` instead of raising, because the bytes being
/// read usually come from a peer and must never be trusted.
let hex_digits : Array[Char] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
]
///|
/// A growable byte sink that the format writers append to.
///
/// `ByteSink` exists instead of `Bytes` because `Bytes` is immutable and the
/// writers do not know the final size in advance. It keeps the
/// "append then freeze" pattern in one place.
pub struct ByteSink {
bytes : Array[Byte]
}
///|
pub fn ByteSink::new() -> ByteSink {
{ bytes: [], }
}
///|
pub fn ByteSink::length(self : ByteSink) -> Int32 {
self.bytes.length()
}
///|
/// Append one byte, keeping the low eight bits of `value`.
pub fn ByteSink::write_u8(self : ByteSink, value : Int32) -> Unit {
self.bytes.push((value & 0xFF).to_byte())
}
///|
/// Append four bytes, most significant first.
pub fn ByteSink::write_u32(self : ByteSink, value : UInt32) -> Unit {
for shift in [24, 16, 8, 0] {
self.write_u8((value >> shift).reinterpret_as_int() & 0xFF)
}
}
///|
/// Append eight bytes, most significant first.
pub fn ByteSink::write_u64(self : ByteSink, value : UInt64) -> Unit {
for shift in [56, 48, 40, 32, 24, 16, 8, 0] {
self.write_u8(((value >> shift) & 0xFFUL).to_int())
}
}
///|
/// Append every byte of `view`.
pub fn ByteSink::write_bytes(self : ByteSink, view : BytesView) -> Unit {
for i = 0; i < view.length(); i = i + 1 {
self.bytes.push(view[i])
}
}
///|
/// An unsigned LEB128 variable length integer. Used by the manifest so that a
/// chunk length below 128 bytes costs one byte rather than four.
pub fn ByteSink::write_varint(self : ByteSink, value : UInt64) -> Unit {
let mut rest = value
let mut more = true
while more {
let low = (rest & 0x7FUL).to_int() & 0x7F
rest = rest >> 7
if rest == 0UL {
self.write_u8(low)
more = false
} else {
self.write_u8(low | 0x80)
}
}
}
///|
/// Freeze the sink into immutable bytes.
pub fn ByteSink::to_bytes(self : ByteSink) -> Bytes {
Bytes::from_array(self.bytes[:])
}
///|
/// The four big endian bytes of a `u32`.
pub fn be_u32(value : UInt32) -> Bytes {
let sink = ByteSink::new()
sink.write_u32(value)
sink.to_bytes()
}
///|
/// The eight big endian bytes of a `u64`.
pub fn be_u64(value : UInt64) -> Bytes {
let sink = ByteSink::new()
sink.write_u64(value)
sink.to_bytes()
}
///|
/// Read one unsigned byte at `offset`, or `None` when it is outside the view.
pub fn read_u8(view : BytesView, offset : Int32) -> Int32? {
if offset < 0 || offset >= view.length() {
return None
}
Some(view[offset].to_int())
}
///|
/// Read four big endian bytes at `offset`, or `None` when they do not fit.
pub fn read_u32(view : BytesView, offset : Int32) -> UInt32? {
if offset < 0 || offset + 4 > view.length() {
return None
}
let mut value : UInt32 = 0U
for i = 0; i < 4; i = i + 1 {
value = (value << 8) | view[offset + i].to_uint()
}
Some(value)
}
///|
/// Read eight big endian bytes at `offset`, or `None` when they do not fit.
pub fn read_u64(view : BytesView, offset : Int32) -> UInt64? {
if offset < 0 || offset + 8 > view.length() {
return None
}
let mut value : UInt64 = 0UL
for i = 0; i < 8; i = i + 1 {
value = (value << 8) | view[offset + i].to_uint64()
}
Some(value)
}
///|
/// Read an unsigned LEB128 integer at `offset`, returning the value and the
/// offset just after it. At most ten bytes are accepted; a longer encoding is a
/// malformed input, not a number.
pub fn read_varint(view : BytesView, offset : Int32) -> (UInt64, Int32)? {
let mut value : UInt64 = 0UL
let mut shift = 0
let mut cursor = offset
while shift <= 63 {
match read_u8(view, cursor) {
None => return None
Some(byte) => {
value = value | ((byte & 0x7F).to_uint64() << shift)
cursor = cursor + 1
if (byte & 0x80) == 0 {
return Some((value, cursor))
}
shift = shift + 7
}
}
}
None
}
///|
/// A bounds checked sub view, or `None` when the range is not fully inside.
pub fn sub_view(view : BytesView, offset : Int32, length : Int32) -> BytesView? {
if offset < 0 || length < 0 || offset + length > view.length() {
return None
}
Some(view.clamped_view(start=offset, end=offset + length))
}
///|
/// Lower case hexadecimal text for `view`, two characters per byte.
pub fn hex_encode(view : BytesView) -> String {
let builder = StringBuilder(size_hint=view.length() * 2)
for i = 0; i < view.length(); i = i + 1 {
let byte = view[i].to_int() & 0xFF
builder.write_char(hex_digits[byte / 16])
builder.write_char(hex_digits[byte % 16])
}
builder.to_string()
}
///|
/// Parse lower or upper case hexadecimal text. An odd length, or a character
/// that is not a hexadecimal digit, is a malformed input rather than a best
/// effort: silently dropping a nibble would make checksums meaningless.
pub fn hex_decode(text : String) -> Bytes? {
if text.length() % 2 != 0 {
return None
}
let sink = ByteSink::new()
let mut high : Int32? = None
for ch in text {
match hex_digit(ch) {
None => return None
Some(nibble) =>
match high {
None => high = Some(nibble)
Some(top) => {
sink.write_u8(top * 16 + nibble)
high = None
}
}
}
}
if high is Some(_) {
return None
}
Some(sink.to_bytes())
}
///|
fn hex_digit(ch : Char) -> Int32? {
if ch >= '0' && ch <= '9' {
Some(ch.to_int() - '0'.to_int())
} else if ch >= 'a' && ch <= 'f' {
Some(ch.to_int() - 'a'.to_int() + 10)
} else if ch >= 'A' && ch <= 'F' {
Some(ch.to_int() - 'A'.to_int() + 10)
} else {
None
}
}
///|
/// Byte wise equality that does not stop at the first difference.
///
/// Used when comparing strong checksums: a mismatch should not tell a peer
/// where in the digest the difference starts.
pub fn constant_time_equal(left : BytesView, right : BytesView) -> Bool {
if left.length() != right.length() {
return false
}
let mut diff = 0
for i = 0; i < left.length(); i = i + 1 {
diff = diff | (left[i].to_int() ^ right[i].to_int())
}
diff == 0
}
///|
/// The number of blocks of `block_size` bytes needed to hold `total` bytes,
/// rounded up. A total length of zero needs zero blocks.
pub fn block_count(total : Int32, block_size : Int32) -> Int32 {
if block_size <= 0 || total <= 0 {
return 0
}
(total + block_size - 1) / block_size
}