Antonio Mika
·
2026-10-09
1package rsync
2
3import (
4 "encoding/binary"
5 "fmt"
6 "io"
7)
8
9// sumHead describes the block signatures of a basis file.
10type sumHead struct {
11 count int32
12 blength int32
13 s2length int32
14 remainder int32
15}
16
17type sumBuf struct {
18 offset int64
19 len int32
20 sum1 uint32
21 sum2 []byte
22 chain int32
23 same bool // matched at the same offset during an in-place update
24}
25
26func (c *conn) writeSumHead(h sumHead) error {
27 if err := c.writeInt32(h.count); err != nil {
28 return err
29 }
30 if err := c.writeInt32(h.blength); err != nil {
31 return err
32 }
33 if err := c.writeInt32(h.s2length); err != nil {
34 return err
35 }
36 return c.writeInt32(h.remainder)
37}
38
39func (c *conn) readSumHead(xferLen int) (sumHead, error) {
40 var h sumHead
41 var err error
42 maxBlength := int32(maxBlockSize)
43 if c.protocol < 30 {
44 maxBlength = oldMaxBlockSize
45 }
46 if h.count, err = c.readInt32(); err != nil {
47 return h, err
48 }
49 if h.count < 0 {
50 return h, fmt.Errorf("invalid checksum count %d", h.count)
51 }
52 if h.blength, err = c.readInt32(); err != nil {
53 return h, err
54 }
55 if h.blength < 0 || h.blength > maxBlength {
56 return h, fmt.Errorf("invalid block length %d", h.blength)
57 }
58 if h.count > 0 && h.blength == 0 {
59 return h, fmt.Errorf("invalid zero block length")
60 }
61 if h.s2length, err = c.readInt32(); err != nil {
62 return h, err
63 }
64 if h.s2length < 0 || int(h.s2length) > xferLen {
65 return h, fmt.Errorf("invalid checksum length %d", h.s2length)
66 }
67 if h.remainder, err = c.readInt32(); err != nil {
68 return h, err
69 }
70 if h.remainder < 0 || h.remainder > h.blength {
71 return h, fmt.Errorf("invalid remainder length %d", h.remainder)
72 }
73 return h, nil
74}
75
76// sumSizesSqroot picks the block size (about the square root of the file
77// length, rounded to a multiple of 8) and the strong checksum length.
78func sumSizesSqroot(length int64, protocol int, fixedBlock int32, csumLength int, xferLen int) (sumHead, error) {
79 maxS2 := min(sumLength, xferLen)
80 var blength int32
81 switch {
82 case fixedBlock > 0:
83 blength = fixedBlock
84 case length <= blockSize*blockSize:
85 blength = blockSize
86 default:
87 maxBlength := int32(maxBlockSize)
88 if protocol < 30 {
89 maxBlength = oldMaxBlockSize
90 }
91 c := int64(1)
92 for l := length; ; {
93 l >>= 2
94 if l == 0 {
95 break
96 }
97 c <<= 1
98 }
99 if c >= int64(maxBlength) {
100 blength = maxBlength
101 } else {
102 var b int64
103 for {
104 b |= c
105 if length < b*b {
106 b &^= c
107 }
108 c >>= 1
109 if c < 8 {
110 break
111 }
112 }
113 blength = int32(max(b, blockSize))
114 }
115 }
116
117 var s2length int
118 if csumLength == sumLength {
119 s2length = maxS2
120 } else {
121 b := blocksumBias
122 for l := length; ; {
123 l >>= 1
124 if l == 0 {
125 break
126 }
127 b += 2
128 }
129 for c := blength; ; {
130 c >>= 1
131 if c == 0 || b == 0 {
132 break
133 }
134 b--
135 }
136 s2length = (b + 1 - 32 + 7) / 8
137 s2length = max(s2length, csumLength)
138 s2length = min(s2length, maxS2)
139 }
140
141 remainder := int32(length % int64(blength))
142 count := length / int64(blength)
143 if remainder != 0 {
144 count++
145 }
146 if count > 0x7FFFFFFF {
147 return sumHead{}, fmt.Errorf("file is too large for checksum sending")
148 }
149 return sumHead{
150 count: int32(count),
151 blength: blength,
152 s2length: int32(s2length),
153 remainder: remainder,
154 }, nil
155}
156
157// encodeSums computes the block signatures of a basis file in their wire
158// form. They are built up front so a read error can't leave a half-written
159// signature on the stream.
160func encodeSums(r io.ReaderAt, length int64, head sumHead, cs *checksums) ([]byte, error) {
161 out := make([]byte, 0, int(head.count)*(4+int(head.s2length)))
162 buf := make([]byte, head.blength)
163 var sum2 []byte
164 var offset int64
165 for i := int32(0); i < head.count; i++ {
166 n := min(int64(head.blength), length-offset)
167 block := buf[:n]
168 if err := readFullAt(r, block, offset); err != nil {
169 return nil, err
170 }
171 offset += n
172 out = binary.LittleEndian.AppendUint32(out, checksum1(block))
173 sum2 = cs.blockSum(block, sum2[:0])
174 out = append(out, sum2[:head.s2length]...)
175 }
176 return out, nil
177}
178
179// readFullAt fills p from r at off, treating a short read as an error.
180func readFullAt(r io.ReaderAt, p []byte, off int64) error {
181 n, err := r.ReadAt(p, off)
182 if n == len(p) {
183 return nil
184 }
185 if err == nil || err == io.EOF {
186 err = io.ErrUnexpectedEOF
187 }
188 return err
189}