aboutsummaryrefslogtreecommitdiffstats
path: root/formats/packp/pktline/pktlines.go
blob: c19aa2ed9570c15a938504cc91b02216d97aa085 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Package pktline implements reading payloads form pkt-lines and creating pkt-lines from payloads.
package pktline

import (
	"bytes"
	"errors"
	"io"
	"strings"
)

const (
	// MaxPayloadSize is the maximum payload size of a pkt-line in bytes.
	MaxPayloadSize = 65516
)

var (
	flush = []byte{'0', '0', '0', '0'}
)

// PktLines values represent a succession of pkt-lines.  Values from
// this type are not zero-value safe, use the New function instead.
type PktLines struct {
	r io.Reader
}

var (
	// ErrPayloadTooLong is returned by the Add methods when any of the
	// provided payloads is bigger than MaxPayloadSize.
	ErrPayloadTooLong = errors.New("payload is too long")
	// ErrEmptyPayload is returned by the Add methods when an empty
	// payload is provided.
	ErrEmptyPayload = errors.New("cannot add empty payloads")
)

// New returns an empty PktLines (with no payloads) ready to be used.
func New() *PktLines {
	return &PktLines{
		r: bytes.NewReader(nil),
	}
}

// AddFlush adds a flush-pkt to p.
func (p *PktLines) AddFlush() {
	p.r = io.MultiReader(p.r, bytes.NewReader(flush))
}

// Add adds the slices in pp as the payloads of a
// corresponding number of pktlines.
func (p *PktLines) Add(pp ...[]byte) error {
	tmp := []io.Reader{p.r}
	for _, p := range pp {
		if err := add(&tmp, p); err != nil {
			return err
		}
	}
	p.r = io.MultiReader(tmp...)

	return nil
}

func add(dst *[]io.Reader, e []byte) error {
	if err := checkPayloadLength(len(e)); err != nil {
		return err
	}

	n := len(e) + 4
	*dst = append(*dst, bytes.NewReader(asciiHex16(n)))
	*dst = append(*dst, bytes.NewReader(e))

	return nil
}

func checkPayloadLength(n int) error {
	switch {
	case n < 0:
		panic("unexpected negative payload length")
	case n == 0:
		return ErrEmptyPayload
	case n > MaxPayloadSize:
		return ErrPayloadTooLong
	default:
		return nil
	}
}

// Returns the hexadecimal ascii representation of the 16 less
// significant bits of n.  The length of the returned slice will always
// be 4.  Example: if n is 1234 (0x4d2), the return value will be
// []byte{'0', '4', 'd', '2'}.
func asciiHex16(n int) []byte {
	var ret [4]byte
	ret[0] = byteToASCIIHex(byte(n & 0xf000 >> 12))
	ret[1] = byteToASCIIHex(byte(n & 0x0f00 >> 8))
	ret[2] = byteToASCIIHex(byte(n & 0x00f0 >> 4))
	ret[3] = byteToASCIIHex(byte(n & 0x000f))

	return ret[:]
}

// turns a byte into its hexadecimal ascii representation.  Example:
// from 11 (0xb) to 'b'.
func byteToASCIIHex(n byte) byte {
	if n < 10 {
		return '0' + n
	}

	return 'a' - 10 + n
}

// AddString adds the strings in pp as payloads of a
// corresponding number of pktlines.
func (p *PktLines) AddString(pp ...string) error {
	tmp := []io.Reader{p.r}
	for _, p := range pp {
		if err := addString(&tmp, p); err != nil {
			return err
		}
	}

	p.r = io.MultiReader(tmp...)

	return nil
}

func addString(dst *[]io.Reader, s string) error {
	if err := checkPayloadLength(len(s)); err != nil {
		return err
	}

	n := len(s) + 4
	*dst = append(*dst, bytes.NewReader(asciiHex16(n)))
	*dst = append(*dst, strings.NewReader(s))

	return nil
}

// Read reads the pktlines for the payloads added so far.
func (p *PktLines) Read(b []byte) (n int, err error) {
	return p.r.Read(b)
}