Documentation
¶
Overview ¶
Package wbxml encodes and decodes the WAP Binary XML (WBXML 1.3) format as profiled by Microsoft Exchange ActiveSync (MS-ASWBXML).
EAS uses WBXML for the body of every command except Ping and OPTIONS. Each EAS namespace lives in a numbered "code page"; tag names are encoded as one-byte tokens whose meaning depends on the active code page. The SWITCH_PAGE control token changes the active page mid-stream.
This package handles encoding (Marshal) and decoding (Unmarshal) of documents and exposes a Registry of named code pages so that callers can add or replace pages as the EAS spec evolves. EAS does not use WBXML attributes, processing instructions, the LITERAL family, or string-table references, so the codec deliberately omits support for them — they would be dead code.
More ¶
See README.md in this directory for the wire-format diagrams, the full code-page table, and tree-walking examples. Or browse the full godoc.
Index ¶
Examples ¶
Constants ¶
const ( PageAirSync byte = 0 PageContacts byte = 1 PageEmail byte = 2 PageCalendar byte = 4 PageMove byte = 5 PageGetItemEstimate byte = 6 PageFolderHierarchy byte = 7 PageMeetingResponse byte = 8 PageTasks byte = 9 PageResolveRecipients byte = 10 PageValidateCert byte = 11 PageContacts2 byte = 12 PagePing byte = 13 PageProvision byte = 14 PageSearch byte = 15 PageGAL byte = 16 PageAirSyncBase byte = 17 PageSettings byte = 18 PageDocumentLibrary byte = 19 PageItemOperations byte = 20 PageComposeMail byte = 21 PageEmail2 byte = 22 PageNotes byte = 23 PageRightsManagement byte = 24 PageFind byte = 25 )
EAS code page numbers.
Variables ¶
This section is empty.
Functions ¶
func Marshal ¶
Marshal serializes a Document to the WBXML byte stream the EAS server expects. The Registry must define every code page referenced by the tree.
Example ¶
Marshal turns a Document tree into the raw WBXML byte stream an EAS server expects. Build the tree with the wbxml.E helper.
The 13-byte output decodes as:
03 01 6a 00 header: WBXML 1.3, public id 1, charset 0x6A (UTF-8), empty string table 00 07 SWITCH_PAGE to FolderHierarchy (page 7) 56 FolderSync (id 0x16) | content flag (0x40) 52 SyncKey (id 0x12) | content flag (0x40) 03 30 00 STR_I "0" terminated by NUL 01 01 END SyncKey, END FolderSync
package main
import (
"fmt"
"github.com/hstern/go-activesync/wbxml"
)
func main() {
doc := &wbxml.Document{
Root: wbxml.E(wbxml.PageFolderHierarchy, "FolderSync",
wbxml.E(wbxml.PageFolderHierarchy, "SyncKey", wbxml.Text("0")),
),
}
out, err := wbxml.Marshal(doc, wbxml.DefaultRegistry())
if err != nil {
panic(err)
}
fmt.Printf("% x\n", out)
}
Output: 03 01 6a 00 00 07 56 52 03 30 00 01 01
Types ¶
type Codepage ¶
type Codepage struct {
// Number is the page identifier emitted in SWITCH_PAGE.
Number byte
// Name is a human-readable label used in error messages
// ("AirSync", "Provision", ...).
Name string
// Tags maps tag identity (0x05..0x3F) → tag name.
Tags map[byte]string
}
Codepage describes one EAS namespace and the tag tokens defined in it.
Tag identities live in 0x05..0x3F. Lower values (0x00..0x04) are reserved for global control tokens (SWITCH_PAGE, END, STR_I, etc.) and values above 0x3F are reserved for the C/A flag bits used to mark "has content" / "has attributes" on a tag byte.
type Document ¶
type Document struct {
// Version is the WBXML version byte (defaults to 0x03 = WBXML 1.3).
Version byte
// PublicID identifies the document type. EAS always uses 0x01 ("unknown,
// look up via charset"). Any nonzero value is preserved on round-trip.
PublicID uint32
// Charset identifies the character encoding (defaults to 0x6A = UTF-8,
// which is what EAS mandates).
Charset uint32
// Root is the document's single root element.
Root *Element
}
Document is a complete WBXML document: header fields plus a single root element. Only Root is significant when constructing a request; the header fields default to their EAS-standard values during Marshal.
func Unmarshal ¶
Unmarshal parses a WBXML byte stream into a Document. The Registry must define every code page referenced by the stream — unknown tag tokens are reported with byte offsets so fixtures can be debugged against the wire.
Example ¶
Unmarshal parses a WBXML byte stream back into a Document tree. Use Find / FindAll / TextContent to walk the result.
package main
import (
"fmt"
"github.com/hstern/go-activesync/wbxml"
)
func main() {
// A minimal FolderSync response: <FolderSync><Status>1</Status>
// <SyncKey>42</SyncKey></FolderSync>
raw := []byte{
0x03, 0x01, 0x6a, 0x00, // header
0x00, 0x07, // SWITCH_PAGE FolderHierarchy
0x56, // FolderSync (content)
0x4c, 0x03, '1', 0x00, 0x01, // Status (content) STR_I "1" END
0x52, 0x03, '4', '2', 0x00, 0x01, // SyncKey (content) STR_I "42" END
0x01, // END FolderSync
}
doc, err := wbxml.Unmarshal(raw, wbxml.DefaultRegistry())
if err != nil {
panic(err)
}
fmt.Println("status:", doc.Root.Find("Status").TextContent())
fmt.Println("synckey:", doc.Root.Find("SyncKey").TextContent())
}
Output: status: 1 synckey: 42
type Element ¶
type Element struct {
// Codepage is the EAS code page the tag lives in (0 = AirSync, 7 =
// FolderHierarchy, etc.). The encoder emits SWITCH_PAGE tokens when this
// changes between sibling elements; the decoder records the active page
// at decode time.
Codepage byte
// Name is the tag name (e.g. "Sync", "FolderSync").
Name string
// Children is the ordered list of child nodes.
Children []Node
}
Element is a tagged node identified by (Codepage, Name).
func E ¶
E constructs a new Element with the given page, name, and children. Convenience for building request documents in tests and command builders:
root := wbxml.E(wbxml.PageFolderHierarchy, "FolderSync",
wbxml.E(wbxml.PageFolderHierarchy, "SyncKey", wbxml.Text("0")),
)
func (*Element) Find ¶
Find returns the first descendant element with the given name, or nil. Search is depth-first, pre-order. Codepage is not matched (EAS tag names are unique within the spec given the relevant page context).
Example ¶
Find walks descendants depth-first by tag name. Use FindAll for every match in document order, or iterate Element.Children directly when the same name is ambiguous across code pages.
package main
import (
"fmt"
"github.com/hstern/go-activesync/wbxml"
)
func main() {
root := wbxml.E(wbxml.PageAirSync, "Sync",
wbxml.E(wbxml.PageAirSync, "Collections",
wbxml.E(wbxml.PageAirSync, "Collection",
wbxml.E(wbxml.PageAirSync, "SyncKey", wbxml.Text("abc")),
wbxml.E(wbxml.PageAirSync, "CollectionId", wbxml.Text("inbox")),
),
),
)
fmt.Println(root.Find("CollectionId").TextContent())
}
Output: inbox
func (*Element) FindAll ¶
FindAll returns all descendant elements with the given name in document order (pre-order traversal).
func (*Element) TextContent ¶
TextContent returns the concatenation of all top-level Text children. Most EAS scalar elements (status codes, sync keys, IDs) use a single Text child; this helper avoids open-coding the type assertion at every call site.
type Node ¶
type Node interface {
// contains filtered or unexported methods
}
Node is anything that may appear as a child of an Element: another Element, Text content, or Opaque (raw byte) content.
EAS rarely mixes these inside the same parent — most elements have either child elements, a single Text child, or a single Opaque child — but the encoding allows them to be freely interleaved and the decoder preserves whatever it sees.
type Opaque ¶
type Opaque []byte
Opaque is raw byte content (encoded as the OPAQUE token in WBXML). EAS uses opaque content for MIME bodies in AirSyncBase elements.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps (page, identity) ↔ tag name. A registry is required for both encode (tag name → identity) and decode (identity → tag name).
Registry is not safe for concurrent mutation; populate it at startup and then treat it as read-only.
func DefaultRegistry ¶
func DefaultRegistry() *Registry
DefaultRegistry returns a Registry populated with all code pages currently implemented.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty registry.
Example ¶
NewRegistry plus Codepage lets callers add namespaces beyond the 25 EAS code pages DefaultRegistry ships with — e.g. for a vendor extension or a non-EAS WBXML profile.
package main
import (
"fmt"
"github.com/hstern/go-activesync/wbxml"
)
func main() {
r := wbxml.NewRegistry()
r.Add(&wbxml.Codepage{
Number: 99,
Name: "MyApp",
Tags: map[byte]string{
0x05: "Hello",
0x06: "World",
},
})
name, _ := r.TagName(99, 0x05)
id, _ := r.TagID(99, "World")
fmt.Printf("0x05 → %s\n", name)
fmt.Printf("World → 0x%02X\n", id)
}
Output: 0x05 → Hello World → 0x06
func (*Registry) Add ¶
Add registers a code page. Panics if the page number is already registered, or if any tag identity is outside 0x05..0x3F.
func (*Registry) PageName ¶
PageName returns the page's human-readable name (e.g. "AirSync") or "" if the page is unknown.