go-git.v4: gopkg.in/src-d/go-git.v4/plumbing/format/pktline Index | Examples | Files

package pktline

import "gopkg.in/src-d/go-git.v4/plumbing/format/pktline"

Package pktline implements reading payloads form pkt-lines and encoding pkt-lines from payloads.

Index

Examples

Package Files

encoder.go scanner.go

Constants

const (
    // MaxPayloadSize is the maximum payload size of a pkt-line in bytes.
    MaxPayloadSize = 65516

    // For compatibility with canonical Git implementation, accept longer pkt-lines
    OversizePayloadMax = 65520
)

Variables

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")
)
var ErrInvalidPktLen = errors.New("invalid pkt-len found")

ErrInvalidPktLen is returned by Err() when an invalid pkt-len is found.

type Encoder Uses

type Encoder struct {
    // contains filtered or unexported fields
}

An Encoder writes pkt-lines to an output stream.

Code:

// 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

func NewEncoder Uses

func NewEncoder(w io.Writer) *Encoder

NewEncoder returns a new encoder that writes to w.

func (*Encoder) Encode Uses

func (e *Encoder) Encode(payloads ...[]byte) error

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 (*Encoder) EncodeString Uses

func (e *Encoder) EncodeString(payloads ...string) error

EncodeString works similarly as Encode but payloads are specified as strings.

func (*Encoder) Encodef Uses

func (e *Encoder) Encodef(format string, a ...interface{}) error

Encodef encodes a single pkt-line with the payload formatted as the format specifier. The rest of the arguments will be used in the format string.

func (*Encoder) Flush Uses

func (e *Encoder) Flush() error

Flush encodes a flush-pkt to the output stream.

type Scanner Uses

type Scanner struct {
    // contains filtered or unexported fields
}

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.

Code:

package main

import (
    "bytes"
    "errors"
    "fmt"
    "io"
    "strings"

    "gopkg.in/src-d/go-git.v4/plumbing/format/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",
        "fff5", "ffff",
        "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) TestDecodeOversizePktLines(c *C) {
    for _, test := range [...]string{
        "fff1" + strings.Repeat("a", 0xfff1),
        "fff2" + strings.Repeat("a", 0xfff2),
        "fff3" + strings.Repeat("a", 0xfff3),
        "fff4" + strings.Repeat("a", 0xfff4),
    } {
        r := strings.NewReader(test)
        sc := pktline.NewScanner(r)
        _ = sc.Scan()
        c.Assert(sc.Err(), IsNil)
    }
}

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)
}

type mockReader struct{}

func (r *mockReader) Read([]byte) (int, error) { return 0, errors.New("foo") }

func (s *SuiteScanner) TestInternalReadError(c *C) {
    sc := pktline.NewScanner(&mockReader{})
    c.Assert(sc.Scan(), Equals, false)
    c.Assert(sc.Err(), ErrorMatches, "foo")
}

// 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 main() {
    // 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")
        } 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())
    }

}

func NewScanner Uses

func NewScanner(r io.Reader) *Scanner

NewScanner returns a new Scanner to read from r.

func (*Scanner) Bytes Uses

func (s *Scanner) Bytes() []byte

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 (*Scanner) Err Uses

func (s *Scanner) Err() error

Err returns the first error encountered by the Scanner.

func (*Scanner) Scan Uses

func (s *Scanner) Scan() bool

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.

Package pktline imports 4 packages (graph) and is imported by 17 packages. Updated 2019-08-01. Refresh now. Tools for package owners.