- for invocation, reorder the parameters for a more "natural language" mental model - for delegation, make "subject" a required parameter to avoid make powerline by mistake - for delegation, implement powerline
204 lines
5.2 KiB
Go
204 lines
5.2 KiB
Go
package container
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/libp2p/go-libp2p/core/crypto"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/ucan-wg/go-ucan/did"
|
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
|
"github.com/ucan-wg/go-ucan/pkg/policy"
|
|
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
|
"github.com/ucan-wg/go-ucan/token/delegation"
|
|
)
|
|
|
|
func TestContainerRoundTrip(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
writer func(ctn Writer, w io.Writer) error
|
|
reader func(io.Reader) (Reader, error)
|
|
}{
|
|
{"car", Writer.ToCarWriter, FromCarReader},
|
|
{"carBase64", Writer.ToCarBase64Writer, FromCarBase64Reader},
|
|
{"cbor", Writer.ToCborWriter, FromCborReader},
|
|
{"cborBase64", Writer.ToCborBase64Writer, FromCborBase64Reader},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
tokens := make(map[cid.Cid]*delegation.Token)
|
|
var dataSize int
|
|
|
|
writer := NewWriter()
|
|
|
|
for i := 0; i < 10; i++ {
|
|
dlg, c, data := randToken()
|
|
writer.AddSealed(c, data)
|
|
tokens[c] = dlg
|
|
dataSize += len(data)
|
|
}
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
err := tc.writer(writer, buf)
|
|
require.NoError(t, err)
|
|
|
|
t.Logf("data size %d", dataSize)
|
|
t.Logf("container overhead: %d%%, %d bytes", int(float32(buf.Len()-dataSize)/float32(dataSize)*100.0), buf.Len()-dataSize)
|
|
|
|
reader, err := tc.reader(bytes.NewReader(buf.Bytes()))
|
|
require.NoError(t, err)
|
|
|
|
for c, dlg := range tokens {
|
|
tknRead, err := reader.GetToken(c)
|
|
require.NoError(t, err)
|
|
|
|
// require.Equal fails as time.Time holds a wall time that is going to be
|
|
// different, even if it represents the same event.
|
|
// We need to do the following instead.
|
|
|
|
dlgRead := tknRead.(*delegation.Token)
|
|
require.Equal(t, dlg.Issuer(), dlgRead.Issuer())
|
|
require.Equal(t, dlg.Audience(), dlgRead.Audience())
|
|
require.Equal(t, dlg.Subject(), dlgRead.Subject())
|
|
require.Equal(t, dlg.Command(), dlgRead.Command())
|
|
require.Equal(t, dlg.Policy(), dlgRead.Policy())
|
|
require.Equal(t, dlg.Nonce(), dlgRead.Nonce())
|
|
require.True(t, dlg.Meta().Equals(dlgRead.Meta()))
|
|
if dlg.NotBefore() != nil {
|
|
// within 1s as the original value gets truncated to seconds when serialized
|
|
require.WithinDuration(t, *dlg.NotBefore(), *dlgRead.NotBefore(), time.Second)
|
|
}
|
|
if dlg.Expiration() != nil {
|
|
// within 1s as the original value gets truncated to seconds when serialized
|
|
require.WithinDuration(t, *dlg.Expiration(), *dlgRead.Expiration(), time.Second)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkContainerSerialisation(b *testing.B) {
|
|
var duration strings.Builder
|
|
var allocByte strings.Builder
|
|
var allocCount strings.Builder
|
|
|
|
for _, builder := range []strings.Builder{duration, allocByte, allocCount} {
|
|
builder.WriteString("car\tcarBase64\tcarGzip\tcarGzipBase64\tcbor\tcborBase64\tcborGzip\tcborGzipBase64\tcborFlate\tcborFlateBase64\n")
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
writer func(ctn Writer, w io.Writer) error
|
|
reader func(io.Reader) (Reader, error)
|
|
}{
|
|
{"car", Writer.ToCarWriter, FromCarReader},
|
|
{"carBase64", Writer.ToCarBase64Writer, FromCarBase64Reader},
|
|
{"cbor", Writer.ToCborWriter, FromCborReader},
|
|
{"cborBase64", Writer.ToCborBase64Writer, FromCborBase64Reader},
|
|
} {
|
|
writer := NewWriter()
|
|
|
|
for i := 0; i < 10; i++ {
|
|
_, c, data := randToken()
|
|
writer.AddSealed(c, data)
|
|
}
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
_ = tc.writer(writer, buf)
|
|
|
|
b.Run(tc.name+"_write", func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
buf := bytes.NewBuffer(nil)
|
|
_ = tc.writer(writer, buf)
|
|
}
|
|
})
|
|
|
|
b.Run(tc.name+"_read", func(b *testing.B) {
|
|
b.ReportAllocs()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = tc.reader(bytes.NewReader(buf.Bytes()))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func randDID() (crypto.PrivKey, did.DID) {
|
|
privKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
d, err := did.FromPrivKey(privKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return privKey, d
|
|
}
|
|
|
|
func randomString(length int) string {
|
|
b := make([]byte, length/2+1)
|
|
_, _ = rand.Read(b)
|
|
return fmt.Sprintf("%x", b)[0:length]
|
|
}
|
|
|
|
func randToken() (*delegation.Token, cid.Cid, []byte) {
|
|
priv, iss := randDID()
|
|
_, aud := randDID()
|
|
cmd := command.New("foo", "bar")
|
|
pol := policy.MustConstruct(
|
|
policy.All(".[]",
|
|
policy.GreaterThan(".value", literal.Int(2)),
|
|
),
|
|
)
|
|
|
|
opts := []delegation.Option{
|
|
delegation.WithExpiration(time.Now().Add(time.Hour)),
|
|
}
|
|
for i := 0; i < 3; i++ {
|
|
opts = append(opts, delegation.WithMeta(randomString(8), randomString(10)))
|
|
}
|
|
|
|
t, err := delegation.Root(iss, aud, cmd, pol, opts...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
b, c, err := t.ToSealed(priv)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return t, c, b
|
|
}
|
|
|
|
func FuzzContainerRead(f *testing.F) {
|
|
// Generate a corpus
|
|
for tokenCount := 0; tokenCount < 10; tokenCount++ {
|
|
writer := NewWriter()
|
|
for i := 0; i < tokenCount; i++ {
|
|
_, c, data := randToken()
|
|
writer.AddSealed(c, data)
|
|
}
|
|
data, err := writer.ToCbor()
|
|
require.NoError(f, err)
|
|
|
|
f.Add(data)
|
|
}
|
|
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
start := time.Now()
|
|
|
|
// search for panics
|
|
_, _ = FromCbor(data)
|
|
|
|
if time.Since(start) > 100*time.Millisecond {
|
|
panic("too long")
|
|
}
|
|
})
|
|
}
|