v0.1.0 2c367cd

wire.go RawHistoryFolder

1package zerobus
2
3import (
4	"encoding/binary"
5	"errors"
6	"math"
7	"strings"
8	"unicode/utf8"
9	"unsafe"
10)
11
12const (
13	maxMessage = 128 << 20 // the D-Bus limit for a whole message
14	maxArray   = 64 << 20  // the D-Bus limit for one array
15	maxNest    = 32        // array or struct levels in one signature
16	maxDepth   = 64        // total levels in a value, variants included
17)
18
19var (
20	// ErrMalformed reports bytes that do not follow the D-Bus wire format.
21	ErrMalformed = errors.New("zerobus: malformed message")
22	// ErrTooLarge reports a message or array over the D-Bus size limits.
23	ErrTooLarge = errors.New("zerobus: message too large")
24	// ErrInvalid reports an outgoing value D-Bus does not allow, such as a
25	// string with a NUL byte or a body that does not match its signature.
26	ErrInvalid = errors.New("zerobus: invalid value")
27)
28
29// alignOf returns the alignment of the type that starts with signature byte c.
30func alignOf(c byte) int {
31	switch c {
32	case 'n', 'q':
33		return 2
34	case 'b', 'i', 'u', 'h', 's', 'o', 'a':
35		return 4
36	case 'x', 't', 'd', '(', '{':
37		return 8
38	}
39	return 1 // y, g, v
40}
41
42// pad rounds off up to a multiple of n, a power of two.
43func pad(off, n int) int { return (off + n - 1) &^ (n - 1) }
44
45// sigEnd returns the index just after the single complete type that starts
46// at sig[i], or -1 if there is none.
47func sigEnd(sig string, i int) int { return typeEnd(sig, i, 0, 0) }
48
49// typeEnd is sigEnd with the array and struct nesting so far. The spec
50// limits each to 32 levels; dict entries count as structs.
51func typeEnd(sig string, i, arrays, structs int) int {
52	if i >= len(sig) || arrays > maxNest || structs > maxNest {
53		return -1
54	}
55	switch sig[i] {
56	case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 'h', 's', 'o', 'g', 'v':
57		return i + 1
58	case 'a':
59		if i+1 < len(sig) && sig[i+1] == '{' {
60			return dictEnd(sig, i+1, arrays+1, structs+1)
61		}
62		return typeEnd(sig, i+1, arrays+1, structs)
63	case '(':
64		j := i + 1
65		if j < len(sig) && sig[j] == ')' {
66			return -1 // empty structs are not allowed
67		}
68		for j < len(sig) && sig[j] != ')' {
69			if j = typeEnd(sig, j, arrays, structs+1); j < 0 {
70				return -1
71			}
72		}
73		if j >= len(sig) {
74			return -1
75		}
76		return j + 1
77	}
78	return -1 // includes '{': a dict entry is only valid as an array element
79}
80
81// dictEnd returns the index just after the dict entry type at sig[i] ('{').
82// Its key must be a basic type and it holds exactly two types.
83func dictEnd(sig string, i, arrays, structs int) int {
84	j := i + 1
85	if j >= len(sig) || !basic(sig[j]) || structs > maxNest {
86		return -1
87	}
88	if j = typeEnd(sig, j+1, arrays, structs); j < 0 || j >= len(sig) || sig[j] != '}' {
89		return -1
90	}
91	return j + 1
92}
93
94func basic(c byte) bool {
95	switch c {
96	case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 'h', 's', 'o', 'g':
97		return true
98	}
99	return false
100}
101
102// validSignature reports whether sig is a list of complete types that fits
103// in a D-Bus signature.
104func validSignature(sig string) bool {
105	if len(sig) > 255 {
106		return false
107	}
108	for i := 0; i < len(sig); {
109		if i = sigEnd(sig, i); i < 0 {
110			return false
111		}
112	}
113	return true
114}
115
116// validPath reports whether p is a valid object path.
117func validPath(p string) bool {
118	if p == "/" {
119		return true
120	}
121	if len(p) < 2 || p[0] != '/' || p[len(p)-1] == '/' {
122		return false
123	}
124	for i := 1; i < len(p); i++ {
125		c := p[i]
126		switch {
127		case c == '/':
128			if p[i-1] == '/' {
129				return false
130			}
131		case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9', c == '_':
132		default:
133			return false
134		}
135	}
136	return true
137}
138
139// validMember reports whether s is a valid member name: letters, digits and
140// '_', not starting with a digit.
141func validMember(s string) bool {
142	if s == "" || len(s) > 255 || s[0] >= '0' && s[0] <= '9' {
143		return false
144	}
145	for i := 0; i < len(s); i++ {
146		if !nameChar(s[i]) {
147			return false
148		}
149	}
150	return true
151}
152
153// nameChar reports whether c may appear in a member or interface name.
154func nameChar(c byte) bool {
155	return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_'
156}
157
158// validInterface reports whether s is a valid interface or error name: two
159// or more member-like elements separated by dots.
160func validInterface(s string) bool {
161	if len(s) > 255 {
162		return false
163	}
164	// strings.Cut, not SplitSeq: before Go 1.25 the iterator allocates.
165	n := 0
166	for more := true; more; n++ {
167		var e string
168		e, s, more = strings.Cut(s, ".")
169		if !validMember(e) {
170			return false
171		}
172	}
173	return n >= 2
174}
175
176// validBusName reports whether s is a valid unique (":1.42") or well-known
177// ("org.example.App") bus name.
178func validBusName(s string) bool {
179	if s == "" || len(s) > 255 {
180		return false
181	}
182	unique := s[0] == ':'
183	if unique {
184		s = s[1:]
185	}
186	n := 0
187	for more := true; more; n++ {
188		var e string
189		e, s, more = strings.Cut(s, ".")
190		if e == "" || !unique && e[0] >= '0' && e[0] <= '9' {
191			return false
192		}
193		for i := 0; i < len(e); i++ {
194			if !nameChar(e[i]) && e[i] != '-' {
195				return false
196			}
197		}
198	}
199	return n >= 2
200}
201
202// Reader decodes D-Bus values in place.
203//
204// Strings and byte slices it returns point into the message buffer: they are
205// valid only until the next Conn.ReadMessage. Copy them (strings.Clone,
206// bytes.Clone) to keep them longer.
207//
208// Read values in the order of the message signature. Errors are sticky:
209// after the first one, every read returns a zero value and Err reports it.
210type Reader struct {
211	buf []byte
212	off int
213	big bool
214	err error
215}
216
217// Err returns the first error the reader met, or nil.
218func (r *Reader) Err() error { return r.err }
219
220// Done reports whether every byte was read without error.
221func (r *Reader) Done() bool { return r.err == nil && r.off == len(r.buf) }
222
223func (r *Reader) fail() {
224	if r.err == nil {
225		r.err = ErrMalformed
226	}
227}
228
229// take aligns to align and returns the next size bytes.
230func (r *Reader) take(align, size int) []byte {
231	if r.err != nil {
232		return nil
233	}
234	off := pad(r.off, align)
235	if off > len(r.buf)-size {
236		r.fail()
237		return nil
238	}
239	r.off = off + size
240	return r.buf[off:r.off]
241}
242
243// Byte reads a BYTE (y).
244func (r *Reader) Byte() byte {
245	if b := r.take(1, 1); b != nil {
246		return b[0]
247	}
248	return 0
249}
250
251// Bool reads a BOOLEAN (b).
252func (r *Reader) Bool() bool {
253	v := r.Uint32()
254	if v > 1 {
255		r.fail()
256		return false
257	}
258	return v == 1
259}
260
261// Int16 reads an INT16 (n).
262func (r *Reader) Int16() int16 { return int16(r.Uint16()) }
263
264// Uint16 reads a UINT16 (q).
265func (r *Reader) Uint16() uint16 {
266	b := r.take(2, 2)
267	if b == nil {
268		return 0
269	}
270	if r.big {
271		return binary.BigEndian.Uint16(b)
272	}
273	return binary.LittleEndian.Uint16(b)
274}
275
276// Int32 reads an INT32 (i).
277func (r *Reader) Int32() int32 { return int32(r.Uint32()) }
278
279// Uint32 reads a UINT32 (u).
280func (r *Reader) Uint32() uint32 {
281	b := r.take(4, 4)
282	if b == nil {
283		return 0
284	}
285	if r.big {
286		return binary.BigEndian.Uint32(b)
287	}
288	return binary.LittleEndian.Uint32(b)
289}
290
291// UnixFD reads a UNIX_FD (h): an index into the fds sent with the message.
292// zerobus does not receive file descriptors, so the index has nothing to
293// point to; it is only read so the following values can be.
294func (r *Reader) UnixFD() uint32 { return r.Uint32() }
295
296// Int64 reads an INT64 (x).
297func (r *Reader) Int64() int64 { return int64(r.Uint64()) }
298
299// Uint64 reads a UINT64 (t).
300func (r *Reader) Uint64() uint64 {
301	b := r.take(8, 8)
302	if b == nil {
303		return 0
304	}
305	if r.big {
306		return binary.BigEndian.Uint64(b)
307	}
308	return binary.LittleEndian.Uint64(b)
309}
310
311// Float64 reads a DOUBLE (d).
312func (r *Reader) Float64() float64 { return math.Float64frombits(r.Uint64()) }
313
314// Str reads a STRING (s). The result points into the message buffer.
315func (r *Reader) Str() string { return r.str(int(r.Uint32())) }
316
317// ObjectPath reads an OBJECT_PATH (o). The result points into the message
318// buffer.
319func (r *Reader) ObjectPath() string { return r.Str() }
320
321// Signature reads a SIGNATURE (g). The result points into the message buffer.
322func (r *Reader) Signature() string { return r.str(int(r.Byte())) }
323
324// str returns the next n bytes as a string and skips the NUL after them.
325func (r *Reader) str(n int) string {
326	if r.err != nil {
327		return ""
328	}
329	if n < 0 || n > len(r.buf)-r.off-1 || r.buf[r.off+n] != 0 {
330		r.fail()
331		return ""
332	}
333	s := unsafe.String(unsafe.SliceData(r.buf[r.off:]), n)
334	r.off += n + 1
335	return s
336}
337
338// Variant reads the signature of a VARIANT (v) and returns it. Read the
339// value that follows with the matching method, or skip it with Skip.
340func (r *Reader) Variant() string {
341	sig := r.Signature()
342	if r.err == nil && sigEnd(sig, 0) != len(sig) {
343		r.fail()
344		return ""
345	}
346	return sig
347}
348
349// Struct aligns the reader to the start of a STRUCT or DICT_ENTRY. Call it
350// before reading the first field.
351func (r *Reader) Struct() { r.take(8, 0) }
352
353// Array reads the header of an ARRAY whose element type starts with the
354// signature byte elem, and returns the offset where the array ends. Read the
355// elements while More returns true:
356//
357//	end := r.Array('s')
358//	for r.More(end) {
359//		name := r.Str()
360//	}
361func (r *Reader) Array(elem byte) int {
362	n := r.Uint32()
363	if r.err != nil {
364		return r.off
365	}
366	if n > maxArray {
367		r.fail()
368		return r.off
369	}
370	off := pad(r.off, alignOf(elem))
371	if off > len(r.buf)-int(n) {
372		r.fail()
373		return r.off
374	}
375	r.off = off
376	return off + int(n)
377}
378
379// More reports whether the array that ends at end has another element.
380func (r *Reader) More(end int) bool {
381	if r.err != nil {
382		return false
383	}
384	if r.off < end {
385		return true
386	}
387	if r.off > end {
388		r.fail()
389	}
390	return false
391}
392
393// SkipTo moves to end, the value returned by Array. Use it to leave an array
394// before its last element.
395func (r *Reader) SkipTo(end int) {
396	if r.err != nil {
397		return
398	}
399	if end < r.off || end > len(r.buf) {
400		r.fail()
401		return
402	}
403	r.off = end
404}
405
406// ByteArray reads an ARRAY of BYTE (ay). The result points into the message
407// buffer.
408func (r *Reader) ByteArray() []byte {
409	end := r.Array('y')
410	if r.err != nil {
411		return nil
412	}
413	b := r.buf[r.off:end:end]
414	r.off = end
415	return b
416}
417
418// Skip reads past values of the given signature. Arrays are skipped by their
419// length, without reading their elements.
420func (r *Reader) Skip(sig string) { r.skip(sig, 0, false) }
421
422// check reads every value of sig, array elements included, and fails on any
423// value the bus would reject. It proves an outgoing body matches its
424// signature.
425func (r *Reader) check(sig string) { r.skip(sig, 0, true) }
426
427func (r *Reader) skip(sig string, depth int, strict bool) {
428	for i := 0; i < len(sig) && r.err == nil; {
429		i = r.skipOne(sig, i, depth, strict)
430	}
431}
432
433// skipOne skips the value of the type at sig[i] and returns the index of the
434// next type. In strict mode it also reads array elements and checks values.
435func (r *Reader) skipOne(sig string, i, depth int, strict bool) int {
436	if depth > maxDepth {
437		r.fail()
438		return len(sig)
439	}
440	switch c := sig[i]; c {
441	case 'y':
442		r.take(1, 1)
443	case 'n', 'q':
444		r.take(2, 2)
445	case 'b':
446		r.Bool()
447	case 'i', 'u', 'h':
448		r.take(4, 4)
449	case 'x', 't', 'd':
450		r.take(8, 8)
451	case 's', 'o':
452		s := r.Str()
453		if strict && (!utf8.ValidString(s) || c == 'o' && !validPath(s)) {
454			r.fail()
455		}
456	case 'g':
457		if s := r.Signature(); strict && !validSignature(s) {
458			r.fail()
459		}
460	case 'v':
461		r.skip(r.Variant(), depth+1, strict)
462	case 'a':
463		next := sigEnd(sig, i)
464		if next < 0 {
465			r.fail()
466			return len(sig)
467		}
468		end := r.Array(sig[i+1])
469		if !strict {
470			r.SkipTo(end)
471			return next
472		}
473		for r.More(end) {
474			r.skipOne(sig, i+1, depth+1, true)
475		}
476		return next
477	case '(', '{':
478		closer := byte(')')
479		if c == '{' {
480			closer = '}'
481		}
482		r.Struct()
483		j := i + 1
484		for j < len(sig) && sig[j] != closer && r.err == nil {
485			j = r.skipOne(sig, j, depth+1, strict)
486		}
487		if j >= len(sig) {
488			r.fail()
489			return len(sig)
490		}
491		return j + 1
492	default:
493		r.fail()
494		return len(sig)
495	}
496	return i + 1
497}
498
499// Encoder appends D-Bus values to an outgoing message. Get one from
500// Conn.NewCall, Conn.Signal, Conn.Reply or Conn.Error, write the body values
501// in the order of the message signature, then call Conn.Send.
502//
503// Errors are sticky and reported by Conn.Send.
504type Encoder struct {
505	buf  []byte
506	body int    // offset of the body; 0 when no message is being built
507	sig  string // body signature, checked by finish
508	err  error
509}
510
511func (e *Encoder) invalid() {
512	if e.err == nil {
513		e.err = ErrInvalid
514	}
515}
516
517func (e *Encoder) pad(n int) {
518	for len(e.buf)&(n-1) != 0 {
519		e.buf = append(e.buf, 0)
520	}
521}
522
523// Byte writes a BYTE (y).
524func (e *Encoder) Byte(v byte) { e.buf = append(e.buf, v) }
525
526// Bool writes a BOOLEAN (b).
527func (e *Encoder) Bool(v bool) {
528	var u uint32
529	if v {
530		u = 1
531	}
532	e.Uint32(u)
533}
534
535// Int16 writes an INT16 (n).
536func (e *Encoder) Int16(v int16) { e.Uint16(uint16(v)) }
537
538// Uint16 writes a UINT16 (q).
539func (e *Encoder) Uint16(v uint16) {
540	e.pad(2)
541	e.buf = binary.LittleEndian.AppendUint16(e.buf, v)
542}
543
544// Int32 writes an INT32 (i).
545func (e *Encoder) Int32(v int32) { e.Uint32(uint32(v)) }
546
547// Uint32 writes a UINT32 (u).
548func (e *Encoder) Uint32(v uint32) {
549	e.pad(4)
550	e.buf = binary.LittleEndian.AppendUint32(e.buf, v)
551}
552
553// Int64 writes an INT64 (x).
554func (e *Encoder) Int64(v int64) { e.Uint64(uint64(v)) }
555
556// Uint64 writes a UINT64 (t).
557func (e *Encoder) Uint64(v uint64) {
558	e.pad(8)
559	e.buf = binary.LittleEndian.AppendUint64(e.buf, v)
560}
561
562// Float64 writes a DOUBLE (d).
563func (e *Encoder) Float64(v float64) { e.Uint64(math.Float64bits(v)) }
564
565// Str writes a STRING (s).
566func (e *Encoder) Str(s string) {
567	e.Uint32(uint32(len(s)))
568	e.text(s)
569}
570
571// ObjectPath writes an OBJECT_PATH (o).
572func (e *Encoder) ObjectPath(p string) {
573	if !validPath(p) {
574		e.invalid()
575	}
576	e.Str(p)
577}
578
579// Signature writes a SIGNATURE (g).
580func (e *Encoder) Signature(sig string) {
581	if !validSignature(sig) {
582		e.invalid()
583	}
584	e.Byte(byte(len(sig)))
585	e.text(sig)
586}
587
588// text appends s and its NUL terminator.
589func (e *Encoder) text(s string) {
590	// The bus disconnects a client that sends a NUL or invalid UTF-8.
591	if strings.IndexByte(s, 0) >= 0 || !utf8.ValidString(s) {
592		e.invalid()
593	}
594	e.buf = append(e.buf, s...)
595	e.buf = append(e.buf, 0)
596}
597
598// Variant writes the signature of a VARIANT (v). Write its value next.
599func (e *Encoder) Variant(sig string) {
600	if sigEnd(sig, 0) != len(sig) {
601		e.invalid()
602	}
603	e.Signature(sig)
604}
605
606// Struct aligns the encoder to the start of a STRUCT or DICT_ENTRY. Call it
607// before writing the first field.
608func (e *Encoder) Struct() { e.pad(8) }
609
610// ArrayMark is an array being written; pass it to EndArray.
611type ArrayMark struct{ at, start int }
612
613// BeginArray starts an ARRAY whose element type starts with the signature
614// byte elem. Write the elements, then call EndArray:
615//
616//	a := e.BeginArray('s')
617//	e.Str("one")
618//	e.Str("two")
619//	e.EndArray(a)
620func (e *Encoder) BeginArray(elem byte) ArrayMark {
621	e.pad(4)
622	at := len(e.buf)
623	e.buf = append(e.buf, 0, 0, 0, 0)
624	e.pad(alignOf(elem))
625	return ArrayMark{at: at, start: len(e.buf)}
626}
627
628// EndArray finishes the array started by BeginArray.
629func (e *Encoder) EndArray(a ArrayMark) {
630	n := len(e.buf) - a.start
631	if n > maxArray {
632		e.invalid()
633		return
634	}
635	binary.LittleEndian.PutUint32(e.buf[a.at:], uint32(n))
636}
637
638// ByteArray writes an ARRAY of BYTE (ay).
639func (e *Encoder) ByteArray(b []byte) {
640	e.Uint32(uint32(len(b)))
641	e.buf = append(e.buf, b...)
642}
643
644// SetFlags sets the flags of the message being built.
645func (e *Encoder) SetFlags(f Flags) {
646	if e.body != 0 {
647		e.buf[2] = byte(f)
648	}
649}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 3

↑↓ moveenter openesc close