forked from davidbanham/go-git-http
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pktparser.go
143 lines (128 loc) · 3.43 KB
/
pktparser.go
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
141
142
143
package githttp
import (
"encoding/hex"
"errors"
"fmt"
)
// pktLineParser is a parser for git pkt-line Format,
// as documented in https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt.
// A zero value of pktLineParser is valid to use as a parser in ready state.
// Output should be read from Lines and Error after Step returns finished true.
// pktLineParser reads until a terminating "0000" flush-pkt. It's good for a single use only.
type pktLineParser struct {
// Lines contains all pkt-lines.
Lines []string
// Error contains the first error encountered while parsing, or nil otherwise.
Error error
// Internal state machine.
state state
next int // next is the number of bytes that need to be written to buf before its contents should be processed by the state machine.
buf []byte
}
// Feed accumulates and parses data.
// It will return early if it reaches end of pkt-line data (indicated by a flush-pkt "0000"),
// or if it encounters a parsing error.
// It must not be called when state is done.
// When done, all of pkt-lines will be available in Lines, and Error will be set if any error occurred.
func (p *pktLineParser) Feed(data []byte) {
for {
// If not enough data to reach next state, append it to buf and return.
if len(data) < p.next {
p.buf = append(p.buf, data...)
p.next -= len(data)
return
}
// There's enough data to reach next state. Take from data only what's needed.
b := data[:p.next]
data = data[p.next:]
p.buf = append(p.buf, b...)
p.next = 0
// Take a step to next state.
err := p.step()
if err != nil {
p.state = done
p.Error = err
return
}
// Break out once reached done state.
if p.state == done {
return
}
}
}
const (
// pkt-len = 4*(HEXDIG)
pktLenSize = 4
)
type state uint8
const (
ready state = iota
readingLen
readingPayload
done
)
// step moves the state machine to the next state.
// buf must contain all the data ready for consumption for current state.
// It must not be called when state is done.
func (p *pktLineParser) step() error {
switch p.state {
case ready:
p.state = readingLen
p.next = pktLenSize
return nil
case readingLen:
// len(p.buf) is 4.
pktLen, err := parsePktLen(p.buf)
if err != nil {
return err
}
switch {
case pktLen == 0:
p.state = done
p.next = 0
p.buf = nil
return nil
default:
p.state = readingPayload
p.next = pktLen - pktLenSize // (pkt-len - 4)*(OCTET)
p.buf = p.buf[:0]
return nil
}
case readingPayload:
p.state = readingLen
p.next = pktLenSize
p.Lines = append(p.Lines, string(p.buf))
p.buf = p.buf[:0]
return nil
default:
panic(fmt.Errorf("unreachable: %v", p.state))
}
}
// parsePktLen parses a pkt-len segment.
// len(b) must be 4.
func parsePktLen(b []byte) (int, error) {
pktLen, err := parseHex(b)
switch {
case err != nil:
return 0, err
case 1 <= pktLen && pktLen < pktLenSize:
return 0, fmt.Errorf("invalid pkt-len: %v", pktLen)
case pktLen > 65524:
// The maximum length of a pkt-line is 65524 bytes (65520 bytes of payload + 4 bytes of length data).
return 0, fmt.Errorf("invalid pkt-len: %v", pktLen)
}
return int(pktLen), nil
}
// parseHex parses a 4-byte hex number.
// len(h) must be 4.
func parseHex(h []byte) (uint16, error) {
var b [2]uint8
n, err := hex.Decode(b[:], h)
switch {
case err != nil:
return 0, err
case n != 2:
return 0, errors.New("short output")
}
return uint16(b[0])<<8 | uint16(b[1]), nil
}