unmarshal.go 3.8 KB
Newer Older
1
package dagcbor
Eric Myhre's avatar
Eric Myhre committed
2 3

import (
4
	"errors"
Eric Myhre's avatar
Eric Myhre committed
5 6 7
	"fmt"
	"math"

8
	cid "github.com/ipfs/go-cid"
Eric Myhre's avatar
Eric Myhre committed
9 10 11
	"github.com/polydawn/refmt/shared"
	"github.com/polydawn/refmt/tok"

12 13
	ipld "github.com/ipld/go-ipld-prime"
	cidlink "github.com/ipld/go-ipld-prime/linking/cid"
Eric Myhre's avatar
Eric Myhre committed
14 15
)

16 17 18
var (
	ErrInvalidMultibase = errors.New("invalid multibase on IPLD link")
)
Eric Myhre's avatar
Eric Myhre committed
19

20 21 22
// This should be identical to the general feature in the parent package,
// except for the `case tok.TBytes` block,
// which has dag-cbor's special sauce for detecting schemafree links.
Eric Myhre's avatar
Eric Myhre committed
23 24 25 26 27 28 29 30

func Unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource) error {
	var tk tok.Token
	done, err := tokSrc.Step(&tk)
	if err != nil {
		return err
	}
	if done && !tk.Type.IsValue() {
31
		return fmt.Errorf("unexpected eof")
Eric Myhre's avatar
Eric Myhre committed
32 33 34 35 36 37 38
	}
	return unmarshal(na, tokSrc, &tk)
}

// starts with the first token already primed.  Necessary to get recursion
//  to flow right without a peek+unpeek system.
func unmarshal(na ipld.NodeAssembler, tokSrc shared.TokenSource, tk *tok.Token) error {
39
	// FUTURE: check for schema.TypedNodeBuilder that's going to parse a Link (they can slurp any token kind they want).
Eric Myhre's avatar
Eric Myhre committed
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
	switch tk.Type {
	case tok.TMapOpen:
		expectLen := tk.Length
		allocLen := tk.Length
		if tk.Length == -1 {
			expectLen = math.MaxInt32
			allocLen = 0
		}
		ma, err := na.BeginMap(allocLen)
		if err != nil {
			return err
		}
		observedLen := 0
		for {
			_, err := tokSrc.Step(tk)
			if err != nil {
				return err
			}
			switch tk.Type {
			case tok.TMapClose:
				if expectLen != math.MaxInt32 && observedLen != expectLen {
					return fmt.Errorf("unexpected mapClose before declared length")
				}
				return ma.Finish()
			case tok.TString:
				// continue
			default:
				return fmt.Errorf("unexpected %s token while expecting map key", tk.Type)
			}
			observedLen++
			if observedLen > expectLen {
				return fmt.Errorf("unexpected continuation of map elements beyond declared length")
			}
			mva, err := ma.AssembleDirectly(tk.Str)
			if err != nil { // return in error if the key was rejected
				return err
			}
			err = Unmarshal(mva, tokSrc)
			if err != nil { // return in error if some part of the recursion errored
				return err
			}
		}
	case tok.TMapClose:
		return fmt.Errorf("unexpected mapClose token")
	case tok.TArrOpen:
		expectLen := tk.Length
		allocLen := tk.Length
		if tk.Length == -1 {
			expectLen = math.MaxInt32
			allocLen = 0
		}
		la, err := na.BeginList(allocLen)
		if err != nil {
			return err
		}
		observedLen := 0
		for {
			_, err := tokSrc.Step(tk)
			if err != nil {
				return err
			}
			switch tk.Type {
			case tok.TArrClose:
				if expectLen != math.MaxInt32 && observedLen != expectLen {
					return fmt.Errorf("unexpected arrClose before declared length")
				}
				return la.Finish()
			default:
				observedLen++
				if observedLen > expectLen {
					return fmt.Errorf("unexpected continuation of array elements beyond declared length")
				}
				err := unmarshal(la.AssembleValue(), tokSrc, tk)
				if err != nil { // return in error if some part of the recursion errored
					return err
				}
			}
		}
	case tok.TArrClose:
		return fmt.Errorf("unexpected arrClose token")
	case tok.TNull:
		return na.AssignNull()
	case tok.TString:
		return na.AssignString(tk.Str)
	case tok.TBytes:
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
		if !tk.Tagged {
			return na.AssignBytes(tk.Bytes)
		}
		switch tk.Tag {
		case linkTag:
			if tk.Bytes[0] != 0 {
				return ErrInvalidMultibase
			}
			elCid, err := cid.Cast(tk.Bytes[1:])
			if err != nil {
				return err
			}
			return na.AssignLink(cidlink.Link{elCid})
		default:
			return fmt.Errorf("unhandled cbor tag %d", tk.Tag)
		}
Eric Myhre's avatar
Eric Myhre committed
141 142 143 144 145 146 147 148 149 150 151 152
	case tok.TBool:
		return na.AssignBool(tk.Bool)
	case tok.TInt:
		return na.AssignInt(int(tk.Int)) // FIXME overflow check
	case tok.TUint:
		return na.AssignInt(int(tk.Uint)) // FIXME overflow check
	case tok.TFloat64:
		return na.AssignFloat(tk.Float64)
	default:
		panic("unreachable")
	}
}