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