package ulreq import ( "bytes" "encoding/hex" "fmt" "io" "strconv" "time" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/format/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 [ 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() (plumbing.Hash, bool) { if len(d.line) < hashSize { d.err = fmt.Errorf("malformed hash: %v", d.line) return plumbing.ZeroHash, false } var hash plumbing.Hash if _, err := hex.Decode(hash[:], d.line[:hashSize]); err != nil { d.error("invalid hash text: %s", err) return plumbing.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 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 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 / deepen-since
    / deepen-not 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 }