diff --git a/api/things.yml b/api/things.yml
index 591091165b8..e36088a6689 100644
--- a/api/things.yml
+++ b/api/things.yml
@@ -636,7 +636,10 @@ components:
id:
type: string
format: uuid
- description: Thing unique identifier
+ description: Thing unique identifier. This can be either
+ provided by the user or left blank. If the user provides a UUID,
+ it would be validated. If there is not one provided then
+ the service will generate one in UUID format.
ThingReqSchema:
type: object
properties:
diff --git a/docker/.env b/docker/.env
index 586f848155f..6b17eb10a44 100644
--- a/docker/.env
+++ b/docker/.env
@@ -309,6 +309,7 @@ MF_SMTP_NOTIFIER_DB_USER=mainflux
MF_SMTP_NOTIFIER_DB_PASS=mainflux
MF_SMTP_NOTIFIER_DB=subscriptions
MF_SMTP_NOTIFIER_TEMPLATE=smtp-notifier.tmpl
+MF_SMTP_NOTIFIER_FROM_ADDR=from@example.com
### SMPP Notifier
diff --git a/docker/addons/smtp-notifier/docker-compose.yml b/docker/addons/smtp-notifier/docker-compose.yml
index 0d65668b285..8da313a2a4a 100644
--- a/docker/addons/smtp-notifier/docker-compose.yml
+++ b/docker/addons/smtp-notifier/docker-compose.yml
@@ -55,6 +55,7 @@ services:
MF_EMAIL_FROM_NAME: ${MF_EMAIL_FROM_NAME}
MF_EMAIL_TEMPLATE: ${MF_EMAIL_TEMPLATE}
MF_SMTP_NOTIFIER_TEMPLATE: ${MF_SMTP_NOTIFIER_TEMPLATE}
+ MF_SMTP_NOTIFIER_FROM_ADDR: ${MF_SMTP_NOTIFIER_FROM_ADDR}
ports:
- ${MF_SMTP_NOTIFIER_PORT}:${MF_SMTP_NOTIFIER_PORT}
networks:
diff --git a/docker/templates/smtp-notifier.tmpl b/docker/templates/smtp-notifier.tmpl
index 516d6b34874..64caa944866 100644
--- a/docker/templates/smtp-notifier.tmpl
+++ b/docker/templates/smtp-notifier.tmpl
@@ -1,4 +1,4 @@
-To: {{.To}}
+To: {{range $index, $v := .To}}{{if $index}},{{end}}{{$v}}{{end}}
From: {{.From}}
Subject: {{.Subject}}
{{.Header}}
diff --git a/docker/templates/users.tmpl b/docker/templates/users.tmpl
index 9725d0030d3..9ac08572dce 100644
--- a/docker/templates/users.tmpl
+++ b/docker/templates/users.tmpl
@@ -1,4 +1,4 @@
-To: {{.To}}
+To: {{range $index, $v := .To}}{{if $index}},{{end}}{{$v}}{{end}}
From: {{.From}}
Subject: {{.Subject}}
{{.Header}}
diff --git a/docker/vernemq/Dockerfile b/docker/vernemq/Dockerfile
index 4b2207f0141..f8d2b0b38b6 100644
--- a/docker/vernemq/Dockerfile
+++ b/docker/vernemq/Dockerfile
@@ -1,9 +1,8 @@
# Builder
-FROM erlang:22-alpine AS builder
+FROM erlang:23.3.2-alpine AS builder
RUN apk add --update git build-base bsd-compat-headers openssl-dev snappy-dev curl \
- && git clone https://github.com/vernemq/vernemq \
+ && git clone -b 1.12.3 https://github.com/vernemq/vernemq \
&& cd vernemq \
- && git checkout eb1a262035af47e90d9edf07f36c1b1503557c1f \
&& make -j 16 rel
# Executor
diff --git a/go.mod b/go.mod
index 073bafc92c6..7ea9b68276e 100644
--- a/go.mod
+++ b/go.mod
@@ -61,7 +61,7 @@ require (
github.com/docker/go-units v0.4.0 // indirect
github.com/dsnet/golib/memfile v0.0.0-20200723050859-c110804dfa93 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
- github.com/fxamacker/cbor/v2 v2.2.0 // indirect
+ github.com/fxamacker/cbor/v2 v2.3.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/golang/snappy v0.0.3 // indirect
diff --git a/go.sum b/go.sum
index 6069d823f66..20bbbda9900 100644
--- a/go.sum
+++ b/go.sum
@@ -297,8 +297,9 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fxamacker/cbor v1.3.2 h1:jMCvPyzpTVWoe1jRDUFPupVoV+DzDvnc1VP+9VU4ql8=
github.com/fxamacker/cbor v1.3.2/go.mod h1:Uy2lR31/2WfmW0yiA4i3t+we5kF3B/wzKsttcux+i/g=
-github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ=
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
+github.com/fxamacker/cbor/v2 v2.3.0 h1:aM45YGMctNakddNNAezPxDUpv38j44Abh+hifNuqXik=
+github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
diff --git a/internal/email/email.go b/internal/email/email.go
index 6264b106e9a..e322f95d3f2 100644
--- a/internal/email/email.go
+++ b/internal/email/email.go
@@ -6,8 +6,9 @@ package email
import (
"bytes"
"fmt"
- "html/template"
+ "net/mail"
"net/smtp"
+ "text/template"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/pkg/errors"
@@ -89,7 +90,8 @@ func (a *Agent) Send(To []string, From, Subject, Header, Content, Footer string)
Footer: Footer,
}
if From == "" {
- tmpl.From = a.conf.FromName
+ from := mail.Address{Name: a.conf.FromName, Address: a.conf.FromAddress}
+ tmpl.From = from.String()
}
if err := a.tmpl.Execute(email, tmpl); err != nil {
diff --git a/pkg/sdk/go/channels_test.go b/pkg/sdk/go/channels_test.go
index 105ebe7d5d4..0ad32f6038f 100644
--- a/pkg/sdk/go/channels_test.go
+++ b/pkg/sdk/go/channels_test.go
@@ -15,7 +15,10 @@ import (
)
var (
- channel = sdk.Channel{ID: "001", Name: "test"}
+ ch1 = sdk.Channel{Name: "test1"}
+ ch2 = sdk.Channel{ID: "fe6b4e92-cc98-425e-b0aa-000000000001", Name: "test1"}
+ ch3 = sdk.Channel{ID: "fe6b4e92-cc98-425e-b0aa-000000000002", Name: "test2"}
+ chPrefix = "fe6b4e92-cc98-425e-b0aa-"
emptyChannel = sdk.Channel{}
)
@@ -24,6 +27,8 @@ func TestCreateChannel(t *testing.T) {
ts := newThingsServer(svc)
defer ts.Close()
+ chWrongExtID := sdk.Channel{ID: "b0aa-000000000001", Name: "1", Metadata:metadata}
+
sdkConf := sdk.Config{
ThingsURL: ts.URL,
MsgContentType: contentType,
@@ -41,21 +46,21 @@ func TestCreateChannel(t *testing.T) {
}{
{
desc: "create new channel",
- channel: channel,
+ channel: ch1,
token: token,
err: nil,
empty: false,
},
{
desc: "create new channel with empty token",
- channel: channel,
+ channel: ch1,
token: "",
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
empty: true,
},
{
desc: "create new channel with invalid token",
- channel: channel,
+ channel: ch1,
token: wrongValue,
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
empty: true,
@@ -67,6 +72,20 @@ func TestCreateChannel(t *testing.T) {
err: nil,
empty: false,
},
+ {
+ desc: "create a new channel with external UUID",
+ channel: ch2,
+ token: token,
+ err: nil,
+ empty: false,
+ },
+ {
+ desc: "create a new channel with wrong external UUID",
+ channel: chWrongExtID,
+ token: token,
+ err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
+ empty: true,
+ },
}
for _, tc := range cases {
@@ -90,8 +109,8 @@ func TestCreateChannels(t *testing.T) {
mainfluxSDK := sdk.NewSDK(sdkConf)
channels := []sdk.Channel{
- sdk.Channel{ID: "001", Name: "1"},
- sdk.Channel{ID: "002", Name: "2"},
+ ch2,
+ ch3,
}
cases := []struct {
@@ -141,7 +160,7 @@ func TestCreateChannels(t *testing.T) {
}
func TestChannel(t *testing.T) {
- svc := newThingsService(map[string]string{token: email})
+ svc := newThingsService(map[string]string{token: adminEmail})
ts := newThingsServer(svc)
defer ts.Close()
sdkConf := sdk.Config{
@@ -151,7 +170,7 @@ func TestChannel(t *testing.T) {
}
mainfluxSDK := sdk.NewSDK(sdkConf)
- id, err := mainfluxSDK.CreateChannel(channel, token)
+ id, err := mainfluxSDK.CreateChannel(ch2, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
cases := []struct {
@@ -166,7 +185,7 @@ func TestChannel(t *testing.T) {
chanID: id,
token: token,
err: nil,
- response: channel,
+ response: ch2,
},
{
desc: "get non-existent channel",
@@ -204,7 +223,9 @@ func TestChannels(t *testing.T) {
var channels []sdk.Channel
mainfluxSDK := sdk.NewSDK(sdkConf)
for i := 1; i < 101; i++ {
- ch := sdk.Channel{ID: fmt.Sprintf("%03d", i), Name: "test"}
+ id := fmt.Sprintf("%s%012d", chPrefix, i)
+ name := fmt.Sprintf("test-%d", i)
+ ch := sdk.Channel{ID: id, Name: name}
_, err := mainfluxSDK.CreateChannel(ch, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
channels = append(channels, ch)
@@ -294,10 +315,9 @@ func TestChannelsByThing(t *testing.T) {
var chsDiscoNum = 1
var channels []sdk.Channel
for i := 1; i < n+1; i++ {
- ch := sdk.Channel{
- ID: fmt.Sprintf("%03d", i),
- Name: "test",
- }
+ id := fmt.Sprintf("%s%012d", chPrefix, i)
+ name := fmt.Sprintf("test-%d", i)
+ ch := sdk.Channel{ID: id, Name: name}
cid, err := mainfluxSDK.CreateChannel(ch, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
@@ -409,7 +429,7 @@ func TestChannelsByThing(t *testing.T) {
}
func TestUpdateChannel(t *testing.T) {
- svc := newThingsService(map[string]string{token: email})
+ svc := newThingsService(map[string]string{token: adminEmail})
ts := newThingsServer(svc)
defer ts.Close()
sdkConf := sdk.Config{
@@ -419,7 +439,7 @@ func TestUpdateChannel(t *testing.T) {
}
mainfluxSDK := sdk.NewSDK(sdkConf)
- id, err := mainfluxSDK.CreateChannel(channel, token)
+ id, err := mainfluxSDK.CreateChannel(ch2, token)
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
cases := []struct {
@@ -467,7 +487,7 @@ func TestUpdateChannel(t *testing.T) {
}
func TestDeleteChannel(t *testing.T) {
- svc := newThingsService(map[string]string{token: email})
+ svc := newThingsService(map[string]string{token: adminEmail})
ts := newThingsServer(svc)
defer ts.Close()
sdkConf := sdk.Config{
@@ -477,7 +497,7 @@ func TestDeleteChannel(t *testing.T) {
}
mainfluxSDK := sdk.NewSDK(sdkConf)
- id, err := mainfluxSDK.CreateChannel(channel, token)
+ id, err := mainfluxSDK.CreateChannel(ch2, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
cases := []struct {
diff --git a/pkg/sdk/go/groups.go b/pkg/sdk/go/groups.go
index b1df7c553a2..199393dc7d0 100644
--- a/pkg/sdk/go/groups.go
+++ b/pkg/sdk/go/groups.go
@@ -11,16 +11,14 @@ import (
"net/http"
"strings"
- "github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/pkg/errors"
)
-const groupsEndpoint = "groups"
-
-type assignRequest struct {
- Type string `json:"type,omitempty"`
- Members []string `json:"members"`
-}
+const (
+ groupsEndpoint = "groups"
+ MaxLevel = uint64(5)
+ MinLevel = uint64(1)
+)
func (sdk mfSDK) CreateGroup(g Group, token string) (string, error) {
data, err := json.Marshal(g)
@@ -128,75 +126,75 @@ func (sdk mfSDK) Unassign(token, groupID string, memberIDs ...string) error {
return nil
}
-func (sdk mfSDK) Members(groupID, token string, offset, limit uint64) (auth.MemberPage, error) {
+func (sdk mfSDK) Members(groupID, token string, offset, limit uint64) (MembersPage, error) {
url := fmt.Sprintf("%s, %s/%s/members?offset=%d&limit=%d&", sdk.authURL, groupsEndpoint, groupID, offset, limit)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
- return auth.MemberPage{}, err
+ return MembersPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
- return auth.MemberPage{}, err
+ return MembersPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
- return auth.MemberPage{}, err
+ return MembersPage{}, err
}
if resp.StatusCode != http.StatusOK {
- return auth.MemberPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
+ return MembersPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
- var tp auth.MemberPage
+ var tp MembersPage
if err := json.Unmarshal(body, &tp); err != nil {
- return auth.MemberPage{}, err
+ return MembersPage{}, err
}
return tp, nil
}
-func (sdk mfSDK) Groups(offset, limit uint64, token string) (auth.GroupPage, error) {
+func (sdk mfSDK) Groups(offset, limit uint64, token string) (GroupsPage, error) {
url := fmt.Sprintf("%s/%s?offset=%d&limit=%d&tree=false", sdk.authURL, groupsEndpoint, offset, limit)
return sdk.getGroups(token, url)
}
-func (sdk mfSDK) Parents(id string, offset, limit uint64, token string) (auth.GroupPage, error) {
- url := fmt.Sprintf("%s/%s/%s/parents?offset=%d&limit=%d&tree=false&level=%d", sdk.authURL, groupsEndpoint, id, offset, limit, auth.MaxLevel)
+func (sdk mfSDK) Parents(id string, offset, limit uint64, token string) (GroupsPage, error) {
+ url := fmt.Sprintf("%s/%s/%s/parents?offset=%d&limit=%d&tree=false&level=%d", sdk.authURL, groupsEndpoint, id, offset, limit, MaxLevel)
return sdk.getGroups(token, url)
}
-func (sdk mfSDK) Children(id string, offset, limit uint64, token string) (auth.GroupPage, error) {
- url := fmt.Sprintf("%s/%s/%s/children?offset=%d&limit=%d&tree=false&level=%d", sdk.authURL, groupsEndpoint, id, offset, limit, auth.MaxLevel)
+func (sdk mfSDK) Children(id string, offset, limit uint64, token string) (GroupsPage, error) {
+ url := fmt.Sprintf("%s/%s/%s/children?offset=%d&limit=%d&tree=false&level=%d", sdk.authURL, groupsEndpoint, id, offset, limit, MaxLevel)
return sdk.getGroups(token, url)
}
-func (sdk mfSDK) getGroups(token, url string) (auth.GroupPage, error) {
+func (sdk mfSDK) getGroups(token, url string) (GroupsPage, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
- return auth.GroupPage{}, err
+ return GroupsPage{}, err
}
resp, err := sdk.sendRequest(req, token, string(CTJSON))
if err != nil {
- return auth.GroupPage{}, err
+ return GroupsPage{}, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
- return auth.GroupPage{}, err
+ return GroupsPage{}, err
}
if resp.StatusCode != http.StatusOK {
- return auth.GroupPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
+ return GroupsPage{}, errors.Wrap(ErrFailedFetch, errors.New(resp.Status))
}
- var tp auth.GroupPage
+ var tp GroupsPage
if err := json.Unmarshal(body, &tp); err != nil {
- return auth.GroupPage{}, err
+ return GroupsPage{}, err
}
return tp, nil
}
diff --git a/pkg/sdk/go/requests.go b/pkg/sdk/go/requests.go
index e4c59c2f53f..1ca437a12f1 100644
--- a/pkg/sdk/go/requests.go
+++ b/pkg/sdk/go/requests.go
@@ -3,6 +3,11 @@
package sdk
+type assignRequest struct {
+ Type string `json:"type,omitempty"`
+ Members []string `json:"members"`
+}
+
// UserPasswordReq contains old and new passwords
type UserPasswordReq struct {
OldPassword string `json:"old_password,omitempty"`
diff --git a/pkg/sdk/go/responses.go b/pkg/sdk/go/responses.go
index d0ee0e6dea0..4aff0cdeea6 100644
--- a/pkg/sdk/go/responses.go
+++ b/pkg/sdk/go/responses.go
@@ -50,3 +50,8 @@ type UsersPage struct {
Users []User `json:"users"`
pageRes
}
+
+type MembersPage struct {
+ Members []Member `json:"members"`
+ pageRes
+}
diff --git a/pkg/sdk/go/sdk.go b/pkg/sdk/go/sdk.go
index 150f62870f8..c1a5a2acb9a 100644
--- a/pkg/sdk/go/sdk.go
+++ b/pkg/sdk/go/sdk.go
@@ -7,8 +7,6 @@ import (
"crypto/tls"
"errors"
"net/http"
-
- "github.com/mainflux/mainflux/auth"
)
const (
@@ -111,6 +109,12 @@ type Channel struct {
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
+//Member represents mainflux member.
+type Member struct {
+ ID string
+ Type string
+}
+
// SDK contains Mainflux API.
type SDK interface {
// CreateUser registers mainflux user.
@@ -157,13 +161,13 @@ type SDK interface {
DeleteGroup(id, token string) error
// Groups returns page of users groups.
- Groups(offset, limit uint64, token string) (auth.GroupPage, error)
+ Groups(offset, limit uint64, token string) (GroupsPage, error)
// Parents returns page of users groups.
- Parents(id string, offset, limit uint64, token string) (auth.GroupPage, error)
+ Parents(id string, offset, limit uint64, token string) (GroupsPage, error)
// Children returns page of users groups.
- Children(id string, offset, limit uint64, token string) (auth.GroupPage, error)
+ Children(id string, offset, limit uint64, token string) (GroupsPage, error)
// Group returns users group object by id.
Group(id, token string) (Group, error)
@@ -175,7 +179,7 @@ type SDK interface {
Unassign(token, groupID string, memberIDs ...string) error
// Members lists members of a group.
- Members(groupID, token string, offset, limit uint64) (auth.MemberPage, error)
+ Members(groupID, token string, offset, limit uint64) (MembersPage, error)
// Memberships lists groups for user.
Memberships(userID, token string, offset, limit uint64) (GroupsPage, error)
diff --git a/pkg/sdk/go/things_test.go b/pkg/sdk/go/things_test.go
index 2be046866aa..2d58ad2e6d8 100644
--- a/pkg/sdk/go/things_test.go
+++ b/pkg/sdk/go/things_test.go
@@ -22,26 +22,28 @@ import (
const (
contentType = "application/senml+json"
email = "user@example.com"
+ adminEmail = "admin@example.com"
otherEmail = "other_user@example.com"
token = "token"
otherToken = "other_token"
wrongValue = "wrong_value"
badID = "999"
emptyValue = ""
-
- keyPrefix = "123e4567-e89b-12d3-a456-"
)
var (
metadata = map[string]interface{}{"meta": "data"}
metadata2 = map[string]interface{}{"meta": "data2"}
- thing = sdk.Thing{ID: "001", Name: "test_device", Metadata: metadata}
+ th1 = sdk.Thing{ID: "fe6b4e92-cc98-425e-b0aa-000000000001", Name: "test1", Metadata: metadata}
+ th2 = sdk.Thing{ID: "fe6b4e92-cc98-425e-b0aa-000000000002", Name: "test2", Metadata: metadata}
emptyThing = sdk.Thing{}
)
func newThingsService(tokens map[string]string) things.Service {
- policies := []mocks.MockSubjectSet{{Object: "users", Relation: "member"}}
- auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{email: policies})
+ userPolicy := mocks.MockSubjectSet{Object: "users", Relation: "member"}
+ adminPolicy := mocks.MockSubjectSet{Object: "authorities", Relation: "member"}
+ auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{
+ adminEmail: {userPolicy, adminPolicy}, email: {userPolicy}})
conns := make(chan mocks.Connection)
thingsRepo := mocks.NewThingRepository(conns)
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
@@ -79,28 +81,28 @@ func TestCreateThing(t *testing.T) {
}{
{
desc: "create new thing",
- thing: thing,
+ thing: th1,
token: token,
err: nil,
- location: "001",
+ location: th1.ID,
},
{
desc: "create new empty thing",
thing: emptyThing,
token: token,
err: nil,
- location: "002",
+ location: fmt.Sprintf("%s%012d", uuid.Prefix, 2),
},
{
desc: "create new thing with empty token",
- thing: thing,
+ thing: th1,
token: "",
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
location: "",
},
{
desc: "create new thing with invalid token",
- thing: thing,
+ thing: th1,
token: wrongValue,
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
location: "",
@@ -128,8 +130,16 @@ func TestCreateThings(t *testing.T) {
mainfluxSDK := sdk.NewSDK(sdkConf)
things := []sdk.Thing{
- sdk.Thing{ID: "001", Name: "1", Key: "1"},
- sdk.Thing{ID: "002", Name: "2", Key: "2"},
+ th1,
+ th2,
+ }
+ thsExtID := []sdk.Thing{
+ {ID: th1.ID, Name: "1", Key: "1", Metadata: metadata},
+ {ID: th2.ID, Name: "2", Key: "2", Metadata: metadata},
+ }
+ thsWrongExtID := []sdk.Thing{
+ {ID: "b0aa-000000000001", Name: "1", Key: "1", Metadata: metadata},
+ {ID: "b0aa-000000000002", Name: "2", Key: "2", Metadata: metadata2},
}
cases := []struct {
@@ -167,6 +177,20 @@ func TestCreateThings(t *testing.T) {
err: createError(sdk.ErrFailedCreation, http.StatusUnauthorized),
res: []sdk.Thing{},
},
+ {
+ desc: "create new things with external UUID",
+ things: thsExtID,
+ token: token,
+ err: nil,
+ res: things,
+ },
+ {
+ desc: "create new things with wrong external UUID",
+ things: thsWrongExtID,
+ token: token,
+ err: createError(sdk.ErrFailedCreation, http.StatusBadRequest),
+ res: []sdk.Thing{},
+ },
}
for _, tc := range cases {
res, err := mainfluxSDK.CreateThings(tc.things, tc.token)
@@ -190,9 +214,9 @@ func TestThing(t *testing.T) {
}
mainfluxSDK := sdk.NewSDK(sdkConf)
- id, err := mainfluxSDK.CreateThing(thing, token)
+ id, err := mainfluxSDK.CreateThing(th1, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
- thing.Key = fmt.Sprintf("%s%012d", keyPrefix, 2)
+ th1.Key = fmt.Sprintf("%s%012d", uuid.Prefix, 1)
cases := []struct {
desc string
@@ -206,7 +230,7 @@ func TestThing(t *testing.T) {
thID: id,
token: token,
err: nil,
- response: thing,
+ response: th1,
},
{
desc: "get non-existent thing",
@@ -245,10 +269,12 @@ func TestThings(t *testing.T) {
mainfluxSDK := sdk.NewSDK(sdkConf)
for i := 1; i < 101; i++ {
- th := sdk.Thing{ID: fmt.Sprintf("%03d", i), Name: "test_device", Metadata: metadata}
+ id := fmt.Sprintf("%s%012d", chPrefix, i)
+ name := fmt.Sprintf("test-%d", i)
+ th := sdk.Thing{ID: id, Name: name, Metadata: metadata}
_, err := mainfluxSDK.CreateThing(th, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
- th.Key = fmt.Sprintf("%s%012d", keyPrefix, 2*i)
+ th.Key = fmt.Sprintf("%s%012d", uuid.Prefix, i)
things = append(things, th)
}
@@ -337,11 +363,13 @@ func TestThingsByChannel(t *testing.T) {
var thsDiscoNum = 1
var things []sdk.Thing
for i := 1; i < n+1; i++ {
+ id := fmt.Sprintf("%s%012d", chPrefix, i)
+ name := fmt.Sprintf("test-%d", i)
th := sdk.Thing{
- ID: fmt.Sprintf("%03d", i),
- Name: "test_device",
+ ID: id,
+ Name: name,
Metadata: metadata,
- Key: fmt.Sprintf("%s%012d", keyPrefix, 2*i+1),
+ Key: fmt.Sprintf("%s%012d", uuid.Prefix, 2*i+1),
}
tid, err := mainfluxSDK.CreateThing(th, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
@@ -464,9 +492,9 @@ func TestUpdateThing(t *testing.T) {
}
mainfluxSDK := sdk.NewSDK(sdkConf)
- id, err := mainfluxSDK.CreateThing(thing, token)
+ id, err := mainfluxSDK.CreateThing(th1, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
- thing.Name = "test2"
+ th1.Name = "test2"
cases := []struct {
desc string
@@ -543,7 +571,7 @@ func TestDeleteThing(t *testing.T) {
}
mainfluxSDK := sdk.NewSDK(sdkConf)
- id, err := mainfluxSDK.CreateThing(thing, token)
+ id, err := mainfluxSDK.CreateThing(th1, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
cases := []struct {
@@ -611,13 +639,13 @@ func TestConnectThing(t *testing.T) {
}
mainfluxSDK := sdk.NewSDK(sdkConf)
- thingID, err := mainfluxSDK.CreateThing(thing, token)
+ thingID, err := mainfluxSDK.CreateThing(th1, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
- chanID1, err := mainfluxSDK.CreateChannel(channel, token)
+ chanID1, err := mainfluxSDK.CreateChannel(ch2, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
- chanID2, err := mainfluxSDK.CreateChannel(channel, otherToken)
+ chanID2, err := mainfluxSDK.CreateChannel(ch3, otherToken)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
cases := []struct {
@@ -712,13 +740,13 @@ func TestConnect(t *testing.T) {
}
mainfluxSDK := sdk.NewSDK(sdkConf)
- thingID, err := mainfluxSDK.CreateThing(thing, token)
+ thingID, err := mainfluxSDK.CreateThing(th1, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
- chanID1, err := mainfluxSDK.CreateChannel(channel, token)
+ chanID1, err := mainfluxSDK.CreateChannel(ch2, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
- chanID2, err := mainfluxSDK.CreateChannel(channel, otherToken)
+ chanID2, err := mainfluxSDK.CreateChannel(ch3, otherToken)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
cases := []struct {
@@ -815,10 +843,10 @@ func TestDisconnectThing(t *testing.T) {
mainfluxSDK := sdk.NewSDK(sdkConf)
- thingID, err := mainfluxSDK.CreateThing(thing, token)
+ thingID, err := mainfluxSDK.CreateThing(th1, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
- chanID1, err := mainfluxSDK.CreateChannel(channel, token)
+ chanID1, err := mainfluxSDK.CreateChannel(ch2, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
conIDs := sdk.ConnectionIDs{
@@ -828,7 +856,7 @@ func TestDisconnectThing(t *testing.T) {
err = mainfluxSDK.Connect(conIDs, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
- chanID2, err := mainfluxSDK.CreateChannel(channel, otherToken)
+ chanID2, err := mainfluxSDK.CreateChannel(ch2, otherToken)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
cases := []struct {
diff --git a/things/api/things/http/endpoint.go b/things/api/things/http/endpoint.go
index 52641a8a386..fc343f8e3f6 100644
--- a/things/api/things/http/endpoint.go
+++ b/things/api/things/http/endpoint.go
@@ -22,6 +22,7 @@ func createThingEndpoint(svc things.Service) endpoint.Endpoint {
th := things.Thing{
Key: req.Key,
+ ID: req.ID,
Name: req.Name,
Metadata: req.Metadata,
}
@@ -52,6 +53,7 @@ func createThingsEndpoint(svc things.Service) endpoint.Endpoint {
th := things.Thing{
Name: tReq.Name,
Key: tReq.Key,
+ ID: tReq.ID,
Metadata: tReq.Metadata,
}
ths = append(ths, th)
@@ -263,7 +265,11 @@ func createChannelEndpoint(svc things.Service) endpoint.Endpoint {
return nil, err
}
- ch := things.Channel{Name: req.Name, Metadata: req.Metadata}
+ ch := things.Channel{
+ Name: req.Name,
+ ID: req.ID,
+ Metadata: req.Metadata}
+
saved, err := svc.CreateChannels(ctx, req.token, ch)
if err != nil {
return nil, err
@@ -290,6 +296,7 @@ func createChannelsEndpoint(svc things.Service) endpoint.Endpoint {
ch := things.Channel{
Metadata: cReq.Metadata,
Name: cReq.Name,
+ ID: cReq.ID,
}
chs = append(chs, ch)
}
diff --git a/things/api/things/http/endpoint_test.go b/things/api/things/http/endpoint_test.go
index 657608fe25f..91b15e4abf1 100644
--- a/things/api/things/http/endpoint_test.go
+++ b/things/api/things/http/endpoint_test.go
@@ -28,6 +28,7 @@ import (
const (
contentType = "application/json"
email = "user@example.com"
+ adminEmail = "admin@example.com"
token = "token"
wrongValue = "wrong_value"
wrongID = 0
@@ -35,6 +36,7 @@ const (
nameKey = "name"
ascKey = "asc"
descKey = "desc"
+ prefix = "fe6b4e92-cc98-425e-b0aa-"
)
var (
@@ -80,8 +82,10 @@ func (tr testRequest) make() (*http.Response, error) {
}
func newService(tokens map[string]string) things.Service {
- policies := []mocks.MockSubjectSet{{Object: "users", Relation: "member"}}
- auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{email: policies})
+ userPolicy := mocks.MockSubjectSet{Object: "users", Relation: "member"}
+ adminPolicy := mocks.MockSubjectSet{Object: "authorities", Relation: "member"}
+ auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{
+ adminEmail: {userPolicy, adminPolicy}, email: {userPolicy}})
conns := make(chan mocks.Connection)
thingsRepo := mocks.NewThingRepository(conns)
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
@@ -128,7 +132,7 @@ func TestCreateThing(t *testing.T) {
contentType: contentType,
auth: token,
status: http.StatusCreated,
- location: "/things/001",
+ location: fmt.Sprintf("/things/%s%012d", uuid.Prefix, 1),
},
{
desc: "add thing with existing key",
@@ -144,7 +148,7 @@ func TestCreateThing(t *testing.T) {
contentType: contentType,
auth: token,
status: http.StatusCreated,
- location: "/things/002",
+ location: fmt.Sprintf("/things/%s%012d", uuid.Prefix, 3),
},
{
desc: "add thing with invalid auth token",
@@ -763,7 +767,10 @@ func TestListThings(t *testing.T) {
data := []thingRes{}
for i := 0; i < 100; i++ {
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ id := fmt.Sprintf("%s%012d", prefix, i + 1)
+ thing1 := thing
+ thing1.ID = id
+ ths, err := svc.CreateThings(context.Background(), token, thing1)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
th := ths[0]
data = append(data, thingRes{
@@ -1000,7 +1007,8 @@ func TestSearchThings(t *testing.T) {
data := []thingRes{}
for i := 0; i < 100; i++ {
name := "name_" + fmt.Sprintf("%03d", i+1)
- ths, err := svc.CreateThings(context.Background(), token, things.Thing{Name: name, Metadata: map[string]interface{}{"test": name}})
+ id := fmt.Sprintf("%s%012d", prefix, i + 1)
+ ths, err := svc.CreateThings(context.Background(), token, things.Thing{ID: id, Name: name, Metadata: map[string]interface{}{"test": name}})
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
th := ths[0]
data = append(data, thingRes{
@@ -1167,7 +1175,10 @@ func TestListThingsByChannel(t *testing.T) {
data := []thingRes{}
for i := 0; i < 101; i++ {
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ id := fmt.Sprintf("%s%012d", prefix, i + 1)
+ thing1 := thing
+ thing1.ID = id
+ ths, err := svc.CreateThings(context.Background(), token, thing1)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
th := ths[0]
err = svc.Connect(context.Background(), token, []string{ch.ID}, []string{th.ID})
@@ -1415,7 +1426,7 @@ func TestCreateChannel(t *testing.T) {
contentType: contentType,
auth: token,
status: http.StatusCreated,
- location: "/channels/001",
+ location: fmt.Sprintf("/channels/%s%012d", uuid.Prefix, 1),
},
{
desc: "create new channel with invalid token",
@@ -1447,7 +1458,7 @@ func TestCreateChannel(t *testing.T) {
contentType: contentType,
auth: token,
status: http.StatusCreated,
- location: "/channels/002",
+ location: fmt.Sprintf("/channels/%s%012d", uuid.Prefix, 2),
},
{
desc: "create new channel with empty request",
@@ -1593,7 +1604,7 @@ func TestCreateChannels(t *testing.T) {
}
func TestUpdateChannel(t *testing.T) {
- svc := newService(map[string]string{token: email})
+ svc := newService(map[string]string{token: adminEmail})
ts := newServer(svc)
defer ts.Close()
@@ -1713,7 +1724,7 @@ func TestUpdateChannel(t *testing.T) {
}
func TestViewChannel(t *testing.T) {
- svc := newService(map[string]string{token: email})
+ svc := newService(map[string]string{token: adminEmail})
ts := newServer(svc)
defer ts.Close()
@@ -2018,7 +2029,10 @@ func TestListChannelsByThing(t *testing.T) {
channels := []channelRes{}
for i := 0; i < 101; i++ {
- chs, err := svc.CreateChannels(context.Background(), token, channel)
+ id := fmt.Sprintf("%s%012d", prefix, i + 1)
+ channel1 := channel
+ channel1.ID = id
+ chs, err := svc.CreateChannels(context.Background(), token, channel1)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
ch := chs[0]
err = svc.Connect(context.Background(), token, []string{ch.ID}, []string{th.ID})
@@ -2184,7 +2198,7 @@ func TestListChannelsByThing(t *testing.T) {
}
func TestRemoveChannel(t *testing.T) {
- svc := newService(map[string]string{token: email})
+ svc := newService(map[string]string{token: adminEmail})
ts := newServer(svc)
defer ts.Close()
diff --git a/things/api/things/http/requests.go b/things/api/things/http/requests.go
index f481aa1d915..7b55d876edd 100644
--- a/things/api/things/http/requests.go
+++ b/things/api/things/http/requests.go
@@ -4,6 +4,7 @@
package http
import (
+ "github.com/gofrs/uuid"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/things"
)
@@ -24,14 +25,28 @@ type createThingReq struct {
token string
Name string `json:"name,omitempty"`
Key string `json:"key,omitempty"`
+ ID string `json:"id,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
+func validateUUID(extID string) (err error) {
+ id, err := uuid.FromString(extID)
+ if id.String() != extID || err != nil {
+ return things.ErrMalformedEntity
+ }
+
+ return nil
+}
+
func (req createThingReq) validate() error {
if req.token == "" {
return things.ErrUnauthorizedAccess
}
+ if req.ID != "" && validateUUID(req.ID) != nil {
+ return things.ErrMalformedEntity
+ }
+
if len(req.Name) > maxNameSize {
return things.ErrMalformedEntity
}
@@ -54,6 +69,10 @@ func (req createThingsReq) validate() error {
}
for _, thing := range req.Things {
+ if thing.ID != "" && validateUUID(thing.ID) != nil {
+ return things.ErrMalformedEntity
+ }
+
if len(thing.Name) > maxNameSize {
return things.ErrMalformedEntity
}
@@ -129,6 +148,7 @@ func (req updateKeyReq) validate() error {
type createChannelReq struct {
token string
Name string `json:"name,omitempty"`
+ ID string `json:"id,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
@@ -137,6 +157,10 @@ func (req createChannelReq) validate() error {
return things.ErrUnauthorizedAccess
}
+ if req.ID != "" && validateUUID(req.ID) != nil {
+ return things.ErrMalformedEntity
+ }
+
if len(req.Name) > maxNameSize {
return things.ErrMalformedEntity
}
@@ -159,6 +183,10 @@ func (req createChannelsReq) validate() error {
}
for _, channel := range req.Channels {
+ if channel.ID != "" && validateUUID(channel.ID) != nil {
+ return things.ErrMalformedEntity
+ }
+
if len(channel.Name) > maxNameSize {
return things.ErrMalformedEntity
}
diff --git a/things/mocks/auth.go b/things/mocks/auth.go
index d6803094b00..9e0051f969a 100644
--- a/things/mocks/auth.go
+++ b/things/mocks/auth.go
@@ -69,12 +69,7 @@ func (svc authServiceMock) AddPolicy(ctx context.Context, in *mainflux.AddPolicy
return &mainflux.AddPolicyRes{}, things.ErrMalformedEntity
}
- // Mock thingsRepository saves the thing ID after padding the ID by 3. (see things/mocks/things.go)
- // Since we are adding policies within the Service layer, we are storing them as a full ID which is
- // eventually not compatible with the one inside of the mock things repository. Therefore, we are
- // getting last three part of the ID as below.
obj := in.GetObj()
- obj = obj[len(obj)-3:]
svc.policies[in.GetSub()] = append(svc.policies[in.GetSub()], MockSubjectSet{Object: obj, Relation: in.GetAct()})
return &mainflux.AddPolicyRes{Authorized: true}, nil
}
diff --git a/things/mocks/channels.go b/things/mocks/channels.go
index f459eb5feab..aedda933e71 100644
--- a/things/mocks/channels.go
+++ b/things/mocks/channels.go
@@ -6,7 +6,6 @@ package mocks
import (
"context"
"fmt"
- "strconv"
"strings"
"sync"
@@ -48,7 +47,9 @@ func (crm *channelRepositoryMock) Save(_ context.Context, channels ...things.Cha
for i := range channels {
crm.counter++
- channels[i].ID = fmt.Sprintf("%03d", crm.counter)
+ if channels[i].ID == "" {
+ channels[i].ID = fmt.Sprintf("%03d", crm.counter)
+ }
crm.channels[key(channels[i].Owner, channels[i].ID)] = channels[i]
}
@@ -136,7 +137,7 @@ func (crm *channelRepositoryMock) RetrieveByThing(_ context.Context, owner, thID
switch pm.Disconnected {
case false:
for _, co := range crm.cconns[thID] {
- id, _ := strconv.ParseUint(co.ID, 10, 64)
+ id := parseID(co.ID)
if id >= first && id < last {
chs = append(chs, co)
}
@@ -144,7 +145,7 @@ func (crm *channelRepositoryMock) RetrieveByThing(_ context.Context, owner, thID
default:
for _, ch := range crm.channels {
conn := false
- id, _ := strconv.ParseUint(ch.ID, 10, 64)
+ id := parseID(ch.ID)
if id >= first && id < last {
for _, co := range crm.cconns[thID] {
if ch.ID == co.ID {
diff --git a/things/mocks/commons.go b/things/mocks/commons.go
index 909c6ce6fb3..5177e6bc81c 100644
--- a/things/mocks/commons.go
+++ b/things/mocks/commons.go
@@ -6,10 +6,13 @@ package mocks
import (
"fmt"
"sort"
+ "strconv"
"github.com/mainflux/mainflux/things"
)
+const uuidLen = 36
+
// Since mocks will store data in map, and they need to resemble the real
// identifiers as much as possible, a key will be created as combination of
// owner and their own identifiers. This will allow searching either by
@@ -83,3 +86,14 @@ func sortChannels(pm things.PageMetadata, chs []things.Channel) []things.Channel
return chs
}
+
+func parseID(ID string) (id uint64) {
+ var serialNum string
+
+ if len(ID) == uuidLen {
+ serialNum = ID[len(ID)-6:]
+ }
+ id, _ = strconv.ParseUint(serialNum, 10, 64)
+
+ return
+}
diff --git a/things/mocks/things.go b/things/mocks/things.go
index 3df7932bd7d..7215ce0da9e 100644
--- a/things/mocks/things.go
+++ b/things/mocks/things.go
@@ -6,7 +6,6 @@ package mocks
import (
"context"
"fmt"
- "strconv"
"strings"
"sync"
@@ -55,7 +54,9 @@ func (trm *thingRepositoryMock) Save(_ context.Context, ths ...things.Thing) ([]
}
trm.counter++
- ths[i].ID = fmt.Sprintf("%03d", trm.counter)
+ if ths[i].ID == "" {
+ ths[i].ID = fmt.Sprintf("%03d", trm.counter)
+ }
trm.things[key(ths[i].Owner, ths[i].ID)] = ths[i]
}
@@ -128,7 +129,7 @@ func (trm *thingRepositoryMock) RetrieveAll(_ context.Context, owner string, pm
// itself (see mocks/commons.go).
prefix := fmt.Sprintf("%s-", owner)
for k, v := range trm.things {
- id, _ := strconv.ParseUint(v.ID, 10, 64)
+ id := parseID(v.ID)
if strings.HasPrefix(k, prefix) && id >= first && id < last {
ths = append(ths, v)
}
@@ -167,7 +168,7 @@ func (trm *thingRepositoryMock) RetrieveByIDs(_ context.Context, thingIDs []stri
for _, id := range thingIDs {
suffix := fmt.Sprintf("-%s", id)
for k, v := range trm.things {
- id, _ := strconv.ParseUint(v.ID, 10, 64)
+ id := parseID(v.ID)
if strings.HasSuffix(k, suffix) && id >= first && id < last {
items = append(items, v)
}
@@ -205,7 +206,7 @@ func (trm *thingRepositoryMock) RetrieveByChannel(_ context.Context, owner, chID
switch pm.Disconnected {
case false:
for _, co := range trm.tconns[chID] {
- id, _ := strconv.ParseUint(co.ID, 10, 64)
+ id := parseID(co.ID)
if id >= first && id < last {
ths = append(ths, co)
}
@@ -213,7 +214,7 @@ func (trm *thingRepositoryMock) RetrieveByChannel(_ context.Context, owner, chID
default:
for _, th := range trm.things {
conn := false
- id, _ := strconv.ParseUint(th.ID, 10, 64)
+ id := parseID(th.ID)
if id >= first && id < last {
for _, co := range trm.tconns[chID] {
if th.ID == co.ID {
diff --git a/things/postgres/channels.go b/things/postgres/channels.go
index fe5a5b05b87..cc523ff709b 100644
--- a/things/postgres/channels.go
+++ b/things/postgres/channels.go
@@ -103,13 +103,12 @@ func (cr channelRepository) Update(ctx context.Context, channel things.Channel)
}
func (cr channelRepository) RetrieveByID(ctx context.Context, owner, id string) (things.Channel, error) {
- q := `SELECT name, metadata FROM channels WHERE id = $1 AND owner = $2;`
+ q := `SELECT name, metadata, owner FROM channels WHERE id = $1;`
dbch := dbChannel{
- ID: id,
- Owner: owner,
+ ID: id,
}
- if err := cr.db.QueryRowxContext(ctx, q, id, owner).StructScan(&dbch); err != nil {
+ if err := cr.db.QueryRowxContext(ctx, q, id).StructScan(&dbch); err != nil {
pqErr, ok := err.(*pq.Error)
if err == sql.ErrNoRows || ok && errInvalid == pqErr.Code.Name() {
return things.Channel{}, things.ErrNotFound
@@ -124,6 +123,7 @@ func (cr channelRepository) RetrieveAll(ctx context.Context, owner string, pm th
nq, name := getNameQuery(pm.Name)
oq := getOrderQuery(pm.Order)
dq := getDirQuery(pm.Dir)
+ ownerQuery := getOwnerQuery(pm.FetchSharedThings)
meta, mq, err := getMetadataQuery(pm.Metadata)
if err != nil {
return things.ChannelsPage{}, errors.Wrap(things.ErrSelectEntity, err)
@@ -137,13 +137,16 @@ func (cr channelRepository) RetrieveAll(ctx context.Context, owner string, pm th
if nq != "" {
query = append(query, nq)
}
+ if ownerQuery != "" {
+ query = append(query, ownerQuery)
+ }
if len(query) > 0 {
- whereClause = fmt.Sprintf("AND %s", strings.Join(query, " AND "))
+ whereClause = fmt.Sprintf(" WHERE %s", strings.Join(query, " AND "))
}
q := fmt.Sprintf(`SELECT id, name, metadata FROM channels
- WHERE owner = :owner %s ORDER BY %s %s LIMIT :limit OFFSET :offset;`, whereClause, oq, dq)
+ %s ORDER BY %s %s LIMIT :limit OFFSET :offset;`, whereClause, oq, dq)
params := map[string]interface{}{
"owner": owner,
@@ -169,7 +172,7 @@ func (cr channelRepository) RetrieveAll(ctx context.Context, owner string, pm th
items = append(items, ch)
}
- cq := fmt.Sprintf(`SELECT COUNT(*) FROM channels WHERE owner = :owner %s;`, whereClause)
+ cq := fmt.Sprintf(`SELECT COUNT(*) FROM channels %s;`, whereClause)
total, err := total(ctx, cr.db, cq, params)
if err != nil {
diff --git a/things/postgres/channels_test.go b/things/postgres/channels_test.go
index 3fec61b245d..afd4d8e306f 100644
--- a/things/postgres/channels_test.go
+++ b/things/postgres/channels_test.go
@@ -186,11 +186,6 @@ func TestSingleChannelRetrieval(t *testing.T) {
ID: nonexistentChanID,
err: things.ErrNotFound,
},
- "retrieve channel with non-existing owner": {
- owner: wrongValue,
- ID: ch.ID,
- err: things.ErrNotFound,
- },
"retrieve channel with malformed ID": {
owner: ch.Owner,
ID: wrongValue,
diff --git a/things/redis/streams_test.go b/things/redis/streams_test.go
index 16de4800347..90c609a9a66 100644
--- a/things/redis/streams_test.go
+++ b/things/redis/streams_test.go
@@ -24,6 +24,7 @@ import (
const (
streamID = "mainflux.things"
email = "user@example.com"
+ adminEmail = "admin@example.com"
token = "token"
thingPrefix = "thing."
thingCreate = thingPrefix + "create"
@@ -39,8 +40,10 @@ const (
)
func newService(tokens map[string]string) things.Service {
- policies := []mocks.MockSubjectSet{{Object: "users", Relation: "member"}}
- auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{email: policies})
+ userPolicy := mocks.MockSubjectSet{Object: "users", Relation: "member"}
+ adminPolicy := mocks.MockSubjectSet{Object: "authorities", Relation: "member"}
+ auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{
+ adminEmail: {userPolicy, adminPolicy}, email: {userPolicy}})
conns := make(chan mocks.Connection)
thingsRepo := mocks.NewThingRepository(conns)
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
@@ -73,7 +76,7 @@ func TestCreateThings(t *testing.T) {
key: token,
err: nil,
event: map[string]interface{}{
- "id": "001",
+ "id": "123e4567-e89b-12d3-a456-000000000001",
"name": "a",
"owner": email,
"metadata": "{\"test\":\"test\"}",
@@ -299,7 +302,7 @@ func TestCreateChannels(t *testing.T) {
key: token,
err: nil,
event: map[string]interface{}{
- "id": "001",
+ "id": "123e4567-e89b-12d3-a456-000000000001",
"name": "a",
"metadata": "{\"test\":\"test\"}",
"owner": email,
@@ -340,7 +343,7 @@ func TestCreateChannels(t *testing.T) {
func TestUpdateChannel(t *testing.T) {
_ = redisClient.FlushAll(context.Background()).Err()
- svc := newService(map[string]string{token: email})
+ svc := newService(map[string]string{token: adminEmail})
// Create channel without sending event.
schs, err := svc.CreateChannels(context.Background(), token, things.Channel{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
@@ -460,7 +463,7 @@ func TestListChannelsByThing(t *testing.T) {
func TestRemoveChannel(t *testing.T) {
_ = redisClient.FlushAll(context.Background()).Err()
- svc := newService(map[string]string{token: email})
+ svc := newService(map[string]string{token: adminEmail})
// Create channel without sending event.
schs, err := svc.CreateChannels(context.Background(), token, things.Channel{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
diff --git a/things/service.go b/things/service.go
index 3e2b0df0fab..03a1dadf8b5 100644
--- a/things/service.go
+++ b/things/service.go
@@ -53,7 +53,6 @@ const (
readRelationKey = "read"
writeRelationKey = "write"
deleteRelationKey = "delete"
- adminSubject = "members:authorities#member"
)
// Service specifies an API that must be fullfiled by the domain service
@@ -193,6 +192,7 @@ func (ts *thingsService) CreateThings(ctx context.Context, token string, things
ths := []Thing{}
for _, thing := range things {
th, err := ts.createThing(ctx, &thing, res)
+
if err != nil {
return []Thing{}, err
}
@@ -204,18 +204,24 @@ func (ts *thingsService) CreateThings(ctx context.Context, token string, things
// createThing saves the Thing and adds identity as an owner(Read, Write, Delete policies) of the Thing.
func (ts *thingsService) createThing(ctx context.Context, thing *Thing, identity *mainflux.UserIdentity) (Thing, error) {
- thID, err := ts.idProvider.ID()
- if err != nil {
- return Thing{}, errors.Wrap(ErrCreateUUID, err)
- }
- thing.ID = thID
+
thing.Owner = identity.GetEmail()
+ if thing.ID == "" {
+ id, err := ts.idProvider.ID()
+ if err != nil {
+ return Thing{}, errors.Wrap(ErrCreateUUID, err)
+ }
+ thing.ID = id
+ }
+
if thing.Key == "" {
- thing.Key, err = ts.idProvider.ID()
+ key, err := ts.idProvider.ID()
+
if err != nil {
return Thing{}, errors.Wrap(ErrCreateUUID, err)
}
+ thing.Key = key
}
ths, err := ts.things.Save(ctx, *thing)
@@ -258,22 +264,24 @@ func (ts *thingsService) ShareThing(ctx context.Context, token, thingID string,
}
if err := ts.authorize(ctx, res.GetId(), thingID, writeRelationKey); err != nil {
- return err
+ if err := ts.authorize(ctx, res.GetId(), authoritiesObject, memberRelationKey); err != nil {
+ return err
+ }
}
return ts.claimOwnership(ctx, thingID, actions, userIDs)
}
-func (ts *thingsService) claimOwnership(ctx context.Context, thingID string, actions, userIDs []string) error {
+func (ts *thingsService) claimOwnership(ctx context.Context, objectID string, actions, userIDs []string) error {
var errs error
for _, userID := range userIDs {
for _, action := range actions {
- apr, err := ts.auth.AddPolicy(ctx, &mainflux.AddPolicyReq{Obj: thingID, Act: action, Sub: userID})
+ apr, err := ts.auth.AddPolicy(ctx, &mainflux.AddPolicyReq{Obj: objectID, Act: action, Sub: userID})
if err != nil {
- errs = errors.Wrap(fmt.Errorf("cannot claim ownership on thing '%s' by user '%s': %s", thingID, userID, err), errs)
+ errs = errors.Wrap(fmt.Errorf("cannot claim ownership on object '%s' by user '%s': %s", objectID, userID, err), errs)
}
if !apr.GetAuthorized() {
- errs = errors.Wrap(fmt.Errorf("cannot claim ownership on thing '%s' by user '%s': unauthorized", thingID, userID), errs)
+ errs = errors.Wrap(fmt.Errorf("cannot claim ownership on object '%s' by user '%s': unauthorized", objectID, userID), errs)
}
}
}
@@ -327,11 +335,11 @@ func (ts *thingsService) ListThings(ctx context.Context, token string, pm PageMe
return Page{}, err
}
return page, err
- // subject = adminSubject
}
- // If the user is not admin, check 'shared' parameter from pagemetada.
- // If user provides 'shared' key, fetch things from policies.
+ // If the user is not admin, check 'shared' parameter from page metadata.
+ // If user provides 'shared' key, fetch things from policies. Otherwise,
+ // fetch things from the database based on thing's 'owner' field.
if pm.FetchSharedThings {
req := &mainflux.ListPoliciesReq{Act: "read", Sub: subject}
lpr, err := ts.auth.ListPolicies(ctx, req)
@@ -388,16 +396,40 @@ func (ts *thingsService) CreateChannels(ctx context.Context, token string, chann
return []Channel{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
- for i := range channels {
- channels[i].ID, err = ts.idProvider.ID()
+ chs := []Channel{}
+ for _, channel := range channels {
+ ch, err := ts.createChannel(ctx, &channel, res)
+ if err != nil {
+ return []Channel{}, err
+ }
+ chs = append(chs, ch)
+ }
+ return chs, nil
+}
+
+func (ts *thingsService) createChannel(ctx context.Context, channel *Channel, identity *mainflux.UserIdentity) (Channel, error) {
+ if channel.ID == "" {
+ chID, err := ts.idProvider.ID()
if err != nil {
- return []Channel{}, errors.Wrap(ErrCreateUUID, err)
+ return Channel{}, errors.Wrap(ErrCreateUUID, err)
}
+ channel.ID = chID
+ }
+ channel.Owner = identity.GetEmail()
- channels[i].Owner = res.GetEmail()
+ chs, err := ts.channels.Save(ctx, *channel)
+ if err != nil {
+ return Channel{}, err
+ }
+ if len(chs) == 0 {
+ return Channel{}, ErrCreateEntity
}
- return ts.channels.Save(ctx, channels...)
+ ss := fmt.Sprintf("%s:%s#%s", "members", authoritiesObject, memberRelationKey)
+ if err := ts.claimOwnership(ctx, chs[0].ID, []string{readRelationKey, writeRelationKey, deleteRelationKey}, []string{identity.GetId(), ss}); err != nil {
+ return Channel{}, err
+ }
+ return chs[0], nil
}
func (ts *thingsService) UpdateChannel(ctx context.Context, token string, channel Channel) error {
@@ -406,6 +438,12 @@ func (ts *thingsService) UpdateChannel(ctx context.Context, token string, channe
return errors.Wrap(ErrUnauthorizedAccess, err)
}
+ if err := ts.authorize(ctx, res.GetId(), channel.ID, writeRelationKey); err != nil {
+ if err := ts.authorize(ctx, res.GetId(), authoritiesObject, memberRelationKey); err != nil {
+ return err
+ }
+ }
+
channel.Owner = res.GetEmail()
return ts.channels.Update(ctx, channel)
}
@@ -416,6 +454,12 @@ func (ts *thingsService) ViewChannel(ctx context.Context, token, id string) (Cha
return Channel{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
+ if err := ts.authorize(ctx, res.GetId(), id, readRelationKey); err != nil {
+ if err := ts.authorize(ctx, res.GetId(), authoritiesObject, memberRelationKey); err != nil {
+ return Channel{}, err
+ }
+ }
+
return ts.channels.RetrieveByID(ctx, res.GetEmail(), id)
}
@@ -425,6 +469,17 @@ func (ts *thingsService) ListChannels(ctx context.Context, token string, pm Page
return ChannelsPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
+ // If the user is admin, fetch all channels from the database.
+ if err := ts.authorize(ctx, res.GetId(), authoritiesObject, memberRelationKey); err == nil {
+ pm.FetchSharedThings = true
+ page, err := ts.channels.RetrieveAll(ctx, res.GetEmail(), pm)
+ if err != nil {
+ return ChannelsPage{}, err
+ }
+ return page, err
+ }
+
+ // By default, fetch channels from database based on the owner field.
return ts.channels.RetrieveAll(ctx, res.GetEmail(), pm)
}
@@ -443,6 +498,12 @@ func (ts *thingsService) RemoveChannel(ctx context.Context, token, id string) er
return errors.Wrap(ErrUnauthorizedAccess, err)
}
+ if err := ts.authorize(ctx, res.GetId(), id, deleteRelationKey); err != nil {
+ if err := ts.authorize(ctx, res.GetId(), authoritiesObject, memberRelationKey); err != nil {
+ return err
+ }
+ }
+
if err := ts.channelCache.Remove(ctx, id); err != nil {
return err
}
diff --git a/things/service_test.go b/things/service_test.go
index f60eb5264d5..b87c92b5b62 100644
--- a/things/service_test.go
+++ b/things/service_test.go
@@ -20,21 +20,28 @@ import (
const (
wrongID = ""
wrongValue = "wrong-value"
+ adminEmail = "admin@example.com"
email = "user@example.com"
email2 = "user2@example.com"
token = "token"
token2 = "token2"
n = uint64(10)
+ prefix = "fe6b4e92-cc98-425e-b0aa-"
)
var (
- thing = things.Thing{Name: "test"}
- channel = things.Channel{Name: "test"}
+ thing = things.Thing{Name: "test"}
+ thingList = [n]things.Thing{}
+ channel = things.Channel{Name: "test"}
+ thsExtID = []things.Thing{{ID: prefix + "000000000001", Name: "a"}, {ID: prefix + "000000000002", Name: "b"}}
+ chsExtID = []things.Channel{{ID: prefix + "000000000001", Name: "a"}, {ID: prefix + "000000000002", Name: "b"}}
)
func newService(tokens map[string]string) things.Service {
- policies := []mocks.MockSubjectSet{{Object: "users", Relation: "member"}}
- auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{email: policies})
+ userPolicy := mocks.MockSubjectSet{Object: "users", Relation: "member"}
+ adminPolicy := mocks.MockSubjectSet{Object: "authorities", Relation: "member"}
+ auth := mocks.NewAuthService(tokens, map[string][]mocks.MockSubjectSet{
+ adminEmail: {userPolicy, adminPolicy}, email: {userPolicy}})
conns := make(chan mocks.Connection)
thingsRepo := mocks.NewThingRepository(conns)
channelsRepo := mocks.NewChannelRepository(thingsRepo, conns)
@@ -45,6 +52,14 @@ func newService(tokens map[string]string) things.Service {
return things.New(auth, thingsRepo, channelsRepo, chanCache, thingCache, idProvider)
}
+func TestInit(t *testing.T) {
+ for i := uint64(0); i < n; i++ {
+ thingList[i].Name = fmt.Sprintf("name-%d", i+1)
+ thingList[i].ID = fmt.Sprintf("%s%012d", prefix, i+1)
+ thingList[i].Key = fmt.Sprintf("%s1%011d", prefix, i+1)
+ }
+}
+
func TestCreateThings(t *testing.T) {
svc := newService(map[string]string{token: email})
@@ -66,6 +81,18 @@ func TestCreateThings(t *testing.T) {
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
+ {
+ desc: "create new things with external UUID",
+ things: thsExtID,
+ token: token,
+ err: nil,
+ },
+ {
+ desc: "create new things with external wrong UUID",
+ things: []things.Thing{{ID: "b0aa-000000000001", Name: "a"}, {ID: "b0aa-000000000002", Name: "b"}},
+ token: token,
+ err: nil,
+ },
}
for _, tc := range cases {
@@ -76,7 +103,7 @@ func TestCreateThings(t *testing.T) {
func TestUpdateThing(t *testing.T) {
svc := newService(map[string]string{token: email})
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
other := things.Thing{ID: wrongID, Key: "x"}
@@ -158,7 +185,7 @@ func TestUpdateKey(t *testing.T) {
func TestShareThing(t *testing.T) {
svc := newService(map[string]string{token: email, token2: email2})
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
policies := []string{"read"}
@@ -201,7 +228,7 @@ func TestShareThing(t *testing.T) {
thingID: th.ID,
policies: []string{"", "read"},
userIDs: []string{email2},
- err: fmt.Errorf("cannot claim ownership on thing '%s' by user '%s': %s", th.ID, email2, things.ErrMalformedEntity),
+ err: fmt.Errorf("cannot claim ownership on object '%s' by user '%s': %s", th.ID, email2, things.ErrMalformedEntity),
},
}
@@ -214,7 +241,7 @@ func TestShareThing(t *testing.T) {
func TestViewThing(t *testing.T) {
svc := newService(map[string]string{token: email})
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
@@ -251,12 +278,11 @@ func TestListThings(t *testing.T) {
m := make(map[string]interface{})
m["serial"] = "123456"
- thing.Metadata = m
+ thingList[0].Metadata = m
var ths []things.Thing
for i := uint64(0); i < n; i++ {
- th := thing
- th.Name = fmt.Sprintf("name-%d", i)
+ th := thingList[i]
ths = append(ths, th)
}
@@ -379,8 +405,7 @@ func TestListThingsByChannel(t *testing.T) {
var ths []things.Thing
for i := uint64(0); i < n; i++ {
- th := thing
- th.Name = fmt.Sprintf("name-%d", i)
+ th := thingList[i]
ths = append(ths, th)
}
@@ -552,7 +577,7 @@ func TestListThingsByChannel(t *testing.T) {
func TestRemoveThing(t *testing.T) {
svc := newService(map[string]string{token: email})
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
sth := ths[0]
@@ -615,6 +640,18 @@ func TestCreateChannels(t *testing.T) {
token: wrongValue,
err: things.ErrUnauthorizedAccess,
},
+ {
+ desc: "create new channels with external UUID",
+ channels: chsExtID,
+ token: token,
+ err: nil,
+ },
+ {
+ desc: "create new channels with invalid external UUID",
+ channels: []things.Channel{{ID: "b0aa-000000000001", Name: "a"}, {ID: "b0aa-000000000002", Name: "b"}},
+ token: token,
+ err: nil,
+ },
}
for _, cc := range cases {
@@ -624,7 +661,7 @@ func TestCreateChannels(t *testing.T) {
}
func TestUpdateChannel(t *testing.T) {
- svc := newService(map[string]string{token: email})
+ svc := newService(map[string]string{token: adminEmail})
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
ch := chs[0]
@@ -663,7 +700,7 @@ func TestUpdateChannel(t *testing.T) {
}
func TestViewChannel(t *testing.T) {
- svc := newService(map[string]string{token: email})
+ svc := newService(map[string]string{token: adminEmail})
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
ch := chs[0]
@@ -846,7 +883,7 @@ func TestListChannels(t *testing.T) {
func TestListChannelsByThing(t *testing.T) {
svc := newService(map[string]string{token: email})
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
th := ths[0]
@@ -1026,7 +1063,7 @@ func TestListChannelsByThing(t *testing.T) {
}
func TestRemoveChannel(t *testing.T) {
- svc := newService(map[string]string{token: email})
+ svc := newService(map[string]string{token: adminEmail})
chs, err := svc.CreateChannels(context.Background(), token, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
ch := chs[0]
@@ -1072,7 +1109,7 @@ func TestRemoveChannel(t *testing.T) {
func TestConnect(t *testing.T) {
svc := newService(map[string]string{token: email})
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
chs, err := svc.CreateChannels(context.Background(), token, channel)
@@ -1125,7 +1162,7 @@ func TestConnect(t *testing.T) {
func TestDisconnect(t *testing.T) {
svc := newService(map[string]string{token: email})
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
chs, err := svc.CreateChannels(context.Background(), token, channel)
@@ -1188,7 +1225,7 @@ func TestDisconnect(t *testing.T) {
func TestCanAccessByKey(t *testing.T) {
svc := newService(map[string]string{token: email})
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
chs, err := svc.CreateChannels(context.Background(), token, channel, channel)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
@@ -1231,7 +1268,7 @@ func TestCanAccessByKey(t *testing.T) {
func TestCanAccessByID(t *testing.T) {
svc := newService(map[string]string{token: email})
- ths, err := svc.CreateThings(context.Background(), token, thing, thing)
+ ths, err := svc.CreateThings(context.Background(), token, thingList[0], thingList[1])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
chs, err := svc.CreateChannels(context.Background(), token, channel)
@@ -1310,7 +1347,7 @@ func TestIsChannelOwner(t *testing.T) {
func TestIdentify(t *testing.T) {
svc := newService(map[string]string{token: email})
- ths, err := svc.CreateThings(context.Background(), token, thing)
+ ths, err := svc.CreateThings(context.Background(), token, thingList[0])
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
th := ths[0]
diff --git a/vendor/github.com/fxamacker/cbor/v2/.golangci.yml b/vendor/github.com/fxamacker/cbor/v2/.golangci.yml
index 04480426495..64a0f02da98 100644
--- a/vendor/github.com/fxamacker/cbor/v2/.golangci.yml
+++ b/vendor/github.com/fxamacker/cbor/v2/.golangci.yml
@@ -47,11 +47,9 @@ linters:
- gocyclo
- gofmt
- goimports
- - golint
- gosec
- govet
- ineffassign
- - maligned
- misspell
- staticcheck
- structcheck
@@ -59,7 +57,9 @@ linters:
- unconvert
- unused
- varcheck
-
+ # noisy linters such as gocritic are enabled by
+ # command line as optional linters inside
+ # .github/workflows/safer-golangci-lint.yml
issues:
# max-issues-per-linter default is 50. Set to 0 to disable limit.
diff --git a/vendor/github.com/fxamacker/cbor/v2/CBOR_GOLANG.md b/vendor/github.com/fxamacker/cbor/v2/CBOR_GOLANG.md
index c9360ca70da..5949d6171ce 100644
--- a/vendor/github.com/fxamacker/cbor/v2/CBOR_GOLANG.md
+++ b/vendor/github.com/fxamacker/cbor/v2/CBOR_GOLANG.md
@@ -1,7 +1,7 @@
👉 [Comparisons](https://github.com/fxamacker/cbor#comparisons) • [Status](https://github.com/fxamacker/cbor#current-status) • [Design Goals](https://github.com/fxamacker/cbor#design-goals) • [Features](https://github.com/fxamacker/cbor#features) • [Standards](https://github.com/fxamacker/cbor#standards) • [Fuzzing](https://github.com/fxamacker/cbor#fuzzing-and-code-coverage) • [Usage](https://github.com/fxamacker/cbor#usage) • [Security Policy](https://github.com/fxamacker/cbor#security-policy) • [License](https://github.com/fxamacker/cbor#license)
# CBOR
-[CBOR](https://en.wikipedia.org/wiki/CBOR) is a data format designed to allow small code size and small message size. CBOR is defined in [RFC 7049 Concise Binary Object Representation](https://tools.ietf.org/html/rfc7049), an [IETF](http://ietf.org/) Internet Standards Document.
+[CBOR](https://en.wikipedia.org/wiki/CBOR) is a data format designed to allow small code size and small message size. CBOR is defined in [RFC 8949 Concise Binary Object Representation](https://tools.ietf.org/html/rfc8949) (previously [RFC 7049](https://tools.ietf.org/html/rfc7049)), an [IETF](http://ietf.org/) Internet Standards Document.
CBOR is also designed to be stable for decades, be extensible without need for version negotiation, and not require a schema.
diff --git a/vendor/github.com/fxamacker/cbor/v2/LICENSE b/vendor/github.com/fxamacker/cbor/v2/LICENSE
index 8d9b736a488..eaa85049214 100644
--- a/vendor/github.com/fxamacker/cbor/v2/LICENSE
+++ b/vendor/github.com/fxamacker/cbor/v2/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019 - present Faye Amacker
+Copyright (c) 2019-present Faye Amacker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+SOFTWARE.
\ No newline at end of file
diff --git a/vendor/github.com/fxamacker/cbor/v2/README.md b/vendor/github.com/fxamacker/cbor/v2/README.md
index 09ac3351a07..7f513f8fb6a 100644
--- a/vendor/github.com/fxamacker/cbor/v2/README.md
+++ b/vendor/github.com/fxamacker/cbor/v2/README.md
@@ -1,115 +1,163 @@
-[![CBOR Library - Slideshow and Latest Docs.](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_slides.gif)](https://github.com/fxamacker/cbor/blob/master/README.md)
-
-# CBOR library in Go
-[__`fxamacker/cbor`__](https://github.com/fxamacker/cbor) is a CBOR encoder & decoder in [Go](https://golang.org). It has a standard API, CBOR tags, options for duplicate map keys, float64→32→16, `toarray`, `keyasint`, etc. Each release passes 375+ tests and 250+ million execs fuzzing.
+# fxamacker/cbor
[![](https://github.com/fxamacker/cbor/workflows/ci/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3Aci)
[![](https://github.com/fxamacker/cbor/workflows/cover%20%E2%89%A598%25/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3A%22cover+%E2%89%A598%25%22)
[![](https://github.com/fxamacker/cbor/workflows/linters/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3Alinters)
[![Go Report Card](https://goreportcard.com/badge/github.com/fxamacker/cbor)](https://goreportcard.com/report/github.com/fxamacker/cbor)
-[![Release](https://img.shields.io/github/release/fxamacker/cbor.svg?style=flat-square)](https://github.com/fxamacker/cbor/releases)
-[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/fxamacker/cbor/master/LICENSE)
+[![](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/release_version_badge.svg?sanitize=1)](https://github.com/fxamacker/cbor/releases)
+[![](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/license_badge.svg?sanitize=1)](https://raw.githubusercontent.com/fxamacker/cbor/master/LICENSE)
-__What is CBOR__? [CBOR](CBOR_GOLANG.md) ([RFC 7049](https://tools.ietf.org/html/rfc7049)) is a binary data format inspired by JSON and MessagePack. CBOR is used in [IETF](https://www.ietf.org) Internet Standards such as COSE ([RFC 8152](https://tools.ietf.org/html/rfc8152)) and CWT ([RFC 8392 CBOR Web Token](https://tools.ietf.org/html/rfc8392)). WebAuthn also uses CBOR.
+[__fxamacker/cbor__](https://github.com/fxamacker/cbor) is a CBOR library in [Go](https://golang.org). It's designed to be safe, fast, small, and easy to use.
-__`fxamacker/cbor`__ is safe and fast. It safely handles malformed CBOR data:
+Features include CBOR tags, duplicate map key detection, float64→32→16, Go struct tags (`toarray`, `keyasint`, `omitempty`), and a standard API. Each release passes hundreds of tests and 250+ million execs of coverage-guided fuzzing.
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_security_table.svg?sanitize=1 "CBOR Security Comparison")
+[CBOR](CBOR_GOLANG.md) ([RFC 7049](https://tools.ietf.org/html/rfc7049) & [RFC 8949](https://tools.ietf.org/html/rfc8949)) is a binary data format inspired by JSON and MessagePack. CBOR is an [Internet Standard](https://en.wikipedia.org/wiki/Internet_Standard) by [IETF](https://www.ietf.org) used in W3C [WebAuthn](https://en.wikipedia.org/wiki/WebAuthn), COSE ([RFC 8152](https://tools.ietf.org/html/rfc8152)), CWT ([RFC 8392 CBOR Web Token](https://tools.ietf.org/html/rfc8392)), and CDDL [(RFC 8610)](https://datatracker.ietf.org/doc/html/rfc8610).
-__`fxamacker/cbor`__ is fast when using CBOR data with Go structs:
+[CBOR Library Installation](https://github.com/x448/cbor/edit/patch-11/README.md#cbor-library-installation) shows how to install and begin using this CBOR encoder and decoder.
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_speed_table.svg?sanitize=1 "CBOR Speed Comparison")
+## CBOR Security
-Benchmarks used data from [RFC 8392 Appendix A.1](https://tools.ietf.org/html/rfc8392#appendix-A.1) and default options for each CBOR library.
+__fxamacker/cbor__ is secure. It rejects malformed CBOR data and can detect duplicate map keys. It doesn't crash when decoding bad CBOR data by having extensive tests, coverage-guided fuzzing, data validation, and avoiding Go's `unsafe` package.
-__`fxamacker/cbor`__ produces smaller binaries. All builds of cisco/senml had MessagePack feature removed:
+| | fxamacker/cbor (all versions) | ugorji/go (1.1.0 - 1.1.7) |
+| :--- | :------------------ | :--------------- |
+| **Malformed CBOR 1** | 87.5 ns/op, 24 B/op, 2 allocs/op | :boom: fatal error: out of memory |
+| **Malformed CBOR 2** | 89.5 ns/op, 24 B/op, 2 allocs/op | :boom: runtime: out of memory: cannot allocate |
+| | Correctly rejected bad data in all versions.
Benchmark is from latest release. | :warning: Just 1 decode of 9 bytes can exhaust memory. |
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_size_comparison.png "CBOR library and program size comparison chart")
+fxamacker/cbor CBOR safety settings include: MaxNestedLevels, MaxArrayElements, MaxMapPairs, and IndefLength.
-
+For more info, see:
+ - [RFC 8949 Section 10 (Security Considerations)](https://tools.ietf.org/html/rfc8949#section-10) or [RFC 7049 Section 8](https://tools.ietf.org/html/rfc7049#section-8).
+ - [Go warning](https://golang.org/pkg/unsafe/), "Packages that import unsafe may be non-portable and are not protected by the Go 1 compatibility guidelines."
-__Standard API__: functions with signatures identical to [`encoding/json`](https://golang.org/pkg/encoding/json/) include:
-`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, and `decoder.Decode`.
+## CBOR Performance
-__Standard interfaces__ allow custom encoding or decoding:
-`BinaryMarshaler`, `BinaryUnmarshaler`, `Marshaler`, and `Unmarshaler`.
+__fxamacker/cbor__ is fast without sacrificing security. It can be faster than libraries relying on `unsafe` package.
-__Struct tags__ like __`toarray`__ & __`keyasint`__ translate Go struct fields to CBOR array elements, etc.
+![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_speed_comparison.svg?sanitize=1 "CBOR speed comparison chart")
-
+__Click to expand:__
-[![CBOR API](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_api_struct_tags.png)](#usage)
+
+ 👉 CBOR Program Size Comparison
-
+__fxamacker/cbor__ produces smaller programs without sacrificing features.
+
+![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_size_comparison.svg?sanitize=1 "CBOR program size comparison chart")
-__`fxamacker/cbor`__ is a full-featured CBOR encoder and decoder. Support for CBOR includes:
+
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_features.svg?sanitize=1 "CBOR Features")
+ 👉 fxamacker/cbor 2.3.0 (safe) vs ugorji/go 1.2.6 (unsafe)
-
+fxamacker/cbor 2.3.0 (not using `unsafe`) is faster than ugorji/go 1.2.6 (using `unsafe`).
+
+```
+name old time/op new time/op delta
+DecodeCWTClaims-4 2.06µs ± 1% 1.25µs ± 0% -39.57% (p=0.000 n=10+9)
+DecodeCOSE/128-Bit_Symmetric_Key-4 1.47µs ± 1% 0.86µs ± 0% -41.25% (p=0.000 n=9+9)
+DecodeCOSE/256-Bit_Symmetric_Key-4 1.50µs ± 2% 0.88µs ± 0% -41.63% (p=0.000 n=10+10)
+DecodeCOSE/ECDSA_P256_256-Bit_Key-4 2.22µs ± 2% 1.45µs ± 0% -34.65% (p=0.000 n=10+10)
+DecodeWebAuthn-4 1.55µs ± 0% 1.32µs ± 0% -14.97% (p=0.000 n=9+10)
+EncodeCWTClaims-4 1.46µs ± 0% 0.78µs ± 0% -46.52% (p=0.000 n=10+10)
+EncodeCOSE/128-Bit_Symmetric_Key-4 1.79µs ± 1% 0.91µs ± 0% -49.38% (p=0.000 n=9+10)
+EncodeCOSE/256-Bit_Symmetric_Key-4 1.79µs ± 1% 0.91µs ± 0% -49.15% (p=0.000 n=10+10)
+EncodeCOSE/ECDSA_P256_256-Bit_Key-4 2.09µs ± 1% 1.14µs ± 0% -45.41% (p=0.000 n=10+10)
+EncodeWebAuthn-4 981ns ± 0% 823ns ± 1% -16.05% (p=0.000 n=10+10)
+
+name old alloc/op new alloc/op delta
+DecodeCWTClaims-4 760B ± 0% 176B ± 0% -76.84% (p=0.000 n=10+10)
+DecodeCOSE/128-Bit_Symmetric_Key-4 800B ± 0% 240B ± 0% -70.00% (p=0.000 n=10+10)
+DecodeCOSE/256-Bit_Symmetric_Key-4 816B ± 0% 256B ± 0% -68.63% (p=0.000 n=10+10)
+DecodeCOSE/ECDSA_P256_256-Bit_Key-4 913B ± 0% 352B ± 0% -61.45% (p=0.000 n=10+10)
+DecodeWebAuthn-4 1.56kB ± 0% 0.99kB ± 0% -36.41% (p=0.000 n=10+10)
+EncodeCWTClaims-4 1.36kB ± 0% 0.18kB ± 0% -87.06% (p=0.000 n=10+10)
+EncodeCOSE/128-Bit_Symmetric_Key-4 1.97kB ± 0% 0.22kB ± 0% -88.62% (p=0.000 n=10+10)
+EncodeCOSE/256-Bit_Symmetric_Key-4 1.97kB ± 0% 0.24kB ± 0% -87.80% (p=0.000 n=10+10)
+EncodeCOSE/ECDSA_P256_256-Bit_Key-4 1.97kB ± 0% 0.32kB ± 0% -83.74% (p=0.000 n=10+10)
+EncodeWebAuthn-4 1.31kB ± 0% 1.09kB ± 0% -17.07% (p=0.000 n=10+10)
+
+name old allocs/op new allocs/op delta
+DecodeCWTClaims-4 6.00 ± 0% 6.00 ± 0% ~ (all equal)
+DecodeCOSE/128-Bit_Symmetric_Key-4 4.00 ± 0% 4.00 ± 0% ~ (all equal)
+DecodeCOSE/256-Bit_Symmetric_Key-4 4.00 ± 0% 4.00 ± 0% ~ (all equal)
+DecodeCOSE/ECDSA_P256_256-Bit_Key-4 7.00 ± 0% 7.00 ± 0% ~ (all equal)
+DecodeWebAuthn-4 5.00 ± 0% 5.00 ± 0% ~ (all equal)
+EncodeCWTClaims-4 4.00 ± 0% 2.00 ± 0% -50.00% (p=0.000 n=10+10)
+EncodeCOSE/128-Bit_Symmetric_Key-4 6.00 ± 0% 2.00 ± 0% -66.67% (p=0.000 n=10+10)
+EncodeCOSE/256-Bit_Symmetric_Key-4 6.00 ± 0% 2.00 ± 0% -66.67% (p=0.000 n=10+10)
+EncodeCOSE/ECDSA_P256_256-Bit_Key-4 6.00 ± 0% 2.00 ± 0% -66.67% (p=0.000 n=10+10)
+EncodeWebAuthn-4 4.00 ± 0% 2.00 ± 0% -50.00% (p=0.000 n=10+10)
+```
+
-⚓ [__Installation__](#installation) • [__System Requirements__](#system-requirements) • [__Quick Start Guide__](#quick-start)
+Benchmarks used Go 1.15.12, linux_amd64 with data from [RFC 8392 Appendix A.1](https://tools.ietf.org/html/rfc8392#appendix-A.1). Default build options were used for all CBOR libraries. Library init code was put outside the benchmark loop for all libraries compared.
-
+## CBOR Library API
-__Why this CBOR library?__ It doesn't crash and it has well-balanced qualities: small, fast, safe and easy. It also has a standard API, CBOR tags (built-in and user-defined), float64→32→16, and duplicate map key options.
+__fxamacker/cbor__ is easy to use. It provides standard API and interfaces.
-* __Standard API__. Codec functions with signatures identical to [`encoding/json`](https://golang.org/pkg/encoding/json/) include:
-`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, and `decoder.Decode`.
+__Standard API__. Function signatures identical to [`encoding/json`](https://golang.org/pkg/encoding/json/) include:
+`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `(*Encoder).Encode`, and `(*Decoder).Decode`.
-* __Customizable__. Standard interfaces are provided to allow user-implemented encoding or decoding:
+__Standard Interfaces__. Custom encoding and decoding is handled by implementing:
`BinaryMarshaler`, `BinaryUnmarshaler`, `Marshaler`, and `Unmarshaler`.
-* __Small apps__. Same programs are 4-9 MB smaller by switching to this library. No code gen and the only imported pkg is [x448/float16](https://github.com/x448/float16) which is maintained by the same team as this library.
+__Predefined Encoding Options__. Encoding options are easy to use and are customizable.
+
+```go
+func CanonicalEncOptions() EncOptions {} // RFC 7049 Canonical CBOR
+func CTAP2EncOptions() EncOptions {} // FIDO2 CTAP2 Canonical CBOR
+func CoreDetEncOptions() EncOptions {} // RFC 8949 Core Deterministic Encoding
+func PreferredUnsortedEncOptions() EncOptions {} // RFC 8949 Preferred Serialization
+```
-* __Small data__. The `toarray`, `keyasint`, and `omitempty` struct tags shrink size of Go structs encoded to CBOR. Integers encode to smallest form that fits. Floats can shrink from float64 -> float32 -> float16 if values fit.
+fxamacker/cbor designed to simplify concurrency. CBOR options can be used without creating unintended runtime side-effects.
-* __Fast__. v1.3 became faster than a well-known library that uses `unsafe` optimizations and code gen. Faster libraries will always exist, but speed is only one factor. This library doesn't use `unsafe` optimizations or code gen.
+## Go Struct Tags
-* __Safe__ and reliable. It prevents crashes on malicious CBOR data by using extensive tests, coverage-guided fuzzing, data validation, and avoiding Go's [`unsafe`](https://golang.org/pkg/unsafe/) pkg. Decoder settings include: `MaxNestedLevels`, `MaxArrayElements`, `MaxMapPairs`, and `IndefLength`.
+__fxamacker/cbor__ provides Go struct tags like __`toarray`__ and __`keyasint`__ to save time and reduce encoded size of data.
-* __Easy__ and saves time. Simple (no param) functions return preset `EncOptions` so you don't have to know the differences between Canonical CBOR and CTAP2 Canonical CBOR to use those standards.
+
-💡 Struct tags are a Go language feature. CBOR tags relate to a CBOR data type (major type 6).
+![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_struct_tags_api.svg?sanitize=1 "CBOR API and Go Struct Tags")
-Struct tags for CBOR and JSON like `` `cbor:"name,omitempty"` `` and `` `json:"name,omitempty"` `` are supported so you can leverage your existing code. If both `cbor:` and `json:` tags exist then it will use `cbor:`.
+## CBOR Features
-New struct tags like __`keyasint`__ and __`toarray`__ make compact CBOR data such as COSE, CWT, and SenML easier to use.
+__fxamacker/cbor__ is a full-featured CBOR encoder and decoder.
-⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+| | CBOR Feature | Description |
+| :--- | :--- | :--- |
+| ☑️ | CBOR tags | API supports built-in and user-defined tags. |
+| ☑️ | Preferred serialization | Integers encode to fewest bytes. Optional float64 → float32 → float16. |
+| ☑️ | Map key sorting | Unsorted, length-first (Canonical CBOR), and bytewise-lexicographic (CTAP2). |
+| ☑️ | Duplicate map keys | Always forbid for encoding and option to allow/forbid for decoding. |
+| ☑️ | Indefinite length data | Option to allow/forbid for encoding and decoding. |
+| ☑️ | Well-formedness | Always checked and enforced. |
+| ☑️ | Basic validity checks | Check UTF-8 validity and optionally check duplicate map keys. |
+| ☑️ | Security considerations | Prevent integer overflow and resource exhaustion (RFC 8949 Section 10). |
-## Installation
+## CBOR Library Installation
-👉 If Go modules aren't used, delete or modify example_test.go
-from `"github.com/fxamacker/cbor/v2"` to `"github.com/fxamacker/cbor"`
+fxamacker/cbor supports Go 1.12 and newer versions. Init the Go module, go get v2, and begin coding.
-Using Go modules is recommended.
```
-$ GO111MODULE=on go get github.com/fxamacker/cbor/v2
+go mod init github.com/my_name/my_repo
+go get github.com/fxamacker/cbor/v2
```
```go
-import (
- "github.com/fxamacker/cbor/v2" // imports as package "cbor"
-)
+import "github.com/fxamacker/cbor/v2" // imports as cbor
```
-[Released versions](https://github.com/fxamacker/cbor/releases) benefit from longer fuzz tests.
-
-## System Requirements
-
-Using Go modules is recommended but not required.
-
-* Go 1.12 (or newer).
-* amd64, arm64, ppc64le and s390x. Other architectures may also work but they are not tested as frequently.
-
-If Go modules feature isn't used, please see [Installation](#installation) about deleting or modifying example_test.go.
-
## Quick Start
🛡️ Use Go's `io.LimitReader` to limit size when decoding very large or indefinite size data.
+Import using "/v2" like this: `import "github.com/fxamacker/cbor/v2"`, and
+it will import version 2.x as package "cbor" (when using Go modules).
+
Functions with identical signatures to encoding/json include:
-`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, `decoder.Decode`.
+`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `(*Encoder).Encode`, `(*Decoder).Decode`.
__Default Mode__
@@ -117,11 +165,8 @@ If default options are acceptable, package level functions can be used for encod
```go
b, err := cbor.Marshal(v) // encode v to []byte b
-
err := cbor.Unmarshal(b, &v) // decode []byte b to v
-
encoder := cbor.NewEncoder(w) // create encoder with io.Writer w
-
decoder := cbor.NewDecoder(r) // create decoder with io.Reader r
```
@@ -140,7 +185,7 @@ __Creating and Using Encoding Modes__
💡 Avoid using init(). For best performance, reuse EncMode and DecMode after creating them.
-Most apps will probably create one EncMode and DecMode before init(). However, there's no limit and each can use different options.
+Most apps will probably create one EncMode and DecMode before init(). There's no limit and each can use different options.
```go
// Create EncOptions using either struct literal or a function.
@@ -158,6 +203,8 @@ encoder := em.NewEncoder(w) // create encoder with io.Writer w
err := encoder.Encode(v) // encode v to io.Writer w
```
+Both `em.Marshal(v)` and `encoder.Encode(v)` use encoding options specified during creation of encoding mode `em`.
+
__Creating Modes With CBOR Tags__
A TagSet is used to specify CBOR tags.
@@ -173,10 +220,10 @@ TagSet and all modes using it are safe for concurrent use. Equivalent API is av
__Predefined Encoding Options__
```go
-func CanonicalEncOptions() EncOptions {} // settings for RFC 7049 Canonical CBOR
-func CTAP2EncOptions() EncOptions {} // settings for FIDO2 CTAP2 Canonical CBOR
-func CoreDetEncOptions() EncOptions {} // settings from a draft RFC (subject to change)
-func PreferredUnsortedEncOptions() EncOptions {} // settings from a draft RFC (subject to change)
+func CanonicalEncOptions() EncOptions {} // RFC 7049 Canonical CBOR
+func CTAP2EncOptions() EncOptions {} // FIDO2 CTAP2 Canonical CBOR
+func CoreDetEncOptions() EncOptions {} // RFC 8949 Core Deterministic Encoding
+func PreferredUnsortedEncOptions() EncOptions {} // RFC 8949 Preferred Serialization
```
The empty curly braces prevent a syntax highlighting bug on GitHub, please ignore them.
@@ -185,49 +232,29 @@ __Struct Tags (keyasint, toarray, omitempty)__
The `keyasint`, `toarray`, and `omitempty` struct tags make it easy to use compact CBOR message formats. Internet standards often use CBOR arrays and CBOR maps with int keys to save space.
-__More Info About API, Options, and Usage__
+The following sections provide more info:
-Options are listed in the Features section: [Encoding Options](#encoding-options) and [Decoding Options](#decoding-options)
-
-For more details about each setting, see [Options](#options) section.
-
-For additional API and usage examples, see [API](#api) and [Usage](#usage) sections.
+* [Struct Tags](#struct-tags-1)
+* [Decoding Options](#decoding-options)
+* [Encoding Options](#encoding-options)
+* [API](#api)
+* [Usage](#usage)
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
## Current Status
-Latest version is v2.x, which has:
+Latest version is v2.3 (May 30, 2021), which has:
-* __Stable API__ – Six codec function signatures will never change. No breaking API changes for other funcs in same major version. And these two functions are subject to change until the draft RFC is approved by IETF (est. in 2020):
- * CoreDetEncOptions() is subject to change because it uses draft standard.
- * PreferredUnsortedEncOptions() is subject to change because it uses draft standard.
+* __Stable API__ – Six codec function signatures will never change. No breaking API changes for other funcs in same major version.
* __Passed all tests__ – v2.x passed all 375+ tests on amd64, arm64, ppc64le and s390x with linux.
-* __Passed fuzzing__ – v2.2 passed 459+ million execs in coverage-guided fuzzing on Feb 24, 2020 (still fuzzing.)
-
-__Why v2.x?__:
-
-v1 required breaking API changes to support new features like CBOR tags, detection of duplicate map keys, and having more functions with identical signatures to `encoding/json`.
-
-v2.1 is roughly 26% faster and uses 57% fewer allocs than v1.x when decoding COSE and CWT using default options.
-
-__Recent Activity__:
-
-* Release v2.1 (Feb. 17, 2020)
- - [x] CBOR tags (major type 6) for encoding and decoding.
- - [x] Decoding options for duplicate map key detection: `DupMapKeyQuiet` (default) and `DupMapKeyEnforcedAPF`
- - [x] Decoding optimizations. Structs using keyasint tag (like COSE and CWT) is
- 24-28% faster and 53-61% fewer allocs than both v1.5 and v2.0.1.
-
-* Release v2.2 (Feb. 24, 2020)
- - [x] CBOR BSTR <--> Go byte array (byte slices were already supported)
- - [x] Add more encoding and decoding options (MaxNestedLevels, MaxArrayElements, MaxMapKeyPairs, TagsMd, etc.)
- - [x] Fix potential error when decoding shorter CBOR indef length array to Go array (slice wasn't affected). This bug affects all prior versions of 1.x and 2.x.
+* __Passed fuzzing__ – v2.2 passed 459+ million execs in coverage-guided fuzzing on Feb 24, 2020 (release date)
+and 3.2+ billion execs on March 7, 2020. v2.3 passed 357+ million execs on May 30, 2021 (and is continuing to fuzz).
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
## Design Goals
This library is designed to be a generic CBOR encoder and decoder. It was initially created for a [WebAuthn (FIDO2) server library](https://github.com/fxamacker/webauthn), because existing CBOR libraries (in Go) didn't meet certain criteria in 2019.
@@ -254,7 +281,16 @@ __Click to expand topic:__
Supported CBOR Features (Highlights)
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_features.svg?sanitize=1 "CBOR Features")
+| | CBOR Feature | Description |
+| :--- | :--- | :--- |
+| ☑️ | CBOR tags | API supports built-in and user-defined tags. |
+| ☑️ | Preferred serialization | Integers encode to fewest bytes. Optional float64 → float32 → float16. |
+| ☑️ | Map key sorting | Unsorted, length-first (Canonical CBOR), and bytewise-lexicographic (CTAP2). |
+| ☑️ | Duplicate map keys | Always forbid for encoding and option to allow/forbid for decoding. |
+| ☑️ | Indefinite length data | Option to allow/forbid for encoding and decoding. |
+| ☑️ | Well-formedness | Always checked and enforced. |
+| ☑️ | Basic validity checks | Check UTF-8 validity and optionally check duplicate map keys. |
+| ☑️ | Security considerations | Prevent integer overflow and resource exhaustion (RFC 8949 Section 10). |
@@ -275,14 +311,14 @@ Features not in Go's standard library are usually not added. However, the __`to
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
## Features
### Standard API
Many function signatures are identical to encoding/json, including:
-`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, `decoder.Decode`.
+`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `(*Encoder).Encode`, `(*Decoder).Decode`.
`RawMessage` can be used to delay CBOR decoding or precompute CBOR encoding, like `encoding/json`.
@@ -293,13 +329,15 @@ Standard interfaces allow user-defined types to have custom CBOR encoding and de
### Struct Tags
-Support "cbor" and "json" keys in Go's struct tags. If both are specified, then "cbor" is used.
+Support "cbor" and "json" keys in Go's struct tags. If both are specified for the same field, then "cbor" is used.
-* `toarray` struct tag allows named struct fields for elements of CBOR arrays.
-* `keyasint` struct tag allows named struct fields for elements of CBOR maps with int keys.
-* `omitempty` struct tag excludes empty field values from being encoded.
+* a different field name can be specified, like encoding/json.
+* `omitempty` omits (ignores) field if value is empty, like encoding/json.
+* `-` always omits (ignores) field, like encoding/json.
+* `keyasint` treats fields as elements of CBOR maps with specified int key.
+* `toarray` treats fields as elements of CBOR arrays.
-See [Usage](#usage).
+See [Struct Tags](#struct-tags-1) for more info.
### CBOR Tags (New in v2.1)
@@ -333,30 +371,30 @@ Integers always encode to the shortest form that preserves value. By default, t
Encoding of other data types and map key sort order are determined by encoder options.
-| Encoding Option | Available Settings (defaults in bold, aliases in italics) |
-| --------------- | --------------------------------------------------------- |
-| EncOptions.Sort | __`SortNone`__, `SortLengthFirst`, `SortBytewiseLexical`, _`SortCanonical`_, _`SortCTAP2`_, _`SortCoreDeterministic`_ |
-| EncOptions.Time | __`TimeUnix`__, `TimeUnixMicro`, `TimeUnixDynamic`, `TimeRFC3339`, `TimeRFC3339Nano` |
-| EncOptions.TimeTag | __`EncTagNone`__, `EncTagRequired` |
-| EncOptions.ShortestFloat | __`ShortestFloatNone`__, `ShortestFloat16` |
-| EncOptions.InfConvert | __`InfConvertFloat16`__, `InfConvertNone` |
-| EncOptions.NaNConvert | __`NaNConvert7e00`__, `NaNConvertNone`, `NaNConvertQuiet`, `NaNConvertPreserveSignal` |
-| EncOptions.IndefLength | __`IndefLengthAllowed`__, `IndefLengthForbidden` |
-| EncOptions.TagsMd | __`TagsAllowed`__, `TagsForbidden` |
+| EncOptions | Available Settings (defaults listed first)
+| :--- | :--- |
+| Sort | [**SortNone**, SortLengthFirst, SortBytewiseLexical
Aliases: SortCanonical, SortCTAP2, SortCoreDeterministic |
+| Time | [**TimeUnix**, TimeUnixMicro, TimeUnixDynamic, TimeRFC3339, TimeRFC3339Nano |
+| TimeTag | [**EncTagNone**, EncTagRequired |
+| ShortestFloat | [**ShortestFloatNone**, ShortestFloat16 |
+| InfConvert | [**InfConvertFloat16**, InfConvertNone |
+| NaNConvert | [**NaNConvert7e00**, NaNConvertNone, NaNConvertQuiet, NaNConvertPreserveSignal |
+| IndefLength | **IndefLengthAllowed**, IndefLengthForbidden |
+| TagsMd | **TagsAllowed**, TagsForbidden |
See [Options](#options) section for details about each setting.
### Decoding Options
-| Decoding Option | Available Settings (defaults in bold, aliases in italics) |
-| --------------- | --------------------------------------------------------- |
-| DecOptions.TimeTag | __`DecTagIgnored`__, `DecTagOptional`, `DecTagRequired` |
-| DecOptions.DupMapKey | __`DupMapKeyQuiet`__, `DupMapKeyEnforcedAPF` |
-| DecOptions.IndefLength | __`IndefLengthAllowed`__, `IndefLengthForbidden` |
-| DecOptions.TagsMd | __`TagsAllowed`__, `TagsForbidden` |
-| DecOptions.MaxNestedLevels | __32__, can be set to [4, 256] |
-| DecOptions.MaxArrayElements | __131072__, can be set to [16, 134217728] |
-| DecOptions.MaxMapPairs | __131072__, can be set to [16, 134217728] |
+| DecOptions | Available Settings (defaults listed first) |
+| :--- | :--- |
+| TimeTag | **DecTagIgnored**, DecTagOptional, DecTagRequired |
+| DupMapKey | **DupMapKeyQuiet**, DupMapKeyEnforcedAPF |
+| IndefLength | **IndefLengthAllowed**, IndefLengthForbidden |
+| TagsMd | **TagsAllowed**, TagsForbidden |
+| MaxNestedLevels | **32**, can be set to [4, 256] |
+| MaxArrayElements | **131072**, can be set to [16, 134217728] |
+| MaxMapPairs | **131072**, can be set to [16, 134217728] |
See [Options](#options) section for details about each setting.
@@ -370,12 +408,21 @@ See [Options](#options) section for details about each setting.
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
## Standards
This library is a full-featured generic CBOR [(RFC 7049)](https://tools.ietf.org/html/rfc7049) encoder and decoder. Notable CBOR features include:
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_features.svg?sanitize=1 "CBOR Features")
+| | CBOR Feature | Description |
+| :--- | :--- | :--- |
+| ☑️ | CBOR tags | API supports built-in and user-defined tags. |
+| ☑️ | Preferred serialization | Integers encode to fewest bytes. Optional float64 → float32 → float16. |
+| ☑️ | Map key sorting | Unsorted, length-first (Canonical CBOR), and bytewise-lexicographic (CTAP2). |
+| ☑️ | Duplicate map keys | Always forbid for encoding and option to allow/forbid for decoding. |
+| ☑️ | Indefinite length data | Option to allow/forbid for encoding and decoding. |
+| ☑️ | Well-formedness | Always checked and enforced. |
+| ☑️ | Basic validity checks | Check UTF-8 validity and optionally check duplicate map keys. |
+| ☑️ | Security considerations | Prevent integer overflow and resource exhaustion (RFC 8949 Section 10). |
See the Features section for list of [Encoding Options](#encoding-options) and [Decoding Options](#decoding-options).
@@ -392,6 +439,8 @@ After well-formedness is verified, basic validity errors are handled as follows:
When decoding well-formed CBOR arrays and maps, decoder saves the first error it encounters and continues with the next item. Options to handle this differently may be added in the future.
+By default, decoder treats time values of floating-point NaN and Infinity as if they are CBOR Null or CBOR Undefined.
+
See [Options](#options) section for detailed settings or [Features](#features) section for a summary of options.
__Click to expand topic:__
@@ -409,22 +458,40 @@ APF suffix means "Allow Partial Fill" so the destination map or struct can conta
+
+ Tag Validity
+
+This library checks tag validity for built-in tags (currently tag numbers 0 and 1):
+
+* Inadmissible type for tag content
+* Inadmissible value for tag content
+
+Unknown tag data items (not tag number 0 or 1) are handled in two ways:
+
+* When decoding into an empty interface, unknown tag data item will be decoded into `cbor.Tag` data type, which contains tag number and tag content. The tag content will be decoded into the default Go data type for the CBOR data type.
+* When decoding into other Go types, unknown tag data item is decoded into the specified Go type. If Go type is registered with a tag number, the tag number can optionally be verified.
+
+Decoder also has an option to forbid tag data items (treat any tag data item as error) which is specified by protocols such as CTAP2 Canonical CBOR.
+
+For more information, see [decoding options](#decoding-options-1) and [tag options](#tag-options).
+
+
+
## Limitations
If any of these limitations prevent you from using this library, please open an issue along with a link to your project.
-* CBOR negative int (type 1) that cannot fit into Go's int64 are not supported, such as RFC 7049 example -18446744073709551616. Decoding these values returns `cbor.UnmarshalTypeError` like Go's `encoding/json`. However, this may be resolved in a future release by adding support for `big.Int`. Until then, users can use the API for custom encoding and decoding.
* CBOR `Undefined` (0xf7) value decodes to Go's `nil` value. CBOR `Null` (0xf6) more closely matches Go's `nil`.
* CBOR map keys with data types not supported by Go for map keys are ignored and an error is returned after continuing to decode remaining items.
* When using io.Reader interface to read very large or indefinite length CBOR data, Go's `io.LimitReader` should be used to limit size.
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
## API
Many function signatures are identical to Go's encoding/json, such as:
-`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, and `decoder.Decode`.
+`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `(*Encoder).Encode`, and `(*Decoder).Decode`.
Interfaces identical or comparable to Go's encoding, encoding/json, or encoding/gob include:
`Marshaler`, `Unmarshaler`, `BinaryMarshaler`, and `BinaryUnmarshaler`.
@@ -480,10 +547,10 @@ The empty curly braces prevent a syntax highlighting bug, please ignore them.
__API for Predefined Encoding Options__
```go
-func CanonicalEncOptions() EncOptions {} // settings for RFC 7049 Canonical CBOR
-func CTAP2EncOptions() EncOptions {} // settings for FIDO2 CTAP2 Canonical CBOR
-func CoreDetEncOptions() EncOptions {} // settings from a draft RFC (subject to change)
-func PreferredUnsortedEncOptions() EncOptions {} // settings from a draft RFC (subject to change)
+func CanonicalEncOptions() EncOptions {} // RFC 7049 Canonical CBOR
+func CTAP2EncOptions() EncOptions {} // FIDO2 CTAP2 Canonical CBOR
+func CoreDetEncOptions() EncOptions {} // RFC 8949 Core Deterministic Encoding
+func PreferredUnsortedEncOptions() EncOptions {} // RFC 8949 Preferred Serialization
```
__API for Creating & Using Decoding Modes__
@@ -547,11 +614,55 @@ See [API docs (godoc.org)](https://godoc.org/github.com/fxamacker/cbor) for more
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
## Options
-Options for the decoding and encoding are listed here.
+Struct tags, decoding options, and encoding options.
+
+### Struct Tags
+
+This library supports both "cbor" and "json" key for some (not all) struct tags. If "cbor" and "json" keys are both present for the same field, then "cbor" key will be used.
+
+| Key | Format Str | Scope | Description |
+| --- | ---------- | ----- | ------------|
+| cbor or json | "myName" | field | Name of field to use such as "myName", etc. like encoding/json. |
+| cbor or json | ",omitempty" | field | Omit (ignore) this field if value is empty, like encoding/json. |
+| cbor or json | "-" | field | Omit (ignore) this field always, like encoding/json. |
+| cbor | ",keyasint" | field | Treat field as an element of CBOR map with specified int as key. |
+| cbor | ",toarray" | struct | Treat each field as an element of CBOR array. This automatically disables "omitempty" and "keyasint" for all fields in the struct. |
+
+The "keyasint" struct tag requires an integer key to be specified:
+
+```
+type myStruct struct {
+ MyField int64 `cbor:-1,keyasint,omitempty`
+ OurField string `cbor:0,keyasint,omitempty`
+ FooField Foo `cbor:5,keyasint,omitempty`
+ BarField Bar `cbor:hello,omitempty`
+ ...
+}
+```
+
+The "toarray" struct tag requires a special field "_" (underscore) to indicate "toarray" applies to the entire struct:
+
+```
+type myStruct struct {
+ _ struct{} `cbor:",toarray"`
+ MyField int64
+ OurField string
+ ...
+}
+```
+
+__Click to expand:__
+
+
+ Example Using CBOR Web Tokens
+
+![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_struct_tags_api.svg?sanitize=1 "CBOR API and Go Struct Tags")
+
+
### Decoding Options
@@ -561,7 +672,17 @@ Options for the decoding and encoding are listed here.
| DecTagOptional | Tag numbers are only checked for validity if present for time values. |
| DecTagRequired | Tag numbers must be provided for time values except for CBOR Null and CBOR Undefined. |
-CBOR Null and CBOR Undefined are silently treated as Go's zero time instant. Go's `time` package provides `IsZero` function, which reports whether t represents the zero time instant, January 1, year 1, 00:00:00 UTC.
+The following CBOR time values are decoded as Go's "zero time instant":
+
+* CBOR Null
+* CBOR Undefined
+* CBOR floating-point NaN
+* CBOR floating-point Infinity
+
+Go's `time` package provides `IsZero` function, which reports whether t represents "zero time instant"
+(January 1, year 1, 00:00:00 UTC).
+
+
| DecOptions.DupMapKey | Description |
| -------------------- | ----------- |
@@ -570,27 +691,37 @@ CBOR Null and CBOR Undefined are silently treated as Go's zero time instant. Go
`DupMapKeyEnforcedAPF` uses "Allow Partial Fill" so the destination map or struct can contain some decoded values at the time of error. Users can respond to the `DupMapKeyError` by discarding the partially filled result if that's required by their protocol.
+
+
| DecOptions.IndefLength | Description |
| ---------------------- | ----------- |
|IndefLengthAllowed (default) | allow indefinite length data |
|IndefLengthForbidden | forbid indefinite length data |
+
+
| DecOptions.TagsMd | Description |
| ----------------- | ----------- |
|TagsAllowed (default) | allow CBOR tags (major type 6) |
|TagsForbidden | forbid CBOR tags (major type 6) |
+
+
| DecOptions.MaxNestedLevels | Description |
| -------------------------- | ----------- |
| 32 (default) | allowed setting is [4, 256] |
+
+
| DecOptions.MaxArrayElements | Description |
| --------------------------- | ----------- |
-| 131072 (default) | allowed setting is [16, 134217728] |
+| 131072 (default) | allowed setting is [16, 2147483647] |
+
+
| DecOptions.MaxMapPairs | Description |
| ---------------------- | ----------- |
-| 131072 (default) | allowed setting is [16, 134217728] |
+| 131072 (default) | allowed setting is [16, 2147483647] |
### Encoding Options
@@ -605,7 +736,7 @@ These functions are provided to create and return a modifiable EncOptions struct
| PreferredUnsortedEncOptions() |Unsorted, encode float64->float32->float16 when values fit, NaN values encoded as float16 0x7e00. |
| CoreDetEncOptions() |PreferredUnsortedEncOptions() + map keys are sorted bytewise lexicographic. |
-🌱 CoreDetEncOptions() and PreferredUnsortedEncOptions() are subject to change until the draft RFC they used is approved by IETF.
+
| EncOptions.Sort | Description |
| --------------- | ----------- |
@@ -616,6 +747,8 @@ These functions are provided to create and return a modifiable EncOptions struct
| SortCTAP2 |(alias) Same as SortBytewiseLexical [(CTAP2 Canonical CBOR)](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#ctap2-canonical-cbor-encoding-form). |
| SortCoreDeterministic |(alias) Same as SortBytewiseLexical. |
+
+
| EncOptions.Time | Description |
| --------------- | ----------- |
| TimeUnix (default) | (seconds) Encode as integer. |
@@ -624,6 +757,8 @@ These functions are provided to create and return a modifiable EncOptions struct
| TimeRFC3339 | (seconds) Encode as RFC 3339 formatted string. |
| TimeRFC3339Nano | (nanoseconds) Encode as RFC3339 formatted string. |
+
+
| EncOptions.TimeTag | Description |
| ------------------ | ----------- |
| EncTagNone (default) | Tag number will not be encoded for time values. |
@@ -635,6 +770,8 @@ By default, undefined (zero instant) time values will encode as CBOR Null withou
Go's `time` package provides `IsZero` function, which reports whether t represents the zero time instant, January 1, year 1, 00:00:00 UTC.
+
+
__Floating-Point Options__
Encoder has 3 types of options for floating-point data: ShortestFloatMode, InfConvertMode, and NaNConvertMode.
@@ -651,6 +788,8 @@ Conversions for infinity and NaN use InfConvert and NaNConvert settings.
| InfConvertFloat16 (default) | Convert +- infinity to float16 since they always preserve value (recommended) |
| InfConvertNone |Don't convert +- infinity to other representations -- used by CTAP2 Canonical CBOR |
+
+
| EncOptions.NaNConvert | Description |
| --------------------- | ----------- |
| NaNConvert7e00 (default) | Encode to 0xf97e00 (CBOR float16 = 0x7e00) -- used by RFC 7049 Canonical CBOR. |
@@ -658,25 +797,47 @@ Conversions for infinity and NaN use InfConvert and NaNConvert settings.
| NaNConvertQuiet | Force quiet bit = 1 and use shortest form that preserves NaN payload. |
| NaNConvertPreserveSignal | Convert to smallest form that preserves value (quit bit unmodified and NaN payload preserved). |
+
+
| EncOptions.IndefLength | Description |
| ---------------------- | ----------- |
|IndefLengthAllowed (default) | allow indefinite length data |
|IndefLengthForbidden | forbid indefinite length data |
+
+
| EncOptions.TagsMd | Description |
| ----------------- | ----------- |
|TagsAllowed (default) | allow CBOR tags (major type 6) |
|TagsForbidden | forbid CBOR tags (major type 6) |
+
+### Tag Options
+
+TagOptions specifies how encoder and decoder handle tag number registered with TagSet.
+
+| TagOptions.DecTag | Description |
+| ------------------ | ----------- |
+| DecTagIgnored (default) | Tag numbers are ignored (if present). |
+| DecTagOptional | Tag numbers are only checked for validity if present. |
+| DecTagRequired | Tag numbers must be provided except for CBOR Null and CBOR Undefined. |
+
+
+
+| TagOptions.EncTag | Description |
+| ------------------ | ----------- |
+| EncTagNone (default) | Tag number will not be encoded. |
+| EncTagRequired | Tag number will be encoded. |
+
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
## Usage
🛡️ Use Go's `io.LimitReader` to limit size when decoding very large or indefinite size data.
Functions with identical signatures to encoding/json include:
-`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, `decoder.Decode`.
+`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `(*Encoder).Encode`, `(*Decoder).Decode`.
__Default Mode__
@@ -729,7 +890,7 @@ The `keyasint`, `toarray`, and `omitempty` struct tags make it easy to use compa
-[![CBOR API](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_api_struct_tags.png)](#usage)
+![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_struct_tags_api.svg?sanitize=1 "CBOR API and Struct Tags")
@@ -807,7 +968,7 @@ For more examples, see [examples_test.go](example_test.go).
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
## Comparisons
@@ -815,27 +976,33 @@ Comparisons are between this newer library and a well-known library that had 1,0
__This library is safer__. Small malicious CBOR messages are rejected quickly before they exhaust system resources.
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_security_table.svg?sanitize=1 "CBOR Security Comparison")
+| | **fxamacker/cbor (1.0 - 2.x)** | **ugorji/go (1.1.0 - 1.1.7)** |
+| :--- | :------------------ | :--------------- |
+| **Malformed CBOR 1** | 59.8 ns/op, 32 B/op, 1 allocs/op | :boom: fatal error: out of memory |
+| **Malformed CBOR 2** | 149 ns/op, 128 B/op, 3 allocs/op | :boom: runtime: out of memory: cannot allocate |
+| | Correctly rejected bad data. | :warning: Only 1 decode < 10 bytes produces fatal error. |
__This library is smaller__. Programs like senmlCat can be 4 MB smaller by switching to this library. Programs using more complex CBOR data types can be 9.2 MB smaller.
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_size_comparison.png "CBOR library and program size comparison chart")
+![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_size_comparison.svg?sanitize=1 "CBOR speed comparison chart")
-__This library is faster__ for encoding and decoding CBOR Web Token (CWT). However, speed is only one factor and it can vary depending on data types and sizes. Unlike the other library, this one doesn't use Go's ```unsafe``` package or code gen.
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_speed_comparison.png "CBOR library speed comparison chart")
+__This library is faster__ for encoding and decoding CBOR Web Token (CWT). However, speed is only one factor and it can vary depending on data types and sizes. Unlike the other library, this one doesn't use Go's ```unsafe``` package or code gen.
-The resource intensive `codec.CborHandle` initialization (in the other library) was placed outside the benchmark loop to make sure their library wasn't penalized.
+![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_speed_comparison.svg?sanitize=1 "CBOR speed comparison chart")
__This library uses less memory__ for encoding and decoding CBOR Web Token (CWT) using test data from RFC 8392 A.1.
-![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.2.0/cbor_memory_table.svg?sanitize=1 "CBOR Speed Comparison")
+| | fxamacker/cbor 2.2 | ugorji/go 1.1.7 |
+| :--- | :--- | :--- |
+| Encode CWT | 176 bytes/op 2 allocs/op | 1424 bytes/op 4 allocs/op |
+| Decode CWT | 176 bytes/op 6 allocs/op | 568 bytes/op 6 allocs/op |
Doing your own comparisons is highly recommended. Use your most common message sizes and data types.
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
## Benchmarks
@@ -878,12 +1045,13 @@ To prevent excessive delays, fuzzing is not restarted for a release if changes a
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
## Versions and API Changes
This project uses [Semantic Versioning](https://semver.org), so the API is always backwards compatible unless the major version number changes.
-These functions have signatures identical to encoding/json and they will likely never change even after major new releases: `Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `encoder.Encode`, and `decoder.Decode`.
+These functions have signatures identical to encoding/json and they will likely never change even after major new releases:
+`Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder`, `(*Encoder).Encode`, and `(*Decoder).Decode`.
Newly added API documented as "subject to change" are excluded from SemVer.
@@ -896,12 +1064,12 @@ This project has adopted the [Contributor Covenant Code of Conduct](CODE_OF_COND
Please refer to [How to Contribute](CONTRIBUTING.md).
## Security Policy
-Security fixes are provided for the latest released version.
+Security fixes are provided for the latest released version of fxamacker/cbor.
-To report security vulnerabilities, please email [faye.github@gmail.com](mailto:faye.github@gmail.com) and allow time for the problem to be resolved before reporting it to the public.
+For the full text of the Security Policy, see [SECURITY.md](SECURITY.md).
## Disclaimers
-Phrases like "no crashes" or "doesn't crash" mean there are no known crash bugs in the latest version based on results of unit tests and coverage-guided fuzzing. It doesn't imply the software is 100% bug-free or 100% invulnerable to all known and unknown attacks.
+Phrases like "no crashes", "doesn't crash", and "is secure" mean there are no known crash bugs in the latest version based on results of unit tests and coverage-guided fuzzing. They don't imply the software is 100% bug-free or 100% invulnerable to all known and unknown attacks.
Please read the license for additional disclaimers and terms.
@@ -909,7 +1077,6 @@ Please read the license for additional disclaimers and terms.
__Making this library better__
-* Montgomery Edwards⁴⁴⁸ for [x448/float16](https://github.com/x448/float16), updating the docs, creating charts & slideshow, filing issues, nudging me to ask for feedback from users, helping with design of v2.0-v2.1 API, and general idea for DupMapKeyEnforcedAPF.
* Stefan Tatschner for using this library in [sep](https://git.sr.ht/~rumpelsepp/sep), being the 1st to discover my CBOR library, requesting time.Time in issue #1, and submitting this library in a [PR to cbor.io](https://github.com/cbor/cbor.github.io/pull/56) on Aug 12, 2019.
* Yawning Angel for using this library to [oasis-core](https://github.com/oasislabs/oasis-core), and requesting BinaryMarshaler in issue #5.
* Jernej Kos for requesting RawMessage in issue #11 and offering feedback on v2.1 API for CBOR tags.
@@ -917,9 +1084,9 @@ __Making this library better__
CBOR BSTR <--> Go array in #133.
* Keith Randall for [fixing Go bugs and providing workarounds](https://github.com/golang/go/issues/36400) so we don't have to wait for new versions of Go.
-__Help clarifying CBOR RFC 7049 or 7049bis__
+__Help clarifying CBOR RFC 7049 or 7049bis (7049bis is the draft of RFC 8949)__
-* Carsten Bormann for RFC 7049 (CBOR), his fast confirmation to my RFC 7049 errata, approving my pull request to 7049bis, and his patience when I misread a line in 7049bis.
+* Carsten Bormann for RFC 7049 (CBOR), adding this library to cbor.io, his fast confirmation to my RFC 7049 errata, approving my pull request to 7049bis, and his patience when I misread a line in 7049bis.
* Laurence Lundblade for his help on the IETF mailing list for 7049bis and for pointing out on a CBORbis issue that CBOR Undefined might be problematic translating to JSON.
* Jeffrey Yasskin for his help on the IETF mailing list for 7049bis.
@@ -929,10 +1096,10 @@ __Words of encouragement and support__
## License
-Copyright © 2019-present [Faye Amacker](https://github.com/fxamacker).
+Copyright © 2019-2021 [Faye Amacker](https://github.com/fxamacker).
-fxamacker/cbor is licensed under the MIT License. See [LICENSE](LICENSE) for the full license text.
+fxamacker/cbor is licensed under the MIT License. See [LICENSE](LICENSE) for the full license text.
-⚓ [Install](#installation) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [Security Policy](#security-policy) • [License](#license)
+⚓ [Quick Start](#quick-start) • [Status](#current-status) • [Design Goals](#design-goals) • [Features](#features) • [Standards](#standards) • [API](#api) • [Options](#options) • [Usage](#usage) • [Fuzzing](#fuzzing-and-code-coverage) • [License](#license)
diff --git a/vendor/github.com/fxamacker/cbor/v2/SECURITY.md b/vendor/github.com/fxamacker/cbor/v2/SECURITY.md
new file mode 100644
index 00000000000..9c05146d16c
--- /dev/null
+++ b/vendor/github.com/fxamacker/cbor/v2/SECURITY.md
@@ -0,0 +1,7 @@
+# Security Policy
+
+Security fixes are provided for the latest released version of fxamacker/cbor.
+
+If the security vulnerability is already known to the public, then you can open an issue as a bug report.
+
+To report security vulnerabilities not yet known to the public, please email faye.github@gmail.com and allow time for the problem to be resolved before reporting it to the public.
diff --git a/vendor/github.com/fxamacker/cbor/v2/cache.go b/vendor/github.com/fxamacker/cbor/v2/cache.go
index ace1669b266..2fdf114bcb3 100644
--- a/vendor/github.com/fxamacker/cbor/v2/cache.go
+++ b/vendor/github.com/fxamacker/cbor/v2/cache.go
@@ -13,10 +13,15 @@ import (
"sync"
)
+type encodeFuncs struct {
+ ef encodeFunc
+ ief isEmptyFunc
+}
+
var (
decodingStructTypeCache sync.Map // map[reflect.Type]*decodingStructType
encodingStructTypeCache sync.Map // map[reflect.Type]*encodingStructType
- encodeFuncCache sync.Map // map[reflect.Type]encodeFunc
+ encodeFuncCache sync.Map // map[reflect.Type]encodeFuncs
typeInfoCache sync.Map // map[reflect.Type]*typeInfo
)
@@ -26,6 +31,7 @@ const (
specialTypeNone specialType = iota
specialTypeUnmarshalerIface
specialTypeEmptyIface
+ specialTypeIface
specialTypeTag
specialTypeTime
)
@@ -52,8 +58,12 @@ func newTypeInfo(t reflect.Type) *typeInfo {
tInfo.nonPtrType = t
tInfo.nonPtrKind = k
- if k == reflect.Interface && t.NumMethod() == 0 {
- tInfo.spclType = specialTypeEmptyIface
+ if k == reflect.Interface {
+ if t.NumMethod() == 0 {
+ tInfo.spclType = specialTypeEmptyIface
+ } else {
+ tInfo.spclType = specialTypeIface
+ }
} else if t == typeTag {
tInfo.spclType = specialTypeTag
} else if t == typeTime {
@@ -108,13 +118,13 @@ func getDecodingStructType(t reflect.Type) *decodingStructType {
}
type encodingStructType struct {
- fields fields
- bytewiseFields fields
- lengthFirstFields fields
- err error
- toArray bool
- omitEmpty bool
- hasAnonymousField bool
+ fields fields
+ bytewiseFields fields
+ lengthFirstFields fields
+ omitEmptyFieldsIdx []int
+ err error
+ toArray bool
+ fixedLength bool // Struct type doesn't have any omitempty or anonymous fields.
}
func (st *encodingStructType) getFields(em *encMode) fields {
@@ -162,9 +172,10 @@ func (x *lengthFirstFieldSorter) Less(i, j int) bool {
return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) <= 0
}
-func getEncodingStructType(t reflect.Type) *encodingStructType {
+func getEncodingStructType(t reflect.Type) (*encodingStructType, error) {
if v, _ := encodingStructTypeCache.Load(t); v != nil {
- return v.(*encodingStructType)
+ structType := v.(*encodingStructType)
+ return structType, structType.err
}
flds, structOptions := getFields(t)
@@ -174,14 +185,14 @@ func getEncodingStructType(t reflect.Type) *encodingStructType {
}
var err error
- var omitEmpty bool
- var hasAnonymousField bool
var hasKeyAsInt bool
var hasKeyAsStr bool
- e := getEncodeState()
+ var omitEmptyIdx []int
+ fixedLength := true
+ e := getEncoderBuffer()
for i := 0; i < len(flds); i++ {
// Get field's encodeFunc
- flds[i].ef = getEncodeFunc(flds[i].typ)
+ flds[i].ef, flds[i].ief = getEncodeFunc(flds[i].typ)
if flds[i].ef == nil {
err = &UnsupportedTypeError{t}
break
@@ -218,20 +229,21 @@ func getEncodingStructType(t reflect.Type) *encodingStructType {
// Check if field is from embedded struct
if len(flds[i].idx) > 1 {
- hasAnonymousField = true
+ fixedLength = false
}
// Check if field can be omitted when empty
if flds[i].omitEmpty {
- omitEmpty = true
+ fixedLength = false
+ omitEmptyIdx = append(omitEmptyIdx, i)
}
}
- putEncodeState(e)
+ putEncoderBuffer(e)
if err != nil {
structType := &encodingStructType{err: err}
encodingStructTypeCache.Store(t, structType)
- return structType
+ return structType, structType.err
}
// Sort fields by canonical order
@@ -247,49 +259,44 @@ func getEncodingStructType(t reflect.Type) *encodingStructType {
}
structType := &encodingStructType{
- fields: flds,
- bytewiseFields: bytewiseFields,
- lengthFirstFields: lengthFirstFields,
- omitEmpty: omitEmpty,
- hasAnonymousField: hasAnonymousField,
+ fields: flds,
+ bytewiseFields: bytewiseFields,
+ lengthFirstFields: lengthFirstFields,
+ omitEmptyFieldsIdx: omitEmptyIdx,
+ fixedLength: fixedLength,
}
encodingStructTypeCache.Store(t, structType)
- return structType
+ return structType, structType.err
}
-func getEncodingStructToArrayType(t reflect.Type, flds fields) *encodingStructType {
- var hasAnonymousField bool
+func getEncodingStructToArrayType(t reflect.Type, flds fields) (*encodingStructType, error) {
for i := 0; i < len(flds); i++ {
// Get field's encodeFunc
- flds[i].ef = getEncodeFunc(flds[i].typ)
+ flds[i].ef, flds[i].ief = getEncodeFunc(flds[i].typ)
if flds[i].ef == nil {
structType := &encodingStructType{err: &UnsupportedTypeError{t}}
encodingStructTypeCache.Store(t, structType)
- return structType
- }
-
- // Check if field is from embedded struct
- if len(flds[i].idx) > 1 {
- hasAnonymousField = true
+ return structType, structType.err
}
}
structType := &encodingStructType{
- fields: flds,
- toArray: true,
- hasAnonymousField: hasAnonymousField,
+ fields: flds,
+ toArray: true,
+ fixedLength: true,
}
encodingStructTypeCache.Store(t, structType)
- return structType
+ return structType, structType.err
}
-func getEncodeFunc(t reflect.Type) encodeFunc {
+func getEncodeFunc(t reflect.Type) (encodeFunc, isEmptyFunc) {
if v, _ := encodeFuncCache.Load(t); v != nil {
- return v.(encodeFunc)
+ fs := v.(encodeFuncs)
+ return fs.ef, fs.ief
}
- f := getEncodeFuncInternal(t)
- encodeFuncCache.Store(t, f)
- return f
+ ef, ief := getEncodeFuncInternal(t)
+ encodeFuncCache.Store(t, encodeFuncs{ef, ief})
+ return ef, ief
}
func getTypeInfo(t reflect.Type) *typeInfo {
diff --git a/vendor/github.com/fxamacker/cbor/v2/decode.go b/vendor/github.com/fxamacker/cbor/v2/decode.go
index 079e6821312..6b6179f50bf 100644
--- a/vendor/github.com/fxamacker/cbor/v2/decode.go
+++ b/vendor/github.com/fxamacker/cbor/v2/decode.go
@@ -10,6 +10,7 @@ import (
"fmt"
"io"
"math"
+ "math/big"
"reflect"
"strconv"
"strings"
@@ -19,46 +20,57 @@ import (
"github.com/x448/float16"
)
-// Unmarshal parses the CBOR-encoded data and stores the result in the value
-// pointed to by v using the default decoding options. If v is nil or not a
-// pointer, Unmarshal returns an error.
+// Unmarshal parses the CBOR-encoded data into the value pointed to by v
+// using default decoding options. If v is nil, not a pointer, or
+// a nil pointer, Unmarshal returns an error.
//
-// Unmarshal uses the inverse of the encodings that Marshal uses, allocating
-// maps, slices, and pointers as necessary, with the following additional rules:
+// To unmarshal CBOR into a value implementing the Unmarshaler interface,
+// Unmarshal calls that value's UnmarshalCBOR method with a valid
+// CBOR value.
+//
+// To unmarshal CBOR byte string into a value implementing the
+// encoding.BinaryUnmarshaler interface, Unmarshal calls that value's
+// UnmarshalBinary method with decoded CBOR byte string.
//
-// To unmarshal CBOR into a pointer, Unmarshal first handles the case of the
-// CBOR being the CBOR literal null. In that case, Unmarshal sets the pointer
-// to nil. Otherwise, Unmarshal unmarshals the CBOR into the value pointed at
-// by the pointer. If the pointer is nil, Unmarshal allocates a new value for
-// it to point to.
+// To unmarshal CBOR into a pointer, Unmarshal sets the pointer to nil
+// if CBOR data is null (0xf6) or undefined (0xf7). Otherwise, Unmarshal
+// unmarshals CBOR into the value pointed to by the pointer. If the
+// pointer is nil, Unmarshal creates a new value for it to point to.
//
-// To unmarshal CBOR into an interface value, Unmarshal stores one of these in
-// the interface value:
+// To unmarshal CBOR into an empty interface value, Unmarshal uses the
+// following rules:
//
-// bool, for CBOR booleans
-// uint64, for CBOR positive integers
-// int64, for CBOR negative integers
-// float64, for CBOR floating points
-// []byte, for CBOR byte strings
-// string, for CBOR text strings
-// []interface{}, for CBOR arrays
-// map[interface{}]interface{}, for CBOR maps
-// nil, for CBOR null
+// CBOR booleans decode to bool.
+// CBOR positive integers decode to uint64.
+// CBOR negative integers decode to int64 (big.Int if value overflows).
+// CBOR floating points decode to float64.
+// CBOR byte strings decode to []byte.
+// CBOR text strings decode to string.
+// CBOR arrays decode to []interface{}.
+// CBOR maps decode to map[interface{}]interface{}.
+// CBOR null and undefined values decode to nil.
+// CBOR times (tag 0 and 1) decode to time.Time.
+// CBOR bignums (tag 2 and 3) decode to big.Int.
//
-// To unmarshal a CBOR array into a slice, Unmarshal allocates a new slice only
+// To unmarshal a CBOR array into a slice, Unmarshal allocates a new slice
// if the CBOR array is empty or slice capacity is less than CBOR array length.
-// Otherwise Unmarshal reuses the existing slice, overwriting existing elements.
-// Unmarshal sets the slice length to CBOR array length.
+// Otherwise Unmarshal overwrites existing elements, and sets slice length
+// to CBOR array length.
//
-// To ummarshal a CBOR array into a Go array, Unmarshal decodes CBOR array
-// elements into corresponding Go array elements. If the Go array is smaller
-// than the CBOR array, the additional CBOR array elements are discarded. If
-// the CBOR array is smaller than the Go array, the additional Go array elements
-// are set to zero values.
+// To unmarshal a CBOR array into a Go array, Unmarshal decodes CBOR array
+// elements into Go array elements. If the Go array is smaller than the
+// CBOR array, the extra CBOR array elements are discarded. If the CBOR
+// array is smaller than the Go array, the extra Go array elements are
+// set to zero values.
+//
+// To unmarshal a CBOR array into a struct, struct must have a special field "_"
+// with struct tag `cbor:",toarray"`. Go array elements are decoded into struct
+// fields. Any "omitempty" struct field tag option is ignored in this case.
//
// To unmarshal a CBOR map into a map, Unmarshal allocates a new map only if the
-// map is nil. Otherwise Unmarshal reuses the existing map, keeping existing
+// map is nil. Otherwise Unmarshal reuses the existing map and keeps existing
// entries. Unmarshal stores key-value pairs from the CBOR map into Go map.
+// See DecOptions.DupMapKey to enable duplicate map key detection.
//
// To unmarshal a CBOR map into a struct, Unmarshal matches CBOR map keys to the
// keys in the following priority:
@@ -67,78 +79,64 @@ import (
// 2. "json" key in struct field tag,
// 3. struct field name.
//
-// Unmarshal prefers an exact match but also accepts a case-insensitive match.
-// Map keys which don't have a corresponding struct field are ignored.
+// Unmarshal tries an exact match for field name, then a case-insensitive match.
+// Map key-value pairs without corresponding struct fields are ignored. See
+// DecOptions.ExtraReturnErrors to return error at unknown field.
//
// To unmarshal a CBOR text string into a time.Time value, Unmarshal parses text
// string formatted in RFC3339. To unmarshal a CBOR integer/float into a
// time.Time value, Unmarshal creates an unix time with integer/float as seconds
// and fractional seconds since January 1, 1970 UTC.
//
-// To unmarshal CBOR into a value implementing the Unmarshaler interface,
-// Unmarshal calls that value's UnmarshalCBOR method.
-//
-// Unmarshal decodes a CBOR byte string into a value implementing
-// encoding.BinaryUnmarshaler.
-//
-// If a CBOR value is not appropriate for a given Go type, or if a CBOR number
-// overflows the Go type, Unmarshal skips that field and completes the
-// unmarshalling as best as it can. If no more serious errors are encountered,
-// unmarshal returns an UnmarshalTypeError describing the earliest such error.
-// In any case, it's not guaranteed that all the remaining fields following the
-// problematic one will be unmarshaled into the target object.
+// To unmarshal CBOR null (0xf6) and undefined (0xf7) values into a
+// slice/map/pointer, Unmarshal sets Go value to nil. Because null is often
+// used to mean "not present", unmarshalling CBOR null and undefined value
+// into any other Go type has no effect and returns no error.
//
-// The CBOR null value unmarshals into a slice/map/pointer/interface by setting
-// that Go value to nil. Because null is often used to mean "not present",
-// unmarshalling a CBOR null into any other Go type has no effect on the value
-// produces no error.
-//
-// Unmarshal ignores CBOR tag data and parses tagged data following CBOR tag.
+// Unmarshal supports CBOR tag 55799 (self-describe CBOR), tag 0 and 1 (time),
+// and tag 2 and 3 (bignum).
func Unmarshal(data []byte, v interface{}) error {
return defaultDecMode.Unmarshal(data, v)
}
-// Unmarshaler is the interface implemented by types that can unmarshal a CBOR
-// representation of themselves. The input can be assumed to be a valid encoding
-// of a CBOR value. UnmarshalCBOR must copy the CBOR data if it wishes to retain
-// the data after returning.
+// Valid checks whether the CBOR data is complete and well-formed.
+func Valid(data []byte) error {
+ return defaultDecMode.Valid(data)
+}
+
+// Unmarshaler is the interface implemented by types that wish to unmarshal
+// CBOR data themselves. The input is a valid CBOR value. UnmarshalCBOR
+// must copy the CBOR data if it needs to use it after returning.
type Unmarshaler interface {
UnmarshalCBOR([]byte) error
}
// InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
type InvalidUnmarshalError struct {
- Type reflect.Type
+ s string
}
func (e *InvalidUnmarshalError) Error() string {
- if e.Type == nil {
- return "cbor: Unmarshal(nil)"
- }
- if e.Type.Kind() != reflect.Ptr {
- return "cbor: Unmarshal(non-pointer " + e.Type.String() + ")"
- }
- return "cbor: Unmarshal(nil " + e.Type.String() + ")"
+ return e.s
}
-// UnmarshalTypeError describes a CBOR value that was not appropriate for a Go type.
+// UnmarshalTypeError describes a CBOR value that can't be decoded to a Go type.
type UnmarshalTypeError struct {
- Value string // description of CBOR value
- Type reflect.Type // type of Go value it could not be assigned to
- Struct string // struct type containing the field
- Field string // name of the field holding the Go value
- errMsg string // additional error message (optional)
+ CBORType string // type of CBOR value
+ GoType string // type of Go value it could not be decoded into
+ StructFieldName string // name of the struct field holding the Go value (optional)
+ errorMsg string // additional error message (optional)
}
func (e *UnmarshalTypeError) Error() string {
var s string
- if e.Struct != "" || e.Field != "" {
- s = "cbor: cannot unmarshal " + e.Value + " into Go struct field " + e.Struct + "." + e.Field + " of type " + e.Type.String()
+ if e.StructFieldName != "" {
+ s = "cbor: cannot unmarshal " + e.CBORType + " into Go struct field " + e.StructFieldName + " of type " + e.GoType
} else {
- s = "cbor: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
+ s = "cbor: cannot unmarshal " + e.CBORType + " into Go value of type " + e.GoType
}
- if e.errMsg != "" {
- s += " (" + e.errMsg + ")"
+ if e.errorMsg != "" {
+ s += " (" + e.errorMsg + ")"
}
return s
}
@@ -153,6 +151,15 @@ func (e *DupMapKeyError) Error() string {
return fmt.Sprintf("cbor: found duplicate map key \"%v\" at map element index %d", e.Key, e.Index)
}
+// UnknownFieldError describes detected unknown field in CBOR map when decoding to Go struct.
+type UnknownFieldError struct {
+ Index int
+}
+
+func (e *UnknownFieldError) Error() string {
+ return fmt.Sprintf("cbor: found unknown field at map element index %d", e.Index)
+}
+
// DupMapKeyMode specifies how to enforce duplicate map key.
type DupMapKeyMode int
@@ -210,6 +217,46 @@ func (tm TagsMode) valid() bool {
return tm < maxTagsMode
}
+// IntDecMode specifies which Go int type (int64 or uint64) should
+// be used when decoding CBOR int (major type 0 and 1) to Go interface{}.
+type IntDecMode int
+
+const (
+ // IntDecConvertNone affects how CBOR int (major type 0 and 1) decodes to Go interface{}.
+ // It makes CBOR positive int (major type 0) decode to uint64 value, and
+ // CBOR negative int (major type 1) decode to int64 value.
+ IntDecConvertNone IntDecMode = iota
+
+ // IntDecConvertSigned affects how CBOR int (major type 0 and 1) decodes to Go interface{}.
+ // It makes CBOR positive/negative int (major type 0 and 1) decode to int64 value.
+ // If value overflows int64, UnmarshalTypeError is returned.
+ IntDecConvertSigned
+
+ maxIntDec
+)
+
+func (idm IntDecMode) valid() bool {
+ return idm < maxIntDec
+}
+
+// ExtraDecErrorCond specifies extra conditions that should be treated as errors.
+type ExtraDecErrorCond uint
+
+// ExtraDecErrorNone indicates no extra error condition.
+const ExtraDecErrorNone ExtraDecErrorCond = 0
+
+const (
+ // ExtraDecErrorUnknownField indicates error condition when destination
+ // Go struct doesn't have a field matching a CBOR map key.
+ ExtraDecErrorUnknownField ExtraDecErrorCond = 1 << iota
+
+ maxExtraDecError
+)
+
+func (ec ExtraDecErrorCond) valid() bool {
+ return ec < maxExtraDecError
+}
+
// DecOptions specifies decoding options.
type DecOptions struct {
// DupMapKey specifies whether to enforce duplicate map key.
@@ -224,11 +271,11 @@ type DecOptions struct {
MaxNestedLevels int
// MaxArrayElements specifies the max number of elements for CBOR arrays.
- // Default is 128*1024=131072 and it can be set to [16, 134217728]
+ // Default is 128*1024=131072 and it can be set to [16, 2147483647]
MaxArrayElements int
// MaxMapPairs specifies the max number of key-value pairs for CBOR maps.
- // Default is 128*1024=131072 and it can be set to [16, 134217728]
+ // Default is 128*1024=131072 and it can be set to [16, 2147483647]
MaxMapPairs int
// IndefLength specifies whether to allow indefinite length CBOR items.
@@ -236,6 +283,13 @@ type DecOptions struct {
// TagsMd specifies whether to allow CBOR tags (major type 6).
TagsMd TagsMode
+
+ // IntDec specifies which Go integer type (int64 or uint64) to use
+ // when decoding CBOR int (major type 0 and 1) to Go interface{}.
+ IntDec IntDecMode
+
+ // ExtraReturnErrors specifies extra conditions that should be treated as errors.
+ ExtraReturnErrors ExtraDecErrorCond
}
// DecMode returns DecMode with immutable options and no tags (safe for concurrency).
@@ -294,11 +348,11 @@ func (opts DecOptions) DecModeWithSharedTags(tags TagSet) (DecMode, error) {
const (
defaultMaxArrayElements = 131072
minMaxArrayElements = 16
- maxMaxArrayElements = 134217728
+ maxMaxArrayElements = 2147483647
defaultMaxMapPairs = 131072
minMaxMapPairs = 16
- maxMaxMapPairs = 134217728
+ maxMaxMapPairs = 2147483647
)
func (opts DecOptions) decMode() (*decMode, error) {
@@ -314,6 +368,9 @@ func (opts DecOptions) decMode() (*decMode, error) {
if !opts.TagsMd.valid() {
return nil, errors.New("cbor: invalid TagsMd " + strconv.Itoa(int(opts.TagsMd)))
}
+ if !opts.IntDec.valid() {
+ return nil, errors.New("cbor: invalid IntDec " + strconv.Itoa(int(opts.IntDec)))
+ }
if opts.MaxNestedLevels == 0 {
opts.MaxNestedLevels = 32
} else if opts.MaxNestedLevels < 4 || opts.MaxNestedLevels > 256 {
@@ -329,34 +386,50 @@ func (opts DecOptions) decMode() (*decMode, error) {
} else if opts.MaxMapPairs < minMaxMapPairs || opts.MaxMapPairs > maxMaxMapPairs {
return nil, errors.New("cbor: invalid MaxMapPairs " + strconv.Itoa(opts.MaxMapPairs) + " (range is [" + strconv.Itoa(minMaxMapPairs) + ", " + strconv.Itoa(maxMaxMapPairs) + "])")
}
+ if !opts.ExtraReturnErrors.valid() {
+ return nil, errors.New("cbor: invalid ExtraReturnErrors " + strconv.Itoa(int(opts.ExtraReturnErrors)))
+ }
dm := decMode{
- dupMapKey: opts.DupMapKey,
- timeTag: opts.TimeTag,
- maxNestedLevels: opts.MaxNestedLevels,
- maxArrayElements: opts.MaxArrayElements,
- maxMapPairs: opts.MaxMapPairs,
- indefLength: opts.IndefLength,
- tagsMd: opts.TagsMd,
+ dupMapKey: opts.DupMapKey,
+ timeTag: opts.TimeTag,
+ maxNestedLevels: opts.MaxNestedLevels,
+ maxArrayElements: opts.MaxArrayElements,
+ maxMapPairs: opts.MaxMapPairs,
+ indefLength: opts.IndefLength,
+ tagsMd: opts.TagsMd,
+ intDec: opts.IntDec,
+ extraReturnErrors: opts.ExtraReturnErrors,
}
return &dm, nil
}
// DecMode is the main interface for CBOR decoding.
type DecMode interface {
+ // Unmarshal parses the CBOR-encoded data into the value pointed to by v
+ // using the decoding mode. If v is nil, not a pointer, or a nil pointer,
+ // Unmarshal returns an error.
+ //
+ // See the documentation for Unmarshal for details.
Unmarshal(data []byte, v interface{}) error
+ // Valid checks whether the CBOR data is complete and well-formed.
+ Valid(data []byte) error
+ // NewDecoder returns a new decoder that reads from r using dm DecMode.
NewDecoder(r io.Reader) *Decoder
+ // DecOptions returns user specified options used to create this DecMode.
DecOptions() DecOptions
}
type decMode struct {
- tags tagProvider
- dupMapKey DupMapKeyMode
- timeTag DecTagMode
- maxNestedLevels int
- maxArrayElements int
- maxMapPairs int
- indefLength IndefLengthMode
- tagsMd TagsMode
+ tags tagProvider
+ dupMapKey DupMapKeyMode
+ timeTag DecTagMode
+ maxNestedLevels int
+ maxArrayElements int
+ maxMapPairs int
+ indefLength IndefLengthMode
+ tagsMd TagsMode
+ intDec IntDecMode
+ extraReturnErrors ExtraDecErrorCond
}
var defaultDecMode, _ = DecOptions{}.decMode()
@@ -364,41 +437,55 @@ var defaultDecMode, _ = DecOptions{}.decMode()
// DecOptions returns user specified options used to create this DecMode.
func (dm *decMode) DecOptions() DecOptions {
return DecOptions{
- DupMapKey: dm.dupMapKey,
- TimeTag: dm.timeTag,
- MaxNestedLevels: dm.maxNestedLevels,
- MaxArrayElements: dm.maxArrayElements,
- MaxMapPairs: dm.maxMapPairs,
- IndefLength: dm.indefLength,
- TagsMd: dm.tagsMd,
+ DupMapKey: dm.dupMapKey,
+ TimeTag: dm.timeTag,
+ MaxNestedLevels: dm.maxNestedLevels,
+ MaxArrayElements: dm.maxArrayElements,
+ MaxMapPairs: dm.maxMapPairs,
+ IndefLength: dm.indefLength,
+ TagsMd: dm.tagsMd,
+ IntDec: dm.intDec,
+ ExtraReturnErrors: dm.extraReturnErrors,
}
}
-// Unmarshal parses the CBOR-encoded data and stores the result in the value
-// pointed to by v using dm DecMode. If v is nil or not a pointer, Unmarshal
-// returns an error.
+// Unmarshal parses the CBOR-encoded data into the value pointed to by v
+// using dm decoding mode. If v is nil, not a pointer, or a nil pointer,
+// Unmarshal returns an error.
//
// See the documentation for Unmarshal for details.
func (dm *decMode) Unmarshal(data []byte, v interface{}) error {
- d := decodeState{data: data, dm: dm}
+ d := decoder{data: data, dm: dm}
return d.value(v)
}
+// Valid checks whether the CBOR data is complete and well-formed.
+func (dm *decMode) Valid(data []byte) error {
+ d := decoder{data: data, dm: dm}
+ return d.valid()
+}
+
// NewDecoder returns a new decoder that reads from r using dm DecMode.
func (dm *decMode) NewDecoder(r io.Reader) *Decoder {
- return &Decoder{r: r, d: decodeState{dm: dm}}
+ return &Decoder{r: r, d: decoder{dm: dm}}
}
-type decodeState struct {
+type decoder struct {
data []byte
off int // next read offset in data
dm *decMode
}
-func (d *decodeState) value(v interface{}) error {
+func (d *decoder) value(v interface{}) error {
+ // v can't be nil, non-pointer, or nil pointer value.
+ if v == nil {
+ return &InvalidUnmarshalError{"cbor: Unmarshal(nil)"}
+ }
rv := reflect.ValueOf(v)
- if rv.Kind() != reflect.Ptr || rv.IsNil() {
- return &InvalidUnmarshalError{reflect.TypeOf(v)}
+ if rv.Kind() != reflect.Ptr {
+ return &InvalidUnmarshalError{"cbor: Unmarshal(non-pointer " + rv.Type().String() + ")"}
+ } else if rv.IsNil() {
+ return &InvalidUnmarshalError{"cbor: Unmarshal(nil " + rv.Type().String() + ")"}
}
off := d.off // Save offset before data validation
@@ -409,16 +496,6 @@ func (d *decodeState) value(v interface{}) error {
}
rv = rv.Elem()
-
- if rv.Kind() == reflect.Interface && rv.NumMethod() == 0 {
- // Fast path to decode to empty interface without retrieving typeInfo.
- iv, err := d.parse()
- if iv != nil {
- rv.Set(reflect.ValueOf(iv))
- }
- return err
- }
-
return d.parseToValue(rv, getTypeInfo(rv.Type()))
}
@@ -458,9 +535,19 @@ func (t cborType) String() string {
}
}
-// parseToValue assumes data is well-formed, and does not perform bounds checking.
-// This function is complicated because it's the main function that decodes CBOR data to reflect.Value.
-func (d *decodeState) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
+const (
+ selfDescribedCBORTagNum = 55799
+)
+
+// parseToValue decodes CBOR data to value. It assumes data is well-formed,
+// and does not perform bounds checking.
+func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
+
+ if tInfo.spclType == specialTypeIface && !v.IsNil() {
+ v = v.Elem()
+ tInfo = getTypeInfo(v.Type())
+ }
+
// Create new value for the pointer v to point to if CBOR value is not nil/undefined.
if !d.nextCBORNil() {
for v.Kind() == reflect.Ptr {
@@ -475,10 +562,31 @@ func (d *decodeState) parseToValue(v reflect.Value, tInfo *typeInfo) error { //n
}
}
+ // Strip self-described CBOR tag number.
+ for d.nextCBORType() == cborTypeTag {
+ off := d.off
+ _, _, tagNum := d.getHead()
+ if tagNum != selfDescribedCBORTagNum {
+ d.off = off
+ break
+ }
+ }
+
+ // Check validity of supported built-in tags.
+ if d.nextCBORType() == cborTypeTag {
+ off := d.off
+ _, _, tagNum := d.getHead()
+ if err := validBuiltinTag(tagNum, d.data[d.off]); err != nil {
+ d.skip()
+ return err
+ }
+ d.off = off
+ }
+
if tInfo.spclType != specialTypeNone {
switch tInfo.spclType {
case specialTypeEmptyIface:
- iv, err := d.parse()
+ iv, err := d.parse(false) // Skipped self-described CBOR tag number already.
if iv != nil {
v.Set(reflect.ValueOf(iv))
}
@@ -486,7 +594,17 @@ func (d *decodeState) parseToValue(v reflect.Value, tInfo *typeInfo) error { //n
case specialTypeTag:
return d.parseToTag(v)
case specialTypeTime:
- return d.parseToTime(v)
+ if d.nextCBORNil() {
+ // Decoding CBOR null and undefined to time.Time is no-op.
+ d.skip()
+ return nil
+ }
+ tm, err := d.parseToTime()
+ if err != nil {
+ return err
+ }
+ v.Set(reflect.ValueOf(tm))
+ return nil
case specialTypeUnmarshalerIface:
return d.parseToUnmarshaler(v)
}
@@ -498,9 +616,12 @@ func (d *decodeState) parseToValue(v reflect.Value, tInfo *typeInfo) error { //n
if t != cborTypeTag {
if tagItem.opts.DecTag == DecTagRequired {
d.skip() // Required tag number is absent, skip entire tag
- return &UnmarshalTypeError{Value: t.String(), Type: tInfo.typ, errMsg: "expect CBOR tag value"}
+ return &UnmarshalTypeError{
+ CBORType: t.String(),
+ GoType: tInfo.typ.String(),
+ errorMsg: "expect CBOR tag value"}
}
- } else if err := d.validRegisteredTagNums(tInfo.nonPtrType, tagItem.num); err != nil {
+ } else if err := d.validRegisteredTagNums(tagItem); err != nil {
d.skip() // Skip tag content
return err
}
@@ -508,16 +629,6 @@ func (d *decodeState) parseToValue(v reflect.Value, tInfo *typeInfo) error { //n
t := d.nextCBORType()
- // Skip tag number(s) here to avoid recursion
- if t == cborTypeTag {
- d.getHead()
- t = d.nextCBORType()
- for t == cborTypeTag {
- d.getHead()
- t = d.nextCBORType()
- }
- }
-
switch t {
case cborTypePositiveInt:
_, _, val := d.getHead()
@@ -525,10 +636,20 @@ func (d *decodeState) parseToValue(v reflect.Value, tInfo *typeInfo) error { //n
case cborTypeNegativeInt:
_, _, val := d.getHead()
if val > math.MaxInt64 {
+ // CBOR negative integer overflows int64, use big.Int to store value.
+ bi := new(big.Int)
+ bi.SetUint64(val)
+ bi.Add(bi, big.NewInt(1))
+ bi.Neg(bi)
+
+ if tInfo.nonPtrType == typeBigInt {
+ v.Set(reflect.ValueOf(*bi))
+ return nil
+ }
return &UnmarshalTypeError{
- Value: t.String(),
- Type: tInfo.nonPtrType,
- errMsg: "-1-" + strconv.FormatUint(val, 10) + " overflows Go's int64",
+ CBORType: t.String(),
+ GoType: tInfo.nonPtrType.String(),
+ errorMsg: bi.String() + " overflows Go's int64",
}
}
nValue := int64(-1) ^ int64(val)
@@ -562,6 +683,53 @@ func (d *decodeState) parseToValue(v reflect.Value, tInfo *typeInfo) error { //n
f := math.Float64frombits(val)
return fillFloat(t, f, v)
}
+ case cborTypeTag:
+ _, _, tagNum := d.getHead()
+ switch tagNum {
+ case 2:
+ // Bignum (tag 2) can be decoded to uint, int, float, slice, array, or big.Int.
+ b := d.parseByteString()
+ bi := new(big.Int).SetBytes(b)
+
+ if tInfo.nonPtrType == typeBigInt {
+ v.Set(reflect.ValueOf(*bi))
+ return nil
+ }
+ if tInfo.nonPtrKind == reflect.Slice || tInfo.nonPtrKind == reflect.Array {
+ return fillByteString(t, b, v)
+ }
+ if bi.IsUint64() {
+ return fillPositiveInt(t, bi.Uint64(), v)
+ }
+ return &UnmarshalTypeError{
+ CBORType: t.String(),
+ GoType: tInfo.nonPtrType.String(),
+ errorMsg: bi.String() + " overflows " + v.Type().String(),
+ }
+ case 3:
+ // Bignum (tag 3) can be decoded to int, float, slice, array, or big.Int.
+ b := d.parseByteString()
+ bi := new(big.Int).SetBytes(b)
+ bi.Add(bi, big.NewInt(1))
+ bi.Neg(bi)
+
+ if tInfo.nonPtrType == typeBigInt {
+ v.Set(reflect.ValueOf(*bi))
+ return nil
+ }
+ if tInfo.nonPtrKind == reflect.Slice || tInfo.nonPtrKind == reflect.Array {
+ return fillByteString(t, b, v)
+ }
+ if bi.IsInt64() {
+ return fillNegativeInt(t, bi.Int64(), v)
+ }
+ return &UnmarshalTypeError{
+ CBORType: t.String(),
+ GoType: tInfo.nonPtrType.String(),
+ errorMsg: bi.String() + " overflows " + v.Type().String(),
+ }
+ }
+ return d.parseToValue(v, tInfo)
case cborTypeArray:
if tInfo.nonPtrKind == reflect.Slice {
return d.parseArrayToSlice(v, tInfo)
@@ -571,7 +739,7 @@ func (d *decodeState) parseToValue(v reflect.Value, tInfo *typeInfo) error { //n
return d.parseArrayToStruct(v, tInfo)
}
d.skip()
- return &UnmarshalTypeError{Value: t.String(), Type: tInfo.nonPtrType}
+ return &UnmarshalTypeError{CBORType: t.String(), GoType: tInfo.nonPtrType.String()}
case cborTypeMap:
if tInfo.nonPtrKind == reflect.Struct {
return d.parseMapToStruct(v, tInfo)
@@ -579,23 +747,29 @@ func (d *decodeState) parseToValue(v reflect.Value, tInfo *typeInfo) error { //n
return d.parseMapToMap(v, tInfo)
}
d.skip()
- return &UnmarshalTypeError{Value: t.String(), Type: tInfo.nonPtrType}
+ return &UnmarshalTypeError{CBORType: t.String(), GoType: tInfo.nonPtrType.String()}
}
return nil
}
-func (d *decodeState) parseToTag(v reflect.Value) error {
+func (d *decoder) parseToTag(v reflect.Value) error {
+ if d.nextCBORNil() {
+ // Decoding CBOR null and undefined to cbor.Tag is no-op.
+ d.skip()
+ return nil
+ }
+
t := d.nextCBORType()
if t != cborTypeTag {
d.skip()
- return &UnmarshalTypeError{Value: t.String(), Type: typeTag}
+ return &UnmarshalTypeError{CBORType: t.String(), GoType: typeTag.String()}
}
// Unmarshal tag number
_, _, num := d.getHead()
// Unmarshal tag content
- content, err := d.parse()
+ content, err := d.parse(false)
if err != nil {
return err
}
@@ -604,15 +778,13 @@ func (d *decodeState) parseToTag(v reflect.Value) error {
return nil
}
-func (d *decodeState) parseToTime(v reflect.Value) error {
+func (d *decoder) parseToTime() (tm time.Time, err error) {
t := d.nextCBORType()
- // Verify that tag number or absent of tag number is acceptable to specified timeTag.
+ // Verify that tag number or absence of tag number is acceptable to specified timeTag.
if t == cborTypeTag {
if d.dm.timeTag == DecTagIgnored {
// Skip tag number
- d.getHead()
- t = d.nextCBORType()
for t == cborTypeTag {
d.getHead()
t = d.nextCBORType()
@@ -620,95 +792,56 @@ func (d *decodeState) parseToTime(v reflect.Value) error {
} else {
// Read tag number
_, _, tagNum := d.getHead()
-
- // Verify tag number (0 or 1) is followed by appropriate tag content type.
- t = d.nextCBORType()
- switch tagNum {
- case 0:
- // Tag content (date/time text string in RFC 3339 format) must be string type.
- if t != cborTypeTextString {
- d.skip()
- return errors.New("cbor: tag number 0 must be followed by text string, got " + t.String())
- }
- case 1:
- // Tag content (epoch date/time) must be uint, int, or float type.
- if t != cborTypePositiveInt && t != cborTypeNegativeInt && (d.data[d.off] < 0xf9 || d.data[d.off] > 0xfb) {
- d.skip()
- return errors.New("cbor: tag number 1 must be followed by integer or floating-point number, got " + t.String())
- }
- default:
+ if tagNum != 0 && tagNum != 1 {
d.skip()
- return errors.New("cbor: wrong tag number for time.Time, got " + strconv.Itoa(int(tagNum)) + ", expect 0 or 1")
+ err = errors.New("cbor: wrong tag number for time.Time, got " + strconv.Itoa(int(tagNum)) + ", expect 0 or 1")
+ return
}
}
} else {
if d.dm.timeTag == DecTagRequired {
d.skip()
- return &UnmarshalTypeError{Value: t.String(), Type: typeTime, errMsg: "expect CBOR tag value"}
+ err = &UnmarshalTypeError{CBORType: t.String(), GoType: typeTime.String(), errorMsg: "expect CBOR tag value"}
+ return
}
}
- switch t {
- case cborTypePositiveInt:
- _, _, val := d.getHead()
- tm := time.Unix(int64(val), 0)
- v.Set(reflect.ValueOf(tm))
- return nil
- case cborTypeNegativeInt:
- _, _, val := d.getHead()
- if val > math.MaxInt64 {
- return &UnmarshalTypeError{
- Value: t.String(),
- Type: typeTime,
- errMsg: "-1-" + strconv.FormatUint(val, 10) + " overflows Go's int64",
- }
- }
- nValue := int64(-1) ^ int64(val)
- tm := time.Unix(nValue, 0)
- v.Set(reflect.ValueOf(tm))
- return nil
- case cborTypeTextString:
- b, err := d.parseTextString()
- if err != nil {
- return err
- }
- tm, err := time.Parse(time.RFC3339, string(b))
+ var content interface{}
+ content, err = d.parse(false)
+ if err != nil {
+ return
+ }
+
+ switch c := content.(type) {
+ case nil:
+ return
+ case uint64:
+ return time.Unix(int64(c), 0), nil
+ case int64:
+ return time.Unix(c, 0), nil
+ case float64:
+ if math.IsNaN(c) || math.IsInf(c, 0) {
+ return
+ }
+ f1, f2 := math.Modf(c)
+ return time.Unix(int64(f1), int64(f2*1e9)), nil
+ case string:
+ tm, err = time.Parse(time.RFC3339, c)
if err != nil {
- return errors.New("cbor: cannot set " + string(b) + " for time.Time: " + err.Error())
- }
- v.Set(reflect.ValueOf(tm))
- return nil
- case cborTypePrimitives:
- _, ai, val := d.getHead()
- var f float64
- switch ai {
- case 22, 23:
- v.Set(reflect.ValueOf(time.Time{}))
- return nil
- case 25:
- f = float64(float16.Frombits(uint16(val)).Float32())
- case 26:
- f = float64(math.Float32frombits(uint32(val)))
- case 27:
- f = math.Float64frombits(val)
- default:
- return &UnmarshalTypeError{Value: t.String(), Type: typeTime}
+ tm = time.Time{}
+ err = errors.New("cbor: cannot set " + c + " for time.Time: " + err.Error())
+ return
}
- if math.IsNaN(f) || math.IsInf(f, 0) {
- v.Set(reflect.ValueOf(time.Time{}))
- return nil
- }
- f1, f2 := math.Modf(f)
- tm := time.Unix(int64(f1), int64(f2*1e9))
- v.Set(reflect.ValueOf(tm))
- return nil
+ return
+ default:
+ err = &UnmarshalTypeError{CBORType: t.String(), GoType: typeTime.String()}
+ return
}
- d.skip()
- return &UnmarshalTypeError{Value: t.String(), Type: typeTime}
}
-// parseToUnmarshaler assumes data is well-formed, and does not perform bounds checking.
-func (d *decodeState) parseToUnmarshaler(v reflect.Value) error {
+// parseToUnmarshaler parses CBOR data to value implementing Unmarshaler interface.
+// It assumes data is well-formed, and does not perform bounds checking.
+func (d *decoder) parseToUnmarshaler(v reflect.Value) error {
if d.nextCBORNil() && v.Kind() == reflect.Ptr && v.IsNil() {
d.skip()
return nil
@@ -726,22 +859,56 @@ func (d *decodeState) parseToUnmarshaler(v reflect.Value) error {
return errors.New("cbor: failed to assert " + v.Type().String() + " as cbor.Unmarshaler")
}
-// parse assumes data is well-formed, and does not perform bounds checking.
-func (d *decodeState) parse() (interface{}, error) {
+// parse parses CBOR data and returns value in default Go type.
+// It assumes data is well-formed, and does not perform bounds checking.
+func (d *decoder) parse(skipSelfDescribedTag bool) (interface{}, error) { //nolint:gocyclo
+ // Strip self-described CBOR tag number.
+ if skipSelfDescribedTag {
+ for d.nextCBORType() == cborTypeTag {
+ off := d.off
+ _, _, tagNum := d.getHead()
+ if tagNum != selfDescribedCBORTagNum {
+ d.off = off
+ break
+ }
+ }
+ }
+
+ // Check validity of supported built-in tags.
+ if d.nextCBORType() == cborTypeTag {
+ off := d.off
+ _, _, tagNum := d.getHead()
+ if err := validBuiltinTag(tagNum, d.data[d.off]); err != nil {
+ d.skip()
+ return nil, err
+ }
+ d.off = off
+ }
+
t := d.nextCBORType()
switch t {
case cborTypePositiveInt:
_, _, val := d.getHead()
- return val, nil
- case cborTypeNegativeInt:
- _, _, val := d.getHead()
+ if d.dm.intDec == IntDecConvertNone {
+ return val, nil
+ }
if val > math.MaxInt64 {
return nil, &UnmarshalTypeError{
- Value: t.String(),
- Type: reflect.TypeOf([]interface{}(nil)).Elem(),
- errMsg: "-1-" + strconv.FormatUint(val, 10) + " overflows Go's int64",
+ CBORType: t.String(),
+ GoType: reflect.TypeOf(int64(0)).String(),
+ errorMsg: strconv.FormatUint(val, 10) + " overflows Go's int64",
}
}
+ return int64(val), nil
+ case cborTypeNegativeInt:
+ _, _, val := d.getHead()
+ if val > math.MaxInt64 {
+ // CBOR negative integer value overflows Go int64, use big.Int instead.
+ bi := new(big.Int).SetUint64(val)
+ bi.Add(bi, big.NewInt(1))
+ bi.Neg(bi)
+ return *bi, nil
+ }
nValue := int64(-1) ^ int64(val)
return nValue, nil
case cborTypeByteString:
@@ -753,38 +920,50 @@ func (d *decodeState) parse() (interface{}, error) {
}
return string(b), nil
case cborTypeTag:
+ tagOff := d.off
_, _, tagNum := d.getHead()
- nt := d.nextCBORType()
- content, err := d.parse()
- if err != nil {
- return nil, err
- }
+ contentOff := d.off
+
switch tagNum {
- case 0:
- // Tag content should be date/time text string in RFC 3339 format.
- s, ok := content.(string)
- if !ok {
- return nil, errors.New("cbor: tag number 0 must be followed by text string, got " + nt.String())
- }
- tm, err := time.Parse(time.RFC3339, s)
- if err != nil {
- return nil, errors.New("cbor: cannot set " + s + " for time.Time: " + err.Error())
+ case 0, 1:
+ d.off = tagOff
+ return d.parseToTime()
+ case 2:
+ b := d.parseByteString()
+ bi := new(big.Int).SetBytes(b)
+ return *bi, nil
+ case 3:
+ b := d.parseByteString()
+ bi := new(big.Int).SetBytes(b)
+ bi.Add(bi, big.NewInt(1))
+ bi.Neg(bi)
+ return *bi, nil
+ }
+
+ if d.dm.tags != nil {
+ // Parse to specified type if tag number is registered.
+ tagNums := []uint64{tagNum}
+ for d.nextCBORType() == cborTypeTag {
+ _, _, num := d.getHead()
+ tagNums = append(tagNums, num)
}
- return tm, nil
- case 1:
- // Tag content should be epoch date/time.
- switch content := content.(type) {
- case uint64:
- return time.Unix(int64(content), 0), nil
- case int64:
- return time.Unix(content, 0), nil
- case float64:
- f1, f2 := math.Modf(content)
- return time.Unix(int64(f1), int64(f2*1e9)), nil
- default:
- return nil, errors.New("cbor: tag number 1 must be followed by integer or floating-point number, got " + nt.String())
+ registeredType := d.dm.tags.getTypeFromTagNum(tagNums)
+ if registeredType != nil {
+ d.off = tagOff
+ rv := reflect.New(registeredType)
+ if err := d.parseToValue(rv.Elem(), getTypeInfo(registeredType)); err != nil {
+ return nil, err
+ }
+ return rv.Elem().Interface(), nil
}
}
+
+ // Parse tag content
+ d.off = contentOff
+ content, err := d.parse(false)
+ if err != nil {
+ return nil, err
+ }
return Tag{tagNum, content}, nil
case cborTypePrimitives:
_, ai, val := d.getHead()
@@ -816,7 +995,7 @@ func (d *decodeState) parse() (interface{}, error) {
// parseByteString parses CBOR encoded byte string. It returns a byte slice
// pointing to a copy of parsed data.
-func (d *decodeState) parseByteString() []byte {
+func (d *decoder) parseByteString() []byte {
_, ai, val := d.getHead()
if ai != 31 {
b := make([]byte, int(val))
@@ -834,14 +1013,10 @@ func (d *decodeState) parseByteString() []byte {
return b
}
-// parseTextString parses CBOR encoded text string. It does not return a string
+// parseTextString parses CBOR encoded text string. It returns a byte slice
// to prevent creating an extra copy of string. Caller should wrap returned
// byte slice as string when needed.
-//
-// parseStruct() uses parseTextString() to improve memory and performance,
-// compared with using parse(reflect.Value). parse(reflect.Value) sets
-// reflect.Value with parsed string, while parseTextString() returns zero-copy []byte.
-func (d *decodeState) parseTextString() ([]byte, error) {
+func (d *decoder) parseTextString() ([]byte, error) {
_, ai, val := d.getHead()
if ai != 31 {
b := d.data[d.off : d.off+int(val)]
@@ -868,7 +1043,7 @@ func (d *decodeState) parseTextString() ([]byte, error) {
return b, nil
}
-func (d *decodeState) parseArray() ([]interface{}, error) {
+func (d *decoder) parseArray() ([]interface{}, error) {
_, ai, val := d.getHead()
hasSize := (ai != 31)
count := int(val)
@@ -879,7 +1054,7 @@ func (d *decodeState) parseArray() ([]interface{}, error) {
var e interface{}
var err, lastErr error
for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
- if e, lastErr = d.parse(); lastErr != nil {
+ if e, lastErr = d.parse(true); lastErr != nil {
if err == nil {
err = lastErr
}
@@ -890,17 +1065,14 @@ func (d *decodeState) parseArray() ([]interface{}, error) {
return v, err
}
-func (d *decodeState) parseArrayToSlice(v reflect.Value, tInfo *typeInfo) error {
+func (d *decoder) parseArrayToSlice(v reflect.Value, tInfo *typeInfo) error {
_, ai, val := d.getHead()
hasSize := (ai != 31)
count := int(val)
if !hasSize {
count = d.numOfItemsUntilBreak() // peek ahead to get array size to preallocate slice for better performance
}
- if count == 0 {
- v.Set(reflect.MakeSlice(tInfo.nonPtrType, 0, 0))
- }
- if v.IsNil() || v.Cap() < count {
+ if v.IsNil() || v.Cap() < count || count == 0 {
v.Set(reflect.MakeSlice(tInfo.nonPtrType, count, count))
}
v.SetLen(count)
@@ -915,7 +1087,7 @@ func (d *decodeState) parseArrayToSlice(v reflect.Value, tInfo *typeInfo) error
return err
}
-func (d *decodeState) parseArrayToArray(v reflect.Value, tInfo *typeInfo) error {
+func (d *decoder) parseArrayToArray(v reflect.Value, tInfo *typeInfo) error {
_, ai, val := d.getHead()
hasSize := (ai != 31)
count := int(val)
@@ -945,7 +1117,7 @@ func (d *decodeState) parseArrayToArray(v reflect.Value, tInfo *typeInfo) error
return err
}
-func (d *decodeState) parseMap() (map[interface{}]interface{}, error) {
+func (d *decoder) parseMap() (map[interface{}]interface{}, error) {
_, ai, val := d.getHead()
hasSize := (ai != 31)
count := int(val)
@@ -955,7 +1127,7 @@ func (d *decodeState) parseMap() (map[interface{}]interface{}, error) {
keyCount := 0
for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
// Parse CBOR map key.
- if k, lastErr = d.parse(); lastErr != nil {
+ if k, lastErr = d.parse(true); lastErr != nil {
if err == nil {
err = lastErr
}
@@ -964,20 +1136,17 @@ func (d *decodeState) parseMap() (map[interface{}]interface{}, error) {
}
// Detect if CBOR map key can be used as Go map key.
- kkind := reflect.ValueOf(k).Kind()
- if tag, ok := k.(Tag); ok {
- kkind = tag.contentKind()
- }
- if !isHashableKind(kkind) {
+ rv := reflect.ValueOf(k)
+ if !isHashableValue(rv) {
if err == nil {
- err = errors.New("cbor: invalid map key type: " + kkind.String())
+ err = errors.New("cbor: invalid map key type: " + rv.Type().String())
}
d.skip()
continue
}
// Parse CBOR map value.
- if e, lastErr = d.parse(); lastErr != nil {
+ if e, lastErr = d.parse(true); lastErr != nil {
if err == nil {
err = lastErr
}
@@ -1007,7 +1176,7 @@ func (d *decodeState) parseMap() (map[interface{}]interface{}, error) {
return m, err
}
-func (d *decodeState) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
+func (d *decoder) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
_, ai, val := d.getHead()
hasSize := (ai != 31)
count := int(val)
@@ -1053,16 +1222,10 @@ func (d *decodeState) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //
}
// Detect if CBOR map key can be used as Go map key.
- if keyIsInterfaceType {
- kkind := keyValue.Elem().Kind()
- if keyValue.Elem().IsValid() {
- if tag, ok := keyValue.Elem().Interface().(Tag); ok {
- kkind = tag.contentKind()
- }
- }
- if !isHashableKind(kkind) {
+ if keyIsInterfaceType && keyValue.Elem().IsValid() {
+ if !isHashableValue(keyValue.Elem()) {
if err == nil {
- err = errors.New("cbor: invalid map key type: " + kkind.String())
+ err = errors.New("cbor: invalid map key type: " + keyValue.Elem().Type().String())
}
d.skip()
continue
@@ -1112,7 +1275,7 @@ func (d *decodeState) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //
return err
}
-func (d *decodeState) parseArrayToStruct(v reflect.Value, tInfo *typeInfo) error {
+func (d *decoder) parseArrayToStruct(v reflect.Value, tInfo *typeInfo) error {
structType := getDecodingStructType(tInfo.nonPtrType)
if structType.err != nil {
return structType.err
@@ -1122,9 +1285,9 @@ func (d *decodeState) parseArrayToStruct(v reflect.Value, tInfo *typeInfo) error
t := d.nextCBORType()
d.skip()
return &UnmarshalTypeError{
- Value: t.String(),
- Type: tInfo.nonPtrType,
- errMsg: "cannot decode CBOR array to struct without toarray option",
+ CBORType: t.String(),
+ GoType: tInfo.nonPtrType.String(),
+ errorMsg: "cannot decode CBOR array to struct without toarray option",
}
}
@@ -1139,27 +1302,41 @@ func (d *decodeState) parseArrayToStruct(v reflect.Value, tInfo *typeInfo) error
d.off = start
d.skip()
return &UnmarshalTypeError{
- Value: t.String(),
- Type: tInfo.typ,
- errMsg: "cannot decode CBOR array to struct with different number of elements",
+ CBORType: t.String(),
+ GoType: tInfo.typ.String(),
+ errorMsg: "cannot decode CBOR array to struct with different number of elements",
}
}
- var err error
+ var err, lastErr error
for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
f := structType.fields[i]
- fv, lastErr := fieldByIndex(v, f.idx)
- if lastErr != nil {
- if err == nil {
+
+ // Get field value by index
+ var fv reflect.Value
+ if len(f.idx) == 1 {
+ fv = v.Field(f.idx[0])
+ } else {
+ fv, lastErr = getFieldValue(v, f.idx, func(v reflect.Value) (reflect.Value, error) {
+ // Return a new value for embedded field null pointer to point to, or return error.
+ if !v.CanSet() {
+ return reflect.Value{}, errors.New("cbor: cannot set embedded pointer to unexported struct: " + v.Type().String())
+ }
+ v.Set(reflect.New(v.Type().Elem()))
+ return v, nil
+ })
+ if lastErr != nil && err == nil {
err = lastErr
}
- d.skip()
- continue
+ if !fv.IsValid() {
+ d.skip()
+ continue
+ }
}
- if lastErr := d.parseToValue(fv, f.typInfo); lastErr != nil {
+
+ if lastErr = d.parseToValue(fv, f.typInfo); lastErr != nil {
if err == nil {
if typeError, ok := lastErr.(*UnmarshalTypeError); ok {
- typeError.Struct = tInfo.typ.String()
- typeError.Field = f.name
+ typeError.StructFieldName = tInfo.typ.String() + "." + f.name
err = typeError
} else {
err = lastErr
@@ -1171,7 +1348,7 @@ func (d *decodeState) parseArrayToStruct(v reflect.Value, tInfo *typeInfo) error
}
// parseMapToStruct needs to be fast so gocyclo can be ignored for now.
-func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
+func (d *decoder) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
structType := getDecodingStructType(tInfo.nonPtrType)
if structType.err != nil {
return structType.err
@@ -1181,22 +1358,31 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
t := d.nextCBORType()
d.skip()
return &UnmarshalTypeError{
- Value: t.String(),
- Type: tInfo.nonPtrType,
- errMsg: "cannot decode CBOR map to struct with toarray option",
+ CBORType: t.String(),
+ GoType: tInfo.nonPtrType.String(),
+ errorMsg: "cannot decode CBOR map to struct with toarray option",
}
}
- foundFldIdx := make([]bool, len(structType.fields))
+ var err, lastErr error
+
+ // Get CBOR map size
_, ai, val := d.getHead()
hasSize := (ai != 31)
count := int(val)
- var err, lastErr error
+
+ // Keeps track of matched struct fields
+ foundFldIdx := make([]bool, len(structType.fields))
+
+ // Keeps track of CBOR map keys to detect duplicate map key
keyCount := 0
- var mapKeys map[interface{}]struct{} // Store map keys, used for detecting duplicate map key.
+ var mapKeys map[interface{}]struct{}
if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
mapKeys = make(map[interface{}]struct{}, len(structType.fields))
}
+
+ errOnUnknownField := (d.dm.extraReturnErrors & ExtraDecErrorUnknownField) > 0
+
for j := 0; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ {
var f *field
var k interface{} // Used by duplicate map key detection
@@ -1250,9 +1436,9 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
if val > math.MaxInt64 {
if err == nil {
err = &UnmarshalTypeError{
- Value: t.String(),
- Type: reflect.TypeOf(int64(0)),
- errMsg: "-1-" + strconv.FormatUint(val, 10) + " overflows Go's int64",
+ CBORType: t.String(),
+ GoType: reflect.TypeOf(int64(0)).String(),
+ errorMsg: "-1-" + strconv.FormatUint(val, 10) + " overflows Go's int64",
}
}
d.skip() // skip value
@@ -1277,24 +1463,20 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
} else {
if err == nil {
err = &UnmarshalTypeError{
- Value: t.String(),
- Type: reflect.TypeOf(""),
- errMsg: "map key is of type " + t.String() + " and cannot be used to match struct field name",
+ CBORType: t.String(),
+ GoType: reflect.TypeOf("").String(),
+ errorMsg: "map key is of type " + t.String() + " and cannot be used to match struct field name",
}
}
if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
// parse key
- k, lastErr = d.parse()
+ k, lastErr = d.parse(true)
if lastErr != nil {
d.skip() // skip value
continue
}
// Detect if CBOR map key can be used as Go map key.
- kkind := reflect.ValueOf(k).Kind()
- if tag, ok := k.(Tag); ok {
- kkind = tag.contentKind()
- }
- if !isHashableKind(kkind) {
+ if !isHashableValue(reflect.ValueOf(k)) {
d.skip() // skip value
continue
}
@@ -1321,24 +1503,47 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
}
if f == nil {
+ if errOnUnknownField {
+ err = &UnknownFieldError{j}
+ d.skip() // Skip value
+ j++
+ // skip the rest of the map
+ for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ {
+ d.skip()
+ d.skip()
+ }
+ return err
+ }
d.skip() // Skip value
continue
}
- // reflect.Value.FieldByIndex() panics at nil pointer to unexported
- // anonymous field. fieldByIndex() returns error.
- fv, lastErr := fieldByIndex(v, f.idx)
- if lastErr != nil {
- if err == nil {
+
+ // Get field value by index
+ var fv reflect.Value
+ if len(f.idx) == 1 {
+ fv = v.Field(f.idx[0])
+ } else {
+ fv, lastErr = getFieldValue(v, f.idx, func(v reflect.Value) (reflect.Value, error) {
+ // Return a new value for embedded field null pointer to point to, or return error.
+ if !v.CanSet() {
+ return reflect.Value{}, errors.New("cbor: cannot set embedded pointer to unexported struct: " + v.Type().String())
+ }
+ v.Set(reflect.New(v.Type().Elem()))
+ return v, nil
+ })
+ if lastErr != nil && err == nil {
err = lastErr
}
- d.skip()
- continue
+ if !fv.IsValid() {
+ d.skip()
+ continue
+ }
}
+
if lastErr = d.parseToValue(fv, f.typInfo); lastErr != nil {
if err == nil {
if typeError, ok := lastErr.(*UnmarshalTypeError); ok {
- typeError.Struct = tInfo.nonPtrType.String()
- typeError.Field = f.name
+ typeError.StructFieldName = tInfo.nonPtrType.String() + "." + f.name
err = typeError
} else {
err = lastErr
@@ -1351,36 +1556,30 @@ func (d *decodeState) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error {
// validRegisteredTagNums verifies that tag numbers match registered tag numbers of type t.
// validRegisteredTagNums assumes next CBOR data type is tag. It scans all tag numbers, and stops at tag content.
-func (d *decodeState) validRegisteredTagNums(t reflect.Type, registeredTagNums []uint64) error {
+func (d *decoder) validRegisteredTagNums(registeredTag *tagItem) error {
// Scan until next cbor data is tag content.
- tagNums := make([]uint64, 0, 2)
+ tagNums := make([]uint64, 0, 1)
for d.nextCBORType() == cborTypeTag {
_, _, val := d.getHead()
tagNums = append(tagNums, val)
}
- // Verify that tag numbers match registered tag numbers of type t
- if len(tagNums) != len(registeredTagNums) {
- return &WrongTagError{t, registeredTagNums, tagNums}
- }
- for i, n := range registeredTagNums {
- if n != tagNums[i] {
- return &WrongTagError{t, registeredTagNums, tagNums}
- }
+ if !registeredTag.equalTagNum(tagNums) {
+ return &WrongTagError{registeredTag.contentType, registeredTag.num, tagNums}
}
return nil
}
-func (d *decodeState) getRegisteredTagItem(vt reflect.Type) *tagItem {
+func (d *decoder) getRegisteredTagItem(vt reflect.Type) *tagItem {
if d.dm.tags != nil {
- return d.dm.tags.get(vt)
+ return d.dm.tags.getTagItemFromType(vt)
}
return nil
}
// skip moves data offset to the next item. skip assumes data is well-formed,
// and does not perform bounds checking.
-func (d *decodeState) skip() {
+func (d *decoder) skip() {
t, ai, val := d.getHead()
if ai == 31 {
@@ -1413,7 +1612,7 @@ func (d *decodeState) skip() {
}
// getHead assumes data is well-formed, and does not perform bounds checking.
-func (d *decodeState) getHead() (t cborType, ai byte, val uint64) {
+func (d *decoder) getHead() (t cborType, ai byte, val uint64) {
t = cborType(d.data[d.off] & 0xe0)
ai = d.data[d.off] & 0x1f
val = uint64(ai)
@@ -1445,7 +1644,7 @@ func (d *decodeState) getHead() (t cborType, ai byte, val uint64) {
return
}
-func (d *decodeState) numOfItemsUntilBreak() int {
+func (d *decoder) numOfItemsUntilBreak() int {
savedOff := d.off
i := 0
for !d.foundBreak() {
@@ -1457,7 +1656,7 @@ func (d *decodeState) numOfItemsUntilBreak() int {
}
// foundBreak assumes data is well-formed, and does not perform bounds checking.
-func (d *decodeState) foundBreak() bool {
+func (d *decoder) foundBreak() bool {
if d.data[d.off] == 0xff {
d.off++
return true
@@ -1465,22 +1664,23 @@ func (d *decodeState) foundBreak() bool {
return false
}
-func (d *decodeState) reset(data []byte) {
+func (d *decoder) reset(data []byte) {
d.data = data
d.off = 0
}
-func (d *decodeState) nextCBORType() cborType {
+func (d *decoder) nextCBORType() cborType {
return cborType(d.data[d.off] & 0xe0)
}
-func (d *decodeState) nextCBORNil() bool {
+func (d *decoder) nextCBORNil() bool {
return d.data[d.off] == 0xf6 || d.data[d.off] == 0xf7
}
var (
typeIntf = reflect.TypeOf([]interface{}(nil)).Elem()
typeTime = reflect.TypeOf(time.Time{})
+ typeBigInt = reflect.TypeOf(big.Int{})
typeUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
typeBinaryUnmarshaler = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
)
@@ -1498,16 +1698,28 @@ func fillPositiveInt(t cborType, val uint64, v reflect.Value) error {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if val > math.MaxInt64 {
- return &UnmarshalTypeError{Value: t.String(), Type: v.Type(), errMsg: strconv.FormatUint(val, 10) + " overflows " + v.Type().String()}
+ return &UnmarshalTypeError{
+ CBORType: t.String(),
+ GoType: v.Type().String(),
+ errorMsg: strconv.FormatUint(val, 10) + " overflows " + v.Type().String(),
+ }
}
if v.OverflowInt(int64(val)) {
- return &UnmarshalTypeError{Value: t.String(), Type: v.Type(), errMsg: strconv.FormatUint(val, 10) + " overflows " + v.Type().String()}
+ return &UnmarshalTypeError{
+ CBORType: t.String(),
+ GoType: v.Type().String(),
+ errorMsg: strconv.FormatUint(val, 10) + " overflows " + v.Type().String(),
+ }
}
v.SetInt(int64(val))
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if v.OverflowUint(val) {
- return &UnmarshalTypeError{Value: t.String(), Type: v.Type(), errMsg: strconv.FormatUint(val, 10) + " overflows " + v.Type().String()}
+ return &UnmarshalTypeError{
+ CBORType: t.String(),
+ GoType: v.Type().String(),
+ errorMsg: strconv.FormatUint(val, 10) + " overflows " + v.Type().String(),
+ }
}
v.SetUint(val)
return nil
@@ -1516,14 +1728,23 @@ func fillPositiveInt(t cborType, val uint64, v reflect.Value) error {
v.SetFloat(f)
return nil
}
- return &UnmarshalTypeError{Value: t.String(), Type: v.Type()}
+ if v.Type() == typeBigInt {
+ i := new(big.Int).SetUint64(val)
+ v.Set(reflect.ValueOf(*i))
+ return nil
+ }
+ return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()}
}
func fillNegativeInt(t cborType, val int64, v reflect.Value) error {
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if v.OverflowInt(val) {
- return &UnmarshalTypeError{Value: t.String(), Type: v.Type(), errMsg: strconv.FormatInt(val, 10) + " overflows " + v.Type().String()}
+ return &UnmarshalTypeError{
+ CBORType: t.String(),
+ GoType: v.Type().String(),
+ errorMsg: strconv.FormatInt(val, 10) + " overflows " + v.Type().String(),
+ }
}
v.SetInt(val)
return nil
@@ -1532,7 +1753,12 @@ func fillNegativeInt(t cborType, val int64, v reflect.Value) error {
v.SetFloat(f)
return nil
}
- return &UnmarshalTypeError{Value: t.String(), Type: v.Type()}
+ if v.Type() == typeBigInt {
+ i := new(big.Int).SetInt64(val)
+ v.Set(reflect.ValueOf(*i))
+ return nil
+ }
+ return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()}
}
func fillBool(t cborType, val bool, v reflect.Value) error {
@@ -1540,7 +1766,7 @@ func fillBool(t cborType, val bool, v reflect.Value) error {
v.SetBool(val)
return nil
}
- return &UnmarshalTypeError{Value: t.String(), Type: v.Type()}
+ return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()}
}
func fillFloat(t cborType, val float64, v reflect.Value) error {
@@ -1548,15 +1774,15 @@ func fillFloat(t cborType, val float64, v reflect.Value) error {
case reflect.Float32, reflect.Float64:
if v.OverflowFloat(val) {
return &UnmarshalTypeError{
- Value: t.String(),
- Type: v.Type(),
- errMsg: strconv.FormatFloat(val, 'E', -1, 64) + " overflows " + v.Type().String(),
+ CBORType: t.String(),
+ GoType: v.Type().String(),
+ errorMsg: strconv.FormatFloat(val, 'E', -1, 64) + " overflows " + v.Type().String(),
}
}
v.SetFloat(val)
return nil
}
- return &UnmarshalTypeError{Value: t.String(), Type: v.Type()}
+ return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()}
}
func fillByteString(t cborType, val []byte, v reflect.Value) error {
@@ -1588,7 +1814,7 @@ func fillByteString(t cborType, val []byte, v reflect.Value) error {
}
return nil
}
- return &UnmarshalTypeError{Value: t.String(), Type: v.Type()}
+ return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()}
}
func fillTextString(t cborType, val []byte, v reflect.Value) error {
@@ -1596,7 +1822,7 @@ func fillTextString(t cborType, val []byte, v reflect.Value) error {
v.SetString(string(val))
return nil
}
- return &UnmarshalTypeError{Value: t.String(), Type: v.Type()}
+ return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()}
}
func isImmutableKind(k reflect.Kind) bool {
@@ -1612,31 +1838,44 @@ func isImmutableKind(k reflect.Kind) bool {
}
}
-func isHashableKind(k reflect.Kind) bool {
- switch k {
+func isHashableValue(rv reflect.Value) bool {
+ switch rv.Kind() {
case reflect.Slice, reflect.Map, reflect.Func:
return false
- default:
- return true
+ case reflect.Struct:
+ switch rv.Type() {
+ case typeTag:
+ tag := rv.Interface().(Tag)
+ return isHashableValue(reflect.ValueOf(tag.Content))
+ case typeBigInt:
+ return false
+ }
}
+ return true
}
-// fieldByIndex returns the nested field corresponding to the index. It
-// allocates pointer to struct field if it is nil and settable.
-// reflect.Value.FieldByIndex() panics at nil pointer to unexported anonymous
-// field. This function returns error.
-func fieldByIndex(v reflect.Value, index []int) (reflect.Value, error) {
- for _, i := range index {
- if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct {
- if v.IsNil() {
- if !v.CanSet() {
- return reflect.Value{}, errors.New("cbor: cannot set embedded pointer to unexported struct: " + v.Type().String())
- }
- v.Set(reflect.New(v.Type().Elem()))
- }
- v = v.Elem()
+// validBuiltinTag checks that supported built-in tag numbers are followed by expected content types.
+func validBuiltinTag(tagNum uint64, contentHead byte) error {
+ t := cborType(contentHead & 0xe0)
+ switch tagNum {
+ case 0:
+ // Tag content (date/time text string in RFC 3339 format) must be string type.
+ if t != cborTypeTextString {
+ return errors.New("cbor: tag number 0 must be followed by text string, got " + t.String())
+ }
+ return nil
+ case 1:
+ // Tag content (epoch date/time) must be uint, int, or float type.
+ if t != cborTypePositiveInt && t != cborTypeNegativeInt && (contentHead < 0xf9 || contentHead > 0xfb) {
+ return errors.New("cbor: tag number 1 must be followed by integer or floating-point number, got " + t.String())
}
- v = v.Field(i)
+ return nil
+ case 2, 3:
+ // Tag content (bignum) must be byte type.
+ if t != cborTypeByteString {
+ return errors.New("cbor: tag number 2 or 3 must be followed by byte string, got " + t.String())
+ }
+ return nil
}
- return v, nil
+ return nil
}
diff --git a/vendor/github.com/fxamacker/cbor/v2/doc.go b/vendor/github.com/fxamacker/cbor/v2/doc.go
index fbb65a5aa91..51db0e036d2 100644
--- a/vendor/github.com/fxamacker/cbor/v2/doc.go
+++ b/vendor/github.com/fxamacker/cbor/v2/doc.go
@@ -7,33 +7,45 @@ standard API + toarray & keyasint struct tags, CBOR tags, float64->32->16,
CTAP2 & Canonical CBOR, duplicate map key options, and is customizable via
simple API.
-CBOR encoding options allow "preferred serialization" by encoding integers and floats
-to their smallest forms (like float16) when values fit.
+Encoding options allow "preferred serialization" by encoding integers and floats
+to their smallest forms (e.g. float16) when values fit.
-Struct tags like "keyasint", "toarray" and "omitempty" makes CBOR data smaller.
+Struct tags like "keyasint", "toarray" and "omitempty" make CBOR data smaller
+and easier to use with structs.
-For example, "toarray" makes struct fields encode to array elements. And "keyasint"
-makes struct fields encode to elements of CBOR map with int keys.
+For example, "toarray" tag makes struct fields encode to CBOR array elements. And
+"keyasint" makes a field encode to an element of CBOR map with specified int key.
+
+Latest docs can be viewed at https://github.com/fxamacker/cbor#cbor-library-in-go
Basics
+The Quick Start guide is at https://github.com/fxamacker/cbor#quick-start
+
Function signatures identical to encoding/json include:
- Marshal, Unmarshal, NewEncoder, NewDecoder, encoder.Encode, decoder.Decode.
+ Marshal, Unmarshal, NewEncoder, NewDecoder, (*Encoder).Encode, (*Decoder).Decode.
+
+Standard interfaces include:
+
+ BinaryMarshaler, BinaryUnmarshaler, Marshaler, and Unmarshaler.
-Codec functions are available at package-level (using defaults) or by creating modes
-from options at runtime.
+Custom encoding and decoding is possible by implementing standard interfaces for
+user-defined Go types.
-"Mode" in this API means definite way of encoding or decoding. Specifically, EncMode or DecMode.
+Codec functions are available at package-level (using defaults options) or by
+creating modes from options at runtime.
-EncMode and DecMode interfaces are created from EncOptions or DecOptions structs. For example,
+"Mode" in this API means definite way of encoding (EncMode) or decoding (DecMode).
+
+EncMode and DecMode interfaces are created from EncOptions or DecOptions structs.
em := cbor.EncOptions{...}.EncMode()
em := cbor.CanonicalEncOptions().EncMode()
em := cbor.CTAP2EncOptions().EncMode()
-Modes use immutable options to avoid side-effects and simplify concurrency. Behavior of modes
-won't accidentally change at runtime after they're created.
+Modes use immutable options to avoid side-effects and simplify concurrency. Behavior of
+modes won't accidentally change at runtime after they're created.
Modes are intended to be reused and are safe for concurrent use.
@@ -84,9 +96,16 @@ Creating and Using Encoding Modes
encoder := em.NewEncoder(w)
err := encoder.Encode(v)
-Default Options
+ // NOTE: Both em.Marshal(v) and encoder.Encode(v) use encoding options
+ // specified during creation of em (encoding mode).
+
+CBOR Options
+
+Predefined Encoding Options: https://github.com/fxamacker/cbor#predefined-encoding-options
-Default encoding options are listed at https://github.com/fxamacker/cbor#api
+Encoding Options: https://github.com/fxamacker/cbor#encoding-options
+
+Decoding Options: https://github.com/fxamacker/cbor#decoding-options
Struct Tags
@@ -101,9 +120,11 @@ makes struct fields encode to elements of CBOR map with int keys.
https://raw.githubusercontent.com/fxamacker/images/master/cbor/v2.0.0/cbor_easy_api.png
+Struct tags are listed at https://github.com/fxamacker/cbor#struct-tags-1
+
Tests and Fuzzing
-Over 375 tests are included in this package. Cover-guided fuzzing is handled by a separate package:
+Over 375 tests are included in this package. Cover-guided fuzzing is handled by
fxamacker/cbor-fuzz.
*/
package cbor
diff --git a/vendor/github.com/fxamacker/cbor/v2/encode.go b/vendor/github.com/fxamacker/cbor/v2/encode.go
index de52b545985..95d2c23f5b9 100644
--- a/vendor/github.com/fxamacker/cbor/v2/encode.go
+++ b/vendor/github.com/fxamacker/cbor/v2/encode.go
@@ -10,6 +10,7 @@ import (
"errors"
"io"
"math"
+ "math/big"
"reflect"
"sort"
"strconv"
@@ -19,9 +20,16 @@ import (
"github.com/x448/float16"
)
-// Marshal returns the CBOR encoding of v using the default encoding options.
+// Marshal returns the CBOR encoding of v using default encoding options.
+// See EncOptions for encoding options.
//
-// Marshal uses the following type-dependent default encodings:
+// Marshal uses the following encoding rules:
+//
+// If value implements the Marshaler interface, Marshal calls its
+// MarshalCBOR method.
+//
+// If value implements encoding.BinaryMarshaler, Marhsal calls its
+// MarshalBinary method and encode it as CBOR byte string.
//
// Boolean values encode as CBOR booleans (type 7).
//
@@ -41,24 +49,15 @@ import (
//
// Struct values encode as CBOR maps (type 5). Each exported struct field
// becomes a pair with field name encoded as CBOR text string (type 3) and
-// field value encoded based on its type.
-//
-// Pointer values encode as the value pointed to.
-//
-// Nil slice/map/pointer/interface values encode as CBOR nulls (type 7).
-//
-// time.Time values encode as text strings specified in RFC3339 when
-// EncOptions.TimeRFC3339 is true; otherwise, time.Time values encode as
-// numerical representation of seconds since January 1, 1970 UTC.
-//
-// If value implements the Marshaler interface, Marshal calls its MarshalCBOR
-// method. If value implements encoding.BinaryMarshaler instead, Marhsal
-// calls its MarshalBinary method and encode it as CBOR byte string.
+// field value encoded based on its type. See struct tag option "keyasint"
+// to encode field name as CBOR integer (type 0 and 1). Also see struct
+// tag option "toarray" for special field "_" to encode struct values as
+// CBOR array (type 4).
//
// Marshal supports format string stored under the "cbor" key in the struct
-// field's tag. CBOR format string can specify the name of the field, "omitempty"
-// and "keyasint" options, and special case "-" for field omission. If "cbor"
-// key is absent, Marshal uses "json" key.
+// field's tag. CBOR format string can specify the name of the field,
+// "omitempty" and "keyasint" options, and special case "-" for field omission.
+// If "cbor" key is absent, Marshal uses "json" key.
//
// Struct field name is treated as integer if it has "keyasint" option in
// its format string. The format string must specify an integer as its
@@ -69,17 +68,27 @@ import (
// "omitempty" is disabled by "toarray" to ensure that the same number
// of elements are encoded every time.
//
-// Anonymous struct fields are usually marshaled as if their exported fields
+// Anonymous struct fields are marshaled as if their exported fields
// were fields in the outer struct. Marshal follows the same struct fields
-// visibility rules used by JSON encoding package. An anonymous struct field
-// with a name given in its CBOR tag is treated as having that name, rather
-// than being anonymous. An anonymous struct field of interface type is
-// treated the same as having that type as its name, rather than being anonymous.
+// visibility rules used by JSON encoding package.
+//
+// time.Time values encode as text strings specified in RFC3339 or numerical
+// representation of seconds since January 1, 1970 UTC depending on
+// EncOptions.Time setting. Also See EncOptions.TimeTag to encode
+// time.Time as CBOR tag with tag number 0 or 1.
//
-// Interface values encode as the value contained in the interface. A nil
-// interface value encodes as the null CBOR value.
+// big.Int values encode as CBOR integers (type 0 and 1) if values fit.
+// Otherwise, big.Int values encode as CBOR bignums (tag 2 and 3). See
+// EncOptions.BigIntConvert to always encode big.Int values as CBOR
+// bignums.
//
-// Channel, complex, and functon values cannot be encoded in CBOR. Attempting
+// Pointer values encode as the value pointed to.
+//
+// Interface values encode as the value stored in the interface.
+//
+// Nil slice/map/pointer/interface values encode as CBOR nulls (type 7).
+//
+// Values of other types cannot be encoded in CBOR. Attempting
// to encode such a value causes Marshal to return an UnsupportedTypeError.
func Marshal(v interface{}) ([]byte, error) {
return defaultEncMode.Marshal(v)
@@ -91,8 +100,8 @@ type Marshaler interface {
MarshalCBOR() ([]byte, error)
}
-// UnsupportedTypeError is returned by Marshal when attempting to encode an
-// unsupported value type.
+// UnsupportedTypeError is returned by Marshal when attempting to encode value
+// of an unsupported type.
type UnsupportedTypeError struct {
Type reflect.Type
}
@@ -235,6 +244,26 @@ func (tm TimeMode) valid() bool {
return tm < maxTimeMode
}
+// BigIntConvertMode specifies how to encode big.Int values.
+type BigIntConvertMode int
+
+const (
+ // BigIntConvertShortest makes big.Int encode to CBOR integer if value fits.
+ // E.g. if big.Int value can be converted to CBOR integer while preserving
+ // value, encoder will encode it to CBOR interger (major type 0 or 1).
+ BigIntConvertShortest BigIntConvertMode = iota
+
+ // BigIntConvertNone makes big.Int encode to CBOR bignum (tag 2 or 3) without
+ // converting it to another CBOR type.
+ BigIntConvertNone
+
+ maxBigIntConvert
+)
+
+func (bim BigIntConvertMode) valid() bool {
+ return bim < maxBigIntConvert
+}
+
// EncOptions specifies encoding options.
type EncOptions struct {
// Sort specifies sorting order.
@@ -250,6 +279,9 @@ type EncOptions struct {
// InfConvert specifies how to encode Inf and it overrides ShortestFloatMode.
InfConvert InfConvertMode
+ // BigIntConvert specifies how to encode big.Int values.
+ BigIntConvert BigIntConvertMode
+
// Time specifies how to encode time.Time.
Time TimeMode
@@ -420,6 +452,9 @@ func (opts EncOptions) encMode() (*encMode, error) {
if !opts.InfConvert.valid() {
return nil, errors.New("cbor: invalid InfConvertMode " + strconv.Itoa(int(opts.InfConvert)))
}
+ if !opts.BigIntConvert.valid() {
+ return nil, errors.New("cbor: invalid BigIntConvertMode " + strconv.Itoa(int(opts.BigIntConvert)))
+ }
if !opts.Time.valid() {
return nil, errors.New("cbor: invalid TimeMode " + strconv.Itoa(int(opts.Time)))
}
@@ -440,6 +475,7 @@ func (opts EncOptions) encMode() (*encMode, error) {
shortestFloat: opts.ShortestFloat,
nanConvert: opts.NaNConvert,
infConvert: opts.InfConvert,
+ bigIntConvert: opts.BigIntConvert,
time: opts.Time,
timeTag: opts.TimeTag,
indefLength: opts.IndefLength,
@@ -461,6 +497,7 @@ type encMode struct {
shortestFloat ShortestFloatMode
nanConvert NaNConvertMode
infConvert InfConvertMode
+ bigIntConvert BigIntConvertMode
time TimeMode
timeTag EncTagMode
indefLength IndefLengthMode
@@ -476,6 +513,7 @@ func (em *encMode) EncOptions() EncOptions {
ShortestFloat: em.shortestFloat,
NaNConvert: em.nanConvert,
InfConvert: em.infConvert,
+ BigIntConvert: em.bigIntConvert,
Time: em.time,
TimeTag: em.timeTag,
IndefLength: em.indefLength,
@@ -485,62 +523,61 @@ func (em *encMode) EncOptions() EncOptions {
func (em *encMode) encTagBytes(t reflect.Type) []byte {
if em.tags != nil {
- if tagItem := em.tags.get(t); tagItem != nil {
+ if tagItem := em.tags.getTagItemFromType(t); tagItem != nil {
return tagItem.cborTagNum
}
}
return nil
}
-// Marshal returns the CBOR encoding of v using em encMode.
+// Marshal returns the CBOR encoding of v using em encoding mode.
//
// See the documentation for Marshal for details.
func (em *encMode) Marshal(v interface{}) ([]byte, error) {
- e := getEncodeState()
+ e := getEncoderBuffer()
if err := encode(e, em, reflect.ValueOf(v)); err != nil {
- putEncodeState(e)
+ putEncoderBuffer(e)
return nil, err
}
buf := make([]byte, e.Len())
copy(buf, e.Bytes())
- putEncodeState(e)
+ putEncoderBuffer(e)
return buf, nil
}
// NewEncoder returns a new encoder that writes to w using em EncMode.
func (em *encMode) NewEncoder(w io.Writer) *Encoder {
- return &Encoder{w: w, em: em, e: getEncodeState()}
+ return &Encoder{w: w, em: em, e: getEncoderBuffer()}
}
-// An encodeState encodes CBOR into a bytes.Buffer.
-type encodeState struct {
+type encoderBuffer struct {
bytes.Buffer
scratch [16]byte
}
-// encodeStatePool caches unused encodeState objects for later reuse.
-var encodeStatePool = sync.Pool{
+// encoderBufferPool caches unused encoderBuffer objects for later reuse.
+var encoderBufferPool = sync.Pool{
New: func() interface{} {
- e := new(encodeState)
+ e := new(encoderBuffer)
e.Grow(32) // TODO: make this configurable
return e
},
}
-func getEncodeState() *encodeState {
- return encodeStatePool.Get().(*encodeState)
+func getEncoderBuffer() *encoderBuffer {
+ return encoderBufferPool.Get().(*encoderBuffer)
}
-// putEncodeState returns e to encodeStatePool.
-func putEncodeState(e *encodeState) {
+func putEncoderBuffer(e *encoderBuffer) {
e.Reset()
- encodeStatePool.Put(e)
+ encoderBufferPool.Put(e)
}
-type encodeFunc func(e *encodeState, em *encMode, v reflect.Value) error
+type encodeFunc func(e *encoderBuffer, em *encMode, v reflect.Value) error
+type isEmptyFunc func(v reflect.Value) (empty bool, err error)
var (
cborFalse = []byte{0xf4}
@@ -551,14 +588,14 @@ var (
cborNegativeInfinity = []byte{0xf9, 0xfc, 0x00}
)
-func encode(e *encodeState, em *encMode, v reflect.Value) error {
+func encode(e *encoderBuffer, em *encMode, v reflect.Value) error {
if !v.IsValid() {
// v is zero value
e.Write(cborNil)
return nil
}
vt := v.Type()
- f := getEncodeFunc(vt)
+ f, _ := getEncodeFunc(vt)
if f == nil {
return &UnsupportedTypeError{vt}
}
@@ -566,7 +603,7 @@ func encode(e *encodeState, em *encMode, v reflect.Value) error {
return f(e, em, v)
}
-func encodeBool(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeBool(e *encoderBuffer, em *encMode, v reflect.Value) error {
if b := em.encTagBytes(v.Type()); b != nil {
e.Write(b)
}
@@ -578,7 +615,7 @@ func encodeBool(e *encodeState, em *encMode, v reflect.Value) error {
return nil
}
-func encodeInt(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeInt(e *encoderBuffer, em *encMode, v reflect.Value) error {
if b := em.encTagBytes(v.Type()); b != nil {
e.Write(b)
}
@@ -592,7 +629,7 @@ func encodeInt(e *encodeState, em *encMode, v reflect.Value) error {
return nil
}
-func encodeUint(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeUint(e *encoderBuffer, em *encMode, v reflect.Value) error {
if b := em.encTagBytes(v.Type()); b != nil {
e.Write(b)
}
@@ -600,7 +637,7 @@ func encodeUint(e *encodeState, em *encMode, v reflect.Value) error {
return nil
}
-func encodeFloat(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeFloat(e *encoderBuffer, em *encMode, v reflect.Value) error {
if b := em.encTagBytes(v.Type()); b != nil {
e.Write(b)
}
@@ -653,7 +690,7 @@ func encodeFloat(e *encodeState, em *encMode, v reflect.Value) error {
return nil
}
-func encodeInf(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeInf(e *encoderBuffer, em *encMode, v reflect.Value) error {
f64 := v.Float()
if em.infConvert == InfConvertFloat16 {
if f64 > 0 {
@@ -669,7 +706,7 @@ func encodeInf(e *encodeState, em *encMode, v reflect.Value) error {
return encodeFloat32(e, float32(f64))
}
-func encodeNaN(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeNaN(e *encoderBuffer, em *encMode, v reflect.Value) error {
switch em.nanConvert {
case NaNConvert7e00:
e.Write(cborNaN)
@@ -727,28 +764,28 @@ func encodeNaN(e *encodeState, em *encMode, v reflect.Value) error {
}
}
-func encodeFloat16(e *encodeState, f16 float16.Float16) error {
+func encodeFloat16(e *encoderBuffer, f16 float16.Float16) error {
e.scratch[0] = byte(cborTypePrimitives) | byte(25)
binary.BigEndian.PutUint16(e.scratch[1:], uint16(f16))
e.Write(e.scratch[:3])
return nil
}
-func encodeFloat32(e *encodeState, f32 float32) error {
+func encodeFloat32(e *encoderBuffer, f32 float32) error {
e.scratch[0] = byte(cborTypePrimitives) | byte(26)
binary.BigEndian.PutUint32(e.scratch[1:], math.Float32bits(f32))
e.Write(e.scratch[:5])
return nil
}
-func encodeFloat64(e *encodeState, f64 float64) error {
+func encodeFloat64(e *encoderBuffer, f64 float64) error {
e.scratch[0] = byte(cborTypePrimitives) | byte(27)
binary.BigEndian.PutUint64(e.scratch[1:], math.Float64bits(f64))
e.Write(e.scratch[:9])
return nil
}
-func encodeByteString(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeByteString(e *encoderBuffer, em *encMode, v reflect.Value) error {
vk := v.Kind()
if vk == reflect.Slice && v.IsNil() {
e.Write(cborNil)
@@ -772,7 +809,7 @@ func encodeByteString(e *encodeState, em *encMode, v reflect.Value) error {
return nil
}
-func encodeString(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeString(e *encoderBuffer, em *encMode, v reflect.Value) error {
if b := em.encTagBytes(v.Type()); b != nil {
e.Write(b)
}
@@ -782,12 +819,11 @@ func encodeString(e *encodeState, em *encMode, v reflect.Value) error {
return nil
}
-// Assuming that arrayEncoder.f != nil
-type arrayEncoder struct {
+type arrayEncodeFunc struct {
f encodeFunc
}
-func (ae arrayEncoder) encodeArray(e *encodeState, em *encMode, v reflect.Value) error {
+func (ae arrayEncodeFunc) encode(e *encoderBuffer, em *encMode, v reflect.Value) error {
if v.Kind() == reflect.Slice && v.IsNil() {
e.Write(cborNil)
return nil
@@ -808,12 +844,11 @@ func (ae arrayEncoder) encodeArray(e *encodeState, em *encMode, v reflect.Value)
return nil
}
-// Assuming that arrayEncoder.kf and arrayEncoder.ef are not nil
-type mapEncoder struct {
+type mapEncodeFunc struct {
kf, ef encodeFunc
}
-func (me mapEncoder) encodeMap(e *encodeState, em *encMode, v reflect.Value) error {
+func (me mapEncodeFunc) encode(e *encoderBuffer, em *encMode, v reflect.Value) error {
if v.IsNil() {
e.Write(cborNil)
return nil
@@ -826,7 +861,7 @@ func (me mapEncoder) encodeMap(e *encodeState, em *encMode, v reflect.Value) err
return e.WriteByte(byte(cborTypeMap))
}
if em.sort != SortNone {
- return me.encodeMapCanonical(e, em, v)
+ return me.encodeCanonical(e, em, v)
}
encodeHead(e, byte(cborTypeMap), uint64(mlen))
iter := v.MapRange()
@@ -906,21 +941,21 @@ func putKeyValues(x *[]keyValue) {
keyValuePool.Put(x)
}
-func (me mapEncoder) encodeMapCanonical(e *encodeState, em *encMode, v reflect.Value) error {
- kve := getEncodeState() // accumulated cbor encoded key-values
+func (me mapEncodeFunc) encodeCanonical(e *encoderBuffer, em *encMode, v reflect.Value) error {
+ kve := getEncoderBuffer() // accumulated cbor encoded key-values
kvsp := getKeyValues(v.Len()) // for sorting keys
kvs := *kvsp
iter := v.MapRange()
for i := 0; iter.Next(); i++ {
off := kve.Len()
if err := me.kf(kve, em, iter.Key()); err != nil {
- putEncodeState(kve)
+ putEncoderBuffer(kve)
putKeyValues(kvsp)
return err
}
n1 := kve.Len() - off
if err := me.ef(kve, em, iter.Value()); err != nil {
- putEncodeState(kve)
+ putEncoderBuffer(kve)
putKeyValues(kvsp)
return err
}
@@ -947,30 +982,42 @@ func (me mapEncoder) encodeMapCanonical(e *encodeState, em *encMode, v reflect.V
e.Write(kvs[i].keyValueCBORData)
}
- putEncodeState(kve)
+ putEncoderBuffer(kve)
putKeyValues(kvsp)
return nil
}
-func encodeStructToArray(e *encodeState, em *encMode, v reflect.Value, flds fields) error {
+func encodeStructToArray(e *encoderBuffer, em *encMode, v reflect.Value) (err error) {
+ structType, err := getEncodingStructType(v.Type())
+ if err != nil {
+ return err
+ }
+
+ if b := em.encTagBytes(v.Type()); b != nil {
+ e.Write(b)
+ }
+
+ flds := structType.fields
+
encodeHead(e, byte(cborTypeArray), uint64(len(flds)))
-FieldLoop:
for i := 0; i < len(flds); i++ {
f := flds[i]
- fv := v
- for k, n := range f.idx {
- if k > 0 {
- if fv.Kind() == reflect.Ptr && fv.Type().Elem().Kind() == reflect.Struct {
- if fv.IsNil() {
- // Write nil for null pointer to embedded struct
- e.Write(cborNil)
- continue FieldLoop
- }
- fv = fv.Elem()
- }
+
+ var fv reflect.Value
+ if len(f.idx) == 1 {
+ fv = v.Field(f.idx[0])
+ } else {
+ // Get embedded field value. No error is expected.
+ fv, _ = getFieldValue(v, f.idx, func(v reflect.Value) (reflect.Value, error) {
+ // Write CBOR nil for null pointer to embedded struct
+ e.Write(cborNil)
+ return reflect.Value{}, nil
+ })
+ if !fv.IsValid() {
+ continue
}
- fv = fv.Field(n)
}
+
if err := f.ef(e, em, fv); err != nil {
return err
}
@@ -978,7 +1025,11 @@ FieldLoop:
return nil
}
-func encodeFixedLengthStruct(e *encodeState, em *encMode, v reflect.Value, flds fields) error {
+func encodeFixedLengthStruct(e *encoderBuffer, em *encMode, v reflect.Value, flds fields) error {
+ if b := em.encTagBytes(v.Type()); b != nil {
+ e.Write(b)
+ }
+
encodeHead(e, byte(cborTypeMap), uint64(len(flds)))
for i := 0; i < len(flds); i++ {
@@ -994,66 +1045,69 @@ func encodeFixedLengthStruct(e *encodeState, em *encMode, v reflect.Value, flds
return nil
}
-func encodeStruct(e *encodeState, em *encMode, v reflect.Value) error {
- vt := v.Type()
- structType := getEncodingStructType(vt)
- if structType.err != nil {
- return structType.err
- }
-
- if b := em.encTagBytes(vt); b != nil {
- e.Write(b)
- }
-
- if structType.toArray {
- return encodeStructToArray(e, em, v, structType.fields)
+func encodeStruct(e *encoderBuffer, em *encMode, v reflect.Value) (err error) {
+ structType, err := getEncodingStructType(v.Type())
+ if err != nil {
+ return err
}
flds := structType.getFields(em)
- if !structType.hasAnonymousField && !structType.omitEmpty {
+ if structType.fixedLength {
return encodeFixedLengthStruct(e, em, v, flds)
}
- kve := getEncodeState() // encode key-value pairs based on struct field tag options
+ kve := getEncoderBuffer() // encode key-value pairs based on struct field tag options
kvcount := 0
-FieldLoop:
for i := 0; i < len(flds); i++ {
f := flds[i]
- fv := v
- for k, n := range f.idx {
- if k > 0 {
- if fv.Kind() == reflect.Ptr && fv.Type().Elem().Kind() == reflect.Struct {
- if fv.IsNil() {
- // Null pointer to embedded struct
- continue FieldLoop
- }
- fv = fv.Elem()
- }
+
+ var fv reflect.Value
+ if len(f.idx) == 1 {
+ fv = v.Field(f.idx[0])
+ } else {
+ // Get embedded field value. No error is expected.
+ fv, _ = getFieldValue(v, f.idx, func(v reflect.Value) (reflect.Value, error) {
+ // Skip null pointer to embedded struct
+ return reflect.Value{}, nil
+ })
+ if !fv.IsValid() {
+ continue
}
- fv = fv.Field(n)
}
- if f.omitEmpty && isEmptyValue(fv) {
- continue
+
+ if f.omitEmpty {
+ empty, err := f.ief(fv)
+ if err != nil {
+ putEncoderBuffer(kve)
+ return err
+ }
+ if empty {
+ continue
+ }
}
kve.Write(f.cborName)
if err := f.ef(kve, em, fv); err != nil {
- putEncodeState(kve)
+ putEncoderBuffer(kve)
return err
}
kvcount++
}
+ if b := em.encTagBytes(v.Type()); b != nil {
+ e.Write(b)
+ }
+
encodeHead(e, byte(cborTypeMap), uint64(kvcount))
e.Write(kve.Bytes())
- putEncodeState(kve)
+ putEncoderBuffer(kve)
return nil
}
-func encodeIntf(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeIntf(e *encoderBuffer, em *encMode, v reflect.Value) error {
if v.IsNil() {
e.Write(cborNil)
return nil
@@ -1061,7 +1115,7 @@ func encodeIntf(e *encodeState, em *encMode, v reflect.Value) error {
return encode(e, em, v.Elem())
}
-func encodeTime(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeTime(e *encoderBuffer, em *encMode, v reflect.Value) error {
t := v.Interface().(time.Time)
if t.IsZero() {
e.Write(cborNil) // Even if tag is required, encode as CBOR null.
@@ -1099,7 +1153,42 @@ func encodeTime(e *encodeState, em *encMode, v reflect.Value) error {
}
}
-func encodeBinaryMarshalerType(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeBigInt(e *encoderBuffer, em *encMode, v reflect.Value) error {
+ vbi := v.Interface().(big.Int)
+ sign := vbi.Sign()
+ bi := new(big.Int).SetBytes(vbi.Bytes()) // bi is absolute value of v
+ if sign < 0 {
+ // For negative number, convert to CBOR encoded number (-v-1).
+ bi.Sub(bi, big.NewInt(1))
+ }
+
+ if em.bigIntConvert == BigIntConvertShortest {
+ if bi.IsUint64() {
+ if sign >= 0 {
+ // Encode as CBOR pos int (major type 0)
+ encodeHead(e, byte(cborTypePositiveInt), bi.Uint64())
+ return nil
+ }
+ // Encode as CBOR neg int (major type 1)
+ encodeHead(e, byte(cborTypeNegativeInt), bi.Uint64())
+ return nil
+ }
+ }
+
+ tagNum := 2
+ if sign < 0 {
+ tagNum = 3
+ }
+ // Write tag number
+ encodeHead(e, byte(cborTypeTag), uint64(tagNum))
+ // Write bignum byte string
+ b := bi.Bytes()
+ encodeHead(e, byte(cborTypeByteString), uint64(len(b)))
+ e.Write(b)
+ return nil
+}
+
+func encodeBinaryMarshalerType(e *encoderBuffer, em *encMode, v reflect.Value) error {
vt := v.Type()
m, ok := v.Interface().(encoding.BinaryMarshaler)
if !ok {
@@ -1119,7 +1208,7 @@ func encodeBinaryMarshalerType(e *encodeState, em *encMode, v reflect.Value) err
return nil
}
-func encodeMarshalerType(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeMarshalerType(e *encoderBuffer, em *encMode, v reflect.Value) error {
if em.tagsMd == TagsForbidden && v.Type() == typeRawTag {
return errors.New("cbor: cannot encode cbor.RawTag when TagsMd is TagsForbidden")
}
@@ -1137,13 +1226,19 @@ func encodeMarshalerType(e *encodeState, em *encMode, v reflect.Value) error {
return nil
}
-func encodeTag(e *encodeState, em *encMode, v reflect.Value) error {
+func encodeTag(e *encoderBuffer, em *encMode, v reflect.Value) error {
if em.tagsMd == TagsForbidden {
return errors.New("cbor: cannot encode cbor.Tag when TagsMd is TagsForbidden")
}
t := v.Interface().(Tag)
+ if t.Number == 0 && t.Content == nil {
+ // Marshal uninitialized cbor.Tag
+ e.Write(cborNil)
+ return nil
+ }
+
// Marshal tag number
encodeHead(e, byte(cborTypeTag), t.Number)
@@ -1155,7 +1250,7 @@ func encodeTag(e *encodeState, em *encMode, v reflect.Value) error {
return nil
}
-func encodeHead(e *encodeState, t byte, n uint64) {
+func encodeHead(e *encoderBuffer, t byte, n uint64) {
if n <= 23 {
e.WriteByte(t | byte(n))
return
@@ -1186,68 +1281,83 @@ func encodeHead(e *encodeState, t byte, n uint64) {
var (
typeMarshaler = reflect.TypeOf((*Marshaler)(nil)).Elem()
typeBinaryMarshaler = reflect.TypeOf((*encoding.BinaryMarshaler)(nil)).Elem()
+ typeRawMessage = reflect.TypeOf(RawMessage(nil))
)
-func getEncodeFuncInternal(t reflect.Type) encodeFunc {
+func getEncodeFuncInternal(t reflect.Type) (encodeFunc, isEmptyFunc) {
k := t.Kind()
if k == reflect.Ptr {
- return getEncodeIndirectValueFunc(t)
- }
- if t == typeTag {
- return encodeTag
- }
- if t == typeTime {
- return encodeTime
+ return getEncodeIndirectValueFunc(t), isEmptyPtr
+ }
+ switch t {
+ case typeTag:
+ return encodeTag, alwaysNotEmpty
+ case typeTime:
+ return encodeTime, alwaysNotEmpty
+ case typeBigInt:
+ return encodeBigInt, alwaysNotEmpty
+ case typeRawMessage:
+ return encodeMarshalerType, isEmptySlice
}
if reflect.PtrTo(t).Implements(typeMarshaler) {
- return encodeMarshalerType
+ return encodeMarshalerType, alwaysNotEmpty
}
if reflect.PtrTo(t).Implements(typeBinaryMarshaler) {
- return encodeBinaryMarshalerType
+ return encodeBinaryMarshalerType, isEmptyBinaryMarshaler
}
switch k {
case reflect.Bool:
- return encodeBool
+ return encodeBool, isEmptyBool
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return encodeInt
+ return encodeInt, isEmptyInt
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- return encodeUint
+ return encodeUint, isEmptyUint
case reflect.Float32, reflect.Float64:
- return encodeFloat
+ return encodeFloat, isEmptyFloat
case reflect.String:
- return encodeString
+ return encodeString, isEmptyString
case reflect.Slice, reflect.Array:
if t.Elem().Kind() == reflect.Uint8 {
- return encodeByteString
+ return encodeByteString, isEmptySlice
}
- f := getEncodeFunc(t.Elem())
+ f, _ := getEncodeFunc(t.Elem())
if f == nil {
- return nil
+ return nil, nil
}
- return arrayEncoder{f: f}.encodeArray
+ return arrayEncodeFunc{f: f}.encode, isEmptySlice
case reflect.Map:
- kf, ef := getEncodeFunc(t.Key()), getEncodeFunc(t.Elem())
+ kf, _ := getEncodeFunc(t.Key())
+ ef, _ := getEncodeFunc(t.Elem())
if kf == nil || ef == nil {
- return nil
+ return nil, nil
}
- return mapEncoder{kf: kf, ef: ef}.encodeMap
+ return mapEncodeFunc{kf: kf, ef: ef}.encode, isEmptyMap
case reflect.Struct:
- return encodeStruct
+ // Get struct's special field "_" tag options
+ if f, ok := t.FieldByName("_"); ok {
+ tag := f.Tag.Get("cbor")
+ if tag != "-" {
+ if hasToArrayOption(tag) {
+ return encodeStructToArray, isEmptyStruct
+ }
+ }
+ }
+ return encodeStruct, isEmptyStruct
case reflect.Interface:
- return encodeIntf
+ return encodeIntf, isEmptyIntf
}
- return nil
+ return nil, nil
}
func getEncodeIndirectValueFunc(t reflect.Type) encodeFunc {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
- f := getEncodeFunc(t)
+ f, _ := getEncodeFunc(t)
if f == nil {
return nil
}
- return func(e *encodeState, em *encMode, v reflect.Value) error {
+ return func(e *encoderBuffer, em *encMode, v reflect.Value) error {
for v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
@@ -1259,22 +1369,101 @@ func getEncodeIndirectValueFunc(t reflect.Type) encodeFunc {
}
}
-func isEmptyValue(v reflect.Value) bool {
- switch v.Kind() {
- case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
- return v.Len() == 0
- case reflect.Bool:
- return !v.Bool()
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return v.Int() == 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return v.Uint() == 0
- case reflect.Float32, reflect.Float64:
- return v.Float() == 0
- case reflect.Interface, reflect.Ptr:
- return v.IsNil()
+func alwaysNotEmpty(v reflect.Value) (empty bool, err error) {
+ return false, nil
+}
+
+func isEmptyBool(v reflect.Value) (bool, error) {
+ return !v.Bool(), nil
+}
+
+func isEmptyInt(v reflect.Value) (bool, error) {
+ return v.Int() == 0, nil
+}
+
+func isEmptyUint(v reflect.Value) (bool, error) {
+ return v.Uint() == 0, nil
+}
+
+func isEmptyFloat(v reflect.Value) (bool, error) {
+ return v.Float() == 0.0, nil
+}
+
+func isEmptyString(v reflect.Value) (bool, error) {
+ return v.Len() == 0, nil
+}
+
+func isEmptySlice(v reflect.Value) (bool, error) {
+ return v.Len() == 0, nil
+}
+
+func isEmptyMap(v reflect.Value) (bool, error) {
+ return v.Len() == 0, nil
+}
+
+func isEmptyPtr(v reflect.Value) (bool, error) {
+ return v.IsNil(), nil
+}
+
+func isEmptyIntf(v reflect.Value) (bool, error) {
+ return v.IsNil(), nil
+}
+
+func isEmptyStruct(v reflect.Value) (bool, error) {
+ structType, err := getEncodingStructType(v.Type())
+ if err != nil {
+ return false, err
+ }
+
+ if structType.toArray {
+ return len(structType.fields) == 0, nil
+ }
+
+ if len(structType.fields) > len(structType.omitEmptyFieldsIdx) {
+ return false, nil
+ }
+
+ for _, i := range structType.omitEmptyFieldsIdx {
+ f := structType.fields[i]
+
+ // Get field value
+ var fv reflect.Value
+ if len(f.idx) == 1 {
+ fv = v.Field(f.idx[0])
+ } else {
+ // Get embedded field value. No error is expected.
+ fv, _ = getFieldValue(v, f.idx, func(v reflect.Value) (reflect.Value, error) {
+ // Skip null pointer to embedded struct
+ return reflect.Value{}, nil
+ })
+ if !fv.IsValid() {
+ continue
+ }
+ }
+
+ empty, err := f.ief(fv)
+ if err != nil {
+ return false, err
+ }
+ if !empty {
+ return false, nil
+ }
+ }
+ return true, nil
+}
+
+func isEmptyBinaryMarshaler(v reflect.Value) (bool, error) {
+ m, ok := v.Interface().(encoding.BinaryMarshaler)
+ if !ok {
+ pv := reflect.New(v.Type())
+ pv.Elem().Set(v)
+ m = pv.Interface().(encoding.BinaryMarshaler)
+ }
+ data, err := m.MarshalBinary()
+ if err != nil {
+ return false, err
}
- return false
+ return len(data) == 0, nil
}
func cannotFitFloat32(f64 float64) bool {
diff --git a/vendor/github.com/fxamacker/cbor/v2/stream.go b/vendor/github.com/fxamacker/cbor/v2/stream.go
index 62319333cf7..29172b92dc4 100644
--- a/vendor/github.com/fxamacker/cbor/v2/stream.go
+++ b/vendor/github.com/fxamacker/cbor/v2/stream.go
@@ -9,22 +9,22 @@ import (
"reflect"
)
-// Decoder reads and decodes CBOR values from an input stream.
+// Decoder reads and decodes CBOR values from io.Reader.
type Decoder struct {
r io.Reader
+ d decoder
buf []byte
- d decodeState
- off int // start of unread data in buf
+ off int // next read offset in buf
bytesRead int
}
-// NewDecoder returns a new decoder that reads from r using the default decoding options.
+// NewDecoder returns a new decoder that reads and decodes from r using
+// the default decoding options.
func NewDecoder(r io.Reader) *Decoder {
return defaultDecMode.NewDecoder(r)
}
-// Decode reads the next CBOR-encoded value from its input and stores it in
-// the value pointed to by v.
+// Decode reads CBOR value and decodes it into the value pointed to by v.
func (dec *Decoder) Decode(v interface{}) error {
if len(dec.buf) == dec.off {
if n, err := dec.read(); n == 0 {
@@ -55,19 +55,17 @@ func (dec *Decoder) NumBytesRead() int {
}
func (dec *Decoder) read() (int, error) {
- // Copy unread data over read data and reset off to 0.
- if dec.off > 0 {
- n := copy(dec.buf, dec.buf[dec.off:])
- dec.buf = dec.buf[:n]
- dec.off = 0
- }
-
// Grow buf if needed.
const minRead = 512
- if cap(dec.buf)-len(dec.buf) < minRead {
- newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
- copy(newBuf, dec.buf)
- dec.buf = newBuf
+ if cap(dec.buf)-len(dec.buf)+dec.off < minRead {
+ oldUnreadBuf := dec.buf[dec.off:]
+ dec.buf = make([]byte, len(dec.buf)-dec.off, 2*cap(dec.buf)+minRead)
+ dec.overwriteBuf(oldUnreadBuf)
+ }
+
+ // Copy unread data over read data and reset off to 0.
+ if dec.off > 0 {
+ dec.overwriteBuf(dec.buf[dec.off:])
}
// Read from reader and reslice buf.
@@ -76,11 +74,17 @@ func (dec *Decoder) read() (int, error) {
return n, err
}
-// Encoder writes CBOR values to an output stream.
+func (dec *Decoder) overwriteBuf(newBuf []byte) {
+ n := copy(dec.buf, newBuf)
+ dec.buf = dec.buf[:n]
+ dec.off = 0
+}
+
+// Encoder writes CBOR values to io.Writer.
type Encoder struct {
w io.Writer
em *encMode
- e *encodeState
+ e *encoderBuffer
indefTypes []cborType
}
@@ -89,7 +93,7 @@ func NewEncoder(w io.Writer) *Encoder {
return defaultEncMode.NewEncoder(w)
}
-// Encode writes the CBOR encoding of v to the stream.
+// Encode writes the CBOR encoding of v.
func (enc *Encoder) Encode(v interface{}) error {
if len(enc.indefTypes) > 0 && v != nil {
indefType := enc.indefTypes[len(enc.indefTypes)-1]
@@ -173,12 +177,10 @@ func (enc *Encoder) startIndefinite(typ cborType) error {
return err
}
-// RawMessage is a raw encoded CBOR value. It implements Marshaler and
-// Unmarshaler interfaces and can be used to delay CBOR decoding or
-// precompute a CBOR encoding.
+// RawMessage is a raw encoded CBOR value.
type RawMessage []byte
-// MarshalCBOR returns m as the CBOR encoding of m.
+// MarshalCBOR returns m or CBOR nil if m is nil.
func (m RawMessage) MarshalCBOR() ([]byte, error) {
if len(m) == 0 {
return cborNil, nil
@@ -186,11 +188,12 @@ func (m RawMessage) MarshalCBOR() ([]byte, error) {
return m, nil
}
-// UnmarshalCBOR sets *m to a copy of data.
+// UnmarshalCBOR creates a copy of data and saves to *m.
func (m *RawMessage) UnmarshalCBOR(data []byte) error {
if m == nil {
return errors.New("cbor.RawMessage: UnmarshalCBOR on nil pointer")
}
- *m = append((*m)[0:0], data...)
+ *m = make([]byte, len(data))
+ copy(*m, data)
return nil
}
diff --git a/vendor/github.com/fxamacker/cbor/v2/structfields.go b/vendor/github.com/fxamacker/cbor/v2/structfields.go
index d73b7194625..7c5974f38aa 100644
--- a/vendor/github.com/fxamacker/cbor/v2/structfields.go
+++ b/vendor/github.com/fxamacker/cbor/v2/structfields.go
@@ -16,6 +16,7 @@ type field struct {
idx []int
typ reflect.Type
ef encodeFunc
+ ief isEmptyFunc
typInfo *typeInfo // used to decoder to reuse type info
tagged bool // used to choose dominant field (at the same level tagged fields dominate untagged fields)
omitEmpty bool // used to skip empty field
@@ -38,20 +39,13 @@ func (x *indexFieldSorter) Swap(i, j int) {
}
func (x *indexFieldSorter) Less(i, j int) bool {
- iIdx := x.fields[i].idx
- jIdx := x.fields[j].idx
- for k, d := range iIdx {
- if k >= len(jIdx) {
- // fields[j].idx is a subset of fields[i].idx.
- return false
- }
- if d != jIdx[k] {
- // fields[i].idx and fields[j].idx are different.
- return d < jIdx[k]
+ iIdx, jIdx := x.fields[i].idx, x.fields[j].idx
+ for k := 0; k < len(iIdx) && k < len(jIdx); k++ {
+ if iIdx[k] != jIdx[k] {
+ return iIdx[k] < jIdx[k]
}
}
- // fields[i].idx is either the same as, or a subset of fields[j].idx.
- return true
+ return len(iIdx) <= len(jIdx)
}
// nameLevelAndTagFieldSorter sorts fields by field name, idx depth, and presence of tag.
@@ -68,99 +62,54 @@ func (x *nameLevelAndTagFieldSorter) Swap(i, j int) {
}
func (x *nameLevelAndTagFieldSorter) Less(i, j int) bool {
- if x.fields[i].name != x.fields[j].name {
- return x.fields[i].name < x.fields[j].name
+ fi, fj := x.fields[i], x.fields[j]
+ if fi.name != fj.name {
+ return fi.name < fj.name
}
- if len(x.fields[i].idx) != len(x.fields[j].idx) {
- return len(x.fields[i].idx) < len(x.fields[j].idx)
+ if len(fi.idx) != len(fj.idx) {
+ return len(fi.idx) < len(fj.idx)
}
- if x.fields[i].tagged != x.fields[j].tagged {
- return x.fields[i].tagged
+ if fi.tagged != fj.tagged {
+ return fi.tagged
}
return i < j // Field i and j have the same name, depth, and tagged status. Nothing else matters.
}
-// getFields returns a list of visible fields of struct type typ following Go
-// visibility rules for struct fields.
-func getFields(typ reflect.Type) (flds fields, structOptions string) {
- // Inspired by typeFields() in stdlib's encoding/json/encode.go.
-
- var current map[reflect.Type][][]int // key: struct type, value: field index of this struct type at the same level
- next := map[reflect.Type][][]int{typ: nil}
- visited := map[reflect.Type]bool{} // Inspected struct type at less nested levels.
-
- for len(next) > 0 {
- current, next = next, map[reflect.Type][][]int{}
-
- for structType, structIdx := range current {
- if len(structIdx) > 1 {
- continue // Fields of the same embedded struct type at the same level are ignored.
- }
-
- if visited[structType] {
- continue
- }
- visited[structType] = true
+// getFields returns visible fields of struct type t following visibility rules for JSON encoding.
+func getFields(t reflect.Type) (flds fields, structOptions string) {
+ // Get special field "_" tag options
+ if f, ok := t.FieldByName("_"); ok {
+ tag := f.Tag.Get("cbor")
+ if tag != "-" {
+ structOptions = tag
+ }
+ }
- var fieldIdx []int
- if len(structIdx) > 0 {
- fieldIdx = structIdx[0]
- }
+ // nTypes contains next level anonymous fields' types and indexes
+ // (there can be multiple fields of the same type at the same level)
+ flds, nTypes := appendFields(t, nil, nil, nil)
- for i := 0; i < structType.NumField(); i++ {
- f := structType.Field(i)
- ft := f.Type
+ if len(nTypes) > 0 {
- if ft.Kind() == reflect.Ptr {
- ft = ft.Elem()
- }
+ var cTypes map[reflect.Type][][]int // current level anonymous fields' types and indexes
+ vTypes := map[reflect.Type]bool{t: true} // visited field types at less nested levels
- exportable := f.PkgPath == ""
- if f.Anonymous {
- if !exportable && ft.Kind() != reflect.Struct {
- // Nonexportable anonymous fields of non-struct type are ignored.
- continue
- }
- // Nonexportable anonymous field of struct type can contain exportable fields for serialization.
- } else if !exportable {
- // Get special field "_" struct options
- if f.Name == "_" {
- tag := f.Tag.Get("cbor")
- if tag != "-" {
- structOptions = tag
- }
- }
- // Nonexportable fields are ignored.
- continue
- }
+ for len(nTypes) > 0 {
+ cTypes, nTypes = nTypes, nil
- tag := f.Tag.Get("cbor")
- if tag == "" {
- tag = f.Tag.Get("json")
- }
- if tag == "-" {
+ for t, idx := range cTypes {
+ // If there are multiple anonymous fields of the same struct type at the same level, all are ignored.
+ if len(idx) > 1 {
continue
}
- idx := make([]int, len(fieldIdx)+1)
- copy(idx, fieldIdx)
- idx[len(fieldIdx)] = i
-
- tagged := len(tag) > 0
- tagFieldName, omitempty, keyasint := getFieldNameAndOptionsFromTag(tag)
-
- fieldName := tagFieldName
- if tagFieldName == "" {
- fieldName = f.Name
- }
-
- if !f.Anonymous || ft.Kind() != reflect.Struct || len(tagFieldName) > 0 {
- flds = append(flds, &field{name: fieldName, idx: idx, typ: f.Type, tagged: tagged, omitEmpty: omitempty, keyAsInt: keyasint})
+ // Anonymous field of the same type at deeper nested level is ignored.
+ if vTypes[t] {
continue
}
+ vTypes[t] = true
- // f is anonymous struct of type ft.
- next[ft] = append(next[ft], idx)
+ flds, nTypes = appendFields(t, idx[0], flds, nTypes)
}
}
}
@@ -168,43 +117,135 @@ func getFields(typ reflect.Type) (flds fields, structOptions string) {
sort.Sort(&nameLevelAndTagFieldSorter{flds})
// Keep visible fields.
- visibleFields := flds[:0]
- for i, j := 0, 0; i < len(flds); i = j {
+ j := 0 // index of next unique field
+ for i := 0; i < len(flds); {
name := flds[i].name
- for j = i + 1; j < len(flds) && flds[j].name == name; j++ {
+ if i == len(flds)-1 || // last field
+ name != flds[i+1].name || // field i has unique field name
+ len(flds[i].idx) < len(flds[i+1].idx) || // field i is at a less nested level than field i+1
+ (flds[i].tagged && !flds[i+1].tagged) { // field i is tagged while field i+1 is not
+ flds[j] = flds[i]
+ j++
}
- if j-i == 1 || len(flds[i].idx) < len(flds[i+1].idx) || (flds[i].tagged && !flds[i+1].tagged) {
- // Keep the field if the field name is unique, or if the first field
- // is at a less nested level, or if the first field is tagged and
- // the second field is not.
- visibleFields = append(visibleFields, flds[i])
+
+ // Skip fields with the same field name.
+ for i++; i < len(flds) && name == flds[i].name; i++ {
}
}
+ if j != len(flds) {
+ flds = flds[:j]
+ }
- sort.Sort(&indexFieldSorter{visibleFields})
+ // Sort fields by field index
+ sort.Sort(&indexFieldSorter{flds})
- return visibleFields, structOptions
+ return flds, structOptions
}
-func getFieldNameAndOptionsFromTag(tag string) (name string, omitEmpty bool, keyAsInt bool) {
- if tag == "" {
- return
- }
- idx := strings.Index(tag, ",")
- if idx == -1 {
- return tag, false, false
- }
- if idx > 0 {
- name = tag[:idx]
- tag = tag[idx:]
- }
- s := ",omitempty"
- if idx = strings.Index(tag, s); idx >= 0 && (len(tag) == idx+len(s) || tag[idx+len(s)] == ',') {
- omitEmpty = true
+// appendFields appends type t's exportable fields to flds and anonymous struct fields to nTypes .
+func appendFields(t reflect.Type, idx []int, flds fields, nTypes map[reflect.Type][][]int) (fields, map[reflect.Type][][]int) {
+ for i := 0; i < t.NumField(); i++ {
+ f := t.Field(i)
+
+ ft := f.Type
+ for ft.Kind() == reflect.Ptr {
+ ft = ft.Elem()
+ }
+
+ if !isFieldExportable(f, ft.Kind()) {
+ continue
+ }
+
+ tag := f.Tag.Get("cbor")
+ if tag == "" {
+ tag = f.Tag.Get("json")
+ }
+ if tag == "-" {
+ continue
+ }
+
+ tagged := len(tag) > 0
+
+ // Parse field tag options
+ var tagFieldName string
+ var omitempty, keyasint bool
+ for j := 0; len(tag) > 0; j++ {
+ var token string
+ idx := strings.IndexByte(tag, ',')
+ if idx == -1 {
+ token, tag = tag, ""
+ } else {
+ token, tag = tag[:idx], tag[idx+1:]
+ }
+ if j == 0 {
+ tagFieldName = token
+ } else {
+ switch token {
+ case "omitempty":
+ omitempty = true
+ case "keyasint":
+ keyasint = true
+ }
+ }
+ }
+
+ fieldName := tagFieldName
+ if tagFieldName == "" {
+ fieldName = f.Name
+ }
+
+ fIdx := make([]int, len(idx)+1)
+ copy(fIdx, idx)
+ fIdx[len(fIdx)-1] = i
+
+ if !f.Anonymous || ft.Kind() != reflect.Struct || len(tagFieldName) > 0 {
+ flds = append(flds, &field{
+ name: fieldName,
+ idx: fIdx,
+ typ: f.Type,
+ omitEmpty: omitempty,
+ keyAsInt: keyasint,
+ tagged: tagged})
+ } else {
+ if nTypes == nil {
+ nTypes = make(map[reflect.Type][][]int)
+ }
+ nTypes[ft] = append(nTypes[ft], fIdx)
+ }
}
- s = ",keyasint"
- if idx = strings.Index(tag, s); idx >= 0 && (len(tag) == idx+len(s) || tag[idx+len(s)] == ',') {
- keyAsInt = true
+
+ return flds, nTypes
+}
+
+// isFieldExportable returns true if f is an exportable (regular or anonymous) field or
+// a nonexportable anonymous field of struct type.
+// Nonexportable anonymous field of struct type can contain exportable fields.
+func isFieldExportable(f reflect.StructField, fk reflect.Kind) bool {
+ exportable := f.PkgPath == ""
+ return exportable || (f.Anonymous && fk == reflect.Struct)
+}
+
+type embeddedFieldNullPtrFunc func(reflect.Value) (reflect.Value, error)
+
+// getFieldValue returns field value of struct v by index. When encountering null pointer
+// to anonymous (embedded) struct field, f is called with the last traversed field value.
+func getFieldValue(v reflect.Value, idx []int, f embeddedFieldNullPtrFunc) (fv reflect.Value, err error) {
+ fv = v
+ for i, n := range idx {
+ fv = fv.Field(n)
+
+ if i < len(idx)-1 {
+ if fv.Kind() == reflect.Ptr && fv.Type().Elem().Kind() == reflect.Struct {
+ if fv.IsNil() {
+ // Null pointer to embedded struct field
+ fv, err = f(fv)
+ if err != nil || !fv.IsValid() {
+ return fv, err
+ }
+ }
+ fv = fv.Elem()
+ }
+ }
}
- return
+ return fv, nil
}
diff --git a/vendor/github.com/fxamacker/cbor/v2/tag.go b/vendor/github.com/fxamacker/cbor/v2/tag.go
index a8121824d38..db29149fdb1 100644
--- a/vendor/github.com/fxamacker/cbor/v2/tag.go
+++ b/vendor/github.com/fxamacker/cbor/v2/tag.go
@@ -13,18 +13,6 @@ type Tag struct {
Content interface{}
}
-func (t Tag) contentKind() reflect.Kind {
- c := t.Content
- for {
- t, ok := c.(Tag)
- if !ok {
- break
- }
- c = t.Content
- }
- return reflect.ValueOf(c).Kind()
-}
-
// RawTag represents CBOR tag data, including tag number and raw tag content.
// RawTag implements Unmarshaler and Marshaler interfaces.
type RawTag struct {
@@ -38,12 +26,17 @@ func (t *RawTag) UnmarshalCBOR(data []byte) error {
return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer")
}
- d := decodeState{data: data, dm: defaultDecMode}
+ // Decoding CBOR null and undefined to cbor.RawTag is no-op.
+ if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {
+ return nil
+ }
+
+ d := decoder{data: data, dm: defaultDecMode}
// Unmarshal tag number.
typ, _, num := d.getHead()
if typ != cborTypeTag {
- return &UnmarshalTypeError{Value: typ.String(), Type: typeRawTag}
+ return &UnmarshalTypeError{CBORType: typ.String(), GoType: typeRawTag.String()}
}
t.Number = num
@@ -56,14 +49,27 @@ func (t *RawTag) UnmarshalCBOR(data []byte) error {
// MarshalCBOR returns CBOR encoding of t.
func (t RawTag) MarshalCBOR() ([]byte, error) {
- e := getEncodeState()
+ if t.Number == 0 && len(t.Content) == 0 {
+ // Marshal uninitialized cbor.RawTag
+ b := make([]byte, len(cborNil))
+ copy(b, cborNil)
+ return b, nil
+ }
+
+ e := getEncoderBuffer()
+
encodeHead(e, byte(cborTypeTag), t.Number)
- buf := make([]byte, len(e.Bytes())+len(t.Content))
+ content := t.Content
+ if len(content) == 0 {
+ content = cborNil
+ }
+
+ buf := make([]byte, len(e.Bytes())+len(content))
n := copy(buf, e.Bytes())
- copy(buf[n:], t.Content)
+ copy(buf[n:], content)
- putEncodeState(e)
+ putEncoderBuffer(e)
return buf, nil
}
@@ -123,7 +129,8 @@ type TagSet interface {
}
type tagProvider interface {
- get(t reflect.Type) *tagItem
+ getTagItemFromType(t reflect.Type) *tagItem
+ getTypeFromTagNum(num []uint64) reflect.Type
}
type tagItem struct {
@@ -133,6 +140,25 @@ type tagItem struct {
opts TagOptions
}
+func (t *tagItem) equalTagNum(num []uint64) bool {
+ // Fast path to compare 1 tag number
+ if len(t.num) == 1 && len(num) == 1 && t.num[0] == num[0] {
+ return true
+ }
+
+ if len(t.num) != len(num) {
+ return false
+ }
+
+ for i := 0; i < len(t.num); i++ {
+ if t.num[i] != num[i] {
+ return false
+ }
+ }
+
+ return true
+}
+
type (
tagSet map[reflect.Type]*tagItem
@@ -142,10 +168,19 @@ type (
}
)
-func (t tagSet) get(typ reflect.Type) *tagItem {
+func (t tagSet) getTagItemFromType(typ reflect.Type) *tagItem {
return t[typ]
}
+func (t tagSet) getTypeFromTagNum(num []uint64) reflect.Type {
+ for typ, tag := range t {
+ if tag.equalTagNum(num) {
+ return typ
+ }
+ }
+ return nil
+}
+
// NewTagSet returns TagSet (safe for concurrency).
func NewTagSet() TagSet {
return &syncTagSet{t: make(map[reflect.Type]*tagItem)}
@@ -165,8 +200,13 @@ func (t *syncTagSet) Add(opts TagOptions, contentType reflect.Type, num uint64,
}
t.Lock()
defer t.Unlock()
- if _, ok := t.t[contentType]; ok {
- return errors.New("cbor: content type " + contentType.String() + " already exists in TagSet")
+ for typ, ti := range t.t {
+ if typ == contentType {
+ return errors.New("cbor: content type " + contentType.String() + " already exists in TagSet")
+ }
+ if ti.equalTagNum(tag.num) {
+ return fmt.Errorf("cbor: tag number %v already exists in TagSet", tag.num)
+ }
}
t.t[contentType] = tag
return nil
@@ -182,13 +222,20 @@ func (t *syncTagSet) Remove(contentType reflect.Type) {
t.Unlock()
}
-func (t *syncTagSet) get(typ reflect.Type) *tagItem {
+func (t *syncTagSet) getTagItemFromType(typ reflect.Type) *tagItem {
t.RLock()
ti := t.t[typ]
t.RUnlock()
return ti
}
+func (t *syncTagSet) getTypeFromTagNum(num []uint64) reflect.Type {
+ t.RLock()
+ rt := t.t.getTypeFromTagNum(num)
+ t.RUnlock()
+ return rt
+}
+
func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum ...uint64) (*tagItem, error) {
if opts.DecTag == DecTagIgnored && opts.EncTag == EncTagNone {
return nil, errors.New("cbor: cannot add tag with DecTagIgnored and EncTagNone options to TagSet")
@@ -199,6 +246,9 @@ func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum
if contentType == typeTime {
return nil, errors.New("cbor: cannot add time.Time to TagSet, use EncOptions.TimeTag and DecOptions.TimeTag instead")
}
+ if contentType == typeBigInt {
+ return nil, errors.New("cbor: cannot add big.Int to TagSet, it's built-in and supported automatically")
+ }
if contentType == typeTag {
return nil, errors.New("cbor: cannot add cbor.Tag to TagSet")
}
@@ -208,24 +258,30 @@ func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum
if num == 0 || num == 1 {
return nil, errors.New("cbor: cannot add tag number 0 or 1 to TagSet, use EncOptions.TimeTag and DecOptions.TimeTag instead")
}
- if reflect.PtrTo(contentType).Implements(typeMarshaler) && opts.EncTag != EncTagNone {
- return nil, errors.New("cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone")
+ if num == 2 || num == 3 {
+ return nil, errors.New("cbor: cannot add tag number 2 or 3 to TagSet, it's built-in and supported automatically")
}
- if reflect.PtrTo(contentType).Implements(typeUnmarshaler) && opts.DecTag != DecTagIgnored {
- return nil, errors.New("cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored")
+ if num == selfDescribedCBORTagNum {
+ return nil, errors.New("cbor: cannot add tag number 55799 to TagSet, it's built-in and ignored automatically")
}
+ //if reflect.PtrTo(contentType).Implements(typeMarshaler) && opts.EncTag != EncTagNone {
+ //return nil, errors.New("cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone")
+ //}
+ //if reflect.PtrTo(contentType).Implements(typeUnmarshaler) && opts.DecTag != DecTagIgnored {
+ //return nil, errors.New("cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored")
+ //}
te := tagItem{num: []uint64{num}, opts: opts, contentType: contentType}
te.num = append(te.num, nestedNum...)
// Cache encoded tag numbers
- e := getEncodeState()
+ e := getEncoderBuffer()
for _, n := range te.num {
encodeHead(e, byte(cborTypeTag), n)
}
te.cborTagNum = make([]byte, e.Len())
copy(te.cborTagNum, e.Bytes())
- putEncodeState(e)
+ putEncoderBuffer(e)
return &te, nil
}
diff --git a/vendor/github.com/fxamacker/cbor/v2/valid.go b/vendor/github.com/fxamacker/cbor/v2/valid.go
index 0d243bb6917..f775afb0ea3 100644
--- a/vendor/github.com/fxamacker/cbor/v2/valid.go
+++ b/vendor/github.com/fxamacker/cbor/v2/valid.go
@@ -68,8 +68,8 @@ func (e *TagsMdError) Error() string {
return "cbor: CBOR tag isn't allowed"
}
-// valid checks whether CBOR data is complete and well-formed.
-func (d *decodeState) valid() error {
+// valid checks whether the CBOR data is complete and well-formed.
+func (d *decoder) valid() error {
if len(d.data) == d.off {
return io.EOF
}
@@ -78,7 +78,7 @@ func (d *decodeState) valid() error {
}
// validInternal checks data's well-formedness and returns max depth and error.
-func (d *decodeState) validInternal(depth int) (int, error) {
+func (d *decoder) validInternal(depth int) (int, error) {
t, ai, val, err := d.validHead()
if err != nil {
return 0, err
@@ -175,7 +175,7 @@ func (d *decodeState) validInternal(depth int) (int, error) {
}
// validIndefiniteString checks indefinite length byte/text string's well-formedness and returns max depth and error.
-func (d *decodeState) validIndefiniteString(t cborType, depth int) (int, error) {
+func (d *decoder) validIndefiniteString(t cborType, depth int) (int, error) {
var err error
for {
if len(d.data) == d.off {
@@ -201,7 +201,7 @@ func (d *decodeState) validIndefiniteString(t cborType, depth int) (int, error)
}
// validIndefiniteArrayOrMap checks indefinite length array/map's well-formedness and returns max depth and error.
-func (d *decodeState) validIndefiniteArrayOrMap(t cborType, depth int) (int, error) {
+func (d *decoder) validIndefiniteArrayOrMap(t cborType, depth int) (int, error) {
var err error
maxDepth := depth
i := 0
@@ -237,7 +237,7 @@ func (d *decodeState) validIndefiniteArrayOrMap(t cborType, depth int) (int, err
return maxDepth, nil
}
-func (d *decodeState) validHead() (t cborType, ai byte, val uint64, err error) {
+func (d *decoder) validHead() (t cborType, ai byte, val uint64, err error) {
dataLen := len(d.data) - d.off
if dataLen == 0 {
return 0, 0, 0, io.ErrUnexpectedEOF
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 026157fe33d..6d9e3421fd4 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -79,7 +79,7 @@ github.com/fiorix/go-smpp/smpp/pdu/pdutlv
# github.com/fsnotify/fsnotify v1.4.9
## explicit; go 1.13
github.com/fsnotify/fsnotify
-# github.com/fxamacker/cbor/v2 v2.2.0
+# github.com/fxamacker/cbor/v2 v2.3.0
## explicit; go 1.12
github.com/fxamacker/cbor/v2
# github.com/go-kit/kit v0.11.0