protocol: add generated bindings and conformance fixtures
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
protocol "github.com/sechmachine/VerseVDI-Protocol/gen/go/protocol"
|
||||
)
|
||||
|
||||
const (
|
||||
datagramHeaderBytes = 21
|
||||
maximumFrameBytes = 65536
|
||||
)
|
||||
|
||||
func main() {
|
||||
entries, err := os.ReadDir("fixtures/conformance")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var results []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || filepath.Ext(entry.Name()) != ".tsv" {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join("fixtures/conformance", entry.Name())
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSuffix(string(data), "\n"), "\n")
|
||||
if len(lines) == 0 || lines[0] != "id\tversion\tkind\tinput\texpected" {
|
||||
panic("invalid fixture header")
|
||||
}
|
||||
for _, line := range lines[1:] {
|
||||
fields := strings.Split(line, "\t")
|
||||
if len(fields) != 5 {
|
||||
panic("invalid fixture row")
|
||||
}
|
||||
actual := evaluate(fields[2], fields[3])
|
||||
if actual != fields[4] {
|
||||
panic(fmt.Sprintf("%s: got %s want %s", fields[0], actual, fields[4]))
|
||||
}
|
||||
results = append(results, fields[0]+"\t"+actual)
|
||||
}
|
||||
}
|
||||
fixtureHash := readFixtureHash()
|
||||
fmt.Printf("Go conformance passed normalized=%s fixtures=%s\n", normalizedDigest(results), fixtureHash)
|
||||
}
|
||||
|
||||
func evaluate(kind, input string) string {
|
||||
parts := map[string]string{}
|
||||
for _, item := range strings.Split(input, ";") {
|
||||
pair := strings.SplitN(item, "=", 2)
|
||||
if len(pair) == 2 {
|
||||
parts[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
switch kind {
|
||||
case "version":
|
||||
if input == "1" || input == "0" || input == "-1" {
|
||||
return "valid"
|
||||
}
|
||||
return "invalid:unsupported_version"
|
||||
case "page":
|
||||
limit, err := strconv.Atoi(parts["limit"])
|
||||
value := protocol.PageInfo{Limit: int64(limit), NextCursor: parts["cursor"]}
|
||||
if err == nil && value.Validate() == nil {
|
||||
return "valid"
|
||||
}
|
||||
return "invalid:invalid_limit"
|
||||
case "manifest":
|
||||
forbidden := []string{"provider_url", "vm_address", "password", "private_key"}
|
||||
for _, key := range forbidden {
|
||||
if _, ok := parts[key]; ok {
|
||||
return "invalid:forbidden_field"
|
||||
}
|
||||
}
|
||||
value := protocol.ConnectionManifest{
|
||||
Version: parts["version"], Purpose: parts["purpose"], SessionID: "session-1",
|
||||
ReconnectSequence: 0,
|
||||
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"]},
|
||||
CorrelationID: "correlation-1",
|
||||
}
|
||||
if value.Validate() == nil {
|
||||
return "valid"
|
||||
}
|
||||
return "invalid:invalid_manifest"
|
||||
case "clipboard":
|
||||
_, hasFile := parts["file"]
|
||||
value := protocol.ClipboardText{Text: parts["text"], Encoding: parts["encoding"]}
|
||||
if !hasFile && value.Validate() == nil {
|
||||
return "valid"
|
||||
}
|
||||
return "invalid:unsupported_clipboard"
|
||||
case "event":
|
||||
sequence, sequenceErr := strconv.ParseInt(parts["sequence"], 10, 64)
|
||||
payloadBytes, payloadErr := strconv.Atoi(parts["payload_bytes"])
|
||||
if parts["version"] != "1" {
|
||||
return "invalid:unsupported_version"
|
||||
}
|
||||
if parts["after"] != "" && parts["earliest"] != "" {
|
||||
after, afterErr := strconv.ParseInt(parts["after"], 10, 64)
|
||||
earliest, earliestErr := strconv.ParseInt(parts["earliest"], 10, 64)
|
||||
if afterErr == nil && earliestErr == nil && after > 0 && earliest > 0 && after < earliest-1 {
|
||||
return "invalid:gap"
|
||||
}
|
||||
}
|
||||
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},
|
||||
OccurredAt: "2099-01-01T00:00:00Z", CorrelationID: parts["correlation_id"], Payload: map[string]any{},
|
||||
}
|
||||
if payloadErr != nil || payloadBytes > 16384 {
|
||||
return "invalid:payload_limit"
|
||||
}
|
||||
if sequenceErr != nil || value.Validate() != nil {
|
||||
return "invalid:required"
|
||||
}
|
||||
return "valid"
|
||||
case "tunnel":
|
||||
if (parts["offered"] == "1" || parts["offered"] == "0" || parts["offered"] == "-1") && parts["feature"] == "control.v1" {
|
||||
return "valid"
|
||||
}
|
||||
if parts["feature"] != "control.v1" {
|
||||
return "invalid:unsupported_feature"
|
||||
}
|
||||
return "invalid:unsupported_version"
|
||||
case "datagram":
|
||||
return classifyDatagram(parts["hex"])
|
||||
default:
|
||||
return "invalid:unknown_kind"
|
||||
}
|
||||
}
|
||||
|
||||
func classifyDatagram(encoded string) string {
|
||||
raw, err := hex.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return "invalid:hex"
|
||||
}
|
||||
if len(raw) < datagramHeaderBytes {
|
||||
return "invalid:truncated"
|
||||
}
|
||||
if string(raw[:2]) != "VD" {
|
||||
return "invalid:magic"
|
||||
}
|
||||
if raw[2] != 1 {
|
||||
return "invalid:unsupported_version"
|
||||
}
|
||||
limits := map[byte]int{1: 1024, 2: 2048, 3: 65515}
|
||||
limit, ok := limits[raw[3]]
|
||||
if !ok {
|
||||
return "invalid:unknown_channel"
|
||||
}
|
||||
if raw[4] != 0 {
|
||||
return "invalid:flags"
|
||||
}
|
||||
if raw[18] == 0 || raw[17] >= raw[18] {
|
||||
return "invalid:fragment"
|
||||
}
|
||||
payloadLength := int(raw[19])<<8 | int(raw[20])
|
||||
if payloadLength > limit {
|
||||
return "invalid:payload_limit"
|
||||
}
|
||||
if len(raw) != datagramHeaderBytes+payloadLength {
|
||||
return "invalid:length_mismatch"
|
||||
}
|
||||
if len(raw) > maximumFrameBytes {
|
||||
return "invalid:frame_limit"
|
||||
}
|
||||
return "valid"
|
||||
}
|
||||
|
||||
func normalizedDigest(results []string) string {
|
||||
const offset = uint64(14695981039346656037)
|
||||
const prime = uint64(1099511628211)
|
||||
value := offset
|
||||
for _, result := range results {
|
||||
for _, byteValue := range []byte(result + "\n") {
|
||||
value ^= uint64(byteValue)
|
||||
value *= prime
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%016x", value)
|
||||
}
|
||||
|
||||
func readFixtureHash() string {
|
||||
data, err := os.ReadFile("fixtures/manifest.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var manifest struct {
|
||||
CorpusSHA256 string `json:"corpus_sha256"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &manifest); err != nil || len(manifest.CorpusSHA256) != sha256.Size*2 {
|
||||
panic("invalid fixture manifest")
|
||||
}
|
||||
return manifest.CorpusSHA256
|
||||
}
|
||||
Reference in New Issue
Block a user