Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67a5c8a3fb
|
||
|
|
cd9b967306
|
||
|
|
f002766c74
|
||
|
|
67bd30907e
|
||
|
|
4e7d474fb3
|
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Package fsx provides some enhancements to [io/fs].
|
||||||
|
*/
|
||||||
|
package fsx
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package fsx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/types"
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: FilterFsPaths
|
||||||
|
|
||||||
|
/*
|
||||||
|
FsPaths returns all paths in [io/fs.FS] `fsys`.
|
||||||
|
|
||||||
|
If `fsys` is nil, both `fsPaths` and `err` will be nil.
|
||||||
|
|
||||||
|
`t` should be an OR'd [io/fs.FileMode] containing all filetypes that should be included.
|
||||||
|
Use [io/fs.ModeType] to match any non-regular-file object, or 0 to explicitly match regular files.
|
||||||
|
If `t` is non-zero but you wish to ALSO include regular files, then specify `inclFiles` as true.
|
||||||
|
(`inclFiles` is ignored if `t` == 0, as this inherently matches regular files only/explicitly.)
|
||||||
|
See [HasType] for more information on this filtering.
|
||||||
|
|
||||||
|
`maxDepth` can be used to limit recursion depth.
|
||||||
|
|
||||||
|
- If `maxDepth` == 0, only direct [io/fs.DirEntry] descendants of `fsys`
|
||||||
|
will be included in `fsPaths`.
|
||||||
|
- If `maxDepth` < 0, ALL [io/fs.DirEntry] descendants of `fsys`
|
||||||
|
will be included in `fsPaths`.
|
||||||
|
- If `maxDepth` > 0, then only children at a depth of this level or lower
|
||||||
|
("higher" in a hierarchy) will be included in `fsPaths`.
|
||||||
|
*/
|
||||||
|
func FsPaths(fsys fs.FS, t fs.FileMode, inclFiles bool, maxDepth int) (fsPaths map[string]fs.DirEntry, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var entries []fs.DirEntry
|
||||||
|
var t fs.FileMode = types.Type()
|
||||||
|
|
||||||
|
if fsys == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if fsPaths, err = getEntries(fsys, t, inclFiles, maxDepth, "."); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HasType is just a convenience wrapper for:
|
||||||
|
|
||||||
|
hasType = m & t != 0
|
||||||
|
|
||||||
|
The type(s) will be explicitly extracted from `m` and `t` first.
|
||||||
|
|
||||||
|
Note that if `t` is an OR'd set of filetypes, then HasType will return true if ANY of the types
|
||||||
|
in `t` match `m`'s type (see [r00t2.io/goutils/bitmask] for more information on bitmasking).
|
||||||
|
If you wish to check if `m` is A SINGLE type, either ensure that `t` is not OR'd
|
||||||
|
(i.e. is a single fs.FileMode type) or use [IsType] instead.
|
||||||
|
*/
|
||||||
|
func HasType(m, t fs.FileMode) (hasType bool) { return m.Type()&t.Type() != 0 }
|
||||||
|
|
||||||
|
/*
|
||||||
|
IsType is just a convenience wrapper for:
|
||||||
|
|
||||||
|
isType = m & t == t
|
||||||
|
|
||||||
|
The type(s) will be explicitly extracted from `m` and `t` first.
|
||||||
|
|
||||||
|
Note that if `t` is an OR'd set of filetypes, then IsType will *never* return true
|
||||||
|
as its type is single-valued/not bitmasked (see [r00t2.io/goutils/bitmask] for more information
|
||||||
|
on bitmasking).
|
||||||
|
If you wish to check if `m` is ONE OF multiple types in `t`, use [HasType] instead.
|
||||||
|
*/
|
||||||
|
func IsType(m, t fs.FileMode) (isType bool) { return m&t == t }
|
||||||
|
|
||||||
|
/*
|
||||||
|
getEntries is used to recursively fetch entries from `fsys`.
|
||||||
|
|
||||||
|
It mostly serves as the recursion for [FsPaths], and the arguments
|
||||||
|
retain their exact meaning except that it requires `relPath`
|
||||||
|
and `maxDepth` (if > 0) will be decremanted in each recursion.
|
||||||
|
*/
|
||||||
|
func getEntries(fsys fs.FS, t fs.FileMode, inclFiles bool, maxDepth int, relPath string) (fsPaths map[string]fs.DirEntry, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var p string
|
||||||
|
var k string
|
||||||
|
var v fs.DirEntry
|
||||||
|
var e []fs.DirEntry
|
||||||
|
var subs map[string]fs.DirEntry = make(map[string]fs.DirEntry)
|
||||||
|
|
||||||
|
if e, err = fs.ReadDir(fsys, relPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fsPaths = make(map[string]fs.DirEntry)
|
||||||
|
|
||||||
|
for idx = range e {
|
||||||
|
p = filepath.Join(relPath, e[idx].Name())
|
||||||
|
if e[idx].IsDir() {
|
||||||
|
if subs, err = getEntries(fsys, t, inclFiles, maxDepth, p); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v = range subs {
|
||||||
|
fsPaths[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t != 0 {
|
||||||
|
if HasType(t, e[idx].Type()) {
|
||||||
|
fsPaths[p] = e[idx]
|
||||||
|
}
|
||||||
|
} else if inclFiles && e[idx].Type() == 0 {
|
||||||
|
fsPaths[p] = e[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
+56
-40
@@ -1,16 +1,16 @@
|
|||||||
package dnsx
|
package dnsx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`bytes`
|
"bytes"
|
||||||
`encoding/base32`
|
"encoding/base32"
|
||||||
`fmt`
|
"fmt"
|
||||||
`math`
|
"math"
|
||||||
`net`
|
"net"
|
||||||
`net/netip`
|
"net/netip"
|
||||||
`strings`
|
"strings"
|
||||||
|
|
||||||
`go4.org/netipx`
|
"go4.org/netipx"
|
||||||
`r00t2.io/goutils/stringsx`
|
"r00t2.io/goutils/stringsx"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -238,19 +238,19 @@ TXT records, etc. - not A/AAAA/CNAME, etc. - see RFC 8553 for details.
|
|||||||
See the following functions for allowing additional syntax/rule validation
|
See the following functions for allowing additional syntax/rule validation
|
||||||
that have record-type-specific accommodations made:
|
that have record-type-specific accommodations made:
|
||||||
|
|
||||||
* [IsFqdnDefinedTxt]
|
- [IsFqdnDefinedTxt]
|
||||||
* [IsFqdnNsec3]
|
- [IsFqdnNsec3]
|
||||||
* [IsFqdnSrv]
|
- [IsFqdnSrv]
|
||||||
* [IsFqdnWildcard]
|
- [IsFqdnWildcard]
|
||||||
|
|
||||||
# RFC Coverage
|
# RFC Coverage
|
||||||
|
|
||||||
This function should conform properly to:
|
This function should conform properly to:
|
||||||
|
|
||||||
* RFC 952
|
- RFC 952
|
||||||
* RFC 1034 and RFC 1035
|
- RFC 1034 and RFC 1035
|
||||||
* RFC 1123
|
- RFC 1123
|
||||||
* RFC 2181 (selectively, see above)
|
- RFC 2181 (selectively, see above)
|
||||||
|
|
||||||
preferring the most up-to-date rules where relevant (e.g. labels may start with digits, as per RFC 1123).
|
preferring the most up-to-date rules where relevant (e.g. labels may start with digits, as per RFC 1123).
|
||||||
It enforces/checks label and overall length limits as defined by RFC.
|
It enforces/checks label and overall length limits as defined by RFC.
|
||||||
@@ -265,12 +265,12 @@ the caller must perform translation first
|
|||||||
|
|
||||||
To reiterate, IDN/IDNA:
|
To reiterate, IDN/IDNA:
|
||||||
|
|
||||||
* RFC 3490
|
- RFC 3490
|
||||||
* RFC 5890
|
- RFC 5890
|
||||||
* RFC 5891
|
- RFC 5891
|
||||||
* RFC 5892
|
- RFC 5892
|
||||||
* RFC 5893
|
- RFC 5893
|
||||||
* RFC 5894
|
- RFC 5894
|
||||||
|
|
||||||
and Punycode (RFC 3492) *MUST* use their ASCII forms, NOT the localized/Unicode formats.
|
and Punycode (RFC 3492) *MUST* use their ASCII forms, NOT the localized/Unicode formats.
|
||||||
|
|
||||||
@@ -299,18 +299,18 @@ func IsFqdn(s string) (fqdn bool) {
|
|||||||
IsFqdnDefinedTxt is like [IsFqdn] but explicitly *only* allows fully-qualified
|
IsFqdnDefinedTxt is like [IsFqdn] but explicitly *only* allows fully-qualified
|
||||||
RFC-defined TXT "subtypes":
|
RFC-defined TXT "subtypes":
|
||||||
|
|
||||||
* ACME DNS-01 (RFC 8555)
|
- ACME DNS-01 (RFC 8555)
|
||||||
* BIMI (RFC draft [bimi])
|
- BIMI (RFC draft [bimi])
|
||||||
* DKIM (RFC 6376, RFC 8301, RFC 8463)
|
- DKIM (RFC 6376, RFC 8301, RFC 8463)
|
||||||
* DKIM ATPS (RFC 6541)
|
- DKIM ATPS (RFC 6541)
|
||||||
* DMARC (RFC 7489, RFC 9091, RFC 9616)
|
- DMARC (RFC 7489, RFC 9091, RFC 9616)
|
||||||
* MTA-STS (RFC 8461)
|
- MTA-STS (RFC 8461)
|
||||||
* TLSRPT (RFC 8460)
|
- TLSRPT (RFC 8460)
|
||||||
|
|
||||||
Note that the following TXT "subtypes" do not have special formatting in labels/name,
|
Note that the following TXT "subtypes" do not have special formatting in labels/name,
|
||||||
and thus are not covered by this function:
|
and thus are not covered by this function:
|
||||||
|
|
||||||
* SPF (RFC 4408, RFC 7208)
|
- SPF (RFC 4408, RFC 7208)
|
||||||
|
|
||||||
[bimi]: https://datatracker.ietf.org/doc/html/draft-brand-indicators-for-message-identification
|
[bimi]: https://datatracker.ietf.org/doc/html/draft-brand-indicators-for-message-identification
|
||||||
*/
|
*/
|
||||||
@@ -359,15 +359,15 @@ can't be 100% confirmed with certainty - only basic checks can be done.
|
|||||||
|
|
||||||
NSEC3 can be found via:
|
NSEC3 can be found via:
|
||||||
|
|
||||||
* RFC 5155
|
- RFC 5155
|
||||||
* RFC 6840
|
- RFC 6840
|
||||||
* RFC 6944
|
- RFC 6944
|
||||||
* RFC 7129
|
- RFC 7129
|
||||||
* RFC 8198
|
- RFC 8198
|
||||||
* RFC 9077
|
- RFC 9077
|
||||||
* RFC 9157
|
- RFC 9157
|
||||||
* RFC 9276
|
- RFC 9276
|
||||||
* RFC 9905
|
- RFC 9905
|
||||||
|
|
||||||
At the time of writing, only one hashing algorithm (SHA-1) has been specified.
|
At the time of writing, only one hashing algorithm (SHA-1) has been specified.
|
||||||
However, because this function does not check against the IANA registration at runtime,
|
However, because this function does not check against the IANA registration at runtime,
|
||||||
@@ -524,6 +524,22 @@ func IsPtr(s string) (isPtr bool, addr net.IP) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsPtrAddr is like IsPtr but only returns the `addr` value.
|
||||||
|
func IsPtrAddr(s string) (addr net.IP) {
|
||||||
|
|
||||||
|
_, addr = IsPtr(s)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPtrChk is like IsPtr but only returns the `isPtr` value.
|
||||||
|
func IsPtrChk(s string) (isPtr bool) {
|
||||||
|
|
||||||
|
isPtr, _ = IsPtr(s)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// commonFqdn is used to validate some rules common to all record names.
|
// commonFqdn is used to validate some rules common to all record names.
|
||||||
func commonFqdn(s string) (isOk bool) {
|
func commonFqdn(s string) (isOk bool) {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ptr
|
||||||
|
|
||||||
|
const (
|
||||||
|
TLD string = "arpa"
|
||||||
|
Zone4 string = "in-addr." + TLD
|
||||||
|
Zone6 string = "ip6." + TLD
|
||||||
|
)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/*
|
||||||
|
Package ptr includes some helper functions for working with PTR records/zones
|
||||||
|
and mapping/converting them to/from [net/netip.Addr] and [net/netip.Prefix] representations respectively.
|
||||||
|
*/
|
||||||
|
package ptr
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package ptr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidBounds error = errors.New("invalid PTR boundary")
|
||||||
|
ErrInvalidPfx error = errors.New("invalid prefix")
|
||||||
|
ErrInvalidPtr error = errors.New("invalid PTR")
|
||||||
|
)
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
package ptr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"go4.org/netipx"
|
||||||
|
"r00t2.io/subnetter/netsplit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddrPtr returns the PTR lookup name of IP address ip (as a [net/netip.Addr]), normalized (lowercased and no trailing dot).
|
||||||
|
func AddrPtr(ip netip.Addr) (ptr string, err error) { return IpStrPtr(ip.String()) }
|
||||||
|
|
||||||
|
// IpPtr returns the PTR lookup name of IP address ip (as a [net.IP]), normalized (lowercased and no trailing dot).
|
||||||
|
func IpPtr(ip net.IP) (ptr string, err error) { return IpStrPtr(ip.String()) }
|
||||||
|
|
||||||
|
// IpStrPtr returns the PTR lookup name of IP address ip (as a string), normalized (lowercased and no trailing dot).
|
||||||
|
func IpStrPtr(ip string) (ptr string, err error) {
|
||||||
|
|
||||||
|
if ptr, err = dns.ReverseAddr(ip); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = normalizePtr(ptr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PfxToPtr returns the PTR zone name(s) for a specified prefix.
|
||||||
|
Any host bits set in pfx will be masked off without error
|
||||||
|
(e.g. 192.0.2.20/24 -> 192.0.2.0/24).
|
||||||
|
|
||||||
|
err will be [ErrInvalidBounds] if pfx prefix length is longer than a /24 (e.g. a /32) for IPv4
|
||||||
|
or longer than the last/shorter than the first 4-bit/nibble/nybble boundary - /124 (e.g. a /128)
|
||||||
|
and /4 respectively - for IPv6.
|
||||||
|
*/
|
||||||
|
func PfxToPtr(pfx netip.Prefix) (ptrZones []string, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var tgtSz uint8
|
||||||
|
var n *net.IPNet
|
||||||
|
var maxBitLen int
|
||||||
|
var pfxs []*netip.Prefix
|
||||||
|
var c *netsplit.CIDRSplitter
|
||||||
|
|
||||||
|
if !pfx.IsValid() {
|
||||||
|
err = ErrInvalidPfx
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pfx = pfx.Masked()
|
||||||
|
|
||||||
|
if pfx.Addr().Is4() {
|
||||||
|
maxBitLen = 24
|
||||||
|
if pfx.Bits() <= 24 {
|
||||||
|
tgtSz = 24
|
||||||
|
}
|
||||||
|
if pfx.Bits() <= 16 {
|
||||||
|
tgtSz = 16
|
||||||
|
}
|
||||||
|
if pfx.Bits() <= 8 {
|
||||||
|
tgtSz = 8
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
maxBitLen = 124
|
||||||
|
if pfx.Bits() < 4 {
|
||||||
|
err = ErrInvalidBounds
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tgtSz = uint8(pfx.Bits())
|
||||||
|
for tgtSz%4 != 0 {
|
||||||
|
tgtSz++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pfx.Bits() > maxBitLen {
|
||||||
|
err = ErrInvalidBounds
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &netsplit.CIDRSplitter{
|
||||||
|
PrefixLength: tgtSz,
|
||||||
|
BaseSplitter: new(netsplit.BaseSplitter),
|
||||||
|
}
|
||||||
|
n = netipx.PrefixIPNet(pfx)
|
||||||
|
c.BaseSplitter.SetParent(*n)
|
||||||
|
|
||||||
|
if pfxs, _, err = c.Split(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ptrZones = make([]string, len(pfxs))
|
||||||
|
for idx = range pfxs {
|
||||||
|
ptrZones[idx] = pfxZonePtr(*pfxs[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// PtrToAddr returns a [net/netip.Addr] from a PTR record name.
|
||||||
|
func PtrToAddr(ptr string) (addr netip.Addr, err error) {
|
||||||
|
|
||||||
|
var pfx netip.Prefix
|
||||||
|
|
||||||
|
if pfx, err = PtrToPfx(ptr); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr = pfx.Addr()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PtrToPfx returns a [net/netip.Prefix] from a PTR zone (or record).
|
||||||
|
|
||||||
|
The same boundary rules as [PfxToPtr] are in place; delegated subzones (RFC 2317) are not supported.
|
||||||
|
If an invalid PTR is received, err will be [ErrInvalidPtr].
|
||||||
|
*/
|
||||||
|
func PtrToPfx(ptr string) (pfx netip.Prefix, err error) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var ok bool
|
||||||
|
var b []byte
|
||||||
|
var sfx string
|
||||||
|
var cidr uint8
|
||||||
|
var spl []string
|
||||||
|
var addr netip.Addr
|
||||||
|
|
||||||
|
ptr = normalizePtr(ptr)
|
||||||
|
spl = strings.Split(ptr, ".")
|
||||||
|
if len(spl) < 2 {
|
||||||
|
err = ErrInvalidPtr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sfx = strings.Join(spl[len(spl)-2:], ".")
|
||||||
|
spl = strings.Split(normalizePtr(strings.TrimSuffix(ptr, sfx)), ".")
|
||||||
|
|
||||||
|
slices.Reverse(spl)
|
||||||
|
|
||||||
|
switch sfx {
|
||||||
|
case Zone4:
|
||||||
|
switch len(spl) {
|
||||||
|
case 0:
|
||||||
|
cidr = 0
|
||||||
|
spl = []string{"0", "0", "0", "0"}
|
||||||
|
case 1, 2, 3, 4:
|
||||||
|
cidr = uint8(len(spl) * 8)
|
||||||
|
for len(spl) < 4 {
|
||||||
|
spl = append(spl, "0")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = ErrInvalidPtr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if addr, err = netip.ParseAddr(
|
||||||
|
strings.Join(spl, "."),
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case Zone6:
|
||||||
|
if len(spl) > 32 {
|
||||||
|
err = ErrInvalidPtr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(spl) == 0 {
|
||||||
|
cidr = 0
|
||||||
|
spl = make([]string, 32)
|
||||||
|
for idx = 0; idx < len(spl); idx++ {
|
||||||
|
spl[idx] = "0"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cidr = uint8(len(spl) * 4)
|
||||||
|
for len(spl) < 32 {
|
||||||
|
spl = append(spl, "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b, err = hex.DecodeString(
|
||||||
|
strings.ToLower(strings.Join(spl, "")),
|
||||||
|
); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if addr, ok = netip.AddrFromSlice(b); !ok {
|
||||||
|
err = ErrInvalidPfx
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = ErrInvalidPtr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pfx, err = addr.Prefix(int(cidr)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizePtr(ptr string) (norm string) { return strings.ToLower(strings.TrimRight(ptr, ".")) }
|
||||||
|
|
||||||
|
func pfxZonePtr(pfx netip.Prefix) (ptr string) {
|
||||||
|
|
||||||
|
if pfx.Addr().Is4() {
|
||||||
|
ptr = pfxZonePtr4(pfx)
|
||||||
|
} else {
|
||||||
|
ptr = pfxZonePtr6(pfx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pfxZonePtr4(pfx netip.Prefix) (ptr string) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
var b []byte
|
||||||
|
var s []string
|
||||||
|
|
||||||
|
idx = pfx.Bits() / 8
|
||||||
|
|
||||||
|
b = pfx.Addr().AsSlice()[0:idx]
|
||||||
|
s = make([]string, len(b))
|
||||||
|
|
||||||
|
for i := 0; i < idx; i++ {
|
||||||
|
s[idx-1-i] = strconv.Itoa(int(uint8(b[i])))
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = strings.Join(s, ".") + "." + Zone4
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pfxZonePtr6(pfx netip.Prefix) (ptr string) {
|
||||||
|
|
||||||
|
var b byte
|
||||||
|
var nib byte
|
||||||
|
var numNibs int
|
||||||
|
var b16 [16]byte
|
||||||
|
var sb *strings.Builder = new(strings.Builder)
|
||||||
|
|
||||||
|
numNibs = pfx.Bits() / 4
|
||||||
|
b16 = pfx.Addr().As16()
|
||||||
|
|
||||||
|
for i := numNibs - 1; i >= 0; i-- {
|
||||||
|
b = b16[i/2]
|
||||||
|
if i%2 == 0 {
|
||||||
|
nib = b >> 4
|
||||||
|
} else {
|
||||||
|
nib = b & 0x0f
|
||||||
|
}
|
||||||
|
fmt.Fprintf(
|
||||||
|
sb,
|
||||||
|
"%x.",
|
||||||
|
nib,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sb.WriteString(Zone6)
|
||||||
|
|
||||||
|
ptr = sb.String()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Package slicesx aims to extend functionality of the stdlib [slices] module.
|
Package slicesx aims to extend functionality of the stdlib [slices] module.
|
||||||
|
|
||||||
|
Most are convenience functions that aid in readability (sometimes at a VERY negligible performance hit),
|
||||||
|
but others can be quite useful (e.g. [Remove], [RemoveAll], [RemoveReverse]) that will save on significant boilerplate.
|
||||||
*/
|
*/
|
||||||
package slicesx
|
package slicesx
|
||||||
|
|||||||
+146
-1
@@ -1,10 +1,51 @@
|
|||||||
package slicesx
|
package slicesx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
First returns the first item/element in slice s.
|
||||||
|
|
||||||
|
e will be the nil/zero value of the slice element type
|
||||||
|
if the slice length is 0 (including if s == nil).
|
||||||
|
*/
|
||||||
|
func First[S ~[]E, E any](s S) (e E) {
|
||||||
|
|
||||||
|
if len(s) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e = s[0]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FirstIndex returns the index of the first element in slice s.
|
||||||
|
|
||||||
|
While at first glance this may seem useless, it may have some usecases as instead of causing panics:
|
||||||
|
|
||||||
|
- If s is empty, idx will be -1.
|
||||||
|
- If s is nil, idx will be -2.
|
||||||
|
- Otherwise idx will ALWAYS be 0.
|
||||||
|
*/
|
||||||
|
func FirstIndex[S ~[]E, E any](s S) (idx int) {
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
idx = -2
|
||||||
|
} else if len(s) == 0 {
|
||||||
|
idx = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Last returns the last item/element in slice s.
|
Last returns the last item/element in slice s.
|
||||||
|
|
||||||
e will be the nil/zero value of the slice element type
|
e will be the nil/zero value of the slice element type
|
||||||
if the slice length is 0.
|
if the slice length is 0 (including if s == nil).
|
||||||
*/
|
*/
|
||||||
func Last[S ~[]E, E any](s S) (e E) {
|
func Last[S ~[]E, E any](s S) (e E) {
|
||||||
|
|
||||||
@@ -41,3 +82,107 @@ func LastIndex[S ~[]E, E any](s S) (idx int) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove removes any values of value elem from slice s up to max number of elems.
|
||||||
|
|
||||||
|
If maxRemove is negative (< 0), start from the end instead.
|
||||||
|
This simply wraps [RemoveReverse]:
|
||||||
|
|
||||||
|
removed = slicesx.RemoveReverse(s, elem, uint(-maxRemove))
|
||||||
|
|
||||||
|
If maxRemove is exactly 0, remove ALL instances of elem.
|
||||||
|
|
||||||
|
If s is nil, removed will be nil as well.
|
||||||
|
|
||||||
|
Note that s will remain untouched, and removed will be a new slice.
|
||||||
|
*/
|
||||||
|
func Remove[S ~[]E, E comparable](s S, elem E, maxRemove int) (removed S) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
removed = []E{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
removed = make(S, 0, len(s))
|
||||||
|
defer func() {
|
||||||
|
if removed != nil {
|
||||||
|
removed = slices.Clip(removed)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if maxRemove < 0 {
|
||||||
|
removed = RemoveReverse(s, elem, uint(-maxRemove))
|
||||||
|
return
|
||||||
|
} else if maxRemove == 0 {
|
||||||
|
maxRemove = len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx = range s {
|
||||||
|
if s[idx] == elem && maxRemove > 0 {
|
||||||
|
maxRemove--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
removed = append(removed, s[idx])
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
RemoveAll is simply a convenience wrapper around [Remove], e.g.:
|
||||||
|
|
||||||
|
removed = slicesx.Remove(s, elem, 0)
|
||||||
|
*/
|
||||||
|
func RemoveAll[S ~[]E, E comparable](s S, elem E) (removed S) { return Remove(s, elem, 0) }
|
||||||
|
|
||||||
|
/*
|
||||||
|
RemoveReverse removes any values of value elem from slice s up to max number of elems in reverse order
|
||||||
|
(i.e. starting from the END of s).
|
||||||
|
|
||||||
|
If maxRemove is exactly 0, remove all instances of elem (via [RemoveAll]), i.e.:
|
||||||
|
|
||||||
|
removed = slicesx.RemoveAll(s, elem)
|
||||||
|
// or, more directly:
|
||||||
|
removed = slicesx.Remove(s, elem, 0)
|
||||||
|
|
||||||
|
If s is nil, removed will be nil as well.
|
||||||
|
|
||||||
|
Note that s will remain untouched, and removed will be a new slice.
|
||||||
|
*/
|
||||||
|
func RemoveReverse[S ~[]E, E comparable](s S, elem E, maxRemove uint) (removed S) {
|
||||||
|
|
||||||
|
var idx int
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
removed = []E{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxRemove == 0 {
|
||||||
|
removed = RemoveAll(s, elem)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
removed = make(S, 0, len(s))
|
||||||
|
|
||||||
|
for idx = len(s) - 1; idx >= 0; idx-- {
|
||||||
|
if s[idx] == elem && maxRemove > 0 {
|
||||||
|
maxRemove--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
removed = append(removed, s[idx])
|
||||||
|
}
|
||||||
|
removed = slices.Clip(removed)
|
||||||
|
slices.Reverse(removed)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -636,7 +636,7 @@ pre.rouge .gs {
|
|||||||
<div class="details">
|
<div class="details">
|
||||||
<span id="author" class="author">Brent Saner</span><br>
|
<span id="author" class="author">Brent Saner</span><br>
|
||||||
<span id="email" class="email"><a href="mailto:bts@square-r00t.net">bts@square-r00t.net</a></span><br>
|
<span id="email" class="email"><a href="mailto:bts@square-r00t.net">bts@square-r00t.net</a></span><br>
|
||||||
<span id="revdate">Last rendered 2026-07-29 15:05:41 -0400</span>
|
<span id="revdate">Last rendered 2026-09-11 22:12:00 -0400</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="toc" class="toc2">
|
<div id="toc" class="toc2">
|
||||||
<div id="toctitle">Table of Contents</div>
|
<div id="toctitle">Table of Contents</div>
|
||||||
|
|||||||
+214
-216
File diff suppressed because it is too large
Load Diff
+37
-36
@@ -1,30 +1,30 @@
|
|||||||
package sprigx
|
package sprigx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`net`
|
"net"
|
||||||
`net/netip`
|
"net/netip"
|
||||||
`os`
|
"os"
|
||||||
`os/user`
|
"os/user"
|
||||||
`path`
|
"path"
|
||||||
`path/filepath`
|
"path/filepath"
|
||||||
`runtime`
|
"runtime"
|
||||||
`time`
|
"time"
|
||||||
|
|
||||||
`github.com/davecgh/go-spew/spew`
|
"github.com/davecgh/go-spew/spew"
|
||||||
`github.com/shirou/gopsutil/v4/cpu`
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
`github.com/shirou/gopsutil/v4/disk`
|
"github.com/shirou/gopsutil/v4/disk"
|
||||||
`github.com/shirou/gopsutil/v4/host`
|
"github.com/shirou/gopsutil/v4/host"
|
||||||
`github.com/shirou/gopsutil/v4/load`
|
"github.com/shirou/gopsutil/v4/load"
|
||||||
`github.com/shirou/gopsutil/v4/mem`
|
"github.com/shirou/gopsutil/v4/mem"
|
||||||
psnet `github.com/shirou/gopsutil/v4/net`
|
psnet "github.com/shirou/gopsutil/v4/net"
|
||||||
`github.com/shirou/gopsutil/v4/process`
|
"github.com/shirou/gopsutil/v4/process"
|
||||||
`github.com/shirou/gopsutil/v4/sensors`
|
"github.com/shirou/gopsutil/v4/sensors"
|
||||||
`go4.org/netipx`
|
"go4.org/netipx"
|
||||||
`r00t2.io/goutils/netx`
|
"r00t2.io/goutils/netx"
|
||||||
`r00t2.io/goutils/netx/dnsx`
|
"r00t2.io/goutils/netx/dnsx"
|
||||||
`r00t2.io/goutils/stringsx`
|
"r00t2.io/goutils/stringsx"
|
||||||
`r00t2.io/goutils/timex`
|
"r00t2.io/goutils/timex"
|
||||||
`r00t2.io/sysutils`
|
"r00t2.io/sysutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -98,19 +98,20 @@ var (
|
|||||||
/*
|
/*
|
||||||
Networking (r00t.io/goutils/netx/dnsx)
|
Networking (r00t.io/goutils/netx/dnsx)
|
||||||
*/
|
*/
|
||||||
"dnsxPtrAddr": dnsx.AddrFromPtr,
|
"dnsxPtrAddr": dnsx.AddrFromPtr,
|
||||||
"dnsxAddrPtr": dnsx.AddrToPtr,
|
"dnsxAddrPtr": dnsx.AddrToPtr,
|
||||||
"dnsxStrWire": dnsx.DnsStrToWire,
|
"dnsxStrWire": dnsx.DnsStrToWire,
|
||||||
"dnsxWireStr": dnsx.DnsWireToStr,
|
"dnsxWireStr": dnsx.DnsWireToStr,
|
||||||
"dnsxPtrIp": dnsx.IpFromPtr,
|
"dnsxPtrIp": dnsx.IpFromPtr,
|
||||||
"dnsxIpPtr": dnsx.IpToPtr,
|
"dnsxIpPtr": dnsx.IpToPtr,
|
||||||
"dnsxIsFqdn": dnsx.IsFqdn,
|
"dnsxIsFqdn": dnsx.IsFqdn,
|
||||||
"dnsxIsTxt": dnsx.IsFqdnDefinedTxt,
|
"dnsxIsTxt": dnsx.IsFqdnDefinedTxt,
|
||||||
"dnsxIsNsec3": dnsx.IsFqdnNsec3,
|
"dnsxIsNsec3": dnsx.IsFqdnNsec3,
|
||||||
"dnsxIsSrv": dnsx.IsFqdnSrv,
|
"dnsxIsSrv": dnsx.IsFqdnSrv,
|
||||||
"dnsxIsWild": dnsx.IsFqdnWildcard,
|
"dnsxIsWild": dnsx.IsFqdnWildcard,
|
||||||
"dnsxIsLbl": dnsx.IsLabel,
|
"dnsxIsLbl": dnsx.IsLabel,
|
||||||
"dnsxIsPtr": dnsx.IsPtr,
|
"dnsxIsPtrAddr": dnsx.IsPtrAddr,
|
||||||
|
"dnsxIsPtr": dnsx.IsPtrChk,
|
||||||
/*
|
/*
|
||||||
Numbers/Math
|
Numbers/Math
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user