diff options
Diffstat (limited to 'formats/packp')
-rw-r--r-- | formats/packp/pktline/pktline.go | 114 | ||||
-rw-r--r-- | formats/packp/pktline/pktline_test.go | 199 | ||||
-rw-r--r-- | formats/packp/pktline/pktlines.go | 140 | ||||
-rw-r--r-- | formats/packp/pktline/pktlines_test.go | 231 | ||||
-rw-r--r-- | formats/packp/pktline/scanner_test.go | 43 |
5 files changed, 394 insertions, 333 deletions
diff --git a/formats/packp/pktline/pktline.go b/formats/packp/pktline/pktline.go deleted file mode 100644 index 58c36fe..0000000 --- a/formats/packp/pktline/pktline.go +++ /dev/null @@ -1,114 +0,0 @@ -// Package pktline implements reading and creating pkt-lines as per -// https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt. -package pktline - -import ( - "bytes" - "errors" - "io" - "strings" -) - -const ( - // MaxPayloadSize is the maximum payload size of a pkt-line in bytes. - MaxPayloadSize = 65516 -) - -var ( - flush = []byte{'0', '0', '0', '0'} -) - -// PktLine values represent a succession of pkt-lines. -// Values from this type are not zero-value safe, see the functions New -// and NewFromString below. -type PktLine struct { - io.Reader -} - -// ErrPayloadTooLong is returned by New and NewFromString when any of -// the provided payloads is bigger than MaxPayloadSize. -var ErrPayloadTooLong = errors.New("payload is too long") - -// New returns the concatenation of several pkt-lines, each of them with -// the payload specified by the contents of each input byte slice. An -// empty payload byte slice will produce a flush-pkt. -func New(payloads ...[]byte) (PktLine, error) { - ret := []io.Reader{} - for _, p := range payloads { - if err := add(&ret, p); err != nil { - return PktLine{}, err - } - } - - return PktLine{io.MultiReader(ret...)}, nil -} - -func add(dst *[]io.Reader, e []byte) error { - if len(e) > MaxPayloadSize { - return ErrPayloadTooLong - } - - if len(e) == 0 { - *dst = append(*dst, bytes.NewReader(flush)) - return nil - } - - n := len(e) + 4 - *dst = append(*dst, bytes.NewReader(int16ToHex(n))) - *dst = append(*dst, bytes.NewReader(e)) - - return nil -} - -// susbtitutes fmt.Sprintf("%04x", n) to avoid memory garbage -// generation. -func int16ToHex(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) into 'b'. -func byteToAsciiHex(n byte) byte { - if n < 10 { - return byte('0' + n) - } - - return byte('a' - 10 + n) -} - -// NewFromStrings returns the concatenation of several pkt-lines, each -// of them with the payload specified by the contents of each input -// string. An empty payload string will produce a flush-pkt. -func NewFromStrings(payloads ...string) (PktLine, error) { - ret := []io.Reader{} - for _, p := range payloads { - if err := addString(&ret, p); err != nil { - return PktLine{}, err - } - } - - return PktLine{io.MultiReader(ret...)}, nil -} - -func addString(dst *[]io.Reader, s string) error { - if len(s) > MaxPayloadSize { - return ErrPayloadTooLong - } - - if len(s) == 0 { - *dst = append(*dst, bytes.NewReader(flush)) - return nil - } - - n := len(s) + 4 - *dst = append(*dst, bytes.NewReader(int16ToHex(n))) - *dst = append(*dst, strings.NewReader(s)) - - return nil -} diff --git a/formats/packp/pktline/pktline_test.go b/formats/packp/pktline/pktline_test.go deleted file mode 100644 index 3c18f53..0000000 --- a/formats/packp/pktline/pktline_test.go +++ /dev/null @@ -1,199 +0,0 @@ -package pktline_test - -import ( - "io" - "io/ioutil" - "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 SuitePktLine struct { -} - -var _ = Suite(&SuitePktLine{}) - -func (s *SuitePktLine) TestNew(c *C) { - for i, test := range [...]struct { - input [][]byte - expected []byte - }{ - { - input: [][]byte{}, - expected: []byte{}, - }, { - input: [][]byte{ - []byte(nil), - }, - expected: []byte("0000"), - }, { - input: [][]byte{ - []byte{}, - }, - expected: []byte("0000"), - }, { - input: [][]byte{ - []byte(""), - }, - expected: []byte("0000"), - }, { - input: [][]byte{ - []byte("hello\n"), - }, - expected: []byte("000ahello\n"), - }, { - input: [][]byte{ - []byte("hello\n"), - []byte("world!\n"), - []byte(""), - []byte("foo"), - []byte(""), - }, - expected: []byte("000ahello\n000bworld!\n00000007foo0000"), - }, { - input: [][]byte{ - []byte(strings.Repeat("a", pktline.MaxPayloadSize)), - }, - expected: []byte("fff0" + strings.Repeat("a", pktline.MaxPayloadSize)), - }, - } { - r, err := pktline.New(test.input...) - c.Assert(err, IsNil, Commentf("input %d = %v", i, test.input)) - - obtained, err := ioutil.ReadAll(r) - c.Assert(err, IsNil, Commentf("input %d = %v", i, test.input)) - - c.Assert(obtained, DeepEquals, test.expected, - Commentf("input %d = %v", i, test.input)) - } -} - -func (s *SuitePktLine) TestNewErrPayloadTooLong(c *C) { - for _, input := range [...][][]byte{ - [][]byte{ - []byte(strings.Repeat("a", pktline.MaxPayloadSize+1)), - }, - [][]byte{ - []byte("hello world!"), - []byte(""), - []byte(strings.Repeat("a", pktline.MaxPayloadSize+1)), - }, - [][]byte{ - []byte("hello world!"), - []byte(strings.Repeat("a", pktline.MaxPayloadSize+1)), - []byte("foo"), - }, - } { - _, err := pktline.New(input...) - - c.Assert(err, Equals, pktline.ErrPayloadTooLong, - Commentf("%v\n", input)) - } -} - -func (s *SuitePktLine) TestNewFromStrings(c *C) { - for _, test := range [...]struct { - input []string - expected []byte - }{ - { - input: []string(nil), - expected: []byte{}, - }, { - input: []string{}, - expected: []byte{}, - }, { - input: []string{""}, - expected: []byte("0000"), - }, { - input: []string{"hello\n"}, - expected: []byte("000ahello\n"), - }, { - input: []string{"hello\n", "world!\n", "", "foo", ""}, - expected: []byte("000ahello\n000bworld!\n00000007foo0000"), - }, { - input: []string{ - strings.Repeat("a", pktline.MaxPayloadSize), - }, - expected: []byte("fff0" + strings.Repeat("a", pktline.MaxPayloadSize)), - }, - } { - r, err := pktline.NewFromStrings(test.input...) - c.Assert(err, IsNil) - - obtained, err := ioutil.ReadAll(r) - c.Assert(err, IsNil) - - c.Assert(obtained, DeepEquals, test.expected, - Commentf("input = %v\n", test.input)) - } -} - -func (s *SuitePktLine) TestNewFromStringsErrPayloadTooLong(c *C) { - for _, input := range [...][]string{ - []string{ - strings.Repeat("a", pktline.MaxPayloadSize+1), - }, - []string{ - "hello world!", - "", - strings.Repeat("a", pktline.MaxPayloadSize+1), - }, - []string{ - "hello world!", - strings.Repeat("a", pktline.MaxPayloadSize+1), - "foo", - }, - } { - _, err := pktline.NewFromStrings(input...) - - c.Assert(err, Equals, pktline.ErrPayloadTooLong, - Commentf("%v\n", input)) - } -} - -func ExampleNew() { - // These are the payloads we want to turn into pkt-lines, - // the empty slice at the end will generate a flush-pkt. - payloads := [][]byte{ - []byte{'h', 'e', 'l', 'l', 'o', '\n'}, - []byte{'w', 'o', 'r', 'l', 'd', '!', '\n'}, - []byte{}, - } - - // Create the pkt-lines, ignoring errors... - pktlines, _ := pktline.New(payloads...) - - // Send the raw data to stdout, ignoring errors... - _, _ = io.Copy(os.Stdout, pktlines) - - // Output: 000ahello - // 000bworld! - // 0000 -} - -func ExampleNewFromStrings() { - // These are the payloads we want to turn into pkt-lines, - // the empty string at the end will generate a flush-pkt. - payloads := []string{ - "hello\n", - "world!\n", - "", - } - - // Create the pkt-lines, ignoring errors... - pktlines, _ := pktline.NewFromStrings(payloads...) - - // Send the raw data to stdout, ignoring errors... - _, _ = io.Copy(os.Stdout, pktlines) - - // Output: 000ahello - // 000bworld! - // 0000 -} diff --git a/formats/packp/pktline/pktlines.go b/formats/packp/pktline/pktlines.go new file mode 100644 index 0000000..c19aa2e --- /dev/null +++ b/formats/packp/pktline/pktlines.go @@ -0,0 +1,140 @@ +// Package pktline implements reading payloads form pkt-lines and creating pkt-lines from payloads. +package pktline + +import ( + "bytes" + "errors" + "io" + "strings" +) + +const ( + // MaxPayloadSize is the maximum payload size of a pkt-line in bytes. + MaxPayloadSize = 65516 +) + +var ( + flush = []byte{'0', '0', '0', '0'} +) + +// PktLines values represent a succession of pkt-lines. Values from +// this type are not zero-value safe, use the New function instead. +type PktLines struct { + r io.Reader +} + +var ( + // ErrPayloadTooLong is returned by the Add methods when any of the + // provided payloads is bigger than MaxPayloadSize. + ErrPayloadTooLong = errors.New("payload is too long") + // ErrEmptyPayload is returned by the Add methods when an empty + // payload is provided. + ErrEmptyPayload = errors.New("cannot add empty payloads") +) + +// New returns an empty PktLines (with no payloads) ready to be used. +func New() *PktLines { + return &PktLines{ + r: bytes.NewReader(nil), + } +} + +// AddFlush adds a flush-pkt to p. +func (p *PktLines) AddFlush() { + p.r = io.MultiReader(p.r, bytes.NewReader(flush)) +} + +// Add adds the slices in pp as the payloads of a +// corresponding number of pktlines. +func (p *PktLines) Add(pp ...[]byte) error { + tmp := []io.Reader{p.r} + for _, p := range pp { + if err := add(&tmp, p); err != nil { + return err + } + } + p.r = io.MultiReader(tmp...) + + return nil +} + +func add(dst *[]io.Reader, e []byte) error { + if err := checkPayloadLength(len(e)); err != nil { + return err + } + + n := len(e) + 4 + *dst = append(*dst, bytes.NewReader(asciiHex16(n))) + *dst = append(*dst, bytes.NewReader(e)) + + return nil +} + +func checkPayloadLength(n int) error { + switch { + case n < 0: + panic("unexpected negative payload length") + case n == 0: + return ErrEmptyPayload + case n > MaxPayloadSize: + return ErrPayloadTooLong + default: + 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 +} + +// AddString adds the strings in pp as payloads of a +// corresponding number of pktlines. +func (p *PktLines) AddString(pp ...string) error { + tmp := []io.Reader{p.r} + for _, p := range pp { + if err := addString(&tmp, p); err != nil { + return err + } + } + + p.r = io.MultiReader(tmp...) + + return nil +} + +func addString(dst *[]io.Reader, s string) error { + if err := checkPayloadLength(len(s)); err != nil { + return err + } + + n := len(s) + 4 + *dst = append(*dst, bytes.NewReader(asciiHex16(n))) + *dst = append(*dst, strings.NewReader(s)) + + return nil +} + +// Read reads the pktlines for the payloads added so far. +func (p *PktLines) Read(b []byte) (n int, err error) { + return p.r.Read(b) +} diff --git a/formats/packp/pktline/pktlines_test.go b/formats/packp/pktline/pktlines_test.go new file mode 100644 index 0000000..e0ba16b --- /dev/null +++ b/formats/packp/pktline/pktlines_test.go @@ -0,0 +1,231 @@ +package pktline_test + +import ( + "io" + "io/ioutil" + "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 SuitePktLine struct { +} + +var _ = Suite(&SuitePktLine{}) + +func (s *SuitePktLine) TestNewIsEmpty(c *C) { + p := pktline.New() + + b, err := ioutil.ReadAll(p) + c.Assert(err, IsNil) + c.Assert(b, DeepEquals, []byte{}) +} + +func (s *SuitePktLine) TestAddFlush(c *C) { + p := pktline.New() + p.AddFlush() + + b, err := ioutil.ReadAll(p) + c.Assert(err, IsNil) + c.Assert(string(b), DeepEquals, "0000") +} + +func (s *SuitePktLine) TestAdd(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"), + []byte("world!\n"), + []byte("foo"), + }, + expected: []byte("000ahello\n000bworld!\n0007foo"), + }, { + 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)), + }, + } { + p := pktline.New() + err := p.Add(test.input...) + c.Assert(err, IsNil, Commentf("input %d = %v", i, test.input)) + + obtained, err := ioutil.ReadAll(p) + c.Assert(err, IsNil, Commentf("input %d = %v", i, test.input)) + + c.Assert(obtained, DeepEquals, test.expected, + Commentf("input %d = %v", i, test.input)) + } +} + +func (s *SuitePktLine) TestAddErrEmptyPayload(c *C) { + for _, input := range [...][][]byte{ + [][]byte{ + []byte{}, + }, + [][]byte{ + []byte(nil), + }, + [][]byte{ + []byte("hello world!"), + []byte{}, + }, + [][]byte{ + []byte{}, + []byte("hello world!"), + }, + } { + p := pktline.New() + err := p.Add(input...) + c.Assert(err, Equals, pktline.ErrEmptyPayload) + } +} + +func (s *SuitePktLine) TestAddErrPayloadTooLong(c *C) { + for _, input := range [...][][]byte{ + [][]byte{ + []byte(strings.Repeat("a", pktline.MaxPayloadSize+1)), + }, + [][]byte{ + []byte("hello world!"), + []byte(strings.Repeat("a", pktline.MaxPayloadSize+1)), + }, + [][]byte{ + []byte("hello world!"), + []byte(strings.Repeat("a", pktline.MaxPayloadSize+1)), + []byte("foo"), + }, + } { + p := pktline.New() + err := p.Add(input...) + c.Assert(err, Equals, pktline.ErrPayloadTooLong, + Commentf("%v\n", input)) + } +} + +func (s *SuitePktLine) TestAddString(c *C) { + for i, test := range [...]struct { + input []string + expected []byte + }{ + { + input: []string{ + "hello\n", + }, + expected: []byte("000ahello\n"), + }, { + input: []string{ + "hello\n", + "world!\n", + "foo", + }, + expected: []byte("000ahello\n000bworld!\n0007foo"), + }, { + 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)), + }, + } { + p := pktline.New() + err := p.AddString(test.input...) + c.Assert(err, IsNil, Commentf("input %d = %v", i, test.input)) + + obtained, err := ioutil.ReadAll(p) + c.Assert(err, IsNil, Commentf("input %d = %v", i, test.input)) + + c.Assert(obtained, DeepEquals, test.expected, + Commentf("input %d = %v", i, test.input)) + } +} + +func (s *SuitePktLine) TestAddStringErrEmptyPayload(c *C) { + for _, input := range [...][]string{ + []string{""}, + []string{"hello world!", ""}, + []string{"", "hello world!"}, + } { + p := pktline.New() + err := p.AddString(input...) + c.Assert(err, Equals, pktline.ErrEmptyPayload) + } +} + +func (s *SuitePktLine) TestAddStringErrPayloadTooLong(c *C) { + for _, input := range [...][]string{ + []string{ + strings.Repeat("a", pktline.MaxPayloadSize+1), + }, + []string{ + "hello world!", + strings.Repeat("a", pktline.MaxPayloadSize+1), + }, + []string{ + "hello world!", + strings.Repeat("a", pktline.MaxPayloadSize+1), + "foo", + }, + } { + p := pktline.New() + err := p.AddString(input...) + c.Assert(err, Equals, pktline.ErrPayloadTooLong, + Commentf("%v\n", input)) + } +} + +func ExamplePktLines() { + // Create an empty collection of pktlines. + p := pktline.New() + + // Add two strings as payloads ("foo\n" and "bar\n"), they will + // end up as two consecutive pktlines. + p.AddString("foo\n", "bar\n") // error checks removed for brevity + + // You can also add byte slices as payloads... + p.Add([]byte("hello\n"), []byte("world!\n")) + + // Add a flush-pkt. + p.AddFlush() + + // PktLines are Readers, so you can directly read the full sequence. + io.Copy(os.Stdout, p) + + // Output: + // 0008foo + // 0008bar + // 000ahello + // 000bworld! + // 0000 +} diff --git a/formats/packp/pktline/scanner_test.go b/formats/packp/pktline/scanner_test.go index 08ca51f..de0f8df 100644 --- a/formats/packp/pktline/scanner_test.go +++ b/formats/packp/pktline/scanner_test.go @@ -41,9 +41,10 @@ func (s *SuiteScanner) TestEmptyReader(c *C) { } func (s *SuiteScanner) TestFlush(c *C) { - r, err := pktline.NewFromStrings("") - c.Assert(err, IsNil) - sc := pktline.NewScanner(r) + p := pktline.New() + p.AddFlush() + sc := pktline.NewScanner(p) + c.Assert(sc.Scan(), Equals, true) payload := sc.Bytes() c.Assert(len(payload), Equals, 0) @@ -69,9 +70,11 @@ func (s *SuiteScanner) TestScanAndPayload(c *C) { strings.Repeat("a", pktline.MaxPayloadSize), strings.Repeat("a", pktline.MaxPayloadSize-1) + "\n", } { - r, err := pktline.NewFromStrings(test) - c.Assert(err, IsNil, Commentf("input len=%x, contents=%.10q\n", len(test), test)) - sc := pktline.NewScanner(r) + p := pktline.New() + err := p.AddString(test) + c.Assert(err, IsNil, + Commentf("input len=%x, contents=%.10q\n", len(test), test)) + sc := pktline.NewScanner(p) c.Assert(sc.Scan(), Equals, true, Commentf("test = %.20q...", test)) @@ -91,8 +94,7 @@ func (s *SuiteScanner) TestSkip(c *C) { input: []string{ "first", "second", - "third", - ""}, + "third"}, n: 1, expected: []byte("second"), }, @@ -100,15 +102,15 @@ func (s *SuiteScanner) TestSkip(c *C) { input: []string{ "first", "second", - "third", - ""}, + "third"}, n: 2, expected: []byte("third"), }, } { - r, err := pktline.NewFromStrings(test.input...) + p := pktline.New() + err := p.AddString(test.input...) c.Assert(err, IsNil) - sc := pktline.NewScanner(r) + sc := pktline.NewScanner(p) for i := 0; i < test.n; i++ { c.Assert(sc.Scan(), Equals, true, Commentf("scan error = %s", sc.Err())) @@ -123,9 +125,10 @@ func (s *SuiteScanner) TestSkip(c *C) { } func (s *SuiteScanner) TestEOF(c *C) { - r, err := pktline.NewFromStrings("first", "second") + p := pktline.New() + err := p.AddString("first", "second") c.Assert(err, IsNil) - sc := pktline.NewScanner(r) + sc := pktline.NewScanner(p) for sc.Scan() { } c.Assert(sc.Err(), IsNil) @@ -161,19 +164,19 @@ func (s *SuiteScanner) TestReadSomeSections(c *C) { // 0000 // and so on func sectionsExample(c *C, nSections, nLines int) io.Reader { - ss := []string{} + p := pktline.New() 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) } - ss = append(ss, "") + err := p.AddString(ss...) + c.Assert(err, IsNil) + p.AddFlush() } - ret, err := pktline.NewFromStrings(ss...) - c.Assert(err, IsNil) - - return ret + return p } func ExampleScanner() { |