Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions construct.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ type Components struct {

// NewFromParts builds an OrderlyID from explicit component values.
//
// NewFromParts may return an error wrapping ErrInvalidPrefix.
// NewFromParts may return an error wrapping ErrInvalidPrefix or
// ErrUnsupportedVersion.
func NewFromParts(c Components, withChecksum bool) (string, error) {
if err := validatePrefix(c.Prefix); err != nil {
return "", err
}
if wireVersion(c.Flags) != supportedWireVersion {
return "", fmt.Errorf("%w: %d", ErrUnsupportedVersion, wireVersion(c.Flags))
}
// Convert absolute time to ms since 2020-01-01 UTC (epoch2020).
var msSince2020 uint64
if c.TimeMs >= epoch2020 {
Expand All @@ -54,8 +58,8 @@ func NewFromParts(c Components, withChecksum bool) (string, error) {
// NewFromPartsHex builds an OrderlyID from explicit component values and a
// big-endian random value encoded as hex.
//
// NewFromPartsHex may return an error wrapping ErrInvalidPrefix or
// ErrInvalidRandomHex.
// NewFromPartsHex may return an error wrapping ErrInvalidPrefix,
// ErrInvalidRandomHex, or ErrUnsupportedVersion.
func NewFromPartsHex(c Components, randomHex string, withChecksum bool) (string, error) {
rb, err := hex.DecodeString(randomHex)
if err != nil {
Expand Down
18 changes: 14 additions & 4 deletions orderlyid.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ var (
ErrInvalidBase32 = errors.New("orderlyid: invalid base32")
// ErrInvalidRandomHex reports invalid random hex input passed to NewFromPartsHex.
ErrInvalidRandomHex = errors.New("orderlyid: invalid random hex")
// ErrUnsupportedVersion reports OrderlyIDs with unsupported wire version bits.
ErrUnsupportedVersion = errors.New("orderlyid: unsupported wire version")
)

func init() {
Expand Down Expand Up @@ -108,9 +110,9 @@ func init() {
}

const (
versionBits = 0 // v1
privacyBitMask = 1 << 5
epoch2020 int64 = 1577836800000 // 2020-01-01T00:00:00Z in ms
supportedWireVersion = 0 // v1
privacyBitMask = 1 << 5
epoch2020 int64 = 1577836800000 // 2020-01-01T00:00:00Z in ms
)

var (
Expand Down Expand Up @@ -200,7 +202,8 @@ type Parsed struct {
// Parse decodes an OrderlyID string and returns its components.
//
// Parse may return errors wrapping ErrInvalidFormat, ErrInvalidPrefix,
// ErrInvalidChecksum, ErrInvalidPayloadLength, or ErrInvalidBase32.
// ErrInvalidChecksum, ErrInvalidPayloadLength, ErrInvalidBase32, or
// ErrUnsupportedVersion.
func Parse(s string) (*Parsed, error) {
s = strings.TrimSpace(s)
base := s
Expand Down Expand Up @@ -242,6 +245,9 @@ func Parse(s string) (*Parsed, error) {
return nil, err
}
ms, flags, tenant, seq, shard, random60 := unpack(buf)
if wireVersion(flags) != supportedWireVersion {
return nil, fmt.Errorf("%w: %d", ErrUnsupportedVersion, wireVersion(flags))
}
return &Parsed{
Prefix: prefix,
TimeMs: int64(ms) + epoch2020,
Expand All @@ -253,6 +259,10 @@ func Parse(s string) (*Parsed, error) {
}, nil
}

func wireVersion(flags byte) byte {
return flags >> 6
}

// Packing layout (big-endian)
// | 48b time | 8b flags | 16b tenant | 12b seq | 16b shard | 60b random |
func pack(ms uint64, flags byte, tenant uint16, seq12 uint16, shard uint16, random60 uint64) (out [20]byte) {
Expand Down
19 changes: 19 additions & 0 deletions orderlyid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,32 @@ func TestParseInvalidChecksumBaseDoesNotPanic(t *testing.T) {
}
}

func TestParseRejectsUnsupportedWireVersion(t *testing.T) {
body := pack(0, 0x40, 0, 0, 0, 0)
id := "order_" + b32encode(body[:])

_, err := Parse(id)
if err == nil {
t.Fatalf("expected unsupported version error")
}
if !errors.Is(err, ErrUnsupportedVersion) {
t.Fatalf("expected ErrUnsupportedVersion, got %v", err)
}
}

func TestNewFromPartsErrorsSupportErrorsIs(t *testing.T) {
if _, err := NewFromParts(Components{Prefix: "Bad!"}, false); err == nil {
t.Fatalf("expected invalid prefix error")
} else if !errors.Is(err, ErrInvalidPrefix) {
t.Fatalf("expected ErrInvalidPrefix, got %v", err)
}

if _, err := NewFromParts(Components{Prefix: "order", Flags: 0x40}, false); err == nil {
t.Fatalf("expected unsupported version error")
} else if !errors.Is(err, ErrUnsupportedVersion) {
t.Fatalf("expected ErrUnsupportedVersion, got %v", err)
}

if _, err := NewFromPartsHex(Components{Prefix: "order"}, "zz", false); err == nil {
t.Fatalf("expected invalid random hex error")
} else if !errors.Is(err, ErrInvalidRandomHex) {
Expand Down
Loading