package extensions_test import ( "encoding/xml" "strings" "testing" "gosrc.io/xmpp/stanza" "dev.narayana.im/narayana/telegabber/xmpp/extensions" ) // parseMessage decodes a full stanza through the exact path telegabber's // component actually uses (stanza.NextPacket, requiring a top-level // jabber:component:accept namespace - confirmed by reading go-xmpp's // parser.go directly), not just a raw xml.Unmarshal of an isolated // struct - this is what actually proves the TypeRegistry dispatch works, // not just that my own struct tags happen to parse in isolation. func parseMessage(t *testing.T, xmlStr string) stanza.Message { t.Helper() dec := xml.NewDecoder(strings.NewReader(xmlStr)) pkt, err := stanza.NextPacket(dec) if err != nil { t.Fatalf("NextPacket: %v", err) } msg, ok := pkt.(stanza.Message) if !ok { t.Fatalf("expected stanza.Message, got %T", pkt) } return msg } func TestOMEMO0EncryptedRoundTrip(t *testing.T) { msg := parseMessage(t, `
a2V5MQ== a2V5Mg== aXY=
cGF5bG9hZA==
`) var enc extensions.OMEMO0Encrypted if !msg.Get(&enc) { t.Fatalf("expected to find OMEMO0Encrypted extension") } if enc.Namespace() != "eu.siacs.conversations.axolotl" { t.Fatalf("got namespace %q", enc.Namespace()) } if enc.Header.SID != 27183 { t.Fatalf("got sid %d, want 27183", enc.Header.SID) } if len(enc.Header.Keys) != 2 { t.Fatalf("expected 2 keys, got %d", len(enc.Header.Keys)) } if !enc.Header.Keys[0].PreKey { t.Fatalf("expected first key to be a prekey") } if enc.Header.Keys[1].PreKey { t.Fatalf("expected second key to not be a prekey") } if enc.Header.Keys[0].Text != "a2V5MQ==" { t.Fatalf("got key text %q", enc.Header.Keys[0].Text) } if enc.Header.IV != "aXY=" { t.Fatalf("got iv %q", enc.Header.IV) } if enc.Payload != "cGF5bG9hZA==" { t.Fatalf("got payload %q", enc.Payload) } } // TestOMEMOEncryptedNamespaceRecovered confirms the central design point // for OMEMOEncrypted: it is registered under BOTH urn:xmpp:omemo:1 and // urn:xmpp:omemo:2 (identical shape), and after decoding, XMLName.Space // correctly reflects whichever one an incoming stanza actually used - this // is what lets the decrypt hook later distinguish Omemo1 from Omemo2 // without needing two separate Go types. func TestOMEMOEncryptedNamespaceRecovered(t *testing.T) { for _, tc := range []struct { name string ns string }{ {"omemo1", "urn:xmpp:omemo:1"}, {"omemo2", "urn:xmpp:omemo:2"}, } { t.Run(tc.name, func(t *testing.T) { msg := parseMessage(t, `
a2V5
cGF5bG9hZA==
`) var enc extensions.OMEMOEncrypted if !msg.Get(&enc) { t.Fatalf("expected to find OMEMOEncrypted extension") } if enc.Namespace() != tc.ns { t.Fatalf("got namespace %q, want %q", enc.Namespace(), tc.ns) } if enc.Header.SID != 27183 { t.Fatalf("got sid %d, want 27183", enc.Header.SID) } if len(enc.Header.Keys) != 1 || enc.Header.Keys[0].JID != "juliet@capulet.lit" { t.Fatalf("got keys %+v", enc.Header.Keys) } if len(enc.Header.Keys[0].Keys) != 1 || !enc.Header.Keys[0].Keys[0].Kex { t.Fatalf("got inner keys %+v", enc.Header.Keys[0].Keys) } if enc.Header.Keys[0].Keys[0].Text != "a2V5" { t.Fatalf("got key text %q", enc.Header.Keys[0].Keys[0].Text) } if enc.Payload != "cGF5bG9hZA==" { t.Fatalf("got payload %q", enc.Payload) } }) } } func TestEMERoundTrip(t *testing.T) { msg := parseMessage(t, ` [This message is OMEMO end-to-end encrypted] `) var eme extensions.EME if !msg.Get(&eme) { t.Fatalf("expected to find EME extension") } if eme.Namespace() != "urn:xmpp:eme:0" { t.Fatalf("got namespace %q", eme.Namespace()) } if eme.EncryptionNamespace != "urn:xmpp:omemo:2" { t.Fatalf("got encryption namespace %q", eme.EncryptionNamespace) } if eme.Name != "OMEMO" { t.Fatalf("got name %q", eme.Name) } }