Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 0042bb0

Browse files
smolamcuadros
authored andcommitted
protocol/packp: add reference update request encoder. (#147)
* add ReferenceUpdateRequest struct. * add ReferenceUpdateRequest decoder. * add ReferenceUpdateRequest encoder.
1 parent 19f59e7 commit 0042bb0

File tree

7 files changed

+759
-0
lines changed

7 files changed

+759
-0
lines changed

plumbing/protocol/packp/advrefs_decode.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ func decodeFirstRef(l *advRefsDecoder) decoderStateFn {
199199
func decodeCaps(p *advRefsDecoder) decoderStateFn {
200200
if err := p.data.Capabilities.Decode(p.line); err != nil {
201201
p.error("invalid capabilities: %s", err)
202+
return nil
202203
}
203204

204205
return decodeOtherRefs

plumbing/protocol/packp/common.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ var (
1515
// common
1616
sp = []byte(" ")
1717
eol = []byte("\n")
18+
eq = []byte{'='}
1819

1920
// advrefs
2021
null = []byte("\x00")
@@ -28,4 +29,7 @@ var (
2829
deepenCommits = []byte("deepen ")
2930
deepenSince = []byte("deepen-since ")
3031
deepenReference = []byte("deepen-not ")
32+
33+
// updreq
34+
shallowNoSp = []byte("shallow")
3135
)

plumbing/protocol/packp/updreq.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package packp
2+
3+
import (
4+
"errors"
5+
6+
"gopkg.in/src-d/go-git.v4/plumbing"
7+
"gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability"
8+
)
9+
10+
var (
11+
ErrEmptyCommands = errors.New("commands cannot be empty")
12+
ErrMalformedCommand = errors.New("malformed command")
13+
)
14+
15+
// ReferenceUpdateRequest values represent reference upload requests.
16+
// Values from this type are not zero-value safe, use the New function instead.
17+
//
18+
// TODO: Add support for push-cert
19+
type ReferenceUpdateRequest struct {
20+
Capabilities *capability.List
21+
Commands []*Command
22+
Shallow *plumbing.Hash
23+
}
24+
25+
// New returns a pointer to a new ReferenceUpdateRequest value.
26+
func NewReferenceUpdateRequest() *ReferenceUpdateRequest {
27+
return &ReferenceUpdateRequest{
28+
Capabilities: capability.NewList(),
29+
Commands: nil,
30+
}
31+
}
32+
33+
func (r *ReferenceUpdateRequest) validate() error {
34+
if len(r.Commands) == 0 {
35+
return ErrEmptyCommands
36+
}
37+
38+
for _, c := range r.Commands {
39+
if err := c.validate(); err != nil {
40+
return err
41+
}
42+
}
43+
44+
return nil
45+
}
46+
47+
type Action string
48+
49+
const (
50+
Create Action = "create"
51+
Update = "update"
52+
Delete = "delete"
53+
Invalid = "invalid"
54+
)
55+
56+
type Command struct {
57+
Name string
58+
Old plumbing.Hash
59+
New plumbing.Hash
60+
}
61+
62+
func (c *Command) Action() Action {
63+
if c.Old == plumbing.ZeroHash && c.New == plumbing.ZeroHash {
64+
return Invalid
65+
}
66+
67+
if c.Old == plumbing.ZeroHash {
68+
return Create
69+
}
70+
71+
if c.New == plumbing.ZeroHash {
72+
return Delete
73+
}
74+
75+
return Update
76+
}
77+
78+
func (c *Command) validate() error {
79+
if c.Action() == Invalid {
80+
return ErrMalformedCommand
81+
}
82+
83+
return nil
84+
}
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
package packp
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"errors"
7+
"fmt"
8+
"io"
9+
10+
"gopkg.in/src-d/go-git.v4/plumbing"
11+
"gopkg.in/src-d/go-git.v4/plumbing/format/pktline"
12+
)
13+
14+
var (
15+
shallowLineLength = len(shallow) + hashSize
16+
minCommandLength = hashSize*2 + 2 + 1
17+
minCommandAndCapsLenth = minCommandLength + 1
18+
)
19+
20+
var (
21+
ErrEmpty = errors.New("empty update-request message")
22+
errNoCommands = errors.New("unexpected EOF before any command")
23+
errMissingCapabilitiesDelimiter = errors.New("capabilities delimiter not found")
24+
)
25+
26+
func errMalformedRequest(reason string) error {
27+
return fmt.Errorf("malformed request: %s", reason)
28+
}
29+
30+
func errInvalidHashSize(got int) error {
31+
return fmt.Errorf("invalid hash size: expected %d, got %d",
32+
hashSize, got)
33+
}
34+
35+
func errInvalidHash(err error) error {
36+
return fmt.Errorf("invalid hash: %s", err.Error())
37+
}
38+
39+
func errInvalidShallowLineLength(got int) error {
40+
return errMalformedRequest(fmt.Sprintf(
41+
"invalid shallow line length: expected %d, got %d",
42+
shallowLineLength, got))
43+
}
44+
45+
func errInvalidCommandCapabilitiesLineLength(got int) error {
46+
return errMalformedRequest(fmt.Sprintf(
47+
"invalid command and capabilities line length: expected at least %d, got %d",
48+
minCommandAndCapsLenth, got))
49+
}
50+
51+
func errInvalidCommandLineLength(got int) error {
52+
return errMalformedRequest(fmt.Sprintf(
53+
"invalid command line length: expected at least %d, got %d",
54+
minCommandLength, got))
55+
}
56+
57+
func errInvalidShallowObjId(err error) error {
58+
return errMalformedRequest(
59+
fmt.Sprintf("invalid shallow object id: %s", err.Error()))
60+
}
61+
62+
func errInvalidOldObjId(err error) error {
63+
return errMalformedRequest(
64+
fmt.Sprintf("invalid old object id: %s", err.Error()))
65+
}
66+
67+
func errInvalidNewObjId(err error) error {
68+
return errMalformedRequest(
69+
fmt.Sprintf("invalid new object id: %s", err.Error()))
70+
}
71+
72+
func errMalformedCommand(err error) error {
73+
return errMalformedRequest(fmt.Sprintf(
74+
"malformed command: %s", err.Error()))
75+
}
76+
77+
// Decode reads the next update-request message form the reader and wr
78+
func (req *ReferenceUpdateRequest) Decode(r io.Reader) error {
79+
d := &updReqDecoder{s: pktline.NewScanner(r)}
80+
return d.Decode(req)
81+
}
82+
83+
type updReqDecoder struct {
84+
s *pktline.Scanner
85+
r *ReferenceUpdateRequest
86+
}
87+
88+
func (d *updReqDecoder) Decode(r *ReferenceUpdateRequest) error {
89+
d.r = r
90+
funcs := []func() error{
91+
d.scanLine,
92+
d.decodeShallow,
93+
d.decodeCommandAndCapabilities,
94+
d.decodeCommands,
95+
r.validate,
96+
}
97+
98+
for _, f := range funcs {
99+
if err := f(); err != nil {
100+
return err
101+
}
102+
}
103+
104+
return nil
105+
}
106+
107+
func (d *updReqDecoder) scanLine() error {
108+
if ok := d.s.Scan(); !ok {
109+
return d.scanErrorOr(ErrEmpty)
110+
}
111+
112+
return nil
113+
}
114+
115+
func (d *updReqDecoder) decodeShallow() error {
116+
b := d.s.Bytes()
117+
118+
if !bytes.HasPrefix(b, shallowNoSp) {
119+
return nil
120+
}
121+
122+
if len(b) != shallowLineLength {
123+
return errInvalidShallowLineLength(len(b))
124+
}
125+
126+
h, err := parseHash(string(b[len(shallow):]))
127+
if err != nil {
128+
return errInvalidShallowObjId(err)
129+
}
130+
131+
if ok := d.s.Scan(); !ok {
132+
return d.scanErrorOr(errNoCommands)
133+
}
134+
135+
d.r.Shallow = &h
136+
137+
return nil
138+
}
139+
140+
func (d *updReqDecoder) decodeCommands() error {
141+
for {
142+
b := d.s.Bytes()
143+
if bytes.Equal(b, pktline.Flush) {
144+
return nil
145+
}
146+
147+
c, err := parseCommand(b)
148+
if err != nil {
149+
return err
150+
}
151+
152+
d.r.Commands = append(d.r.Commands, c)
153+
154+
if ok := d.s.Scan(); !ok {
155+
return d.s.Err()
156+
}
157+
}
158+
}
159+
160+
func (d *updReqDecoder) decodeCommandAndCapabilities() error {
161+
b := d.s.Bytes()
162+
i := bytes.IndexByte(b, 0)
163+
if i == -1 {
164+
return errMissingCapabilitiesDelimiter
165+
}
166+
167+
if len(b) < minCommandAndCapsLenth {
168+
return errInvalidCommandCapabilitiesLineLength(len(b))
169+
}
170+
171+
cmd, err := parseCommand(b[:i])
172+
if err != nil {
173+
return err
174+
}
175+
176+
d.r.Commands = append(d.r.Commands, cmd)
177+
178+
if err := d.r.Capabilities.Decode(b[i+1:]); err != nil {
179+
return err
180+
}
181+
182+
if err := d.scanLine(); err != nil {
183+
return err
184+
}
185+
186+
return nil
187+
}
188+
189+
func parseCommand(b []byte) (*Command, error) {
190+
if len(b) < minCommandLength {
191+
return nil, errInvalidCommandLineLength(len(b))
192+
}
193+
194+
var os, ns, n string
195+
if _, err := fmt.Sscanf(string(b), "%s %s %s", &os, &ns, &n); err != nil {
196+
return nil, errMalformedCommand(err)
197+
}
198+
199+
oh, err := parseHash(os)
200+
if err != nil {
201+
return nil, errInvalidOldObjId(err)
202+
}
203+
204+
nh, err := parseHash(ns)
205+
if err != nil {
206+
return nil, errInvalidNewObjId(err)
207+
}
208+
209+
return &Command{Old: oh, New: nh, Name: n}, nil
210+
}
211+
212+
func parseHash(s string) (plumbing.Hash, error) {
213+
if len(s) != hashSize {
214+
return plumbing.ZeroHash, errInvalidHashSize(len(s))
215+
}
216+
217+
if _, err := hex.DecodeString(s); err != nil {
218+
return plumbing.ZeroHash, errInvalidHash(err)
219+
}
220+
221+
h := plumbing.NewHash(s)
222+
return h, nil
223+
}
224+
225+
func (d *updReqDecoder) scanErrorOr(origErr error) error {
226+
if err := d.s.Err(); err != nil {
227+
return err
228+
}
229+
230+
return origErr
231+
}

0 commit comments

Comments
 (0)