feat(protocol): define gateway control envelopes
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
protocol "git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol/gen/go/protocol"
|
||||
)
|
||||
@@ -87,9 +88,9 @@ func evaluate(kind, input string) string {
|
||||
Gateway: protocol.ManifestGateway{
|
||||
ID: parts["gateway_id"], Addresses: []string{"gateway.control.test:443"}, PublicIdentity: parts["gateway_id"],
|
||||
},
|
||||
Tunnel: protocol.ManifestTunnel{Versions: []string{parts["protocol"] + "/1"}, Features: []string{"control.v1"}},
|
||||
Profile: protocol.ManifestProfile{ID: "standard", Bounds: protocol.ManifestBounds{MinimumKbps: 1, TargetKbps: 2, MaximumKbps: 3}},
|
||||
Grant: protocol.GrantReference{OpaqueValue: parts["grant"], ExpiresAt: parts["expires_at"], Audience: parts["audience"]},
|
||||
Tunnel: protocol.ManifestTunnel{Versions: []string{parts["protocol"] + "/1"}, Features: []string{"control.v1"}},
|
||||
Profile: protocol.ManifestProfile{ID: "standard", Bounds: protocol.ManifestBounds{MinimumKbps: 1, TargetKbps: 2, MaximumKbps: 3}},
|
||||
Grant: protocol.GrantReference{OpaqueValue: parts["grant"], ExpiresAt: parts["expires_at"], Audience: parts["audience"]},
|
||||
CorrelationID: "correlation-1",
|
||||
}
|
||||
if value.Validate() == nil {
|
||||
@@ -118,7 +119,7 @@ func evaluate(kind, input string) string {
|
||||
}
|
||||
value := protocol.EventEnvelope{
|
||||
EventID: "event-1", Sequence: sequence, Type: "broker.session.changed", Version: 1,
|
||||
Resource: protocol.ResourceLink{Type: "broker_session", ID: "session-1", Version: 1},
|
||||
Resource: protocol.ResourceLink{Type: "broker_session", ID: "session-1", Version: 1},
|
||||
OccurredAt: "2099-01-01T00:00:00Z", CorrelationID: parts["correlation_id"], Payload: map[string]any{},
|
||||
}
|
||||
if payloadErr != nil || payloadBytes > 16384 {
|
||||
@@ -138,11 +139,178 @@ func evaluate(kind, input string) string {
|
||||
return "invalid:unsupported_version"
|
||||
case "datagram":
|
||||
return classifyDatagram(parts["hex"])
|
||||
case "gateway_input":
|
||||
return classifyGatewayInput(parts["hex"])
|
||||
case "gateway_feedback":
|
||||
return classifyGatewayFeedback(parts["hex"])
|
||||
case "gateway_clipboard":
|
||||
if _, hasFile := parts["file"]; hasFile {
|
||||
return "invalid:forbidden"
|
||||
}
|
||||
value := protocol.GatewayClipboardText{Direction: parts["direction"], Text: parts["text"], Encoding: parts["encoding"], LoopToken: parts["loop_token"]}
|
||||
if value.Validate() != nil {
|
||||
return "invalid:clipboard"
|
||||
}
|
||||
return "valid"
|
||||
case "gateway_clipboard_audit":
|
||||
if _, hasText := parts["text"]; hasText {
|
||||
return "invalid:forbidden"
|
||||
}
|
||||
textBytes, err := strconv.ParseInt(parts["text_bytes"], 10, 64)
|
||||
if err != nil {
|
||||
return "invalid:clipboard_audit"
|
||||
}
|
||||
value := protocol.GatewayClipboardAudit{Version: "1", SessionID: "fixture-session", Direction: parts["direction"], Outcome: parts["outcome"], TextBytes: textBytes, Reason: parts["reason"]}
|
||||
if value.Validate() != nil {
|
||||
return "invalid:clipboard_audit"
|
||||
}
|
||||
return "valid"
|
||||
default:
|
||||
return "invalid:unknown_kind"
|
||||
}
|
||||
}
|
||||
|
||||
func decodeGatewayHex(encoded string) ([]byte, string) {
|
||||
raw, err := hex.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return nil, "invalid:hex"
|
||||
}
|
||||
return raw, ""
|
||||
}
|
||||
|
||||
func classifyGatewayInput(encoded string) string {
|
||||
raw, invalid := decodeGatewayHex(encoded)
|
||||
if invalid != "" {
|
||||
return invalid
|
||||
}
|
||||
if len(raw) < 6 {
|
||||
return "invalid:truncated"
|
||||
}
|
||||
if string(raw[:4]) != "VGI1" {
|
||||
return "invalid:magic"
|
||||
}
|
||||
kind, length := raw[4], int(raw[5])
|
||||
if len(raw) != 6+length {
|
||||
return "invalid:length"
|
||||
}
|
||||
body := raw[6:]
|
||||
switch kind {
|
||||
case 1:
|
||||
if len(body) != 4 || body[0] > 1 || (body[2] == 0 && body[3] == 0) {
|
||||
return "invalid:field"
|
||||
}
|
||||
case 2:
|
||||
if len(body) != 3 {
|
||||
return "invalid:length"
|
||||
}
|
||||
if body[0] > 1 || body[1] < 1 || body[1] > 5 {
|
||||
return "invalid:field"
|
||||
}
|
||||
if body[2] != 0 {
|
||||
return "invalid:reserved"
|
||||
}
|
||||
case 3:
|
||||
if len(body) != 4 {
|
||||
return "invalid:length"
|
||||
}
|
||||
case 4:
|
||||
if len(body) < 1 || len(body) > 4 || !utf8.Valid(body) || utf8.RuneCount(body) != 1 {
|
||||
return "invalid:utf8"
|
||||
}
|
||||
case 5:
|
||||
if len(body) != 17 {
|
||||
return "invalid:length"
|
||||
}
|
||||
if body[0] > 15 {
|
||||
return "invalid:field"
|
||||
}
|
||||
if body[1] == 0 && body[2] == 0 {
|
||||
for _, value := range body[3:] {
|
||||
if value != 0 {
|
||||
return "invalid:field"
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return "invalid:kind"
|
||||
}
|
||||
return "valid"
|
||||
}
|
||||
|
||||
func classifyGatewayFeedback(encoded string) string {
|
||||
raw, invalid := decodeGatewayHex(encoded)
|
||||
if invalid != "" {
|
||||
return invalid
|
||||
}
|
||||
if len(raw) < 8 {
|
||||
return "invalid:truncated"
|
||||
}
|
||||
if string(raw[:4]) != "VGF1" {
|
||||
return "invalid:magic"
|
||||
}
|
||||
direction, kind := raw[4], raw[5]
|
||||
if len(raw) != 8+(int(raw[6])<<8)+int(raw[7]) {
|
||||
return "invalid:length"
|
||||
}
|
||||
if direction != 0 && direction != 1 {
|
||||
return "invalid:direction"
|
||||
}
|
||||
body := raw[8:]
|
||||
if direction == 0 {
|
||||
if kind >= 0x10 && kind <= 0x12 {
|
||||
return "invalid:direction"
|
||||
}
|
||||
switch kind {
|
||||
case 1:
|
||||
if len(body) == 0 {
|
||||
return "valid"
|
||||
}
|
||||
case 2:
|
||||
if validFECStatus(body) {
|
||||
return "valid"
|
||||
}
|
||||
return "invalid:field"
|
||||
default:
|
||||
return "invalid:type"
|
||||
}
|
||||
return "invalid:length"
|
||||
}
|
||||
if kind == 1 || kind == 2 {
|
||||
return "invalid:direction"
|
||||
}
|
||||
switch kind {
|
||||
case 0x10:
|
||||
if len(body) == 4 {
|
||||
return "valid"
|
||||
}
|
||||
return "invalid:length"
|
||||
case 0x11:
|
||||
if len(body) != 5 {
|
||||
return "invalid:length"
|
||||
}
|
||||
if body[0] <= 15 {
|
||||
return "valid"
|
||||
}
|
||||
case 0x12:
|
||||
if len(body) != 1 {
|
||||
return "invalid:length"
|
||||
}
|
||||
if body[0] <= 1 {
|
||||
return "valid"
|
||||
}
|
||||
default:
|
||||
return "invalid:type"
|
||||
}
|
||||
return "invalid:field"
|
||||
}
|
||||
|
||||
func validFECStatus(body []byte) bool {
|
||||
if len(body) != 21 || int(body[10])<<8|int(body[11]) == 0 || int(body[14])<<8|int(body[15]) > int(body[10])<<8|int(body[11]) || int(body[16])<<8|int(body[17]) > int(body[12])<<8|int(body[13]) || body[18] > 100 || body[20] == 0 || body[19] >= body[20] {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func classifyDatagram(encoded string) string {
|
||||
raw, err := hex.DecodeString(encoded)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user