diff options
author | Máximo Cuadros <mcuadros@gmail.com> | 2016-11-08 23:46:38 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-11-08 23:46:38 +0100 |
commit | ac095bb12c4d29722b60ba9f20590fa7cfa6bc7d (patch) | |
tree | 223f36f336ba3414b1e45cac8af6c4744a5d7ef6 /formats/packp/pktline | |
parent | e523701393598f4fa241dd407af9ff8925507a1a (diff) | |
download | go-git-ac095bb12c4d29722b60ba9f20590fa7cfa6bc7d.tar.gz |
new plumbing package (#118)
* plumbing: now core was renamed to core, and formats and clients moved inside
Diffstat (limited to 'formats/packp/pktline')
-rw-r--r-- | formats/packp/pktline/encoder.go | 123 | ||||
-rw-r--r-- | formats/packp/pktline/encoder_test.go | 249 | ||||
-rw-r--r-- | formats/packp/pktline/scanner.go | 133 | ||||
-rw-r--r-- | formats/packp/pktline/scanner_test.go | 225 |
4 files changed, 0 insertions, 730 deletions
diff --git a/formats/packp/pktline/encoder.go b/formats/packp/pktline/encoder.go deleted file mode 100644 index 0a88a9b..0000000 --- a/formats/packp/pktline/encoder.go +++ /dev/null @@ -1,123 +0,0 @@ -// Package pktline implements reading payloads form pkt-lines and encoding pkt-lines from payloads. -package pktline - -import ( - "bytes" - "errors" - "fmt" - "io" -) - -// An Encoder writes pkt-lines to an output stream. -type Encoder struct { - w io.Writer -} - -const ( - // MaxPayloadSize is the maximum payload size of a pkt-line in bytes. - MaxPayloadSize = 65516 -) - -var ( - // FlushPkt are the contents of a flush-pkt pkt-line. - FlushPkt = []byte{'0', '0', '0', '0'} - // Flush is the payload to use with the Encode method to encode a flush-pkt. - Flush = []byte{} - // FlushString is the payload to use with the EncodeString method to encode a flush-pkt. - FlushString = "" - // ErrPayloadTooLong is returned by the Encode methods when any of the - // provided payloads is bigger than MaxPayloadSize. - ErrPayloadTooLong = errors.New("payload is too long") -) - -// NewEncoder returns a new encoder that writes to w. -func NewEncoder(w io.Writer) *Encoder { - return &Encoder{ - w: w, - } -} - -// Flush encodes a flush-pkt to the output stream. -func (e *Encoder) Flush() error { - _, err := e.w.Write(FlushPkt) - return err -} - -// Encode encodes a pkt-line with the payload specified and write it to -// the output stream. If several payloads are specified, each of them -// will get streamed in their own pkt-lines. -func (e *Encoder) Encode(payloads ...[]byte) error { - for _, p := range payloads { - if err := e.encodeLine(p); err != nil { - return err - } - } - - return nil -} - -func (e *Encoder) encodeLine(p []byte) error { - if len(p) > MaxPayloadSize { - return ErrPayloadTooLong - } - - if bytes.Equal(p, Flush) { - if err := e.Flush(); err != nil { - return err - } - return nil - } - - n := len(p) + 4 - if _, err := e.w.Write(asciiHex16(n)); err != nil { - return err - } - if _, err := e.w.Write(p); err != nil { - return err - } - - return nil -} - -// Returns the hexadecimal ascii representation of the 16 less -// significant bits of n. The length of the returned slice will always -// be 4. Example: if n is 1234 (0x4d2), the return value will be -// []byte{'0', '4', 'd', '2'}. -func asciiHex16(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) to 'b'. -func byteToASCIIHex(n byte) byte { - if n < 10 { - return '0' + n - } - - return 'a' - 10 + n -} - -// EncodeString works similarly as Encode but payloads are specified as strings. -func (e *Encoder) EncodeString(payloads ...string) error { - for _, p := range payloads { - if err := e.Encode([]byte(p)); err != nil { - return err - } - } - - return nil -} - -// Encodef encodes a single pkt-line with the payload formatted as -// the format specifier and the rest of the arguments suggest. -func (e *Encoder) Encodef(format string, a ...interface{}) error { - return e.EncodeString( - fmt.Sprintf(format, a...), - ) -} diff --git a/formats/packp/pktline/encoder_test.go b/formats/packp/pktline/encoder_test.go deleted file mode 100644 index 618002d..0000000 --- a/formats/packp/pktline/encoder_test.go +++ /dev/null @@ -1,249 +0,0 @@ -package pktline_test - -import ( - "bytes" - "os" - "strings" - "testing" - - "gopkg.in/src-d/go-git.v4/formats/packp/pktline" - - . "gopkg.in/check.v1" -) - -func Test(t *testing.T) { TestingT(t) } - -type SuiteEncoder struct{} - -var _ = Suite(&SuiteEncoder{}) - -func (s *SuiteEncoder) TestFlush(c *C) { - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - - err := e.Flush() - c.Assert(err, IsNil) - - obtained := buf.Bytes() - c.Assert(obtained, DeepEquals, pktline.FlushPkt) -} - -func (s *SuiteEncoder) TestEncode(c *C) { - for i, test := range [...]struct { - input [][]byte - expected []byte - }{ - { - input: [][]byte{ - []byte("hello\n"), - }, - expected: []byte("000ahello\n"), - }, { - input: [][]byte{ - []byte("hello\n"), - pktline.Flush, - }, - expected: []byte("000ahello\n0000"), - }, { - input: [][]byte{ - []byte("hello\n"), - []byte("world!\n"), - []byte("foo"), - }, - expected: []byte("000ahello\n000bworld!\n0007foo"), - }, { - input: [][]byte{ - []byte("hello\n"), - pktline.Flush, - []byte("world!\n"), - []byte("foo"), - pktline.Flush, - }, - expected: []byte("000ahello\n0000000bworld!\n0007foo0000"), - }, { - input: [][]byte{ - []byte(strings.Repeat("a", pktline.MaxPayloadSize)), - }, - expected: []byte( - "fff0" + strings.Repeat("a", pktline.MaxPayloadSize)), - }, { - input: [][]byte{ - []byte(strings.Repeat("a", pktline.MaxPayloadSize)), - []byte(strings.Repeat("b", pktline.MaxPayloadSize)), - }, - expected: []byte( - "fff0" + strings.Repeat("a", pktline.MaxPayloadSize) + - "fff0" + strings.Repeat("b", pktline.MaxPayloadSize)), - }, - } { - comment := Commentf("input %d = %v\n", i, test.input) - - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - - err := e.Encode(test.input...) - c.Assert(err, IsNil, comment) - - c.Assert(buf.Bytes(), DeepEquals, test.expected, comment) - } -} - -func (s *SuiteEncoder) TestEncodeErrPayloadTooLong(c *C) { - for i, input := range [...][][]byte{ - { - []byte(strings.Repeat("a", pktline.MaxPayloadSize+1)), - }, - { - []byte("hello world!"), - []byte(strings.Repeat("a", pktline.MaxPayloadSize+1)), - }, - { - []byte("hello world!"), - []byte(strings.Repeat("a", pktline.MaxPayloadSize+1)), - []byte("foo"), - }, - } { - comment := Commentf("input %d = %v\n", i, input) - - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - - err := e.Encode(input...) - c.Assert(err, Equals, pktline.ErrPayloadTooLong, comment) - } -} - -func (s *SuiteEncoder) TestEncodeStrings(c *C) { - for i, test := range [...]struct { - input []string - expected []byte - }{ - { - input: []string{ - "hello\n", - }, - expected: []byte("000ahello\n"), - }, { - input: []string{ - "hello\n", - pktline.FlushString, - }, - expected: []byte("000ahello\n0000"), - }, { - input: []string{ - "hello\n", - "world!\n", - "foo", - }, - expected: []byte("000ahello\n000bworld!\n0007foo"), - }, { - input: []string{ - "hello\n", - pktline.FlushString, - "world!\n", - "foo", - pktline.FlushString, - }, - expected: []byte("000ahello\n0000000bworld!\n0007foo0000"), - }, { - input: []string{ - strings.Repeat("a", pktline.MaxPayloadSize), - }, - expected: []byte( - "fff0" + strings.Repeat("a", pktline.MaxPayloadSize)), - }, { - input: []string{ - strings.Repeat("a", pktline.MaxPayloadSize), - strings.Repeat("b", pktline.MaxPayloadSize), - }, - expected: []byte( - "fff0" + strings.Repeat("a", pktline.MaxPayloadSize) + - "fff0" + strings.Repeat("b", pktline.MaxPayloadSize)), - }, - } { - comment := Commentf("input %d = %v\n", i, test.input) - - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - - err := e.EncodeString(test.input...) - c.Assert(err, IsNil, comment) - c.Assert(buf.Bytes(), DeepEquals, test.expected, comment) - } -} - -func (s *SuiteEncoder) TestEncodeStringErrPayloadTooLong(c *C) { - for i, input := range [...][]string{ - { - strings.Repeat("a", pktline.MaxPayloadSize+1), - }, - { - "hello world!", - strings.Repeat("a", pktline.MaxPayloadSize+1), - }, - { - "hello world!", - strings.Repeat("a", pktline.MaxPayloadSize+1), - "foo", - }, - } { - comment := Commentf("input %d = %v\n", i, input) - - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - - err := e.EncodeString(input...) - c.Assert(err, Equals, pktline.ErrPayloadTooLong, comment) - } -} - -func (s *SuiteEncoder) TestEncodef(c *C) { - format := " %s %d\n" - str := "foo" - d := 42 - - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - - err := e.Encodef(format, str, d) - c.Assert(err, IsNil) - - expected := []byte("000c foo 42\n") - c.Assert(buf.Bytes(), DeepEquals, expected) -} - -func ExampleEncoder() { - // Create an encoder that writes pktlines to stdout. - e := pktline.NewEncoder(os.Stdout) - - // Encode some data as a new pkt-line. - _ = e.Encode([]byte("data\n")) // error checks removed for brevity - - // Encode a flush-pkt. - _ = e.Flush() - - // Encode a couple of byte slices and a flush in one go. Each of - // them will end up as payloads of their own pktlines. - _ = e.Encode( - []byte("hello\n"), - []byte("world!\n"), - pktline.Flush, - ) - - // You can also encode strings: - _ = e.EncodeString( - "foo\n", - "bar\n", - pktline.FlushString, - ) - - // You can also format and encode a payload: - _ = e.Encodef(" %s %d\n", "foo", 42) - // Output: - // 0009data - // 0000000ahello - // 000bworld! - // 00000008foo - // 0008bar - // 0000000c foo 42 -} diff --git a/formats/packp/pktline/scanner.go b/formats/packp/pktline/scanner.go deleted file mode 100644 index 3ce2adf..0000000 --- a/formats/packp/pktline/scanner.go +++ /dev/null @@ -1,133 +0,0 @@ -package pktline - -import ( - "errors" - "io" -) - -const ( - lenSize = 4 -) - -// ErrInvalidPktLen is returned by Err() when an invalid pkt-len is found. -var ErrInvalidPktLen = errors.New("invalid pkt-len found") - -// Scanner provides a convenient interface for reading the payloads of a -// series of pkt-lines. It takes an io.Reader providing the source, -// which then can be tokenized through repeated calls to the Scan -// method. -// -// After each Scan call, the Bytes method will return the payload of the -// corresponding pkt-line on a shared buffer, which will be 65516 bytes -// or smaller. Flush pkt-lines are represented by empty byte slices. -// -// Scanning stops at EOF or the first I/O error. -type Scanner struct { - r io.Reader // The reader provided by the client - err error // Sticky error - payload []byte // Last pkt-payload - len [lenSize]byte // Last pkt-len -} - -// NewScanner returns a new Scanner to read from r. -func NewScanner(r io.Reader) *Scanner { - return &Scanner{ - r: r, - } -} - -// Err returns the first error encountered by the Scanner. -func (s *Scanner) Err() error { - return s.err -} - -// Scan advances the Scanner to the next pkt-line, whose payload will -// then be available through the Bytes method. Scanning stops at EOF -// or the first I/O error. After Scan returns false, the Err method -// will return any error that occurred during scanning, except that if -// it was io.EOF, Err will return nil. -func (s *Scanner) Scan() bool { - var l int - l, s.err = s.readPayloadLen() - if s.err == io.EOF { - s.err = nil - return false - } - if s.err != nil { - return false - } - - if cap(s.payload) < l { - s.payload = make([]byte, 0, l) - } - - if _, s.err = io.ReadFull(s.r, s.payload[:l]); s.err != nil { - return false - } - s.payload = s.payload[:l] - - return true -} - -// Bytes returns the most recent payload generated by a call to Scan. -// The underlying array may point to data that will be overwritten by a -// subsequent call to Scan. It does no allocation. -func (s *Scanner) Bytes() []byte { - return s.payload -} - -// Method readPayloadLen returns the payload length by reading the -// pkt-len and substracting the pkt-len size. -func (s *Scanner) readPayloadLen() (int, error) { - if _, err := io.ReadFull(s.r, s.len[:]); err != nil { - if err == io.EOF { - return 0, err - } - return 0, ErrInvalidPktLen - } - - n, err := hexDecode(s.len) - if err != nil { - return 0, err - } - - switch { - case n == 0: - return 0, nil - case n <= lenSize: - return 0, ErrInvalidPktLen - case n > MaxPayloadSize+lenSize: - return 0, ErrInvalidPktLen - default: - return n - lenSize, nil - } -} - -// Turns the hexadecimal representation of a number in a byte slice into -// a number. This function substitute strconv.ParseUint(string(buf), 16, -// 16) and/or hex.Decode, to avoid generating new strings, thus helping the -// GC. -func hexDecode(buf [lenSize]byte) (int, error) { - var ret int - for i := 0; i < lenSize; i++ { - n, err := asciiHexToByte(buf[i]) - if err != nil { - return 0, ErrInvalidPktLen - } - ret = 16*ret + int(n) - } - return ret, nil -} - -// turns the hexadecimal ascii representation of a byte into its -// numerical value. Example: from 'b' to 11 (0xb). -func asciiHexToByte(b byte) (byte, error) { - switch { - case b >= '0' && b <= '9': - return b - '0', nil - case b >= 'a' && b <= 'f': - return b - 'a' + 10, nil - default: - return 0, ErrInvalidPktLen - } -} diff --git a/formats/packp/pktline/scanner_test.go b/formats/packp/pktline/scanner_test.go deleted file mode 100644 index b5a3c7d..0000000 --- a/formats/packp/pktline/scanner_test.go +++ /dev/null @@ -1,225 +0,0 @@ -package pktline_test - -import ( - "bytes" - "fmt" - "io" - "strings" - - "gopkg.in/src-d/go-git.v4/formats/packp/pktline" - - . "gopkg.in/check.v1" -) - -type SuiteScanner struct{} - -var _ = Suite(&SuiteScanner{}) - -func (s *SuiteScanner) TestInvalid(c *C) { - for _, test := range [...]string{ - "0001", "0002", "0003", "0004", - "0001asdfsadf", "0004foo", - "fff1", "fff2", - "gorka", - "0", "003", - " 5a", "5 a", "5 \n", - "-001", "-000", - } { - r := strings.NewReader(test) - sc := pktline.NewScanner(r) - _ = sc.Scan() - c.Assert(sc.Err(), ErrorMatches, pktline.ErrInvalidPktLen.Error(), - Commentf("data = %q", test)) - } -} - -func (s *SuiteScanner) TestEmptyReader(c *C) { - r := strings.NewReader("") - sc := pktline.NewScanner(r) - hasPayload := sc.Scan() - c.Assert(hasPayload, Equals, false) - c.Assert(sc.Err(), Equals, nil) -} - -func (s *SuiteScanner) TestFlush(c *C) { - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - err := e.Flush() - c.Assert(err, IsNil) - - sc := pktline.NewScanner(&buf) - c.Assert(sc.Scan(), Equals, true) - - payload := sc.Bytes() - c.Assert(len(payload), Equals, 0) -} - -func (s *SuiteScanner) TestPktLineTooShort(c *C) { - r := strings.NewReader("010cfoobar") - - sc := pktline.NewScanner(r) - - c.Assert(sc.Scan(), Equals, false) - c.Assert(sc.Err(), ErrorMatches, "unexpected EOF") -} - -func (s *SuiteScanner) TestScanAndPayload(c *C) { - for _, test := range [...]string{ - "a", - "a\n", - strings.Repeat("a", 100), - strings.Repeat("a", 100) + "\n", - strings.Repeat("\x00", 100), - strings.Repeat("\x00", 100) + "\n", - strings.Repeat("a", pktline.MaxPayloadSize), - strings.Repeat("a", pktline.MaxPayloadSize-1) + "\n", - } { - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - err := e.EncodeString(test) - c.Assert(err, IsNil, - Commentf("input len=%x, contents=%.10q\n", len(test), test)) - - sc := pktline.NewScanner(&buf) - c.Assert(sc.Scan(), Equals, true, - Commentf("test = %.20q...", test)) - - obtained := sc.Bytes() - c.Assert(obtained, DeepEquals, []byte(test), - Commentf("in = %.20q out = %.20q", test, string(obtained))) - } -} - -func (s *SuiteScanner) TestSkip(c *C) { - for _, test := range [...]struct { - input []string - n int - expected []byte - }{ - { - input: []string{ - "first", - "second", - "third"}, - n: 1, - expected: []byte("second"), - }, - { - input: []string{ - "first", - "second", - "third"}, - n: 2, - expected: []byte("third"), - }, - } { - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - err := e.EncodeString(test.input...) - c.Assert(err, IsNil) - - sc := pktline.NewScanner(&buf) - for i := 0; i < test.n; i++ { - c.Assert(sc.Scan(), Equals, true, - Commentf("scan error = %s", sc.Err())) - } - c.Assert(sc.Scan(), Equals, true, - Commentf("scan error = %s", sc.Err())) - - obtained := sc.Bytes() - c.Assert(obtained, DeepEquals, test.expected, - Commentf("\nin = %.20q\nout = %.20q\nexp = %.20q", - test.input, obtained, test.expected)) - } -} - -func (s *SuiteScanner) TestEOF(c *C) { - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - err := e.EncodeString("first", "second") - c.Assert(err, IsNil) - - sc := pktline.NewScanner(&buf) - for sc.Scan() { - } - c.Assert(sc.Err(), IsNil) -} - -// A section are several non flush-pkt lines followed by a flush-pkt, which -// how the git protocol sends long messages. -func (s *SuiteScanner) TestReadSomeSections(c *C) { - nSections := 2 - nLines := 4 - data := sectionsExample(c, nSections, nLines) - sc := pktline.NewScanner(data) - - sectionCounter := 0 - lineCounter := 0 - for sc.Scan() { - if len(sc.Bytes()) == 0 { - sectionCounter++ - } - lineCounter++ - } - c.Assert(sc.Err(), IsNil) - c.Assert(sectionCounter, Equals, nSections) - c.Assert(lineCounter, Equals, (1+nLines)*nSections) -} - -// returns nSection sections, each of them with nLines pkt-lines (not -// counting the flush-pkt: -// -// 0009 0.0\n -// 0009 0.1\n -// ... -// 0000 -// and so on -func sectionsExample(c *C, nSections, nLines int) io.Reader { - var buf bytes.Buffer - e := pktline.NewEncoder(&buf) - - for section := 0; section < nSections; section++ { - ss := []string{} - for line := 0; line < nLines; line++ { - line := fmt.Sprintf(" %d.%d\n", section, line) - ss = append(ss, line) - } - err := e.EncodeString(ss...) - c.Assert(err, IsNil) - err = e.Flush() - c.Assert(err, IsNil) - } - - return &buf -} - -func ExampleScanner() { - // A reader is needed as input. - input := strings.NewReader("000ahello\n" + - "000bworld!\n" + - "0000", - ) - - // Create the scanner... - s := pktline.NewScanner(input) - - // and scan every pkt-line found in the input. - for s.Scan() { - payload := s.Bytes() - if len(payload) == 0 { // zero sized payloads correspond to flush-pkts. - fmt.Println("FLUSH-PKT DETECTED\n") - } else { // otherwise, you will be able to access the full payload. - fmt.Printf("PAYLOAD = %q\n", string(payload)) - } - } - - // this will catch any error when reading from the input, if any. - if s.Err() != nil { - fmt.Println(s.Err()) - } - - // Output: - // PAYLOAD = "hello\n" - // PAYLOAD = "world!\n" - // FLUSH-PKT DETECTED -} |