ident: implement member identifier
Signed-off-by: Yonah <contrib@gensokyo.uk>
This commit is contained in:
84
ident/member.go
Normal file
84
ident/member.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package ident
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// M represents a unique member identifier.
|
||||
type M struct {
|
||||
// A per-system value incremented by some unspecified amount every time the
|
||||
// metadata of a member first appears to the backend.
|
||||
Serial uint64
|
||||
// An instant in time, some time after the corresponding member metadata
|
||||
// first appeared to the backend, represented in nanoseconds since
|
||||
// 1970-01-01.
|
||||
Time uint64
|
||||
// Randomly generated value. The implementation must guarantee that the same
|
||||
// value cannot be emitted for a Time value.
|
||||
ID uint64
|
||||
|
||||
// Underlying system.
|
||||
System S
|
||||
}
|
||||
|
||||
const (
|
||||
// SizeMember is the size of the binary representation of [M].
|
||||
SizeMember = SizeSystem * 2
|
||||
// EncodedSizeMember is the size of the string representation of [M].
|
||||
EncodedSizeMember = SizeMember / 3 * 4
|
||||
)
|
||||
|
||||
// Encode appends the canonical string representation of mid to dst and returns
|
||||
// the extended buffer.
|
||||
func (mid *M) Encode(dst []byte) []byte {
|
||||
var buf [SizeMember - SizeSystem]byte
|
||||
|
||||
p := buf[:]
|
||||
binary.LittleEndian.PutUint64(p, mid.Serial)
|
||||
p = p[8:]
|
||||
binary.LittleEndian.PutUint64(p, mid.Time)
|
||||
p = p[8:]
|
||||
binary.LittleEndian.PutUint64(p, mid.ID)
|
||||
dst = base64.URLEncoding.AppendEncode(dst, buf[:])
|
||||
|
||||
return mid.System.Encode(dst)
|
||||
}
|
||||
|
||||
// String returns the canonical string representation of mid.
|
||||
func (mid *M) String() string {
|
||||
s := mid.Encode(nil)
|
||||
return unsafe.String(unsafe.SliceData(s), len(s))
|
||||
}
|
||||
|
||||
// MarshalText returns the result of Encode.
|
||||
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}
|
||||
}
|
||||
|
||||
var buf [SizeMember - SizeSystem]byte
|
||||
if n, err := base64.URLEncoding.Decode(
|
||||
buf[:],
|
||||
data[:EncodedSizeMember-EncodedSizeSystem],
|
||||
); err != nil {
|
||||
return err
|
||||
} else if n != SizeMember-SizeSystem {
|
||||
return ErrNewline
|
||||
}
|
||||
|
||||
p := buf[:]
|
||||
mid.Serial = binary.LittleEndian.Uint64(p)
|
||||
p = p[8:]
|
||||
mid.Time = binary.LittleEndian.Uint64(p)
|
||||
p = p[8:]
|
||||
mid.ID = binary.LittleEndian.Uint64(p)
|
||||
|
||||
return mid.System.UnmarshalText(data[EncodedSizeMember-EncodedSizeSystem:])
|
||||
}
|
||||
84
ident/member_test.go
Normal file
84
ident/member_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package ident_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.gensokyo.uk/cofront/cof-spec/ident"
|
||||
)
|
||||
|
||||
func TestM(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rTestCases[ident.M, *ident.M]{
|
||||
{"short", ident.M{}, nil, &ident.UnexpectedSizeError{
|
||||
Data: nil,
|
||||
Want: ident.EncodedSizeMember,
|
||||
}},
|
||||
|
||||
{"malformed", ident.M{}, []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,
|
||||
|
||||
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.M{}, []byte(
|
||||
"AAAA" + strings.Repeat("\n", ident.EncodedSizeMember-4),
|
||||
), ident.ErrNewline},
|
||||
|
||||
{"valid", ident.M{
|
||||
Serial: 0xfdfdfdfdfdfdfdfd,
|
||||
|
||||
Time: uint64(time.Date(
|
||||
0xfd, 7, 15,
|
||||
23, 59, 59, 0xcab,
|
||||
time.UTC,
|
||||
).UnixNano()),
|
||||
|
||||
ID: 0x2e736e64,
|
||||
|
||||
System: ident.S{
|
||||
Site: ident.TrivialSite,
|
||||
Host: ident.TrivialHost,
|
||||
|
||||
Time: uint64(time.Date(
|
||||
0xfd, 7, 15,
|
||||
23, 59, 59, 0xcafe,
|
||||
time.UTC,
|
||||
).UnixNano()),
|
||||
|
||||
ID: 0xfee1dead0badf00d,
|
||||
},
|
||||
}, base64.URLEncoding.AppendEncode(nil, []byte{
|
||||
/* member */
|
||||
|
||||
/* 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,
|
||||
|
||||
/* system */
|
||||
|
||||
/* site: */ 0xfe, 0xca, 0xed, 0xfe,
|
||||
/* host: */ 0xbe, 0xba, 0xfe, 0xca,
|
||||
|
||||
/* time: */ 0xfe, 0, 0x43, 0xce, 0xf1, 0x92, 0x4a, 0x10,
|
||||
/* id: */ 0xd, 0xf0, 0xad, 0xb, 0xad, 0xde, 0xe1, 0xfe,
|
||||
}), nil},
|
||||
}.run(t)
|
||||
}
|
||||
Reference in New Issue
Block a user