feat(protocol): define gateway control envelopes
This commit is contained in:
@@ -53,10 +53,119 @@ fn evaluate(kind: &str, input: &str) -> &'static str {
|
||||
}
|
||||
"tunnel" => "invalid:unsupported_version",
|
||||
"datagram" => classify_datagram(values.get("hex").map(String::as_str).unwrap_or_default()),
|
||||
"gateway_input" => classify_gateway_input(values.get("hex").map(String::as_str).unwrap_or_default()),
|
||||
"gateway_feedback" => classify_gateway_feedback(values.get("hex").map(String::as_str).unwrap_or_default()),
|
||||
"gateway_clipboard" if values.contains_key("file") => "invalid:forbidden",
|
||||
"gateway_clipboard"
|
||||
if matches!(values.get("direction").map(String::as_str), Some("client_to_provider") | Some("provider_to_client"))
|
||||
&& values.get("encoding").map(String::as_str) == Some("utf-8")
|
||||
&& values.get("loop_token").map_or(false, |value| (16..=128).contains(&value.len()))
|
||||
&& values.get("text").map_or(false, |value| value.len() <= 65536) => "valid",
|
||||
"gateway_clipboard" => "invalid:clipboard",
|
||||
"gateway_clipboard_audit" if values.contains_key("text") => "invalid:forbidden",
|
||||
"gateway_clipboard_audit"
|
||||
if matches!(values.get("direction").map(String::as_str), Some("client_to_provider") | Some("provider_to_client"))
|
||||
&& matches!(values.get("outcome").map(String::as_str), Some("forwarded") | Some("suppressed") | Some("rejected"))
|
||||
&& matches!(values.get("reason").map(String::as_str), Some("forwarded") | Some("loop") | Some("policy") | Some("rate") | Some("provider") | Some("malformed"))
|
||||
&& values.get("text_bytes").and_then(|value| value.parse::<usize>().ok()).map_or(false, |size| size <= 65536) => "valid",
|
||||
"gateway_clipboard_audit" => "invalid:clipboard_audit",
|
||||
_ => "invalid:unknown_kind",
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_gateway_input(encoded: &str) -> &'static str {
|
||||
let raw = match decode_hex(encoded) {
|
||||
Some(raw) => raw,
|
||||
None => return "invalid:hex",
|
||||
};
|
||||
if raw.len() < 6 {
|
||||
return "invalid:truncated";
|
||||
}
|
||||
if raw[0..4] != *b"VGI1" {
|
||||
return "invalid:magic";
|
||||
}
|
||||
let kind = raw[4];
|
||||
let body = &raw[6..];
|
||||
if body.len() != raw[5] as usize {
|
||||
return "invalid:length";
|
||||
}
|
||||
match kind {
|
||||
1 if body.len() == 4 && body[0] <= 1 && (body[2] != 0 || body[3] != 0) => "valid",
|
||||
1 => "invalid:field",
|
||||
2 if body.len() != 3 => "invalid:length",
|
||||
2 if body[0] > 1 || !(1..=5).contains(&body[1]) => "invalid:field",
|
||||
2 if body[2] != 0 => "invalid:reserved",
|
||||
2 => "valid",
|
||||
3 if body.len() == 4 => "valid",
|
||||
3 => "invalid:length",
|
||||
4 if (1..=4).contains(&body.len()) && std::str::from_utf8(body).ok().map_or(false, |value| value.chars().count() == 1) => "valid",
|
||||
4 => "invalid:utf8",
|
||||
5 if body.len() != 17 => "invalid:length",
|
||||
5 if body[0] > 15 => "invalid:field",
|
||||
5 if body[1] == 0 && body[2] == 0 && body[3..].iter().any(|value| *value != 0) => "invalid:field",
|
||||
5 => "valid",
|
||||
_ => "invalid:kind",
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_gateway_feedback(encoded: &str) -> &'static str {
|
||||
let raw = match decode_hex(encoded) {
|
||||
Some(raw) => raw,
|
||||
None => return "invalid:hex",
|
||||
};
|
||||
if raw.len() < 8 {
|
||||
return "invalid:truncated";
|
||||
}
|
||||
if raw[0..4] != *b"VGF1" {
|
||||
return "invalid:magic";
|
||||
}
|
||||
let direction = raw[4];
|
||||
let kind = raw[5];
|
||||
let body = &raw[8..];
|
||||
if body.len() != ((raw[6] as usize) << 8 | raw[7] as usize) {
|
||||
return "invalid:length";
|
||||
}
|
||||
if direction > 1 {
|
||||
return "invalid:direction";
|
||||
}
|
||||
if direction == 0 {
|
||||
if (0x10..=0x12).contains(&kind) {
|
||||
return "invalid:direction";
|
||||
}
|
||||
return match kind {
|
||||
1 if body.is_empty() => "valid",
|
||||
1 => "invalid:length",
|
||||
2 if valid_fec_status(body) => "valid",
|
||||
2 => "invalid:field",
|
||||
_ => "invalid:type",
|
||||
};
|
||||
}
|
||||
if kind == 1 || kind == 2 {
|
||||
return "invalid:direction";
|
||||
}
|
||||
match kind {
|
||||
0x10 if body.len() == 4 => "valid",
|
||||
0x10 => "invalid:length",
|
||||
0x11 if body.len() != 5 => "invalid:length",
|
||||
0x11 if body[0] <= 15 => "valid",
|
||||
0x11 => "invalid:field",
|
||||
0x12 if body.len() != 1 => "invalid:length",
|
||||
0x12 if body[0] <= 1 => "valid",
|
||||
0x12 => "invalid:field",
|
||||
_ => "invalid:type",
|
||||
}
|
||||
}
|
||||
|
||||
fn valid_fec_status(body: &[u8]) -> bool {
|
||||
body.len() == 21
|
||||
&& ((body[10] as u16) << 8 | body[11] as u16) > 0
|
||||
&& ((body[14] as u16) << 8 | body[15] as u16) <= ((body[10] as u16) << 8 | body[11] as u16)
|
||||
&& ((body[16] as u16) << 8 | body[17] as u16) <= ((body[12] as u16) << 8 | body[13] as u16)
|
||||
&& body[18] <= 100
|
||||
&& body[20] > 0
|
||||
&& body[19] < body[20]
|
||||
}
|
||||
|
||||
fn decode_hex(input: &str) -> Option<Vec<u8>> {
|
||||
if input.len() % 2 != 0 {
|
||||
return None;
|
||||
|
||||
Reference in New Issue
Block a user