From aa48eb8ac49c7b612e1700f38356b676e80c3583 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Wed, 3 Jan 2024 07:18:19 -0500 Subject: [PATCH] feat: add CID --- cid/cid.go | 230 +++++++++++++++++++++++ cid/cid_test.go | 476 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 706 insertions(+) create mode 100644 cid/cid.go create mode 100644 cid/cid_test.go diff --git a/cid/cid.go b/cid/cid.go new file mode 100644 index 0000000..739c5cc --- /dev/null +++ b/cid/cid.go @@ -0,0 +1,230 @@ +package cid + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "git.lumeweb.com/LumeWeb/libs5-go/cid/multibase" + "git.lumeweb.com/LumeWeb/libs5-go/internal/bases" + "git.lumeweb.com/LumeWeb/libs5-go/multihash" + "git.lumeweb.com/LumeWeb/libs5-go/types" + "git.lumeweb.com/LumeWeb/libs5-go/utils" +) + +var ( + errEmptyBytes = errors.New("empty bytes") + errInvalidInputType = errors.New("invalid input type for bytes") +) + +type CID struct { + multibase.Multibase + Type types.CIDType + Hash multihash.Multihash + Size uint32 +} + +func New(Type types.CIDType, Hash multihash.Multihash, Size uint32) *CID { + c := &CID{ + Type: Type, + Hash: Hash, + Size: Size, + } + m := multibase.New(c) + c.Multibase = m + + return c +} + +func (cid *CID) getPrefixBytes() []byte { + return []byte{byte(cid.Type)} +} + +func (cid *CID) ToBytes() []byte { + if cid.Type == types.CIDTypeBridge { + return cid.Hash.FullBytes + } else if cid.Type == types.CIDTypeRaw { + sizeBytes := utils.EncodeEndian(cid.Size, 8) + + for len(sizeBytes) > 0 && sizeBytes[len(sizeBytes)-1] == 0 { + sizeBytes = sizeBytes[:len(sizeBytes)-1] + } + if len(sizeBytes) == 0 { + sizeBytes = []byte{0} + } + + return utils.ConcatBytes(cid.getPrefixBytes(), cid.Hash.FullBytes, sizeBytes) + } + + return utils.ConcatBytes(cid.getPrefixBytes(), cid.Hash.FullBytes) +} + +func Decode(cid string) (*CID, error) { + decodedBytes, err := multibase.DecodeString(cid) + if err != nil { + return nil, err + } + + cidInstance, err := initCID(decodedBytes) + + if err != nil { + return nil, err + } + + return cidInstance, nil +} + +func FromRegistry(bytes []byte) (*CID, error) { + if len(bytes) == 0 { + return nil, errEmptyBytes + } + + registryType := types.RegistryType(bytes[0]) + if _, exists := types.RegistryTypeMap[registryType]; !exists { + return nil, fmt.Errorf("invalid registry type %d", bytes[0]) + } + + bytes = bytes[1:] + + cidInstance, err := initCID(bytes) + + if err != nil { + return nil, err + } + + return cidInstance, nil +} + +func FromBytes(bytes []byte) (*CID, error) { + return initCID(bytes) +} + +func FromHash(bytes interface{}, size uint32, cidType types.CIDType) (*CID, error) { + var ( + byteSlice []byte + err error + ) + + switch v := bytes.(type) { + case string: + byteSlice, err = hex.DecodeString(v) + if err != nil { + return nil, err + } + case []byte: + byteSlice = v + default: + return nil, errInvalidInputType + } + + if _, exists := types.CIDTypeMap[cidType]; !exists { + return nil, fmt.Errorf("invalid hash type %d", cidType) + } + + return New(cidType, *multihash.New(byteSlice), size), nil +} + +func Verify(bytes interface{}) bool { + var ( + byteSlice []byte + err error + ) + + switch v := bytes.(type) { + case string: + byteSlice, err = multibase.DecodeString(v) // Assuming MultibaseDecodeString function is defined + if err != nil { + return false + } + case []byte: + byteSlice = v + default: + return false + } + + _, err = initCID(byteSlice) + return err == nil +} + +func (cid *CID) CopyWith(newType int, newSize uint32) (*CID, error) { + if newType == 0 { + newType = int(cid.Type) + } + + if _, exists := types.CIDTypeMap[types.CIDType(newType)]; !exists { + return nil, fmt.Errorf("invalid cid type %d", newType) + } + + return New(types.CIDType(newType), cid.Hash, newSize), nil +} + +func (cid *CID) ToRegistryEntry() []byte { + registryType := types.RegistryTypeCID + cidBytes := cid.ToBytes() + return utils.ConcatBytes([]byte{byte(registryType)}, cidBytes) +} + +func (cid *CID) ToRegistryCID() ([]byte, error) { + registryCIDType := types.CIDTypeResolver + copiedCID, err := cid.CopyWith(int(registryCIDType), cid.Size) + if err != nil { + return nil, err + } + return copiedCID.ToBytes(), nil +} + +func (cid *CID) ToString() (string, error) { + if cid.Type == types.CIDTypeBridge { + return cid.Hash.ToString() + } + + return bases.ToBase58BTC(cid.ToBytes()) +} + +func (cid *CID) Equals(other *CID) bool { + return bytes.Equal(cid.ToBytes(), other.ToBytes()) +} + +func (cid *CID) HashCode() int { + fullBytes := cid.ToBytes() + if len(fullBytes) < 4 { + return 0 + } + + return int(fullBytes[0]) + + int(fullBytes[1])<<8 + + int(fullBytes[2])<<16 + + int(fullBytes[3])<<24 +} + +func FromRegistryPublicKey(pubkey interface{}) (*CID, error) { + return FromHash(pubkey, 0, types.CIDTypeResolver) +} + +func initCID(bytes []byte) (*CID, error) { + if len(bytes) == 0 { + return nil, errEmptyBytes + } + + cidType := types.CIDType(bytes[0]) + if cidType == types.CIDTypeBridge { + hash := multihash.New(bytes[1:35]) + return New(cidType, *hash, 0), nil + } + + hashBytes := bytes[1:34] + hash := multihash.New(hashBytes) + + var size uint32 + if len(bytes) > 34 { + sizeBytes := bytes[34:] + sizeValue := utils.DecodeEndian(sizeBytes) + size = sizeValue + } + + if _, exists := types.CIDTypeMap[cidType]; !exists { + return nil, fmt.Errorf("invalid cid type %d", cidType) + } + + return New(cidType, *hash, size), nil +} diff --git a/cid/cid_test.go b/cid/cid_test.go new file mode 100644 index 0000000..4e756e7 --- /dev/null +++ b/cid/cid_test.go @@ -0,0 +1,476 @@ +package cid + +import ( + "git.lumeweb.com/LumeWeb/libs5-go/cid/multibase" + "git.lumeweb.com/LumeWeb/libs5-go/internal/testdata" + "git.lumeweb.com/LumeWeb/libs5-go/multihash" + "git.lumeweb.com/LumeWeb/libs5-go/types" + "reflect" + "testing" +) + +func TestCID_CopyWith(t *testing.T) { + type fields struct { + Multibase multibase.Multibase + Type types.CIDType + Hash multihash.Multihash + Size uint32 + } + type args struct { + newType int + newSize uint32 + } + tests := []struct { + name string + fields fields + args args + want *CID + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cid := &CID{ + Multibase: tt.fields.Multibase, + Type: tt.fields.Type, + Hash: tt.fields.Hash, + Size: tt.fields.Size, + } + got, err := cid.CopyWith(tt.args.newType, tt.args.newSize) + if (err != nil) != tt.wantErr { + t.Errorf("CopyWith() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("CopyWith() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCID_Equals(t *testing.T) { + type fields struct { + Multibase multibase.Multibase + Type types.CIDType + Hash multihash.Multihash + Size uint32 + } + type args struct { + other *CID + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cid := &CID{ + Multibase: tt.fields.Multibase, + Type: tt.fields.Type, + Hash: tt.fields.Hash, + Size: tt.fields.Size, + } + if got := cid.Equals(tt.args.other); got != tt.want { + t.Errorf("Equals() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCID_HashCode(t *testing.T) { + type fields struct { + Multibase multibase.Multibase + Type types.CIDType + Hash multihash.Multihash + Size uint32 + } + tests := []struct { + name string + fields fields + want int + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cid := &CID{ + Multibase: tt.fields.Multibase, + Type: tt.fields.Type, + Hash: tt.fields.Hash, + Size: tt.fields.Size, + } + if got := cid.HashCode(); got != tt.want { + t.Errorf("HashCode() = %v, want %v", got, tt.want) + } + }) + } +} + +/*func TestCID_ToBytes(t *testing.T) { + FromHash(testdata.RawBase58CID) + + println(len(testdata.RawCIDBytes)) + println(utils.DecodeEndian(testdata.RawCIDBytes[35:])) + return + type fields struct { + Type types.CIDType + Hash multihash.Multihash + Size uint32 + } + tests := []struct { + name string + fields fields + want []byte + }{ + { + name: "Bridge CID", + fields: fields{ + Type: types.CIDTypeBridge, + Hash: multihash.New(), // Replace with a valid hash value + }, + want: , // Replace with the expected byte output for Bridge CID + }, + { + name: "Raw CID with Non-Zero Size", + fields: fields{ + Type: types.CIDTypeRaw, + Hash: *multihash.New(testdata.RawCIDBytes[1:34]), + Size: utils.DecodeEndian(testdata.RawCIDBytes[34:]), + }, + want: testdata.RawCIDBytes, + }, + { + name: "Raw CID with Zero Size", + fields: fields{ + Type: types.CIDTypeRaw, + Hash: yourHashValue, // Replace with a valid hash value + Size: 0, // Zero size + }, + want: yourExpectedBytesForRawCIDWithZeroSize, // Replace with the expected byte output + }, + { + name: "Default CID", + fields: fields{ + Type: types.CIDTypeDefault, + Hash: yourHashValue, // Replace with a valid hash value + }, + want: yourExpectedBytesForDefaultCID, // Replace with the expected byte output for Default CID + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cid := &CID{ + Type: tt.fields.Type, + Hash: tt.fields.Hash, + Size: tt.fields.Size, + } + m := multibase.New(cid) + cid.Multibase = m + if got := cid.ToBytes(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToBytes() = %v, want %v", got, tt.want) + } + }) + } +}*/ + +func TestCID_ToRegistryCID(t *testing.T) { + type fields struct { + Multibase multibase.Multibase + Type types.CIDType + Hash multihash.Multihash + Size uint32 + } + tests := []struct { + name string + fields fields + want []byte + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cid := &CID{ + Multibase: tt.fields.Multibase, + Type: tt.fields.Type, + Hash: tt.fields.Hash, + Size: tt.fields.Size, + } + got, err := cid.ToRegistryCID() + if (err != nil) != tt.wantErr { + t.Errorf("ToRegistryCID() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToRegistryCID() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCID_ToRegistryEntry(t *testing.T) { + type fields struct { + Multibase multibase.Multibase + Type types.CIDType + Hash multihash.Multihash + Size uint32 + } + tests := []struct { + name string + fields fields + want []byte + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cid := &CID{ + Multibase: tt.fields.Multibase, + Type: tt.fields.Type, + Hash: tt.fields.Hash, + Size: tt.fields.Size, + } + if got := cid.ToRegistryEntry(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToRegistryEntry() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCID_ToString(t *testing.T) { + type fields struct { + Multibase multibase.Multibase + Type types.CIDType + Hash multihash.Multihash + Size uint32 + } + tests := []struct { + name string + fields fields + want string + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cid := &CID{ + Multibase: tt.fields.Multibase, + Type: tt.fields.Type, + Hash: tt.fields.Hash, + Size: tt.fields.Size, + } + got, err := cid.ToString() + if (err != nil) != tt.wantErr { + t.Errorf("ToString() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ToString() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCID_getPrefixBytes(t *testing.T) { + type fields struct { + Multibase multibase.Multibase + Type types.CIDType + Hash multihash.Multihash + Size uint32 + } + tests := []struct { + name string + fields fields + want []byte + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cid := &CID{ + Multibase: tt.fields.Multibase, + Type: tt.fields.Type, + Hash: tt.fields.Hash, + Size: tt.fields.Size, + } + if got := cid.getPrefixBytes(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getPrefixBytes() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDecode(t *testing.T) { + type args struct { + cid string + } + tests := []struct { + name string + args args + want *CID + wantErr bool + }{ + /* { + name: "Valid Bridge CID", + args:args {cid: ""}, + want: nil, + wantErr: false, + },*/ + + { + name: "Valid Raw Base 58 CID", + args: args{cid: testdata.RawBase58CID}, + want: New(types.CIDTypeRaw, *multihash.New(testdata.RawCIDBytes[1:34]), testdata.RawCIDSize), + wantErr: false, + }, + { + name: "Valid Media 58 CID", + args: args{cid: testdata.MediaBase58CID}, + want: New(types.CIDTypeMetadataMedia, *multihash.New(testdata.MediaCIDBytes[1:34]), testdata.MediaCIDSize), + wantErr: false, + }, + { + name: "Valid Resolver CID", + args: args{cid: testdata.ResolverBase58CID}, + want: New(types.CIDTypeResolver, *multihash.New(testdata.ResolverCIDBytes[1:34]), testdata.ResolverCIDSize), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := Decode(tt.args.cid) + if (err != nil) != tt.wantErr { + t.Errorf("DecodeCID() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("DecodeCID() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFromBytes(t *testing.T) { + type args struct { + bytes []byte + } + tests := []struct { + name string + args args + want *CID + wantErr bool + }{ + { + name: "Valid Raw Base 58 CID", + args: args{bytes: testdata.RawCIDBytes}, + want: New(types.CIDTypeRaw, *multihash.New(testdata.RawCIDBytes[1:34]), testdata.RawCIDSize), + wantErr: false, + }, + { + name: "Valid Media 58 CID", + args: args{bytes: testdata.MediaCIDBytes}, + want: New(types.CIDTypeMetadataMedia, *multihash.New(testdata.MediaCIDBytes[1:34]), testdata.MediaCIDSize), + wantErr: false, + }, + { + name: "Valid Resolver CID", + args: args{bytes: testdata.ResolverCIDBytes}, + want: New(types.CIDTypeResolver, *multihash.New(testdata.ResolverCIDBytes[1:34]), testdata.ResolverCIDSize), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := FromBytes(tt.args.bytes) + if (err != nil) != tt.wantErr { + t.Errorf("FromBytes() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FromBytes() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFromHash(t *testing.T) { + type args struct { + bytes interface{} + size uint32 + cidType types.CIDType + } + tests := []struct { + name string + args args + want *CID + wantErr bool + }{ + { + name: "Valid Raw Base 58 CID", + args: args{bytes: testdata.RawCIDBytes[1:34], size: testdata.RawCIDSize, cidType: types.CIDTypeRaw}, + want: New(types.CIDTypeRaw, *multihash.New(testdata.RawCIDBytes[1:34]), testdata.RawCIDSize), + wantErr: false, + }, + { + name: "Valid Media 58 CID", + args: args{bytes: testdata.MediaCIDBytes[1:34], size: testdata.MediaCIDSize, cidType: types.CIDTypeMetadataMedia}, + want: New(types.CIDTypeMetadataMedia, *multihash.New(testdata.MediaCIDBytes[1:34]), testdata.MediaCIDSize), + wantErr: false, + }, + { + name: "Valid Resolver CID", + args: args{bytes: testdata.ResolverCIDBytes[1:34], size: testdata.ResolverCIDSize, cidType: types.CIDTypeResolver}, + want: New(types.CIDTypeResolver, *multihash.New(testdata.ResolverCIDBytes[1:34]), testdata.ResolverCIDSize), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := FromHash(tt.args.bytes, tt.args.size, tt.args.cidType) + if (err != nil) != tt.wantErr { + t.Errorf("FromHash() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FromHash() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFromRegistry(t *testing.T) { + type args struct { + bytes []byte + } + tests := []struct { + name string + args args + want *CID + wantErr bool + }{ + { + name: "Valid Resolver Data", + args: args{bytes: testdata.ResolverDataBytes}, + want: New(types.CIDTypeMetadataWebapp, *multihash.New(testdata.ResolverDataBytes[2:35]), testdata.ResolverDataSize), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := FromRegistry(tt.args.bytes) + if (err != nil) != tt.wantErr { + t.Errorf("FromRegistry() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FromRegistry() got = %v, want %v", got, tt.want) + } + }) + } +}