v0.2.0 07ab4d2

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	n := 0
165	for more := true; more; n++ {
166		var e string
167		e, s, more = strings.Cut(s, ".")
168		if !validMember(e) {
169			return false
170		}
171	}
172	return n >= 2
173}
174
175// validBusName reports whether s is a valid unique (":1.42") or well-known
176// ("org.example.App") bus name.
177func validBusName(s string) bool {
178	if s == "" || len(s) > 255 {
179		return false
180	}
181	unique := s[0] == ':'
182	if unique {
183		s = s[1:]
184	}
185	n := 0
186	for more := true; more; n++ {
187		var e string
188		e, s, more = strings.Cut(s, ".")
189		if e == "" || !unique && e[0] >= '0' && e[0] <= '9' {
190			return false
191		}
192		for i := 0; i < len(e); i++ {
193			if !nameChar(e[i]) && e[i] != '-' {
194				return false
195			}
196		}
197	}
198	return n >= 2
199}
200
201// Reader decodes D-Bus values in place.
202//
203// Strings and byte slices it returns point into the message buffer: they are
204// valid only until the next Conn.ReadMessage. Copy them (strings.Clone,
205// bytes.Clone) to keep them longer.
206//
207// Read values in the order of the message signature. Errors are sticky:
208// after the first one, every read returns a zero value and Err reports it.
209type Reader struct {
210	buf []byte
211	off int
212	big bool
213	err error
214}
215
216// Err returns the first error the reader met, or nil.
217func (r *Reader) Err() error { return r.err }
218
219// Done reports whether every byte was read without error.
220func (r *Reader) Done() bool { return r.err == nil && r.off == len(r.buf) }
221
222func (r *Reader) fail() {
223	if r.err == nil {
224		r.err = ErrMalformed
225	}
226}
227
228// take aligns to align and returns the next size bytes.
229func (r *Reader) take(align, size int) []byte {
230	if r.err != nil {
231		return nil
232	}
233	off := pad(r.off, align)
234	if off > len(r.buf)-size {
235		r.fail()
236		return nil
237	}
238	r.off = off + size
239	return r.buf[off:r.off]
240}
241
242// Byte reads a BYTE (y).
243func (r *Reader) Byte() byte {
244	if b := r.take(1, 1); b != nil {
245		return b[0]
246	}
247	return 0
248}
249
250// Bool reads a BOOLEAN (b).
251func (r *Reader) Bool() bool {
252	v := r.Uint32()
253	if v > 1 {
254		r.fail()
255		return false
256	}
257	return v == 1
258}
259
260// Int16 reads an INT16 (n).
261func (r *Reader) Int16() int16 { return int16(r.Uint16()) }
262
263// Uint16 reads a UINT16 (q).
264func (r *Reader) Uint16() uint16 {
265	b := r.take(2, 2)
266	if b == nil {
267		return 0
268	}
269	if r.big {
270		return binary.BigEndian.Uint16(b)
271	}
272	return binary.LittleEndian.Uint16(b)
273}
274
275// Int32 reads an INT32 (i).
276func (r *Reader) Int32() int32 { return int32(r.Uint32()) }
277
278// Uint32 reads a UINT32 (u).
279func (r *Reader) Uint32() uint32 {
280	b := r.take(4, 4)
281	if b == nil {
282		return 0
283	}
284	if r.big {
285		return binary.BigEndian.Uint32(b)
286	}
287	return binary.LittleEndian.Uint32(b)
288}
289
290// UnixFD reads a UNIX_FD (h): an index into the fds sent with the message.
291// zerobus does not receive file descriptors, so the index has nothing to
292// point to; it is only read so the following values can be.
293func (r *Reader) UnixFD() uint32 { return r.Uint32() }
294
295// Int64 reads an INT64 (x).
296func (r *Reader) Int64() int64 { return int64(r.Uint64()) }
297
298// Uint64 reads a UINT64 (t).
299func (r *Reader) Uint64() uint64 {
300	b := r.take(8, 8)
301	if b == nil {
302		return 0
303	}
304	if r.big {
305		return binary.BigEndian.Uint64(b)
306	}
307	return binary.LittleEndian.Uint64(b)
308}
309
310// Float64 reads a DOUBLE (d).
311func (r *Reader) Float64() float64 { return math.Float64frombits(r.Uint64()) }
312
313// Str reads a STRING (s). The result points into the message buffer.
314func (r *Reader) Str() string { return r.str(int(r.Uint32())) }
315
316// ObjectPath reads an OBJECT_PATH (o). The result points into the message
317// buffer.
318func (r *Reader) ObjectPath() string { return r.Str() }
319
320// Signature reads a SIGNATURE (g). The result points into the message buffer.
321func (r *Reader) Signature() string { return r.str(int(r.Byte())) }
322
323// str returns the next n bytes as a string and skips the NUL after them.
324func (r *Reader) str(n int) string {
325	if r.err != nil {
326		return ""
327	}
328	if n < 0 || n > len(r.buf)-r.off-1 || r.buf[r.off+n] != 0 {
329		r.fail()
330		return ""
331	}
332	s := unsafe.String(unsafe.SliceData(r.buf[r.off:]), n)
333	r.off += n + 1
334	return s
335}
336
337// Variant reads the signature of a VARIANT (v) and returns it. Read the
338// value that follows with the matching method, or skip it with Skip.
339func (r *Reader) Variant() string {
340	sig := r.Signature()
341	if r.err == nil && sigEnd(sig, 0) != len(sig) {
342		r.fail()
343		return ""
344	}
345	return sig
346}
347
348// Struct aligns the reader to the start of a STRUCT or DICT_ENTRY. Call it
349// before reading the first field.
350func (r *Reader) Struct() { r.take(8, 0) }
351
352// Array reads the header of an ARRAY whose element type starts with the
353// signature byte elem, and returns the offset where the array ends. Read the
354// elements while More returns true:
355//
356//	end := r.Array('s')
357//	for r.More(end) {
358//		name := r.Str()
359//	}
360func (r *Reader) Array(elem byte) int {
361	n := r.Uint32()
362	if r.err != nil {
363		return r.off
364	}
365	if n > maxArray {
366		r.fail()
367		return r.off
368	}
369	off := pad(r.off, alignOf(elem))
370	if off > len(r.buf)-int(n) {
371		r.fail()
372		return r.off
373	}
374	r.off = off
375	return off + int(n)
376}
377
378// More reports whether the array that ends at end has another element.
379func (r *Reader) More(end int) bool {
380	if r.err != nil {
381		return false
382	}
383	if r.off < end {
384		return true
385	}
386	if r.off > end {
387		r.fail()
388	}
389	return false
390}
391
392// SkipTo moves to end, the value returned by Array. Use it to leave an array
393// before its last element.
394func (r *Reader) SkipTo(end int) {
395	if r.err != nil {
396		return
397	}
398	if end < r.off || end > len(r.buf) {
399		r.fail()
400		return
401	}
402	r.off = end
403}
404
405// ByteArray reads an ARRAY of BYTE (ay). The result points into the message
406// buffer.
407func (r *Reader) ByteArray() []byte {
408	end := r.Array('y')
409	if r.err != nil {
410		return nil
411	}
412	b := r.buf[r.off:end:end]
413	r.off = end
414	return b
415}
416
417// Skip reads past values of the given signature. Arrays are skipped by their
418// length, without reading their elements.
419func (r *Reader) Skip(sig string) { r.skip(sig, 0, false) }
420
421// check reads every value of sig, array elements included, and fails on any
422// value the bus would reject. It proves an outgoing body matches its
423// signature.
424func (r *Reader) check(sig string) { r.skip(sig, 0, true) }
425
426func (r *Reader) skip(sig string, depth int, strict bool) {
427	for i := 0; i < len(sig) && r.err == nil; {
428		i = r.skipOne(sig, i, depth, strict)
429	}
430}
431
432// skipOne skips the value of the type at sig[i] and returns the index of the
433// next type. In strict mode it also reads array elements and checks values.
434func (r *Reader) skipOne(sig string, i, depth int, strict bool) int {
435	if depth > maxDepth {
436		r.fail()
437		return len(sig)
438	}
439	switch c := sig[i]; c {
440	case 'y':
441		r.take(1, 1)
442	case 'n', 'q':
443		r.take(2, 2)
444	case 'b':
445		r.Bool()
446	case 'i', 'u', 'h':
447		r.take(4, 4)
448	case 'x', 't', 'd':
449		r.take(8, 8)
450	case 's', 'o':
451		s := r.Str()
452		if strict && (!utf8.ValidString(s) || c == 'o' && !validPath(s)) {
453			r.fail()
454		}
455	case 'g':
456		if s := r.Signature(); strict && !validSignature(s) {
457			r.fail()
458		}
459	case 'v':
460		r.skip(r.Variant(), depth+1, strict)
461	case 'a':
462		next := sigEnd(sig, i)
463		if next < 0 {
464			r.fail()
465			return len(sig)
466		}
467		end := r.Array(sig[i+1])
468		if !strict {
469			r.SkipTo(end)
470			return next
471		}
472		for r.More(end) {
473			r.skipOne(sig, i+1, depth+1, true)
474		}
475		return next
476	case '(', '{':
477		closer := byte(')')
478		if c == '{' {
479			closer = '}'
480		}
481		r.Struct()
482		j := i + 1
483		for j < len(sig) && sig[j] != closer && r.err == nil {
484			j = r.skipOne(sig, j, depth+1, strict)
485		}
486		if j >= len(sig) {
487			r.fail()
488			return len(sig)
489		}
490		return j + 1
491	default:
492		r.fail()
493		return len(sig)
494	}
495	return i + 1
496}
497
498// Encoder appends D-Bus values to an outgoing message. Get one from
499// Conn.NewCall, Conn.Signal, Conn.Reply or Conn.Error, write the body values
500// in the order of the message signature, then call Conn.Send.
501//
502// Errors are sticky and reported by Conn.Send.
503type Encoder struct {
504	buf  []byte
505	body int    // offset of the body; 0 when no message is being built
506	sig  string // body signature, checked by finish
507	err  error
508}
509
510func (e *Encoder) invalid() {
511	if e.err == nil {
512		e.err = ErrInvalid
513	}
514}
515
516func (e *Encoder) pad(n int) {
517	for len(e.buf)&(n-1) != 0 {
518		e.buf = append(e.buf, 0)
519	}
520}
521
522// Byte writes a BYTE (y).
523func (e *Encoder) Byte(v byte) { e.buf = append(e.buf, v) }
524
525// Bool writes a BOOLEAN (b).
526func (e *Encoder) Bool(v bool) {
527	var u uint32
528	if v {
529		u = 1
530	}
531	e.Uint32(u)
532}
533
534// Int16 writes an INT16 (n).
535func (e *Encoder) Int16(v int16) { e.Uint16(uint16(v)) }
536
537// Uint16 writes a UINT16 (q).
538func (e *Encoder) Uint16(v uint16) {
539	e.pad(2)
540	e.buf = binary.LittleEndian.AppendUint16(e.buf, v)
541}
542
543// Int32 writes an INT32 (i).
544func (e *Encoder) Int32(v int32) { e.Uint32(uint32(v)) }
545
546// Uint32 writes a UINT32 (u).
547func (e *Encoder) Uint32(v uint32) {
548	e.pad(4)
549	e.buf = binary.LittleEndian.AppendUint32(e.buf, v)
550}
551
552// Int64 writes an INT64 (x).
553func (e *Encoder) Int64(v int64) { e.Uint64(uint64(v)) }
554
555// Uint64 writes a UINT64 (t).
556func (e *Encoder) Uint64(v uint64) {
557	e.pad(8)
558	e.buf = binary.LittleEndian.AppendUint64(e.buf, v)
559}
560
561// Float64 writes a DOUBLE (d).
562func (e *Encoder) Float64(v float64) { e.Uint64(math.Float64bits(v)) }
563
564// Str writes a STRING (s).
565func (e *Encoder) Str(s string) {
566	e.Uint32(uint32(len(s)))
567	e.text(s)
568}
569
570// ObjectPath writes an OBJECT_PATH (o).
571func (e *Encoder) ObjectPath(p string) {
572	if !validPath(p) {
573		e.invalid()
574	}
575	e.Str(p)
576}
577
578// Signature writes a SIGNATURE (g).
579func (e *Encoder) Signature(sig string) {
580	if !validSignature(sig) {
581		e.invalid()
582	}
583	e.Byte(byte(len(sig)))
584	e.text(sig)
585}
586
587// text appends s and its NUL terminator.
588func (e *Encoder) text(s string) {
589	// The bus disconnects a client that sends a NUL or invalid UTF-8.
590	if strings.IndexByte(s, 0) >= 0 || !utf8.ValidString(s) {
591		e.invalid()
592	}
593	e.buf = append(e.buf, s...)
594	e.buf = append(e.buf, 0)
595}
596
597// Variant writes the signature of a VARIANT (v). Write its value next.
598func (e *Encoder) Variant(sig string) {
599	if sigEnd(sig, 0) != len(sig) {
600		e.invalid()
601	}
602	e.Signature(sig)
603}
604
605// Struct aligns the encoder to the start of a STRUCT or DICT_ENTRY. Call it
606// before writing the first field.
607func (e *Encoder) Struct() { e.pad(8) }
608
609// ArrayMark is an array being written; pass it to EndArray.
610type ArrayMark struct{ at, start int }
611
612// BeginArray starts an ARRAY whose element type starts with the signature
613// byte elem. Write the elements, then call EndArray:
614//
615//	a := e.BeginArray('s')
616//	e.Str("one")
617//	e.Str("two")
618//	e.EndArray(a)
619func (e *Encoder) BeginArray(elem byte) ArrayMark {
620	e.pad(4)
621	at := len(e.buf)
622	e.buf = append(e.buf, 0, 0, 0, 0)
623	e.pad(alignOf(elem))
624	return ArrayMark{at: at, start: len(e.buf)}
625}
626
627// EndArray finishes the array started by BeginArray.
628func (e *Encoder) EndArray(a ArrayMark) {
629	n := len(e.buf) - a.start
630	if n > maxArray {
631		e.invalid()
632		return
633	}
634	binary.LittleEndian.PutUint32(e.buf[a.at:], uint32(n))
635}
636
637// ByteArray writes an ARRAY of BYTE (ay).
638func (e *Encoder) ByteArray(b []byte) {
639	e.Uint32(uint32(len(b)))
640	e.buf = append(e.buf, b...)
641}
642
643// SetFlags sets the flags of the message being built.
644func (e *Encoder) SetFlags(f Flags) {
645	if e.body != 0 {
646		e.buf[2] = byte(f)
647	}
648}

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