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, `
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, `
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)
}
}