///|
struct ProtoField {
name : String
json_name : String
number : Int
kind : Int
label : Int
type_name : String
oneof : Int?
optional : Bool
}
///|
struct ProtoMessage {
fields : Map[Int, ProtoField]
map_entry : Bool
proto3 : Bool
}
///|
struct ProtoEnum {
values : Map[Int, String]
first : Int
}
///|
pub struct ProtobufDecoder {
messages : Map[String, ProtoMessage]
enums : Map[String, ProtoEnum]
options : ProtobufOptions
field_count : Array[Int]
}
///|
fn proto_name(prefix : String, name : String) -> String {
if prefix == "" {
name
} else {
prefix + "." + name
}
}
///|
fn proto_identifier(name : String) -> Bool {
if name == "" {
return false
}
let mut index = 0
for char in name {
let code = char.to_int()
if !(code == 95 ||
(code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
(index > 0 && code >= 48 && code <= 57)) {
return false
}
index += 1
}
true
}
///|
fn proto_json_name(name : String) -> String {
let out = StringBuilder::new()
let mut upper = false
for char in name {
if char == '_' {
upper = true
} else {
let code = char.to_int()
out.write_char(
if upper && code >= 97 && code <= 122 {
(code - 32).unsafe_to_char()
} else {
char
},
)
upper = false
}
}
out.to_string()
}
///|
fn ProtobufDecoder::field(
self : ProtobufDecoder,
bytes : Bytes,
budget : Array[Int],
) -> ProtoField raise McapError {
let value = proto_wire(bytes, budget)
let mut name = ""
let mut json_name = ""
let mut number = 0
let mut kind = 0
let mut label = 1
let mut type_name = ""
let mut oneof : Int? = None
let mut optional = false
while value.next() is Some(item) {
match item.number {
1 => name = item.text()
3 => number = item.number()
4 => label = item.number()
5 => kind = item.number()
6 => type_name = item.text()
9 => oneof = Some(item.number())
10 => json_name = item.text()
17 => optional = item.number() != 0
_ => value.skip(item, self.options.depth)
}
}
if !proto_identifier(name) ||
number < 1 ||
number > 536870911 ||
kind < 1 ||
kind > 18 ||
kind == 10 ||
label < 1 ||
label > 3 ||
(oneof is Some(id) && (id < 0 || label != 1)) {
raise Invalid("invalid or unsupported protobuf field descriptor")
}
if type_name.has_prefix(".") {
type_name = type_name[1:].to_string()
}
self.field_count[0] += 1
if self.field_count[0] > self.options.fields {
raise Invalid("protobuf descriptor field budget exceeded")
}
{
name,
json_name: if json_name == "" {
proto_json_name(name)
} else {
json_name
},
number,
kind,
label,
type_name,
oneof,
optional,
}
}
///|
fn ProtobufDecoder::enum_type(
self : ProtobufDecoder,
prefix : String,
bytes : Bytes,
budget : Array[Int],
) -> Unit raise McapError {
let value = proto_wire(bytes, budget)
let mut name = ""
let entries = []
while value.next() is Some(item) {
if item.number == 1 {
name = item.text()
} else if item.number == 2 {
entries.push(item.bytes())
} else {
value.skip(item, self.options.depth)
}
}
let full = proto_name(prefix, name)
if !proto_identifier(name) ||
self.enums.contains(full) ||
self.messages.contains(full) {
raise Invalid("duplicate or empty protobuf enum")
}
let values : Map[Int, String] = Map([])
let mut first : Int? = None
let labels : Map[String, Bool] = Map([])
for bytes in entries {
let entry = proto_wire(bytes, budget)
let mut label = ""
let mut number = 0
while entry.next() is Some(item) {
if item.number == 1 {
label = item.text()
} else if item.number == 2 {
number = item.number()
} else {
entry.skip(item, self.options.depth)
}
}
if !proto_identifier(label) || labels.contains(label) {
raise Invalid("empty protobuf enum label")
}
labels[label] = true
if first is None {
first = Some(number)
}
if !values.contains(number) {
values[number] = label
}
self.field_count[0] += 1
if self.field_count[0] > self.options.fields {
raise Invalid("protobuf enum value budget exceeded")
}
}
if first is None {
raise Invalid("protobuf enum has no values")
}
self.enums[full] = { values, first: first.unwrap(), }
}
///|
fn ProtobufDecoder::message_type(
self : ProtobufDecoder,
prefix : String,
bytes : Bytes,
budget : Array[Int],
depth : Int,
proto3 : Bool,
) -> Unit raise McapError {
if depth > self.options.depth {
raise Invalid("protobuf descriptor depth exceeded")
}
let value = proto_wire(bytes, budget)
let mut name = ""
let fields = []
let nested = []
let enums = []
let mut map_entry = false
let mut oneof_count = 0
while value.next() is Some(item) {
match item.number {
1 => name = item.text()
2 => fields.push(item.bytes())
3 => nested.push(item.bytes())
4 => enums.push(item.bytes())
8 => {
ignore(item.bytes())
oneof_count += 1
}
7 => {
let options = proto_wire(item.bytes(), budget)
while options.next() is Some(option) {
if option.number == 7 {
map_entry = option.number() != 0
} else {
options.skip(option, self.options.depth)
}
}
}
_ => value.skip(item, self.options.depth)
}
}
let full = proto_name(prefix, name)
if !proto_identifier(name) ||
self.messages.contains(full) ||
self.enums.contains(full) {
raise Invalid("duplicate or empty protobuf message")
}
let by_number : Map[Int, ProtoField] = Map([])
let names : Map[String, Bool] = Map([])
let source_names : Map[String, Bool] = Map([])
for bytes in fields {
let field = self.field(bytes, budget)
if (field.oneof is Some(id) && id >= oneof_count) ||
(field.optional && (!proto3 || field.oneof is None)) ||
(proto3 && field.label == 2) ||
(field.number >= 19000 && field.number <= 19999) {
raise Invalid("invalid protobuf presence, oneof or reserved field number")
}
if by_number.contains(field.number) ||
names.contains(field.json_name) ||
source_names.contains(field.name) {
raise Invalid("duplicate protobuf field number or JSON name")
}
by_number[field.number] = field
names[field.json_name] = true
source_names[field.name] = true
}
for field in by_number.values() {
if field.optional &&
by_number
.values()
.filter(fn(other) { other.oneof == field.oneof })
.count() !=
1 {
raise Invalid("proto3 optional requires its own synthetic oneof")
}
}
self.messages[full] = { fields: by_number, map_entry, proto3, }
if self.messages.length() + self.enums.length() > self.options.fields {
raise Invalid("protobuf type budget exceeded")
}
for child in nested {
self.message_type(full, child, budget, depth + 1, proto3)
}
for child in enums {
self.enum_type(full, child, budget)
}
}
///|
pub fn protobuf_decoder(
descriptor_set : Bytes,
options? : ProtobufOptions = protobuf_options(),
) -> ProtobufDecoder raise McapError {
if options.descriptor_bytes < 0 ||
descriptor_set.length() > options.descriptor_bytes ||
options.message_bytes < 0 ||
options.depth < 1 ||
options.depth > 128 ||
options.items < 1 ||
options.fields < 1 {
raise Invalid("invalid protobuf descriptor or budgets")
}
let decoder : ProtobufDecoder = {
messages: Map([]),
enums: Map([]),
options,
field_count: [0],
}
let budget = [options.items]
let value = proto_wire(descriptor_set, budget)
while value.next() is Some(item) {
if item.number != 1 {
value.skip(item, options.depth)
continue
}
let file = proto_wire(item.bytes(), budget)
let mut package = ""
let mut proto3 = false
let messages = []
let enums = []
while file.next() is Some(field) {
match field.number {
2 => package = field.text()
4 => messages.push(field.bytes())
5 => enums.push(field.bytes())
12 => {
let syntax = field.text()
proto3 = syntax == "proto3"
if syntax != "" && syntax != "proto2" && syntax != "proto3" {
raise Invalid(
"protobuf Editions require an explicit supported decoder",
)
}
}
14 =>
raise Invalid(
"protobuf Editions require an explicit supported decoder",
)
_ => file.skip(field, options.depth)
}
}
for message in messages {
if package != "" &&
!package.split(".").all(fn(part) { proto_identifier(part.to_string()) }) {
raise Invalid("invalid protobuf package name")
}
decoder.message_type(package, message, budget, 1, proto3)
}
for enum_type in enums {
decoder.enum_type(package, enum_type, budget)
}
}
for message in decoder.messages.values() {
for field in message.fields.values() {
if field.kind == 11 && !decoder.messages.contains(field.type_name) {
raise Invalid(
"unresolved protobuf message type; include descriptor imports",
)
}
if field.kind == 14 && !decoder.enums.contains(field.type_name) {
raise Invalid("unresolved protobuf enum type")
}
}
if message.map_entry {
if message.fields.length() != 2 ||
!message.fields.contains(1) ||
!message.fields.contains(2) ||
message.fields[1].label == 3 ||
message.fields[2].label == 3 ||
[1, 2, 10, 11, 12, 14].contains(message.fields[1].kind) {
raise Invalid("invalid protobuf map entry")
}
}
}
decoder
}