diff --git a/internal/nameutil/name.go b/internal/nameutil/name.go new file mode 100644 index 0000000..f81ad6c --- /dev/null +++ b/internal/nameutil/name.go @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: EUPL-1.2 + +package nameutil + +import core "dappco.re/go/core" + +// CatalogLabel converts string and byte inputs into a raw catalog label. +// +// It accepts the same input types as the public service helpers and preserves +// the bytes exactly, without applying canonical name suffix trimming. +func CatalogLabel(name any) (string, bool) { + switch v := name.(type) { + case string: + return v, true + case []byte: + return string(v), true + default: + return "", false + } +} + +// Canonicalize normalizes string and byte inputs into a lowercased LNS label. +// +// The helper rejects non-ASCII input, lowercases ASCII letters, and strips the +// optional trailing `.lthn` or trailing dot suffix used by service callers. +func Canonicalize(name any) (string, bool) { + var value []byte + + switch v := name.(type) { + case string: + value = []byte(v) + case []byte: + value = append([]byte(nil), v...) + default: + return "", false + } + + if len(value) == 0 { + return "", false + } + + for i := range value { + if value[i]&0x80 != 0 { + return "", false + } + + if value[i] >= 'A' && value[i] <= 'Z' { + value[i] += 'a' - 'A' + } + } + + str := string(value) + + switch { + case core.HasSuffix(str, ".lthn."): + str = core.TrimSuffix(str, ".lthn.") + case core.HasSuffix(str, ".lthn"): + str = core.TrimSuffix(str, ".lthn") + case core.HasSuffix(str, "."): + str = core.TrimSuffix(str, ".") + } + + if len(str) == 0 { + return "", false + } + + return str, true +} diff --git a/lns.go b/lns.go index 0516df4..582f221 100644 --- a/lns.go +++ b/lns.go @@ -14,6 +14,7 @@ package lns import ( core "dappco.re/go/core" + "dappco.re/go/lns/internal/nameutil" "dappco.re/go/lns/pkg/covenant" "dappco.re/go/lns/pkg/primitives" ) @@ -136,7 +137,7 @@ func (s *Service) HashByBinary(name []byte) (primitives.Hash, error) { // ResolveString returns the canonical hash for a .lthn name supplied as a string. func (s *Service) ResolveString(name string) (primitives.Hash, error) { - normalized, ok := canonicalizeName(name) + normalized, ok := nameutil.Canonicalize(name) if !ok { return primitives.Hash{}, core.E("lns.Service.ResolveString", "invalid name", nil) } @@ -146,7 +147,7 @@ func (s *Service) ResolveString(name string) (primitives.Hash, error) { // ResolveBinary returns the canonical hash for a .lthn name supplied as bytes. func (s *Service) ResolveBinary(name []byte) (primitives.Hash, error) { - normalized, ok := canonicalizeName(name) + normalized, ok := nameutil.Canonicalize(name) if !ok { return primitives.Hash{}, core.E("lns.Service.ResolveBinary", "invalid name", nil) } @@ -212,7 +213,7 @@ func (s *Service) VerifyByName(name any) bool { // // ok := svc.VerifyString("example.lthn") func (s *Service) VerifyString(name string) bool { - normalized, ok := canonicalizeName(name) + normalized, ok := nameutil.Canonicalize(name) if !ok { return false } @@ -224,7 +225,7 @@ func (s *Service) VerifyString(name string) bool { // // ok := svc.VerifyBinary([]byte("example.lthn")) func (s *Service) VerifyBinary(name []byte) bool { - normalized, ok := canonicalizeName(name) + normalized, ok := nameutil.Canonicalize(name) if !ok { return false } @@ -270,7 +271,7 @@ func (s *Service) GetReserved(name any) (covenant.ReservedName, bool) { // svc := c.Service("lns").(*lns.Service) // item, ok := svc.GetReservedName("reserved") func (s *Service) GetReservedName(name any) (covenant.ReservedName, bool) { - if label, ok := catalogLabel(name); ok { + if label, ok := nameutil.CatalogLabel(name); ok { if item, ok := covenant.GetReservedName(label); ok { return item, true } @@ -433,7 +434,7 @@ func (s *Service) GetLocked(name any) (covenant.LockedName, bool) { // svc := c.Service("lns").(*lns.Service) // item, ok := svc.GetLockedName("nec") func (s *Service) GetLockedName(name any) (covenant.LockedName, bool) { - if label, ok := catalogLabel(name); ok { + if label, ok := nameutil.CatalogLabel(name); ok { if item, ok := covenant.GetLockedName(label); ok { return item, true } @@ -791,58 +792,3 @@ func (s *Service) LockedValues() []covenant.LockedName { func (s *Service) GetLockedValues() []covenant.LockedName { return s.LockedValues() } - -func catalogLabel(name any) (string, bool) { - switch v := name.(type) { - case string: - return v, true - case []byte: - return string(v), true - default: - return "", false - } -} - -func canonicalizeName(name any) (string, bool) { - var value []byte - - switch v := name.(type) { - case string: - value = []byte(v) - case []byte: - value = append([]byte(nil), v...) - default: - return "", false - } - - if len(value) == 0 { - return "", false - } - - for i := range value { - if value[i]&0x80 != 0 { - return "", false - } - - if value[i] >= 'A' && value[i] <= 'Z' { - value[i] += 'a' - 'A' - } - } - - str := string(value) - - switch { - case core.HasSuffix(str, ".lthn."): - str = core.TrimSuffix(str, ".lthn.") - case core.HasSuffix(str, ".lthn"): - str = core.TrimSuffix(str, ".lthn") - case core.HasSuffix(str, "."): - str = core.TrimSuffix(str, ".") - } - - if len(str) == 0 { - return "", false - } - - return str, true -} diff --git a/pkg/dns/resolve.go b/pkg/dns/resolve.go index 272da6e..364e58b 100644 --- a/pkg/dns/resolve.go +++ b/pkg/dns/resolve.go @@ -12,6 +12,7 @@ package dns import ( core "dappco.re/go/core" + "dappco.re/go/lns/internal/nameutil" "dappco.re/go/lns/pkg/covenant" "dappco.re/go/lns/pkg/primitives" ) @@ -60,7 +61,7 @@ func WithCore(c *core.Core) ServiceOption { // svc := dns.NewService() // hash, err := svc.Resolve("example.lthn") func (s *Service) Resolve(name any) (primitives.Hash, error) { - normalized, ok := canonicalizeName(name) + normalized, ok := nameutil.Canonicalize(name) if !ok { return primitives.Hash{}, core.E("dns.Service.Resolve", "invalid name type", nil) } @@ -87,7 +88,7 @@ func (s *Service) Hash(name any) (primitives.Hash, error) { // svc := dns.NewService() // ok := svc.Verify("example.lthn") func (s *Service) Verify(name any) bool { - normalized, ok := canonicalizeName(name) + normalized, ok := nameutil.Canonicalize(name) if !ok { return false } @@ -150,7 +151,7 @@ func (s *Service) ResolveString(name string) (primitives.Hash, error) { // svc := dns.NewService() // hash, err := svc.ResolveBinary([]byte("example.lthn")) func (s *Service) ResolveBinary(name []byte) (primitives.Hash, error) { - normalized, ok := canonicalizeName(name) + normalized, ok := nameutil.Canonicalize(name) if !ok { return primitives.Hash{}, core.E("dns.Service.ResolveBinary", "invalid name type", nil) } @@ -188,7 +189,7 @@ func (s *Service) VerifyString(name string) bool { // svc := dns.NewService() // ok := svc.VerifyBinary([]byte("example.lthn")) func (s *Service) VerifyBinary(name []byte) bool { - normalized, ok := canonicalizeName(name) + normalized, ok := nameutil.Canonicalize(name) if !ok { return false } @@ -240,29 +241,3 @@ func (s *Service) HashByName(name any) (primitives.Hash, error) { func (s *Service) VerifyByName(name any) bool { return s.VerifyName(name) } - -func canonicalizeName(name any) (string, bool) { - var value string - - switch v := name.(type) { - case string: - value = v - case []byte: - value = string(v) - default: - return "", false - } - - value = core.Lower(value) - - switch { - case core.HasSuffix(value, ".lthn."): - value = core.TrimSuffix(value, ".lthn.") - case core.HasSuffix(value, ".lthn"): - value = core.TrimSuffix(value, ".lthn") - case core.HasSuffix(value, "."): - value = core.TrimSuffix(value, ".") - } - - return value, true -}