main pico / pkg / rsync / sums.go
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}