aboutsummaryrefslogtreecommitdiff
path: root/objects
diff options
context:
space:
mode:
Diffstat (limited to 'objects')
-rw-r--r--objects/id.go111
-rw-r--r--objects/id_test.go77
-rw-r--r--objects/object.go17
-rw-r--r--objects/object_test.go18
4 files changed, 219 insertions, 4 deletions
diff --git a/objects/id.go b/objects/id.go
new file mode 100644
index 0000000..22eeaa2
--- /dev/null
+++ b/objects/id.go
@@ -0,0 +1,111 @@
+package objects
+
+import (
+ "bytes"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "golang.org/x/crypto/sha3"
+ "hash"
+ "io"
+ "strings"
+)
+
+type ObjectIdAlgo string
+
+const (
+ OIdAlgoSHA3_256 ObjectIdAlgo = "sha3-256"
+ OIdAlgoDefault = OIdAlgoSHA3_256
+)
+
+var allowedAlgos = map[ObjectIdAlgo]struct{}{OIdAlgoSHA3_256: {}}
+
+func (algo ObjectIdAlgo) checkAlgo() bool {
+ _, ok := allowedAlgos[algo]
+ return ok
+}
+
+func (algo ObjectIdAlgo) sumLength() int {
+ if algo != OIdAlgoSHA3_256 {
+ panic("Only sha3-256 is implemented!")
+ }
+
+ return 32
+}
+
+// ObjectId identifies an object using a cryptogrpahic hash
+type ObjectId struct {
+ Algo ObjectIdAlgo
+ Sum []byte
+}
+
+func (oid ObjectId) wellformed() bool {
+ return oid.Algo.checkAlgo() && len(oid.Sum) == oid.Algo.sumLength()
+}
+
+func (oid ObjectId) String() string {
+ return fmt.Sprintf("%s:%s", oid.Algo, hex.EncodeToString(oid.Sum))
+}
+
+func ParseObjectId(s string) (oid ObjectId, err error) {
+ parts := strings.SplitN(s, ":", 2)
+ if len(parts) != 2 {
+ err = errors.New("Invalid ObjectId format")
+ return
+ }
+
+ oid.Algo = ObjectIdAlgo(parts[0])
+
+ oid.Sum, err = hex.DecodeString(parts[1])
+ if err != nil {
+ return
+ }
+
+ if !oid.wellformed() {
+ err = errors.New("Object ID is malformed")
+ }
+
+ return
+}
+
+func (a ObjectId) Equals(b ObjectId) bool {
+ return a.Algo == b.Algo && bytes.Equal(a.Sum, b.Sum)
+}
+
+// ObjectIdGenerator generates an ObjectId from the binary representation of an object
+type ObjectIdGenerator interface {
+ io.Writer // Must not fail
+ GetId() ObjectId
+}
+
+func (algo ObjectIdAlgo) Generator() ObjectIdGenerator {
+ if algo != OIdAlgoSHA3_256 {
+ panic("Only sha3-256 is implemented!")
+ }
+
+ return hashObjectIdGenerator{
+ algo: algo,
+ Hash: sha3.New256(),
+ }
+}
+
+type hashObjectIdGenerator struct {
+ algo ObjectIdAlgo
+ hash.Hash
+}
+
+func (h hashObjectIdGenerator) GetId() ObjectId {
+ return ObjectId{
+ Algo: h.algo,
+ Sum: h.Sum([]byte{}),
+ }
+}
+
+func (oid ObjectId) VerifyObject(o Object) bool {
+ gen := oid.Algo.Generator()
+ if err := o.Serialize(gen); err != nil {
+ panic(err)
+ }
+
+ return gen.GetId().Equals(oid)
+}
diff --git a/objects/id_test.go b/objects/id_test.go
new file mode 100644
index 0000000..361798d
--- /dev/null
+++ b/objects/id_test.go
@@ -0,0 +1,77 @@
+package objects
+
+import (
+ "testing"
+)
+
+func TestParseValidId(t *testing.T) {
+ have, err := ParseObjectId("sha3-256:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
+ if err != nil {
+ t.Fatalf("Unexpected error: %s", err)
+ }
+
+ want := ObjectId{
+ Algo: OIdAlgoSHA3_256,
+ Sum: []byte{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ },
+ }
+
+ if !have.Equals(want) {
+ t.Errorf("unexpected result, want: '%s', have: '%s'", want, have)
+ }
+}
+
+func TestParseMalformedIds(t *testing.T) {
+ malformed := []string{
+ "", // Empty not permitted
+ "sha3-256", // Missing :
+ "sha3-256:", // Missing hex sum
+ ":abcdef", // Missing algo
+ "foobar:abcdef", // Basic format ok, but unknown algo
+ "sha3-256:foobar", // Not hexadecimal
+ "sha3-256:abcdef", // sum length mismatch
+ }
+
+ for _, s := range malformed {
+ oid, err := ParseObjectId(s)
+ if err == nil {
+ t.Errorf("'%s' resulted in valid id (%s), expected error", s, oid)
+ }
+ }
+}
+
+func TestParseSerialize(t *testing.T) {
+ want := ObjectId{
+ Algo: OIdAlgoSHA3_256,
+ Sum: []byte{
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ },
+ }
+
+ have, err := ParseObjectId(want.String())
+ if err != nil {
+ t.Fatalf("Unexpected error: %s", err)
+ }
+
+ if !have.Equals(want) {
+ t.Errorf("unexpected result, want: '%s', have: '%s'", want, have)
+ }
+}
+
+func TestSerializeParse(t *testing.T) {
+ want := "sha3-256:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+
+ oid, err := ParseObjectId(want)
+ if err != nil {
+ t.Fatalf("Unexpected error: %s", err)
+ }
+
+ have := oid.String()
+
+ if have != want {
+ t.Errorf("unexpected result, want: '%s', have: '%s'", want, have)
+ }
+}
diff --git a/objects/object.go b/objects/object.go
index 2bc3a53..a1d2335 100644
--- a/objects/object.go
+++ b/objects/object.go
@@ -7,10 +7,6 @@ import (
"strings"
)
-type ObjectId interface {
- fmt.Stringer
-}
-
type ObjectType string
const (
@@ -25,6 +21,7 @@ type Object struct {
Payload []byte
}
+// SerialiteObject writes the binary representation of an object to a io.Writer
func (o Object) Serialize(w io.Writer) error {
if _, err := fmt.Fprintf(w, "%s %d\n", o.Type, len(o.Payload)); err != nil {
return err
@@ -34,6 +31,16 @@ func (o Object) Serialize(w io.Writer) error {
return err
}
+func (o Object) SerializeAndId(w io.Writer, algo ObjectIdAlgo) (ObjectId, error) {
+ gen := algo.Generator()
+
+ if err := o.Serialize(io.MultiWriter(w, gen)); err != nil {
+ return ObjectId{}, err
+ }
+
+ return gen.GetId(), nil
+}
+
type UnserializeError struct {
Reason error
}
@@ -65,6 +72,8 @@ func (br *bytewiseReader) ReadByte() (b byte, err error) {
return
}
+// UnserializeObject attempts to read an object from a stream.
+// It is advisable to pass a buffered reader, if feasible.
func UnserializeObject(r io.Reader) (Object, error) {
br := newBytewiseReader(r)
diff --git a/objects/object_test.go b/objects/object_test.go
index c596b39..54b33db 100644
--- a/objects/object_test.go
+++ b/objects/object_test.go
@@ -86,3 +86,21 @@ func TestSerialize(t *testing.T) {
t.Errorf("Unexpected serialization result: %v", b)
}
}
+
+func TestSerializeAndId(t *testing.T) {
+ o := Object{
+ Type: OTBlob,
+ Payload: []byte("foo bar\nbaz"),
+ }
+
+ buf := new(bytes.Buffer)
+ id, err := o.SerializeAndId(buf, OIdAlgoDefault)
+
+ if err != nil {
+ t.Fatalf("Serialization failed:%s", err)
+ }
+
+ if !id.VerifyObject(o) {
+ t.Errorf("Verification failed")
+ }
+}