-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b334fb6
commit 09b83e2
Showing
2 changed files
with
172 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Adler Rolling Checksum | ||
// Based on rsync algorithm | ||
// See also: https://rsync.samba.org/tech_report/node3.html | ||
package sync | ||
|
||
// The sums are done modulo 65521 (the largest prime number smaller than 2^16). | ||
const M = 65521 | ||
|
||
type Adler32 struct { | ||
window []byte // A fixed size array of temporary evaluated bytes | ||
count int // Last position | ||
old uint8 // Last element rolled out | ||
a, b uint16 // adler32 formula | ||
} | ||
|
||
// Factory function | ||
func NewAdler32() Adler32 { | ||
return Adler32{ | ||
window: make([]byte, 0), | ||
count: 0, | ||
a: 0, | ||
b: 0, | ||
} | ||
} | ||
|
||
// Calculate initial checksum from byte slice | ||
func (h Adler32) Write(data []byte) Adler32 { | ||
//https://en.wikipedia.org/wiki/Adler-32 | ||
//https://rsync.samba.org/tech_report/node3.html | ||
for index, char := range data { | ||
h.a += uint16(char) | ||
h.b += uint16(len(data)-index) * uint16(char) | ||
h.count++ | ||
} | ||
|
||
h.a %= M | ||
h.b %= M | ||
return h | ||
} | ||
|
||
// Calculate and return Checksum | ||
func (h Adler32) Sum() uint32 { | ||
// Enforce 16 bits | ||
// a = 920 = 0x398 (base 16) | ||
// b = 4582 = 0x11E6 | ||
// Output = 0x11E6 << 16 + 0x398 = 0x11E60398 | ||
return uint32(h.b)<<16 | uint32(h.a)&0xFFFFF | ||
} | ||
|
||
func (h Adler32) Window() []byte { return h.window } | ||
func (h Adler32) Count() int { return h.count } | ||
func (h Adler32) Removed() uint8 { return h.old } | ||
|
||
// Add byte to rolling checksum | ||
func (h Adler32) RollIn(input byte) Adler32 { | ||
h.a = (h.a + uint16(input)) % M | ||
h.b = (h.b + h.a) % M | ||
// Keep stored windows bytes while get processed | ||
h.window = append(h.window, input) | ||
h.count++ | ||
return h | ||
} | ||
|
||
// Substract byte from checksum | ||
func (h Adler32) RollOut() Adler32 { | ||
// If window is empty. Nothing to roll out! | ||
if len(h.window) == 0 { | ||
h.count = 0 | ||
return h | ||
} | ||
|
||
h.old = h.window[0] | ||
h.a = (h.a - uint16(h.old)) % M | ||
h.b = (h.b - (uint16(len(h.window)) * uint16(h.old))) % M | ||
h.window = h.window[1:] | ||
h.count-- | ||
|
||
return h | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package sync | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestWriteSum(t *testing.T) { | ||
rolling := NewAdler32().Write([]byte("how are you doing")) | ||
w0 := rolling.Sum() | ||
|
||
if 944178772 != w0 { | ||
t.Errorf("Expected 944178772 as hash for input text") | ||
} | ||
|
||
if 17 != rolling.Count() { | ||
t.Errorf("Expected 17 as output for current window") | ||
} | ||
} | ||
|
||
func TestWindowOverflow(t *testing.T) { | ||
rolling := NewAdler32().Write([]byte("abcdef")). | ||
RollOut(). // remove a | ||
RollOut(). // remove b | ||
RollOut(). // remove c | ||
RollOut(). // remove d | ||
RollOut(). // remove e | ||
RollOut(). // remove f | ||
RollOut() // overflow | ||
|
||
if rolling.Count() > 0 { | ||
t.Errorf("Expected count equal 0") | ||
} | ||
} | ||
|
||
func TestRollIn(t *testing.T) { | ||
rolling := NewAdler32() | ||
|
||
w0 := rolling.Write([]byte("ow are you doing")).Sum() | ||
w1 := rolling. | ||
RollIn('o'). | ||
RollIn('w'). | ||
RollIn(' '). | ||
RollIn('a'). | ||
RollIn('r'). | ||
RollIn('e'). | ||
RollIn(' '). | ||
RollIn('y'). | ||
RollIn('o'). | ||
RollIn('u'). | ||
RollIn(' '). | ||
RollIn('d'). | ||
RollIn('o'). | ||
RollIn('i'). | ||
RollIn('n'). | ||
RollIn('g'). | ||
Sum() | ||
|
||
if w0 != w1 { | ||
t.Errorf("Expected same hash for same input after RolledIn bytes") | ||
} | ||
|
||
} | ||
|
||
func TestRollOut(t *testing.T) { | ||
rolling := NewAdler32() | ||
|
||
w0 := rolling.Write([]byte("w are you doing")).Sum() | ||
w1 := rolling.RollIn('h'). | ||
RollIn('o'). | ||
RollIn('w'). | ||
RollIn(' '). | ||
RollIn('a'). | ||
RollIn('r'). | ||
RollIn('e'). | ||
RollIn(' '). | ||
RollIn('y'). | ||
RollIn('o'). | ||
RollIn('u'). | ||
RollIn(' '). | ||
RollIn('d'). | ||
RollIn('o'). | ||
RollIn('i'). | ||
RollIn('n'). | ||
RollIn('g'). | ||
RollOut(). // remove h | ||
RollOut(). // remove o | ||
Sum() | ||
|
||
if w0 != w1 { | ||
t.Errorf("Expected same hash for same text after RolledOut byte") | ||
} | ||
|
||
} |