feat(protocol): define gateway control envelopes
This commit is contained in:
@@ -30,18 +30,100 @@ func evaluate(_ kind: String, _ input: String) -> String {
|
||||
if ["1", "0", "-1"].contains(values["offered"] ?? "") && values["feature"] == "control.v1" { return "valid" }
|
||||
return values["feature"] == "control.v1" ? "invalid:unsupported_version" : "invalid:unsupported_feature"
|
||||
case "datagram": return classifyDatagram(values["hex"] ?? "")
|
||||
case "gateway_input": return classifyGatewayInput(values["hex"] ?? "")
|
||||
case "gateway_feedback": return classifyGatewayFeedback(values["hex"] ?? "")
|
||||
case "gateway_clipboard":
|
||||
if values["file"] != nil { return "invalid:forbidden" }
|
||||
guard ["client_to_provider", "provider_to_client"].contains(values["direction"] ?? ""), values["encoding"] == "utf-8", let token = values["loop_token"], (16...128).contains(token.utf8.count), let text = values["text"], text.utf8.count <= 65536 else { return "invalid:clipboard" }
|
||||
return "valid"
|
||||
case "gateway_clipboard_audit":
|
||||
if values["text"] != nil { return "invalid:forbidden" }
|
||||
guard ["client_to_provider", "provider_to_client"].contains(values["direction"] ?? ""), ["forwarded", "suppressed", "rejected"].contains(values["outcome"] ?? ""), ["forwarded", "loop", "policy", "rate", "provider", "malformed"].contains(values["reason"] ?? ""), let textBytes = Int(values["text_bytes"] ?? ""), (0...65536).contains(textBytes) else { return "invalid:clipboard_audit" }
|
||||
return "valid"
|
||||
default: return "invalid:unknown_kind"
|
||||
}
|
||||
}
|
||||
|
||||
func classifyDatagram(_ encoded: String) -> String {
|
||||
func decodeHex(_ encoded: String) -> [UInt8]? {
|
||||
let characters = Array(encoded)
|
||||
guard characters.count % 2 == 0 else { return "invalid:hex" }
|
||||
guard characters.count % 2 == 0 else { return nil }
|
||||
var raw: [UInt8] = []
|
||||
for index in stride(from: 0, to: characters.count, by: 2) {
|
||||
guard let byte = UInt8(String(characters[index...index + 1]), radix: 16) else { return "invalid:hex" }
|
||||
guard let byte = UInt8(String(characters[index...index + 1]), radix: 16) else { return nil }
|
||||
raw.append(byte)
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
func classifyGatewayInput(_ encoded: String) -> String {
|
||||
guard let raw = decodeHex(encoded) else { return "invalid:hex" }
|
||||
guard raw.count >= 6 else { return "invalid:truncated" }
|
||||
guard Array(raw[0..<4]) == Array("VGI1".utf8) else { return "invalid:magic" }
|
||||
let kind = raw[4]
|
||||
let body = Array(raw.dropFirst(6))
|
||||
guard body.count == Int(raw[5]) else { return "invalid:length" }
|
||||
switch kind {
|
||||
case 1:
|
||||
return body.count == 4 && body[0] <= 1 && (body[2] != 0 || body[3] != 0) ? "valid" : "invalid:field"
|
||||
case 2:
|
||||
guard body.count == 3 else { return "invalid:length" }
|
||||
guard body[0] <= 1 && (1...5).contains(body[1]) else { return "invalid:field" }
|
||||
return body[2] == 0 ? "valid" : "invalid:reserved"
|
||||
case 3: return body.count == 4 ? "valid" : "invalid:length"
|
||||
case 4:
|
||||
guard (1...4).contains(body.count), let scalar = String(bytes: body, encoding: .utf8), scalar.unicodeScalars.count == 1 else { return "invalid:utf8" }
|
||||
return "valid"
|
||||
case 5:
|
||||
guard body.count == 17 else { return "invalid:length" }
|
||||
guard body[0] <= 15 else { return "invalid:field" }
|
||||
guard body[1] != 0 || body[2] != 0 || body.dropFirst(3).allSatisfy({ $0 == 0 }) else { return "invalid:field" }
|
||||
return "valid"
|
||||
default: return "invalid:kind"
|
||||
}
|
||||
}
|
||||
|
||||
func classifyGatewayFeedback(_ encoded: String) -> String {
|
||||
guard let raw = decodeHex(encoded) else { return "invalid:hex" }
|
||||
guard raw.count >= 8 else { return "invalid:truncated" }
|
||||
guard Array(raw[0..<4]) == Array("VGF1".utf8) else { return "invalid:magic" }
|
||||
let direction = raw[4]
|
||||
let kind = raw[5]
|
||||
let body = Array(raw.dropFirst(8))
|
||||
guard body.count == Int(raw[6]) * 256 + Int(raw[7]) else { return "invalid:length" }
|
||||
guard direction <= 1 else { return "invalid:direction" }
|
||||
if direction == 0 {
|
||||
if (0x10...0x12).contains(kind) { return "invalid:direction" }
|
||||
switch kind {
|
||||
case 1: return body.isEmpty ? "valid" : "invalid:length"
|
||||
case 2:
|
||||
return validFECStatus(body) ? "valid" : "invalid:field"
|
||||
default: return "invalid:type"
|
||||
}
|
||||
}
|
||||
if kind == 1 || kind == 2 { return "invalid:direction" }
|
||||
switch kind {
|
||||
case 0x10: return body.count == 4 ? "valid" : "invalid:length"
|
||||
case 0x11:
|
||||
guard body.count == 5 else { return "invalid:length" }
|
||||
return body[0] <= 15 ? "valid" : "invalid:field"
|
||||
case 0x12:
|
||||
guard body.count == 1 else { return "invalid:length" }
|
||||
return body[0] <= 1 ? "valid" : "invalid:field"
|
||||
default: return "invalid:type"
|
||||
}
|
||||
}
|
||||
|
||||
func validFECStatus(_ body: [UInt8]) -> Bool {
|
||||
guard body.count == 21 else { return false }
|
||||
let totalData = Int(body[10]) * 256 + Int(body[11])
|
||||
let totalParity = Int(body[12]) * 256 + Int(body[13])
|
||||
let receivedData = Int(body[14]) * 256 + Int(body[15])
|
||||
let receivedParity = Int(body[16]) * 256 + Int(body[17])
|
||||
return totalData > 0 && receivedData <= totalData && receivedParity <= totalParity && body[18] <= 100 && body[20] > 0 && body[19] < body[20]
|
||||
}
|
||||
|
||||
func classifyDatagram(_ encoded: String) -> String {
|
||||
guard let raw = decodeHex(encoded) else { return "invalid:hex" }
|
||||
guard raw.count >= 21 else { return "invalid:truncated" }
|
||||
guard raw[0] == 0x56 && raw[1] == 0x44 else { return "invalid:magic" }
|
||||
guard raw[2] == 1 else { return "invalid:unsupported_version" }
|
||||
|
||||
Reference in New Issue
Block a user