Go · 2241 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 // Package protocol carries the smart-HTTP and (later) smart-SSH bits of
4 // the git wire protocol that we generate ourselves. The bulk of the
5 // protocol — capability negotiation, want/have, multi-ack, side-band —
6 // stays inside canonical `git`'s `upload-pack` and `receive-pack`. We
7 // only emit the framing we need to wrap their advertise-refs output for
8 // HTTP transport.
9 package protocol
10
11 import (
12 "fmt"
13 "io"
14 )
15
16 // MaxPktLine is git's hard limit for a single packet: 65520 bytes of
17 // payload + 4 bytes of length prefix. Anything longer is malformed.
18 const MaxPktLine = 65520
19
20 // FlushPkt is the literal pkt-line sequence that says "no more
21 // packets." git uses it to terminate sub-streams.
22 const FlushPkt = "0000"
23
24 // WritePkt writes one pkt-line packet — a 4-hex-digit length prefix
25 // (covering payload + 4) followed by the payload. Exposed so the smart-
26 // HTTP info/refs handler can prepend its `# service=...` advertisement.
27 func WritePkt(w io.Writer, payload string) error {
28 if len(payload)+4 > MaxPktLine {
29 return fmt.Errorf("pkt-line: payload too long (%d > %d)", len(payload), MaxPktLine-4)
30 }
31 if _, err := fmt.Fprintf(w, "%04x%s", len(payload)+4, payload); err != nil {
32 return err
33 }
34 return nil
35 }
36
37 // WriteFlush emits the flush packet (`0000`). Marks the end of a
38 // sub-stream like the service advertisement preamble.
39 func WriteFlush(w io.Writer) error {
40 _, err := io.WriteString(w, FlushPkt)
41 return err
42 }
43
44 // WriteServiceAdvertisement writes the standard preamble that the smart-
45 // HTTP info/refs response uses to tell git which service is being
46 // advertised. The wire format is:
47 //
48 // 001e# service=git-upload-pack\n0000
49 // └┬┘└──────────┬─────────────┘└─┬─┘
50 // │ │ └ flush
51 // │ └ payload (trailing newline included)
52 // └ length prefix (hex)
53 //
54 // Then `git upload-pack --advertise-refs --stateless-rpc <repo>` writes
55 // its actual ref advertisement after this.
56 func WriteServiceAdvertisement(w io.Writer, service string) error {
57 if err := WritePkt(w, "# service="+service+"\n"); err != nil {
58 return err
59 }
60 return WriteFlush(w)
61 }
62