From 970acd0ee460ab4370c5f89f5a0e81e6278b18ee Mon Sep 17 00:00:00 2001 From: brent saner Date: Fri, 5 Sep 2025 13:53:29 -0400 Subject: [PATCH] v1.10.0 FIXED: * Windows logging ADDED: * netx (and netx/inetcksum), the latter of which implements the Internet Checksum as a hash.Hash. --- logging/consts_windows.go | 4 +- netx/docs.go | 4 + netx/inetcksum/consts.go | 24 ++ netx/inetcksum/docs.go | 26 ++ netx/inetcksum/funcs.go | 62 ++++ netx/inetcksum/funcs_inetchecksum.go | 351 +++++++++++++++++++++ netx/inetcksum/funcs_inetchecksumsimple.go | 153 +++++++++ netx/inetcksum/types.go | 68 ++++ 8 files changed, 690 insertions(+), 2 deletions(-) create mode 100644 netx/docs.go create mode 100644 netx/inetcksum/consts.go create mode 100644 netx/inetcksum/docs.go create mode 100644 netx/inetcksum/funcs.go create mode 100644 netx/inetcksum/funcs_inetchecksum.go create mode 100644 netx/inetcksum/funcs_inetchecksumsimple.go create mode 100644 netx/inetcksum/types.go diff --git a/logging/consts_windows.go b/logging/consts_windows.go index 6de88eb..aeb1426 100644 --- a/logging/consts_windows.go +++ b/logging/consts_windows.go @@ -7,10 +7,10 @@ import ( // Flags for logger configuration. These are used internally. // LogUndefined indicates an undefined Logger type. -LogUndefined bitmask.MaskBit = 0 +const LogUndefined bitmask.MaskBit = 0 const ( // LogWinLogger indicates a WinLogger Logger type (Event Log). - LogWinLogger bitmask.MaskBit= 1 << iota + LogWinLogger bitmask.MaskBit = 1 << iota // LogFile flags a FileLogger Logger type. LogFile // LogStdout flags a StdLogger Logger type. diff --git a/netx/docs.go b/netx/docs.go new file mode 100644 index 0000000..584e11d --- /dev/null +++ b/netx/docs.go @@ -0,0 +1,4 @@ +/* +Package netx includes extensions to the stdlib `net` module. +*/ +package netx diff --git a/netx/inetcksum/consts.go b/netx/inetcksum/consts.go new file mode 100644 index 0000000..0a9af60 --- /dev/null +++ b/netx/inetcksum/consts.go @@ -0,0 +1,24 @@ +package inetcksum + +import ( + `encoding/binary` +) + +const ( + // EmptyCksum is returned for checksums of 0-length byte slices/buffers. + EmptyCksum uint16 = 0xffff +) + +const ( + // cksumMask is AND'd with a checksum to get the "carried ones". + cksumMask uint32 = 0x0000ffff + // cksumShift is used in the "carried-ones folding". + cksumShift uint32 = 0x00000010 + // padShift is used to "pad out" a checksum for odd-length buffers by left-shifting. + padShift uint32 = 0x00000008 +) + +var ( + // ord is the byte order used by the Internet Checksum. + ord binary.ByteOrder = binary.BigEndian +) diff --git a/netx/inetcksum/docs.go b/netx/inetcksum/docs.go new file mode 100644 index 0000000..fdeb497 --- /dev/null +++ b/netx/inetcksum/docs.go @@ -0,0 +1,26 @@ +/* +Package inetcksum applies the "Internet Checksum" algorithm as specified/described in: + + * [RFC 1071] + * [RFC 1141] + * [RFC 1624] + +It provides [InetChecksum], which can be used as a: + + * [hash.Hash] + * [io.ByteWriter] + * [io.StringWriter] + * [io.Writer] + * [io.WriterTo] + +and is concurrency-safe. + +There is also an [InetChecksumSimple] provided, which is more +tailored for performance/resource usage at the cost of concurrency +safety and data retention. + +[RFC 1071]: https://datatracker.ietf.org/doc/html/rfc1071 +[RFC 1141]: https://datatracker.ietf.org/doc/html/rfc1141 +[RFC 1624]: https://datatracker.ietf.org/doc/html/rfc1624 +*/ +package inetcksum diff --git a/netx/inetcksum/funcs.go b/netx/inetcksum/funcs.go new file mode 100644 index 0000000..659eb48 --- /dev/null +++ b/netx/inetcksum/funcs.go @@ -0,0 +1,62 @@ +package inetcksum + +import ( + `io` +) + +// New returns a new initialized [InetChecksum]. It will never panic. +func New() (i *InetChecksum) { + + i = &InetChecksum{} + _ = i.Aligned() + + return +} + +/* +NewFromBytes returns a new [InetChecksum] initialized with explicit bytes. + +b may be nil or 0-length; this will not cause an error. +*/ +func NewFromBytes(b []byte) (i *InetChecksum, copied int, err error) { + + var cksum InetChecksum + + if b != nil && len(b) > 0 { + if copied, err = cksum.Write(b); err != nil { + return + } + _ = i.Aligned() + } else { + i = New() + return + } + + i = &cksum + + return +} + +/* +NewFromBuf returns an [InetChecksum] from a specified [io.Reader]. + +buf may be nil. If it isn't, NewFromBuf will call [io.Copy] on buf. +Note that this may exhaust your passed buf or advance its current seek position/offset, +depending on its type. +*/ +func NewFromBuf(buf io.Reader) (i *InetChecksum, copied int64, err error) { + + var cksum InetChecksum + + _ = i.Aligned() + + if buf != nil { + if copied, err = io.Copy(&cksum, buf); err != nil { + return + } + } + + i = &cksum + + return +} diff --git a/netx/inetcksum/funcs_inetchecksum.go b/netx/inetcksum/funcs_inetchecksum.go new file mode 100644 index 0000000..4ab3ab2 --- /dev/null +++ b/netx/inetcksum/funcs_inetchecksum.go @@ -0,0 +1,351 @@ +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 +} diff --git a/netx/inetcksum/funcs_inetchecksumsimple.go b/netx/inetcksum/funcs_inetchecksumsimple.go new file mode 100644 index 0000000..89624ef --- /dev/null +++ b/netx/inetcksum/funcs_inetchecksumsimple.go @@ -0,0 +1,153 @@ +package inetcksum + +/* +Aligned returns true if the current checksum for an InetChecksumSimple 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. +*/ +func (i *InetChecksumSimple) Aligned() (aligned bool) { + + aligned = i.aligned + + return +} + +// BlockSize returns the number of bytes at a time that InetChecksumSimple operates on. (It will always return 1.) +func (i *InetChecksumSimple) BlockSize() (blockSize int) { + + blockSize = 1 + + return +} + +// Size returns how many bytes a checksum is. (It will always return 2.) +func (i *InetChecksumSimple) 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 *InetChecksumSimple) 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 *InetChecksumSimple) Sum16() (cksum uint16) { + + var thisSum uint32 + + thisSum = i.sum + + 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 + } + + // Fold the "carried ones". + for thisSum > cksumMask { + thisSum = (thisSum & cksumMask) + (thisSum >> cksumShift) + } + cksum = ^uint16(thisSum) + + return +} + +/* +Sum16Bytes is a convenience wrapper around [InetChecksumSimple.Sum16] +which returns a slice of the uint16 as a 2-byte-long slice instead. +*/ +func (i *InetChecksumSimple) Sum16Bytes() (cksum []byte) { + + var sum16 uint16 = i.Sum16() + + cksum = make([]byte, 2) + + ord.PutUint16(cksum, sum16) + + return +} + +/* +Write writes data to the underlying InetChecksumSimple buffer. It conforms to [io.Writer]. + +p may be nil or empty; no error will be returned and n will be 0 if so. + +A copy of p is made first and all hashing operations are performed on that copy. +*/ +func (i *InetChecksumSimple) Write(p []byte) (n int, err error) { + + var idx int + var bufLen int + var buf []byte + var iter int + + 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) + + 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 + } + } + + return +} + +// WriteByte checksums a single byte. It conforms to [io.ByteWriter]. +func (i *InetChecksumSimple) WriteByte(c byte) (err error) { + + 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 + } + + return +} diff --git a/netx/inetcksum/types.go b/netx/inetcksum/types.go new file mode 100644 index 0000000..637dff8 --- /dev/null +++ b/netx/inetcksum/types.go @@ -0,0 +1,68 @@ +package inetcksum + +import ( + `bytes` + `sync` +) + +type ( + /* + InetChecksum implements [hash.Hash] and various other stdlib interfaces. + + If the current data in an InetChecksum's buffer is not aligned + to an even number of bytes -- e.g. InetChecksum.buf.Len() % 2 != 0, + [InetChecksum.Aligned] will return false (otherwise it will return + true). + + If [InetChecksum.Aligned] returns false, the checksum result of an + [InetChecksum.Sum] or [InetChecksum.Sum16] (or any other operation + returning a sum) will INCLUDE THE PAD NULL BYTE (which is only + applied *at the time of the Sum/Sum32 call) and is NOT applied to + the persistent underlying storage. + + InetChecksum differs from [InetChecksumSimple] in that it: + + * Is MUCH better-suited/safer for concurrent operations - ALL + methods are concurrency-safe. + * Allows the data that is hashed to be recovered from a + sequential internal buffer. (See [InetChecksum.DisablePersist] + to disable the persistent internal buffer.) + + At the cost of increased memory usage and additional cycles for mutexing. + + Note that once persistence is disabled for an InetChecksum, it cannot be + re-enabled until/unless [InetChecksum.Reset] is called (which will reset + the persistence to enabled with a fresh buffer). Any data within the + persistent buffer will be removed if [InetChecksum.DisablePersist] is called. + */ + InetChecksum struct { + buf bytes.Buffer + disabledBuf bool + aligned bool + last byte + sum uint32 + bufLock sync.RWMutex + alignLock sync.RWMutex + lastLock sync.RWMutex + sumLock sync.RWMutex + } + + /* + InetChecksumSimple is like [InetChecksum], but with a few key differences. + + It is MUCH much more performant/optimized for *single throughput* operations. + Because it also does not retain a buffer of what was hashed, it uses *far* less + memory over time. + + However, the downside is it is NOT concurrency safe. There are no promises made + about safety or proper checksum ordering with concurrency for this type, but it + should have much better performance for non-concurrent use. + + It behaves much more like a traditional [hash.Hash]. + */ + InetChecksumSimple struct { + aligned bool + last byte + sum uint32 + } +)