diff --git a/ident/ident_test.go b/ident/ident_test.go index bc13b5c..f49edca 100644 --- a/ident/ident_test.go +++ b/ident/ident_test.go @@ -117,15 +117,17 @@ func TestFM(t *testing.T) { {"valid", ident.F[ident.M, *ident.M]{ I: &ident.M{ - Serial: 0xfdfdfdfdfdfdfdfd, + PartM: ident.PartM{ + Serial: 0xfdfdfdfdfdfdfdfd, - Time: uint64(time.Date( - 0xfd, 7, 15, - 23, 59, 59, 0xcab, - time.UTC, - ).UnixNano()), + Time: uint64(time.Date( + 0xfd, 7, 15, + 23, 59, 59, 0xcab, + time.UTC, + ).UnixNano()), - ID: 0x2e736e64, + ID: 0x2e736e64, + }, System: ident.S{ Site: ident.TrivialSite, diff --git a/ident/member.go b/ident/member.go index 9d727f0..0f7adbf 100644 --- a/ident/member.go +++ b/ident/member.go @@ -6,8 +6,8 @@ import ( "unsafe" ) -// M represents a unique member identifier. -type M struct { +// PartM represents the first half of [M]. +type PartM struct { // A per-system value incremented by some unspecified amount every time the // metadata of a member first appears to the backend. Serial uint64 @@ -18,7 +18,11 @@ type M struct { // Randomly generated value. The implementation must guarantee that the same // value cannot be emitted for a Time value. ID uint64 +} +// M represents a unique member identifier. +type M struct { + PartM // Underlying system. System S } @@ -35,19 +39,35 @@ const ( // EncodedSize returns the number of bytes appended by Encode. func (*M) EncodedSize() int { return EncodedSizeMember } -// Encode appends the canonical string representation of mid to dst and returns +// Encode appends the canonical string representation of pm to dst and returns // the extended buffer. -func (mid *M) Encode(dst []byte) []byte { +func (pm *PartM) Encode(dst []byte) []byte { var buf [SizeMember - SizeSystem]byte p := buf[:] - binary.LittleEndian.PutUint64(p, mid.Serial) + binary.LittleEndian.PutUint64(p, pm.Serial) p = p[8:] - binary.LittleEndian.PutUint64(p, mid.Time) + binary.LittleEndian.PutUint64(p, pm.Time) p = p[8:] - binary.LittleEndian.PutUint64(p, mid.ID) - dst = base64.URLEncoding.AppendEncode(dst, buf[:]) + binary.LittleEndian.PutUint64(p, pm.ID) + return base64.URLEncoding.AppendEncode(dst, buf[:]) +} +// String returns the canonical string representation of pm. +func (pm *PartM) String() string { + s := pm.Encode(nil) + return unsafe.String(unsafe.SliceData(s), len(s)) +} + +// MarshalText returns the result of Encode. +func (pm *PartM) MarshalText() (data []byte, err error) { + return pm.Encode(nil), nil +} + +// Encode appends the canonical string representation of mid to dst and returns +// the extended buffer. +func (mid *M) Encode(dst []byte) []byte { + dst = mid.PartM.Encode(dst) return mid.System.Encode(dst) } @@ -62,16 +82,16 @@ func (mid *M) MarshalText() (data []byte, err error) { return mid.Encode(nil), nil } -// UnmarshalText strictly decodes data into mid. -func (mid *M) UnmarshalText(data []byte) error { - if len(data) != EncodedSizeMember { - return &UnexpectedSizeError{data, EncodedSizeMember} +// UnmarshalText strictly decodes data into pm. +func (pm *PartM) UnmarshalText(data []byte) error { + if len(data) != EncodedSizeMember-EncodedSizeSystem { + return &UnexpectedSizeError{data, EncodedSizeMember - EncodedSizeSystem} } var buf [SizeMember - SizeSystem]byte if n, err := base64.URLEncoding.Decode( buf[:], - data[:EncodedSizeMember-EncodedSizeSystem], + data, ); err != nil { return err } else if n != SizeMember-SizeSystem { @@ -79,11 +99,23 @@ func (mid *M) UnmarshalText(data []byte) error { } p := buf[:] - mid.Serial = binary.LittleEndian.Uint64(p) + pm.Serial = binary.LittleEndian.Uint64(p) p = p[8:] - mid.Time = binary.LittleEndian.Uint64(p) + pm.Time = binary.LittleEndian.Uint64(p) p = p[8:] - mid.ID = binary.LittleEndian.Uint64(p) + pm.ID = binary.LittleEndian.Uint64(p) + return nil +} +// UnmarshalText strictly decodes data into mid. +func (mid *M) UnmarshalText(data []byte) error { + if len(data) != EncodedSizeMember { + return &UnexpectedSizeError{data, EncodedSizeMember} + } + if err := mid.PartM.UnmarshalText( + data[:EncodedSizeMember-EncodedSizeSystem], + ); err != nil { + return err + } return mid.System.UnmarshalText(data[EncodedSizeMember-EncodedSizeSystem:]) } diff --git a/ident/member_test.go b/ident/member_test.go index a50ad98..fd04662 100644 --- a/ident/member_test.go +++ b/ident/member_test.go @@ -9,6 +9,48 @@ import ( "git.gensokyo.uk/cofront/cof-spec/ident" ) +func TestPartM(t *testing.T) { + t.Parallel() + + rTestCases[ident.PartM, *ident.PartM]{ + {"short", ident.PartM{}, nil, &ident.UnexpectedSizeError{ + Data: nil, + Want: ident.EncodedSizeMember - ident.EncodedSizeSystem, + }}, + + {"malformed", ident.PartM{}, []byte{ + 0xfe, 0xe1, 0xde, 0xad, + 0xfe, 0xe1, 0xde, 0xad, + 0xfe, 0xe1, 0xde, 0xad, + 0xfe, 0xe1, 0xde, 0xad, + 0xfe, 0xe1, 0xde, 0xad, + 0xfe, 0xe1, 0xde, 0xad, + 0xfe, 0xe1, 0xde, 0xad, + 0xfe, 0xe1, 0xde, 0xad, + }, base64.CorruptInputError(0)}, + + {"newline", ident.PartM{}, []byte( + "AAAA" + strings.Repeat("\n", ident.EncodedSizeMember-ident.EncodedSizeSystem-4), + ), ident.ErrNewline}, + + {"valid", ident.PartM{ + Serial: 0xfdfdfdfdfdfdfdfd, + + Time: uint64(time.Date( + 0xfd, 7, 15, + 23, 59, 59, 0xcab, + time.UTC, + ).UnixNano()), + + ID: 0x2e736e64, + }, base64.URLEncoding.AppendEncode(nil, []byte{ + /* serial: */ 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, + /* time: */ 0xab, 0x42, 0x42, 0xce, 0xf1, 0x92, 0x4a, 0x10, + /* id: */ 0x64, 0x6e, 0x73, 0x2e, 0, 0, 0, 0, + }), nil}, + }.run(t) +} + func TestM(t *testing.T) { t.Parallel() @@ -43,15 +85,17 @@ func TestM(t *testing.T) { ), ident.ErrNewline}, {"valid", ident.M{ - Serial: 0xfdfdfdfdfdfdfdfd, + PartM: ident.PartM{ + Serial: 0xfdfdfdfdfdfdfdfd, - Time: uint64(time.Date( - 0xfd, 7, 15, - 23, 59, 59, 0xcab, - time.UTC, - ).UnixNano()), + Time: uint64(time.Date( + 0xfd, 7, 15, + 23, 59, 59, 0xcab, + time.UTC, + ).UnixNano()), - ID: 0x2e736e64, + ID: 0x2e736e64, + }, System: ident.S{ Site: ident.TrivialSite,