package handshake_test import ( "encoding/base64" "encoding/binary" "encoding/hex" "testing" "github.com/honeybbq/teamspeak-go/handshake" ) // Real-world TeamSpeak anonymous license captured from a live server handshake. const testLicenseBase64 = "AQBgjAAqtcBUrw5futTtkl3+EM3OW4Lal6OTPlwuv4xV/gIRFlEAG0Nl" + "AAcAAAAgQW5vbnltb3VzAACWSZf+Mjl5RT5mu4rvf8nhAZp9TjXO10XfGHQ9HQPtHiAYiqjtGItRrQ==" func decodeTestLicense(t *testing.T) []byte { t.Helper() data, err := base64.StdEncoding.DecodeString(testLicenseBase64) if err != nil { t.Fatalf("base64 decode failed: %v", err) } return data } func TestParseLicensesValid(t *testing.T) { data := decodeTestLicense(t) chain, err := handshake.ParseLicenses(data) if err != nil { t.Fatalf("ParseLicenses failed: %v", err) } if len(chain.Blocks) != 2 { t.Errorf("expected 2 blocks, got %d", len(chain.Blocks)) } } func TestParseLicensesEmptyInput(t *testing.T) { _, err := handshake.ParseLicenses([]byte{}) if err == nil { t.Error("expected error for empty input") } } func TestParseLicensesWrongVersion(t *testing.T) { // Version byte at index 0 must be 1 _, err := handshake.ParseLicenses([]byte{0x02, 0x00}) if err == nil { t.Error("expected error for unsupported version") } } func TestParseLicensesTooShortBlock(t *testing.T) { // Version OK but block data too short (< 42 bytes) data := make([]byte, 10) data[0] = 0x01 // valid version // remaining 9 bytes are not enough for a license block (needs 42) _, err := handshake.ParseLicenses(data) if err == nil { t.Error("expected error for truncated block") } } func TestDeriveKeyLength(t *testing.T) { data := decodeTestLicense(t) chain, err := handshake.ParseLicenses(data) if err != nil { t.Fatal(err) } key, err := chain.DeriveKey() if err != nil { t.Fatalf("DeriveKey failed: %v", err) } if len(key) != 32 { t.Errorf("expected 32-byte key, got %d bytes", len(key)) } } func TestDeriveKeyDeterministic(t *testing.T) { data := decodeTestLicense(t) chain, err := handshake.ParseLicenses(data) if err != nil { t.Fatal(err) } key1, err := chain.DeriveKey() if err != nil { t.Fatal(err) } key2, err := chain.DeriveKey() if err != nil { t.Fatal(err) } if hex.EncodeToString(key1) != hex.EncodeToString(key2) { t.Error("DeriveKey is not deterministic") } } func TestDeriveKeyNonZero(t *testing.T) { data := decodeTestLicense(t) chain, err := handshake.ParseLicenses(data) if err != nil { t.Fatal(err) } key, err := chain.DeriveKey() if err != nil { t.Fatal(err) } allZero := true for _, b := range key { if b != 0 { allZero = false break } } if allZero { t.Error("derived key should not be all zeros") } } func TestParseLicensesExpectedKeyKnownValue(t *testing.T) { // Known expected key derived from this specific anonymous license. const expectedKeyHex = "82a168e11f9f3e3496fbf8479cd3e17d9b0945e224a71fb371af619a256b8446" data := decodeTestLicense(t) chain, err := handshake.ParseLicenses(data) if err != nil { t.Fatal(err) } key, err := chain.DeriveKey() if err != nil { t.Fatal(err) } if got := hex.EncodeToString(key); got != expectedKeyHex { t.Errorf("DeriveKey = %s, want %s", got, expectedKeyHex) } } // TS5/TS6 server license block (type 8) tests // buildTs5LicenseBlob constructs a synthetic version-1 license containing a // single Ts5Server block (type 8) with the given properties. func buildTs5LicenseBlob(props [][]byte) []byte { // Block layout: // [0] key kind = 0 // [1:33] 32-byte Ed25519 public key (identity point) // [33] block type = 8 // [34:38] not valid before (BE uint32) // [38:42] not valid after (BE uint32) // [42] server license type // [43] property count // [44+] length-prefixed properties const headerSize = 44 totalPropsSize := 0 for _, p := range props { totalPropsSize += 1 + len(p) } block := make([]byte, headerSize, headerSize+totalPropsSize) block[0] = 0x00 block[1] = 0x01 // Ed25519 identity point (0,1) block[33] = 0x08 binary.BigEndian.PutUint32(block[34:38], 0x00000000) binary.BigEndian.PutUint32(block[38:42], 0x7FFFFFFF) block[42] = 7 block[43] = byte(len(props)) for _, p := range props { block = append(block, byte(len(p))) block = append(block, p...) } return append([]byte{0x01}, block...) // version prefix } func TestParseTs5ServerBlock(t *testing.T) { data := buildTs5LicenseBlob([][]byte{ []byte("issuer.example.com"), {0xDE, 0xAD}, }) chain, err := handshake.ParseLicenses(data) if err != nil { t.Fatalf("ParseLicenses failed: %v", err) } if len(chain.Blocks) != 1 { t.Errorf("expected 1 block, got %d", len(chain.Blocks)) } key, err := chain.DeriveKey() if err != nil { t.Fatalf("DeriveKey failed: %v", err) } if len(key) != 32 { t.Errorf("expected 32-byte key, got %d", len(key)) } } func TestParseTs5ServerBlockZeroProperties(t *testing.T) { data := buildTs5LicenseBlob(nil) chain, err := handshake.ParseLicenses(data) if err != nil { t.Fatalf("ParseLicenses failed: %v", err) } if len(chain.Blocks) != 1 { t.Errorf("expected 1 block, got %d", len(chain.Blocks)) } } func TestParseTs5ServerBlockTruncatedPropertyData(t *testing.T) { data := buildTs5LicenseBlob([][]byte{{0x01, 0x02, 0x03}}) // Chop off last byte so the property data is incomplete. data = data[:len(data)-1] _, err := handshake.ParseLicenses(data) if err == nil { t.Error("expected error for truncated property data") } } func TestParseTs5ServerBlockTruncatedPropertyLength(t *testing.T) { // Claim 2 properties but only provide 1. data := buildTs5LicenseBlob([][]byte{{0xAA}}) data[1+43] = 2 // override property count to 2 _, err := handshake.ParseLicenses(data) if err == nil { t.Error("expected error when property count exceeds available data") } }