91 lines
2.2 KiB
Go
91 lines
2.2 KiB
Go
// Package ctl implements the com:ctl command envelope codec and the sid/cid
|
|
// correlation tracker from N95-FULL-SPECIFICATION.md §§6 and 14.
|
|
package ctl
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"strings"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// Namespace is the com:ctl query namespace.
|
|
const Namespace = "com:ctl"
|
|
|
|
// Attr is one attribute on the ctl element.
|
|
type Attr struct {
|
|
Name string
|
|
Value string
|
|
}
|
|
|
|
// Outbound describes one ctl command. Inner is an already-built XML fragment
|
|
// inserted verbatim; Envelope never re-escapes it. OmitCtlID encodes a ctl
|
|
// element without id, as Move requires.
|
|
type Outbound struct {
|
|
TD string
|
|
Attrs []Attr
|
|
Inner []byte
|
|
OmitCtlID bool
|
|
}
|
|
|
|
var (
|
|
sidSeq atomic.Uint64
|
|
cidSeq atomic.Uint64
|
|
)
|
|
|
|
// Envelope wraps out in the §6.1 iq-set envelope. It returns the compact
|
|
// stanza, the decimal stanza id sid, and the zero-padded 8-digit ctl id cid.
|
|
// cid is empty when out.OmitCtlID is set. Both ids come from process-wide
|
|
// counters so they are unique among outstanding operations.
|
|
func Envelope(from, to string, out Outbound) (stanza []byte, sid, cid string, err error) {
|
|
if from == "" || to == "" || out.TD == "" {
|
|
return nil, "", "", fmt.Errorf("ctl: envelope requires from, to and td")
|
|
}
|
|
sid = fmt.Sprintf("%d", sidSeq.Add(1))
|
|
|
|
var b strings.Builder
|
|
b.WriteString(`<iq id="`)
|
|
b.WriteString(escAttr(sid))
|
|
b.WriteString(`" to="`)
|
|
b.WriteString(escAttr(to))
|
|
b.WriteString(`" from="`)
|
|
b.WriteString(escAttr(from))
|
|
b.WriteString(`" type="set"><query xmlns="`)
|
|
b.WriteString(Namespace)
|
|
b.WriteString(`"><ctl td="`)
|
|
b.WriteString(escAttr(out.TD))
|
|
b.WriteString(`"`)
|
|
if !out.OmitCtlID {
|
|
cid = fmt.Sprintf("%08d", cidSeq.Add(1))
|
|
b.WriteString(` id="`)
|
|
b.WriteString(cid)
|
|
b.WriteString(`"`)
|
|
}
|
|
for _, a := range out.Attrs {
|
|
b.WriteString(` `)
|
|
b.WriteString(a.Name)
|
|
b.WriteString(`="`)
|
|
b.WriteString(escAttr(a.Value))
|
|
b.WriteString(`"`)
|
|
}
|
|
if len(out.Inner) == 0 {
|
|
b.WriteString(`/>`)
|
|
} else {
|
|
b.WriteString(`>`)
|
|
b.Write(out.Inner)
|
|
b.WriteString(`</ctl>`)
|
|
}
|
|
b.WriteString(`</query></iq>`)
|
|
return []byte(b.String()), sid, cid, nil
|
|
}
|
|
|
|
func EscapeAttr(s string) string {
|
|
return escAttr(s)
|
|
}
|
|
|
|
func escAttr(s string) string {
|
|
var b strings.Builder
|
|
_ = xml.EscapeText(&b, []byte(s))
|
|
return b.String()
|
|
}
|