127 lines
2.7 KiB
Go
127 lines
2.7 KiB
Go
/*
|
|
Package testsink provides a partial implementation of the systemd-journald
|
|
Unix socket. Datagrams received on its socket are decoded and stored for unit
|
|
tests to examine.
|
|
*/
|
|
package testsink
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// Message returns the Nth message received. It waits for the message to arrive.
|
|
func (sink *Sink) Message(N int) (Message, error) {
|
|
sink.lock.Lock()
|
|
defer sink.lock.Unlock()
|
|
|
|
for len(sink.msgs) <= N {
|
|
sink.mcond.Wait()
|
|
if sink.err != nil {
|
|
return Message{}, sink.err
|
|
}
|
|
}
|
|
return sink.msgs[N], nil
|
|
}
|
|
|
|
// Message is recorded for each datagram received.
|
|
type Message struct {
|
|
Raw []byte
|
|
}
|
|
|
|
type DecodedAttr struct {
|
|
Key, Val string
|
|
}
|
|
|
|
func (m *Message) Decode() (msg string, attr []DecodedAttr, err error) {
|
|
raw := m.Raw
|
|
var errs []error
|
|
|
|
DecodeLoop:
|
|
for len(raw) > 0 {
|
|
n, key := decodeAttrKey(raw)
|
|
raw = raw[n:]
|
|
switch {
|
|
case len(raw) == 0:
|
|
errs = append(errs, fmt.Errorf("unterminated attribute name %q", key))
|
|
break DecodeLoop
|
|
case key == "":
|
|
errs = append(errs, errors.New("empty attribute name"))
|
|
}
|
|
|
|
var val string
|
|
switch raw[0] {
|
|
case '=':
|
|
n, val = decodeAttrValText(raw[1:])
|
|
case '\n':
|
|
var err error
|
|
n, val, err = decodeAttrValLen(raw[1:])
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
}
|
|
raw = raw[1+n:]
|
|
if len(raw) == 0 {
|
|
errs = append(errs, fmt.Errorf("unterminated value for attribute %q", key))
|
|
break DecodeLoop
|
|
}
|
|
|
|
if raw[0] != '\n' {
|
|
errs = append(errs, errors.New("incorrectly terminated attribute value"))
|
|
}
|
|
raw = raw[1:]
|
|
|
|
switch key {
|
|
case "MESSAGE":
|
|
msg = val
|
|
default:
|
|
attr = append(attr, DecodedAttr{Key: key, Val: val})
|
|
}
|
|
}
|
|
|
|
return msg, attr, errors.Join(errs...)
|
|
}
|
|
|
|
func decodeAttrKey(raw []byte) (n int, key string) {
|
|
for i := range raw {
|
|
switch raw[i] {
|
|
case '\n', '=':
|
|
return i, string(raw[:i])
|
|
}
|
|
}
|
|
return len(raw), string(raw)
|
|
}
|
|
|
|
func decodeAttrValText(raw []byte) (n int, val string) {
|
|
term := bytes.IndexByte(raw, '\n')
|
|
if term == -1 {
|
|
term = len(raw)
|
|
}
|
|
return term, string(raw[:term])
|
|
}
|
|
|
|
func decodeAttrValLen(raw []byte) (n int, val string, err error) {
|
|
if len(raw) < 8 {
|
|
return len(raw), "", errors.New("not enough bytes for binary attribute value length")
|
|
}
|
|
amt := binary.LittleEndian.Uint64(raw)
|
|
raw = raw[8:]
|
|
if uint64(len(raw)) < amt {
|
|
return 8 + len(raw), string(raw), errors.New("not enough bytes for binary attribute value")
|
|
}
|
|
return int(8 + amt), string(raw[:amt]), nil
|
|
}
|
|
|
|
// GetAttr returns the value of the attribute whose key name matches, and a
|
|
// boolean to indicate if it found a match.
|
|
func GetAttr(attr []DecodedAttr, key string) (value string, ok bool) {
|
|
for i := range attr {
|
|
if attr[i].Key == key {
|
|
return attr[i].Val, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|