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
|
// Package pktline implements reading and creating pkt-lines as per
// https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt.
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'}
)
// PktLine values represent a succession of pkt-lines.
// Values from this type are not zero-value safe, see the functions New
// and NewFromString below.
type PktLine struct {
io.Reader
}
// ErrPayloadTooLong is returned by New and NewFromString when any of
// the provided payloads is bigger than MaxPayloadSize.
var ErrPayloadTooLong = errors.New("payload is too long")
// New returns the concatenation of several pkt-lines, each of them with
// the payload specified by the contents of each input byte slice. An
// empty payload byte slice will produce a flush-pkt.
func New(payloads ...[]byte) (PktLine, error) {
ret := []io.Reader{}
for _, p := range payloads {
if err := add(&ret, p); err != nil {
return PktLine{}, err
}
}
return PktLine{io.MultiReader(ret...)}, nil
}
func add(dst *[]io.Reader, e []byte) error {
if len(e) > MaxPayloadSize {
return ErrPayloadTooLong
}
if len(e) == 0 {
*dst = append(*dst, bytes.NewReader(flush))
return nil
}
n := len(e) + 4
*dst = append(*dst, bytes.NewReader(int16ToHex(n)))
*dst = append(*dst, bytes.NewReader(e))
return nil
}
// susbtitutes fmt.Sprintf("%04x", n) to avoid memory garbage
// generation.
func int16ToHex(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) into 'b'.
func byteToAsciiHex(n byte) byte {
if n < 10 {
return byte('0' + n)
}
return byte('a' - 10 + n)
}
// NewFromStrings returns the concatenation of several pkt-lines, each
// of them with the payload specified by the contents of each input
// string. An empty payload string will produce a flush-pkt.
func NewFromStrings(payloads ...string) (PktLine, error) {
ret := []io.Reader{}
for _, p := range payloads {
if err := addString(&ret, p); err != nil {
return PktLine{}, err
}
}
return PktLine{io.MultiReader(ret...)}, nil
}
func addString(dst *[]io.Reader, s string) error {
if len(s) > MaxPayloadSize {
return ErrPayloadTooLong
}
if len(s) == 0 {
*dst = append(*dst, bytes.NewReader(flush))
return nil
}
n := len(s) + 4
*dst = append(*dst, bytes.NewReader(int16ToHex(n)))
*dst = append(*dst, strings.NewReader(s))
return nil
}
|