feat: initial Multihash
This commit is contained in:
parent
7f502187e6
commit
3d4fdfb9e3
|
@ -0,0 +1,84 @@
|
|||
package multihash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.lumeweb.com/LumeWeb/libs5-go/internal/bases"
|
||||
"git.lumeweb.com/LumeWeb/libs5-go/types"
|
||||
"github.com/multiformats/go-multibase"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var (
|
||||
errorNotBase64Url = errors.New("not a base64url string")
|
||||
)
|
||||
|
||||
type Multihash struct {
|
||||
FullBytes []byte
|
||||
}
|
||||
|
||||
func NewMultihash(fullBytes []byte) *Multihash {
|
||||
return &Multihash{FullBytes: fullBytes}
|
||||
}
|
||||
|
||||
func (m *Multihash) FunctionType() types.HashType {
|
||||
return types.HashType(m.FullBytes[0])
|
||||
}
|
||||
|
||||
func (m *Multihash) HashBytes() []byte {
|
||||
return m.FullBytes[1:]
|
||||
}
|
||||
|
||||
func FromBase64Url(hash string) (*Multihash, error) {
|
||||
encoder, _ := multibase.EncoderByName("base64url")
|
||||
encoding, err := getEncoding(hash)
|
||||
|
||||
if encoding != encoder.Encoding() {
|
||||
return nil, errorNotBase64Url
|
||||
}
|
||||
|
||||
_, ret, err := multibase.Decode(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewMultihash(ret), nil
|
||||
}
|
||||
|
||||
func (m *Multihash) ToBase64Url() (string, error) {
|
||||
return bases.ToBase64Url(m.FullBytes)
|
||||
}
|
||||
|
||||
func (m *Multihash) ToBase32() (string, error) {
|
||||
return bases.ToBase32(m.FullBytes)
|
||||
}
|
||||
|
||||
func (m *Multihash) ToString() (string, error) {
|
||||
if m.FunctionType() == types.HashType(types.CIDTypeBridge) {
|
||||
return string(m.FullBytes), nil // Assumes the bytes are valid UTF-8
|
||||
}
|
||||
return m.ToBase64Url()
|
||||
}
|
||||
|
||||
func (m *Multihash) Equals(other *Multihash) bool {
|
||||
return bytes.Equal(m.FullBytes, other.FullBytes)
|
||||
}
|
||||
|
||||
func (m *Multihash) HashCode() int {
|
||||
return int(m.FullBytes[0]) +
|
||||
int(m.FullBytes[1])<<8 +
|
||||
int(m.FullBytes[2])<<16 +
|
||||
int(m.FullBytes[3])<<24
|
||||
}
|
||||
|
||||
func getEncoding(hash string) (multibase.Encoding, error) {
|
||||
r, _ := utf8.DecodeRuneInString(hash)
|
||||
enc := multibase.Encoding(r)
|
||||
|
||||
_, ok := multibase.EncodingToStr[enc]
|
||||
if !ok {
|
||||
return -1, fmt.Errorf("unsupported multibase encoding: %d", enc)
|
||||
|
||||
}
|
||||
return enc, nil
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
package multihash
|
||||
|
||||
import (
|
||||
"git.lumeweb.com/LumeWeb/libs5-go/internal/testdata"
|
||||
"git.lumeweb.com/LumeWeb/libs5-go/types"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFromBase64Url(t *testing.T) {
|
||||
type args struct {
|
||||
hash string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Multihash
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid Base64 URL Encoded String",
|
||||
args: args{hash: testdata.MediaBase64CID},
|
||||
want: &Multihash{FullBytes: testdata.MediaCIDBytes},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid Base64 URL String",
|
||||
args: args{hash: "@@invalid@@"},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Empty String",
|
||||
args: args{hash: ""},
|
||||
want: nil,
|
||||
wantErr: true, // or false
|
||||
},
|
||||
{
|
||||
name: "Non-URL Base64 Encoded String",
|
||||
args: args{hash: "aGVsbG8gd29ybGQ="},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "String Not Representing a Multihash",
|
||||
args: args{hash: "cGxhaW50ZXh0"},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Long String",
|
||||
args: args{hash: "uYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh"},
|
||||
want: &Multihash{FullBytes: []byte(strings.Repeat("a", 750))},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := FromBase64Url(tt.args.hash)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("FromBase64Url() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("FromBase64Url() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultihash_FunctionType(t *testing.T) {
|
||||
type fields struct {
|
||||
FullBytes []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want types.HashType
|
||||
}{
|
||||
{
|
||||
name: "Is Raw CID",
|
||||
fields: fields{
|
||||
FullBytes: testdata.RawCIDBytes[1:34],
|
||||
},
|
||||
want: types.HashTypeBlake3,
|
||||
}, {
|
||||
name: "Is Resolver CID",
|
||||
fields: fields{
|
||||
FullBytes: testdata.ResolverCIDBytes[1:34],
|
||||
},
|
||||
want: types.HashTypeEd25519,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := &Multihash{
|
||||
FullBytes: tt.fields.FullBytes,
|
||||
}
|
||||
if got := m.FunctionType(); got != tt.want {
|
||||
t.Errorf("FunctionType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultihash_ToBase32(t *testing.T) {
|
||||
type fields struct {
|
||||
FullBytes []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Is Raw CID",
|
||||
fields: fields{
|
||||
FullBytes: testdata.RawCIDBytes,
|
||||
},
|
||||
want: "beyprdl3g2it54ua2ian3a5wybrnva6kr7kzkpv6wbfvgjsb2aepw6yc6t4eq",
|
||||
}, {
|
||||
name: "Is Media CID",
|
||||
fields: fields{
|
||||
FullBytes: testdata.MediaCIDBytes,
|
||||
},
|
||||
want: "byupv6i7z5g6unmx2btc2ihsrrivnox6gqnwfgiwkpuw36d6q4dl35hi",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := &Multihash{
|
||||
FullBytes: tt.fields.FullBytes,
|
||||
}
|
||||
got, err := m.ToBase32()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ToBase32() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ToBase32() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultihash_ToBase64Url(t *testing.T) {
|
||||
type fields struct {
|
||||
FullBytes []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Is Raw CID",
|
||||
fields: fields{
|
||||
FullBytes: testdata.RawCIDBytes,
|
||||
},
|
||||
want: "uJh8Rr2bSJ95QGkAbsHbYDFtQeVH6sqfX1glqZMg6AR9vYF6fCQ",
|
||||
}, {
|
||||
name: "Is Media CID",
|
||||
fields: fields{
|
||||
FullBytes: testdata.MediaCIDBytes,
|
||||
},
|
||||
want: "uxR9fI_npvUay-gzFpB5RiirXX8aDbFMiyn0tvw_Q4Ne-nQ",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := &Multihash{
|
||||
FullBytes: tt.fields.FullBytes,
|
||||
}
|
||||
got, err := m.ToBase64Url()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ToBase64Url() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ToBase64Url() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMultihash(t *testing.T) {
|
||||
type args struct {
|
||||
fullBytes []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *Multihash
|
||||
}{
|
||||
{
|
||||
name: "Valid Base64 URL Encoded String",
|
||||
args: args{fullBytes: testdata.RawCIDBytes},
|
||||
want: &Multihash{FullBytes: testdata.RawCIDBytes},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := NewMultihash(tt.args.fullBytes); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewMultihash() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue