///|
/// An untrusted frame never establishes provider authenticity. The process host
/// must first qualify its executable and freshly complete the scoped operation.
pub(all) suberror TransportError {
Invalid(String)
}
///|
pub const SERVICE_ID : String = "morphir-package/provider-snapshot/0.1.0-draft.1"
///|
pub const MAX_REQUEST_BYTES : Int32 = 4194304
///|
pub const MAX_RESPONSE_BYTES : Int32 = 16777216
///|
fn[T] invalid(detail : String) -> T raise {
raise Invalid("metadata.restore_invalid_response: " + detail)
}
///|
/// Bound raw binary structure before the general Ion reader allocates values.
/// Native artifacts are blobs and are skipped without examining their contents.
fn preflight(bytes : Bytes) -> Unit raise {
if bytes.length() < 4 ||
bytes.length() > MAX_RESPONSE_BYTES ||
bytes[0] != b'\xe0' ||
bytes[1] != b'\x01' ||
bytes[2] != b'\x00' ||
bytes[3] != b'\xea' {
invalid("Require one bounded Ion 1.0 binary frame")
}
let mut count = 0
fn uint(position : Int32, end : Int32) -> (Int32, Int32) raise {
let mut value = 0
let mut cursor = position
for _ in 0..<5 {
if cursor >= end {
invalid("Truncated binary length")
}
let byte = bytes[cursor].to_int()
cursor += 1
if value > 16777216 / 128 {
invalid("Binary integer exceeds framing limit")
}
value = value * 128 + (byte & 127)
if (byte & 128) != 0 {
return (value, cursor)
}
}
invalid("Overlong binary integer")
}
fn value(position : Int32, end : Int32, depth : Int32) -> Int32 raise {
count += 1
if count > 8192 || depth > 16 || position >= end {
invalid("Binary structure limit")
}
let descriptor = bytes[position].to_int()
let kind = descriptor >> 4
let nibble = descriptor & 15
if kind == 15 {
invalid("Reserved binary descriptor")
}
let mut start = position + 1
if nibble == 15 {
return start
}
if kind == 1 {
if nibble != 0 && nibble != 1 {
invalid("Invalid Boolean descriptor")
}
return start
}
let length = if nibble == 14 || (kind == 13 && nibble == 1) {
let (length, next) = uint(start, end)
start = next
length
} else {
nibble
}
if length > end - start {
invalid("Truncated binary value")
}
let stop = start + length
if (kind == 2 || kind == 3) && length > 4 {
invalid("Protocol integer exceeds range")
}
if kind == 11 || kind == 12 || kind == 13 {
let mut cursor = start
while cursor < stop {
if kind == 13 {
let (_, next) = uint(cursor, stop)
cursor = next
}
cursor = value(cursor, stop, depth + 1)
}
} else if kind == 14 {
let (annotation_bytes, after_length) = uint(start, stop)
if annotation_bytes == 0 || annotation_bytes > stop - after_length {
invalid("Invalid annotation wrapper")
}
let annotation_end = after_length + annotation_bytes
let mut cursor = after_length
while cursor < annotation_end {
let (_, next) = uint(cursor, annotation_end)
cursor = next
}
if value(annotation_end, stop, depth + 1) != stop {
invalid("Trailing annotated value")
}
}
stop
}
let mut cursor = 4
while cursor < bytes.length() {
if cursor + 4 <= bytes.length() &&
bytes[cursor] == b'\xe0' &&
bytes[cursor + 1] == b'\x01' &&
bytes[cursor + 2] == b'\x00' &&
bytes[cursor + 3] == b'\xea' {
cursor += 4
} else {
cursor = value(cursor, bytes.length(), 0)
}
}
}
///|
fn members(value : @ion.IonValue) -> Map[String, @ion.IonValue] raise {
if value.has_annotations() {
invalid("Unexpected nested annotation")
}
guard value.kind() is @ion.Struct(entries) else {
invalid("Expected a struct")
}
let result = Map([])
for entry in entries {
guard entry.name().text() is Some(name) && !result.contains(name) else {
invalid("Duplicate or unresolved field")
}
result[name] = entry.value()
}
result
}
///|
fn fields(
value : @ion.IonValue,
names : Array[String],
) -> Map[String, @ion.IonValue] raise {
let result = members(value)
if result.length() != names.length() ||
!result.keys().all(name => names.contains(name)) {
invalid("Unknown or missing field")
}
result
}
///|
fn frame(
bytes : Bytes,
annotation : String,
names : Array[String],
) -> Map[String, @ion.IonValue] raise {
preflight(bytes)
let value = @binary.read_binary_value(
bytes[:],
fuel=Limited(@ion.Budget(128)),
)
if value.annotations().map(a => a.text()) != [Some(annotation)] {
invalid("Wrong response profile")
}
fields(value.with_annotations([]), names)
}
///|
fn required(
values : Map[String, @ion.IonValue],
key : String,
) -> @ion.IonValue raise {
values.get(key).unwrap_or_else(fn() raise { invalid("Missing " + key) })
}
///|
fn text(value : @ion.IonValue) -> String raise {
if value.has_annotations() {
invalid("Annotated scalar")
}
guard value.kind() is @ion.String(text) &&
text.length() <= 4096 &&
!text.contains("\u0000") else {
invalid("Expected a bounded string")
}
text
}
///|
fn blob(value : @ion.IonValue) -> Bytes raise {
if value.has_annotations() {
invalid("Annotated blob")
}
guard value.kind() is @ion.Blob(bytes) else {
invalid("Expected exact byte blob")
}
bytes
}
///|
fn list(value : @ion.IonValue, limit : Int32) -> Array[@ion.IonValue] raise {
if value.has_annotations() {
invalid("Annotated list")
}
guard value.kind() is @ion.List(values) && values.length() <= limit else {
invalid("Expected bounded list")
}
values
}
///|
fn raw_digest(value : String) -> String raise {
if value.length() != 71 ||
!value.has_prefix("sha256:") ||
!value[7:]
.to_owned()
.to_array()
.all(c => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
invalid("Invalid raw SHA-256 identity")
}
value[7:].to_owned()
}
///|
fn record(values : Array[(String, @ion.IonValue)]) -> @ion.IonValue {
@ion.IonValue::from_fields(values.map(v => @ion.field(v.0, v.1)))
}