// A minimal ASN.1 reader: tags, lengths and children.
//
// This is the framing layer `plist.mbt` builds on when it decodes the DER
// property list Apple stores in the entitlements slot. It implements exactly
// what that decoding needs — definite lengths in short and long form, the
// indefinite form BER permits, and the child ranges of a constructed element —
// and refuses anything it does not understand rather than guessing.
//
// It is not a general ASN.1 library and does not interpret tags: which tag
// means "dictionary" is a question about Apple's plist encoding, and that
// answer lives in `plist.mbt`, not here.
///|
/// One DER element: its tag, its content range and the offset just past it.
pub(all) struct DerElement {
/// The identifier octet. Only single-byte tags are decoded, which covers
/// every tag that appears in a property list.
tag : UInt32
/// First byte of the content.
content_start : Int64
/// One past the last content byte.
content_end : Int64
/// One past the last byte of the whole element, header included.
next : Int64
}
///|
/// The universal tag number for a SEQUENCE or SEQUENCE OF.
///
/// In Apple's plist encoding a SEQUENCE is an array; a dictionary is a
/// context-class tag instead, defined in `plist.mbt`.
pub const TAG_SEQUENCE : UInt32 = 0x30
///|
/// The universal tag number for an OCTET STRING.
pub const TAG_OCTET_STRING : UInt32 = 0x04
///|
/// The universal tag number for a UTF8String.
pub const TAG_UTF8 : UInt32 = 0x0C
///|
/// Reads one element's header and content range.
///
/// Handles both definite lengths (short and long form) and the indefinite form
/// BER permits, which ends at a zero-length end-of-contents marker.
pub fn read_element(
bytes : Bytes,
offset : Int64,
limit : Int64,
) -> DerElement raise MachscopeError {
if offset + 2L > limit {
raise UnexpectedEnd(offset~, needed=2L, available=limit - offset)
}
let tag = bytes.at(offset.to_int()).to_int().reinterpret_as_uint()
let first_len = bytes.at(offset.to_int() + 1).to_int()
let mut cursor = offset + 2L
let content_len : Int64? = if first_len < 0x80 {
Some(first_len.to_int64())
} else if first_len == 0x80 {
None // indefinite: caller walks children until end-of-contents
} else {
let n = first_len & 0x7F
if n > 4 {
raise Unsupported(what="DER length longer than 32 bits")
}
let mut len = 0L
for _ in 0.. limit {
raise UnexpectedEnd(offset=cursor, needed=1L, available=limit - cursor)
}
len = (len << 8) | bytes.at(cursor.to_int()).to_int().to_int64()
cursor = cursor + 1L
}
Some(len)
}
match content_len {
Some(len) => {
if cursor + len > limit {
raise OutOfBounds(what="DER content", offset=cursor, size=len, limit~)
}
DerElement::{
tag,
content_start: cursor,
content_end: cursor + len,
next: cursor + len,
}
}
None => {
// Walk children until the two zero bytes that close the element.
let mut at = cursor
let mut end = -1L
while at + 2L <= limit {
let t = bytes.at(at.to_int()).to_int()
let l = bytes.at(at.to_int() + 1).to_int()
if t == 0 && l == 0 {
end = at + 2L
break
}
let child = read_element(bytes, at, limit)
at = child.next
}
if end < 0L {
raise UnexpectedEnd(offset~, needed=2L, available=limit - at)
}
DerElement::{ tag, content_start: cursor, content_end: at, next: end, }
}
}
}
///|
/// Collects the immediate children of a constructed element.
///
/// Indefinite-length parents have their children walked until the
/// end-of-contents marker; definite-length parents stop at their declared end.
pub fn children(
bytes : Bytes,
parent : DerElement,
) -> Array[DerElement] raise MachscopeError {
let out : Array[DerElement] = []
let mut at = parent.content_start
while at < parent.content_end {
// Skip the end-of-contents marker of an indefinite element.
if parent.content_end - at >= 2L {
let t = bytes.at(at.to_int()).to_int()
let l = bytes.at(at.to_int() + 1).to_int()
if t == 0 && l == 0 {
break
}
}
let child = read_element(bytes, at, parent.content_end)
out.push(child)
at = child.next
}
out
}