diff --git a/logtail/id.go b/logtail/id.go index 5f78bd744..164995c42 100644 --- a/logtail/id.go +++ b/logtail/id.go @@ -8,6 +8,7 @@ "crypto/rand" "crypto/sha256" "encoding/hex" + "errors" "fmt" ) @@ -37,6 +38,23 @@ func (id PrivateID) MarshalText() ([]byte, error) { return b, nil } +// ParsePrivateID returns a PrivateID from its hex (String) representation. +func ParsePrivateID(s string) (PrivateID, error) { + if len(s) != 64 { + return PrivateID{}, errors.New("invalid length") + } + var p PrivateID + for i := range p { + a, ok1 := fromHexChar(s[i*2+0]) + b, ok2 := fromHexChar(s[i*2+1]) + if !ok1 || !ok2 { + return PrivateID{}, errors.New("invalid hex character") + } + p[i] = (a << 4) | b + } + return p, nil +} + func (id *PrivateID) UnmarshalText(s []byte) error { b, err := hex.DecodeString(string(s)) if err != nil { @@ -101,3 +119,17 @@ func (id PublicID) String() string { } return string(b) } + +// fromHexChar converts a hex character into its value and a success flag. +func fromHexChar(c byte) (byte, bool) { + switch { + case '0' <= c && c <= '9': + return c - '0', true + case 'a' <= c && c <= 'f': + return c - 'a' + 10, true + case 'A' <= c && c <= 'F': + return c - 'A' + 10, true + } + + return 0, false +} diff --git a/logtail/id_test.go b/logtail/id_test.go index 25000eab7..b335562d6 100644 --- a/logtail/id_test.go +++ b/logtail/id_test.go @@ -51,4 +51,12 @@ func TestIDs(t *testing.T) { if id1.String() != id3.String() { t.Fatalf("id1.String()=%v does not match id3.String()=%v", id1.String(), id3.String()) } + + id4, err := ParsePrivateID(id1.String()) + if err != nil { + t.Fatalf("failed to ParsePrivateID(%q): %v", id1.String(), err) + } + if id1 != id4 { + t.Fatalf("ParsePrivateID returned different id") + } }