
FIXED: * Windows logging ADDED: * netx (and netx/inetcksum), the latter of which implements the Internet Checksum as a hash.Hash.
352 lines
7.6 KiB
Go
352 lines
7.6 KiB
Go
package inetcksum
|
|
|
|
import (
|
|
`io`
|
|
)
|
|
|
|
/*
|
|
Aligned returns true if the current underlying buffer in an InetChecksum is
|
|
aligned to the algorithm's requirement for an even number of bytes.
|
|
|
|
Note that if Aligned returns false, a single null pad byte will be applied
|
|
to the underlying data buffer at time of a Sum* call, but will not be written
|
|
to the persistent underlying storage.
|
|
|
|
If aligned's underlying buffer/storage is empty or nil, aligned will be true.
|
|
|
|
Aligned will also force-set the internal state's aligned status.
|
|
*/
|
|
func (i *InetChecksum) Aligned() (aligned bool) {
|
|
|
|
i.alignLock.Lock()
|
|
defer i.alignLock.Unlock()
|
|
|
|
i.bufLock.RLock()
|
|
aligned = i.buf.Len()&2 == 0
|
|
i.bufLock.RUnlock()
|
|
|
|
i.aligned = aligned
|
|
|
|
return
|
|
}
|
|
|
|
// BlockSize returns the number of bytes at a time that InetChecksum operates on. (It will always return 1.)
|
|
func (i *InetChecksum) BlockSize() (blockSize int) {
|
|
|
|
blockSize = 1
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
Bytes returns teh bytes currently in the internal storage.
|
|
|
|
curBuf will be nil if the internal storage has not yet been initialized.
|
|
*/
|
|
func (i *InetChecksum) Bytes() (curBuf []byte) {
|
|
|
|
i.bufLock.RLock()
|
|
defer i.bufLock.RUnlock()
|
|
|
|
if i.buf.Len() != 0 {
|
|
curBuf = i.buf.Bytes()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Clear empties the internal buffer (but does not affect the checksum state).
|
|
func (i *InetChecksum) Clear() {
|
|
|
|
i.bufLock.Lock()
|
|
defer i.bufLock.Unlock()
|
|
|
|
i.buf.Reset()
|
|
}
|
|
|
|
/*
|
|
DisablePersist disables the internal persistence of an InetChecksum.
|
|
|
|
This is recommended for integrations that desire the concurrency safety
|
|
of an InetChecksum but want a smaller memory footprint and do not need a copy
|
|
of data that was hashed.
|
|
|
|
Any data existing in the buffer will NOT be cleared out if DisablePersist is called.
|
|
You must call [InetChecksum.Clear] to do that.
|
|
|
|
Persistence CANNOT be reenabled once disabled. [InetChecksum.Reset]
|
|
must be called to re-enable persistence.
|
|
*/
|
|
func (i *InetChecksum) DisablePersist() {
|
|
|
|
i.bufLock.Lock()
|
|
defer i.bufLock.Unlock()
|
|
|
|
i.disabledBuf = true
|
|
}
|
|
|
|
// Len returns the current amount of bytes stored in this InetChecksum's internal buffer.
|
|
func (i *InetChecksum) Len() (l int) {
|
|
|
|
i.bufLock.RLock()
|
|
defer i.bufLock.RUnlock()
|
|
l = i.buf.Len()
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
Reset resets the internal buffer/storage to an empty state.
|
|
|
|
If persistence was disabled ([InetChecksum.DisablePersist]),
|
|
this method will re-enable it with an empty buffer.
|
|
If you wish the buffer to be disabled, you must invoke [InetChecksum.DisablePersist]
|
|
again.
|
|
|
|
If you only wish to clear the buffer without losing the checksum state,
|
|
use [InetChecksum.Clear].
|
|
*/
|
|
func (i *InetChecksum) Reset() {
|
|
|
|
i.alignLock.Lock()
|
|
i.bufLock.Lock()
|
|
i.sumLock.Lock()
|
|
i.lastLock.Lock()
|
|
|
|
i.aligned = false
|
|
i.alignLock.Unlock()
|
|
|
|
i.buf.Reset()
|
|
i.disabledBuf = false
|
|
i.bufLock.Unlock()
|
|
|
|
i.last = 0x00
|
|
i.lastLock.Unlock()
|
|
|
|
i.sum = 0
|
|
i.sumLock.Unlock()
|
|
}
|
|
|
|
// Size returns how many bytes a checksum is. (It will always return 2.)
|
|
func (i *InetChecksum) Size() (bufSize int) {
|
|
|
|
bufSize = 2
|
|
|
|
return
|
|
}
|
|
|
|
// Sum computes the checksum cksum of the current buffer and appends it as big-endian bytes to b.
|
|
func (i *InetChecksum) Sum(b []byte) (cksumAppended []byte) {
|
|
|
|
var sum16 []byte = i.Sum16Bytes()
|
|
|
|
cksumAppended = append(b, sum16...)
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
Sum16 computes the checksum of the current buffer and returns it as a uint16.
|
|
|
|
This is the native number used in the IPv4 header.
|
|
All other Sum* methods wrap this method.
|
|
|
|
If the underlying buffer is empty or nil, cksum will be 0xffff (65535)
|
|
in line with common implementations.
|
|
*/
|
|
func (i *InetChecksum) Sum16() (cksum uint16) {
|
|
|
|
var thisSum uint32
|
|
|
|
i.alignLock.RLock()
|
|
i.lastLock.RLock()
|
|
i.sumLock.RLock()
|
|
|
|
thisSum = i.sum
|
|
i.sumLock.RUnlock()
|
|
|
|
if !i.aligned {
|
|
/*
|
|
"Pad" at the end of the additive ops - a bitshift is used on the sum integer itself
|
|
instead of a binary.Append() or append() or such to avoid additional memory allocation.
|
|
*/
|
|
thisSum += uint32(i.last) << padShift
|
|
}
|
|
i.lastLock.RUnlock()
|
|
i.alignLock.RUnlock()
|
|
|
|
// Fold the "carried ones".
|
|
for thisSum > cksumMask {
|
|
thisSum = (thisSum & cksumMask) + (thisSum >> cksumShift)
|
|
}
|
|
cksum = ^uint16(thisSum)
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
Sum16Bytes is a convenience wrapper around [InetChecksum.Sum16]
|
|
which returns a slice of the uint16 as a 2-byte-long slice instead.
|
|
*/
|
|
func (i *InetChecksum) Sum16Bytes() (cksum []byte) {
|
|
|
|
var sum16 uint16 = i.Sum16()
|
|
|
|
cksum = make([]byte, 2)
|
|
|
|
ord.PutUint16(cksum, sum16)
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
Write writes data to the underlying InetChecksum buffer. It conforms to [io.Writer].
|
|
|
|
If this operation returns an error, you MUST call [InetChecksum.Reset] as the instance
|
|
being used can no longer be considered to be in a consistent state.
|
|
|
|
p may be nil or empty; no error will be returned and n will be 0 if so.
|
|
|
|
Write is concurrency safe; a copy of p is made first and all hashing/internal
|
|
storage writing is performed on/which that copy.
|
|
*/
|
|
func (i *InetChecksum) Write(p []byte) (n int, err error) {
|
|
|
|
var idx int
|
|
var bufLen int
|
|
var buf []byte
|
|
var iter int
|
|
var origLast byte
|
|
var origAligned bool
|
|
var origSum uint32
|
|
|
|
if p == nil || len(p) == 0 {
|
|
return
|
|
}
|
|
|
|
// The TL;DR here is the checksum boils down to:
|
|
// cksum = cksum + ((high << 8) | low)
|
|
|
|
bufLen = len(p)
|
|
buf = make([]byte, bufLen)
|
|
copy(buf, p)
|
|
|
|
i.alignLock.Lock()
|
|
defer i.alignLock.Unlock()
|
|
i.bufLock.Lock()
|
|
defer i.bufLock.Unlock()
|
|
i.sumLock.Lock()
|
|
defer i.sumLock.Unlock()
|
|
i.lastLock.Lock()
|
|
defer i.lastLock.Unlock()
|
|
|
|
origLast = i.last
|
|
origAligned = i.aligned
|
|
origSum = i.sum
|
|
|
|
if !i.aligned {
|
|
// Last write was unaligned, so pair i.last in.
|
|
i.sum += (uint32(i.last) << padShift) | uint32(buf[0])
|
|
i.aligned = true
|
|
idx = 1
|
|
}
|
|
|
|
// Operate on bytepairs.
|
|
// Note that idx is set to either 0 or 1 depending on if
|
|
// buf[0] has already been summed in.
|
|
for iter = idx; iter < bufLen; iter += 2 {
|
|
if iter+1 < bufLen {
|
|
// Technically could use "i.sum += uint32(ord.Uint16(buf[iter:iter+2))" here instead.
|
|
i.sum += (uint32(buf[iter]) << padShift) | uint32(buf[iter+1])
|
|
} else {
|
|
i.last = buf[iter]
|
|
i.aligned = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if !i.disabledBuf {
|
|
if n, err = i.buf.Write(buf); err != nil {
|
|
i.sum = origSum
|
|
i.aligned = origAligned
|
|
i.last = origLast
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// WriteByte writes a single byte to the underlying storage. It conforms to [io.ByteWriter].
|
|
func (i *InetChecksum) WriteByte(c byte) (err error) {
|
|
|
|
var origLast byte
|
|
var origAligned bool
|
|
var origSum uint32
|
|
|
|
i.alignLock.Lock()
|
|
defer i.alignLock.Unlock()
|
|
i.bufLock.Lock()
|
|
defer i.bufLock.Unlock()
|
|
i.sumLock.Lock()
|
|
defer i.sumLock.Unlock()
|
|
i.lastLock.Lock()
|
|
defer i.lastLock.Unlock()
|
|
|
|
origLast = i.last
|
|
origAligned = i.aligned
|
|
origSum = i.sum
|
|
|
|
if i.aligned {
|
|
// Since it's a single byte, we just set i.last and unalign.
|
|
i.last = c
|
|
i.aligned = false
|
|
} else {
|
|
// It's unaligned, so join with i.last and align.
|
|
i.sum += (uint32(i.last) << padShift) | uint32(c)
|
|
i.aligned = true
|
|
}
|
|
|
|
if !i.disabledBuf {
|
|
if err = i.WriteByte(c); err != nil {
|
|
i.sum = origSum
|
|
i.aligned = origAligned
|
|
i.last = origLast
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// WriteString writes a string to the underlying storage. It conforms to [io.StringWriter].
|
|
func (i *InetChecksum) WriteString(s string) (n int, err error) {
|
|
|
|
if n, err = i.Write([]byte(s)); err != nil {
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// WriteTo writes the current contents of the underlying buffer to w. The contents are not drained. Noop if persistence is disabled.
|
|
func (i *InetChecksum) WriteTo(w io.Writer) (n int64, err error) {
|
|
|
|
var wrtn int
|
|
|
|
if i.disabledBuf {
|
|
return
|
|
}
|
|
|
|
i.bufLock.RLock()
|
|
defer i.bufLock.RUnlock()
|
|
|
|
if wrtn, err = w.Write(i.buf.Bytes()); err != nil {
|
|
n = int64(wrtn)
|
|
return
|
|
}
|
|
n = int64(wrtn)
|
|
|
|
return
|
|
}
|