linking.go 5.87 KB
Newer Older
1 2
package ipld

3 4 5 6
import (
	"context"
	"io"
)
7

Eric Myhre's avatar
Eric Myhre committed
8 9
// This file contains all the functions on LinkSystem.
// These are the helpful, user-facing functions we expect folks to use "most of the time" when loading and storing data.
10

Eric Myhre's avatar
Eric Myhre committed
11 12 13 14 15 16 17
// Varations:
// - Load vs Store vs ComputeLink
// - With or without LinkContext?
//   - Brevity would be nice but I can't think of what to name the functions, so: everything takes LinkContext.  Zero value is fine though.
// - [for load direction only]: Prototype (and return Node|error) or Assembler (and just return error)?
//   - naming: Load vs Fill.
// - 'Must' variants.
18

Eric Myhre's avatar
Eric Myhre committed
19 20 21 22 23 24 25 26 27
// Can we get as far as a `QuickLoad(lnk Link) (Node, error)` function, which doesn't even ask you for a NodePrototype?
//  No, not quite.  (Alas.)  If we tried to do so, and make it use `basicnode.Prototype`, we'd have import cycles; ded.

func (lsys *LinkSystem) Load(lnkCtx LinkContext, lnk Link, np NodePrototype) (Node, error) {
	nb := np.NewBuilder()
	if err := lsys.Fill(lnkCtx, lnk, nb); err != nil {
		return nil, err
	}
	return nb.Build(), nil
28 29
}

Eric Myhre's avatar
Eric Myhre committed
30 31 32 33 34 35
func (lsys *LinkSystem) MustLoad(lnkCtx LinkContext, lnk Link, np NodePrototype) Node {
	if n, err := lsys.Load(lnkCtx, lnk, np); err != nil {
		panic(err)
	} else {
		return n
	}
36 37
}

Eric Myhre's avatar
Eric Myhre committed
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
func (lsys *LinkSystem) Fill(lnkCtx LinkContext, lnk Link, na NodeAssembler) error {
	if lnkCtx.Ctx == nil {
		lnkCtx.Ctx = context.Background()
	}
	// Choose all the parts.
	decoder, err := lsys.DecoderChooser(lnk)
	if err != nil {
		return ErrLinkingSetup{"could not choose a decoder", err}
	}
	hasher, err := lsys.HasherChooser(lnk.Prototype())
	if err != nil {
		return ErrLinkingSetup{"could not choose a hasher", err}
	}
	if lsys.StorageReadOpener == nil {
		return ErrLinkingSetup{"no storage configured for reading", io.ErrClosedPipe} // REVIEW: better cause?
	}
	// Open storage, read it, verify it, and feed the codec to assemble the nodes.
55 56
	//  We have two paths through this: if a `Bytes() []byte` method is handy, we'll assume it's faster than going through reader.
	//   These diverge significantly, because if we give up on streaming, it makes sense to do the full hash check first before decoding at all.
Eric Myhre's avatar
Eric Myhre committed
57 58 59 60
	reader, err := lsys.StorageReadOpener(lnkCtx, lnk)
	if err != nil {
		return err
	}
61 62 63 64 65 66 67 68
	if buf, ok := reader.(interface{ Bytes() []byte }); ok {
		// Flush everything to the hasher in one big slice.
		hasher.Write(buf.Bytes())
		hash := hasher.Sum(nil)
		// Bit of a jig to get something we can do the hash equality check on.
		lnk2 := lnk.Prototype().BuildLink(hash)
		if lnk2 != lnk {
			return ErrHashMismatch{Actual: lnk2, Expected: lnk}
Eric Myhre's avatar
Eric Myhre committed
69
		}
70
		// Perform decoding (knowing the hash is already verified).
71 72
		//  Note that the decoder recieves the same reader as we started with,
		//   and as a result, is also free to detect a `Bytes() []byte` accessor and do any optimizations it wishes to based on that.
73 74 75
		return decoder(na, reader)
	} else {
		// Tee the stream so that the hasher is fed as the unmarshal progresses through the stream.
76 77 78
		//  Note: the tee means *the decoder doesn't get to see the original reader type*.
		//   This is part of why the `Bytes() []byte` branch above is useful; the decoder loses any ability to do a similar check
		//    and optimization when the tee is in the middle.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
		tee := io.TeeReader(reader, hasher)
		decodeErr := decoder(na, tee)
		if decodeErr != nil { // It is important to security to check the hash before returning any other observation about the content.
			// This copy is for data remaining the block that wasn't already pulled through the TeeReader by the decoder.
			_, err := io.Copy(hasher, reader)
			if err != nil {
				return err
			}
		}
		hash := hasher.Sum(nil)
		// Bit of a jig to get something we can do the hash equality check on.
		lnk2 := lnk.Prototype().BuildLink(hash)
		if lnk2 != lnk {
			return ErrHashMismatch{Actual: lnk2, Expected: lnk}
		}
		if decodeErr != nil {
			return decodeErr
		}
		return nil
Eric Myhre's avatar
Eric Myhre committed
98 99
	}
}
100

Eric Myhre's avatar
Eric Myhre committed
101 102 103 104 105
func (lsys *LinkSystem) MustFill(lnkCtx LinkContext, lnk Link, na NodeAssembler) {
	if err := lsys.Fill(lnkCtx, lnk, na); err != nil {
		panic(err)
	}
}
106

Eric Myhre's avatar
Eric Myhre committed
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
func (lsys *LinkSystem) Store(lnkCtx LinkContext, lp LinkPrototype, n Node) (Link, error) {
	if lnkCtx.Ctx == nil {
		lnkCtx.Ctx = context.Background()
	}
	// Choose all the parts.
	encoder, err := lsys.EncoderChooser(lp)
	if err != nil {
		return nil, ErrLinkingSetup{"could not choose an encoder", err}
	}
	hasher, err := lsys.HasherChooser(lp)
	if err != nil {
		return nil, ErrLinkingSetup{"could not choose a hasher", err}
	}
	if lsys.StorageWriteOpener == nil {
		return nil, ErrLinkingSetup{"no storage configured for writing", io.ErrClosedPipe} // REVIEW: better cause?
	}
	// Open storage write stream, feed serial data to the storage and the hasher, and funnel the codec output into both.
	writer, commitFn, err := lsys.StorageWriteOpener(lnkCtx)
	if err != nil {
		return nil, err
	}
	tee := io.MultiWriter(writer, hasher)
	err = encoder(n, tee)
	if err != nil {
		return nil, err
	}
	lnk := lp.BuildLink(hasher.Sum(nil))
	return lnk, commitFn(lnk)
}
136

Eric Myhre's avatar
Eric Myhre committed
137 138 139 140 141 142
func (lsys *LinkSystem) MustStore(lnkCtx LinkContext, lp LinkPrototype, n Node) Link {
	if lnk, err := lsys.Store(lnkCtx, lp, n); err != nil {
		panic(err)
	} else {
		return lnk
	}
143 144
}

Eric Myhre's avatar
Eric Myhre committed
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
// ComputeLink returns a Link for the given data, but doesn't do anything else
// (e.g. it doesn't try to store any of the serial-form data anywhere else).
func (lsys *LinkSystem) ComputeLink(lp LinkPrototype, n Node) (Link, error) {
	encoder, err := lsys.EncoderChooser(lp)
	if err != nil {
		return nil, ErrLinkingSetup{"could not choose an encoder", err}
	}
	hasher, err := lsys.HasherChooser(lp)
	if err != nil {
		return nil, ErrLinkingSetup{"could not choose a hasher", err}
	}
	err = encoder(n, hasher)
	if err != nil {
		return nil, err
	}
	return lp.BuildLink(hasher.Sum(nil)), nil
}

func (lsys *LinkSystem) MustComputeLink(lp LinkPrototype, n Node) Link {
	if lnk, err := lsys.ComputeLink(lp, n); err != nil {
		panic(err)
	} else {
		return lnk
	}
}