diff options
author | Alberto Cortés <alcortesm@gmail.com> | 2016-11-02 10:14:15 +0100 |
---|---|---|
committer | Máximo Cuadros <mcuadros@gmail.com> | 2016-11-02 09:14:15 +0000 |
commit | 1918bfe458729488e4a656cbbef7a2430e7ec2f5 (patch) | |
tree | d130d025584f3db0f781028db8978bb0a6a227ef /formats/packp/ulreq/decoder.go | |
parent | 6b7464a22c6177d9e0cf96e1aaaae13c127c3149 (diff) | |
download | go-git-1918bfe458729488e4a656cbbef7a2430e7ec2f5.tar.gz |
ulreq: adds encoder and decoder for upload-request messages (#106)
* ulreq: adds encoder and decoder for upload-request messages
* ulreq: stop using _test suffix for testing package names (@mcuadros comment)
Diffstat (limited to 'formats/packp/ulreq/decoder.go')
-rw-r--r-- | formats/packp/ulreq/decoder.go | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/formats/packp/ulreq/decoder.go b/formats/packp/ulreq/decoder.go new file mode 100644 index 0000000..63ccd4d --- /dev/null +++ b/formats/packp/ulreq/decoder.go @@ -0,0 +1,287 @@ +package ulreq + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "strconv" + "time" + + "gopkg.in/src-d/go-git.v4/core" + "gopkg.in/src-d/go-git.v4/formats/packp/pktline" +) + +const ( + hashSize = 40 +) + +var ( + eol = []byte("\n") + sp = []byte(" ") + want = []byte("want ") + shallow = []byte("shallow ") + deepen = []byte("deepen") + deepenCommits = []byte("deepen ") + deepenSince = []byte("deepen-since ") + deepenReference = []byte("deepen-not ") +) + +// A Decoder reads and decodes AdvRef values from an input stream. +type Decoder struct { + s *pktline.Scanner // a pkt-line scanner from the input stream + line []byte // current pkt-line contents, use parser.nextLine() to make it advance + nLine int // current pkt-line number for debugging, begins at 1 + err error // sticky error, use the parser.error() method to fill this out + data *UlReq // parsed data is stored here +} + +// NewDecoder returns a new decoder that reads from r. +// +// Will not read more data from r than necessary. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{ + s: pktline.NewScanner(r), + } +} + +// Decode reads the next upload-request form its input and +// stores it in the value pointed to by v. +func (d *Decoder) Decode(v *UlReq) error { + d.data = v + + for state := decodeFirstWant; state != nil; { + state = state(d) + } + + return d.err +} + +type decoderStateFn func(*Decoder) decoderStateFn + +// fills out the parser stiky error +func (d *Decoder) error(format string, a ...interface{}) { + d.err = fmt.Errorf("pkt-line %d: %s", d.nLine, + fmt.Sprintf(format, a...)) +} + +// Reads a new pkt-line from the scanner, makes its payload available as +// p.line and increments p.nLine. A successful invocation returns true, +// otherwise, false is returned and the sticky error is filled out +// accordingly. Trims eols at the end of the payloads. +func (d *Decoder) nextLine() bool { + d.nLine++ + + if !d.s.Scan() { + if d.err = d.s.Err(); d.err != nil { + return false + } + + d.error("EOF") + return false + } + + d.line = d.s.Bytes() + d.line = bytes.TrimSuffix(d.line, eol) + + return true +} + +// Expected format: want <hash>[ capabilities] +func decodeFirstWant(d *Decoder) decoderStateFn { + if ok := d.nextLine(); !ok { + return nil + } + + if !bytes.HasPrefix(d.line, want) { + d.error("missing 'want ' prefix") + return nil + } + d.line = bytes.TrimPrefix(d.line, want) + + hash, ok := d.readHash() + if !ok { + return nil + } + d.data.Wants = append(d.data.Wants, hash) + + return decodeCaps +} + +func (d *Decoder) readHash() (core.Hash, bool) { + if len(d.line) < hashSize { + d.err = fmt.Errorf("malformed hash: %v", d.line) + return core.ZeroHash, false + } + + var hash core.Hash + if _, err := hex.Decode(hash[:], d.line[:hashSize]); err != nil { + d.error("invalid hash text: %s", err) + return core.ZeroHash, false + } + d.line = d.line[hashSize:] + + return hash, true +} + +// Expected format: sp cap1 sp cap2 sp cap3... +func decodeCaps(d *Decoder) decoderStateFn { + if len(d.line) == 0 { + return decodeOtherWants + } + + d.line = bytes.TrimPrefix(d.line, sp) + + for _, c := range bytes.Split(d.line, sp) { + name, values := readCapability(c) + d.data.Capabilities.Add(name, values...) + } + + return decodeOtherWants +} + +// Capabilities are a single string or a name=value. +// Even though we are only going to read at moust 1 value, we return +// a slice of values, as Capability.Add receives that. +func readCapability(data []byte) (name string, values []string) { + pair := bytes.SplitN(data, []byte{'='}, 2) + if len(pair) == 2 { + values = append(values, string(pair[1])) + } + + return string(pair[0]), values +} + +// Expected format: want <hash> +func decodeOtherWants(d *Decoder) decoderStateFn { + if ok := d.nextLine(); !ok { + return nil + } + + if bytes.HasPrefix(d.line, shallow) { + return decodeShallow + } + + if bytes.HasPrefix(d.line, deepen) { + return decodeDeepen + } + + if len(d.line) == 0 { + return nil + } + + if !bytes.HasPrefix(d.line, want) { + d.error("unexpected payload while expecting a want: %q", d.line) + return nil + } + d.line = bytes.TrimPrefix(d.line, want) + + hash, ok := d.readHash() + if !ok { + return nil + } + d.data.Wants = append(d.data.Wants, hash) + + return decodeOtherWants +} + +// Expected format: shallow <hash> +func decodeShallow(d *Decoder) decoderStateFn { + if bytes.HasPrefix(d.line, deepen) { + return decodeDeepen + } + + if len(d.line) == 0 { + return nil + } + + if !bytes.HasPrefix(d.line, shallow) { + d.error("unexpected payload while expecting a shallow: %q", d.line) + return nil + } + d.line = bytes.TrimPrefix(d.line, shallow) + + hash, ok := d.readHash() + if !ok { + return nil + } + d.data.Shallows = append(d.data.Shallows, hash) + + if ok := d.nextLine(); !ok { + return nil + } + + return decodeShallow +} + +// Expected format: deepen <n> / deepen-since <ul> / deepen-not <ref> +func decodeDeepen(d *Decoder) decoderStateFn { + if bytes.HasPrefix(d.line, deepenCommits) { + return decodeDeepenCommits + } + + if bytes.HasPrefix(d.line, deepenSince) { + return decodeDeepenSince + } + + if bytes.HasPrefix(d.line, deepenReference) { + return decodeDeepenReference + } + + if len(d.line) == 0 { + return nil + } + + d.error("unexpected deepen specification: %q", d.line) + return nil +} + +func decodeDeepenCommits(d *Decoder) decoderStateFn { + d.line = bytes.TrimPrefix(d.line, deepenCommits) + + var n int + if n, d.err = strconv.Atoi(string(d.line)); d.err != nil { + return nil + } + if n < 0 { + d.err = fmt.Errorf("negative depth") + return nil + } + d.data.Depth = DepthCommits(n) + + return decodeFlush +} + +func decodeDeepenSince(d *Decoder) decoderStateFn { + d.line = bytes.TrimPrefix(d.line, deepenSince) + + var secs int64 + secs, d.err = strconv.ParseInt(string(d.line), 10, 64) + if d.err != nil { + return nil + } + t := time.Unix(secs, 0).UTC() + d.data.Depth = DepthSince(t) + + return decodeFlush +} + +func decodeDeepenReference(d *Decoder) decoderStateFn { + d.line = bytes.TrimPrefix(d.line, deepenReference) + + d.data.Depth = DepthReference(string(d.line)) + + return decodeFlush +} + +func decodeFlush(d *Decoder) decoderStateFn { + if ok := d.nextLine(); !ok { + return nil + } + + if len(d.line) != 0 { + d.err = fmt.Errorf("unexpected payload while expecting a flush-pkt: %q", d.line) + } + + return nil +} |