/* 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 }