main pico / pkg / rsync-receiver / rsyncchecksum / rsyncchecksum.go
Eric Bower  ·  2026-05-31
 1package rsyncchecksum
 2
 3import (
 4	"encoding/binary"
 5	"io"
 6	"os"
 7
 8	"github.com/mmcloughlin/md4"
 9)
10
11func Tag2(s1, s2 uint16) uint16 {
12	return (((s1) + (s2)) & 0xFFFF)
13}
14
15func Tag(sum uint32) uint16 {
16	return Tag2(uint16(sum&0xFFFF), uint16(sum>>16))
17}
18
19// SignExtend mirrors how C converts from (signed char) to uint32, i.e. using
20// sign extension. get_checksum1 treats the buffer as (signed char*) instead of
21// (unsigned char*), which likely was not a conscious choice, but here we are.
22//
23// This function is exported for use in the rolling checksum in match.go.
24func SignExtend(b byte) uint32 {
25	val := uint32(b)
26	return uint32(int32(val<<24) >> 24)
27}
28
29func Checksum1(buf []byte) uint32 {
30	bufLen := len(buf)
31	var s1, s2 uint32
32	var i int
33
34	if bufLen > 4 {
35		for i = 0; i < (bufLen - 4); i += 4 {
36			s2 += 4*(s1+SignExtend(buf[i])) +
37				3*SignExtend(buf[i+1]) +
38				2*SignExtend(buf[i+2]) +
39				SignExtend(buf[i+3])
40			s1 += SignExtend(buf[i+0]) +
41				SignExtend(buf[i+1]) +
42				SignExtend(buf[i+2]) +
43				SignExtend(buf[i+3])
44		}
45	}
46	for ; i < bufLen; i++ {
47		s1 += SignExtend(buf[i])
48		s2 += s1
49	}
50	return (s1 & 0xffff) + (s2 << 16)
51}
52
53func Checksum2(seed int32, buf []byte) []byte {
54	h := md4.New()
55	h.Write(buf)
56	_ = binary.Write(h, binary.LittleEndian, seed) // hash.Hash.Write never fails
57	return h.Sum(nil)
58}
59
60func FileChecksum(fn string) ([]byte, error) {
61	f, err := os.Open(fn)
62	if err != nil {
63		return nil, err
64	}
65	defer func() { _ = f.Close() }()
66	h := md4.New()
67	if _, err := io.Copy(h, f); err != nil {
68		return nil, err
69	}
70	return h.Sum(nil), nil
71}
72
73const Size = md4.Size