Compare commits
1 Commits
v1.0.1
...
proof-chec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb97653529 |
@@ -57,7 +57,7 @@ Not implemented yet:
|
||||
|
||||
Besides that, `go-ucan` also includes:
|
||||
- a simplified [DID](https://www.w3.org/TR/did-core/) and [did-key](https://w3c-ccg.github.io/did-method-key/) implementation
|
||||
- a [token container](https://github.com/ucan-wg/go-ucan/tree/v1/pkg/container) with CBOR and CAR format, to package and carry tokens together, see [SPEC](pkg/container/SPEC.md)
|
||||
- a [token container](https://github.com/ucan-wg/go-ucan/tree/v1/pkg/container) with CBOR and CAR format, to package and carry tokens together
|
||||
- support for encrypted values in token's metadata
|
||||
|
||||
## Getting Help
|
||||
|
||||
@@ -28,7 +28,7 @@ type Persona int
|
||||
//
|
||||
// [table]: https://en.wikipedia.org/wiki/Alice_and_Bob#Cryptographic_systems
|
||||
const (
|
||||
PersonaAlice Persona = iota + 1
|
||||
PersonaAlice Persona = iota
|
||||
PersonaBob
|
||||
PersonaCarol
|
||||
PersonaDan
|
||||
|
||||
16
go.mod
16
go.mod
@@ -4,17 +4,16 @@ go 1.23
|
||||
|
||||
require (
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
||||
github.com/ipfs/go-cid v0.5.0
|
||||
github.com/ipfs/go-cid v0.4.1
|
||||
github.com/ipld/go-ipld-prime v0.21.0
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.3
|
||||
github.com/libp2p/go-libp2p v0.33.0
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.1
|
||||
github.com/libp2p/go-libp2p v0.36.3
|
||||
github.com/mr-tron/base58 v1.2.0
|
||||
github.com/multiformats/go-multibase v0.2.0
|
||||
github.com/multiformats/go-multicodec v0.9.0
|
||||
github.com/multiformats/go-multihash v0.2.3
|
||||
github.com/multiformats/go-varint v0.0.7
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
gotest.tools/v3 v3.5.1
|
||||
)
|
||||
|
||||
@@ -22,7 +21,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.6 // indirect
|
||||
@@ -35,8 +34,9 @@ require (
|
||||
github.com/polydawn/refmt v0.89.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
google.golang.org/protobuf v1.36.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
|
||||
41
go.sum
41
go.sum
@@ -16,14 +16,14 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
|
||||
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
|
||||
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
|
||||
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
|
||||
github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E=
|
||||
github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -36,14 +36,14 @@ github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCG
|
||||
github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
|
||||
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.3 h1:Ud4lb2QuxRClYAmRleF50KrbKIoM1TddXgBrneT5/Jo=
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.3/go.mod h1:q6uFgbgZfEmQrfJfrCo90QcQOcXFMfbI/fO0NqRtvZo=
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.1 h1:Y2ltVl8J6izLYFs54BVcpXLv5msSW4o8eXwnzZLI32E=
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.1/go.mod h1:4LvZg7oxu6Q5VJwn7Mk/UwooNRnTHUpXBj2C4j3HNx0=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
||||
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
|
||||
github.com/libp2p/go-libp2p v0.33.0 h1:yTPSr8sJRbfeEYXyeN8VPVSlTlFjtMUwGDRniwaf/xQ=
|
||||
github.com/libp2p/go-libp2p v0.33.0/go.mod h1:RIJFRQVUBKy82dnW7J5f1homqqv6NcsDJAl3e7CRGfE=
|
||||
github.com/libp2p/go-libp2p v0.36.3 h1:NHz30+G7D8Y8YmznrVZZla0ofVANrvBl2c+oARfMeDQ=
|
||||
github.com/libp2p/go-libp2p v0.36.3/go.mod h1:4Y5vFyCUiJuluEPmpnKYf6WFx5ViKPUYs/ixe9ANFZ8=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
@@ -52,8 +52,8 @@ github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aG
|
||||
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
|
||||
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
|
||||
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
|
||||
github.com/multiformats/go-multiaddr v0.12.2 h1:9G9sTY/wCYajKa9lyfWPmpZAwe6oV+Wb1zcmMS1HG24=
|
||||
github.com/multiformats/go-multiaddr v0.12.2/go.mod h1:GKyaTYjZRdcUhyOetrxTk9z0cW+jA/YrnqTOvKgi44M=
|
||||
github.com/multiformats/go-multiaddr v0.13.0 h1:BCBzs61E3AGHcYYTv8dqRH43ZfyrqM8RXVPT8t13tLQ=
|
||||
github.com/multiformats/go-multiaddr v0.13.0/go.mod h1:sBXrNzucqkFJhvKOiwwLyqamGa/P5EIXNPLovyhQCII=
|
||||
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
|
||||
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
|
||||
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
|
||||
@@ -81,24 +81,25 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
|
||||
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
|
||||
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
# UCAN container Specification v0.1.0
|
||||
|
||||
## Editors
|
||||
|
||||
* [Michael Muré], [Consensys]
|
||||
|
||||
## Authors
|
||||
|
||||
* [Michael Muré], [Consensys]
|
||||
* [Hugo Dias]
|
||||
|
||||
## Language
|
||||
|
||||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [BCP 14] when, and only when, they appear in all capitals, as shown here.
|
||||
|
||||
# 0 Abstract
|
||||
|
||||
[User-Controlled Authorization Network (UCAN)][UCAN] is a trustless, secure, local-first, user-originated authorization and revocation scheme. This document describes a container format for transmitting one or more UCAN tokens as bytes, regardless of the transport.
|
||||
|
||||
# 1 Introduction
|
||||
|
||||
The UCAN spec itself is transport agnostic. This specification describes how to transfer one or more [UCAN] tokens bundled together, regardless of the transport.
|
||||
|
||||
# 2 Container format
|
||||
|
||||
## 2.1 Inner structure
|
||||
|
||||
UCAN tokens, regardless of their kind ([Delegation], [Invocation], [Revocation], [Promise]) MUST be first signed and serialized into DAG-CBOR bytes according to their respective specification. As the token's CID is not part of the serialized container, any CID returned by this operation is to be ignored.
|
||||
|
||||
All the tokens' bytes MUST be assembled in a [CBOR] array. The ordering of tokens in the array MUST NOT matter. This array SHOULD NOT have duplicate entries.
|
||||
|
||||
That array is then inserted as the value under the `ctn-v1` string key, in a CBOR map. There MUST NOT be other keys.
|
||||
|
||||
For clarity, the CBOR shape is given below:
|
||||
|
||||
```json
|
||||
{
|
||||
"ctn-v1": [
|
||||
<token1 bytes>,
|
||||
<token2 bytes>,
|
||||
<token3 bytes>,
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 2.2 Serialisation
|
||||
|
||||
To serialize the container into bytes, the inner CBOR structure MUST then be serialized into bytes according to the CBOR specification. The resulting bytes MAY be compressed by a supported algorithm, then MAY be encoded with a supported base encoding.
|
||||
|
||||
The following compression algorithms are REQUIRED to be supported:
|
||||
- [GZIP]
|
||||
|
||||
The following base encoding combinations are REQUIRED to be supported:
|
||||
- base64, standard alphabet, padding
|
||||
- base64, URL alphabet, no padding
|
||||
|
||||
The CBOR bytes MUST be prepended by a single byte header to indicate the selection combination of base encoding and compression. This header value MUST be set according to the following table:
|
||||
|
||||
| Header as hex | Header as ASCII | Base encoding | Compression |
|
||||
|---------------|-----------------|-------------------------|----------------|
|
||||
| 0x40 | @ | raw bytes | no compression |
|
||||
| 0x42 | B | base64 std padding | no compression |
|
||||
| 0x43 | C | base64 url (no padding) | no compression |
|
||||
| 0x4D | M | raw bytes | gzip |
|
||||
| 0x4F | O | base64 std padding | gzip |
|
||||
| 0x50 | P | base64 url (no padding) | gzip |
|
||||
|
||||
For clarity, the resulting serialisation is in the form of `<header byte><cbor bytes, optionally compressed, optionally encoded>`.
|
||||
|
||||
# 3 FAQ
|
||||
|
||||
## 3.1 Why not include the UCAN CIDs?
|
||||
|
||||
Several attacks are possible if UCAN tokens aren't validated. If CIDs aren't validated, at least two attacks are possible: [privilege escalation] and [cache poisoning], as UCAN delegation proofs depends on a correct hash-linked structure.
|
||||
|
||||
By not including the CID in the container, the recipient is forced to hash (and thus validate) the CIDs for each token. If presented with a claimed CID paired with the token bytes, implementers could ignore CID validation, breaking a core part of the proof chain security model. Hash functions are very fast on a couple kilobytes of data so the overhead is still very low. It also significantly reduces the size of the container.
|
||||
|
||||
## 3.2 Why compress? Why not always compress?
|
||||
|
||||
Compression is a relatively demanding operation. As such, using it is a tradeoff between size on the wire and CPU/memory usage, both when writing and reading a container. The transport itself can make compression worthwhile or not: for example, HTTP/2 and HTTP/3 headers are already compressed, but HTTP/1 headers are not. This being highly contextual, the choice is left to the final implementer.
|
||||
|
||||
# 4 Implementation recommendations
|
||||
|
||||
## 4.1 Dissociate reader and writer
|
||||
|
||||
While it is tempting to write a single implementation to read and write a container, it is RECOMMENDED to separate the implementation into a reader and a writer. The writer can simply accept arbitrary tokens as bytes, while the reader provides a read-only view with convenient access functions.
|
||||
|
||||
# 5 Acknowledgments
|
||||
|
||||
Many thanks to all the [Fission] team and in particular to [Brooklyn Zelenka] for creating and pushing [UCAN] and other critical pieces like [WNFS], and generally being awesome and supportive people.
|
||||
|
||||
<!-- External Links -->
|
||||
|
||||
[BCP 14]: https://www.rfc-editor.org/info/bcp14
|
||||
[Brooklyn Zelenka]: https://github.com/expede
|
||||
[CBOR]: https://www.rfc-editor.org/rfc/rfc8949.html
|
||||
[Consensys]: https://consensys.io/
|
||||
[Delegation]: https://github.com/ucan-wg/delegation/tree/v1_ipld
|
||||
[Fission]: https://fission.codes
|
||||
[GZIP]: https://datatracker.ietf.org/doc/html/rfc1952
|
||||
[Hugo Dias]: https://github.com/hugomrdias
|
||||
[Invocation]: https://github.com/ucan-wg/invocation
|
||||
[Michael Muré]: https://github.com/MichaelMure/
|
||||
[Promise]: https://github.com/ucan-wg/promise/tree/v1-rc1
|
||||
[Revocation]: https://github.com/ucan-wg/revocation/tree/first-draft
|
||||
[UCAN]: https://github.com/ucan-wg/spec
|
||||
[WNFS]: https://github.com/wnfs-wg
|
||||
[cache poisoning]: https://en.wikipedia.org/wiki/Cache_poisoning
|
||||
[privilede escalation]: https://en.wikipedia.org/wiki/Privilege_escalation
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
OH4sIAAAAAAAA/5zX+ZOUxR3HcY7FwhKToFJGFAU1ghzL8/RztpxzrzsHszuzcy1Ru5/unmd6dubZnXsGE9EtFVnFSCiNEFfFiCSKqIVI8ECBKGiJiMSTFdCYeGDK6GpMWcSUtUOq8ts++Qfev7zq+6nu+5lRys+riKuTYwdvSCw9NrDty6c/Wf668n73nFsqQ988uh1Z8rTNu9X6hkO33S/vG1i92+//ontN6/BubU54fntiUnXqrPNfo9p5bnTWd3PP3IhMt/z52L5i2UD5+aQnvVRsFVqFeQWjVdxsoDKp6SRDrsjS+hUNNZjNtFdJsa1CzUwCxSK8imjO7curtFHwBqy8US3hlBT08XQlkWeGkSPmfGZZ8zEqGLTWOzX9yyemGZli8X+b1XAxESdxALIVX6E3xkiY1coeswLDMOp1BVRgOrGnqxEtukoJo9fqub7fQD09Rmv3z/vRYtZaQT1lOs4olvH/nyU5WkIPmEAQkAYg5lhDqigxjeimJoqEMgC4JipMViURiCYTBQHLBHGZCkyRNcgYzVt5gwauDa67+uiirx+KuQZnjgB9/2H3noUzrj79rQdv27HpuSufnOrfvu3Myf98rfv5Tfds2OX9de9w92Mfm/m9s2eOe/etzPnd/1jdeqI8vvuVbYOb8Q2Ltky0CZRDlAfcJKqHGSklXclQxR+mPKbgcM5n5awkRYmwM2SltSQqjRYo3RFD/lDOnW/TYrxd7ayVQaQme10dVl3RPCardrQHkFJUvTUT2QCyk20CaQpUFF1CnFJJpDqVoGYSDKhuMJVDlaoqpIIMTIZVhcoMcCYAnSgC1GkT6BF9x2d9X19nTBw/+cgI0KO+/W+sk4cmDz7/7AM7D7s2+BdIFw0vGLv9yzm7Vv3h1QN/WZHwxy9Qj0evv2ftkX891BLy7V2cWjY+sGV9f+zYHh4eYxOoXE8E3BFPNB+q0Kw7XexLpfXeXiPrd5WC3N0Wj7iBFfeQMqi06aMFKtcMf9wRrbN4EKOa7KxYfRkQ0/3ecJ/LV/dnkl2OgEPSvIViNmQDyE62CSRDgWEBilwECpWwADVsKkQnhgQZ13QZMVWVITGRCLCmI8ANCWuiSiSqNIF+ExuqnnSds6b7zv4XRoDWzN08c6H1GByE86wXj+561L/q9pc+G1jy4/ya71uPb3kXLJmwe/jlHFu3+NhFK2/8Sce4sZtiLQezq/uVBx6OXnv3n0+zC+T1F5MuENTKFW8u0ld3GcVQNl0gZodSiCk01UkMV9SVauRNXR0tEOtkic5YKtXlDgKt4VFKuhWoGiku417g6wuUimZ7DbTJDAGv2waQnWwTCIka0XVJ4JCKVJGQLGqmIamaYaiQKwpVIFQMgZgUCQLSicqxTKCuyxLATaD4JZf/4mhvbf19y15uGwFyrodHo9vOP7kzObhklvuTfQ/j3IQLn5nz6dKNn0xo7H/nkQs+HT70VvLwmWzvm3c/Ofn7g3f91jvkb7/5vpWHHl8EX3/B7sSxiq9R5bkIsAJKJurhPj9z1GgDh3gy18ViyOP2sa5oUso4C9aoJ84fq/fKKm3LtRe8gWTe568V8z7sMCU1FnA7gJnOhXq9Lk+XaHTamTgb2SaQQIFEEaJcgLIqQEEQgaloGsKIMs5ESRYFQVYlU2UEEVnEXCUSA0zTBLUJJM/nb351ecu/h6dLlREg67y3b3be96Ky/p7L7jzeO9s5Y9Ka5fNxZHc8cVC6+K83iXPfO/GjL5ybrbZrWq4QPu64rfu7adMP7v/uyt/f0r7yJjBpkk2gvgQPOTusdDGqhOuKNxROw0ba29lFUjCbqNV1MSDWZZxgViRkjBbI8jUa1aKl0Y42qz3sTVt11dQSeo5WqsGKD1NY0sN6QS8aUhXaALKTPQWEFU0nKuMi0aGCDayoJjIwZjrGHMoEAUI1QTINxUAShCo3DCwTWdOZ0QRaAhdf1yIf7unm5atGgObsH8rs5FO2wH1r/zb84c8WrNLL179/za+G51WvkvaKXjguP3XmXWetHTix59zd32Te9h2ftGTbhU/dv3PvjcvAlBnjzrAJVOjMUKhKznS4jZR10ZWMVz1JZ5zWHbmuSqnglzpSMqPVuiPgkkcL1Butd1DQVQmGgByp0S5Xo5SLKXlfvD1QDolQZ2oxXIwFk42AUrEBZCfbBBJlSZFFReIIQZlqhgR1U0MSlilROBAMTACBAjYZMyjUich1w1B+2Dd4auIOnLPCY3wFNzxIc+tGgKqX5an27Qtn37C2d+ykS7XPnz30TSi9onDd2eeMK7N4/6x78XLQMnnlm6/mFlbIeRee9qWybfCjBceSi6YVdg2s7ZxgE6ga7sp29QGTJ+LOWAooJcsXJ9loQgvFmZOXOgNt7nox1jBRyAVGC8SNbK0zCyKmrDsDRrrhShLqqGQ1DKJyJVMvknY/jNV52hQbThtAdrJNIIgVSdBljUuCwCSgMxGbBhENJBLIBWYgAzCsAZNAwdAFXeZMVVX4wywKTaBZTxz9E9y6b0JPgu8YAZo9A258I6l75KfJM2Omb+T3zjDPTW393SUXnHjswB3DY05a7wQ7ux9fXjtjx4QxS6fPfmns3598sWf9lkvB83sOXrxytt1XXK5IHCHk1Tx6sc0fTPdlCjTBYmWHHNH8XR2s3khVjKqeFD085R71BeXrdVytWKqSqCaDMiehTm/Q4jCVrUcdrBKTOfR4vEGto01N2bkgG9lTF6RBwFQicAqJALGiaropCwggCWMuAyJThAxETUIA0CVV57qkUEVlOtSbQGMGJuaOTFzyx/FHjj81AvTqwIExd3vB9FuXrdHmrp7106G33/vgo9tP3zjF8t5ZOLz52q2bjt1x6+yTH6zQbmGpiasOJ5Xw+oU7n3v8YMuH77Vs/bbF7kcVmjBmuhuhVMyVUNWiSXg9XMs2yvUkpCnma8BKxlK0eqXT8o0WqBgpmI5YKuLvyLiUeqTsp42S6qiasUCjlFG9Xk1pr3ujpN0BLWoDyE62CSRpMtBFTeaipKqYyZJKTEwURGWkcVHAWDcY0TSTYipjjFUu6YL6w2P8v0AnP45M2du/fWgHeOWD/wQAAP//IuQ6Y1MQAAA=
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
PH4sIAAAAAAAA_5zWa5cUxR3HcVcRiejmuCII3siKwgnubnd1dVW1eIS5rzM9w8zs3HZETFd19_T07PTcZ2cH1KMgoB41YgIo3nM0G1GCRhCRiILIEaIhIEo0Bi9c9ewaFfEazYMd8iCPdvIGvk8-p_71e1RnZaujyt_e2_LQLYk5Fbb1QefFR9-_5LzWO59-9PfWoWV_nN_f177z06eH31j13Je8-Wpk35bTccKjtV_22MRfnz9DufuSg8Ntn02KzZtw83sLxj-mGE441FIoVZhidal9qTl8J9fJdRRZJz_IlIpaI2pavTKjDVxZR_5MQXa4aFIFgr-gMCrrOTWUqWNajNZERzin0GLRz7IxsTcfAgnGsqrRpedyXVQpMq2Wn5K68dmLWLpU-p9msRbQ-usuHEsGorZK0BbwxWzxvCdrhDK2vL23UMCRTM0RlStVxPK5vpsXMaWvj3Vee90i5Wq9s6r0VbRTWalC__-smtXKyu8MERNOwJSZkCACeAFgzkC6xGkYAhOLqkJEiBXJkJAuCUBUTMIA4KEgAEWzchbT5DXHfxwc7LiqNnPmRwdHgBa8NO6jDQ9uvW6wa7DHYetfP3DxO4s7JhybLa1YcerUV-Yae7aDww_P2_zjvH9euWqOOW3yjBWtO2__bKzLvfnR_enEpmaBMtFuy4qEsgrlqRMpPm86mql6apjI4V63V_bFgl5WT-USpQhDowVK2amiFmM8tEUsiRJcCeowHM9AvVQreLGE83LMkcBBI99dAE0ANZNtAGFewJjwkilBJPG8TphoKEQhmihKpsgBXuIVhXEGRRQpRGAmhzkRqIpOaQNo4afuoTWTj60cfjw5fQQoxy98ZPGmF3YsWLJo8voJ587Y9-X38geyLBq_nHXZIy9fPun5m1pebqVzv7J-NuaM08XEzYl3v3kmtfuCr2Yc-VAa3vtxS7NAnpKSKsBEzVaMphWdFB0kgNSEgHpwiPe7a0qaxYtVG_SVq_7RAhkey-asYmfUZ1XKMbGSr-Vo1UZ60rZyNV6Uy8l8MY_iES1ic0abAGom2wCCEqcLVFRNpEKBaghAaCBJUYgKOVNVGMQCUyXRQBqFTJWwKQkIIoZFQW8AvZV64bRDez_6flYbbmu8oDa_euu2yJB9w1vX3uO-YrZvobj2yK07V7-1YebjrRcgbsvS4fiyJ9_gpx4KvT5n_A7PTz-cqF_asuuaoTFnn2N-bbU2CaSHi0bJlrEi3kyh6NFrgqOu59VKOJ2t9Xj4sllT4wXqJdFQfzI2aiBvIGSG_Zls2IsDSdKblRV3QlQz2X7Z2evudVBk16QIM1yiCZsBaiLbAOIEnROQppu8AjgKiKqIhgQVwDQCTCKJkqCIBIqGpoicCilvcoxDWJewcPLE3TD0fHzb0y_BrtRd2gjQN3fd--36ju_CYzuV8adcsOeA95UvVv-09N1t05_bN7DllouWTN-18r4nTm-9Y8uZ2L02fj1XLizHlzyfrZ-x65Pt503b0uyJs0oDhpWuSh4vwFpJjlcDciKRCbtQTzmvxkWrKIWCUQm51FomO1ogKyMLyXSPx0xHxKrixwFT7a9XgzUUTuedXgDi8XKmgGJmJOwMNQHUTLYBhERGkE6YCTXEYYAYxxuE5xGnCbopAkWjAtCgbhCMKAUQmQqkvA4wJaQBpNyhPfDyK9Ijs-yOv40ArTv_hoMtSbB12NjtzD1Ef35ow47k_D3frn41f-2LX6YmdTxzzba3t180_7XFV5m-fz304tj2Td-t7Nx_oblszv7ctI2Hmz1xhayl1myOkhEOxUt2S_S4shFvMI7dIbseyPa4AlalJ8q6g2KhGBktUJGVfQEaSsXtsK7EnXnC6zIP84ZHrYUyGR9vcxXj9lwuaIvG7E0ANZM9-QcBHkNCNFMFEKhYFTRiSBoUOV0RTE3DKmWMCJKhijzgeJWakgoIwyLUuAZQSlq-Y177mBOSHt49AjRF-PBAe9d1H3ffcvyZczrHLAhP9X3jC5y797YFa-mNxYl_evDqo4fu_8NZh3d88un1R1f_5cfBf8_76p3tv_psyooll_k-nj2mWaC6b8BCmVrCytYjIFZKF1wytmUFKdijVuoCLIdrBV8dk3AxbIwWqJRAkpyRMnkY9ReLcipX8PR3u6RcrheykFINGCCc7-lJ25LMpjUB1Ez25IljDHFYpCYUiUYR4yVkUMKYIEiSiTDBSFcRgAajPAOU6ibjmShBToOwAXRf-6V31zceuXHdjHV9I0D7Jn7wgXzr4TVX_-PYw_mpLatO69_qW7R1Q-m8o9OcsGP2rM_f3lM_3n0_-aL18zWHV-eHzkik9r4_LriMt16a98Tm889sdiTASDqRwFLW3evj6_mU6i96cqInGEY4Ea0F1brlsPUXJSlCrcpogfRCzGll1YKRsqXrWEuVajq21wK5buKv5FxOV7wQy4Wz5Wwg3NRIaCbbABKopoi6BkymSSrTRCpSQ8UqEEQBmkTTBQp0VcSGBgAvYaiZCgQMUiz89wXdVtm7cNtjqvLmb5ZtHAHq2vfX9Uem2q-ZXTzw3oub0ruemj52f29hwuWeZ2_6_qylidcO5d9p3310-_wptTV3Dv1WXZzbuLsNDU2-dzDy7Sf84MxxTQKZAxWi5wO0Agci0WTOnStHklUpbPaHSi7JcsV9UbvH6_IHzHTVNeqRwLtlj6rV_T3ZehxGex2KLA3EXX4XhNmaGZdj-VjCRgy37HA4mhkJTWQbQBImhOkcbyIeQA1oiOcNBVBEdBWYlOc0IgBAREPnBYoABSavUAI0RhFuAM22T_nzs8NTTyz94YFVI0Bze_9-4utz3dvfW5B7fXL7uJWvr-1rO-uG5evusY_jlk-ynvzheOfE5IFIt4ieunAfO3bK-CWuX2w-0HbQP_fDi-8Y2NPsitNYLCfr2YLGEhrwg7LNoL3ZWKLSAzwDxNvNV_OA1L0oFVdToz5xuURswBupyoo7Ggua8Vg8kIjV5TLvcyCUDJsV7PXKuglTehn5mwBqJnvyBSEd6qpKTV5HCEOeU4mBCJSwpImmIkBd4BRGoUEUHUlMQ6amQVVVJB2CBpBwxRsH-9-cvnYnOfvd_wQAAP__hMJ751MQAAA
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,21 +0,0 @@
|
||||
package containertest
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed Base64StdPadding
|
||||
var Base64StdPadding string
|
||||
|
||||
//go:embed Base64StdPaddingGzipped
|
||||
var Base64StdPaddingGzipped string
|
||||
|
||||
//go:embed Base64URL
|
||||
var Base64URL string
|
||||
|
||||
//go:embed Base64URLGzipped
|
||||
var Base64URLGzipped string
|
||||
|
||||
//go:embed Bytes
|
||||
var Bytes []byte
|
||||
|
||||
//go:embed BytesGzipped
|
||||
var BytesGzipped []byte
|
||||
@@ -1,118 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const containerVersionTag = "ctn-v1"
|
||||
|
||||
type header byte
|
||||
|
||||
const (
|
||||
headerRawBytes = header(0x40)
|
||||
headerBase64StdPadding = header(0x42)
|
||||
headerBase64URL = header(0x43)
|
||||
headerRawBytesGzip = header(0x4D)
|
||||
headerBase64StdPaddingGzip = header(0x4F)
|
||||
headerBase64URLGzip = header(0x50)
|
||||
)
|
||||
|
||||
func (h header) encoder(w io.Writer) *payloadWriter {
|
||||
res := &payloadWriter{rawWriter: w, writer: w, header: h}
|
||||
|
||||
switch h {
|
||||
case headerBase64StdPadding, headerBase64StdPaddingGzip:
|
||||
b64Writer := base64.NewEncoder(base64.StdEncoding, res.writer)
|
||||
res.writer = b64Writer
|
||||
res.closers = append([]io.Closer{b64Writer}, res.closers...)
|
||||
case headerBase64URL, headerBase64URLGzip:
|
||||
b64Writer := base64.NewEncoder(base64.RawURLEncoding, res.writer)
|
||||
res.writer = b64Writer
|
||||
res.closers = append([]io.Closer{b64Writer}, res.closers...)
|
||||
}
|
||||
|
||||
switch h {
|
||||
case headerRawBytesGzip, headerBase64StdPaddingGzip, headerBase64URLGzip:
|
||||
gzipWriter := gzip.NewWriter(res.writer)
|
||||
res.writer = gzipWriter
|
||||
res.closers = append([]io.Closer{gzipWriter}, res.closers...)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func payloadDecoder(r io.Reader) (io.Reader, error) {
|
||||
headerBuf := make([]byte, 1)
|
||||
_, err := r.Read(headerBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := header(headerBuf[0])
|
||||
|
||||
switch h {
|
||||
case headerRawBytes,
|
||||
headerBase64StdPadding,
|
||||
headerBase64URL,
|
||||
headerRawBytesGzip,
|
||||
headerBase64StdPaddingGzip,
|
||||
headerBase64URLGzip:
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown container header")
|
||||
}
|
||||
|
||||
switch h {
|
||||
case headerBase64StdPadding, headerBase64StdPaddingGzip:
|
||||
r = base64.NewDecoder(base64.StdEncoding, r)
|
||||
case headerBase64URL, headerBase64URLGzip:
|
||||
r = base64.NewDecoder(base64.RawURLEncoding, r)
|
||||
}
|
||||
|
||||
switch h {
|
||||
case headerRawBytesGzip, headerBase64StdPaddingGzip, headerBase64URLGzip:
|
||||
gzipReader, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = gzipReader
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
var _ io.WriteCloser = &payloadWriter{}
|
||||
|
||||
// payloadWriter is tasked with two things:
|
||||
// - prepend the header byte
|
||||
// - call Close() on all the underlying io.Writer
|
||||
type payloadWriter struct {
|
||||
rawWriter io.Writer
|
||||
writer io.Writer
|
||||
header header
|
||||
headerWrote bool
|
||||
closers []io.Closer
|
||||
}
|
||||
|
||||
func (w *payloadWriter) Write(p []byte) (n int, err error) {
|
||||
if !w.headerWrote {
|
||||
_, err := w.rawWriter.Write([]byte{byte(w.header)})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
w.headerWrote = true
|
||||
}
|
||||
return w.writer.Write(p)
|
||||
}
|
||||
|
||||
func (w *payloadWriter) Close() error {
|
||||
var errs error
|
||||
for _, closer := range w.closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"iter"
|
||||
@@ -9,7 +11,7 @@ import (
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/cbor"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/token"
|
||||
@@ -21,31 +23,86 @@ var ErrNotFound = fmt.Errorf("not found")
|
||||
var ErrMultipleInvocations = fmt.Errorf("multiple invocations")
|
||||
|
||||
// Reader is a token container reader. It exposes the tokens conveniently decoded.
|
||||
type Reader map[cid.Cid]bundle
|
||||
type Reader map[cid.Cid]token.Token
|
||||
|
||||
type bundle struct {
|
||||
sealed []byte
|
||||
token token.Token
|
||||
// GetToken returns an arbitrary decoded token, from its CID.
|
||||
// If not found, ErrNotFound is returned.
|
||||
func (ctn Reader) GetToken(cid cid.Cid) (token.Token, error) {
|
||||
tkn, ok := ctn[cid]
|
||||
if !ok {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return tkn, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
payload, err := payloadDecoder(r)
|
||||
// GetDelegation is the same as GetToken but only return a delegation.Token, with the right type.
|
||||
func (ctn Reader) GetDelegation(cid cid.Cid) (*delegation.Token, error) {
|
||||
tkn, err := ctn.GetToken(cid)
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return nil, delegation.ErrDelegationNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tkn, ok := tkn.(*delegation.Token); ok {
|
||||
return tkn, nil
|
||||
}
|
||||
return nil, delegation.ErrDelegationNotFound
|
||||
}
|
||||
|
||||
n, err := ipld.DecodeStreaming(payload, cbor.Decode)
|
||||
// GetAllDelegations returns all the delegation.Token in the container.
|
||||
func (ctn Reader) GetAllDelegations() iter.Seq2[cid.Cid, *delegation.Token] {
|
||||
return func(yield func(cid.Cid, *delegation.Token) bool) {
|
||||
for c, t := range ctn {
|
||||
if t, ok := t.(*delegation.Token); ok {
|
||||
if !yield(c, t) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetInvocation returns a single invocation.Token.
|
||||
// If none are found, ErrNotFound is returned.
|
||||
// If more than one invocation exist, ErrMultipleInvocations is returned.
|
||||
func (ctn Reader) GetInvocation() (*invocation.Token, error) {
|
||||
var res *invocation.Token
|
||||
for _, t := range ctn {
|
||||
if inv, ok := t.(*invocation.Token); ok {
|
||||
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.
|
||||
func (ctn Reader) GetAllInvocations() iter.Seq2[cid.Cid, *invocation.Token] {
|
||||
return func(yield func(cid.Cid, *invocation.Token) bool) {
|
||||
for c, t := range ctn {
|
||||
if t, ok := t.(*invocation.Token); ok {
|
||||
if !yield(c, t) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FromCbor decodes a DAG-CBOR encoded container.
|
||||
func FromCbor(data []byte) (Reader, error) {
|
||||
return FromCborReader(bytes.NewReader(data))
|
||||
}
|
||||
|
||||
// FromCborReader is the same as FromCbor, but with an io.Reader.
|
||||
func FromCborReader(r io.Reader) (Reader, error) {
|
||||
n, err := ipld.DecodeStreaming(r, dagcbor.Decode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -67,7 +124,7 @@ func FromReader(r io.Reader) (Reader, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid container format: version must be string")
|
||||
}
|
||||
if version != containerVersionTag {
|
||||
if version != currentContainerVersion {
|
||||
return nil, fmt.Errorf("unsupported container version: %s", version)
|
||||
}
|
||||
|
||||
@@ -94,105 +151,52 @@ func FromReader(r io.Reader) (Reader, error) {
|
||||
return ctn, nil
|
||||
}
|
||||
|
||||
// GetToken returns an arbitrary decoded token, from its CID.
|
||||
// If not found, ErrNotFound is returned.
|
||||
func (ctn Reader) GetToken(cid cid.Cid) (token.Token, error) {
|
||||
bndl, ok := ctn[cid]
|
||||
if !ok {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return bndl.token, nil
|
||||
// FromCborBase64 decodes a base64 DAG-CBOR encoded container.
|
||||
func FromCborBase64(data string) (Reader, error) {
|
||||
return FromCborBase64Reader(strings.NewReader(data))
|
||||
}
|
||||
|
||||
// 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
|
||||
// FromCborBase64Reader is the same as FromCborBase64, but with an io.Reader.
|
||||
func FromCborBase64Reader(r io.Reader) (Reader, error) {
|
||||
return FromCborReader(base64.NewDecoder(base64.StdEncoding, r))
|
||||
}
|
||||
|
||||
// GetAllTokens return all the tokens in the container.
|
||||
func (ctn Reader) GetAllTokens() iter.Seq[token.Bundle] {
|
||||
return func(yield func(token.Bundle) bool) {
|
||||
for c, bndl := range ctn {
|
||||
if !yield(token.Bundle{
|
||||
Cid: c,
|
||||
Decoded: bndl.token,
|
||||
Sealed: bndl.sealed,
|
||||
}) {
|
||||
return
|
||||
}
|
||||
// FromCar decodes a CAR file encoded container.
|
||||
func FromCar(data []byte) (Reader, error) {
|
||||
return FromCarReader(bytes.NewReader(data))
|
||||
}
|
||||
|
||||
// FromCarReader is the same as FromCar, but with an io.Reader.
|
||||
func FromCarReader(r io.Reader) (Reader, error) {
|
||||
_, it, err := readCar(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctn := make(Reader)
|
||||
|
||||
for block, err := range it {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ctn.addToken(block.data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ctn, nil
|
||||
}
|
||||
|
||||
// GetDelegation is the same as GetToken but only return a delegation.Token, with the right type.
|
||||
// If not found, delegation.ErrDelegationNotFound is returned.
|
||||
func (ctn Reader) GetDelegation(cid cid.Cid) (*delegation.Token, error) {
|
||||
tkn, err := ctn.GetToken(cid)
|
||||
if err != nil { // only ErrNotFound expected
|
||||
return nil, delegation.ErrDelegationNotFound
|
||||
}
|
||||
if tkn, ok := tkn.(*delegation.Token); ok {
|
||||
return tkn, nil
|
||||
}
|
||||
return nil, delegation.ErrDelegationNotFound
|
||||
// FromCarBase64 decodes a base64 CAR file encoded container.
|
||||
func FromCarBase64(data string) (Reader, error) {
|
||||
return FromCarReader(strings.NewReader(data))
|
||||
}
|
||||
|
||||
// GetAllDelegations returns all the delegation.Token in the container.
|
||||
func (ctn Reader) GetAllDelegations() iter.Seq[delegation.Bundle] {
|
||||
return func(yield func(delegation.Bundle) bool) {
|
||||
for c, bndl := range ctn {
|
||||
if t, ok := bndl.token.(*delegation.Token); ok {
|
||||
if !yield(delegation.Bundle{
|
||||
Cid: c,
|
||||
Decoded: t,
|
||||
Sealed: bndl.sealed,
|
||||
}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetInvocation returns a single invocation.Token.
|
||||
// If none are found, ErrNotFound is returned.
|
||||
// If more than one invocation exists, ErrMultipleInvocations is returned.
|
||||
func (ctn Reader) GetInvocation() (*invocation.Token, error) {
|
||||
var res *invocation.Token
|
||||
for _, bndl := range ctn {
|
||||
if inv, ok := bndl.token.(*invocation.Token); ok {
|
||||
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.
|
||||
func (ctn Reader) GetAllInvocations() iter.Seq[invocation.Bundle] {
|
||||
return func(yield func(invocation.Bundle) bool) {
|
||||
for c, bndl := range ctn {
|
||||
if t, ok := bndl.token.(*invocation.Token); ok {
|
||||
if !yield(invocation.Bundle{
|
||||
Cid: c,
|
||||
Decoded: t,
|
||||
Sealed: bndl.sealed,
|
||||
}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// FromCarBase64Reader is the same as FromCarBase64, but with an io.Reader.
|
||||
func FromCarBase64Reader(r io.Reader) (Reader, error) {
|
||||
return FromCarReader(base64.NewDecoder(base64.StdEncoding, r))
|
||||
}
|
||||
|
||||
func (ctn Reader) addToken(data []byte) error {
|
||||
@@ -200,19 +204,6 @@ func (ctn Reader) addToken(data []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctn[c] = bundle{
|
||||
sealed: data,
|
||||
token: tkn,
|
||||
}
|
||||
ctn[c] = tkn
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -22,22 +22,14 @@ import (
|
||||
|
||||
func TestContainerRoundTrip(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
expectedHeader header
|
||||
writer any
|
||||
name string
|
||||
writer func(ctn Writer, w io.Writer) error
|
||||
reader func(io.Reader) (Reader, error)
|
||||
}{
|
||||
{"Bytes", headerRawBytes, Writer.ToBytes},
|
||||
{"BytesWriter", headerRawBytes, Writer.ToBytesWriter},
|
||||
{"BytesGzipped", headerRawBytesGzip, Writer.ToBytesGzipped},
|
||||
{"BytesGzippedWriter", headerRawBytesGzip, Writer.ToBytesGzippedWriter},
|
||||
{"Base64StdPadding", headerBase64StdPadding, Writer.ToBase64StdPadding},
|
||||
{"Base64StdPaddingWriter", headerBase64StdPadding, Writer.ToBase64StdPaddingWriter},
|
||||
{"Base64StdPaddingGzipped", headerBase64StdPaddingGzip, Writer.ToBase64StdPaddingGzipped},
|
||||
{"Base64StdPaddingGzippedWriter", headerBase64StdPaddingGzip, Writer.ToBase64StdPaddingGzippedWriter},
|
||||
{"Base64URL", headerBase64URL, Writer.ToBase64URL},
|
||||
{"Base64URLWriter", headerBase64URL, Writer.ToBase64URLWriter},
|
||||
{"Base64URLGzipped", headerBase64URLGzip, Writer.ToBase64URLGzipped},
|
||||
{"Base64URLGzipWriter", headerBase64URLGzip, Writer.ToBase64URLGzipWriter},
|
||||
{"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)
|
||||
@@ -47,53 +39,21 @@ func TestContainerRoundTrip(t *testing.T) {
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
dlg, c, data := randToken()
|
||||
writer.AddSealed(data)
|
||||
writer.AddSealed(c, data)
|
||||
tokens[c] = dlg
|
||||
dataSize += len(data)
|
||||
}
|
||||
|
||||
var reader Reader
|
||||
var serialLen int
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
switch fn := tc.writer.(type) {
|
||||
case func(ctn Writer, w io.Writer) error:
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := fn(writer, buf)
|
||||
require.NoError(t, err)
|
||||
serialLen = buf.Len()
|
||||
err := tc.writer(writer, buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
h, err := buf.ReadByte()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, byte(tc.expectedHeader), h)
|
||||
err = buf.UnreadByte()
|
||||
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 = FromReader(bytes.NewReader(buf.Bytes()))
|
||||
require.NoError(t, err)
|
||||
|
||||
case func(ctn Writer) ([]byte, error):
|
||||
b, err := fn(writer)
|
||||
require.NoError(t, err)
|
||||
serialLen = len(b)
|
||||
|
||||
require.Equal(t, byte(tc.expectedHeader), b[0])
|
||||
|
||||
reader, err = FromBytes(b)
|
||||
require.NoError(t, err)
|
||||
|
||||
case func(ctn Writer) (string, error):
|
||||
s, err := fn(writer)
|
||||
require.NoError(t, err)
|
||||
serialLen = len(s)
|
||||
|
||||
require.Equal(t, byte(tc.expectedHeader), s[0])
|
||||
|
||||
reader, err = FromString(s)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
t.Logf("data size %d, container size %d, overhead: %d%%, %d bytes",
|
||||
dataSize, serialLen, int(float32(serialLen-dataSize)/float32(dataSize)*100.0), serialLen-dataSize)
|
||||
reader, err := tc.reader(bytes.NewReader(buf.Bytes()))
|
||||
require.NoError(t, err)
|
||||
|
||||
for c, dlg := range tokens {
|
||||
tknRead, err := reader.GetToken(c)
|
||||
@@ -138,18 +98,16 @@ func BenchmarkContainerSerialisation(b *testing.B) {
|
||||
writer func(ctn Writer, w io.Writer) error
|
||||
reader func(io.Reader) (Reader, error)
|
||||
}{
|
||||
{"Bytes", Writer.ToBytesWriter, FromReader},
|
||||
{"BytesGzipped", Writer.ToBytesGzippedWriter, FromReader},
|
||||
{"Base64StdPadding", Writer.ToBase64StdPaddingWriter, FromReader},
|
||||
{"Base64StdPaddingGzipped", Writer.ToBase64StdPaddingGzippedWriter, FromReader},
|
||||
{"Base64URL", Writer.ToBase64URLWriter, FromReader},
|
||||
{"Base64URLGzip", Writer.ToBase64URLGzipWriter, FromReader},
|
||||
{"car", Writer.ToCarWriter, FromCarReader},
|
||||
{"carBase64", Writer.ToCarBase64Writer, FromCarBase64Reader},
|
||||
{"cbor", Writer.ToCborWriter, FromCborReader},
|
||||
{"cborBase64", Writer.ToCborBase64Writer, FromCborBase64Reader},
|
||||
} {
|
||||
writer := NewWriter()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, _, data := randToken()
|
||||
writer.AddSealed(data)
|
||||
_, c, data := randToken()
|
||||
writer.AddSealed(c, data)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
@@ -223,10 +181,10 @@ func FuzzContainerRead(f *testing.F) {
|
||||
for tokenCount := 0; tokenCount < 10; tokenCount++ {
|
||||
writer := NewWriter()
|
||||
for i := 0; i < tokenCount; i++ {
|
||||
_, _, data := randToken()
|
||||
writer.AddSealed(data)
|
||||
_, c, data := randToken()
|
||||
writer.AddSealed(c, data)
|
||||
}
|
||||
data, err := writer.ToBytes()
|
||||
data, err := writer.ToCbor()
|
||||
require.NoError(f, err)
|
||||
|
||||
f.Add(data)
|
||||
@@ -236,7 +194,7 @@ func FuzzContainerRead(f *testing.F) {
|
||||
start := time.Now()
|
||||
|
||||
// search for panics
|
||||
_, _ = FromBytes(data)
|
||||
_, _ = FromCbor(data)
|
||||
|
||||
if time.Since(start) > 100*time.Millisecond {
|
||||
panic("too long")
|
||||
|
||||
@@ -2,138 +2,107 @@ package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/cbor"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
)
|
||||
|
||||
// Writer is a token container writer. It provides a convenient way to aggregate and serialize tokens together.
|
||||
type Writer map[string]struct{}
|
||||
type Writer map[cid.Cid][]byte
|
||||
|
||||
func NewWriter() Writer {
|
||||
return make(Writer)
|
||||
}
|
||||
|
||||
// AddSealed includes a "sealed" token (serialized with a ToSealed* function) in the container.
|
||||
func (ctn Writer) AddSealed(data []byte) {
|
||||
ctn[string(data)] = struct{}{}
|
||||
func (ctn Writer) AddSealed(cid cid.Cid, data []byte) {
|
||||
ctn[cid] = data
|
||||
}
|
||||
|
||||
// ToBytes encode the container into raw bytes.
|
||||
func (ctn Writer) ToBytes() ([]byte, error) {
|
||||
return ctn.toBytes(headerRawBytes)
|
||||
}
|
||||
const currentContainerVersion = "ctn-v1"
|
||||
|
||||
// ToBytesWriter is the same as ToBytes, but with an io.Writer.
|
||||
func (ctn Writer) ToBytesWriter(w io.Writer) error {
|
||||
return ctn.toWriter(headerRawBytes, w)
|
||||
}
|
||||
|
||||
// ToBytesGzipped encode the container into gzipped bytes.
|
||||
func (ctn Writer) ToBytesGzipped() ([]byte, error) {
|
||||
return ctn.toBytes(headerRawBytesGzip)
|
||||
}
|
||||
|
||||
// ToBytesGzippedWriter is the same as ToBytesGzipped, but with an io.Writer.
|
||||
func (ctn Writer) ToBytesGzippedWriter(w io.Writer) error {
|
||||
return ctn.toWriter(headerRawBytesGzip, w)
|
||||
}
|
||||
|
||||
// ToBase64StdPadding encode the container into a base64 string, with standard encoding and padding.
|
||||
func (ctn Writer) ToBase64StdPadding() (string, error) {
|
||||
return ctn.toString(headerBase64StdPadding)
|
||||
}
|
||||
|
||||
// ToBase64StdPaddingWriter is the same as ToBase64StdPadding, but with an io.Writer.
|
||||
func (ctn Writer) ToBase64StdPaddingWriter(w io.Writer) error {
|
||||
return ctn.toWriter(headerBase64StdPadding, w)
|
||||
}
|
||||
|
||||
// ToBase64StdPaddingGzipped encode the container into a pre-gzipped base64 string, with standard encoding and padding.
|
||||
func (ctn Writer) ToBase64StdPaddingGzipped() (string, error) {
|
||||
return ctn.toString(headerBase64StdPaddingGzip)
|
||||
}
|
||||
|
||||
// ToBase64StdPaddingGzippedWriter is the same as ToBase64StdPaddingGzipped, but with an io.Writer.
|
||||
func (ctn Writer) ToBase64StdPaddingGzippedWriter(w io.Writer) error {
|
||||
return ctn.toWriter(headerBase64StdPaddingGzip, w)
|
||||
}
|
||||
|
||||
// ToBase64URL encode the container into base64 string, with URL-safe encoding and no padding.
|
||||
func (ctn Writer) ToBase64URL() (string, error) {
|
||||
return ctn.toString(headerBase64URL)
|
||||
}
|
||||
|
||||
// ToBase64URLWriter is the same as ToBase64URL, but with an io.Writer.
|
||||
func (ctn Writer) ToBase64URLWriter(w io.Writer) error {
|
||||
return ctn.toWriter(headerBase64URL, w)
|
||||
}
|
||||
|
||||
// ToBase64URL encode the container into pre-gzipped base64 string, with URL-safe encoding and no padding.
|
||||
func (ctn Writer) ToBase64URLGzipped() (string, error) {
|
||||
return ctn.toString(headerBase64URLGzip)
|
||||
}
|
||||
|
||||
// ToBase64URLWriter is the same as ToBase64URL, but with an io.Writer.
|
||||
func (ctn Writer) ToBase64URLGzipWriter(w io.Writer) error {
|
||||
return ctn.toWriter(headerBase64URLGzip, w)
|
||||
}
|
||||
|
||||
func (ctn Writer) toBytes(header header) ([]byte, error) {
|
||||
// ToCbor encode the container into a DAG-CBOR binary format.
|
||||
func (ctn Writer) ToCbor() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := ctn.toWriter(header, &buf)
|
||||
err := ctn.ToCborWriter(&buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (ctn Writer) toString(header header) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
err := ctn.toWriter(header, &buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (ctn Writer) toWriter(header header, w io.Writer) (err error) {
|
||||
encoder := header.encoder(w)
|
||||
|
||||
defer func() {
|
||||
err = encoder.Close()
|
||||
}()
|
||||
// ToCborWriter is the same as ToCbor, but with an io.Writer.
|
||||
func (ctn Writer) ToCborWriter(w io.Writer) error {
|
||||
node, err := qp.BuildMap(basicnode.Prototype.Any, 1, func(ma datamodel.MapAssembler) {
|
||||
qp.MapEntry(ma, containerVersionTag, qp.List(int64(len(ctn)), func(la datamodel.ListAssembler) {
|
||||
for data, _ := range ctn {
|
||||
qp.ListEntry(la, qp.Bytes([]byte(data)))
|
||||
qp.MapEntry(ma, currentContainerVersion, qp.List(int64(len(ctn)), func(la datamodel.ListAssembler) {
|
||||
for _, data := range ctn {
|
||||
qp.ListEntry(la, qp.Bytes(data))
|
||||
}
|
||||
}))
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ipld.EncodeStreaming(encoder, node, cbor.Encode)
|
||||
return ipld.EncodeStreaming(w, node, dagcbor.Encode)
|
||||
}
|
||||
|
||||
// ToReader convert a container Writer into a Reader.
|
||||
// Most likely, you only want to use this in tests for convenience.
|
||||
// This is not optimized and can panic.
|
||||
func (ctn Writer) ToReader() Reader {
|
||||
data, err := ctn.ToBytes()
|
||||
// ToCborBase64 encode the container into a base64 encoded DAG-CBOR binary format.
|
||||
func (ctn Writer) ToCborBase64() (string, error) {
|
||||
var buf bytes.Buffer
|
||||
err := ctn.ToCborBase64Writer(&buf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
reader, err := FromBytes(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return reader
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// ToCborBase64Writer is the same as ToCborBase64, but with an io.Writer.
|
||||
func (ctn Writer) ToCborBase64Writer(w io.Writer) error {
|
||||
w2 := base64.NewEncoder(base64.StdEncoding, w)
|
||||
defer w2.Close()
|
||||
return ctn.ToCborWriter(w2)
|
||||
}
|
||||
|
||||
// ToCar encode the container into a CAR file.
|
||||
func (ctn Writer) ToCar() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := ctn.ToCarWriter(&buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// ToCarWriter is the same as ToCar, but with an io.Writer.
|
||||
func (ctn Writer) ToCarWriter(w io.Writer) error {
|
||||
return writeCar(w, nil, func(yield func(carBlock, error) bool) {
|
||||
for c, data := range ctn {
|
||||
if !yield(carBlock{c: c, data: data}, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ToCarBase64 encode the container into a base64 encoded CAR file.
|
||||
func (ctn Writer) ToCarBase64() (string, error) {
|
||||
var buf bytes.Buffer
|
||||
err := ctn.ToCarBase64Writer(&buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// ToCarBase64Writer is the same as ToCarBase64, but with an io.Writer.
|
||||
func (ctn Writer) ToCarBase64Writer(w io.Writer) error {
|
||||
w2 := base64.NewEncoder(base64.StdEncoding, w)
|
||||
defer w2.Close()
|
||||
return ctn.ToCarWriter(w2)
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWriterDedup(t *testing.T) {
|
||||
ctn := NewWriter()
|
||||
|
||||
_, _, sealed := randToken()
|
||||
ctn.AddSealed(sealed)
|
||||
require.Len(t, ctn, 1)
|
||||
|
||||
ctn.AddSealed(sealed)
|
||||
require.Len(t, ctn, 1)
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func statementFromIPLD(path string, node datamodel.Node) (Statement, error) {
|
||||
}
|
||||
case 3:
|
||||
switch op {
|
||||
case KindEqual, KindNotEqual, KindLessThan, KindLessThanOrEqual, KindGreaterThan, KindGreaterThanOrEqual:
|
||||
case KindEqual, KindLessThan, KindLessThanOrEqual, KindGreaterThan, KindGreaterThanOrEqual:
|
||||
sel, err := arg2AsSelector(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -21,31 +21,10 @@ func TestIpldRoundTrip(t *testing.T) {
|
||||
]
|
||||
]`
|
||||
|
||||
// must contain all the operators
|
||||
const allOps = `
|
||||
[
|
||||
["and", [
|
||||
["==", ".foo1", ".bar1"],
|
||||
["!=", ".foo2", ".bar2"]
|
||||
]],
|
||||
["or", [
|
||||
[">", ".foo5", 5.2],
|
||||
[">=", ".foo6", 6.2]
|
||||
]],
|
||||
["not", ["like", ".foo7", "*@example.com"]],
|
||||
["all", ".foo8",
|
||||
["<", ".foo3", 3]
|
||||
],
|
||||
["any", ".foo9",
|
||||
["<=", ".foo4", 4]
|
||||
]
|
||||
]`
|
||||
|
||||
for _, tc := range []struct {
|
||||
name, dagJsonStr string
|
||||
}{
|
||||
{"illustrativeExample", illustrativeExample},
|
||||
{"allOps", allOps},
|
||||
} {
|
||||
nodes, err := ipld.Decode([]byte(tc.dagJsonStr), dagjson.Decode)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
|
||||
const (
|
||||
// MaxInt53 represents the maximum safe integer in JavaScript (2^53 - 1)
|
||||
MaxInt53 int64 = 9007199254740991
|
||||
MaxInt53 = 9007199254740991
|
||||
// MinInt53 represents the minimum safe integer in JavaScript (-2^53 + 1)
|
||||
MinInt53 int64 = -9007199254740991
|
||||
MinInt53 = -9007199254740991
|
||||
)
|
||||
|
||||
func ValidateIntegerBoundsIPLD(node ipld.Node) error {
|
||||
|
||||
@@ -185,7 +185,7 @@ func anyAssemble(val any) qp.Assemble {
|
||||
return qp.Int(i)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
u := rv.Uint()
|
||||
if u > uint64(limits.MaxInt53) {
|
||||
if u > limits.MaxInt53 {
|
||||
panic(fmt.Sprintf("unsigned integer %d exceeds safe bounds", u))
|
||||
}
|
||||
return qp.Int(int64(u))
|
||||
|
||||
@@ -82,17 +82,6 @@ func matchStatement(cur Statement, node ipld.Node) (_ matchResult, leafMost Stat
|
||||
}
|
||||
return boolToRes(datamodel.DeepEqual(s.value, res))
|
||||
}
|
||||
case KindNotEqual:
|
||||
if s, ok := cur.(equality); ok {
|
||||
res, err := s.selector.Select(node)
|
||||
if err != nil {
|
||||
return matchResultNoData, cur
|
||||
}
|
||||
if res == nil { // optional selector didn't match
|
||||
return matchResultOptionalNoData, nil
|
||||
}
|
||||
return boolToRes(!datamodel.DeepEqual(s.value, res))
|
||||
}
|
||||
case KindGreaterThan:
|
||||
if s, ok := cur.(equality); ok {
|
||||
res, err := s.selector.Select(node)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
t.Run("equality", func(t *testing.T) {
|
||||
t.Run("eq string", func(t *testing.T) {
|
||||
t.Run("string", func(t *testing.T) {
|
||||
nd := literal.String("test")
|
||||
|
||||
pol := MustConstruct(Equal(".", literal.String("test")))
|
||||
@@ -35,7 +35,7 @@ func TestMatch(t *testing.T) {
|
||||
require.Equal(t, pol[0], leaf)
|
||||
})
|
||||
|
||||
t.Run("eq int", func(t *testing.T) {
|
||||
t.Run("int", func(t *testing.T) {
|
||||
nd := literal.Int(138)
|
||||
|
||||
pol := MustConstruct(Equal(".", literal.Int(138)))
|
||||
@@ -54,7 +54,7 @@ func TestMatch(t *testing.T) {
|
||||
require.Equal(t, pol[0], leaf)
|
||||
})
|
||||
|
||||
t.Run("eq float", func(t *testing.T) {
|
||||
t.Run("float", func(t *testing.T) {
|
||||
nd := literal.Float(1.138)
|
||||
|
||||
pol := MustConstruct(Equal(".", literal.Float(1.138)))
|
||||
@@ -73,7 +73,7 @@ func TestMatch(t *testing.T) {
|
||||
require.Equal(t, pol[0], leaf)
|
||||
})
|
||||
|
||||
t.Run("eq IPLD Link", func(t *testing.T) {
|
||||
t.Run("IPLD Link", func(t *testing.T) {
|
||||
l0 := cidlink.Link{Cid: cid.MustParse("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq")}
|
||||
l1 := cidlink.Link{Cid: cid.MustParse("bafkreifau35r7vi37tvbvfy3hdwvgb4tlflqf7zcdzeujqcjk3rsphiwte")}
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestMatch(t *testing.T) {
|
||||
require.Equal(t, pol[0], leaf)
|
||||
})
|
||||
|
||||
t.Run("eq string in map", func(t *testing.T) {
|
||||
t.Run("string in map", func(t *testing.T) {
|
||||
nd, _ := literal.Map(map[string]any{
|
||||
"foo": "bar",
|
||||
})
|
||||
@@ -121,7 +121,7 @@ func TestMatch(t *testing.T) {
|
||||
require.Equal(t, pol[0], leaf)
|
||||
})
|
||||
|
||||
t.Run("eq string in list", func(t *testing.T) {
|
||||
t.Run("string in list", func(t *testing.T) {
|
||||
nd, _ := literal.List([]any{"foo"})
|
||||
|
||||
pol := MustConstruct(Equal(".[0]", literal.String("foo")))
|
||||
@@ -134,132 +134,6 @@ func TestMatch(t *testing.T) {
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
})
|
||||
|
||||
t.Run("neq string", func(t *testing.T) {
|
||||
nd := literal.String("test")
|
||||
|
||||
pol := MustConstruct(NotEqual(".", literal.String("test")))
|
||||
ok, leaf := pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".", literal.String("test2")))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".", literal.Int(138)))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
})
|
||||
|
||||
t.Run("neq int", func(t *testing.T) {
|
||||
nd := literal.Int(138)
|
||||
|
||||
pol := MustConstruct(NotEqual(".", literal.Int(138)))
|
||||
ok, leaf := pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".", literal.Int(1138)))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".", literal.String("138")))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
})
|
||||
|
||||
t.Run("neq float", func(t *testing.T) {
|
||||
nd := literal.Float(1.138)
|
||||
|
||||
pol := MustConstruct(NotEqual(".", literal.Float(1.138)))
|
||||
ok, leaf := pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".", literal.Float(11.38)))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".", literal.String("138")))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
})
|
||||
|
||||
t.Run("neq IPLD Link", func(t *testing.T) {
|
||||
l0 := cidlink.Link{Cid: cid.MustParse("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq")}
|
||||
l1 := cidlink.Link{Cid: cid.MustParse("bafkreifau35r7vi37tvbvfy3hdwvgb4tlflqf7zcdzeujqcjk3rsphiwte")}
|
||||
|
||||
nd := literal.Link(l0)
|
||||
|
||||
pol := MustConstruct(NotEqual(".", literal.Link(l0)))
|
||||
ok, leaf := pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".", literal.Link(l1)))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".", literal.String("bafybeif4owy5gno5lwnixqm52rwqfodklf76hsetxdhffuxnplvijskzqq")))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
})
|
||||
|
||||
t.Run("neq string in map", func(t *testing.T) {
|
||||
nd, _ := literal.Map(map[string]any{
|
||||
"foo": "bar",
|
||||
})
|
||||
|
||||
pol := MustConstruct(NotEqual(".foo", literal.String("bar")))
|
||||
ok, leaf := pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".[\"foo\"]", literal.String("bar")))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".foo", literal.String("baz")))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
|
||||
// missing data will fail, as not optional
|
||||
pol = MustConstruct(NotEqual(".foobar", literal.String("bar")))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
})
|
||||
|
||||
t.Run("neq string in list", func(t *testing.T) {
|
||||
nd, _ := literal.List([]any{"foo"})
|
||||
|
||||
pol := MustConstruct(NotEqual(".[0]", literal.String("foo")))
|
||||
ok, leaf := pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
|
||||
pol = MustConstruct(NotEqual(".[0]", literal.String("bar")))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
|
||||
// missing data will fail, as not optional
|
||||
pol = MustConstruct(NotEqual(".[1]", literal.String("foo")))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("inequality", func(t *testing.T) {
|
||||
@@ -371,61 +245,20 @@ func TestMatch(t *testing.T) {
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
})
|
||||
|
||||
t.Run("lt float", func(t *testing.T) {
|
||||
nd := literal.Float(1.38)
|
||||
|
||||
pol := MustConstruct(LessThan(".", literal.Float(1)))
|
||||
ok, leaf := pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
|
||||
pol = MustConstruct(LessThan(".", literal.Float(2)))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
})
|
||||
|
||||
t.Run("lte float", func(t *testing.T) {
|
||||
nd := literal.Float(1.38)
|
||||
|
||||
pol := MustConstruct(LessThanOrEqual(".", literal.Float(1)))
|
||||
ok, leaf := pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
|
||||
pol = MustConstruct(GreaterThanOrEqual(".", literal.Float(1.38)))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
|
||||
pol = MustConstruct(LessThanOrEqual(".", literal.Float(2)))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("negation", func(t *testing.T) {
|
||||
nd, _ := literal.Map(map[string]any{
|
||||
"foo": false,
|
||||
})
|
||||
nd := literal.Bool(false)
|
||||
|
||||
pol := MustConstruct(Not(Equal(".foo", literal.Bool(true))))
|
||||
pol := MustConstruct(Not(Equal(".", literal.Bool(true))))
|
||||
ok, leaf := pol.Match(nd)
|
||||
require.True(t, ok)
|
||||
require.Nil(t, leaf)
|
||||
|
||||
pol = MustConstruct(Not(Equal(".foo", literal.Bool(false))))
|
||||
pol = MustConstruct(Not(Equal(".", literal.Bool(false))))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, pol[0], leaf)
|
||||
|
||||
// missing data will fail, as not optional
|
||||
pol = MustConstruct(Not(Equal(".foobar", literal.Bool(true))))
|
||||
ok, leaf = pol.Match(nd)
|
||||
require.False(t, ok)
|
||||
require.Equal(t, MustConstruct(Equal(".foobar", literal.Bool(true)))[0], leaf)
|
||||
})
|
||||
|
||||
t.Run("conjunction", func(t *testing.T) {
|
||||
@@ -649,7 +482,6 @@ func FuzzMatch(f *testing.F) {
|
||||
f.Add([]byte(`[["all", ".reviewer", ["like", ".email", "*@example.com"]]]`), []byte(`{"reviewer": [{"email": "alice@example.com"}, {"email": "bob@example.com"}]}`))
|
||||
f.Add([]byte(`[["any", ".tags", ["or", [["==", ".", "news"], ["==", ".", "press"]]]]]`), []byte(`{"tags": ["news", "press"]}`))
|
||||
f.Add([]byte(`[["==", ".name", "Alice"]]`), []byte(`{"name": "Alice"}`))
|
||||
f.Add([]byte(`[["!=", ".name", "Alice"]]`), []byte(`{"name": "Alice"}`))
|
||||
f.Add([]byte(`[[">", ".age", 30]]`), []byte(`{"age": 31}`))
|
||||
f.Add([]byte(`[["<=", ".height", 180]]`), []byte(`{"height": 170}`))
|
||||
f.Add([]byte(`[["not", ["==", ".status", "inactive"]]]`), []byte(`{"status": "active"}`))
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
|
||||
const (
|
||||
KindEqual = "==" // implemented by equality
|
||||
KindNotEqual = "!=" // implemented by equality
|
||||
KindGreaterThan = ">" // implemented by equality
|
||||
KindGreaterThanOrEqual = ">=" // implemented by equality
|
||||
KindLessThan = "<" // implemented by equality
|
||||
@@ -88,13 +87,6 @@ func Equal(selector string, value ipld.Node) Constructor {
|
||||
}
|
||||
}
|
||||
|
||||
func NotEqual(selector string, value ipld.Node) Constructor {
|
||||
return func() (Statement, error) {
|
||||
sel, err := selpkg.Parse(selector)
|
||||
return equality{kind: KindNotEqual, selector: sel, value: value}, err
|
||||
}
|
||||
}
|
||||
|
||||
func GreaterThan(selector string, value ipld.Node) Constructor {
|
||||
return func() (Statement, error) {
|
||||
sel, err := selpkg.Parse(selector)
|
||||
@@ -133,7 +125,7 @@ func (n negation) Kind() string {
|
||||
|
||||
func (n negation) String() string {
|
||||
child := n.statement.String()
|
||||
return fmt.Sprintf(`["%s", %s]`, n.Kind(), strings.ReplaceAll(child, "\n", "\n "))
|
||||
return fmt.Sprintf(`["%s", "%s"]`, n.Kind(), strings.ReplaceAll(child, "\n", "\n "))
|
||||
}
|
||||
|
||||
func Not(cstor Constructor) Constructor {
|
||||
@@ -157,7 +149,7 @@ func (c connective) String() string {
|
||||
for i, statement := range c.statements {
|
||||
childs[i] = strings.ReplaceAll(statement.String(), "\n", "\n ")
|
||||
}
|
||||
return fmt.Sprintf("[\"%s\", [\n %s\n]]", c.kind, strings.Join(childs, ",\n "))
|
||||
return fmt.Sprintf("[\"%s\", [\n %s]]\n", c.kind, strings.Join(childs, ",\n "))
|
||||
}
|
||||
|
||||
func And(cstors ...Constructor) Constructor {
|
||||
@@ -216,7 +208,7 @@ func (n quantifier) Kind() string {
|
||||
|
||||
func (n quantifier) String() string {
|
||||
child := n.statement.String()
|
||||
return fmt.Sprintf("[\"%s\", \"%s\",\n %s\n]", n.Kind(), n.selector, strings.ReplaceAll(child, "\n", "\n "))
|
||||
return fmt.Sprintf("[\"%s\", \"%s\",\n %s]", n.Kind(), n.selector, strings.ReplaceAll(child, "\n", "\n "))
|
||||
}
|
||||
|
||||
func All(selector string, cstor Constructor) Constructor {
|
||||
|
||||
@@ -28,14 +28,12 @@ func ExamplePolicy() {
|
||||
// [
|
||||
// ["==", ".status", "draft"],
|
||||
// ["all", ".reviewer",
|
||||
// ["like", ".email", "*@example.com"]
|
||||
// ],
|
||||
// ["like", ".email", "*@example.com"]],
|
||||
// ["any", ".tags",
|
||||
// ["or", [
|
||||
// ["==", ".", "news"],
|
||||
// ["==", ".", "press"]
|
||||
// ]]
|
||||
// ]
|
||||
// ["==", ".", "press"]]]
|
||||
// ]
|
||||
// ]
|
||||
}
|
||||
|
||||
@@ -61,14 +59,12 @@ func ExamplePolicy_accumulate() {
|
||||
// [
|
||||
// ["==", ".status", "draft"],
|
||||
// ["all", ".reviewer",
|
||||
// ["like", ".email", "*@example.com"]
|
||||
// ],
|
||||
// ["like", ".email", "*@example.com"]],
|
||||
// ["any", ".tags",
|
||||
// ["or", [
|
||||
// ["==", ".", "news"],
|
||||
// ["==", ".", "press"]
|
||||
// ]]
|
||||
// ]
|
||||
// ["==", ".", "press"]]]
|
||||
// ]
|
||||
// ]
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package policytest
|
||||
|
||||
import (
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
|
||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
@@ -11,8 +10,9 @@ import (
|
||||
// EmptyPolicy provides a Policy with no statements.
|
||||
var EmptyPolicy = policy.Policy{}
|
||||
|
||||
// SpecPolicy provides a valid Policy containing the statements that are included
|
||||
// in the second code block of the [Validation] section of the delegation specification.
|
||||
// ExampleValidationPolicy provides a instantiated SpecPolicy containing the
|
||||
// statements that are included in the second code block of the [Validation]
|
||||
// section of the delegation specification.
|
||||
//
|
||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||
var SpecPolicy = policy.MustConstruct(
|
||||
@@ -24,8 +24,8 @@ var SpecPolicy = policy.MustConstruct(
|
||||
// specification has been finished/merged.
|
||||
|
||||
// SpecValidArguments provides valid, instantiated Arguments containing
|
||||
// the key/value pairs that are included in portion of the second code block
|
||||
// of the [Validation] section of the delegation specification.
|
||||
// the key/value pairs that are included in portion of the the second code
|
||||
// block of the [Validation] section of the delegation specification.
|
||||
//
|
||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||
var SpecValidArguments = args.NewBuilder().
|
||||
@@ -41,8 +41,8 @@ var SpecValidArguments = args.NewBuilder().
|
||||
var specValidArgumentsIPLD = mustIPLD(SpecValidArguments)
|
||||
|
||||
// SpecInvalidArguments provides invalid, instantiated Arguments containing
|
||||
// the key/value pairs that are included in portion of the second code block
|
||||
// of the [Validation] section of the delegation specification.
|
||||
// the key/value pairs that are included in portion of the the second code
|
||||
// block of the [Validation] section of the delegation specification.
|
||||
//
|
||||
// [Validation]: https://github.com/ucan-wg/delegation/tree/v1_ipld#validation
|
||||
var SpecInvalidArguments = args.NewBuilder().
|
||||
|
||||
@@ -68,7 +68,7 @@ func Parse(str string) (Selector, error) {
|
||||
if err != nil {
|
||||
return nil, newParseError("invalid index", str, col, tok)
|
||||
}
|
||||
if int64(idx) > limits.MaxInt53 || int64(idx) < limits.MinInt53 {
|
||||
if idx > limits.MaxInt53 || idx < limits.MinInt53 {
|
||||
return nil, newParseError(fmt.Sprintf("index %d exceeds safe integer bounds", idx), str, col, tok)
|
||||
}
|
||||
sel = append(sel, segment{str: tok, optional: opt, index: idx})
|
||||
@@ -173,37 +173,37 @@ func tokenize(str string) []string {
|
||||
return toks
|
||||
}
|
||||
|
||||
type parseErr struct {
|
||||
type parseerr struct {
|
||||
msg string
|
||||
src string
|
||||
col int
|
||||
tok string
|
||||
}
|
||||
|
||||
func (p parseErr) Name() string {
|
||||
func (p parseerr) Name() string {
|
||||
return "ParseError"
|
||||
}
|
||||
|
||||
func (p parseErr) Message() string {
|
||||
func (p parseerr) Message() string {
|
||||
return p.msg
|
||||
}
|
||||
|
||||
func (p parseErr) Column() int {
|
||||
func (p parseerr) Column() int {
|
||||
return p.col
|
||||
}
|
||||
|
||||
func (p parseErr) Error() string {
|
||||
func (p parseerr) Error() string {
|
||||
return p.msg
|
||||
}
|
||||
|
||||
func (p parseErr) Source() string {
|
||||
func (p parseerr) Source() string {
|
||||
return p.src
|
||||
}
|
||||
|
||||
func (p parseErr) Token() string {
|
||||
func (p parseerr) Token() string {
|
||||
return p.tok
|
||||
}
|
||||
|
||||
func newParseError(message string, source string, column int, token string) error {
|
||||
return parseErr{message, source, column, token}
|
||||
return parseerr{message, source, column, token}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ type Selector []segment
|
||||
// Select perform the selection described by the selector on the input IPLD DAG.
|
||||
// Select can return:
|
||||
// - exactly one matched IPLD node
|
||||
// - a resolutionErr error if not being able to resolve to a node
|
||||
// - a resolutionerr error if not being able to resolve to a node
|
||||
// - nil and no errors, if the selector couldn't match on an optional segment (with ?).
|
||||
func (s Selector) Select(subject ipld.Node) (ipld.Node, error) {
|
||||
return resolve(s, subject, nil)
|
||||
@@ -316,27 +316,27 @@ func kindString(n datamodel.Node) string {
|
||||
return n.Kind().String()
|
||||
}
|
||||
|
||||
type resolutionErr struct {
|
||||
type resolutionerr struct {
|
||||
msg string
|
||||
at []string
|
||||
}
|
||||
|
||||
func (r resolutionErr) Name() string {
|
||||
func (r resolutionerr) Name() string {
|
||||
return "ResolutionError"
|
||||
}
|
||||
|
||||
func (r resolutionErr) Message() string {
|
||||
func (r resolutionerr) Message() string {
|
||||
return fmt.Sprintf("can not resolve path: .%s", strings.Join(r.at, "."))
|
||||
}
|
||||
|
||||
func (r resolutionErr) At() []string {
|
||||
func (r resolutionerr) At() []string {
|
||||
return r.at
|
||||
}
|
||||
|
||||
func (r resolutionErr) Error() string {
|
||||
func (r resolutionerr) Error() string {
|
||||
return r.Message()
|
||||
}
|
||||
|
||||
func newResolutionError(message string, at []string) error {
|
||||
return resolutionErr{message, at}
|
||||
return resolutionerr{message, at}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ func TestSelect(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
require.Empty(t, res)
|
||||
|
||||
require.ErrorAs(t, err, &resolutionErr{}, "error should be a resolution error")
|
||||
require.ErrorAs(t, err, &resolutionerr{}, "error should be a resolution error")
|
||||
})
|
||||
|
||||
t.Run("optional not exists", func(t *testing.T) {
|
||||
@@ -351,7 +351,7 @@ func FuzzParseAndSelect(f *testing.F) {
|
||||
|
||||
// look for panic()
|
||||
_, err = sel.Select(node)
|
||||
if err != nil && !errors.As(err, &resolutionErr{}) {
|
||||
if err != nil && !errors.As(err, &resolutionerr{}) {
|
||||
// not normal, we should only have resolution errors
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func New(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, sub d
|
||||
}
|
||||
|
||||
// Root creates a validated UCAN delegation Token from the provided parameters and options.
|
||||
// This is typically used to create and give power to an agent.
|
||||
// This is typically used to create and give a power to an agent.
|
||||
//
|
||||
// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on itself".
|
||||
func Root(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
|
||||
@@ -154,16 +154,6 @@ func (t *Token) Expiration() *time.Time {
|
||||
return t.expiration
|
||||
}
|
||||
|
||||
// IsRoot tells if the token is a root delegation.
|
||||
func (t *Token) IsRoot() bool {
|
||||
return t.issuer == t.subject
|
||||
}
|
||||
|
||||
// IsPowerline tells if the token is a powerline delegation.
|
||||
func (t *Token) IsPowerline() bool {
|
||||
return t.subject == did.Undef
|
||||
}
|
||||
|
||||
// IsValidNow verifies that the token can be used at the current time, based on expiration or "not before" fields.
|
||||
// This does NOT do any other kind of verifications.
|
||||
func (t *Token) IsValidNow() bool {
|
||||
@@ -182,6 +172,25 @@ func (t *Token) IsValidAt(ti time.Time) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Covers indicate if this token has the power to allow the given sub-delegation.
|
||||
// This function only verifies the principals alignment
|
||||
func (t *Token) Covers(subDelegation *Token) bool {
|
||||
// The Subject of each delegation must equal the invocation's Subject (or Audience if defined). - 4f
|
||||
if t.Subject() != sub {
|
||||
return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrWrongSub, dlgCid, sub, dlg.Subject())
|
||||
}
|
||||
|
||||
// The Issuer of each delegation must be the Audience in the next one. - 4d
|
||||
if t.Audience() != subDelegation.Issuer() {
|
||||
return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrBrokenChain, dlgCid, iss, dlg.Audience())
|
||||
}
|
||||
|
||||
// The command of each delegation must "allow" the one before it. - 4g
|
||||
if !dlg.Command().Covers(cmd) {
|
||||
return fmt.Errorf("%w: delegation %s, %s doesn't cover %s", ErrCommandNotCovered, dlgCid, dlg.Command(), cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Token) String() string {
|
||||
var res strings.Builder
|
||||
|
||||
|
||||
@@ -20,16 +20,39 @@ const (
|
||||
subJectCmd = "/foo/bar"
|
||||
subjectPol = `
|
||||
[
|
||||
["==", ".status", "draft"],
|
||||
["all", ".reviewer",
|
||||
["like", ".email", "*@example.com"]
|
||||
],
|
||||
["any", ".tags",
|
||||
["or", [
|
||||
["==", ".", "news"],
|
||||
["==", ".", "press"]
|
||||
]]
|
||||
]
|
||||
[
|
||||
"==",
|
||||
".status",
|
||||
"draft"
|
||||
],
|
||||
[
|
||||
"all",
|
||||
".reviewer",
|
||||
[
|
||||
"like",
|
||||
".email",
|
||||
"*@example.com"
|
||||
]
|
||||
],
|
||||
[
|
||||
"any",
|
||||
".tags",
|
||||
[
|
||||
"or",
|
||||
[
|
||||
[
|
||||
"==",
|
||||
".",
|
||||
"news"
|
||||
],
|
||||
[
|
||||
"==",
|
||||
".",
|
||||
"press"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
`
|
||||
|
||||
@@ -57,9 +80,6 @@ func TestConstructors(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.False(t, tkn.IsRoot())
|
||||
require.False(t, tkn.IsPowerline())
|
||||
|
||||
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -77,9 +97,6 @@ func TestConstructors(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, tkn.IsRoot())
|
||||
require.False(t, tkn.IsPowerline())
|
||||
|
||||
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -97,9 +114,6 @@ func TestConstructors(t *testing.T) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.False(t, tkn.IsRoot())
|
||||
require.True(t, tkn.IsPowerline())
|
||||
|
||||
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ func (g *generator) writeGoFile() error {
|
||||
Println("}")
|
||||
|
||||
Println()
|
||||
Println("var AllBundles = []delegation.Bundle{")
|
||||
Println("var AllBundles = []*delegation.Bundle{")
|
||||
for _, d := range g.dlgs {
|
||||
Printf("\t%sBundle,\n", d.name)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ var fs embed.FS
|
||||
var _ delegation.Loader = (*DelegationLoader)(nil)
|
||||
|
||||
type DelegationLoader struct {
|
||||
bundles map[cid.Cid]delegation.Bundle
|
||||
bundles map[cid.Cid]*delegation.Bundle
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -75,7 +75,7 @@ func loadDelegations() (*DelegationLoader, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bundles := make(map[cid.Cid]delegation.Bundle, len(dirEntries))
|
||||
bundles := make(map[cid.Cid]*delegation.Bundle, len(dirEntries))
|
||||
|
||||
for _, dirEntry := range dirEntries {
|
||||
data, err := fs.ReadFile(filepath.Join(TokenDir, dirEntry.Name()))
|
||||
@@ -88,7 +88,7 @@ func loadDelegations() (*DelegationLoader, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bundles[id] = delegation.Bundle{Cid: id, Decoded: tkn, Sealed: data}
|
||||
bundles[id] = &delegation.Bundle{Cid: id, Decoded: tkn, Sealed: data}
|
||||
}
|
||||
|
||||
return &DelegationLoader{
|
||||
@@ -106,7 +106,7 @@ func CidToName(id cid.Cid) string {
|
||||
return cidToName[id]
|
||||
}
|
||||
|
||||
func mustGetBundle(id cid.Cid) delegation.Bundle {
|
||||
func mustGetBundle(id cid.Cid) *delegation.Bundle {
|
||||
bundle, ok := GetDelegationLoader().bundles[id]
|
||||
if !ok {
|
||||
panic(delegation.ErrDelegationNotFound)
|
||||
|
||||
@@ -195,7 +195,7 @@ var AllTokens = []*delegation.Token{
|
||||
TokenErinFrank_ValidExamplePolicy,
|
||||
}
|
||||
|
||||
var AllBundles = []delegation.Bundle{
|
||||
var AllBundles = []*delegation.Bundle{
|
||||
TokenAliceBobBundle,
|
||||
TokenBobCarolBundle,
|
||||
TokenCarolDanBundle,
|
||||
|
||||
@@ -272,14 +272,12 @@ func ExampleFromSealed() {
|
||||
// Policy (pol): [
|
||||
// ["==", ".status", "draft"],
|
||||
// ["all", ".reviewer",
|
||||
// ["like", ".email", "*@example.com"]
|
||||
// ],
|
||||
// ["like", ".email", "*@example.com"]],
|
||||
// ["any", ".tags",
|
||||
// ["or", [
|
||||
// ["==", ".", "news"],
|
||||
// ["==", ".", "press"]
|
||||
// ]]
|
||||
// ]
|
||||
// ["==", ".", "press"]]]
|
||||
// ]
|
||||
// ]
|
||||
// Nonce (nonce): 000102030405060708090a0b
|
||||
// Meta (meta): {}
|
||||
|
||||
@@ -16,7 +16,7 @@ type Loader interface {
|
||||
GetDelegation(cid cid.Cid) (*Token, error)
|
||||
}
|
||||
|
||||
// Bundle carries together a decoded token with its Cid and raw signed data.
|
||||
// Bundle carries together a decoded delegation with its Cid and raw signed data.
|
||||
type Bundle struct {
|
||||
Cid cid.Cid
|
||||
Decoded *Token
|
||||
|
||||
@@ -39,10 +39,3 @@ type Marshaller interface {
|
||||
// ToDagJsonWriter is the same as ToDagJson, but it accepts an io.Writer.
|
||||
ToDagJsonWriter(w io.Writer, privKey crypto.PrivKey) error
|
||||
}
|
||||
|
||||
// Bundle carries together a decoded token with its Cid and raw signed data.
|
||||
type Bundle struct {
|
||||
Cid cid.Cid
|
||||
Decoded Token
|
||||
Sealed []byte
|
||||
}
|
||||
|
||||
@@ -59,9 +59,6 @@ type Token struct {
|
||||
|
||||
// New creates an invocation Token with the provided options.
|
||||
//
|
||||
// The given proofs MUST be ordered from the leaf (matching the invocation) to
|
||||
// the root delegation.
|
||||
//
|
||||
// If no nonce is provided, a random 12-byte nonce is generated. Use the
|
||||
// WithNonce or WithEmptyNonce options to specify provide your own nonce
|
||||
// or to leave the nonce empty respectively.
|
||||
|
||||
@@ -37,9 +37,9 @@ import (
|
||||
// 4. When the proof chain is being validated (verifyProofs below):
|
||||
// a. There must be at least one delegation in the proof chain.
|
||||
// b. All referenced delegations must be available.
|
||||
// c. The first proof must be issued to the Invoker (audience DID).
|
||||
// d. The Issuer of each delegation must be the Audience in the next one.
|
||||
// e. The last token must be a root delegation.
|
||||
// c. The first proof must be issued to the Invoker.
|
||||
// d. The Issuer of each delegation must be the Audience in the parent delegation.
|
||||
// e. The chain must terminate with a root delegation.
|
||||
// f. The Subject of each delegation must equal the invocation's Subject (or Audience if defined)
|
||||
// g. The command of each delegation must "allow" the one before it.
|
||||
//
|
||||
@@ -63,7 +63,7 @@ func (t *Token) verifyProofs(delegations []*delegation.Token) error {
|
||||
sub = t.audience
|
||||
}
|
||||
|
||||
// control from the invocation to the root delegation
|
||||
// control from the invocation to the root
|
||||
for i, dlgCid := range t.proof {
|
||||
dlg := delegations[i]
|
||||
|
||||
@@ -72,7 +72,7 @@ func (t *Token) verifyProofs(delegations []*delegation.Token) error {
|
||||
return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrWrongSub, dlgCid, sub, dlg.Subject())
|
||||
}
|
||||
|
||||
// The first proof must be issued to the Invoker (audience DID). - 4c
|
||||
// The first proof must be issued to the Invoker. - 4c
|
||||
// The Issuer of each delegation must be the Audience in the next one. - 4d
|
||||
if dlg.Audience() != iss {
|
||||
return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrBrokenChain, dlgCid, iss, dlg.Audience())
|
||||
|
||||
@@ -2,6 +2,7 @@ package invocation_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
@@ -37,13 +38,18 @@ func TestSchemaRoundTrip(t *testing.T) {
|
||||
cborBytes, id, err := p1.ToSealed(privKey)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id))
|
||||
fmt.Println("cborBytes length", len(cborBytes))
|
||||
fmt.Println("cbor", string(cborBytes))
|
||||
|
||||
p2, c2, err := invocation.FromSealed(cborBytes)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, id, c2)
|
||||
fmt.Println("read Cbor", p2)
|
||||
|
||||
readJson, err := p2.ToDagJson(privKey)
|
||||
require.NoError(t, err)
|
||||
fmt.Println("readJson length", len(readJson))
|
||||
fmt.Println("json: ", string(readJson))
|
||||
|
||||
assert.JSONEq(t, string(invocationJson), string(readJson))
|
||||
})
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package invocation
|
||||
|
||||
import "github.com/ipfs/go-cid"
|
||||
|
||||
// Bundle carries together a decoded token with its Cid and raw signed data.
|
||||
type Bundle struct {
|
||||
Cid cid.Cid
|
||||
Decoded *Token
|
||||
Sealed []byte
|
||||
}
|
||||
Reference in New Issue
Block a user