feat(protocol): bind tunnel admission to device proof
This commit is contained in:
@@ -171,6 +171,7 @@ def generate_go(defs: dict[str, dict[str, Any]], schema_hash: str, version: str,
|
||||
"\"errors\"",
|
||||
"\"fmt\"",
|
||||
"\"reflect\"",
|
||||
"\"strings\"",
|
||||
"\"time\"",
|
||||
")",
|
||||
"",
|
||||
@@ -255,6 +256,16 @@ def generate_go(defs: dict[str, dict[str, Any]], schema_hash: str, version: str,
|
||||
"}",
|
||||
"",
|
||||
])
|
||||
out.extend([
|
||||
"func (v TunnelAdmissionRequest) DeviceAdmissionTranscript() []byte {",
|
||||
"\tfields := []string{v.SessionID, v.GatewayID, v.Audience, v.Grant, fmt.Sprintf(\"%d\", v.ReconnectSequence), v.ClientNonce, v.Capabilities.Transport, v.Capabilities.Framing, v.Capabilities.Media, v.Capabilities.Audio, v.Capabilities.SourceRateControl, v.Capabilities.ClientDecode}",
|
||||
"\tvar transcript strings.Builder",
|
||||
"\ttranscript.WriteString(\"versevdi/tunnel-admission/v1\")",
|
||||
"\tfor _, field := range fields { fmt.Fprintf(&transcript, \"%d:%s\", len(field), field) }",
|
||||
"\treturn []byte(transcript.String())",
|
||||
"}",
|
||||
"",
|
||||
])
|
||||
# Use io.EOF in generated code without making every generated decoder depend on
|
||||
# error-string comparison; replace the deliberately compact placeholder.
|
||||
text = "\n".join(out).replace('"errors"\n"fmt"', '"errors"\n"fmt"\n\"io"')
|
||||
@@ -395,6 +406,16 @@ def generate_rust(defs: dict[str, dict[str, Any]], schema_hash: str, compatibili
|
||||
if prop_name not in required:
|
||||
typ = f"Option<{typ}>"
|
||||
out.append(f" pub fn {field}(&self) -> &{typ} {{ &self.{field} }}")
|
||||
if name == "TunnelAdmissionRequest":
|
||||
out.extend([
|
||||
" pub fn device_admission_transcript(&self) -> Vec<u8> {",
|
||||
" let reconnect_sequence = self.reconnectSequence.to_string();",
|
||||
" let fields = [&self.sessionId, &self.gatewayId, &self.audience, &self.grant, &reconnect_sequence, &self.clientNonce, &self.capabilities.transport, &self.capabilities.framing, &self.capabilities.media, &self.capabilities.audio, &self.capabilities.sourceRateControl, &self.capabilities.clientDecode];",
|
||||
" let mut transcript = String::from(\"versevdi/tunnel-admission/v1\");",
|
||||
" for field in fields { transcript.push_str(&format!(\"{}:{}\", field.as_bytes().len(), field)); }",
|
||||
" transcript.into_bytes()",
|
||||
" }",
|
||||
])
|
||||
out.extend(["}", ""])
|
||||
out.extend([
|
||||
"pub fn intersect_capability_profiles(profiles: &[CapabilityProfile]) -> Result<CapabilityProfile, ValidationError> {",
|
||||
@@ -517,6 +538,15 @@ def generate_swift(defs: dict[str, dict[str, Any]], schema_hash: str, compatibil
|
||||
out.extend(swift_validation(definition))
|
||||
out.extend([" }", "", " public static func decodeJSON(_ data: Data) throws -> Self { try JSONDecoder().decode(Self.self, from: data) }", " public func encodeJSON() throws -> Data { try validate(); return try JSONEncoder().encode(self) }", "}", ""])
|
||||
out.extend([
|
||||
"public extension TunnelAdmissionRequest {",
|
||||
" func deviceAdmissionTranscript() -> Data {",
|
||||
" let fields = [sessionId, gatewayId, audience, grant, String(reconnectSequence), clientNonce, capabilities.transport, capabilities.framing, capabilities.media, capabilities.audio, capabilities.sourceRateControl, capabilities.clientDecode]",
|
||||
" var transcript = \"versevdi/tunnel-admission/v1\"",
|
||||
" for field in fields { transcript += \"\\(field.utf8.count):\\(field)\" }",
|
||||
" return Data(transcript.utf8)",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
"public extension CapabilityProfile {",
|
||||
" static func intersection(_ profiles: [CapabilityProfile]) throws -> CapabilityProfile {",
|
||||
" guard let selected = profiles.first else { throw ContractValidationError(field: \"capabilities\", code: \"no_overlap\") }",
|
||||
|
||||
@@ -38,9 +38,13 @@ let capability = try CapabilityProfile(
|
||||
let request = try TunnelAdmissionRequest(
|
||||
version: "1", sessionId: "session", gatewayId: "gateway", audience: "audience",
|
||||
grant: String(repeating: "g", count: 43), reconnectSequence: 0,
|
||||
clientNonce: String(repeating: "n", count: 16), capabilities: capability
|
||||
clientNonce: String(repeating: "n", count: 16), deviceSignature: String(repeating: "s", count: 86), capabilities: capability
|
||||
)
|
||||
_ = request
|
||||
let transcript = "versevdi/tunnel-admission/v17:session7:gateway8:audience43:" + String(repeating: "g", count: 43) + "1:016:" + String(repeating: "n", count: 16) + "10:quic-tls1311:datagram-v17:encoded7:encoded6:server9:h264-opus"
|
||||
guard String(data: request.deviceAdmissionTranscript(), encoding: .utf8) == transcript else {
|
||||
fatalError("unexpected device admission transcript")
|
||||
}
|
||||
let incompatible = try CapabilityProfile(
|
||||
transport: "quic-tls13", framing: "datagram-v1", media: "encoded",
|
||||
audio: "encoded", sourceRateControl: "server", clientDecode: "hevc-opus"
|
||||
@@ -97,17 +101,25 @@ fn main() {
|
||||
"quic-tls13".into(), "datagram-v1".into(), "encoded".into(),
|
||||
"encoded".into(), "server".into(), "h264-opus".into(),
|
||||
).unwrap();
|
||||
let request = TunnelAdmissionRequest::new(
|
||||
"1".into(), "session".into(), "gateway".into(), "audience".into(),
|
||||
"g".repeat(43), 0, "n".repeat(16), "s".repeat(86), capabilities.clone(),
|
||||
).unwrap();
|
||||
let transcript = "versevdi/tunnel-admission/v17:session7:gateway8:audience43:".to_string()
|
||||
+ &"g".repeat(43) + "1:016:" + &"n".repeat(16)
|
||||
+ "10:quic-tls1311:datagram-v17:encoded7:encoded6:server9:h264-opus";
|
||||
assert_eq!(request.device_admission_transcript(), transcript.into_bytes());
|
||||
assert!(TunnelAdmissionRequest::new(
|
||||
"2".into(), "session".into(), "gateway".into(), "audience".into(),
|
||||
"g".repeat(43), 0, "n".repeat(16), capabilities.clone(),
|
||||
"g".repeat(43), 0, "n".repeat(16), "s".repeat(86), capabilities.clone(),
|
||||
).is_err());
|
||||
assert!(TunnelAdmissionRequest::new(
|
||||
"0".into(), "session".into(), "gateway".into(), "audience".into(),
|
||||
"g".repeat(43), 0, "n".repeat(16), capabilities.clone(),
|
||||
"g".repeat(43), 0, "n".repeat(16), "s".repeat(86), capabilities.clone(),
|
||||
).is_err());
|
||||
assert!(TunnelAdmissionRequest::new(
|
||||
"1".into(), "session".into(), "gateway".into(), "audience".into(),
|
||||
"g".repeat(43), 0, "short".into(), capabilities.clone(),
|
||||
"g".repeat(43), 0, "short".into(), "s".repeat(86), capabilities.clone(),
|
||||
).is_err());
|
||||
assert!(intersect_capability_profiles(&[capabilities.clone(), capabilities.clone()]).is_ok());
|
||||
let incompatible = CapabilityProfile::new(
|
||||
|
||||
Reference in New Issue
Block a user