diff --git a/internal/testrand/rand.go b/internal/testrand/rand.go index 933482fe2..c6b479f6d 100644 --- a/internal/testrand/rand.go +++ b/internal/testrand/rand.go @@ -93,6 +93,13 @@ func SerialNumber() storj.SerialNumber { return serial } +// StreamID creates a random stream ID +func StreamID() storj.StreamID { + var streamID storj.StreamID + Read(streamID[:]) + return streamID +} + // UUID creates a random uuid. func UUID() uuid.UUID { var uuid uuid.UUID diff --git a/pkg/pb/types.go b/pkg/pb/types.go index 0d412c5de..669301295 100644 --- a/pkg/pb/types.go +++ b/pkg/pb/types.go @@ -19,3 +19,6 @@ type PieceID = storj.PieceID // SerialNumber is an alias to storj.SerialNumber for use in generated protobuf code type SerialNumber = storj.SerialNumber + +// StreamID is an alias to storj.StreamID for use in generated protobuf code +type StreamID = storj.StreamID diff --git a/pkg/storj/streamid.go b/pkg/storj/streamid.go new file mode 100644 index 000000000..0e666636c --- /dev/null +++ b/pkg/storj/streamid.go @@ -0,0 +1,105 @@ +// Copyright (C) 2019 Storj Labs, Inc. +// See LICENSE for copying information. + +package storj + +import ( + "database/sql/driver" + "encoding/base32" + + "github.com/zeebo/errs" +) + +// ErrStreamID is used when something goes wrong with a stream ID +var ErrStreamID = errs.Class("stream ID error") + +// streamIDEncoding is base32 without padding +var streamIDEncoding = base32.StdEncoding.WithPadding(base32.NoPadding) + +// StreamID is the unique identifier for stream related to object +type StreamID [16]byte + +// StreamIDFromString decodes an base32 encoded +func StreamIDFromString(s string) (StreamID, error) { + idBytes, err := streamIDEncoding.DecodeString(s) + if err != nil { + return StreamID{}, ErrStreamID.Wrap(err) + } + return StreamIDFromBytes(idBytes) +} + +// StreamIDFromBytes converts a byte slice into a stream ID +func StreamIDFromBytes(b []byte) (StreamID, error) { + if len(b) != len(StreamID{}) { + return StreamID{}, ErrStreamID.New("not enough bytes to make a stream ID; have %d, need %d", len(b), len(NodeID{})) + } + + var id StreamID + copy(id[:], b) + return id, nil +} + +// IsZero returns whether stream ID is unassigned +func (id StreamID) IsZero() bool { + return id == StreamID{} +} + +// String representation of the stream ID +func (id StreamID) String() string { return streamIDEncoding.EncodeToString(id.Bytes()) } + +// Bytes returns bytes of the stream ID +func (id StreamID) Bytes() []byte { return id[:] } + +// Marshal serializes a stream ID +func (id StreamID) Marshal() ([]byte, error) { + return id.Bytes(), nil +} + +// MarshalTo serializes a stream ID into the passed byte slice +func (id *StreamID) MarshalTo(data []byte) (n int, err error) { + n = copy(data, id.Bytes()) + return n, nil +} + +// Unmarshal deserializes a stream ID +func (id *StreamID) Unmarshal(data []byte) error { + var err error + *id, err = StreamIDFromBytes(data) + return err +} + +// Size returns the length of a stream ID (implements gogo's custom type interface) +func (id *StreamID) Size() int { + return len(id) +} + +// MarshalJSON serializes a stream ID to a json string as bytes +func (id StreamID) MarshalJSON() ([]byte, error) { + return []byte(`"` + id.String() + `"`), nil +} + +// UnmarshalJSON deserializes a json string (as bytes) to a stream ID +func (id *StreamID) UnmarshalJSON(data []byte) error { + var err error + *id, err = StreamIDFromString(string(data)) + if err != nil { + return err + } + return nil +} + +// Value set a stream ID to a database field +func (id StreamID) Value() (driver.Value, error) { + return id.Bytes(), nil +} + +// Scan extracts a stream ID from a database field +func (id *StreamID) Scan(src interface{}) (err error) { + b, ok := src.([]byte) + if !ok { + return ErrStreamID.New("Stream ID Scan expects []byte") + } + n, err := StreamIDFromBytes(b) + *id = n + return err +} diff --git a/pkg/storj/streamid_test.go b/pkg/storj/streamid_test.go new file mode 100644 index 000000000..1a73ca63e --- /dev/null +++ b/pkg/storj/streamid_test.go @@ -0,0 +1,33 @@ +// Copyright (C) 2019 Storj Labs, Inc. +// See LICENSE for copying information. + +package storj_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "storj.io/storj/internal/testrand" + "storj.io/storj/pkg/storj" +) + +func TestStreamID_Encode(t *testing.T) { + _, err := storj.StreamIDFromString("likn43kilfzd") + assert.Error(t, err) + + _, err = storj.StreamIDFromBytes([]byte{1, 2, 3, 4, 5}) + assert.Error(t, err) + + for i := 0; i < 10; i++ { + streamID := testrand.StreamID() + + fromString, err := storj.StreamIDFromString(streamID.String()) + assert.NoError(t, err) + fromBytes, err := storj.StreamIDFromBytes(streamID.Bytes()) + assert.NoError(t, err) + + assert.Equal(t, streamID, fromString) + assert.Equal(t, streamID, fromBytes) + } +}