~james/core

core/gitclient/pkt.go -rw-r--r-- 2.9 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 14 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
}