package ect import ( "encoding/base64" "encoding/json" "regexp" "strings" ) // ExtMaxSize is the recommended max serialized size of ext (Section 4.2.7). const ExtMaxSize = 4096 // ExtMaxDepth is the recommended max JSON nesting depth in ext. const ExtMaxDepth = 5 // DefaultMaxPredLength is the recommended max number of predecessor references. const DefaultMaxPredLength = 100 // uuidRegex matches RFC 9562 UUID: 8-4-4-4-12 hex. var uuidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) // allowedHashAlgs are the spec-recommended hash algorithm prefixes for inp_hash/out_hash. var allowedHashAlgs = map[string]bool{"sha-256": true, "sha-384": true, "sha-512": true} // ValidateExt returns an error if ext exceeds ExtMaxSize or ExtMaxDepth. func ValidateExt(ext map[string]interface{}) error { if ext == nil || len(ext) == 0 { return nil } b, err := json.Marshal(ext) if err != nil { return err } if len(b) > ExtMaxSize { return ErrExtSize } if depth(b) > ExtMaxDepth { return ErrExtDepth } return nil } func depth(b []byte) int { var max, level int for _, c := range b { switch c { case '{', '[': level++ if level > max { max = level } case '}', ']': level-- } } return max } // ValidUUID returns true if s is a UUID string (RFC 9562). func ValidUUID(s string) bool { return uuidRegex.MatchString(s) } // ValidateHashFormat returns nil if s is empty or matches "algorithm:base64url" (sha-256, sha-384, sha-512). func ValidateHashFormat(s string) error { if s == "" { return nil } idx := strings.Index(s, ":") if idx <= 0 { return ErrHashFormat } alg := strings.ToLower(s[:idx]) if !allowedHashAlgs[alg] { return ErrHashFormat } encoded := s[idx+1:] if encoded == "" { return ErrHashFormat } _, err := base64.RawURLEncoding.DecodeString(encoded) return err }