Files
ha-n95-local-control/internal/ctl/encode.go
T

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()
}