Antonio Mika
·
2026-10-09
1package rsync
2
3import (
4 "crypto/md5"
5 "encoding/binary"
6 "fmt"
7 "hash"
8
9 "github.com/cespare/xxhash/v2"
10 "github.com/mmcloughlin/md4"
11)
12
13// csumType identifies a strong checksum. md4Old is the implied checksum of
14// protocols 27-29, which seeds the whole-file sum; md4 is the same digest
15// when negotiated by name in protocol 30+.
16type csumType int
17
18const (
19 csumNone csumType = iota
20 csumMD4Old
21 csumMD4
22 csumMD5
23 csumXXH64
24)
25
26// checksumNames is our preference order for negotiation. It matches rsync's
27// table order so both sides converge on the same choice.
28var checksumNames = []struct {
29 name string
30 typ csumType
31}{
32 {"xxh64", csumXXH64},
33 {"xxhash", csumXXH64},
34 {"md5", csumMD5},
35 {"md4", csumMD4},
36 {"none", csumNone},
37}
38
39func parseChecksumName(name string) (csumType, error) {
40 for _, c := range checksumNames {
41 if c.name == name {
42 return c.typ, nil
43 }
44 }
45 return 0, fmt.Errorf("unknown checksum name: %s", name)
46}
47
48func (t csumType) size() int {
49 switch t {
50 case csumNone:
51 return 1
52 case csumXXH64:
53 return 8
54 default:
55 return 16
56 }
57}
58
59// checksum1 is rsync's rolling weak checksum. The bytes are treated as
60// signed chars, as rsync's C implementation does.
61func checksum1(buf []byte) uint32 {
62 var s1, s2 uint32
63 i := 0
64 for ; i < len(buf)-4; i += 4 {
65 b0 := uint32(int8(buf[i]))
66 b1 := uint32(int8(buf[i+1]))
67 b2 := uint32(int8(buf[i+2]))
68 b3 := uint32(int8(buf[i+3]))
69 s2 += 4*(s1+b0) + 3*b1 + 2*b2 + b3
70 s1 += b0 + b1 + b2 + b3
71 }
72 for ; i < len(buf); i++ {
73 s1 += uint32(int8(buf[i]))
74 s2 += s1
75 }
76 return (s1 & 0xffff) + (s2 << 16)
77}
78
79// checksums holds the negotiated digests and the session seed.
80type checksums struct {
81 xfer csumType
82 file csumType
83 seed int32
84 properSeedOrder bool
85}
86
87func (c *checksums) seedBytes() []byte {
88 var b [4]byte
89 binary.LittleEndian.PutUint32(b[:], uint32(c.seed))
90 return b[:]
91}
92
93// blockSum is rsync's get_checksum2: the strong checksum of one block.
94func (c *checksums) blockSum(buf []byte, out []byte) []byte {
95 switch c.xfer {
96 case csumXXH64:
97 d := xxhash.NewWithSeed(uint64(int64(c.seed)))
98 _, _ = d.Write(buf)
99 return binary.LittleEndian.AppendUint64(out, d.Sum64())
100 case csumMD5:
101 h := md5.New()
102 if c.properSeedOrder {
103 if c.seed != 0 {
104 h.Write(c.seedBytes())
105 }
106 h.Write(buf)
107 } else {
108 h.Write(buf)
109 if c.seed != 0 {
110 h.Write(c.seedBytes())
111 }
112 }
113 return h.Sum(out)
114 case csumMD4, csumMD4Old:
115 h := md4.New()
116 h.Write(buf)
117 if c.seed != 0 {
118 h.Write(c.seedBytes())
119 }
120 return h.Sum(out)
121 default:
122 return append(out, 0)
123 }
124}
125
126// newXferSum starts the whole-file checksum that follows each transferred
127// file (rsync's sum_init with the transfer digest).
128func (c *checksums) newXferSum() hash.Hash {
129 switch c.xfer {
130 case csumMD4Old:
131 h := md4.New()
132 h.Write(c.seedBytes())
133 return h
134 default:
135 return newPlainHash(c.xfer)
136 }
137}
138
139// newFileSum starts the --checksum digest of a whole file.
140func (c *checksums) newFileSum() hash.Hash {
141 return newPlainHash(c.file)
142}
143
144func newPlainHash(t csumType) hash.Hash {
145 switch t {
146 case csumMD4, csumMD4Old:
147 return md4.New()
148 case csumMD5:
149 return md5.New()
150 case csumXXH64:
151 return &xxh64LE{d: xxhash.NewWithSeed(0)}
152 default:
153 return &noneHash{}
154 }
155}
156
157// xxh64LE emits the digest little-endian, as rsync's SIVAL64 does.
158type xxh64LE struct{ d *xxhash.Digest }
159
160func (x *xxh64LE) Write(p []byte) (int, error) { return x.d.Write(p) }
161func (x *xxh64LE) Sum(b []byte) []byte { return binary.LittleEndian.AppendUint64(b, x.d.Sum64()) }
162func (x *xxh64LE) Reset() { x.d.Reset() }
163func (x *xxh64LE) Size() int { return 8 }
164func (x *xxh64LE) BlockSize() int { return 32 }
165
166type noneHash struct{}
167
168func (noneHash) Write(p []byte) (int, error) { return len(p), nil }
169func (noneHash) Sum(b []byte) []byte { return append(b, 0) }
170func (noneHash) Reset() {}
171func (noneHash) Size() int { return 1 }
172func (noneHash) BlockSize() int { return 1 }