package gitclient
import (
"bytes"
"errors"
"fmt"
"io"
"strconv"
)
type pktKind int
const (
pktData pktKind = iota
pktFlush
pktDelim
pktResponseEnd
)
const maxPktLen = 65520
func writePkt(w io.Writer, line string) error {
if len(line)+4 > maxPktLen {
return fmt.Errorf("pkt-line too long: %d bytes", len(line))
}
_, err := fmt.Fprintf(w, "%04x%s", len(line)+4, line)
return err
}
func writeFlush(w io.Writer) error {
_, err := io.WriteString(w, "0000")
return err
}
func writeDelim(w io.Writer) error {
_, err := io.WriteString(w, "0001")
return err
}
type pktReader struct {
r io.Reader
buf []byte
}
func newPktReader(r io.Reader) *pktReader {
return &pktReader{r: r, buf: make([]byte, maxPktLen)}
}
// payload is only valid until the next call
func (p *pktReader) next() (pktKind, []byte, error) {
if _, err := io.ReadFull(p.r, p.buf[:4]); err != nil {
if errors.Is(err, io.EOF) {
err = io.ErrUnexpectedEOF
}
return 0, nil, err
}
n, err := strconv.ParseUint(string(p.buf[:4]), 16, 16)
if err != nil {
return 0, nil, fmt.Errorf("bad pkt-line length %q", p.buf[:4])
}
switch {
case n == 0:
return pktFlush, nil, nil
case n == 1:
return pktDelim, nil, nil
case n == 2:
return pktResponseEnd, nil, nil
case n < 4 || n > maxPktLen:
return 0, nil, fmt.Errorf("bad pkt-line length %d", n)
}
payload := p.buf[:n-4]
if _, err := io.ReadFull(p.r, payload); err != nil {
return 0, nil, err
}
return pktData, payload, nil
}
// skips acknowledgments/shallow-info; the reader yields band 1 of the
// packfile section, band 3 becomes an error
func packfileSection(p *pktReader) (io.Reader, error) {
for {
kind, line, err := p.next()
if err != nil {
return nil, err
}
if kind == pktFlush || kind == pktResponseEnd {
return nil, errors.New("fetch response has no packfile section")
}
if msg, ok := bytes.CutPrefix(line, []byte("ERR ")); ok {
return nil, fmt.Errorf("remote: %s", bytes.TrimSpace(msg))
}
if string(line) == "packfile\n" {
return &sideband{p: p}, nil
}
}
}
type sideband struct {
p *pktReader
data []byte
done bool
}
func (s *sideband) Read(b []byte) (int, error) {
for len(s.data) == 0 {
if s.done {
return 0, io.EOF
}
kind, line, err := s.p.next()
if err != nil {
return 0, err
}
if kind != pktData {
s.done = true
continue
}
if len(line) == 0 {
return 0, errors.New("empty sideband pkt-line")
}
switch line[0] {
case 1:
s.data = line[1:]
case 2:
case 3:
return 0, fmt.Errorf("remote: %s", bytes.TrimSpace(line[1:]))
default:
return 0, fmt.Errorf("bad sideband %d", line[0])
}
}
n := copy(b, s.data)
s.data = s.data[n:]
return n, nil
}
// errors with ErrTooLarge once more than n bytes come through
type capReader struct {
r io.Reader
n int64
}
func (c *capReader) Read(b []byte) (int, error) {
n, err := c.r.Read(b)
c.n -= int64(n)
if c.n < 0 {
return 0, ErrTooLarge
}
return n, err
}