feat(protocol): carry complete encoded media frames
Verify Protocol / verify (push) Canceled after 0s
Verify Protocol / module (push) Successful in 53s

This commit is contained in:
sechmachine
2026-07-30 22:23:22 +07:00
parent d1d00d6472
commit c554cac00d
25 changed files with 303 additions and 74 deletions
+1
View File
@@ -87,6 +87,7 @@ def check_text_boundaries() -> None:
ROOT / "openapi/control-v1.yaml",
ROOT / "proto/versevdi/control/v1/control.proto",
ROOT / "frames/datagram-v1.md",
ROOT / "frames/datagram-v2.md",
ROOT / "frames/registry.json",
ROOT / "registries/features.json",
ROOT / "registries/datagrams.json",
+24 -11
View File
@@ -14,11 +14,6 @@ import (
protocol "git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol/gen/go/protocol"
)
const (
datagramHeaderBytes = 21
maximumFrameBytes = 65536
)
func main() {
entries, err := os.ReadDir("fixtures/conformance")
if err != nil {
@@ -320,16 +315,24 @@ func classifyDatagram(encoded string) string {
if err != nil {
return "invalid:hex"
}
if len(raw) < datagramHeaderBytes {
if len(raw) < 3 {
return "invalid:truncated"
}
if string(raw[:2]) != "VD" {
return "invalid:magic"
}
if raw[2] != 1 {
if raw[2] != 1 && raw[2] != 2 {
return "invalid:unsupported_version"
}
headerBytes := 21
limits := map[byte]int{1: 1024, 2: 2048, 3: 65515, 10: 1179, 11: 1179, 12: 1179}
if raw[2] == 2 {
headerBytes = 23
limits = map[byte]int{10: 1177, 11: 1177}
}
if len(raw) < headerBytes {
return "invalid:truncated"
}
limit, ok := limits[raw[3]]
if !ok {
return "invalid:unknown_channel"
@@ -337,17 +340,27 @@ func classifyDatagram(encoded string) string {
if raw[4] != 0 {
return "invalid:flags"
}
if raw[18] == 0 || raw[17] >= raw[18] {
fragmentIndex, fragmentCount := int(raw[17]), int(raw[18])
payloadOffset := 19
if raw[2] == 2 {
fragmentIndex = int(raw[17])<<8 | int(raw[18])
fragmentCount = int(raw[19])<<8 | int(raw[20])
payloadOffset = 21
if fragmentCount > 891 {
return "invalid:fragment_limit"
}
}
if fragmentCount == 0 || fragmentIndex >= fragmentCount {
return "invalid:fragment"
}
payloadLength := int(raw[19])<<8 | int(raw[20])
payloadLength := int(raw[payloadOffset])<<8 | int(raw[payloadOffset+1])
if payloadLength > limit {
return "invalid:payload_limit"
}
if len(raw) != datagramHeaderBytes+payloadLength {
if len(raw) != headerBytes+payloadLength {
return "invalid:length_mismatch"
}
if len(raw) > maximumFrameBytes {
if raw[2] == 1 && len(raw) > 65536 || raw[2] == 2 && len(raw) > 1200 {
return "invalid:frame_limit"
}
return "valid"
+25 -11
View File
@@ -189,36 +189,50 @@ fn classify_datagram(encoded: &str) -> &'static str {
Some(raw) => raw,
None => return "invalid:hex",
};
if raw.len() < 21 {
if raw.len() < 3 {
return "invalid:truncated";
}
if raw[0..2] != *b"VD" {
return "invalid:magic";
}
if raw[2] != 1 {
if raw[2] != 1 && raw[2] != 2 {
return "invalid:unsupported_version";
}
let limit = match raw[3] {
1 => 1024,
2 => 2048,
3 => 65515,
10 | 11 | 12 => 1179,
let header_bytes = if raw[2] == 1 { 21 } else { 23 };
if raw.len() < header_bytes {
return "invalid:truncated";
}
let limit = match (raw[2], raw[3]) {
(1, 1) => 1024,
(1, 2) => 2048,
(1, 3) => 65515,
(1, 10 | 11 | 12) => 1179,
(2, 10 | 11) => 1177,
_ => return "invalid:unknown_channel",
};
if raw[4] != 0 {
return "invalid:flags";
}
if raw[18] == 0 || raw[17] >= raw[18] {
let (fragment_index, fragment_count, payload_offset) = if raw[2] == 1 {
(raw[17] as usize, raw[18] as usize, 19)
} else {
let count = ((raw[19] as usize) << 8) | raw[20] as usize;
if count > 891 {
return "invalid:fragment_limit";
}
((((raw[17] as usize) << 8) | raw[18] as usize), count, 21)
};
if fragment_count == 0 || fragment_index >= fragment_count {
return "invalid:fragment";
}
let payload_length = ((raw[19] as usize) << 8) | raw[20] as usize;
let payload_length = ((raw[payload_offset] as usize) << 8) | raw[payload_offset + 1] as usize;
if payload_length > limit {
return "invalid:payload_limit";
}
if raw.len() != 21 + payload_length {
if raw.len() != header_bytes + payload_length {
return "invalid:length_mismatch";
}
if raw.len() > 65536 {
if raw[2] == 1 && raw.len() > 65536 || raw[2] == 2 && raw.len() > 1200 {
return "invalid:frame_limit";
}
"valid"
+18 -11
View File
@@ -129,23 +129,30 @@ func validFECStatus(_ body: [UInt8]) -> Bool {
func classifyDatagram(_ encoded: String) -> String {
guard let raw = decodeHex(encoded) else { return "invalid:hex" }
guard raw.count >= 21 else { return "invalid:truncated" }
guard raw.count >= 3 else { return "invalid:truncated" }
guard raw[0] == 0x56 && raw[1] == 0x44 else { return "invalid:magic" }
guard raw[2] == 1 else { return "invalid:unsupported_version" }
guard raw[2] == 1 || raw[2] == 2 else { return "invalid:unsupported_version" }
let headerBytes = raw[2] == 1 ? 21 : 23
guard raw.count >= headerBytes else { return "invalid:truncated" }
let limit: Int
switch raw[3] {
case 1: limit = 1024
case 2: limit = 2048
case 3: limit = 65515
case 10, 11, 12: limit = 1179
switch (raw[2], raw[3]) {
case (1, 1): limit = 1024
case (1, 2): limit = 2048
case (1, 3): limit = 65515
case (1, 10), (1, 11), (1, 12): limit = 1179
case (2, 10), (2, 11): limit = 1177
default: return "invalid:unknown_channel"
}
guard raw[4] == 0 else { return "invalid:flags" }
guard raw[18] > 0 && raw[17] < raw[18] else { return "invalid:fragment" }
let payloadLength = Int(raw[19]) * 256 + Int(raw[20])
let fragmentIndex = raw[2] == 1 ? Int(raw[17]) : Int(raw[17]) * 256 + Int(raw[18])
let fragmentCount = raw[2] == 1 ? Int(raw[18]) : Int(raw[19]) * 256 + Int(raw[20])
if raw[2] == 2 && fragmentCount > 891 { return "invalid:fragment_limit" }
guard fragmentCount > 0 && fragmentIndex < fragmentCount else { return "invalid:fragment" }
let payloadOffset = raw[2] == 1 ? 19 : 21
let payloadLength = Int(raw[payloadOffset]) * 256 + Int(raw[payloadOffset + 1])
guard payloadLength <= limit else { return "invalid:payload_limit" }
guard raw.count == 21 + payloadLength else { return "invalid:length_mismatch" }
guard raw.count <= 65536 else { return "invalid:frame_limit" }
guard raw.count == headerBytes + payloadLength else { return "invalid:length_mismatch" }
guard raw[2] == 1 ? raw.count <= 65536 : raw.count <= 1200 else { return "invalid:frame_limit" }
return "valid"
}
+1
View File
@@ -15,6 +15,7 @@ TEXT_PATHS = (
"proto/versevdi/control/v1/control.proto",
"proto/versevdi/tunnel/v1/tunnel.proto",
"frames/datagram-v1.md",
"frames/datagram-v2.md",
"frames/registry.json",
"registries/features.json",
"registries/datagrams.json",
+19
View File
@@ -35,6 +35,17 @@ let capability = try CapabilityProfile(
transport: "quic-tls13", framing: "datagram-v1", media: "encoded",
audio: "encoded", sourceRateControl: "server", clientDecode: ["h264-opus"]
)
_ = try CapabilityProfile(
transport: "quic-tls13", framing: "datagram-v2", media: "encoded",
audio: "encoded", sourceRateControl: "server", clientDecode: ["h264-opus"]
)
do {
_ = try CapabilityProfile(
transport: "quic-tls13", framing: "datagram-v3", media: "encoded",
audio: "encoded", sourceRateControl: "server", clientDecode: ["h264-opus"]
)
fatalError("unregistered framing was accepted")
} catch { }
let request = try TunnelAdmissionRequest(
version: "1", sessionId: "session", gatewayId: "gateway", audience: "audience",
grant: String(repeating: "g", count: 43), reconnectSequence: 0,
@@ -162,6 +173,14 @@ fn main() {
"quic-tls13".into(), "datagram-v1".into(), "encoded".into(),
"encoded".into(), "server".into(), vec!["h264-opus".into()],
).unwrap();
assert!(CapabilityProfile::new(
"quic-tls13".into(), "datagram-v2".into(), "encoded".into(),
"encoded".into(), "server".into(), vec!["h264-opus".into()],
).is_ok());
assert!(CapabilityProfile::new(
"quic-tls13".into(), "datagram-v3".into(), "encoded".into(),
"encoded".into(), "server".into(), vec!["h264-opus".into()],
).is_err());
let request = TunnelAdmissionRequest::new(
"1".into(), "session".into(), "gateway".into(), "audience".into(),
"g".repeat(43), 0, "n".repeat(16), "s".repeat(86), capabilities.clone(),
+2 -1
View File
@@ -31,9 +31,10 @@ def main() -> int:
value = json.loads((ROOT / registry).read_text(encoding="utf-8"))
entries = value.get("features", value.get("datagrams"))
assert entries and len({entry["id"] for entry in entries}) == len(entries)
maximum_bound = 1_048_576 if registry.endswith("datagrams.json") else 65_536
for entry in entries:
maximum = entry.get("max_frame_bytes", entry.get("max_payload_bytes"))
assert isinstance(maximum, int) and 1 <= maximum <= 65536
assert isinstance(maximum, int) and 1 <= maximum <= maximum_bound
manifest = json.loads((ROOT / "fixtures/valid/manifest.json").read_text(encoding="utf-8"))
assert set(manifest).issubset(set(defs["ConnectionManifest"]["properties"]))
+33 -22
View File
@@ -7,47 +7,58 @@ import binascii
import pathlib
ROOT = pathlib.Path(__file__).resolve().parents[1]
HEADER_BYTES = 21
MAX_FRAME_BYTES = 65536
CHANNEL_LIMITS = {1: 1024, 2: 2048, 3: 65515, 10: 1179, 11: 1179, 12: 1179}
V1_CHANNEL_LIMITS = {1: 1024, 2: 2048, 3: 65515, 10: 1179, 11: 1179, 12: 1179}
V2_CHANNEL_LIMITS = {10: 1177, 11: 1177}
def classify(raw: bytes) -> str:
if len(raw) < HEADER_BYTES:
if len(raw) < 3:
return "invalid:truncated"
if raw[:2] != b"VD":
return "invalid:magic"
if raw[2] != 1:
if raw[2] not in (1, 2):
return "invalid:unsupported_version"
if raw[3] not in CHANNEL_LIMITS:
header_bytes = 21 if raw[2] == 1 else 23
if len(raw) < header_bytes:
return "invalid:truncated"
limits = V1_CHANNEL_LIMITS if raw[2] == 1 else V2_CHANNEL_LIMITS
if raw[3] not in limits:
return "invalid:unknown_channel"
if raw[4] != 0:
return "invalid:flags"
fragment_index, fragment_count = raw[17], raw[18]
if raw[2] == 1:
fragment_index, fragment_count = raw[17], raw[18]
payload_length = int.from_bytes(raw[19:21], "big")
else:
fragment_index = int.from_bytes(raw[17:19], "big")
fragment_count = int.from_bytes(raw[19:21], "big")
payload_length = int.from_bytes(raw[21:23], "big")
if fragment_count > 891:
return "invalid:fragment_limit"
if fragment_count == 0 or fragment_index >= fragment_count:
return "invalid:fragment"
payload_length = int.from_bytes(raw[19:21], "big")
if payload_length > CHANNEL_LIMITS[raw[3]]:
if payload_length > limits[raw[3]]:
return "invalid:payload_limit"
if len(raw) != HEADER_BYTES + payload_length:
if len(raw) != header_bytes + payload_length:
return "invalid:length_mismatch"
if len(raw) > MAX_FRAME_BYTES:
if raw[2] == 1 and len(raw) > 65536 or raw[2] == 2 and len(raw) > 1200:
return "invalid:frame_limit"
return "valid"
def main() -> None:
lines = (ROOT / "fixtures/conformance/datagram-v1.tsv").read_text(encoding="utf-8").splitlines()
assert lines[0] == "id\tversion\tkind\tinput\texpected"
for line in lines[1:]:
identifier, version, kind, input_value, expected = line.split("\t")
assert kind == "datagram" and version == "1"
encoded = input_value.removeprefix("hex=")
try:
actual = classify(binascii.unhexlify(encoded))
except binascii.Error:
actual = "invalid:hex"
assert actual == expected, f"{identifier}: {actual} != {expected}"
for fixture in ("datagram-v1.tsv", "datagram-v2.tsv"):
lines = (ROOT / "fixtures/conformance" / fixture).read_text(encoding="utf-8").splitlines()
assert lines[0] == "id\tversion\tkind\tinput\texpected"
for line in lines[1:]:
identifier, version, kind, input_value, expected = line.split("\t")
assert kind == "datagram" and version in ("1", "2")
encoded = input_value.removeprefix("hex=")
try:
actual = classify(binascii.unhexlify(encoded))
except binascii.Error:
actual = "invalid:hex"
assert actual == expected, f"{fixture}:{identifier}: {actual} != {expected}"
print("Datagram frame validation passed")