///|
priv struct ReadState {
bytes : Bytes
offsets : Array[Int]
sizes : Array[Int]
mut remaining_words : Int
}
///|
/// An immutable, framed Cap'n Proto message. Readers share its traversal budget.
pub struct Message {
priv state : ReadState
priv nesting_limit : Int
}
///|
/// A lazy pointer. Its target is validated when it is dereferenced.
pub struct PointerReader {
priv state : ReadState
priv segment : Int
priv word : Int
priv depth : Int
}
///|
/// A view of a struct's data and pointer sections.
pub struct StructReader {
priv state : ReadState
priv segment : Int
priv start : Int
priv data_bytes : Int
priv pointer_start : Int
priv pointer_count : Int
priv depth : Int
}
///|
/// A lazy list view, including lists of inline composite structs.
pub struct ListReader {
priv state : ReadState
priv segment : Int
priv start : Int
priv count : Int
priv code : Int
priv stride : Int
priv data_words : Int
priv pointer_count : Int
priv depth : Int
}
///|
fn read_u32(bytes : Bytes, pos : Int) -> UInt {
bytes[pos].to_uint() |
(bytes[pos + 1].to_uint() << 8) |
(bytes[pos + 2].to_uint() << 16) |
(bytes[pos + 3].to_uint() << 24)
}
///|
fn read_u64(bytes : Bytes, pos : Int) -> UInt64 {
read_u32(bytes, pos).to_uint64() |
(read_u32(bytes, pos + 4).to_uint64() << 32)
}
///|
fn read_charge(state : ReadState, words : Int) -> Unit raise CapnpError {
if words < 0 || words > state.remaining_words {
raise Limit("message traversal limit exceeded")
}
state.remaining_words -= words
}
///|
fn read_extent(
state : ReadState,
segment : Int,
word : Int,
count : Int,
) -> Unit raise CapnpError {
if segment < 0 || segment >= state.sizes.length() {
raise Bounds("segment index outside message")
}
let size = state.sizes[segment]
if word < 0 || count < 0 || word > size || count > size - word {
raise Bounds("object outside segment")
}
}
///|
fn read_word(
state : ReadState,
segment : Int,
word : Int,
) -> UInt64 raise CapnpError {
read_extent(state, segment, word, 1)
read_u64(state.bytes, state.offsets[segment] + word * 8)
}
///|
/// Read exactly one message using standard (unpacked) stream framing.
/// Trailing bytes are rejected; use frame_length to select a frame from a stream.
pub fn read_message(
bytes : Bytes,
traversal_limit_words? : Int = 8388608,
nesting_limit? : Int = 64,
max_segments? : Int = 512,
) -> Message raise CapnpError {
if traversal_limit_words < 0 || nesting_limit < 0 || max_segments < 1 {
raise Limit("invalid reader limit")
}
if bytes.length() < 8 {
raise InvalidMessage("truncated segment table")
}
let minus_one = read_u32(bytes, 0)
if minus_one >= max_segments.reinterpret_as_uint() {
raise Limit("too many message segments")
}
let count = minus_one.reinterpret_as_int() + 1
if count > (bytes.length() - 4) / 4 {
raise InvalidMessage("truncated segment table")
}
let header64 = (count.to_int64() + 2) / 2 * 8
if header64 > bytes.length().to_int64() {
raise InvalidMessage("truncated segment table padding")
}
let header = header64.to_int()
let offsets : Array[Int] = []
let sizes : Array[Int] = []
let mut cursor = header
for i in 0.. ((bytes.length() - cursor) / 8).reinterpret_as_uint() {
raise InvalidMessage("truncated message segment")
}
offsets.push(cursor)
sizes.push(size.reinterpret_as_int())
cursor += size.reinterpret_as_int() * 8
}
if cursor != bytes.length() {
raise InvalidMessage("trailing bytes after framed message")
}
if sizes[0] == 0 {
raise InvalidMessage("first segment has no root pointer")
}
{
state: { bytes, offsets, sizes, remaining_words: traversal_limit_words, },
nesting_limit,
}
}
///|
pub fn Message::root_pointer(self : Message) -> PointerReader {
{ state: self.state, segment: 0, word: 0, depth: self.nesting_limit, }
}
///|
pub fn Message::root(self : Message) -> StructReader raise CapnpError {
self.root_pointer().as_struct()
}
///|
pub fn Message::segment_count(self : Message) -> Int {
self.state.sizes.length()
}
///|
pub fn Message::remaining_traversal_words(self : Message) -> Int {
self.state.remaining_words
}
///|
fn read_null(state : ReadState, depth : Int) -> PointerReader {
{ state, segment: 0, word: -1, depth, }
}
///|
pub fn PointerReader::is_null(self : PointerReader) -> Bool {
self.word == -1 ||
read_u64(self.state.bytes, self.state.offsets[self.segment] + self.word * 8) ==
0
}
///|
fn read_empty_struct(state : ReadState, depth : Int) -> StructReader {
{
state,
segment: 0,
start: 0,
data_bytes: 0,
pointer_start: 0,
pointer_count: 0,
depth,
}
}
///|
fn read_empty_list(state : ReadState, depth : Int) -> ListReader {
{
state,
segment: 0,
start: 0,
count: 0,
code: 0,
stride: 0,
data_words: 0,
pointer_count: 0,
depth,
}
}