2024-10-02 11:56:32 +02:00
|
|
|
package container
|
|
|
|
|
|
|
|
|
|
import (
|
2024-11-21 15:49:29 +01:00
|
|
|
"bytes"
|
2024-10-02 11:56:32 +02:00
|
|
|
"fmt"
|
|
|
|
|
"io"
|
2024-11-05 16:26:53 +01:00
|
|
|
"iter"
|
2024-12-11 16:05:16 +01:00
|
|
|
"strings"
|
2024-10-02 11:56:32 +02:00
|
|
|
|
|
|
|
|
"github.com/ipfs/go-cid"
|
|
|
|
|
"github.com/ipld/go-ipld-prime"
|
2025-01-07 18:16:27 +01:00
|
|
|
"github.com/ipld/go-ipld-prime/codec/cbor"
|
2024-10-02 11:56:32 +02:00
|
|
|
"github.com/ipld/go-ipld-prime/datamodel"
|
|
|
|
|
|
|
|
|
|
"github.com/ucan-wg/go-ucan/token"
|
|
|
|
|
"github.com/ucan-wg/go-ucan/token/delegation"
|
|
|
|
|
"github.com/ucan-wg/go-ucan/token/invocation"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var ErrNotFound = fmt.Errorf("not found")
|
2024-11-27 15:57:13 +01:00
|
|
|
var ErrMultipleInvocations = fmt.Errorf("multiple invocations")
|
2024-10-02 11:56:32 +02:00
|
|
|
|
2024-10-07 18:46:19 +02:00
|
|
|
// Reader is a token container reader. It exposes the tokens conveniently decoded.
|
2025-01-23 17:12:26 +01:00
|
|
|
type Reader map[cid.Cid]bundle
|
|
|
|
|
|
|
|
|
|
type bundle struct {
|
|
|
|
|
sealed []byte
|
|
|
|
|
token token.Token
|
|
|
|
|
}
|
2024-10-02 11:56:32 +02:00
|
|
|
|
2025-01-08 17:31:04 +01:00
|
|
|
// FromBytes decodes a container from a []byte
|
|
|
|
|
func FromBytes(data []byte) (Reader, error) {
|
|
|
|
|
return FromReader(bytes.NewReader(data))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FromString decodes a container from a string
|
|
|
|
|
func FromString(s string) (Reader, error) {
|
|
|
|
|
return FromReader(strings.NewReader(s))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FromReader decodes a container from an io.Reader.
|
|
|
|
|
func FromReader(r io.Reader) (Reader, error) {
|
2025-01-20 12:14:45 +01:00
|
|
|
payload, err := payloadDecoder(r)
|
2025-01-08 17:31:04 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n, err := ipld.DecodeStreaming(payload, cbor.Decode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if n.Kind() != datamodel.Kind_Map {
|
|
|
|
|
return nil, fmt.Errorf("invalid container format: expected map")
|
|
|
|
|
}
|
|
|
|
|
if n.Length() != 1 {
|
|
|
|
|
return nil, fmt.Errorf("invalid container format: expected single version key")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get the first (and only) key-value pair
|
|
|
|
|
it := n.MapIterator()
|
|
|
|
|
key, tokensNode, err := it.Next()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
version, err := key.AsString()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("invalid container format: version must be string")
|
|
|
|
|
}
|
|
|
|
|
if version != containerVersionTag {
|
|
|
|
|
return nil, fmt.Errorf("unsupported container version: %s", version)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tokensNode.Kind() != datamodel.Kind_List {
|
|
|
|
|
return nil, fmt.Errorf("invalid container format: tokens must be a list")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctn := make(Reader, tokensNode.Length())
|
|
|
|
|
it2 := tokensNode.ListIterator()
|
|
|
|
|
for !it2.Done() {
|
|
|
|
|
_, val, err := it2.Next()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
data, err := val.AsBytes()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
err = ctn.addToken(data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ctn, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-07 18:46:19 +02:00
|
|
|
// GetToken returns an arbitrary decoded token, from its CID.
|
|
|
|
|
// If not found, ErrNotFound is returned.
|
2024-10-02 11:56:32 +02:00
|
|
|
func (ctn Reader) GetToken(cid cid.Cid) (token.Token, error) {
|
2025-01-23 17:12:26 +01:00
|
|
|
bndl, ok := ctn[cid]
|
2024-10-02 11:56:32 +02:00
|
|
|
if !ok {
|
|
|
|
|
return nil, ErrNotFound
|
|
|
|
|
}
|
2025-01-23 17:12:26 +01:00
|
|
|
return bndl.token, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetSealed returns an arbitrary sealed token, from its CID.
|
|
|
|
|
// If not found, ErrNotFound is returned.
|
|
|
|
|
func (ctn Reader) GetSealed(cid cid.Cid) ([]byte, error) {
|
|
|
|
|
bndl, ok := ctn[cid]
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, ErrNotFound
|
|
|
|
|
}
|
|
|
|
|
return bndl.sealed, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetAllTokens return all the tokens in the container.
|
2025-01-29 14:28:13 +01:00
|
|
|
func (ctn Reader) GetAllTokens() iter.Seq[token.Bundle] {
|
|
|
|
|
return func(yield func(token.Bundle) bool) {
|
2025-01-23 17:12:26 +01:00
|
|
|
for c, bndl := range ctn {
|
2025-01-29 14:28:13 +01:00
|
|
|
if !yield(token.Bundle{
|
2025-01-23 17:12:26 +01:00
|
|
|
Cid: c,
|
|
|
|
|
Decoded: bndl.token,
|
|
|
|
|
Sealed: bndl.sealed,
|
|
|
|
|
}) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-02 11:56:32 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-07 18:46:19 +02:00
|
|
|
// GetDelegation is the same as GetToken but only return a delegation.Token, with the right type.
|
2025-01-13 13:25:57 +01:00
|
|
|
// If not found, delegation.ErrDelegationNotFound is returned.
|
2024-10-02 11:56:32 +02:00
|
|
|
func (ctn Reader) GetDelegation(cid cid.Cid) (*delegation.Token, error) {
|
|
|
|
|
tkn, err := ctn.GetToken(cid)
|
2025-01-13 13:25:57 +01:00
|
|
|
if err != nil { // only ErrNotFound expected
|
2024-11-20 12:34:24 +01:00
|
|
|
return nil, delegation.ErrDelegationNotFound
|
|
|
|
|
}
|
2024-10-02 11:56:32 +02:00
|
|
|
if tkn, ok := tkn.(*delegation.Token); ok {
|
|
|
|
|
return tkn, nil
|
|
|
|
|
}
|
2024-11-20 12:34:24 +01:00
|
|
|
return nil, delegation.ErrDelegationNotFound
|
2024-10-02 11:56:32 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-05 12:11:20 +02:00
|
|
|
// GetDelegationBundle is the same as GetToken but only return a delegation.Bundle, with the right type.
|
|
|
|
|
// If not found, delegation.ErrDelegationNotFound is returned.
|
|
|
|
|
func (ctn Reader) GetDelegationBundle(cid cid.Cid) (*delegation.Bundle, error) {
|
|
|
|
|
bndl, ok := ctn[cid]
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, delegation.ErrDelegationNotFound
|
|
|
|
|
}
|
|
|
|
|
if tkn, ok := bndl.token.(*delegation.Token); ok {
|
|
|
|
|
return &delegation.Bundle{
|
|
|
|
|
Cid: cid,
|
|
|
|
|
Decoded: tkn,
|
|
|
|
|
Sealed: bndl.sealed,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
return nil, delegation.ErrDelegationNotFound
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-05 16:26:53 +01:00
|
|
|
// GetAllDelegations returns all the delegation.Token in the container.
|
2025-08-05 12:11:20 +02:00
|
|
|
func (ctn Reader) GetAllDelegations() iter.Seq[*delegation.Bundle] {
|
|
|
|
|
return func(yield func(*delegation.Bundle) bool) {
|
2025-01-23 17:12:26 +01:00
|
|
|
for c, bndl := range ctn {
|
|
|
|
|
if t, ok := bndl.token.(*delegation.Token); ok {
|
2025-08-05 12:11:20 +02:00
|
|
|
if !yield(&delegation.Bundle{
|
2025-01-23 17:12:26 +01:00
|
|
|
Cid: c,
|
|
|
|
|
Decoded: t,
|
|
|
|
|
Sealed: bndl.sealed,
|
|
|
|
|
}) {
|
2024-11-05 16:26:53 +01:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-27 15:57:13 +01:00
|
|
|
// GetInvocation returns a single invocation.Token.
|
2024-10-07 18:46:19 +02:00
|
|
|
// If none are found, ErrNotFound is returned.
|
2025-01-08 17:31:04 +01:00
|
|
|
// If more than one invocation exists, ErrMultipleInvocations is returned.
|
2024-10-02 11:56:32 +02:00
|
|
|
func (ctn Reader) GetInvocation() (*invocation.Token, error) {
|
2024-11-27 15:57:13 +01:00
|
|
|
var res *invocation.Token
|
2025-01-23 17:12:26 +01:00
|
|
|
for _, bndl := range ctn {
|
|
|
|
|
if inv, ok := bndl.token.(*invocation.Token); ok {
|
2024-11-27 15:57:13 +01:00
|
|
|
if res != nil {
|
|
|
|
|
return nil, ErrMultipleInvocations
|
|
|
|
|
}
|
|
|
|
|
res = inv
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if res == nil {
|
|
|
|
|
return nil, ErrNotFound
|
|
|
|
|
}
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetAllInvocations returns all the invocation.Token in the container.
|
2025-01-29 14:28:13 +01:00
|
|
|
func (ctn Reader) GetAllInvocations() iter.Seq[invocation.Bundle] {
|
|
|
|
|
return func(yield func(invocation.Bundle) bool) {
|
2025-01-23 17:12:26 +01:00
|
|
|
for c, bndl := range ctn {
|
|
|
|
|
if t, ok := bndl.token.(*invocation.Token); ok {
|
2025-01-29 14:28:13 +01:00
|
|
|
if !yield(invocation.Bundle{
|
2025-01-23 17:12:26 +01:00
|
|
|
Cid: c,
|
|
|
|
|
Decoded: t,
|
|
|
|
|
Sealed: bndl.sealed,
|
|
|
|
|
}) {
|
2024-11-27 15:57:13 +01:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-02 11:56:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ctn Reader) addToken(data []byte) error {
|
|
|
|
|
tkn, c, err := token.FromSealed(data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2025-01-23 17:12:26 +01:00
|
|
|
ctn[c] = bundle{
|
|
|
|
|
sealed: data,
|
|
|
|
|
token: tkn,
|
|
|
|
|
}
|
2024-10-02 11:56:32 +02:00
|
|
|
return nil
|
|
|
|
|
}
|
2025-01-23 17:12:26 +01:00
|
|
|
|
|
|
|
|
// ToWriter convert a container Reader into a Writer.
|
|
|
|
|
// Most likely, you only want to use this in tests for convenience.
|
|
|
|
|
func (ctn Reader) ToWriter() Writer {
|
|
|
|
|
writer := NewWriter()
|
|
|
|
|
for _, bndl := range ctn {
|
|
|
|
|
writer.AddSealed(bndl.sealed)
|
|
|
|
|
}
|
|
|
|
|
return writer
|
|
|
|
|
}
|