From cfbfd82c0ece721606d94245c7a281c7cf0bc8e2 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Mon, 6 Aug 2018 12:29:37 -0700 Subject: [PATCH 001/291] it might work, idk yet This commit was moved from ipld/go-car@6dbe39639e51fe746484d45737435ef93fb6be02 --- ipld/car/README.md | 28 ++++++++++ ipld/car/car.go | 131 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 ipld/car/README.md create mode 100644 ipld/car/car.go diff --git a/ipld/car/README.md b/ipld/car/README.md new file mode 100644 index 0000000000..ffd258c9e7 --- /dev/null +++ b/ipld/car/README.md @@ -0,0 +1,28 @@ +go-car (go!) +================== + +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) +[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) +[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) +[![Coverage Status](https://codecov.io/gh/ipfs/go-car/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/go-car/branch/master) +[![Travis CI](https://travis-ci.org/ipfs/go-car.svg?branch=master)](https://travis-ci.org/ipfs/go-car) + +> go-car is a simple way of packing a merkledag into a single file + + +## Table of Contents + +- [Install](#install) +- [Contribute](#contribute) +- [License](#license) + + +## Contribute + +PRs are welcome! + +Small note: If editing the Readme, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. + +## License + +MIT © Whyrusleeping diff --git a/ipld/car/car.go b/ipld/car/car.go new file mode 100644 index 0000000000..dfac121097 --- /dev/null +++ b/ipld/car/car.go @@ -0,0 +1,131 @@ +package car + +import ( + "archive/tar" + "context" + "fmt" + "io" + "io/ioutil" + + "github.com/ipfs/go-block-format" + cid "github.com/ipfs/go-cid" + bstore "github.com/ipfs/go-ipfs-blockstore" + format "github.com/ipfs/go-ipld-format" + dag "github.com/ipfs/go-merkledag" +) + +func WriteCar(ctx context.Context, ds format.DAGService, root *cid.Cid, w io.Writer) error { + tw := tar.NewWriter(w) + + rh := &tar.Header{ + Typeflag: tar.TypeSymlink, + Name: "root", + Linkname: root.String(), + } + if err := tw.WriteHeader(rh); err != nil { + return err + } + + cw := &carWriter{ds: ds, tw: tw} + + seen := cid.NewSet() + if err := dag.EnumerateChildren(ctx, cw.enumGetLinks, root, seen.Visit); err != nil { + return err + } + + return tw.Flush() +} + +func LoadCar(ctx context.Context, bs bstore.Blockstore, r io.Reader) (*cid.Cid, error) { + tr := tar.NewReader(r) + root, err := tr.Next() + if err != nil { + return nil, err + } + + if root.Name != "root" || root.Typeflag != tar.TypeSymlink { + return nil, fmt.Errorf("expected first entry in CAR to by symlink named 'root'") + } + + rootcid, err := cid.Decode(root.Linkname) + if err != nil { + return nil, err + } + + for { + obj, err := tr.Next() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + + c, err := cid.Decode(obj.Name) + if err != nil { + return nil, err + } + + // safety 1st + limr := io.LimitReader(tr, 2<<20) + data, err := ioutil.ReadAll(limr) + if err != nil { + return nil, err + } + + hashed, err := c.Prefix().Sum(data) + if err != nil { + return nil, err + } + + if !hashed.Equals(c) { + return nil, fmt.Errorf("mismatch in content integrity, name: %s, data: %s", c, hashed) + } + + blk, err := blocks.NewBlockWithCid(data, c) + if err != nil { + return nil, err + } + + if err := bs.Put(blk); err != nil { + return nil, err + } + } + + return rootcid, nil +} + +type carWriter struct { + ds format.DAGService + tw *tar.Writer +} + +func (cw *carWriter) enumGetLinks(ctx context.Context, c *cid.Cid) ([]*format.Link, error) { + nd, err := cw.ds.Get(ctx, c) + if err != nil { + return nil, err + } + + if err := cw.writeNode(ctx, nd); err != nil { + return nil, err + } + + return nd.Links(), nil +} + +func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { + hdr := &tar.Header{ + Name: nd.Cid().String(), + Typeflag: tar.TypeReg, + } + + if err := cw.tw.WriteHeader(hdr); err != nil { + return err + } + + if _, err := cw.tw.Write(nd.RawData()); err != nil { + return err + } + + return nil +} From d9b1bb9fb86bc18bab3eb6c99d03bbba69e5704b Mon Sep 17 00:00:00 2001 From: Jeromy Date: Thu, 9 Aug 2018 23:22:06 -0700 Subject: [PATCH 002/291] gx publish 1.0.1 This commit was moved from ipld/go-car@ba1ec22f5cf62d8732da86eee6974f079fe8d2fb --- ipld/car/car.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ipld/car/car.go b/ipld/car/car.go index dfac121097..3f1996aac5 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -117,6 +117,7 @@ func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { hdr := &tar.Header{ Name: nd.Cid().String(), Typeflag: tar.TypeReg, + Size: int64(len(nd.RawData())), } if err := cw.tw.WriteHeader(hdr); err != nil { From d1dad64b1c2d50bd0f05c37cfec880bb8226ba92 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Sun, 12 Aug 2018 18:12:19 -0700 Subject: [PATCH 003/291] Refactor to new algorithm, add tests and tool This commit was moved from ipld/go-car@d936d8ce635bc303e4c8993dc40c65c70f074113 --- ipld/car/car.go | 188 ++++++++++++++++++++++++------------------ ipld/car/car/main.go | 116 ++++++++++++++++++++++++++ ipld/car/car_test.go | 71 ++++++++++++++++ ipld/car/util/util.go | 104 +++++++++++++++++++++++ 4 files changed, 398 insertions(+), 81 deletions(-) create mode 100644 ipld/car/car/main.go create mode 100644 ipld/car/car_test.go create mode 100644 ipld/car/util/util.go diff --git a/ipld/car/car.go b/ipld/car/car.go index 3f1996aac5..2ab1a3b20f 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -1,132 +1,158 @@ package car import ( - "archive/tar" + "bufio" "context" "fmt" "io" - "io/ioutil" - "github.com/ipfs/go-block-format" - cid "github.com/ipfs/go-cid" - bstore "github.com/ipfs/go-ipfs-blockstore" - format "github.com/ipfs/go-ipld-format" - dag "github.com/ipfs/go-merkledag" + util "github.com/ipfs/go-car/util" + + cbor "gx/ipfs/QmSyK1ZiAP98YvnxsTfQpb669V2xeTHRbG4Y6fgKS3vVSd/go-ipld-cbor" + "gx/ipfs/QmVzK524a2VWLqyvtBeiHKsUAWYgeAk4DBeZoY7vpNPNRx/go-block-format" + cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" + format "gx/ipfs/QmZtNq8dArGfnpCZfx2pUNY7UcjGhVp5qqwQ4hH6mpTMRQ/go-ipld-format" + bstore "gx/ipfs/QmcD7SqfyQyA91TZUQ7VPRYbGarxmY7EsQewVYMuN5LNSv/go-ipfs-blockstore" + dag "gx/ipfs/QmeCaeBmCCEJrZahwXY4G2G8zRaNBWskrfKWoQ6Xv6c1DR/go-merkledag" ) -func WriteCar(ctx context.Context, ds format.DAGService, root *cid.Cid, w io.Writer) error { - tw := tar.NewWriter(w) +func init() { + cbor.RegisterCborType(CarHeader{}) +} - rh := &tar.Header{ - Typeflag: tar.TypeSymlink, - Name: "root", - Linkname: root.String(), - } - if err := tw.WriteHeader(rh); err != nil { - return err +type CarHeader struct { + Roots []*cid.Cid + Version uint64 +} + +type carWriter struct { + ds format.DAGService + w io.Writer +} + +func WriteCar(ctx context.Context, ds format.DAGService, roots []*cid.Cid, w io.Writer) error { + cw := &carWriter{ds: ds, w: w} + + h := &CarHeader{ + Roots: roots, + Version: 1, } - cw := &carWriter{ds: ds, tw: tw} + if err := cw.WriteHeader(h); err != nil { + return fmt.Errorf("failed to write car header: %s", err) + } seen := cid.NewSet() - if err := dag.EnumerateChildren(ctx, cw.enumGetLinks, root, seen.Visit); err != nil { - return err + for _, r := range roots { + if err := dag.EnumerateChildren(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { + return err + } } - - return tw.Flush() + return nil } -func LoadCar(ctx context.Context, bs bstore.Blockstore, r io.Reader) (*cid.Cid, error) { - tr := tar.NewReader(r) - root, err := tr.Next() +func ReadHeader(br *bufio.Reader) (*CarHeader, error) { + hb, err := util.LdRead(br) if err != nil { return nil, err } - if root.Name != "root" || root.Typeflag != tar.TypeSymlink { - return nil, fmt.Errorf("expected first entry in CAR to by symlink named 'root'") + var ch CarHeader + if err := cbor.DecodeInto(hb, &ch); err != nil { + return nil, err } - rootcid, err := cid.Decode(root.Linkname) + return &ch, nil +} + +func (cw *carWriter) WriteHeader(h *CarHeader) error { + hb, err := cbor.DumpObject(h) if err != nil { - return nil, err + return err } - for { - obj, err := tr.Next() - if err != nil { - if err == io.EOF { - break - } - return nil, err - } + return util.LdWrite(cw.w, hb) +} - c, err := cid.Decode(obj.Name) - if err != nil { - return nil, err - } +func (cw *carWriter) enumGetLinks(ctx context.Context, c *cid.Cid) ([]*format.Link, error) { + nd, err := cw.ds.Get(ctx, c) + if err != nil { + return nil, err + } - // safety 1st - limr := io.LimitReader(tr, 2<<20) - data, err := ioutil.ReadAll(limr) - if err != nil { - return nil, err - } + if err := cw.writeNode(ctx, nd); err != nil { + return nil, err + } - hashed, err := c.Prefix().Sum(data) - if err != nil { - return nil, err - } + return nd.Links(), nil +} - if !hashed.Equals(c) { - return nil, fmt.Errorf("mismatch in content integrity, name: %s, data: %s", c, hashed) - } +func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { + return util.LdWrite(cw.w, nd.Cid().Bytes(), nd.RawData()) +} - blk, err := blocks.NewBlockWithCid(data, c) - if err != nil { - return nil, err - } +type carReader struct { + br *bufio.Reader + Header *CarHeader +} - if err := bs.Put(blk); err != nil { - return nil, err - } +func NewCarReader(r io.Reader) (*carReader, error) { + br := bufio.NewReader(r) + ch, err := ReadHeader(br) + if err != nil { + return nil, err } - return rootcid, nil -} + if len(ch.Roots) == 0 { + return nil, fmt.Errorf("empty car") + } -type carWriter struct { - ds format.DAGService - tw *tar.Writer + if ch.Version != 1 { + return nil, fmt.Errorf("invalid car version: %d", ch.Version) + } + + return &carReader{ + br: br, + Header: ch, + }, nil } -func (cw *carWriter) enumGetLinks(ctx context.Context, c *cid.Cid) ([]*format.Link, error) { - nd, err := cw.ds.Get(ctx, c) +func (cr *carReader) Next() (blocks.Block, error) { + c, data, err := util.ReadNode(cr.br) if err != nil { return nil, err } - if err := cw.writeNode(ctx, nd); err != nil { + hashed, err := c.Prefix().Sum(data) + if err != nil { return nil, err } - return nd.Links(), nil + if !hashed.Equals(c) { + return nil, fmt.Errorf("mismatch in content integrity, name: %s, data: %s", c, hashed) + } + + return blocks.NewBlockWithCid(data, c) } -func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { - hdr := &tar.Header{ - Name: nd.Cid().String(), - Typeflag: tar.TypeReg, - Size: int64(len(nd.RawData())), +func LoadCar(bs bstore.Blockstore, r io.Reader) (*CarHeader, error) { + cr, err := NewCarReader(r) + if err != nil { + return nil, err } - if err := cw.tw.WriteHeader(hdr); err != nil { - return err - } + for { + blk, err := cr.Next() + switch err { + case io.EOF: + return cr.Header, nil + default: + return nil, err + case nil: + } - if _, err := cw.tw.Write(nd.RawData()); err != nil { - return err + if err := bs.Put(blk); err != nil { + return nil, err + } } - - return nil } diff --git a/ipld/car/car/main.go b/ipld/car/car/main.go new file mode 100644 index 0000000000..97127ca7c0 --- /dev/null +++ b/ipld/car/car/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "os" + + "github.com/ipfs/go-car" + + cli "github.com/urfave/cli" +) + +var headerCmd = cli.Command{ + Name: "header", + Action: func(c *cli.Context) error { + if !c.Args().Present() { + return fmt.Errorf("must pass a car file to inspect") + } + arg := c.Args().First() + + fi, err := os.Open(arg) + if err != nil { + return err + } + defer fi.Close() + + ch, err := car.ReadHeader(bufio.NewReader(fi)) + if err != nil { + return err + } + + b, err := json.MarshalIndent(ch, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil + }, +} + +var verifyCmd = cli.Command{ + Name: "verify", + Action: func(c *cli.Context) error { + if !c.Args().Present() { + return fmt.Errorf("must pass a car file to inspect") + } + arg := c.Args().First() + + fi, err := os.Open(arg) + if err != nil { + return err + } + defer fi.Close() + + cr, err := car.NewCarReader(fi) + if err != nil { + return err + } + + for { + _, err := cr.Next() + switch err { + case io.EOF: + return nil + default: + return err + case nil: + } + } + }, +} + +var lsCmd = cli.Command{ + Name: "ls", + Action: func(c *cli.Context) error { + if !c.Args().Present() { + return fmt.Errorf("must pass a car file to inspect") + } + arg := c.Args().First() + + fi, err := os.Open(arg) + if err != nil { + return err + } + defer fi.Close() + + cr, err := car.NewCarReader(fi) + if err != nil { + return err + } + + for { + blk, err := cr.Next() + switch err { + case io.EOF: + return nil + default: + return err + case nil: + } + fmt.Println(blk.Cid()) + } + }, +} + +func main() { + app := cli.NewApp() + app.Commands = []cli.Command{ + headerCmd, + lsCmd, + verifyCmd, + } + app.RunAndExitOnError() +} diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go new file mode 100644 index 0000000000..169c005202 --- /dev/null +++ b/ipld/car/car_test.go @@ -0,0 +1,71 @@ +package car + +import ( + "bytes" + "context" + "testing" + + cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" + format "gx/ipfs/QmZtNq8dArGfnpCZfx2pUNY7UcjGhVp5qqwQ4hH6mpTMRQ/go-ipld-format" + dag "gx/ipfs/QmeCaeBmCCEJrZahwXY4G2G8zRaNBWskrfKWoQ6Xv6c1DR/go-merkledag" + dstest "gx/ipfs/QmeCaeBmCCEJrZahwXY4G2G8zRaNBWskrfKWoQ6Xv6c1DR/go-merkledag/test" +) + +func assertAddNodes(t *testing.T, ds format.DAGService, nds ...format.Node) { + for _, nd := range nds { + if err := ds.Add(context.Background(), nd); err != nil { + t.Fatal(err) + } + } +} + +func TestRoundtrip(t *testing.T) { + dserv := dstest.Mock() + a := dag.NewRawNode([]byte("aaaa")) + b := dag.NewRawNode([]byte("bbbb")) + c := dag.NewRawNode([]byte("cccc")) + + nd1 := &dag.ProtoNode{} + nd1.AddNodeLink("cat", a) + + nd2 := &dag.ProtoNode{} + nd2.AddNodeLink("first", nd1) + nd2.AddNodeLink("dog", b) + + nd3 := &dag.ProtoNode{} + nd3.AddNodeLink("second", nd2) + nd3.AddNodeLink("bear", c) + + assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) + + buf := new(bytes.Buffer) + if err := WriteCar(context.Background(), dserv, []*cid.Cid{nd3.Cid()}, buf); err != nil { + t.Fatal(err) + } + + bserv := dstest.Bserv() + ch, err := LoadCar(bserv.Blockstore(), buf) + if err != nil { + t.Fatal(err) + } + + if len(ch.Roots) != 1 { + t.Fatal("should have one root") + } + + if !ch.Roots[0].Equals(nd3.Cid()) { + t.Fatal("got wrong cid") + } + + bs := bserv.Blockstore() + for _, nd := range []format.Node{a, b, c, nd1, nd2, nd3} { + has, err := bs.Has(nd.Cid()) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("should have cid in blockstore") + } + } +} diff --git a/ipld/car/util/util.go b/ipld/car/util/util.go new file mode 100644 index 0000000000..e39aaee08f --- /dev/null +++ b/ipld/car/util/util.go @@ -0,0 +1,104 @@ +package util + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + + mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash" + cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" +) + +var cidv0Pref = []byte{0x12, 0x20} + +type BytesReader interface { + io.Reader + io.ByteReader +} + +// TODO: this belongs in the go-cid package +func ReadCid(buf []byte) (*cid.Cid, int, error) { + if bytes.Equal(buf[:2], cidv0Pref) { + c, err := cid.Cast(buf[:34]) + return c, 34, err + } + + br := bytes.NewReader(buf) + + // assume cidv1 + vers, err := binary.ReadUvarint(br) + if err != nil { + return nil, 0, err + } + + // TODO: the go-cid package allows version 0 here as well + if vers != 1 { + return nil, 0, fmt.Errorf("invalid cid version number") + } + + codec, err := binary.ReadUvarint(br) + if err != nil { + return nil, 0, err + } + + mhr := mh.NewReader(br) + h, err := mhr.ReadMultihash() + if err != nil { + return nil, 0, err + } + + return cid.NewCidV1(codec, h), len(buf) - br.Len(), nil +} + +func ReadNode(br *bufio.Reader) (*cid.Cid, []byte, error) { + data, err := LdRead(br) + if err != nil { + return nil, nil, err + } + + c, n, err := ReadCid(data) + if err != nil { + return nil, nil, err + } + + return c, data[n:], nil +} + +func LdWrite(w io.Writer, d ...[]byte) error { + var sum uint64 + for _, s := range d { + sum += uint64(len(s)) + } + + buf := make([]byte, 8) + n := binary.PutUvarint(buf, sum) + _, err := w.Write(buf[:n]) + if err != nil { + return err + } + + for _, s := range d { + _, err = w.Write(s) + if err != nil { + return err + } + } + + return nil +} + +func LdRead(r *bufio.Reader) ([]byte, error) { + l, err := binary.ReadUvarint(r) + if err != nil { + return nil, err + } + + buf := make([]byte, l) + if _, err := io.ReadFull(r, buf); err != nil { + return nil, err + } + + return buf, nil +} From a8eca1817ad6630e0029685b52a48bb06a9f1cab Mon Sep 17 00:00:00 2001 From: Jeromy Date: Thu, 16 Aug 2018 11:58:14 -0700 Subject: [PATCH 004/291] gx publish 1.1.1 This commit was moved from ipld/go-car@72913caaf56152e92ec9a7b8977349234c15576c --- ipld/car/car.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 2ab1a3b20f..1d68e92a7b 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -8,7 +8,7 @@ import ( util "github.com/ipfs/go-car/util" - cbor "gx/ipfs/QmSyK1ZiAP98YvnxsTfQpb669V2xeTHRbG4Y6fgKS3vVSd/go-ipld-cbor" + cbor "gx/ipfs/QmPbqRavwDZLfmpeW6eoyAoQ5rT2LoCW98JhvRc22CqkZS/go-ipld-cbor" "gx/ipfs/QmVzK524a2VWLqyvtBeiHKsUAWYgeAk4DBeZoY7vpNPNRx/go-block-format" cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" format "gx/ipfs/QmZtNq8dArGfnpCZfx2pUNY7UcjGhVp5qqwQ4hH6mpTMRQ/go-ipld-format" From 94f1ce6e63af15c491b066d0f93e578b05809360 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 28 Aug 2018 13:52:49 +0200 Subject: [PATCH 005/291] chore: update deps + gx-go uw This commit was moved from ipld/go-car@112b7904144b67e952d0c0ebb56dee8ffc089dc4 --- ipld/car/car.go | 16 ++++++++-------- ipld/car/car/main.go | 2 +- ipld/car/car_test.go | 8 ++++---- ipld/car/util/util.go | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 1d68e92a7b..1f2bb0f7a4 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -6,14 +6,14 @@ import ( "fmt" "io" - util "github.com/ipfs/go-car/util" - - cbor "gx/ipfs/QmPbqRavwDZLfmpeW6eoyAoQ5rT2LoCW98JhvRc22CqkZS/go-ipld-cbor" - "gx/ipfs/QmVzK524a2VWLqyvtBeiHKsUAWYgeAk4DBeZoY7vpNPNRx/go-block-format" - cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" - format "gx/ipfs/QmZtNq8dArGfnpCZfx2pUNY7UcjGhVp5qqwQ4hH6mpTMRQ/go-ipld-format" - bstore "gx/ipfs/QmcD7SqfyQyA91TZUQ7VPRYbGarxmY7EsQewVYMuN5LNSv/go-ipfs-blockstore" - dag "gx/ipfs/QmeCaeBmCCEJrZahwXY4G2G8zRaNBWskrfKWoQ6Xv6c1DR/go-merkledag" + "github.com/ipfs/go-block-format" + cid "github.com/ipfs/go-cid" + bstore "github.com/ipfs/go-ipfs-blockstore" + cbor "github.com/ipfs/go-ipld-cbor" + format "github.com/ipfs/go-ipld-format" + dag "github.com/ipfs/go-merkledag" + + util "gx/ipfs/QmU9oYpqJsNWwAAJju8CzE7mv4NHAJUDWhoKHqgnhMCBy5/go-car/util" ) func init() { diff --git a/ipld/car/car/main.go b/ipld/car/car/main.go index 97127ca7c0..877f692f14 100644 --- a/ipld/car/car/main.go +++ b/ipld/car/car/main.go @@ -7,7 +7,7 @@ import ( "io" "os" - "github.com/ipfs/go-car" + "gx/ipfs/QmU9oYpqJsNWwAAJju8CzE7mv4NHAJUDWhoKHqgnhMCBy5/go-car" cli "github.com/urfave/cli" ) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 169c005202..c4a9f6cc15 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -5,10 +5,10 @@ import ( "context" "testing" - cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" - format "gx/ipfs/QmZtNq8dArGfnpCZfx2pUNY7UcjGhVp5qqwQ4hH6mpTMRQ/go-ipld-format" - dag "gx/ipfs/QmeCaeBmCCEJrZahwXY4G2G8zRaNBWskrfKWoQ6Xv6c1DR/go-merkledag" - dstest "gx/ipfs/QmeCaeBmCCEJrZahwXY4G2G8zRaNBWskrfKWoQ6Xv6c1DR/go-merkledag/test" + cid "github.com/ipfs/go-cid" + format "github.com/ipfs/go-ipld-format" + dag "github.com/ipfs/go-merkledag" + dstest "github.com/ipfs/go-merkledag/test" ) func assertAddNodes(t *testing.T, ds format.DAGService, nds ...format.Node) { diff --git a/ipld/car/util/util.go b/ipld/car/util/util.go index e39aaee08f..a5ce11d770 100644 --- a/ipld/car/util/util.go +++ b/ipld/car/util/util.go @@ -7,8 +7,8 @@ import ( "fmt" "io" - mh "gx/ipfs/QmPnFwZ2JXKnXgMw8CdBPxn7FWh6LLdjUjxV1fKHuJnkr8/go-multihash" - cid "gx/ipfs/QmYVNvtQkeZ6AKSwDrjQTs432QtL6umrrK41EBq3cu7iSP/go-cid" + cid "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" ) var cidv0Pref = []byte{0x12, 0x20} From 5f606e36adf4dc80d5e3c91dd2eb4ffc587c3fc8 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Tue, 28 Aug 2018 13:57:09 +0200 Subject: [PATCH 006/291] chore: fix rewrite This commit was moved from ipld/go-car@e66d5d2c2b7d529864ce5618d2c2db4c0efaade7 --- ipld/car/car.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 1f2bb0f7a4..2568c0bc41 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -13,7 +13,7 @@ import ( format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" - util "gx/ipfs/QmU9oYpqJsNWwAAJju8CzE7mv4NHAJUDWhoKHqgnhMCBy5/go-car/util" + util "github.com/ipfs/go-car/util" ) func init() { From a431982246605b43095d384e0b159973557e87e2 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Wed, 29 Aug 2018 17:33:48 -0700 Subject: [PATCH 007/291] fix bad rewrite This commit was moved from ipld/go-car@916320ff443cb8cf8ea6b3ad2709d93fa974265f --- ipld/car/car/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/car/main.go b/ipld/car/car/main.go index 877f692f14..97127ca7c0 100644 --- a/ipld/car/car/main.go +++ b/ipld/car/car/main.go @@ -7,7 +7,7 @@ import ( "io" "os" - "gx/ipfs/QmU9oYpqJsNWwAAJju8CzE7mv4NHAJUDWhoKHqgnhMCBy5/go-car" + "github.com/ipfs/go-car" cli "github.com/urfave/cli" ) From 994594072487cf471da5d4c8af49cf99250b26bb Mon Sep 17 00:00:00 2001 From: Jeromy Date: Tue, 23 Oct 2018 12:54:20 -0700 Subject: [PATCH 008/291] gx publish 1.1.6 This commit was moved from ipld/go-car@0c4f3fcea33d34eb1883669ee3ac6f0676ea015c --- ipld/car/car.go | 6 +++--- ipld/car/car_test.go | 2 +- ipld/car/util/util.go | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 2568c0bc41..69ad7410d2 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -21,7 +21,7 @@ func init() { } type CarHeader struct { - Roots []*cid.Cid + Roots []cid.Cid Version uint64 } @@ -30,7 +30,7 @@ type carWriter struct { w io.Writer } -func WriteCar(ctx context.Context, ds format.DAGService, roots []*cid.Cid, w io.Writer) error { +func WriteCar(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.Writer) error { cw := &carWriter{ds: ds, w: w} h := &CarHeader{ @@ -74,7 +74,7 @@ func (cw *carWriter) WriteHeader(h *CarHeader) error { return util.LdWrite(cw.w, hb) } -func (cw *carWriter) enumGetLinks(ctx context.Context, c *cid.Cid) ([]*format.Link, error) { +func (cw *carWriter) enumGetLinks(ctx context.Context, c cid.Cid) ([]*format.Link, error) { nd, err := cw.ds.Get(ctx, c) if err != nil { return nil, err diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index c4a9f6cc15..01288fea86 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -39,7 +39,7 @@ func TestRoundtrip(t *testing.T) { assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) buf := new(bytes.Buffer) - if err := WriteCar(context.Background(), dserv, []*cid.Cid{nd3.Cid()}, buf); err != nil { + if err := WriteCar(context.Background(), dserv, []cid.Cid{nd3.Cid()}, buf); err != nil { t.Fatal(err) } diff --git a/ipld/car/util/util.go b/ipld/car/util/util.go index a5ce11d770..c53d59506a 100644 --- a/ipld/car/util/util.go +++ b/ipld/car/util/util.go @@ -19,7 +19,7 @@ type BytesReader interface { } // TODO: this belongs in the go-cid package -func ReadCid(buf []byte) (*cid.Cid, int, error) { +func ReadCid(buf []byte) (cid.Cid, int, error) { if bytes.Equal(buf[:2], cidv0Pref) { c, err := cid.Cast(buf[:34]) return c, 34, err @@ -30,37 +30,37 @@ func ReadCid(buf []byte) (*cid.Cid, int, error) { // assume cidv1 vers, err := binary.ReadUvarint(br) if err != nil { - return nil, 0, err + return cid.Cid{}, 0, err } // TODO: the go-cid package allows version 0 here as well if vers != 1 { - return nil, 0, fmt.Errorf("invalid cid version number") + return cid.Cid{}, 0, fmt.Errorf("invalid cid version number") } codec, err := binary.ReadUvarint(br) if err != nil { - return nil, 0, err + return cid.Cid{}, 0, err } mhr := mh.NewReader(br) h, err := mhr.ReadMultihash() if err != nil { - return nil, 0, err + return cid.Cid{}, 0, err } return cid.NewCidV1(codec, h), len(buf) - br.Len(), nil } -func ReadNode(br *bufio.Reader) (*cid.Cid, []byte, error) { +func ReadNode(br *bufio.Reader) (cid.Cid, []byte, error) { data, err := LdRead(br) if err != nil { - return nil, nil, err + return cid.Cid{}, nil, err } c, n, err := ReadCid(data) if err != nil { - return nil, nil, err + return cid.Cid{}, nil, err } return c, data[n:], nil From ab218255fb7488a719c3d1743194b0fb4f52cf2c Mon Sep 17 00:00:00 2001 From: frrist Date: Tue, 24 Sep 2019 12:23:59 +1000 Subject: [PATCH 009/291] define general Store interface replaces Blockstore - LoadCar accepts Store interface for putting blocks. This commit was moved from ipld/go-car@a64e86334410e380ad2165e2ec5bff15ea62c7e0 --- ipld/car/car.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 69ad7410d2..7a32b40739 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -8,7 +8,6 @@ import ( "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" - bstore "github.com/ipfs/go-ipfs-blockstore" cbor "github.com/ipfs/go-ipld-cbor" format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" @@ -20,6 +19,10 @@ func init() { cbor.RegisterCborType(CarHeader{}) } +type Store interface { + Put(blocks.Block) error +} + type CarHeader struct { Roots []cid.Cid Version uint64 @@ -135,7 +138,7 @@ func (cr *carReader) Next() (blocks.Block, error) { return blocks.NewBlockWithCid(data, c) } -func LoadCar(bs bstore.Blockstore, r io.Reader) (*CarHeader, error) { +func LoadCar(s Store, r io.Reader) (*CarHeader, error) { cr, err := NewCarReader(r) if err != nil { return nil, err @@ -151,7 +154,7 @@ func LoadCar(bs bstore.Blockstore, r io.Reader) (*CarHeader, error) { case nil: } - if err := bs.Put(blk); err != nil { + if err := s.Put(blk); err != nil { return nil, err } } From 1a94e111b02109ba9da507b22f50dba9d8c8a415 Mon Sep 17 00:00:00 2001 From: frrist Date: Thu, 17 Oct 2019 13:56:32 -0700 Subject: [PATCH 010/291] update go-merkledag too v0.2.4 This commit was moved from ipld/go-car@6bca8656ee37dcc2ed011117958f64427c2ef2ab --- ipld/car/car.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 7a32b40739..f24c42faea 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -47,7 +47,7 @@ func WriteCar(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.W seen := cid.NewSet() for _, r := range roots { - if err := dag.EnumerateChildren(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { + if err := dag.Walk(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { return err } } From 04fad5e5f5b11f146f9c9008b5b395f8c9502a42 Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Wed, 15 Jan 2020 12:40:12 -0800 Subject: [PATCH 011/291] allow car creation with custom link walker This commit was moved from ipld/go-car@99513a3f029530e0489ad86209e646dd361287da --- ipld/car/car.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index f24c42faea..1ad9c1549a 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -6,7 +6,7 @@ import ( "fmt" "io" - "github.com/ipfs/go-block-format" + blocks "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" format "github.com/ipfs/go-ipld-format" @@ -29,12 +29,19 @@ type CarHeader struct { } type carWriter struct { - ds format.DAGService - w io.Writer + ds format.DAGService + w io.Writer + walk WalkFunc } +type WalkFunc func(format.Node) ([]*format.Link, error) + func WriteCar(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.Writer) error { - cw := &carWriter{ds: ds, w: w} + return WriteCarWithWalker(ctx, ds, roots, w, DefaultWalkFunc) +} + +func WriteCarWithWalker(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.Writer, walk WalkFunc) error { + cw := &carWriter{ds: ds, w: w, walk: walk} h := &CarHeader{ Roots: roots, @@ -54,6 +61,10 @@ func WriteCar(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.W return nil } +func DefaultWalkFunc(nd format.Node) ([]*format.Link, error) { + return nd.Links(), nil +} + func ReadHeader(br *bufio.Reader) (*CarHeader, error) { hb, err := util.LdRead(br) if err != nil { From db9b481249a43a358e2bdb2716cc3efa64b8f7f9 Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Mon, 20 Jan 2020 17:36:34 -0800 Subject: [PATCH 012/291] take advantage of batching when loading car, if available This commit was moved from ipld/go-car@f188c0e24291401335b90626aad4ba562948b525 --- ipld/car/car.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/ipld/car/car.go b/ipld/car/car.go index 1ad9c1549a..515fc0af76 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -149,12 +149,61 @@ func (cr *carReader) Next() (blocks.Block, error) { return blocks.NewBlockWithCid(data, c) } +type batchStore interface { + PutMany([]blocks.Block) error +} + func LoadCar(s Store, r io.Reader) (*CarHeader, error) { cr, err := NewCarReader(r) if err != nil { return nil, err } + if bs, ok := s.(batchStore); ok { + return loadCarFast(bs, cr) + } + + return loadCarSlow(s, cr) +} + +func loadCarFast(s batchStore, cr *carReader) (*CarHeader, error) { + var buf []blocks.Block + for { + blk, err := cr.Next() + switch err { + case io.EOF: + if len(buf) > 0 { + if err := s.PutMany(buf); err != nil { + return nil, err + } + } + return cr.Header, nil + default: + return nil, err + case nil: + } + + buf = append(buf, blk) + + if len(buf) > 1000 { + if err := s.PutMany(buf); err != nil { + return nil, err + } + buf = buf[:0] + } + } + + if len(buf) > 0 { + if err := s.PutMany(buf); err != nil { + return nil, err + } + } + + return cr.Header, nil +} + +func loadCarSlow(s Store, cr *carReader) (*CarHeader, error) { + for { blk, err := cr.Next() switch err { From f9b70fe85e23ba07c78a5bff4f825c8f133243aa Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 4 Mar 2020 10:44:30 -0800 Subject: [PATCH 013/291] migrate to IPLD org This commit was moved from ipld/go-car@da36c369c24a63aedea42e1fe1f8764d28a4cd24 --- ipld/car/README.md | 10 +++++----- ipld/car/car.go | 2 +- ipld/car/car/main.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ipld/car/README.md b/ipld/car/README.md index ffd258c9e7..1142a22935 100644 --- a/ipld/car/README.md +++ b/ipld/car/README.md @@ -1,11 +1,11 @@ go-car (go!) ================== -[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) -[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) -[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) -[![Coverage Status](https://codecov.io/gh/ipfs/go-car/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/go-car/branch/master) -[![Travis CI](https://travis-ci.org/ipfs/go-car.svg?branch=master)](https://travis-ci.org/ipfs/go-car) +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) +[![](https://img.shields.io/badge/project-ipld-orange.svg?style=flat-square)](https://github.com/ipld/ipld) +[![](https://img.shields.io/badge/freenode-%23ipld-orange.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipld) +[![Coverage Status](https://codecov.io/gh/ipld/go-car/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/go-car/branch/master) +[![Travis CI](https://travis-ci.org/ipld/go-car.svg?branch=master)](https://travis-ci.org/ipld/go-car) > go-car is a simple way of packing a merkledag into a single file diff --git a/ipld/car/car.go b/ipld/car/car.go index 515fc0af76..326f94b2af 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -12,7 +12,7 @@ import ( format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" - util "github.com/ipfs/go-car/util" + util "github.com/ipld/go-car/util" ) func init() { diff --git a/ipld/car/car/main.go b/ipld/car/car/main.go index 97127ca7c0..c88459adea 100644 --- a/ipld/car/car/main.go +++ b/ipld/car/car/main.go @@ -7,7 +7,7 @@ import ( "io" "os" - "github.com/ipfs/go-car" + "github.com/ipld/go-car" cli "github.com/urfave/cli" ) From fa4fac72a9e95146b838ffdff1a6f4ceec20b8ff Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 4 Mar 2020 10:59:10 -0800 Subject: [PATCH 014/291] fix: don't use private types in public functions This commit was moved from ipld/go-car@d192da14125f2cd1c153b97ea2cb0d2d8bbcdcfd --- ipld/car/car.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 326f94b2af..d13f2cbcec 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -105,12 +105,12 @@ func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { return util.LdWrite(cw.w, nd.Cid().Bytes(), nd.RawData()) } -type carReader struct { +type CarReader struct { br *bufio.Reader Header *CarHeader } -func NewCarReader(r io.Reader) (*carReader, error) { +func NewCarReader(r io.Reader) (*CarReader, error) { br := bufio.NewReader(r) ch, err := ReadHeader(br) if err != nil { @@ -125,13 +125,13 @@ func NewCarReader(r io.Reader) (*carReader, error) { return nil, fmt.Errorf("invalid car version: %d", ch.Version) } - return &carReader{ + return &CarReader{ br: br, Header: ch, }, nil } -func (cr *carReader) Next() (blocks.Block, error) { +func (cr *CarReader) Next() (blocks.Block, error) { c, data, err := util.ReadNode(cr.br) if err != nil { return nil, err @@ -166,7 +166,7 @@ func LoadCar(s Store, r io.Reader) (*CarHeader, error) { return loadCarSlow(s, cr) } -func loadCarFast(s batchStore, cr *carReader) (*CarHeader, error) { +func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { var buf []blocks.Block for { blk, err := cr.Next() @@ -202,7 +202,7 @@ func loadCarFast(s batchStore, cr *carReader) (*CarHeader, error) { return cr.Header, nil } -func loadCarSlow(s Store, cr *carReader) (*CarHeader, error) { +func loadCarSlow(s Store, cr *CarReader) (*CarHeader, error) { for { blk, err := cr.Next() From ccad8106a9084e9fc17f08f326b41a67ddd1fb7a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 4 Mar 2020 10:59:31 -0800 Subject: [PATCH 015/291] chore: remove dead code This commit was moved from ipld/go-car@7ff5726c9e54af3067279729ef908265709503c8 --- ipld/car/car.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index d13f2cbcec..f98779e9b1 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -192,14 +192,6 @@ func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { buf = buf[:0] } } - - if len(buf) > 0 { - if err := s.PutMany(buf); err != nil { - return nil, err - } - } - - return cr.Header, nil } func loadCarSlow(s Store, cr *CarReader) (*CarHeader, error) { From 4abdfc0609e6f64a624769494c85c7ba95dcc4b8 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Mon, 2 Dec 2019 18:18:55 -0800 Subject: [PATCH 016/291] feat(car): add selector support Add a mechanism for doing CAR files that write with selectors fix #14 This commit was moved from ipld/go-car@c487ec993500fb7f7e2d5288bc4530332e5568ea --- ipld/car/car.go | 87 ++++++++++++++++++++++++++++++++++++++++++-- ipld/car/car_test.go | 76 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 4 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index f98779e9b1..2a7880d2f4 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -2,15 +2,24 @@ package car import ( "bufio" + "bytes" "context" + "errors" "fmt" "io" + ipldfree "github.com/ipld/go-ipld-prime/impl/free" + "github.com/ipld/go-ipld-prime/traversal" + blocks "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" + "github.com/ipld/go-ipld-prime" + dagpb "github.com/ipld/go-ipld-prime-proto" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/traversal/selector" util "github.com/ipld/go-car/util" ) @@ -23,6 +32,15 @@ type Store interface { Put(blocks.Block) error } +type ReadStore interface { + Get(cid.Cid) (blocks.Block, error) +} + +type CarDag struct { + Root cid.Cid + Selector selector.Selector +} + type CarHeader struct { Roots []cid.Cid Version uint64 @@ -41,17 +59,18 @@ func WriteCar(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.W } func WriteCarWithWalker(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.Writer, walk WalkFunc) error { - cw := &carWriter{ds: ds, w: w, walk: walk} + h := &CarHeader{ Roots: roots, Version: 1, } - if err := cw.WriteHeader(h); err != nil { + if err := WriteHeader(h, w); err != nil { return fmt.Errorf("failed to write car header: %s", err) } + cw := &carWriter{ds: ds, w: w, walk: walk} seen := cid.NewSet() for _, r := range roots { if err := dag.Walk(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { @@ -65,6 +84,66 @@ func DefaultWalkFunc(nd format.Node) ([]*format.Link, error) { return nd.Links(), nil } +func WriteSelectiveCar(ctx context.Context, store ReadStore, dags []CarDag, w io.Writer) error { + + roots := make([]cid.Cid, 0, len(dags)) + for _, carDag := range dags { + roots = append(roots, carDag.Root) + } + + h := &CarHeader{ + Roots: roots, + Version: 1, + } + + if err := WriteHeader(h, w); err != nil { + return fmt.Errorf("failed to write car header: %s", err) + } + + var loader ipld.Loader = func(lnk ipld.Link, ctx ipld.LinkContext) (io.Reader, error) { + cl, ok := lnk.(cidlink.Link) + if !ok { + return nil, errors.New("Incorrect Link Type") + } + c := cl.Cid + fmt.Println(c) + blk, err := store.Get(c) + if err != nil { + return nil, err + } + raw := blk.RawData() + err = util.LdWrite(w, c.Bytes(), raw) + if err != nil { + return nil, err + } + return bytes.NewReader(raw), nil + } + + nbc := dagpb.AddDagPBSupportToChooser(func(ipld.Link, ipld.LinkContext) ipld.NodeBuilder { + return ipldfree.NodeBuilder() + }) + + for _, carDag := range dags { + lnk := cidlink.Link{Cid: carDag.Root} + nb := nbc(lnk, ipld.LinkContext{}) + nd, err := lnk.Load(ctx, ipld.LinkContext{}, nb, loader) + if err != nil { + return err + } + err = traversal.Progress{ + Cfg: &traversal.Config{ + Ctx: ctx, + LinkLoader: loader, + LinkNodeBuilderChooser: nbc, + }, + }.WalkAdv(nd, carDag.Selector, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) + if err != nil { + return err + } + } + return nil +} + func ReadHeader(br *bufio.Reader) (*CarHeader, error) { hb, err := util.LdRead(br) if err != nil { @@ -79,13 +158,13 @@ func ReadHeader(br *bufio.Reader) (*CarHeader, error) { return &ch, nil } -func (cw *carWriter) WriteHeader(h *CarHeader) error { +func WriteHeader(h *CarHeader, w io.Writer) error { hb, err := cbor.DumpObject(h) if err != nil { return err } - return util.LdWrite(cw.w, hb) + return util.LdWrite(w, hb) } func (cw *carWriter) enumGetLinks(ctx context.Context, c cid.Cid) ([]*format.Link, error) { diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 01288fea86..98288e9cb8 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -9,6 +9,9 @@ import ( format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" + ipldfree "github.com/ipld/go-ipld-prime/impl/free" + "github.com/ipld/go-ipld-prime/traversal/selector" + "github.com/ipld/go-ipld-prime/traversal/selector/builder" ) func assertAddNodes(t *testing.T, ds format.DAGService, nds ...format.Node) { @@ -69,3 +72,76 @@ func TestRoundtrip(t *testing.T) { } } } + +func TestRoundtripSelective(t *testing.T) { + sourceBserv := dstest.Bserv() + sourceBs := sourceBserv.Blockstore() + dserv := dag.NewDAGService(sourceBserv) + a := dag.NewRawNode([]byte("aaaa")) + b := dag.NewRawNode([]byte("bbbb")) + c := dag.NewRawNode([]byte("cccc")) + + nd1 := &dag.ProtoNode{} + nd1.AddNodeLink("cat", a) + + nd2 := &dag.ProtoNode{} + nd2.AddNodeLink("first", nd1) + nd2.AddNodeLink("dog", b) + + nd3 := &dag.ProtoNode{} + nd3.AddNodeLink("second", nd2) + nd3.AddNodeLink("bear", c) + + assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) + + buf := new(bytes.Buffer) + ssb := builder.NewSelectorSpecBuilder(ipldfree.NodeBuilder()) + selector, err := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { + efsb.Insert("Links", + ssb.ExploreIndex(1, ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())))) + }).Selector() + + if err != nil { + t.Fatal("Did not build selector") + } + if err := WriteSelectiveCar(context.Background(), sourceBs, []CarDag{CarDag{Root: nd3.Cid(), Selector: selector}}, buf); err != nil { + t.Fatal(err) + } + + bserv := dstest.Bserv() + ch, err := LoadCar(bserv.Blockstore(), buf) + if err != nil { + t.Fatal(err) + } + + if len(ch.Roots) != 1 { + t.Fatal("should have one root") + } + + if !ch.Roots[0].Equals(nd3.Cid()) { + t.Fatal("got wrong cid") + } + + bs := bserv.Blockstore() + for _, nd := range []format.Node{a, b, nd1, nd2, nd3} { + has, err := bs.Has(nd.Cid()) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("should have cid in blockstore") + } + } + + for _, nd := range []format.Node{c} { + has, err := bs.Has(nd.Cid()) + if err != nil { + t.Fatal(err) + } + + if has { + t.Fatal("should NOT have cid in blockstore") + } + } +} From 009237a9ce8f6e507eefc89a24d613d8f7f6d9bb Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Thu, 30 Jan 2020 13:47:04 -0800 Subject: [PATCH 017/291] feat(util): ldsize function add ldsize function to estimate size of an LdWrite This commit was moved from ipld/go-car@dd7f14e81be301d402efa5635595bb97063b8d88 --- ipld/car/util/util.go | 10 ++++++++++ ipld/car/util/util_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 ipld/car/util/util_test.go diff --git a/ipld/car/util/util.go b/ipld/car/util/util.go index c53d59506a..47c7e78d66 100644 --- a/ipld/car/util/util.go +++ b/ipld/car/util/util.go @@ -89,6 +89,16 @@ func LdWrite(w io.Writer, d ...[]byte) error { return nil } +func LdSize(d ...[]byte) uint64 { + var sum uint64 + for _, s := range d { + sum += uint64(len(s)) + } + buf := make([]byte, 8) + n := binary.PutUvarint(buf, sum) + return sum + uint64(n) +} + func LdRead(r *bufio.Reader) ([]byte, error) { l, err := binary.ReadUvarint(r) if err != nil { diff --git a/ipld/car/util/util_test.go b/ipld/car/util/util_test.go new file mode 100644 index 0000000000..1e0567c8c4 --- /dev/null +++ b/ipld/car/util/util_test.go @@ -0,0 +1,26 @@ +package util_test + +import ( + "bytes" + "math/rand" + "testing" + + "github.com/ipfs/go-car/util" + "github.com/stretchr/testify/require" +) + +func TestLdSize(t *testing.T) { + for i := 0; i < 5; i++ { + var buf bytes.Buffer + data := make([][]byte, 5) + for j := 0; j < 5; j++ { + data[j] = make([]byte, rand.Intn(30)) + _, err := rand.Read(data[j]) + require.NoError(t, err) + } + size := util.LdSize(data...) + err := util.LdWrite(&buf, data...) + require.NoError(t, err) + require.Equal(t, uint64(len(buf.Bytes())), size) + } +} From aa603d65b59a40efa856e62f4a67cac6419542f4 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Thu, 30 Jan 2020 15:35:54 -0800 Subject: [PATCH 018/291] feat(selectivecar): add collection step when writing a car, seperate traversal of the selector from the actual writing of the Car file, which allows us to gather information about what will be written in the car, and to dedupe writes This commit was moved from ipld/go-car@053026ac271b1d8c63615cdc1b9a274c34edb3c8 --- ipld/car/car.go | 78 ++--------------- ipld/car/car_test.go | 47 ++++------ ipld/car/selectivecar.go | 170 +++++++++++++++++++++++++++++++++++++ ipld/car/util/util_test.go | 2 +- 4 files changed, 196 insertions(+), 101 deletions(-) create mode 100644 ipld/car/selectivecar.go diff --git a/ipld/car/car.go b/ipld/car/car.go index 2a7880d2f4..71d153c64b 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -2,23 +2,15 @@ package car import ( "bufio" - "bytes" "context" - "errors" "fmt" "io" - ipldfree "github.com/ipld/go-ipld-prime/impl/free" - "github.com/ipld/go-ipld-prime/traversal" - blocks "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" - "github.com/ipld/go-ipld-prime" - dagpb "github.com/ipld/go-ipld-prime-proto" - cidlink "github.com/ipld/go-ipld-prime/linking/cid" "github.com/ipld/go-ipld-prime/traversal/selector" util "github.com/ipld/go-car/util" @@ -60,7 +52,6 @@ func WriteCar(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.W func WriteCarWithWalker(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.Writer, walk WalkFunc) error { - h := &CarHeader{ Roots: roots, Version: 1, @@ -84,66 +75,6 @@ func DefaultWalkFunc(nd format.Node) ([]*format.Link, error) { return nd.Links(), nil } -func WriteSelectiveCar(ctx context.Context, store ReadStore, dags []CarDag, w io.Writer) error { - - roots := make([]cid.Cid, 0, len(dags)) - for _, carDag := range dags { - roots = append(roots, carDag.Root) - } - - h := &CarHeader{ - Roots: roots, - Version: 1, - } - - if err := WriteHeader(h, w); err != nil { - return fmt.Errorf("failed to write car header: %s", err) - } - - var loader ipld.Loader = func(lnk ipld.Link, ctx ipld.LinkContext) (io.Reader, error) { - cl, ok := lnk.(cidlink.Link) - if !ok { - return nil, errors.New("Incorrect Link Type") - } - c := cl.Cid - fmt.Println(c) - blk, err := store.Get(c) - if err != nil { - return nil, err - } - raw := blk.RawData() - err = util.LdWrite(w, c.Bytes(), raw) - if err != nil { - return nil, err - } - return bytes.NewReader(raw), nil - } - - nbc := dagpb.AddDagPBSupportToChooser(func(ipld.Link, ipld.LinkContext) ipld.NodeBuilder { - return ipldfree.NodeBuilder() - }) - - for _, carDag := range dags { - lnk := cidlink.Link{Cid: carDag.Root} - nb := nbc(lnk, ipld.LinkContext{}) - nd, err := lnk.Load(ctx, ipld.LinkContext{}, nb, loader) - if err != nil { - return err - } - err = traversal.Progress{ - Cfg: &traversal.Config{ - Ctx: ctx, - LinkLoader: loader, - LinkNodeBuilderChooser: nbc, - }, - }.WalkAdv(nd, carDag.Selector, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) - if err != nil { - return err - } - } - return nil -} - func ReadHeader(br *bufio.Reader) (*CarHeader, error) { hb, err := util.LdRead(br) if err != nil { @@ -167,6 +98,15 @@ func WriteHeader(h *CarHeader, w io.Writer) error { return util.LdWrite(w, hb) } +func SizeHeader(h *CarHeader) (uint64, error) { + hb, err := cbor.DumpObject(h) + if err != nil { + return 0, err + } + + return util.LdSize(hb), nil +} + func (cw *carWriter) enumGetLinks(ctx context.Context, c cid.Cid) ([]*format.Link, error) { nd, err := cw.ds.Get(ctx, c) if err != nil { diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 98288e9cb8..6f1628d034 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -12,6 +12,7 @@ import ( ipldfree "github.com/ipld/go-ipld-prime/impl/free" "github.com/ipld/go-ipld-prime/traversal/selector" "github.com/ipld/go-ipld-prime/traversal/selector/builder" + "github.com/stretchr/testify/require" ) func assertAddNodes(t *testing.T, ds format.DAGService, nds ...format.Node) { @@ -94,54 +95,38 @@ func TestRoundtripSelective(t *testing.T) { assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) - buf := new(bytes.Buffer) ssb := builder.NewSelectorSpecBuilder(ipldfree.NodeBuilder()) selector, err := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { efsb.Insert("Links", ssb.ExploreIndex(1, ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())))) }).Selector() + require.NoError(t, err) - if err != nil { - t.Fatal("Did not build selector") - } - if err := WriteSelectiveCar(context.Background(), sourceBs, []CarDag{CarDag{Root: nd3.Cid(), Selector: selector}}, buf); err != nil { - t.Fatal(err) - } + sc := NewSelectiveCar(context.Background(), sourceBs, []CarDag{CarDag{Root: nd3.Cid(), Selector: selector}}) + scr, err := sc.Traverse() + require.NoError(t, err) + buf := new(bytes.Buffer) + err = scr.Write(buf) + require.NoError(t, err) + require.Equal(t, scr.Size(), uint64(buf.Len())) bserv := dstest.Bserv() ch, err := LoadCar(bserv.Blockstore(), buf) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + require.Equal(t, len(ch.Roots), 1) - if len(ch.Roots) != 1 { - t.Fatal("should have one root") - } - - if !ch.Roots[0].Equals(nd3.Cid()) { - t.Fatal("got wrong cid") - } + require.True(t, ch.Roots[0].Equals(nd3.Cid())) bs := bserv.Blockstore() for _, nd := range []format.Node{a, b, nd1, nd2, nd3} { has, err := bs.Has(nd.Cid()) - if err != nil { - t.Fatal(err) - } - - if !has { - t.Fatal("should have cid in blockstore") - } + require.NoError(t, err) + require.True(t, has) } for _, nd := range []format.Node{c} { has, err := bs.Has(nd.Cid()) - if err != nil { - t.Fatal(err) - } - - if has { - t.Fatal("should NOT have cid in blockstore") - } + require.NoError(t, err) + require.False(t, has) } } diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go new file mode 100644 index 0000000000..9f5d037bc4 --- /dev/null +++ b/ipld/car/selectivecar.go @@ -0,0 +1,170 @@ +package car + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + + cid "github.com/ipfs/go-cid" + util "github.com/ipld/go-car/util" + "github.com/ipld/go-ipld-prime" + dagpb "github.com/ipld/go-ipld-prime-proto" + ipldfree "github.com/ipld/go-ipld-prime/impl/free" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/traversal" +) + +type CarBlock struct { + BlockCID cid.Cid + Offset uint64 + Size uint64 +} + +type SelectiveCar struct { + ctx context.Context + dags []CarDag + store ReadStore +} + +type SelectiveCarResult struct { + SelectiveCar + header CarHeader + carBlocks []CarBlock + size uint64 +} + +func NewSelectiveCar(ctx context.Context, store ReadStore, dags []CarDag) SelectiveCar { + return SelectiveCar{ + ctx: ctx, + store: store, + dags: dags, + } +} + +type selectiveCarTraverser struct { + offset uint64 + cidSet *cid.Set + sc SelectiveCar +} + +func (sc SelectiveCar) Traverse() (SelectiveCarResult, error) { + traverser := &selectiveCarTraverser{0, cid.NewSet(), sc} + return traverser.traverse() +} + +func (sct *selectiveCarTraverser) traverse() (SelectiveCarResult, error) { + header, err := sct.traverseHeader() + if err != nil { + return SelectiveCarResult{}, err + } + carBlocks, err := sct.traverseBlocks() + if err != nil { + return SelectiveCarResult{}, err + } + return SelectiveCarResult{ + sct.sc, + header, + carBlocks, + sct.offset, + }, nil +} + +func (sct *selectiveCarTraverser) traverseHeader() (CarHeader, error) { + roots := make([]cid.Cid, 0, len(sct.sc.dags)) + for _, carDag := range sct.sc.dags { + roots = append(roots, carDag.Root) + } + + header := CarHeader{ + Roots: roots, + Version: 1, + } + + size, err := SizeHeader(&header) + if err != nil { + return CarHeader{}, err + } + + sct.offset += size + + return header, nil +} + +func (sct *selectiveCarTraverser) traverseBlocks() ([]CarBlock, error) { + var carBlocks []CarBlock + var loader ipld.Loader = func(lnk ipld.Link, ctx ipld.LinkContext) (io.Reader, error) { + cl, ok := lnk.(cidlink.Link) + if !ok { + return nil, errors.New("Incorrect Link Type") + } + c := cl.Cid + blk, err := sct.sc.store.Get(c) + if err != nil { + return nil, err + } + raw := blk.RawData() + if !sct.cidSet.Has(c) { + sct.cidSet.Add(c) + size := util.LdSize(c.Bytes(), raw) + carBlocks = append(carBlocks, CarBlock{ + BlockCID: c, + Offset: sct.offset, + Size: size, + }) + sct.offset += size + } + return bytes.NewReader(raw), nil + } + + nbc := dagpb.AddDagPBSupportToChooser(func(ipld.Link, ipld.LinkContext) ipld.NodeBuilder { + return ipldfree.NodeBuilder() + }) + + for _, carDag := range sct.sc.dags { + lnk := cidlink.Link{Cid: carDag.Root} + nb := nbc(lnk, ipld.LinkContext{}) + nd, err := lnk.Load(sct.sc.ctx, ipld.LinkContext{}, nb, loader) + if err != nil { + return nil, err + } + err = traversal.Progress{ + Cfg: &traversal.Config{ + Ctx: sct.sc.ctx, + LinkLoader: loader, + LinkNodeBuilderChooser: nbc, + }, + }.WalkAdv(nd, carDag.Selector, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) + if err != nil { + return nil, err + } + } + return carBlocks, nil +} + +func (sc SelectiveCarResult) Size() uint64 { + return sc.size +} + +func (sc SelectiveCarResult) CarBlocks() []CarBlock { + return sc.carBlocks +} + +func (sc SelectiveCarResult) Write(w io.Writer) error { + if err := WriteHeader(&sc.header, w); err != nil { + return fmt.Errorf("failed to write car header: %s", err) + } + for _, carBlock := range sc.carBlocks { + blk, err := sc.store.Get(carBlock.BlockCID) + if err != nil { + return err + } + raw := blk.RawData() + err = util.LdWrite(w, carBlock.BlockCID.Bytes(), raw) + if err != nil { + return err + } + } + return nil +} diff --git a/ipld/car/util/util_test.go b/ipld/car/util/util_test.go index 1e0567c8c4..e85aed334a 100644 --- a/ipld/car/util/util_test.go +++ b/ipld/car/util/util_test.go @@ -5,7 +5,7 @@ import ( "math/rand" "testing" - "github.com/ipfs/go-car/util" + "github.com/ipld/go-car/util" "github.com/stretchr/testify/require" ) From 78d6bc78ca100dd0ff57a5f4cb70ff0d592e6a80 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Thu, 30 Jan 2020 15:53:41 -0800 Subject: [PATCH 019/291] feat(selectivecar): switch to nodes Use an ipld.Node for a selector rather than an actual selector operation -- that way it can be serialized as needed This commit was moved from ipld/go-car@e661423c1d0841c890ae15ad258010dcec30c888 --- ipld/car/car.go | 6 ------ ipld/car/car_test.go | 5 ++--- ipld/car/selectivecar.go | 12 +++++++++++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 71d153c64b..6cb1606f43 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -11,7 +11,6 @@ import ( cbor "github.com/ipfs/go-ipld-cbor" format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" - "github.com/ipld/go-ipld-prime/traversal/selector" util "github.com/ipld/go-car/util" ) @@ -28,11 +27,6 @@ type ReadStore interface { Get(cid.Cid) (blocks.Block, error) } -type CarDag struct { - Root cid.Cid - Selector selector.Selector -} - type CarHeader struct { Roots []cid.Cid Version uint64 diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 6f1628d034..4acb161006 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -96,11 +96,10 @@ func TestRoundtripSelective(t *testing.T) { assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) ssb := builder.NewSelectorSpecBuilder(ipldfree.NodeBuilder()) - selector, err := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { + selector := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { efsb.Insert("Links", ssb.ExploreIndex(1, ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())))) - }).Selector() - require.NoError(t, err) + }).Node() sc := NewSelectiveCar(context.Background(), sourceBs, []CarDag{CarDag{Root: nd3.Cid(), Selector: selector}}) scr, err := sc.Traverse() diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 9f5d037bc4..19a80f7866 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -14,8 +14,14 @@ import ( ipldfree "github.com/ipld/go-ipld-prime/impl/free" cidlink "github.com/ipld/go-ipld-prime/linking/cid" "github.com/ipld/go-ipld-prime/traversal" + "github.com/ipld/go-ipld-prime/traversal/selector" ) +type CarDag struct { + Root cid.Cid + Selector ipld.Node +} + type CarBlock struct { BlockCID cid.Cid Offset uint64 @@ -123,6 +129,10 @@ func (sct *selectiveCarTraverser) traverseBlocks() ([]CarBlock, error) { }) for _, carDag := range sct.sc.dags { + parsed, err := selector.ParseSelector(carDag.Selector) + if err != nil { + return nil, err + } lnk := cidlink.Link{Cid: carDag.Root} nb := nbc(lnk, ipld.LinkContext{}) nd, err := lnk.Load(sct.sc.ctx, ipld.LinkContext{}, nb, loader) @@ -135,7 +145,7 @@ func (sct *selectiveCarTraverser) traverseBlocks() ([]CarBlock, error) { LinkLoader: loader, LinkNodeBuilderChooser: nbc, }, - }.WalkAdv(nd, carDag.Selector, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) + }.WalkAdv(nd, parsed, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) if err != nil { return nil, err } From f014e0e10e6324cb6c413986d2f01722b6a71215 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Fri, 31 Jan 2020 13:32:08 -0800 Subject: [PATCH 020/291] feat(selectivecar): add two step process Allows a selective car to simply be written, or to be prepared then dumped to disk This commit was moved from ipld/go-car@4aa335b14ef95fba8795f8d00a22020c51b64c48 --- ipld/car/car_test.go | 28 ++++- ipld/car/selectivecar.go | 218 ++++++++++++++++++++++++--------------- 2 files changed, 160 insertions(+), 86 deletions(-) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 4acb161006..1e58417e14 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -88,6 +88,7 @@ func TestRoundtripSelective(t *testing.T) { nd2 := &dag.ProtoNode{} nd2.AddNodeLink("first", nd1) nd2.AddNodeLink("dog", b) + nd2.AddNodeLink("repeat", nd1) nd3 := &dag.ProtoNode{} nd3.AddNodeLink("second", nd2) @@ -101,14 +102,31 @@ func TestRoundtripSelective(t *testing.T) { ssb.ExploreIndex(1, ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())))) }).Node() - sc := NewSelectiveCar(context.Background(), sourceBs, []CarDag{CarDag{Root: nd3.Cid(), Selector: selector}}) - scr, err := sc.Traverse() - require.NoError(t, err) + sc := NewSelectiveCar(context.Background(), sourceBs, []Dag{Dag{Root: nd3.Cid(), Selector: selector}}) + // write car in one step buf := new(bytes.Buffer) - err = scr.Write(buf) + blockCount := 0 + err := sc.Write(buf, func(block Block) error { + blockCount++ + return nil + }) + require.Equal(t, blockCount, 5) + require.NoError(t, err) + + // write car in two steps + scp, err := sc.Prepare() require.NoError(t, err) - require.Equal(t, scr.Size(), uint64(buf.Len())) + buf2 := new(bytes.Buffer) + err = scp.Dump(buf2) + require.NoError(t, err) + + // verify preparation step correctly assesed length + require.Equal(t, scp.Size, uint64(buf.Len())) + // verify equal data written by both methods + require.Equal(t, buf.Bytes(), buf2.Bytes()) + + // readout car and verify contents bserv := dstest.Bserv() ch, err := LoadCar(bserv.Blockstore(), buf) require.NoError(t, err) diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 19a80f7866..bc5e176e8f 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -17,31 +17,47 @@ import ( "github.com/ipld/go-ipld-prime/traversal/selector" ) -type CarDag struct { +// Dag is a root/selector combo to put into a car +type Dag struct { Root cid.Cid Selector ipld.Node } -type CarBlock struct { +// Block is all information and metadata about a block that is part of a car file +type Block struct { BlockCID cid.Cid + Data []byte Offset uint64 Size uint64 } +// SelectiveCar is a car file based on root + selector combos instead of just +// a single root and complete dag walk type SelectiveCar struct { ctx context.Context - dags []CarDag + dags []Dag store ReadStore } -type SelectiveCarResult struct { +// OnCarHeaderFunc is called during traversal when the header is created +type OnCarHeaderFunc func(CarHeader) error + +// OnNewCarBlockFunc is called during traveral when a new unique block is encountered +type OnNewCarBlockFunc func(Block) error + +// SelectiveCarPrepared is a SelectiveCar that has already been traversed, such that it +// can be written quicker with Dump. It also contains metadata already collection about +// the Car file like size and number of blocks that go into it +type SelectiveCarPrepared struct { SelectiveCar - header CarHeader - carBlocks []CarBlock - size uint64 + Size uint64 + Header CarHeader + Cids []cid.Cid } -func NewSelectiveCar(ctx context.Context, store ReadStore, dags []CarDag) SelectiveCar { +// NewSelectiveCar creates a new SelectiveCar for the given car file based +// a block store and set of root+selector pairs +func NewSelectiveCar(ctx context.Context, store ReadStore, dags []Dag) SelectiveCar { return SelectiveCar{ ctx: ctx, store: store, @@ -49,35 +65,97 @@ func NewSelectiveCar(ctx context.Context, store ReadStore, dags []CarDag) Select } } -type selectiveCarTraverser struct { - offset uint64 - cidSet *cid.Set - sc SelectiveCar +func (sc SelectiveCar) traverse(onCarHeader OnCarHeaderFunc, onNewCarBlock OnNewCarBlockFunc) (uint64, error) { + traverser := &selectiveCarTraverser{onCarHeader, onNewCarBlock, 0, cid.NewSet(), sc} + return traverser.traverse() } -func (sc SelectiveCar) Traverse() (SelectiveCarResult, error) { - traverser := &selectiveCarTraverser{0, cid.NewSet(), sc} - return traverser.traverse() +// Prepare traverse a car file and collects data on what is about to be written, but +// does not actually write the file +func (sc SelectiveCar) Prepare() (SelectiveCarPrepared, error) { + var header CarHeader + var cids []cid.Cid + + onCarHeader := func(h CarHeader) error { + header = h + return nil + } + onNewCarBlock := func(block Block) error { + cids = append(cids, block.BlockCID) + return nil + } + size, err := sc.traverse(onCarHeader, onNewCarBlock) + if err != nil { + return SelectiveCarPrepared{}, err + } + return SelectiveCarPrepared{sc, size, header, cids}, nil +} + +func (sc SelectiveCar) Write(w io.Writer, userOnNewCarBlocks ...OnNewCarBlockFunc) error { + onCarHeader := func(h CarHeader) error { + if err := WriteHeader(&h, w); err != nil { + return fmt.Errorf("failed to write car header: %s", err) + } + return nil + } + onNewCarBlock := func(block Block) error { + err := util.LdWrite(w, block.BlockCID.Bytes(), block.Data) + if err != nil { + return err + } + for _, userOnNewCarBlock := range userOnNewCarBlocks { + err := userOnNewCarBlock(block) + if err != nil { + return err + } + } + return nil + } + _, err := sc.traverse(onCarHeader, onNewCarBlock) + return err +} + +// Dump writes the car file as quickly as possible based on information already +// collected +func (sc SelectiveCarPrepared) Dump(w io.Writer) error { + if err := WriteHeader(&sc.Header, w); err != nil { + return fmt.Errorf("failed to write car header: %s", err) + } + for _, c := range sc.Cids { + blk, err := sc.store.Get(c) + if err != nil { + return err + } + raw := blk.RawData() + err = util.LdWrite(w, c.Bytes(), raw) + if err != nil { + return err + } + } + return nil } -func (sct *selectiveCarTraverser) traverse() (SelectiveCarResult, error) { - header, err := sct.traverseHeader() +type selectiveCarTraverser struct { + onCarHeader OnCarHeaderFunc + onNewCarBlock OnNewCarBlockFunc + offset uint64 + cidSet *cid.Set + sc SelectiveCar +} + +func (sct *selectiveCarTraverser) traverse() (uint64, error) { + err := sct.traverseHeader() if err != nil { - return SelectiveCarResult{}, err + return 0, err } - carBlocks, err := sct.traverseBlocks() + err = sct.traverseBlocks() if err != nil { - return SelectiveCarResult{}, err - } - return SelectiveCarResult{ - sct.sc, - header, - carBlocks, - sct.offset, - }, nil + return 0, err + } + return sct.offset, nil } -func (sct *selectiveCarTraverser) traverseHeader() (CarHeader, error) { +func (sct *selectiveCarTraverser) traverseHeader() error { roots := make([]cid.Cid, 0, len(sct.sc.dags)) for _, carDag := range sct.sc.dags { roots = append(roots, carDag.Root) @@ -90,39 +168,43 @@ func (sct *selectiveCarTraverser) traverseHeader() (CarHeader, error) { size, err := SizeHeader(&header) if err != nil { - return CarHeader{}, err + return err } sct.offset += size - return header, nil + return sct.onCarHeader(header) } -func (sct *selectiveCarTraverser) traverseBlocks() ([]CarBlock, error) { - var carBlocks []CarBlock - var loader ipld.Loader = func(lnk ipld.Link, ctx ipld.LinkContext) (io.Reader, error) { - cl, ok := lnk.(cidlink.Link) - if !ok { - return nil, errors.New("Incorrect Link Type") - } - c := cl.Cid - blk, err := sct.sc.store.Get(c) +func (sct *selectiveCarTraverser) loader(lnk ipld.Link, ctx ipld.LinkContext) (io.Reader, error) { + cl, ok := lnk.(cidlink.Link) + if !ok { + return nil, errors.New("Incorrect Link Type") + } + c := cl.Cid + blk, err := sct.sc.store.Get(c) + if err != nil { + return nil, err + } + raw := blk.RawData() + if !sct.cidSet.Has(c) { + sct.cidSet.Add(c) + size := util.LdSize(c.Bytes(), raw) + err := sct.onNewCarBlock(Block{ + BlockCID: c, + Data: raw, + Offset: sct.offset, + Size: size, + }) if err != nil { return nil, err } - raw := blk.RawData() - if !sct.cidSet.Has(c) { - sct.cidSet.Add(c) - size := util.LdSize(c.Bytes(), raw) - carBlocks = append(carBlocks, CarBlock{ - BlockCID: c, - Offset: sct.offset, - Size: size, - }) - sct.offset += size - } - return bytes.NewReader(raw), nil + sct.offset += size } + return bytes.NewReader(raw), nil +} + +func (sct *selectiveCarTraverser) traverseBlocks() error { nbc := dagpb.AddDagPBSupportToChooser(func(ipld.Link, ipld.LinkContext) ipld.NodeBuilder { return ipldfree.NodeBuilder() @@ -131,47 +213,21 @@ func (sct *selectiveCarTraverser) traverseBlocks() ([]CarBlock, error) { for _, carDag := range sct.sc.dags { parsed, err := selector.ParseSelector(carDag.Selector) if err != nil { - return nil, err + return err } lnk := cidlink.Link{Cid: carDag.Root} nb := nbc(lnk, ipld.LinkContext{}) - nd, err := lnk.Load(sct.sc.ctx, ipld.LinkContext{}, nb, loader) + nd, err := lnk.Load(sct.sc.ctx, ipld.LinkContext{}, nb, sct.loader) if err != nil { - return nil, err + return err } err = traversal.Progress{ Cfg: &traversal.Config{ Ctx: sct.sc.ctx, - LinkLoader: loader, + LinkLoader: sct.loader, LinkNodeBuilderChooser: nbc, }, }.WalkAdv(nd, parsed, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) - if err != nil { - return nil, err - } - } - return carBlocks, nil -} - -func (sc SelectiveCarResult) Size() uint64 { - return sc.size -} - -func (sc SelectiveCarResult) CarBlocks() []CarBlock { - return sc.carBlocks -} - -func (sc SelectiveCarResult) Write(w io.Writer) error { - if err := WriteHeader(&sc.header, w); err != nil { - return fmt.Errorf("failed to write car header: %s", err) - } - for _, carBlock := range sc.carBlocks { - blk, err := sc.store.Get(carBlock.BlockCID) - if err != nil { - return err - } - raw := blk.RawData() - err = util.LdWrite(w, carBlock.BlockCID.Bytes(), raw) if err != nil { return err } From 8451f201c27fcd265bf2cda46cea0b998ddd3ba4 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Fri, 31 Jan 2020 14:04:34 -0800 Subject: [PATCH 021/291] refactor(selectivecar): convert to accessors convert SelectiveCarPrepared data to accessors This commit was moved from ipld/go-car@53dd8b04794ffe365d919ae79b00e4802024fe16 --- ipld/car/car_test.go | 6 ++++-- ipld/car/selectivecar.go | 25 ++++++++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 1e58417e14..462d301263 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -121,8 +121,10 @@ func TestRoundtripSelective(t *testing.T) { err = scp.Dump(buf2) require.NoError(t, err) - // verify preparation step correctly assesed length - require.Equal(t, scp.Size, uint64(buf.Len())) + // verify preparation step correctly assesed length and blocks + require.Equal(t, scp.Size(), uint64(buf.Len())) + require.Equal(t, len(scp.Cids()), blockCount) + // verify equal data written by both methods require.Equal(t, buf.Bytes(), buf2.Bytes()) diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index bc5e176e8f..f98e81bd26 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -50,9 +50,9 @@ type OnNewCarBlockFunc func(Block) error // the Car file like size and number of blocks that go into it type SelectiveCarPrepared struct { SelectiveCar - Size uint64 - Header CarHeader - Cids []cid.Cid + size uint64 + header CarHeader + cids []cid.Cid } // NewSelectiveCar creates a new SelectiveCar for the given car file based @@ -115,13 +115,28 @@ func (sc SelectiveCar) Write(w io.Writer, userOnNewCarBlocks ...OnNewCarBlockFun return err } +// Size returns the total size in bytes of the car file that will be written +func (sc SelectiveCarPrepared) Size() uint64 { + return sc.size +} + +// Header returns the header for the car file that will be written +func (sc SelectiveCarPrepared) Header() CarHeader { + return sc.header +} + +// Cids returns the list of unique block cids that will be written to the car file +func (sc SelectiveCarPrepared) Cids() []cid.Cid { + return sc.cids +} + // Dump writes the car file as quickly as possible based on information already // collected func (sc SelectiveCarPrepared) Dump(w io.Writer) error { - if err := WriteHeader(&sc.Header, w); err != nil { + if err := WriteHeader(&sc.header, w); err != nil { return fmt.Errorf("failed to write car header: %s", err) } - for _, c := range sc.Cids { + for _, c := range sc.cids { blk, err := sc.store.Get(c) if err != nil { return err From 8c25847b697743e5b88499e22f8c0e34efa461a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 21 Feb 2020 20:07:41 +0100 Subject: [PATCH 022/291] Use correct walk func in enumGetLinks This commit was moved from ipld/go-car@07620eec177fd87b8f79db2e0e08ddbf8bfe6640 --- ipld/car/car.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 6cb1606f43..6fc4b6277e 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -111,7 +111,7 @@ func (cw *carWriter) enumGetLinks(ctx context.Context, c cid.Cid) ([]*format.Lin return nil, err } - return nd.Links(), nil + return cw.walk(nd) } func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { From 2a57a154e34e0477231d774b9b7a02212153d516 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Fri, 6 Mar 2020 15:10:34 -0800 Subject: [PATCH 023/291] fix(car): minor cleanups rename SizeHeader to HeaderSize, add comments and cleanups in selective car test This commit was moved from ipld/go-car@6236fbaa4401505430e95f045cf53ebc2bb57015 --- ipld/car/car.go | 2 +- ipld/car/car_test.go | 13 ++++++++++++- ipld/car/selectivecar.go | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 6fc4b6277e..bce59d347b 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -92,7 +92,7 @@ func WriteHeader(h *CarHeader, w io.Writer) error { return util.LdWrite(w, hb) } -func SizeHeader(h *CarHeader) (uint64, error) { +func HeaderSize(h *CarHeader) (uint64, error) { hb, err := cbor.DumpObject(h) if err != nil { return 0, err diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 462d301263..4c969626f4 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -97,6 +97,14 @@ func TestRoundtripSelective(t *testing.T) { assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) ssb := builder.NewSelectorSpecBuilder(ipldfree.NodeBuilder()) + + // the graph assembled above looks as follows, in order: + // nd3 -> [c, nd2 -> [nd1 -> a, b, nd1 -> a]] + // this selector starts at n3, and traverses a link at index 1 (nd2, the second link, zero indexed) + // it then recursively traverses all of its children + // the only node skipped is 'c' -- link at index 0 immediately below nd3 + // the purpose is simply to show we are not writing the entire dag underneath + // nd3 selector := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { efsb.Insert("Links", ssb.ExploreIndex(1, ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())))) @@ -114,8 +122,11 @@ func TestRoundtripSelective(t *testing.T) { require.Equal(t, blockCount, 5) require.NoError(t, err) + // create a new builder for two-step write + sc2 := NewSelectiveCar(context.Background(), sourceBs, []Dag{Dag{Root: nd3.Cid(), Selector: selector}}) + // write car in two steps - scp, err := sc.Prepare() + scp, err := sc2.Prepare() require.NoError(t, err) buf2 := new(bytes.Buffer) err = scp.Dump(buf2) diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index f98e81bd26..705cf563f2 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -181,7 +181,7 @@ func (sct *selectiveCarTraverser) traverseHeader() error { Version: 1, } - size, err := SizeHeader(&header) + size, err := HeaderSize(&header) if err != nil { return err } From a5d1ab5c001725e595b97fdce884d8b39db72cec Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 27 Mar 2020 10:32:42 -0700 Subject: [PATCH 024/291] feat: only require the NodeGetter interface instead of the DAG interface This makes it possible to pass in a dagservice "session" for better performance when fetching from bitswap. This commit was moved from ipld/go-car@8a3014575e8e7f9c73c9bf7882256b36528b390d --- ipld/car/car.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index bce59d347b..9f7e6d10db 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -33,18 +33,18 @@ type CarHeader struct { } type carWriter struct { - ds format.DAGService + ds format.NodeGetter w io.Writer walk WalkFunc } type WalkFunc func(format.Node) ([]*format.Link, error) -func WriteCar(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.Writer) error { +func WriteCar(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.Writer) error { return WriteCarWithWalker(ctx, ds, roots, w, DefaultWalkFunc) } -func WriteCarWithWalker(ctx context.Context, ds format.DAGService, roots []cid.Cid, w io.Writer, walk WalkFunc) error { +func WriteCarWithWalker(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.Writer, walk WalkFunc) error { h := &CarHeader{ Roots: roots, From 9237b405df0d2baef83ec17b8262084678d68a5b Mon Sep 17 00:00:00 2001 From: Eric Myhre Date: Wed, 29 Apr 2020 19:41:19 +0200 Subject: [PATCH 025/291] Update go-ipld-prime to the era of NodeAssembler. This commit was moved from ipld/go-car@96126600d5b591231b5aaa29ed3f086cf74c2214 --- ipld/car/car_test.go | 4 ++-- ipld/car/selectivecar.go | 21 +++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 4c969626f4..3265d8374b 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -9,7 +9,7 @@ import ( format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" - ipldfree "github.com/ipld/go-ipld-prime/impl/free" + basicnode "github.com/ipld/go-ipld-prime/node/basic" "github.com/ipld/go-ipld-prime/traversal/selector" "github.com/ipld/go-ipld-prime/traversal/selector/builder" "github.com/stretchr/testify/require" @@ -96,7 +96,7 @@ func TestRoundtripSelective(t *testing.T) { assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) - ssb := builder.NewSelectorSpecBuilder(ipldfree.NodeBuilder()) + ssb := builder.NewSelectorSpecBuilder(basicnode.Style.Any) // the graph assembled above looks as follows, in order: // nd3 -> [c, nd2 -> [nd1 -> a, b, nd1 -> a]] diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 705cf563f2..0b11fc8155 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -11,8 +11,8 @@ import ( util "github.com/ipld/go-car/util" "github.com/ipld/go-ipld-prime" dagpb "github.com/ipld/go-ipld-prime-proto" - ipldfree "github.com/ipld/go-ipld-prime/impl/free" cidlink "github.com/ipld/go-ipld-prime/linking/cid" + basicnode "github.com/ipld/go-ipld-prime/node/basic" "github.com/ipld/go-ipld-prime/traversal" "github.com/ipld/go-ipld-prime/traversal/selector" ) @@ -221,8 +221,8 @@ func (sct *selectiveCarTraverser) loader(lnk ipld.Link, ctx ipld.LinkContext) (i func (sct *selectiveCarTraverser) traverseBlocks() error { - nbc := dagpb.AddDagPBSupportToChooser(func(ipld.Link, ipld.LinkContext) ipld.NodeBuilder { - return ipldfree.NodeBuilder() + nsc := dagpb.AddDagPBSupportToChooser(func(ipld.Link, ipld.LinkContext) (ipld.NodeStyle, error) { + return basicnode.Style.Any, nil }) for _, carDag := range sct.sc.dags { @@ -231,16 +231,21 @@ func (sct *selectiveCarTraverser) traverseBlocks() error { return err } lnk := cidlink.Link{Cid: carDag.Root} - nb := nbc(lnk, ipld.LinkContext{}) - nd, err := lnk.Load(sct.sc.ctx, ipld.LinkContext{}, nb, sct.loader) + ns, err := nsc(lnk, ipld.LinkContext{}) if err != nil { return err } + nb := ns.NewBuilder() + err = lnk.Load(sct.sc.ctx, ipld.LinkContext{}, nb, sct.loader) + if err != nil { + return err + } + nd := nb.Build() err = traversal.Progress{ Cfg: &traversal.Config{ - Ctx: sct.sc.ctx, - LinkLoader: sct.loader, - LinkNodeBuilderChooser: nbc, + Ctx: sct.sc.ctx, + LinkLoader: sct.loader, + LinkTargetNodeStyleChooser: nsc, }, }.WalkAdv(nd, parsed, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) if err != nil { From 238bbdfc284823c0e152378022f958c074215dc3 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Tue, 22 Sep 2020 17:43:51 -0700 Subject: [PATCH 026/291] feat(deps): update ipld libs update ipld libs to fairly recent version (lastest used by go-ipld-prime-proto This commit was moved from ipld/go-car@3df22374d842b97d8091debdfe1ee731c8dbaaa1 --- ipld/car/car_test.go | 2 +- ipld/car/selectivecar.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 3265d8374b..53d59b3653 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -96,7 +96,7 @@ func TestRoundtripSelective(t *testing.T) { assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) - ssb := builder.NewSelectorSpecBuilder(basicnode.Style.Any) + ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) // the graph assembled above looks as follows, in order: // nd3 -> [c, nd2 -> [nd1 -> a, b, nd1 -> a]] diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 0b11fc8155..a416237310 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -221,8 +221,8 @@ func (sct *selectiveCarTraverser) loader(lnk ipld.Link, ctx ipld.LinkContext) (i func (sct *selectiveCarTraverser) traverseBlocks() error { - nsc := dagpb.AddDagPBSupportToChooser(func(ipld.Link, ipld.LinkContext) (ipld.NodeStyle, error) { - return basicnode.Style.Any, nil + nsc := dagpb.AddDagPBSupportToChooser(func(ipld.Link, ipld.LinkContext) (ipld.NodePrototype, error) { + return basicnode.Prototype.Any, nil }) for _, carDag := range sct.sc.dags { @@ -243,9 +243,9 @@ func (sct *selectiveCarTraverser) traverseBlocks() error { nd := nb.Build() err = traversal.Progress{ Cfg: &traversal.Config{ - Ctx: sct.sc.ctx, - LinkLoader: sct.loader, - LinkTargetNodeStyleChooser: nsc, + Ctx: sct.sc.ctx, + LinkLoader: sct.loader, + LinkTargetNodePrototypeChooser: nsc, }, }.WalkAdv(nd, parsed, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) if err != nil { From a663575e700bef8b585d2fac0e43977e9c367375 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 5 Nov 2020 17:07:39 +1100 Subject: [PATCH 027/291] fix: main NewReader call This commit was moved from ipld/go-car@b8a3c261e6ea3844fb7256de666baf0f7eba6a6c --- ipld/car/car/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/car/main.go b/ipld/car/car/main.go index c88459adea..8ccd4e5d87 100644 --- a/ipld/car/car/main.go +++ b/ipld/car/car/main.go @@ -26,7 +26,7 @@ var headerCmd = cli.Command{ } defer fi.Close() - ch, err := car.ReadHeader(bufio.NewReader(fi)) + ch, _, err := car.ReadHeader(bufio.NewReader(fi)) if err != nil { return err } From d76751b039b3ac87e945d952bd43f6c47e655e06 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 5 Nov 2020 17:08:20 +1100 Subject: [PATCH 028/291] feat: handle mid-varint EOF case as UnexpectedEOF This commit was moved from ipld/go-car@c2f1ff261ecf16720deb8e5ee9f772bd51c0e477 --- ipld/car/car_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++ ipld/car/util/util.go | 7 +++++ 2 files changed, 72 insertions(+) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 53d59b3653..e1e842402e 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -3,6 +3,8 @@ package car import ( "bytes" "context" + "encoding/hex" + "io" "testing" cid "github.com/ipfs/go-cid" @@ -160,3 +162,66 @@ func TestRoundtripSelective(t *testing.T) { require.False(t, has) } } + +func TestEOFHandling(t *testing.T) { + // fixture is a clean single-block, single-root CAR + fixture, err := hex.DecodeString("3aa265726f6f747381d82a58250001711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80b6776657273696f6e012c01711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80ba165646f646779f5") + if err != nil { + t.Fatal(err) + } + + load := func(t *testing.T, byts []byte) *CarReader { + cr, err := NewCarReader(bytes.NewReader(byts)) + if err != nil { + t.Fatal(err) + } + + blk, err := cr.Next() + if err != nil { + t.Fatal(err) + } + if blk.Cid().String() != "bafyreiavd7u6opdcm6tqmddpnrgmvfb4enxuwglhenejmchnwqvixd5ibm" { + t.Fatal("unexpected CID") + } + + return cr + } + + t.Run("CleanEOF", func(t *testing.T) { + cr := load(t, fixture) + + blk, err := cr.Next() + if err != io.EOF { + t.Fatal("Didn't get expected EOF") + } + if blk != nil { + t.Fatal("EOF returned expected block") + } + }) + + t.Run("BadVarint", func(t *testing.T) { + fixtureBadVarint := append(fixture, 160) + cr := load(t, fixtureBadVarint) + + blk, err := cr.Next() + if err != io.ErrUnexpectedEOF { + t.Fatal("Didn't get unexpected EOF") + } + if blk != nil { + t.Fatal("EOF returned unexpected block") + } + }) + + t.Run("TruncatedBlock", func(t *testing.T) { + fixtureTruncatedBlock := append(fixture, 100, 0, 0) + cr := load(t, fixtureTruncatedBlock) + + blk, err := cr.Next() + if err != io.ErrUnexpectedEOF { + t.Fatal("Didn't get unexpected EOF") + } + if blk != nil { + t.Fatal("EOF returned unexpected block") + } + }) +} diff --git a/ipld/car/util/util.go b/ipld/car/util/util.go index 47c7e78d66..08048f333e 100644 --- a/ipld/car/util/util.go +++ b/ipld/car/util/util.go @@ -100,8 +100,15 @@ func LdSize(d ...[]byte) uint64 { } func LdRead(r *bufio.Reader) ([]byte, error) { + if _, err := r.Peek(1); err != nil { // no more blocks, likely clean io.EOF + return nil, err + } + l, err := binary.ReadUvarint(r) if err != nil { + if err == io.EOF { + return nil, io.ErrUnexpectedEOF // don't silently pretend this is a clean EOF + } return nil, err } From 76030f7606088a461fd69768b3a7e9c86d1445e3 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Tue, 17 Nov 2020 12:20:17 -0800 Subject: [PATCH 029/291] feat(car): allow block hooks when using two step write This commit was moved from ipld/go-car@7f9fecd191336b673dda3a7445af9fd5576f4459 --- ipld/car/car_test.go | 11 ++++++++++- ipld/car/selectivecar.go | 28 +++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index e1e842402e..1fb87f5f9b 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -117,7 +117,9 @@ func TestRoundtripSelective(t *testing.T) { // write car in one step buf := new(bytes.Buffer) blockCount := 0 + var oneStepBlocks []Block err := sc.Write(buf, func(block Block) error { + oneStepBlocks = append(oneStepBlocks, block) blockCount++ return nil }) @@ -128,7 +130,11 @@ func TestRoundtripSelective(t *testing.T) { sc2 := NewSelectiveCar(context.Background(), sourceBs, []Dag{Dag{Root: nd3.Cid(), Selector: selector}}) // write car in two steps - scp, err := sc2.Prepare() + var twoStepBlocks []Block + scp, err := sc2.Prepare(func(block Block) error { + twoStepBlocks = append(twoStepBlocks, block) + return nil + }) require.NoError(t, err) buf2 := new(bytes.Buffer) err = scp.Dump(buf2) @@ -141,6 +147,9 @@ func TestRoundtripSelective(t *testing.T) { // verify equal data written by both methods require.Equal(t, buf.Bytes(), buf2.Bytes()) + // verify equal blocks were passed to user block hook funcs + require.Equal(t, oneStepBlocks, twoStepBlocks) + // readout car and verify contents bserv := dstest.Bserv() ch, err := LoadCar(bserv.Blockstore(), buf) diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index a416237310..007da104ce 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -50,9 +50,10 @@ type OnNewCarBlockFunc func(Block) error // the Car file like size and number of blocks that go into it type SelectiveCarPrepared struct { SelectiveCar - size uint64 - header CarHeader - cids []cid.Cid + size uint64 + header CarHeader + cids []cid.Cid + userOnNewCarBlocks []OnNewCarBlockFunc } // NewSelectiveCar creates a new SelectiveCar for the given car file based @@ -72,7 +73,7 @@ func (sc SelectiveCar) traverse(onCarHeader OnCarHeaderFunc, onNewCarBlock OnNew // Prepare traverse a car file and collects data on what is about to be written, but // does not actually write the file -func (sc SelectiveCar) Prepare() (SelectiveCarPrepared, error) { +func (sc SelectiveCar) Prepare(userOnNewCarBlocks ...OnNewCarBlockFunc) (SelectiveCarPrepared, error) { var header CarHeader var cids []cid.Cid @@ -88,7 +89,7 @@ func (sc SelectiveCar) Prepare() (SelectiveCarPrepared, error) { if err != nil { return SelectiveCarPrepared{}, err } - return SelectiveCarPrepared{sc, size, header, cids}, nil + return SelectiveCarPrepared{sc, size, header, cids, userOnNewCarBlocks}, nil } func (sc SelectiveCar) Write(w io.Writer, userOnNewCarBlocks ...OnNewCarBlockFunc) error { @@ -133,6 +134,10 @@ func (sc SelectiveCarPrepared) Cids() []cid.Cid { // Dump writes the car file as quickly as possible based on information already // collected func (sc SelectiveCarPrepared) Dump(w io.Writer) error { + offset, err := HeaderSize(&sc.header) + if err != nil { + return fmt.Errorf("failed to size car header: %s", err) + } if err := WriteHeader(&sc.header, w); err != nil { return fmt.Errorf("failed to write car header: %s", err) } @@ -142,10 +147,23 @@ func (sc SelectiveCarPrepared) Dump(w io.Writer) error { return err } raw := blk.RawData() + size := util.LdSize(c.Bytes(), raw) err = util.LdWrite(w, c.Bytes(), raw) if err != nil { return err } + for _, userOnNewCarBlock := range sc.userOnNewCarBlocks { + err := userOnNewCarBlock(Block{ + BlockCID: c, + Data: raw, + Offset: offset, + Size: size, + }) + if err != nil { + return err + } + } + offset += size } return nil } From dc0e57b1f3455ec0efd4a4f4c9e374c07bc2e25f Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 3 Dec 2020 17:47:26 +0700 Subject: [PATCH 030/291] run gofmt -s This commit was moved from ipld/go-car@4aefd37300f7a1137654051f108f04fbaa693d23 --- ipld/car/car_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 1fb87f5f9b..b50111b27a 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -112,7 +112,7 @@ func TestRoundtripSelective(t *testing.T) { ssb.ExploreIndex(1, ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())))) }).Node() - sc := NewSelectiveCar(context.Background(), sourceBs, []Dag{Dag{Root: nd3.Cid(), Selector: selector}}) + sc := NewSelectiveCar(context.Background(), sourceBs, []Dag{{Root: nd3.Cid(), Selector: selector}}) // write car in one step buf := new(bytes.Buffer) @@ -127,7 +127,7 @@ func TestRoundtripSelective(t *testing.T) { require.NoError(t, err) // create a new builder for two-step write - sc2 := NewSelectiveCar(context.Background(), sourceBs, []Dag{Dag{Root: nd3.Cid(), Selector: selector}}) + sc2 := NewSelectiveCar(context.Background(), sourceBs, []Dag{{Root: nd3.Cid(), Selector: selector}}) // write car in two steps var twoStepBlocks []Block From bcd1cabaf12474893ba17b2ee3efbc0c0f395928 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 8 Mar 2021 11:18:12 -0800 Subject: [PATCH 031/291] ci: remove travis support we use github actions now This commit was moved from ipld/go-car@bf7d93e8fb3d66d686a0812ab81a716bc587258f --- ipld/car/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/ipld/car/README.md b/ipld/car/README.md index 1142a22935..65124038a2 100644 --- a/ipld/car/README.md +++ b/ipld/car/README.md @@ -5,7 +5,6 @@ go-car (go!) [![](https://img.shields.io/badge/project-ipld-orange.svg?style=flat-square)](https://github.com/ipld/ipld) [![](https://img.shields.io/badge/freenode-%23ipld-orange.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipld) [![Coverage Status](https://codecov.io/gh/ipld/go-car/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/go-car/branch/master) -[![Travis CI](https://travis-ci.org/ipld/go-car.svg?branch=master)](https://travis-ci.org/ipld/go-car) > go-car is a simple way of packing a merkledag into a single file From f5906a25fd14c10d1b7d5050959d309b5d9af756 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 24 Mar 2021 16:08:47 -0700 Subject: [PATCH 032/291] chore: relicense All copyrightable contributions were made by PL employees anyways. This commit was moved from ipld/go-car@da44d7096cb24954d5b8f1d1fdf8f4572123c0d9 --- ipld/car/LICENSE | 4 ++++ ipld/car/LICENSE-APACHE | 5 +++++ ipld/car/LICENSE-MIT | 19 +++++++++++++++++++ ipld/car/README.md | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 ipld/car/LICENSE create mode 100644 ipld/car/LICENSE-APACHE create mode 100644 ipld/car/LICENSE-MIT diff --git a/ipld/car/LICENSE b/ipld/car/LICENSE new file mode 100644 index 0000000000..2c2c6eb27b --- /dev/null +++ b/ipld/car/LICENSE @@ -0,0 +1,4 @@ +This project is dual-licensed under Apache 2.0 and MIT terms. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/ipld/car/LICENSE-APACHE b/ipld/car/LICENSE-APACHE new file mode 100644 index 0000000000..14478a3b60 --- /dev/null +++ b/ipld/car/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/ipld/car/LICENSE-MIT b/ipld/car/LICENSE-MIT new file mode 100644 index 0000000000..72dc60d84b --- /dev/null +++ b/ipld/car/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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. diff --git a/ipld/car/README.md b/ipld/car/README.md index 65124038a2..033318ee12 100644 --- a/ipld/car/README.md +++ b/ipld/car/README.md @@ -24,4 +24,4 @@ Small note: If editing the Readme, please conform to the [standard-readme](https ## License -MIT © Whyrusleeping +Apache-2.0/MIT © Protocol Labs From dcbdec2fbdf167765c86395e37cb2cd26c9d0c70 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 24 Mar 2021 20:20:35 -0700 Subject: [PATCH 033/291] chore: remove LICENSE It looks like this may be confusing pkg.go.dev's license detection. This commit was moved from ipld/go-car@317e565c9ce5e797b52e1601212ecaefdb194b8d --- ipld/car/LICENSE | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 ipld/car/LICENSE diff --git a/ipld/car/LICENSE b/ipld/car/LICENSE deleted file mode 100644 index 2c2c6eb27b..0000000000 --- a/ipld/car/LICENSE +++ /dev/null @@ -1,4 +0,0 @@ -This project is dual-licensed under Apache 2.0 and MIT terms. - -MIT: https://www.opensource.org/licenses/mit -Apache-2.0: https://www.apache.org/licenses/license-2.0 From 22ba694bcd784e959a7ca9d73f595b77c2e1cbce Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 24 Mar 2021 20:46:01 -0700 Subject: [PATCH 034/291] chore: switch to a single license file This one should work with both github and godoc. This commit was moved from ipld/go-car@eee4102e3bcaa4c3178517733620194ee5bf3e42 --- ipld/car/LICENSE-APACHE | 5 - ipld/car/LICENSE-MIT | 19 ---- ipld/car/LICENSE.md | 229 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 24 deletions(-) delete mode 100644 ipld/car/LICENSE-APACHE delete mode 100644 ipld/car/LICENSE-MIT create mode 100644 ipld/car/LICENSE.md diff --git a/ipld/car/LICENSE-APACHE b/ipld/car/LICENSE-APACHE deleted file mode 100644 index 14478a3b60..0000000000 --- a/ipld/car/LICENSE-APACHE +++ /dev/null @@ -1,5 +0,0 @@ -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/ipld/car/LICENSE-MIT b/ipld/car/LICENSE-MIT deleted file mode 100644 index 72dc60d84b..0000000000 --- a/ipld/car/LICENSE-MIT +++ /dev/null @@ -1,19 +0,0 @@ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -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. diff --git a/ipld/car/LICENSE.md b/ipld/car/LICENSE.md new file mode 100644 index 0000000000..15601cba67 --- /dev/null +++ b/ipld/car/LICENSE.md @@ -0,0 +1,229 @@ +The contents of this repository are Copyright (c) corresponding authors and +contributors, licensed under the `Permissive License Stack` meaning either of: + +- Apache-2.0 Software License: https://www.apache.org/licenses/LICENSE-2.0 + ([...4tr2kfsq](https://gateway.ipfs.io/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) + +- MIT Software License: https://opensource.org/licenses/MIT + ([...vljevcba](https://gateway.ipfs.io/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) + +You may not use the contents of this repository except in compliance +with one of the listed Licenses. For an extended clarification of the +intent behind the choice of Licensing please refer to +https://protocol.ai/blog/announcing-the-permissive-license-stack/ + +Unless required by applicable law or agreed to in writing, software +distributed under the terms listed in this notice is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +either express or implied. See each License for the specific language +governing permissions and limitations under that License. + + +`SPDX-License-Identifier: Apache-2.0 OR MIT` + +Verbatim copies of both licenses are included below: + +
Apache-2.0 Software License + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +``` +
+ +
MIT Software License + +``` +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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. +``` +
From 6cc2e74de007dee0da375acdd0c34b2826c4210b Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Thu, 1 Apr 2021 16:17:27 +0700 Subject: [PATCH 035/291] fix staticcheck errors This commit was moved from ipld/go-car@4f09635583701cee465e56eb52a68064dbefee38 --- ipld/car/car.go | 25 +++++++++++-------------- ipld/car/selectivecar.go | 2 +- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 9f7e6d10db..30fdd3449f 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -183,17 +183,16 @@ func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { var buf []blocks.Block for { blk, err := cr.Next() - switch err { - case io.EOF: - if len(buf) > 0 { - if err := s.PutMany(buf); err != nil { - return nil, err + if err != nil { + if err == io.EOF { + if len(buf) > 0 { + if err := s.PutMany(buf); err != nil { + return nil, err + } } + return cr.Header, nil } - return cr.Header, nil - default: return nil, err - case nil: } buf = append(buf, blk) @@ -208,15 +207,13 @@ func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { } func loadCarSlow(s Store, cr *CarReader) (*CarHeader, error) { - for { blk, err := cr.Next() - switch err { - case io.EOF: - return cr.Header, nil - default: + if err != nil { + if err == io.EOF { + return cr.Header, nil + } return nil, err - case nil: } if err := s.Put(blk); err != nil { diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 007da104ce..15de2d63d0 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -212,7 +212,7 @@ func (sct *selectiveCarTraverser) traverseHeader() error { func (sct *selectiveCarTraverser) loader(lnk ipld.Link, ctx ipld.LinkContext) (io.Reader, error) { cl, ok := lnk.(cidlink.Link) if !ok { - return nil, errors.New("Incorrect Link Type") + return nil, errors.New("incorrect link type") } c := cl.Cid blk, err := sct.sc.store.Get(c) From 1e4dfb56f44d498e3cae7fb3a308e74a2b109999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 8 Mar 2021 12:45:29 +0000 Subject: [PATCH 036/291] replace go-ipld-prime-proto with go-codec-dagpb We're transitioning to the latter codec for dag-pb on ipld-prime, so go-car should use it too to avoid registering two codecs to handle the same multicodec code. Besides swapping the library, we also update go-ipld-prime to grab its raw codec, to be used for raw nodes. We also roll our own little version of go-ipld-prime-proto's AddDagPBSupportToChooser. With go-codec-dagpb, raw nodes don't need a special prototype, so we need just one special case for PBNode. It also doesn't seem worthwhile to add API to go-codec-dagpb for what is essentially three lines of fairly simple code. We can always revisit that if more downstream users require it too. This commit was moved from ipld/go-car@97bd4ecd4dfcf4e3d20d4cbd5a14aaebcabf4e37 --- ipld/car/selectivecar.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 15de2d63d0..f612fd4bdc 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -10,11 +10,14 @@ import ( cid "github.com/ipfs/go-cid" util "github.com/ipld/go-car/util" "github.com/ipld/go-ipld-prime" - dagpb "github.com/ipld/go-ipld-prime-proto" cidlink "github.com/ipld/go-ipld-prime/linking/cid" basicnode "github.com/ipld/go-ipld-prime/node/basic" "github.com/ipld/go-ipld-prime/traversal" "github.com/ipld/go-ipld-prime/traversal/selector" + + // The dag-pb and raw codecs are necessary for unixfs. + dagpb "github.com/ipld/go-codec-dagpb" + _ "github.com/ipld/go-ipld-prime/codec/raw" ) // Dag is a root/selector combo to put into a car @@ -238,10 +241,14 @@ func (sct *selectiveCarTraverser) loader(lnk ipld.Link, ctx ipld.LinkContext) (i } func (sct *selectiveCarTraverser) traverseBlocks() error { - - nsc := dagpb.AddDagPBSupportToChooser(func(ipld.Link, ipld.LinkContext) (ipld.NodePrototype, error) { + nsc := func(lnk ipld.Link, lctx ipld.LinkContext) (ipld.NodePrototype, error) { + // We can decode all nodes into basicnode's Any, except for + // dagpb nodes, which must explicitly use the PBNode prototype. + if lnk, ok := lnk.(cidlink.Link); ok && lnk.Cid.Prefix().Codec == 0x70 { + return dagpb.Type.PBNode, nil + } return basicnode.Prototype.Any, nil - }) + } for _, carDag := range sct.sc.dags { parsed, err := selector.ParseSelector(carDag.Selector) @@ -249,10 +256,7 @@ func (sct *selectiveCarTraverser) traverseBlocks() error { return err } lnk := cidlink.Link{Cid: carDag.Root} - ns, err := nsc(lnk, ipld.LinkContext{}) - if err != nil { - return err - } + ns, _ := nsc(lnk, ipld.LinkContext{}) // nsc won't error nb := ns.NewBuilder() err = lnk.Load(sct.sc.ctx, ipld.LinkContext{}, nb, sct.loader) if err != nil { From 56487887b4839f02317d03228b03f851b127f7be Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Thu, 11 Mar 2021 18:15:57 -0800 Subject: [PATCH 037/291] feat(car): update for ipld linksystem updates to be compatible with ipld linksystem This commit was moved from ipld/go-car@adf2cca1ad46d409e5d6bc3312d380c752287547 --- ipld/car/selectivecar.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index f612fd4bdc..50a955206a 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -70,7 +70,9 @@ func NewSelectiveCar(ctx context.Context, store ReadStore, dags []Dag) Selective } func (sc SelectiveCar) traverse(onCarHeader OnCarHeaderFunc, onNewCarBlock OnNewCarBlockFunc) (uint64, error) { - traverser := &selectiveCarTraverser{onCarHeader, onNewCarBlock, 0, cid.NewSet(), sc} + + traverser := &selectiveCarTraverser{onCarHeader, onNewCarBlock, 0, cid.NewSet(), sc, cidlink.DefaultLinkSystem()} + traverser.lsys.StorageReadOpener = traverser.loader return traverser.traverse() } @@ -177,6 +179,7 @@ type selectiveCarTraverser struct { offset uint64 cidSet *cid.Set sc SelectiveCar + lsys ipld.LinkSystem } func (sct *selectiveCarTraverser) traverse() (uint64, error) { @@ -212,7 +215,7 @@ func (sct *selectiveCarTraverser) traverseHeader() error { return sct.onCarHeader(header) } -func (sct *selectiveCarTraverser) loader(lnk ipld.Link, ctx ipld.LinkContext) (io.Reader, error) { +func (sct *selectiveCarTraverser) loader(ctx ipld.LinkContext, lnk ipld.Link) (io.Reader, error) { cl, ok := lnk.(cidlink.Link) if !ok { return nil, errors.New("incorrect link type") @@ -257,16 +260,14 @@ func (sct *selectiveCarTraverser) traverseBlocks() error { } lnk := cidlink.Link{Cid: carDag.Root} ns, _ := nsc(lnk, ipld.LinkContext{}) // nsc won't error - nb := ns.NewBuilder() - err = lnk.Load(sct.sc.ctx, ipld.LinkContext{}, nb, sct.loader) + nd, err := sct.lsys.Load(ipld.LinkContext{Ctx: sct.sc.ctx}, lnk, ns) if err != nil { return err } - nd := nb.Build() err = traversal.Progress{ Cfg: &traversal.Config{ Ctx: sct.sc.ctx, - LinkLoader: sct.loader, + LinkSystem: sct.lsys, LinkTargetNodePrototypeChooser: nsc, }, }.WalkAdv(nd, parsed, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) From 6edffc7791305aa9e70e214d327eaa7ca9f57ce5 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 3 Jun 2021 13:37:15 +1000 Subject: [PATCH 038/291] fix: ReadHeader return value mismatch This commit was moved from ipld/go-car@97fb3e695cac0071c0935196cd99e5bef828501b --- ipld/car/car/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/car/main.go b/ipld/car/car/main.go index 8ccd4e5d87..c88459adea 100644 --- a/ipld/car/car/main.go +++ b/ipld/car/car/main.go @@ -26,7 +26,7 @@ var headerCmd = cli.Command{ } defer fi.Close() - ch, _, err := car.ReadHeader(bufio.NewReader(fi)) + ch, err := car.ReadHeader(bufio.NewReader(fi)) if err != nil { return err } From 873f8f2e2f10b27697efa657caaa9d4f7b197c77 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 3 Jun 2021 13:57:27 +1000 Subject: [PATCH 039/291] fix: lint errors This commit was moved from ipld/go-car@5bff03e9b7458ee5bdefb43671eeeb787ec59aa9 --- ipld/car/car/main.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/ipld/car/car/main.go b/ipld/car/car/main.go index c88459adea..e1be8830ee 100644 --- a/ipld/car/car/main.go +++ b/ipld/car/car/main.go @@ -61,12 +61,11 @@ var verifyCmd = cli.Command{ for { _, err := cr.Next() - switch err { - case io.EOF: + if err == io.EOF { return nil - default: + } + if err != nil { return err - case nil: } } }, @@ -93,12 +92,11 @@ var lsCmd = cli.Command{ for { blk, err := cr.Next() - switch err { - case io.EOF: + if err == io.EOF { return nil - default: + } + if err != nil { return err - case nil: } fmt.Println(blk.Cid()) } @@ -112,5 +110,5 @@ func main() { lsCmd, verifyCmd, } - app.RunAndExitOnError() + app.Run(os.Args) } From b81b803836215c86c92bd15f2d5cf01c3a3c2289 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 3 Jun 2021 18:58:25 +1000 Subject: [PATCH 040/291] chore: add header error tests Ref: https://github.com/ipld/js-car/pull/28 the primary difference with js-car is that go-car requires a non-empty roots array whereas js-car is fine with empty roots array, hence the test fixture differences This commit was moved from ipld/go-car@2876c180ffecb8139ff380cefa519842e38f456c --- ipld/car/.gitignore | 3 ++ ipld/car/car.go | 10 +++--- ipld/car/car_test.go | 78 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 ipld/car/.gitignore diff --git a/ipld/car/.gitignore b/ipld/car/.gitignore new file mode 100644 index 0000000000..3b831d277f --- /dev/null +++ b/ipld/car/.gitignore @@ -0,0 +1,3 @@ +car/car +main +coverage.txt diff --git a/ipld/car/car.go b/ipld/car/car.go index 30fdd3449f..267237f28c 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -77,7 +77,7 @@ func ReadHeader(br *bufio.Reader) (*CarHeader, error) { var ch CarHeader if err := cbor.DecodeInto(hb, &ch); err != nil { - return nil, err + return nil, fmt.Errorf("invalid header: %v", err) } return &ch, nil @@ -130,14 +130,14 @@ func NewCarReader(r io.Reader) (*CarReader, error) { return nil, err } - if len(ch.Roots) == 0 { - return nil, fmt.Errorf("empty car") - } - if ch.Version != 1 { return nil, fmt.Errorf("invalid car version: %d", ch.Version) } + if len(ch.Roots) == 0 { + return nil, fmt.Errorf("empty car, no roots") + } + return &CarReader{ br: br, Header: ch, diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index b50111b27a..e9ae105dc9 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/hex" "io" + "strings" "testing" cid "github.com/ipfs/go-cid" @@ -234,3 +235,80 @@ func TestEOFHandling(t *testing.T) { } }) } + +func TestBadHeaders(t *testing.T) { + makeCar := func(t *testing.T, byts string) error { + fixture, err := hex.DecodeString(byts) + if err != nil { + t.Fatal(err) + } + _, err = NewCarReader(bytes.NewReader(fixture)) + return err + } + + t.Run("Sanity check {version:1,roots:[baeaaaa3bmjrq]}", func(t *testing.T) { + err := makeCar(t, "1ca265726f6f747381d82a4800010000036162636776657273696f6e01") + if err != nil { + t.Fatal(err) + } + }) + + t.Run("{version:2}", func(t *testing.T) { + err := makeCar(t, "0aa16776657273696f6e02") + if err.Error() != "invalid car version: 2" { + t.Fatalf("bad error: %v", err) + } + }) + + // an unfortunate error because we don't use a pointer + t.Run("{roots:[baeaaaa3bmjrq]}", func(t *testing.T) { + err := makeCar(t, "13a165726f6f747381d82a480001000003616263") + if err.Error() != "invalid car version: 0" { + t.Fatalf("bad error: %v", err) + } + }) + + t.Run("{version:\"1\",roots:[baeaaaa3bmjrq]}", func(t *testing.T) { + err := makeCar(t, "1da265726f6f747381d82a4800010000036162636776657273696f6e6131") + if !strings.HasPrefix(err.Error(), "invalid header: ") { + t.Fatalf("bad error: %v", err) + } + }) + + t.Run("{version:1}", func(t *testing.T) { + err := makeCar(t, "0aa16776657273696f6e01") + if err.Error() != "empty car, no roots" { + t.Fatalf("bad error: %v", err) + } + }) + + t.Run("{version:1,roots:{cid:baeaaaa3bmjrq}}", func(t *testing.T) { + err := makeCar(t, "20a265726f6f7473a163636964d82a4800010000036162636776657273696f6e01") + if !strings.HasPrefix(err.Error(), "invalid header: ") { + t.Fatalf("bad error: %v", err) + } + }) + + t.Run("{version:1,roots:[baeaaaa3bmjrq],blip:true}", func(t *testing.T) { + err := makeCar(t, "22a364626c6970f565726f6f747381d82a4800010000036162636776657273696f6e01") + if !strings.HasPrefix(err.Error(), "invalid header: ") { + t.Fatalf("bad error: %v", err) + } + }) + + t.Run("[1,[]]", func(t *testing.T) { + err := makeCar(t, "03820180") + if !strings.HasPrefix(err.Error(), "invalid header: ") { + t.Fatalf("bad error: %v", err) + } + }) + + // this is an unfortunate error, it'd be nice to catch it better but it's + // very unlikely we'd ever see this in practice + t.Run("null", func(t *testing.T) { + err := makeCar(t, "01f6") + if !strings.HasPrefix(err.Error(), "invalid car version: 0") { + t.Fatalf("bad error: %v", err) + } + }) +} From 24de071cad2b9bbb7240bb246d6759ceb05111e8 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 3 Jun 2021 21:28:32 +1000 Subject: [PATCH 041/291] chore: refactor header tests to iterate over a struct This commit was moved from ipld/go-car@dde2a73215367e9d274512a1aa7b2d48a363d962 --- ipld/car/car_test.go | 124 +++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 58 deletions(-) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index e9ae105dc9..0c35156360 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -237,6 +237,58 @@ func TestEOFHandling(t *testing.T) { } func TestBadHeaders(t *testing.T) { + testCases := []struct { + name string + hex string + errStr string // either the whole error string + errPfx string // or just the prefix + }{ + { + "{version:2}", + "0aa16776657273696f6e02", + "invalid car version: 2", + "", + }, + { + // an unfortunate error because we don't use a pointer + "{roots:[baeaaaa3bmjrq]}", + "13a165726f6f747381d82a480001000003616263", + "invalid car version: 0", + "", + }, { + "{version:\"1\",roots:[baeaaaa3bmjrq]}", + "1da265726f6f747381d82a4800010000036162636776657273696f6e6131", + "", "invalid header: ", + }, { + "{version:1}", + "0aa16776657273696f6e01", + "empty car, no roots", + "", + }, { + "{version:1,roots:{cid:baeaaaa3bmjrq}}", + "20a265726f6f7473a163636964d82a4800010000036162636776657273696f6e01", + "", + "invalid header: ", + }, { + "{version:1,roots:[baeaaaa3bmjrq],blip:true}", + "22a364626c6970f565726f6f747381d82a4800010000036162636776657273696f6e01", + "", + "invalid header: ", + }, { + "[1,[]]", + "03820180", + "", + "invalid header: ", + }, { + // this is an unfortunate error, it'd be nice to catch it better but it's + // very unlikely we'd ever see this in practice + "null", + "01f6", + "", + "invalid car version: 0", + }, + } + makeCar := func(t *testing.T, byts string) error { fixture, err := hex.DecodeString(byts) if err != nil { @@ -253,62 +305,18 @@ func TestBadHeaders(t *testing.T) { } }) - t.Run("{version:2}", func(t *testing.T) { - err := makeCar(t, "0aa16776657273696f6e02") - if err.Error() != "invalid car version: 2" { - t.Fatalf("bad error: %v", err) - } - }) - - // an unfortunate error because we don't use a pointer - t.Run("{roots:[baeaaaa3bmjrq]}", func(t *testing.T) { - err := makeCar(t, "13a165726f6f747381d82a480001000003616263") - if err.Error() != "invalid car version: 0" { - t.Fatalf("bad error: %v", err) - } - }) - - t.Run("{version:\"1\",roots:[baeaaaa3bmjrq]}", func(t *testing.T) { - err := makeCar(t, "1da265726f6f747381d82a4800010000036162636776657273696f6e6131") - if !strings.HasPrefix(err.Error(), "invalid header: ") { - t.Fatalf("bad error: %v", err) - } - }) - - t.Run("{version:1}", func(t *testing.T) { - err := makeCar(t, "0aa16776657273696f6e01") - if err.Error() != "empty car, no roots" { - t.Fatalf("bad error: %v", err) - } - }) - - t.Run("{version:1,roots:{cid:baeaaaa3bmjrq}}", func(t *testing.T) { - err := makeCar(t, "20a265726f6f7473a163636964d82a4800010000036162636776657273696f6e01") - if !strings.HasPrefix(err.Error(), "invalid header: ") { - t.Fatalf("bad error: %v", err) - } - }) - - t.Run("{version:1,roots:[baeaaaa3bmjrq],blip:true}", func(t *testing.T) { - err := makeCar(t, "22a364626c6970f565726f6f747381d82a4800010000036162636776657273696f6e01") - if !strings.HasPrefix(err.Error(), "invalid header: ") { - t.Fatalf("bad error: %v", err) - } - }) - - t.Run("[1,[]]", func(t *testing.T) { - err := makeCar(t, "03820180") - if !strings.HasPrefix(err.Error(), "invalid header: ") { - t.Fatalf("bad error: %v", err) - } - }) - - // this is an unfortunate error, it'd be nice to catch it better but it's - // very unlikely we'd ever see this in practice - t.Run("null", func(t *testing.T) { - err := makeCar(t, "01f6") - if !strings.HasPrefix(err.Error(), "invalid car version: 0") { - t.Fatalf("bad error: %v", err) - } - }) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := makeCar(t, tc.hex) + if tc.errStr != "" { + if err.Error() != tc.errStr { + t.Fatalf("bad error: %v", err) + } + } else { + if !strings.HasPrefix(err.Error(), tc.errPfx) { + t.Fatalf("bad error: %v", err) + } + } + }) + } } From c3b78ad4698e92945dfa0fc4d54eb248ca461742 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 3 Jun 2021 21:43:30 +1000 Subject: [PATCH 042/291] chore: make sure we get an error where we expect one This commit was moved from ipld/go-car@dce539042aceacdd96e96ca6ea6eed8989b696b1 --- ipld/car/car_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 0c35156360..13f96887bf 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -308,6 +308,9 @@ func TestBadHeaders(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { err := makeCar(t, tc.hex) + if err == nil { + t.Fatal("expected error from bad header, didn't get one") + } if tc.errStr != "" { if err.Error() != tc.errStr { t.Fatalf("bad error: %v", err) From 9a1b1c060a4a778d169e05737d527415db47cffb Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 10 Jun 2021 11:12:05 +0100 Subject: [PATCH 043/291] Add `doc.go` to avoid CI issues for empty modules We might want to get this fixed at source; it is valid for a module to have no go files i think, and CI should tolerate that. This commit was moved from ipld/go-car@0c1de8338d512c16f7e505f71916e196c081b23d --- ipld/car/v2/doc.go | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 ipld/car/v2/doc.go diff --git a/ipld/car/v2/doc.go b/ipld/car/v2/doc.go new file mode 100644 index 0000000000..5b210211bf --- /dev/null +++ b/ipld/car/v2/doc.go @@ -0,0 +1,3 @@ +// Package car represents the CAR v2 implementation. +// TODO add CAR v2 byte structure here. +package car From f390a51295aec6089a4f289ba7c1707a7107c599 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 9 Jun 2021 13:08:17 +0100 Subject: [PATCH 044/291] Define basic CAR v2 constants along with tests Define the magic CAR v2 prefix proposed in ipld/specs#248 and assert that it remains a valid CAR v1 header, along with other tests that verify the expected length, content and version number. Define basic structs to represent CAR v2 header, along with placeholder for the future "characteristics" bitfield, and the optional padding between CAR v1 dump and index added to the end of a CAR v2 archive. Relates to: - https://github.com/ipld/specs/pull/248#issuecomment-833141588 This commit was moved from ipld/go-car@b76ee51478058763a5c7503cfd1226f5680f501c --- ipld/car/v2/car.go | 46 ++++++++++++++++++++++++ ipld/car/v2/car_test.go | 78 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 ipld/car/v2/car.go create mode 100644 ipld/car/v2/car_test.go diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go new file mode 100644 index 0000000000..c71d502f18 --- /dev/null +++ b/ipld/car/v2/car.go @@ -0,0 +1,46 @@ +package car + +const prefixBytesSize = 16 +const headerBytesSize = 32 + +var ( + // The fixed prefix of a CAR v2, signalling the version number to previous versions for graceful fail over. + PrefixBytes = []byte{ + 0x0a, // unit(10) + 0xa1, // map(1) + 0x67, // string(7) + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // "version" + 0x02, // uint(2) + } + // The size of the CAR v2 prefix in 11 bytes, (i.e. 11). + PrefixBytesSize = uint64(len(PrefixBytes)) + // Reserved 128 bits space to capture future characteristics of CAR v2 such as order, duplication, etc. + EmptyCharacteristics = new(Characteristics) +) + +type ( + // Header represents the CAR v2 header/pragma. + Header struct { + // 128-bit characteristics of this CAR v2 file, such as order, deduplication, etc. Reserved for future use. + Characteristics Characteristics + // The size of CAR v1 encapsulated in this CAR v2 as bytes. + CarV1Size uint64 + // The offset from the beginning of the file at which the CAR v2 index begins. + IndexOffset uint64 + } + // Characteristics is a bitfield placeholder for capturing the characteristics of a CAR v2 such as order and determinism. + Characteristics struct { + Hi uint64 + Lo uint64 + } +) + +// Size gets the size of Header in number of bytes. +func (h *Header) Size() int { + return headerBytesSize +} + +// Size gets the size of Characteristics in number of bytes. +func (c *Characteristics) Size() int { + return prefixBytesSize +} diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go new file mode 100644 index 0000000000..21a726ff6c --- /dev/null +++ b/ipld/car/v2/car_test.go @@ -0,0 +1,78 @@ +package car_test + +import ( + cbor "github.com/ipfs/go-ipld-cbor" + car_v1 "github.com/ipld/go-car" + car_v2 "github.com/ipld/go-car/v2" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCarV2PrefixLength(t *testing.T) { + tests := []struct { + name string + want interface{} + got interface{} + }{ + { + "cached size should be 11 bytes", + 11, + car_v2.PrefixBytesSize, + }, + { + "actual size should be 11 bytes", + 11, + len(car_v2.PrefixBytes), + }, + { + "should start with varint(10)", + car_v2.PrefixBytes[0], + 10, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert.EqualValues(t, tt.want, tt.got, "CarV2Prefix got = %v, want %v", tt.got, tt.want) + }) + } +} + +func TestCarV2PrefixIsValidCarV1Header(t *testing.T) { + var v1h car_v1.CarHeader + err := cbor.DecodeInto(car_v2.PrefixBytes[1:], &v1h) + assert.NoError(t, err, "cannot decode prefix as CBOR with CAR v1 header structure") + assert.Equal(t, car_v1.CarHeader{ + Roots: nil, + Version: 2, + }, v1h, "CAR v2 prefix must be a valid CAR v1 header") +} + +func TestEmptyCharacteristics(t *testing.T) { + tests := []struct { + name string + want interface{} + got interface{} + }{ + { + "is of size 16 bytes", + 16, + car_v2.EmptyCharacteristics.Size(), + }, + { + "is a whole lot of nothin'", + &car_v2.Characteristics{}, + car_v2.EmptyCharacteristics, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert.EqualValues(t, tt.want, tt.got, "EmptyCharacteristics got = %v, want %v", tt.got, tt.want) + }) + } +} + +func TestHeader_SizeIs32Bytes(t *testing.T) { + assert.Equal(t, 32, new(car_v2.Header).Size()) +} From 588083c1860e6a9e233c8de14d02bc17f9baa4a4 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 10 Jun 2021 17:12:06 +0100 Subject: [PATCH 045/291] Export constant header size Reflecting on PR review comments, no harm in exposing this and it may be useful to the users of the library. This commit was moved from ipld/go-car@29e325ac9c5fe71798611f34f3bea08bcfc11766 --- ipld/car/v2/car.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index c71d502f18..68a78ffa1b 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -1,7 +1,7 @@ package car -const prefixBytesSize = 16 -const headerBytesSize = 32 +// HeaderBytesSize is the fixed size of CAR v2 header in number of bytes. +const HeaderBytesSize = 32 var ( // The fixed prefix of a CAR v2, signalling the version number to previous versions for graceful fail over. @@ -13,7 +13,7 @@ var ( 0x02, // uint(2) } // The size of the CAR v2 prefix in 11 bytes, (i.e. 11). - PrefixBytesSize = uint64(len(PrefixBytes)) + PrefixBytesSize = len(PrefixBytes) // Reserved 128 bits space to capture future characteristics of CAR v2 such as order, duplication, etc. EmptyCharacteristics = new(Characteristics) ) @@ -37,10 +37,10 @@ type ( // Size gets the size of Header in number of bytes. func (h *Header) Size() int { - return headerBytesSize + return HeaderBytesSize } // Size gets the size of Characteristics in number of bytes. func (c *Characteristics) Size() int { - return prefixBytesSize + return PrefixBytesSize } From 733fc62cde55619c503d87fd852c8936636ef201 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 10 Jun 2021 17:22:47 +0100 Subject: [PATCH 046/291] Add `CarV1Offset` field to CAR v2 header Reflecting on the review comment, adding this field could provide optimisation opportunities in the future in the context of block alignment. See: - https://github.com/ipld/go-car/pull/80/files#r649241583 This commit was moved from ipld/go-car@17e0aa9ef03c77f0ab0bf019b92c5313fb809a5a --- ipld/car/v2/car.go | 12 +++++++++--- ipld/car/v2/car_test.go | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index 68a78ffa1b..c274d33e09 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -1,7 +1,11 @@ package car -// HeaderBytesSize is the fixed size of CAR v2 header in number of bytes. -const HeaderBytesSize = 32 +const ( + // HeaderBytesSize is the fixed size of CAR v2 header in number of bytes. + HeaderBytesSize = 40 + // CharacteristicsBytesSize is the fixed size of Characteristics bitfield within CAR v2 header in number of bytes. + CharacteristicsBytesSize = 16 +) var ( // The fixed prefix of a CAR v2, signalling the version number to previous versions for graceful fail over. @@ -23,6 +27,8 @@ type ( Header struct { // 128-bit characteristics of this CAR v2 file, such as order, deduplication, etc. Reserved for future use. Characteristics Characteristics + // The offset from the beginning of the file at which the dump of CAR v1 starts. + CarV1Offset uint64 // The size of CAR v1 encapsulated in this CAR v2 as bytes. CarV1Size uint64 // The offset from the beginning of the file at which the CAR v2 index begins. @@ -42,5 +48,5 @@ func (h *Header) Size() int { // Size gets the size of Characteristics in number of bytes. func (c *Characteristics) Size() int { - return PrefixBytesSize + return CharacteristicsBytesSize } diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index 21a726ff6c..aecec024d3 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -74,5 +74,5 @@ func TestEmptyCharacteristics(t *testing.T) { } func TestHeader_SizeIs32Bytes(t *testing.T) { - assert.Equal(t, 32, new(car_v2.Header).Size()) + assert.Equal(t, 40, new(car_v2.Header).Size()) } From 430bfd6572eb36c78adf599ab783432735369ddf Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 10 Jun 2021 17:54:37 +0100 Subject: [PATCH 047/291] Fix test case name to reflect what the test is Now that the `CarV1Offset` is added to the header, the size is increased from `32` to `40` bytes. Make it so in the test case name. This commit was moved from ipld/go-car@968459cc8d5824473ea939d3c3ece2ebe9edc510 --- ipld/car/v2/car_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index aecec024d3..ab53268504 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -73,6 +73,6 @@ func TestEmptyCharacteristics(t *testing.T) { } } -func TestHeader_SizeIs32Bytes(t *testing.T) { +func TestHeader_SizeIs40Bytes(t *testing.T) { assert.Equal(t, 40, new(car_v2.Header).Size()) } From 2d24be205ddb4154ec70781035ee6eb6cfce8d19 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 9 Jun 2021 13:54:08 +0100 Subject: [PATCH 048/291] Implement CAR v2 header construction and marshalling Define a constructor with sensible defaults, and the ability to customize the defaults conveniently. Implement `io.WriterTo` interface for both header and characteristics to provide a consistent standard API for writing data into a given `io.Writer`. This commit was moved from ipld/go-car@e571973176cfe57e86a6c5f92e84477c62376757 --- ipld/car/v2/car.go | 105 +++++++++++++++++++++++++++++++--- ipld/car/v2/car_test.go | 123 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 218 insertions(+), 10 deletions(-) diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index c274d33e09..428cb591ba 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -1,10 +1,15 @@ package car +import ( + "encoding/binary" + "io" +) + const ( // HeaderBytesSize is the fixed size of CAR v2 header in number of bytes. - HeaderBytesSize = 40 + HeaderBytesSize uint64 = 40 // CharacteristicsBytesSize is the fixed size of Characteristics bitfield within CAR v2 header in number of bytes. - CharacteristicsBytesSize = 16 + CharacteristicsBytesSize uint64 = 16 ) var ( @@ -17,7 +22,7 @@ var ( 0x02, // uint(2) } // The size of the CAR v2 prefix in 11 bytes, (i.e. 11). - PrefixBytesSize = len(PrefixBytes) + PrefixBytesSize = uint64(len(PrefixBytes)) // Reserved 128 bits space to capture future characteristics of CAR v2 such as order, duplication, etc. EmptyCharacteristics = new(Characteristics) ) @@ -25,8 +30,9 @@ var ( type ( // Header represents the CAR v2 header/pragma. Header struct { + io.WriterTo // 128-bit characteristics of this CAR v2 file, such as order, deduplication, etc. Reserved for future use. - Characteristics Characteristics + Characteristics *Characteristics // The offset from the beginning of the file at which the dump of CAR v1 starts. CarV1Offset uint64 // The size of CAR v1 encapsulated in this CAR v2 as bytes. @@ -36,17 +42,100 @@ type ( } // Characteristics is a bitfield placeholder for capturing the characteristics of a CAR v2 such as order and determinism. Characteristics struct { + io.WriterTo Hi uint64 Lo uint64 } ) -// Size gets the size of Header in number of bytes. -func (h *Header) Size() int { - return HeaderBytesSize +// WriteTo writes this characteristics to the given writer. +func (c *Characteristics) WriteTo(w io.Writer) (n int64, err error) { + wn, err := writeUint64To(w, c.Hi) + if err != nil { + return + } + n += wn + wn, err = writeUint64To(w, c.Lo) + if err != nil { + return + } + n += wn + return } // Size gets the size of Characteristics in number of bytes. -func (c *Characteristics) Size() int { +func (c *Characteristics) Size() uint64 { return CharacteristicsBytesSize } + +// NewHeader instantiates a new CAR v2 header, given the byte length of a CAR v1. +func NewHeader(carV1Size uint64) *Header { + header := &Header{ + Characteristics: EmptyCharacteristics, + CarV1Size: carV1Size, + } + header.CarV1Offset = PrefixBytesSize + HeaderBytesSize + header.IndexOffset = header.CarV1Offset + carV1Size + return header +} + +// Size gets the size of Header in number of bytes. +func (h *Header) Size() uint64 { + return HeaderBytesSize +} + +// WithIndexPadding sets the index offset from the beginning of the file for this header and returns the +// header for convenient chained calls. +// The index offset is calculated as the sum of PrefixBytesLen, HeaderBytesLen, +// Header#CarV1Len, and the given padding. +func (h *Header) WithIndexPadding(padding uint64) *Header { + h.IndexOffset = h.IndexOffset + padding + return h +} + +// WithCarV1Padding sets the CAR v1 dump offset from the beginning of the file for this header and returns the +// header for convenient chained calls. +// The CAR v1 offset is calculated as the sum of PrefixBytesLen, HeaderBytesLen and the given padding. +// The call to this function also shifts the Header#IndexOffset forward by the given padding. +func (h *Header) WithCarV1Padding(padding uint64) *Header { + h.CarV1Offset = h.CarV1Offset + padding + h.IndexOffset = h.IndexOffset + padding + return h +} + +// WriteTo serializes this header as bytes and writes them using the given io.Writer. +func (h *Header) WriteTo(w io.Writer) (n int64, err error) { + chars := h.Characteristics + if chars == nil { + chars = EmptyCharacteristics + } + wn, err := chars.WriteTo(w) + if err != nil { + return + } + n += wn + wn, err = writeUint64To(w, h.CarV1Offset) + if err != nil { + return + } + n += wn + wn, err = writeUint64To(w, h.CarV1Size) + if err != nil { + return + } + n += wn + wn, err = writeUint64To(w, h.IndexOffset) + if err != nil { + return + } + n += wn + return +} + +func writeUint64To(w io.Writer, v uint64) (n int64, err error) { + err = binary.Write(w, binary.LittleEndian, v) + if err == nil { + n = 8 + } + return +} diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index ab53268504..b40d1fc896 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -1,6 +1,7 @@ package car_test import ( + "bytes" cbor "github.com/ipfs/go-ipld-cbor" car_v1 "github.com/ipld/go-car" car_v2 "github.com/ipld/go-car/v2" @@ -56,7 +57,7 @@ func TestEmptyCharacteristics(t *testing.T) { }{ { "is of size 16 bytes", - 16, + car_v2.CharacteristicsBytesSize, car_v2.EmptyCharacteristics.Size(), }, { @@ -74,5 +75,123 @@ func TestEmptyCharacteristics(t *testing.T) { } func TestHeader_SizeIs40Bytes(t *testing.T) { - assert.Equal(t, 40, new(car_v2.Header).Size()) + assert.Equal(t, uint64(40), new(car_v2.Header).Size()) +} + +func TestHeader_Marshal(t *testing.T) { + tests := []struct { + name string + target car_v2.Header + wantMarshal []byte + wantErr bool + }{ + { + "header with nil characteristics is marshalled as empty characteristics", + car_v2.Header{ + Characteristics: nil, + }, + []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }, + false, + }, + { + "header with empty characteristics is marshalled as expected", + car_v2.Header{ + Characteristics: car_v2.EmptyCharacteristics, + }, + []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }, + false, + }, + { + "non-empty header is marshalled as expected", + car_v2.Header{ + Characteristics: &car_v2.Characteristics{ + Hi: 1001, Lo: 1002, + }, + CarV1Offset: 99, + CarV1Size: 100, + IndexOffset: 101, + }, + []byte{ + 0xe9, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xea, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x63, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x65, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf := &bytes.Buffer{} + written, err := tt.target.WriteTo(buf) + if (err != nil) != tt.wantErr { + t.Errorf("Marshal() error = %v, wantErr %v", err, tt.wantErr) + return + } + gotMarshal := buf.Bytes() + assert.Equal(t, tt.wantMarshal, gotMarshal, "Header.WriteTo() gotMarshal = %v, wantMarshal %v", gotMarshal, tt.wantMarshal) + assert.EqualValues(t, car_v2.HeaderBytesSize, uint64(len(gotMarshal)), "WriteTo() CAR v2 header length must always be %v bytes long", car_v2.HeaderBytesSize) + assert.EqualValues(t, car_v2.HeaderBytesSize, uint64(written), "WriteTo() CAR v2 header byte count must always be %v bytes long", car_v2.HeaderBytesSize) + }) + } +} + +func TestHeader_WithPadding(t *testing.T) { + tests := []struct { + name string + subject *car_v2.Header + wantCarV1Offset uint64 + wantIndexOffset uint64 + }{ + { + "when no padding, offsets are sum of sizes", + car_v2.NewHeader(123), + car_v2.PrefixBytesSize + car_v2.HeaderBytesSize, + car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + 123, + }, + { + "when only padding car v1, both offsets shift", + car_v2.NewHeader(123).WithCarV1Padding(3), + car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + 3, + car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + 3 + 123, + }, + { + "when padding both car v1 and index, both offsets shift with additional index shift", + car_v2.NewHeader(123).WithCarV1Padding(3).WithIndexPadding(7), + car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + 3, + car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + 3 + 123 + 7, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.EqualValues(t, tt.wantCarV1Offset, tt.subject.CarV1Offset) + assert.EqualValues(t, tt.wantIndexOffset, tt.subject.IndexOffset) + }) + } +} + +func TestNewHeaderHasExpectedValues(t *testing.T) { + wantCarV1Len := uint64(1413) + want := &car_v2.Header{ + Characteristics: car_v2.EmptyCharacteristics, + CarV1Offset: car_v2.PrefixBytesSize + car_v2.HeaderBytesSize, + CarV1Size: wantCarV1Len, + IndexOffset: car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + wantCarV1Len, + } + got := car_v2.NewHeader(wantCarV1Len) + assert.Equal(t, want, got, "NewHeader got = %v, want = %v", got, want) } From af87a902703011734e1928581f93951a7357fece Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 10 Jun 2021 16:59:26 +0100 Subject: [PATCH 049/291] Implement CAR v2 writer that uses `format.NodeGetter` Implement a CAR v2 writer, that produces binary structure corresponding to the specification of car V2, consisting of: 1. Version prefix 2. CAR v2 header 3. Dump of Car v1 4. Carbs index The implementation also facilities optional padding before dump of CAR v1 and Carbs index to provide scope for future optimisations. The padding is defined as a dedicated type that writes zero-valued bytes for a given padding size. The CAR v2 header, then captures the offsets from the beginning of the file for both the CAR v1 dump and Carbs index. This allows the user to quickly skip to the part of the CAR v2 they need as well as offset alignment, if necessary, by altering the value of padding. Note, this is an intermediate implementation, and does not correctly count the number of bytes written by the writer, pending the transfer/refactor of Carbs implementation in this repo. In the meantime, This implementation depends on Carbs as a go module. The implementation of extensive writer tests is postponed until Carbs is transferred and the written byte-count can be returned from the index marshaller. Address review comments - Avoid representing `Characteristics` as a pointer for easier casting of memory regions. - Remove anonymous fields in structs as a human readable way to document what interfaces a struct implements. - Simplify Header writing and written byte count calculation. The change reduces the number of lines by assuming that `writeUint64To` will always write 8 bytes. - Avoid `_` in package names. Use `carv1` and `carv2` to name corresponding packages. - Rename `Marshal` in tests to reflect `WriteTo` related tests. The method was renamed in the implementation but tests were not renamed. - Write padding in bulk to reduce redundant memory allocation. Write padding in bulks of `1024` bytes to reduce large memory allocation when padding itself is large. - Avoid using # in docs. Use go syntax instead, i.e. `.`. - Remove redundant type in constants, since it is implicitly converted. - Simplify over-refactored prefix write Since it is called only once. - Avoid `Header` pointer, since its size is small and this would reduce unnecessary allocations. - Use buffer in writer to store encoded Car V1. This is to avoid reallocation of bytes buffer. - Add TODO re optimisation of index generation. The current implementation reads the entire CAR v1 into memory in order to index it, because `carbs` API requires `io.Reader`. Once `a`carbs` is incorporated into this repo consider refactoring the API to make this a streaming operation that avoids copying all the bytes since CAR v1 can be large. - Remove a dedicated var for empty characteristics, since, we assume the default to be all zero, and it is easy enough to construct a zero valued Characteristics. - Unexport PrefixBytesSize, because we can, and if it is public it might have to stay public forever. So, unexport until we know we need it exported. - Remove `.Size()` on CAR v2 structs. Because we have the constant for them and don't want to expose them twice. - Use CamelCase for sub-test naming. This is to establish CamelCase for sub-test naming as the convention for this repo since it keeps the test names consistent in code and in the output ot `go test`. - Unexport padding as a type since it is only used internally This commit was moved from ipld/go-car@875a6548b3047e46f493f6edd29b2f37d4fb5b4e --- ipld/car/v2/car.go | 83 ++++++++-------------- ipld/car/v2/car_test.go | 136 ++++++++++++------------------------ ipld/car/v2/writer.go | 138 +++++++++++++++++++++++++++++++++++++ ipld/car/v2/writer_test.go | 96 ++++++++++++++++++++++++++ 4 files changed, 307 insertions(+), 146 deletions(-) create mode 100644 ipld/car/v2/writer.go create mode 100644 ipld/car/v2/writer_test.go diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index 428cb591ba..e3f7c7b79a 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -7,9 +7,10 @@ import ( const ( // HeaderBytesSize is the fixed size of CAR v2 header in number of bytes. - HeaderBytesSize uint64 = 40 + HeaderBytesSize = 40 // CharacteristicsBytesSize is the fixed size of Characteristics bitfield within CAR v2 header in number of bytes. - CharacteristicsBytesSize uint64 = 16 + CharacteristicsBytesSize = 16 + uint64BytesSize = 8 ) var ( @@ -22,17 +23,14 @@ var ( 0x02, // uint(2) } // The size of the CAR v2 prefix in 11 bytes, (i.e. 11). - PrefixBytesSize = uint64(len(PrefixBytes)) - // Reserved 128 bits space to capture future characteristics of CAR v2 such as order, duplication, etc. - EmptyCharacteristics = new(Characteristics) + prefixBytesSize = uint64(len(PrefixBytes)) ) type ( // Header represents the CAR v2 header/pragma. Header struct { - io.WriterTo // 128-bit characteristics of this CAR v2 file, such as order, deduplication, etc. Reserved for future use. - Characteristics *Characteristics + Characteristics Characteristics // The offset from the beginning of the file at which the dump of CAR v1 starts. CarV1Offset uint64 // The size of CAR v1 encapsulated in this CAR v2 as bytes. @@ -42,53 +40,39 @@ type ( } // Characteristics is a bitfield placeholder for capturing the characteristics of a CAR v2 such as order and determinism. Characteristics struct { - io.WriterTo Hi uint64 Lo uint64 } ) // WriteTo writes this characteristics to the given writer. -func (c *Characteristics) WriteTo(w io.Writer) (n int64, err error) { - wn, err := writeUint64To(w, c.Hi) - if err != nil { +func (c Characteristics) WriteTo(w io.Writer) (n int64, err error) { + if err = writeUint64To(w, c.Hi); err != nil { return } - n += wn - wn, err = writeUint64To(w, c.Lo) - if err != nil { + n += uint64BytesSize + if err = writeUint64To(w, c.Lo); err != nil { return } - n += wn + n += uint64BytesSize return } -// Size gets the size of Characteristics in number of bytes. -func (c *Characteristics) Size() uint64 { - return CharacteristicsBytesSize -} - // NewHeader instantiates a new CAR v2 header, given the byte length of a CAR v1. -func NewHeader(carV1Size uint64) *Header { - header := &Header{ - Characteristics: EmptyCharacteristics, - CarV1Size: carV1Size, +func NewHeader(carV1Size uint64) Header { + header := Header{ + CarV1Size: carV1Size, } - header.CarV1Offset = PrefixBytesSize + HeaderBytesSize + header.CarV1Offset = prefixBytesSize + HeaderBytesSize header.IndexOffset = header.CarV1Offset + carV1Size return header } -// Size gets the size of Header in number of bytes. -func (h *Header) Size() uint64 { - return HeaderBytesSize -} - // WithIndexPadding sets the index offset from the beginning of the file for this header and returns the // header for convenient chained calls. // The index offset is calculated as the sum of PrefixBytesLen, HeaderBytesLen, -// Header#CarV1Len, and the given padding. -func (h *Header) WithIndexPadding(padding uint64) *Header { +// Header.CarV1Len, and the given padding. +func (h Header) WithIndexPadding(padding uint64) Header { h.IndexOffset = h.IndexOffset + padding return h } @@ -96,46 +80,35 @@ func (h *Header) WithIndexPadding(padding uint64) *Header { // WithCarV1Padding sets the CAR v1 dump offset from the beginning of the file for this header and returns the // header for convenient chained calls. // The CAR v1 offset is calculated as the sum of PrefixBytesLen, HeaderBytesLen and the given padding. -// The call to this function also shifts the Header#IndexOffset forward by the given padding. -func (h *Header) WithCarV1Padding(padding uint64) *Header { +// The call to this function also shifts the Header.IndexOffset forward by the given padding. +func (h Header) WithCarV1Padding(padding uint64) Header { h.CarV1Offset = h.CarV1Offset + padding h.IndexOffset = h.IndexOffset + padding return h } // WriteTo serializes this header as bytes and writes them using the given io.Writer. -func (h *Header) WriteTo(w io.Writer) (n int64, err error) { - chars := h.Characteristics - if chars == nil { - chars = EmptyCharacteristics - } - wn, err := chars.WriteTo(w) +func (h Header) WriteTo(w io.Writer) (n int64, err error) { + wn, err := h.Characteristics.WriteTo(w) if err != nil { return } n += wn - wn, err = writeUint64To(w, h.CarV1Offset) - if err != nil { + if err = writeUint64To(w, h.CarV1Offset); err != nil { return } - n += wn - wn, err = writeUint64To(w, h.CarV1Size) - if err != nil { + n += uint64BytesSize + if err = writeUint64To(w, h.CarV1Size); err != nil { return } - n += wn - wn, err = writeUint64To(w, h.IndexOffset) - if err != nil { + n += uint64BytesSize + if err = writeUint64To(w, h.IndexOffset); err != nil { return } - n += wn + n += uint64BytesSize return } -func writeUint64To(w io.Writer, v uint64) (n int64, err error) { - err = binary.Write(w, binary.LittleEndian, v) - if err == nil { - n = 8 - } - return +func writeUint64To(w io.Writer, v uint64) error { + return binary.Write(w, binary.LittleEndian, v) } diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index b40d1fc896..6e1c877718 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -3,12 +3,14 @@ package car_test import ( "bytes" cbor "github.com/ipfs/go-ipld-cbor" - car_v1 "github.com/ipld/go-car" - car_v2 "github.com/ipld/go-car/v2" + carv1 "github.com/ipld/go-car" + carv2 "github.com/ipld/go-car/v2" "github.com/stretchr/testify/assert" "testing" ) +var prefixBytesSize = uint64(11) + func TestCarV2PrefixLength(t *testing.T) { tests := []struct { name string @@ -16,18 +18,13 @@ func TestCarV2PrefixLength(t *testing.T) { got interface{} }{ { - "cached size should be 11 bytes", + "ActualSizeShouldBe11", 11, - car_v2.PrefixBytesSize, + len(carv2.PrefixBytes), }, { - "actual size should be 11 bytes", - 11, - len(car_v2.PrefixBytes), - }, - { - "should start with varint(10)", - car_v2.PrefixBytes[0], + "ShouldStartWithVarint(10)", + carv2.PrefixBytes[0], 10, }, } @@ -40,69 +37,26 @@ func TestCarV2PrefixLength(t *testing.T) { } func TestCarV2PrefixIsValidCarV1Header(t *testing.T) { - var v1h car_v1.CarHeader - err := cbor.DecodeInto(car_v2.PrefixBytes[1:], &v1h) + var v1h carv1.CarHeader + err := cbor.DecodeInto(carv2.PrefixBytes[1:], &v1h) assert.NoError(t, err, "cannot decode prefix as CBOR with CAR v1 header structure") - assert.Equal(t, car_v1.CarHeader{ + assert.Equal(t, carv1.CarHeader{ Roots: nil, Version: 2, }, v1h, "CAR v2 prefix must be a valid CAR v1 header") } -func TestEmptyCharacteristics(t *testing.T) { - tests := []struct { - name string - want interface{} - got interface{} - }{ - { - "is of size 16 bytes", - car_v2.CharacteristicsBytesSize, - car_v2.EmptyCharacteristics.Size(), - }, - { - "is a whole lot of nothin'", - &car_v2.Characteristics{}, - car_v2.EmptyCharacteristics, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - assert.EqualValues(t, tt.want, tt.got, "EmptyCharacteristics got = %v, want %v", tt.got, tt.want) - }) - } -} - -func TestHeader_SizeIs40Bytes(t *testing.T) { - assert.Equal(t, uint64(40), new(car_v2.Header).Size()) -} - -func TestHeader_Marshal(t *testing.T) { +func TestHeader_WriteTo(t *testing.T) { tests := []struct { - name string - target car_v2.Header - wantMarshal []byte - wantErr bool + name string + target carv2.Header + wantWrite []byte + wantErr bool }{ { - "header with nil characteristics is marshalled as empty characteristics", - car_v2.Header{ - Characteristics: nil, - }, - []byte{ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - }, - false, - }, - { - "header with empty characteristics is marshalled as expected", - car_v2.Header{ - Characteristics: car_v2.EmptyCharacteristics, + "HeaderWithEmptyCharacteristicsIsWrittenAsExpected", + carv2.Header{ + Characteristics: carv2.Characteristics{}, }, []byte{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, @@ -114,9 +68,9 @@ func TestHeader_Marshal(t *testing.T) { false, }, { - "non-empty header is marshalled as expected", - car_v2.Header{ - Characteristics: &car_v2.Characteristics{ + "NonEmptyHeaderIsWrittenAsExpected", + carv2.Header{ + Characteristics: carv2.Characteristics{ Hi: 1001, Lo: 1002, }, CarV1Offset: 99, @@ -138,13 +92,13 @@ func TestHeader_Marshal(t *testing.T) { buf := &bytes.Buffer{} written, err := tt.target.WriteTo(buf) if (err != nil) != tt.wantErr { - t.Errorf("Marshal() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("WriteTo() error = %v, wantErr %v", err, tt.wantErr) return } - gotMarshal := buf.Bytes() - assert.Equal(t, tt.wantMarshal, gotMarshal, "Header.WriteTo() gotMarshal = %v, wantMarshal %v", gotMarshal, tt.wantMarshal) - assert.EqualValues(t, car_v2.HeaderBytesSize, uint64(len(gotMarshal)), "WriteTo() CAR v2 header length must always be %v bytes long", car_v2.HeaderBytesSize) - assert.EqualValues(t, car_v2.HeaderBytesSize, uint64(written), "WriteTo() CAR v2 header byte count must always be %v bytes long", car_v2.HeaderBytesSize) + gotWrite := buf.Bytes() + assert.Equal(t, tt.wantWrite, gotWrite, "Header.WriteTo() gotWrite = %v, wantWrite %v", gotWrite, tt.wantWrite) + assert.EqualValues(t, carv2.HeaderBytesSize, uint64(len(gotWrite)), "WriteTo() CAR v2 header length must always be %v bytes long", carv2.HeaderBytesSize) + assert.EqualValues(t, carv2.HeaderBytesSize, uint64(written), "WriteTo() CAR v2 header byte count must always be %v bytes long", carv2.HeaderBytesSize) }) } } @@ -152,27 +106,27 @@ func TestHeader_Marshal(t *testing.T) { func TestHeader_WithPadding(t *testing.T) { tests := []struct { name string - subject *car_v2.Header + subject carv2.Header wantCarV1Offset uint64 wantIndexOffset uint64 }{ { - "when no padding, offsets are sum of sizes", - car_v2.NewHeader(123), - car_v2.PrefixBytesSize + car_v2.HeaderBytesSize, - car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + 123, + "WhenNoPaddingOffsetsAreSumOfSizes", + carv2.NewHeader(123), + prefixBytesSize + carv2.HeaderBytesSize, + prefixBytesSize + carv2.HeaderBytesSize + 123, }, { - "when only padding car v1, both offsets shift", - car_v2.NewHeader(123).WithCarV1Padding(3), - car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + 3, - car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + 3 + 123, + "WhenOnlyPaddingCarV1BothOffsetsShift", + carv2.NewHeader(123).WithCarV1Padding(3), + prefixBytesSize + carv2.HeaderBytesSize + 3, + prefixBytesSize + carv2.HeaderBytesSize + 3 + 123, }, { - "when padding both car v1 and index, both offsets shift with additional index shift", - car_v2.NewHeader(123).WithCarV1Padding(3).WithIndexPadding(7), - car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + 3, - car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + 3 + 123 + 7, + "WhenPaddingBothCarV1AndIndexBothOffsetsShiftWithAdditionalIndexShift", + carv2.NewHeader(123).WithCarV1Padding(3).WithIndexPadding(7), + prefixBytesSize + carv2.HeaderBytesSize + 3, + prefixBytesSize + carv2.HeaderBytesSize + 3 + 123 + 7, }, } @@ -186,12 +140,12 @@ func TestHeader_WithPadding(t *testing.T) { func TestNewHeaderHasExpectedValues(t *testing.T) { wantCarV1Len := uint64(1413) - want := &car_v2.Header{ - Characteristics: car_v2.EmptyCharacteristics, - CarV1Offset: car_v2.PrefixBytesSize + car_v2.HeaderBytesSize, + want := carv2.Header{ + Characteristics: carv2.Characteristics{}, + CarV1Offset: prefixBytesSize + carv2.HeaderBytesSize, CarV1Size: wantCarV1Len, - IndexOffset: car_v2.PrefixBytesSize + car_v2.HeaderBytesSize + wantCarV1Len, + IndexOffset: prefixBytesSize + carv2.HeaderBytesSize + wantCarV1Len, } - got := car_v2.NewHeader(wantCarV1Len) + got := carv2.NewHeader(wantCarV1Len) assert.Equal(t, want, got, "NewHeader got = %v, want = %v", got, want) } diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go new file mode 100644 index 0000000000..3e90817bc2 --- /dev/null +++ b/ipld/car/v2/writer.go @@ -0,0 +1,138 @@ +package car + +import ( + "bytes" + "context" + "github.com/ipfs/go-cid" + format "github.com/ipfs/go-ipld-format" + carv1 "github.com/ipld/go-car" + "github.com/willscott/carbs" + "io" +) + +const bulkPaddingBytesSize = 1024 + +var bulkPadding = make([]byte, bulkPaddingBytesSize) + +type ( + // padding represents the number of padding bytes. + padding uint64 + // Writer writes CAR v2 into a give io.Writer. + Writer struct { + Walk carv1.WalkFunc + IndexCodec carbs.IndexCodec + NodeGetter format.NodeGetter + CarV1Padding uint64 + IndexPadding uint64 + + ctx context.Context + roots []cid.Cid + encodedCarV1 *bytes.Buffer + } +) + +// WriteTo writes this padding to the given writer as default value bytes. +func (p padding) WriteTo(w io.Writer) (n int64, err error) { + var reminder int64 + if p > bulkPaddingBytesSize { + reminder = int64(p % bulkPaddingBytesSize) + iter := int(p / bulkPaddingBytesSize) + for i := 0; i < iter; i++ { + if _, err = w.Write(bulkPadding); err != nil { + return + } + n += bulkPaddingBytesSize + } + } else { + reminder = int64(p) + } + + paddingBytes := make([]byte, reminder) + _, err = w.Write(paddingBytes) + n += reminder + return +} + +// NewWriter instantiates a new CAR v2 writer. +// The writer instantiated uses `carbs.IndexSorted` as the index codec, +// and `carv1.DefaultWalkFunc` as the default walk function. +func NewWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *Writer { + return &Writer{ + Walk: carv1.DefaultWalkFunc, + IndexCodec: carbs.IndexSorted, + NodeGetter: ng, + ctx: ctx, + roots: roots, + encodedCarV1: new(bytes.Buffer), + } +} + +// WriteTo writes the given root CIDs according to CAR v2 specification, traversing the DAG using the +// Writer.Walk function. +func (w *Writer) WriteTo(writer io.Writer) (n int64, err error) { + _, err = writer.Write(PrefixBytes) + if err != nil { + return + } + n += int64(prefixBytesSize) + // We read the entire car into memory because carbs.GenerateIndex takes a reader. + // Future PRs will make this more efficient by exposing necessary interfaces in carbs so that + // this can be done in an streaming manner. + if err = carv1.WriteCarWithWalker(w.ctx, w.NodeGetter, w.roots, w.encodedCarV1, w.Walk); err != nil { + return + } + carV1Len := w.encodedCarV1.Len() + + wn, err := w.writeHeader(writer, carV1Len) + if err != nil { + return + } + n += wn + + wn, err = padding(w.CarV1Padding).WriteTo(writer) + if err != nil { + return + } + n += wn + + carV1Bytes := w.encodedCarV1.Bytes() + wwn, err := writer.Write(carV1Bytes) + if err != nil { + return + } + n += int64(wwn) + + wn, err = padding(w.IndexPadding).WriteTo(writer) + if err != nil { + return + } + n += wn + + wn, err = w.writeIndex(writer, carV1Bytes) + if err == nil { + n += wn + } + return +} + +func (w *Writer) writeHeader(writer io.Writer, carV1Len int) (int64, error) { + header := NewHeader(uint64(carV1Len)). + WithCarV1Padding(w.CarV1Padding). + WithIndexPadding(w.IndexPadding) + return header.WriteTo(writer) +} + +func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (n int64, err error) { + // TODO avoid recopying the bytes by refacting carbs once it is integrated here. + // Right now we copy the bytes since carbs takes a writer. + // Consider refactoring carbs to make this process more efficient. + // We should avoid reading the entire car into memory since it can be large. + reader := bytes.NewReader(carV1) + index, err := carbs.GenerateIndex(reader, int64(len(carV1)), carbs.IndexSorted, true) + if err != nil { + return + } + err = index.Marshal(writer) + // FIXME refactor carbs to expose the number of bytes written. + return +} diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go new file mode 100644 index 0000000000..caf7e66680 --- /dev/null +++ b/ipld/car/v2/writer_test.go @@ -0,0 +1,96 @@ +package car + +import ( + "bytes" + "context" + "github.com/ipfs/go-cid" + format "github.com/ipfs/go-ipld-format" + dag "github.com/ipfs/go-merkledag" + dstest "github.com/ipfs/go-merkledag/test" + "github.com/stretchr/testify/assert" + "github.com/willscott/carbs" + "testing" +) + +func TestPadding_WriteTo(t *testing.T) { + tests := []struct { + name string + padding padding + wantBytes []byte + wantN int64 + wantErr bool + }{ + { + "ZeroPaddingIsNoBytes", + padding(0), + nil, + 0, + false, + }, + { + "NonZeroPaddingIsCorrespondingZeroValueBytes", + padding(3), + []byte{0x00, 0x00, 0x00}, + 3, + false, + }, + { + "PaddingLargerThanTheBulkPaddingSizeIsCorrespondingZeroValueBytes", + padding(1025), + make([]byte, 1025), + 1025, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &bytes.Buffer{} + gotN, gotErr := tt.padding.WriteTo(w) + if tt.wantErr { + assert.Error(t, gotErr) + return + } + gotBytes := w.Bytes() + assert.Equal(t, tt.wantN, gotN) + assert.Equal(t, tt.wantBytes, gotBytes) + }) + } +} + +func TestNewWriter(t *testing.T) { + dagService := dstest.Mock() + wantRoots := generateRootCid(t, dagService) + writer := NewWriter(context.Background(), dagService, wantRoots) + assert.Equal(t, carbs.IndexSorted, writer.IndexCodec) + assert.Equal(t, wantRoots, writer.roots) +} + +func generateRootCid(t *testing.T, adder format.NodeAdder) []cid.Cid { + // TODO convert this into a utility testing lib that takes an rng and generates a random DAG with some threshold for depth/breadth. + this := dag.NewRawNode([]byte("fish")) + that := dag.NewRawNode([]byte("lobster")) + other := dag.NewRawNode([]byte("🌊")) + + one := &dag.ProtoNode{} + assertAddNodeLink(t, one, this, "fishmonger") + + another := &dag.ProtoNode{} + assertAddNodeLink(t, another, one, "barreleye") + assertAddNodeLink(t, another, that, "🐡") + + andAnother := &dag.ProtoNode{} + assertAddNodeLink(t, andAnother, another, "🍤") + + assertAddNodes(t, adder, this, that, other, one, another, andAnother) + return []cid.Cid{andAnother.Cid()} +} + +func assertAddNodeLink(t *testing.T, pn *dag.ProtoNode, fn format.Node, name string) { + assert.NoError(t, pn.AddNodeLink(name, fn)) +} + +func assertAddNodes(t *testing.T, adder format.NodeAdder, nds ...format.Node) { + for _, nd := range nds { + assert.NoError(t, adder.Add(context.Background(), nd)) + } +} From 8b06ad1fab876d64460d10ea6117d100ac3f5ef3 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 15 Jun 2021 09:55:31 +0100 Subject: [PATCH 050/291] Copy `carbs` indexing mechanism int `go-car` repo The implementation comes from here: - https://github.com/willscott/carbs The rationale for copying the code here is to be able to taylor the indexing API to the needs of CAR v2 in one place. This commit was moved from ipld/go-car@d9c8b766b77fa8cfde55c99c738dcdc02cda0b93 --- ipld/car/v2/carbs/carbs.go | 301 ++++++++++++++++++++++++++++++ ipld/car/v2/carbs/carbs_test.go | 108 +++++++++++ ipld/car/v2/carbs/index.go | 89 +++++++++ ipld/car/v2/carbs/indexgobhash.go | 44 +++++ ipld/car/v2/carbs/indexhashed.go | 43 +++++ ipld/car/v2/carbs/indexsorted.go | 197 +++++++++++++++++++ ipld/car/v2/carbs/reader.go | 20 ++ ipld/car/v2/carbs/test.car | Bin 0 -> 479907 bytes ipld/car/v2/carbs/util/hydrate.go | 51 +++++ ipld/car/v2/writer.go | 4 +- ipld/car/v2/writer_test.go | 2 +- 11 files changed, 856 insertions(+), 3 deletions(-) create mode 100644 ipld/car/v2/carbs/carbs.go create mode 100644 ipld/car/v2/carbs/carbs_test.go create mode 100644 ipld/car/v2/carbs/index.go create mode 100644 ipld/car/v2/carbs/indexgobhash.go create mode 100644 ipld/car/v2/carbs/indexhashed.go create mode 100644 ipld/car/v2/carbs/indexsorted.go create mode 100644 ipld/car/v2/carbs/reader.go create mode 100644 ipld/car/v2/carbs/test.car create mode 100644 ipld/car/v2/carbs/util/hydrate.go diff --git a/ipld/car/v2/carbs/carbs.go b/ipld/car/v2/carbs/carbs.go new file mode 100644 index 0000000000..ca7256f815 --- /dev/null +++ b/ipld/car/v2/carbs/carbs.go @@ -0,0 +1,301 @@ +package carbs + +import ( + "bufio" + "bytes" + "context" + "encoding/binary" + "fmt" + "io" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + bs "github.com/ipfs/go-ipfs-blockstore" + "github.com/multiformats/go-multihash" + + pb "github.com/cheggaaa/pb/v3" + car "github.com/ipld/go-car" + "github.com/ipld/go-car/util" + "golang.org/x/exp/mmap" +) + +var errNotFound = bs.ErrNotFound + +// Carbs provides a read-only Car Block Store. +type Carbs struct { + backing io.ReaderAt + idx Index +} + +var _ bs.Blockstore = (*Carbs)(nil) + +func (c *Carbs) Read(idx int64) (cid.Cid, []byte, error) { + bcid, data, err := util.ReadNode(bufio.NewReader(&unatreader{c.backing, idx})) + return bcid, data, err +} + +// DeleteBlock doesn't delete a block on RO blockstore +func (c *Carbs) DeleteBlock(_ cid.Cid) error { + return fmt.Errorf("read only") +} + +// Has indicates if the store has a cid +func (c *Carbs) Has(key cid.Cid) (bool, error) { + offset, err := c.idx.Get(key) + if err != nil { + return false, err + } + uar := unatreader{c.backing, int64(offset)} + _, err = binary.ReadUvarint(&uar) + if err != nil { + return false, err + } + cid, _, err := readCid(c.backing, uar.at) + if err != nil { + return false, err + } + return cid.Equals(key), nil +} + +var cidv0Pref = []byte{0x12, 0x20} + +func readCid(store io.ReaderAt, at int64) (cid.Cid, int, error) { + var tag [2]byte + if _, err := store.ReadAt(tag[:], at); err != nil { + return cid.Undef, 0, err + } + if bytes.Equal(tag[:], cidv0Pref) { + cid0 := make([]byte, 34) + if _, err := store.ReadAt(cid0, at); err != nil { + return cid.Undef, 0, err + } + c, err := cid.Cast(cid0) + return c, 34, err + } + + // assume cidv1 + br := &unatreader{store, at} + vers, err := binary.ReadUvarint(br) + if err != nil { + return cid.Cid{}, 0, err + } + + // TODO: the go-cid package allows version 0 here as well + if vers != 1 { + return cid.Cid{}, 0, fmt.Errorf("invalid cid version number: %d", vers) + } + + codec, err := binary.ReadUvarint(br) + if err != nil { + return cid.Cid{}, 0, err + } + + mhr := multihash.NewReader(br) + h, err := mhr.ReadMultihash() + if err != nil { + return cid.Cid{}, 0, err + } + + return cid.NewCidV1(codec, h), int(br.at - at), nil +} + +// Get gets a block from the store +func (c *Carbs) Get(key cid.Cid) (blocks.Block, error) { + offset, err := c.idx.Get(key) + if err != nil { + return nil, err + } + entry, bytes, err := c.Read(int64(offset)) + if err != nil { + fmt.Printf("failed get %d:%v\n", offset, err) + return nil, bs.ErrNotFound + } + if !entry.Equals(key) { + return nil, bs.ErrNotFound + } + return blocks.NewBlockWithCid(bytes, key) +} + +// GetSize gets how big a item is +func (c *Carbs) GetSize(key cid.Cid) (int, error) { + idx, err := c.idx.Get(key) + if err != nil { + return -1, err + } + len, err := binary.ReadUvarint(&unatreader{c.backing, int64(idx)}) + if err != nil { + return -1, bs.ErrNotFound + } + cid, _, err := readCid(c.backing, int64(idx+len)) + if err != nil { + return 0, err + } + if !cid.Equals(key) { + return -1, bs.ErrNotFound + } + // get cid. validate. + return int(len), err +} + +// Put does nothing on a ro store +func (c *Carbs) Put(blocks.Block) error { + return fmt.Errorf("read only") +} + +// PutMany does nothing on a ro store +func (c *Carbs) PutMany([]blocks.Block) error { + return fmt.Errorf("read only") +} + +// AllKeysChan returns the list of keys in the store +func (c *Carbs) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) + if err != nil { + return nil, fmt.Errorf("Error reading car header: %w", err) + } + offset, err := car.HeaderSize(header) + if err != nil { + return nil, err + } + + ch := make(chan cid.Cid, 5) + go func() { + done := ctx.Done() + + rdr := unatreader{c.backing, int64(offset)} + for true { + l, err := binary.ReadUvarint(&rdr) + thisItemForNxt := rdr.at + if err != nil { + return + } + c, _, err := readCid(c.backing, thisItemForNxt) + if err != nil { + return + } + rdr.at = thisItemForNxt + int64(l) + + select { + case ch <- c: + continue + case <-done: + return + } + } + }() + return ch, nil +} + +// HashOnRead does nothing +func (c *Carbs) HashOnRead(enabled bool) { + return +} + +// Roots returns the root CIDs of the backing car +func (c *Carbs) Roots() ([]cid.Cid, error) { + header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) + if err != nil { + return nil, fmt.Errorf("Error reading car header: %w", err) + } + return header.Roots, nil +} + +// Load opens a carbs data store, generating an index if it does not exist +func Load(path string, noPersist bool) (*Carbs, error) { + reader, err := mmap.Open(path) + if err != nil { + return nil, err + } + idx, err := Restore(path) + if err != nil { + idx, err = GenerateIndex(reader, 0, IndexSorted, false) + if err != nil { + return nil, err + } + if !noPersist { + if err = Save(idx, path); err != nil { + return nil, err + } + } + } + obj := Carbs{ + backing: reader, + idx: idx, + } + return &obj, nil +} + +// Of opens a carbs data store from an existing reader of the base data and index +func Of(backing io.ReaderAt, index Index) *Carbs { + return &Carbs{backing, index} +} + +// GenerateIndex provides a low-level interface to create an index over a +// reader to a car stream. +func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool) (Index, error) { + indexcls, ok := IndexAtlas[codec] + if !ok { + return nil, fmt.Errorf("unknown codec: %#v", codec) + } + + bar := pb.New64(size) + bar.Set(pb.Bytes, true) + bar.Set(pb.Terminal, true) + + bar.Start() + + header, err := car.ReadHeader(bufio.NewReader(&unatreader{store, 0})) + if err != nil { + return nil, fmt.Errorf("Error reading car header: %w", err) + } + offset, err := car.HeaderSize(header) + if err != nil { + return nil, err + } + bar.Add64(int64(offset)) + + index := indexcls() + + records := make([]Record, 0) + rdr := unatreader{store, int64(offset)} + for true { + thisItemIdx := rdr.at + l, err := binary.ReadUvarint(&rdr) + bar.Add64(int64(l)) + thisItemForNxt := rdr.at + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + c, _, err := readCid(store, thisItemForNxt) + if err != nil { + return nil, err + } + records = append(records, Record{c, uint64(thisItemIdx)}) + rdr.at = thisItemForNxt + int64(l) + } + + if err := index.Load(records); err != nil { + return nil, err + } + + bar.Finish() + + return index, nil +} + +// Generate walks a car file and generates an index of cid->byte offset in it. +func Generate(path string, codec IndexCodec) error { + store, err := mmap.Open(path) + if err != nil { + return err + } + idx, err := GenerateIndex(store, 0, codec, false) + if err != nil { + return err + } + + return Save(idx, path) +} diff --git a/ipld/car/v2/carbs/carbs_test.go b/ipld/car/v2/carbs/carbs_test.go new file mode 100644 index 0000000000..5ec52e972f --- /dev/null +++ b/ipld/car/v2/carbs/carbs_test.go @@ -0,0 +1,108 @@ +package carbs + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/ipfs/go-cid" + format "github.com/ipfs/go-ipld-format" +) + +/* +func mkCar() (string, error) { + f, err := ioutil.TempFile(os.TempDir(), "car") + if err != nil { + return "", err + } + defer f.Close() + + ds := mockNodeGetter{ + Nodes: make(map[cid.Cid]format.Node), + } + type linker struct { + Name string + Links []*format.Link + } + cbornode.RegisterCborType(linker{}) + + children := make([]format.Node, 0, 10) + childLinks := make([]*format.Link, 0, 10) + for i := 0; i < 10; i++ { + child, _ := cbornode.WrapObject([]byte{byte(i)}, multihash.SHA2_256, -1) + children = append(children, child) + childLinks = append(childLinks, &format.Link{Name: fmt.Sprintf("child%d", i), Cid: child.Cid()}) + } + b, err := cbornode.WrapObject(linker{Name: "root", Links: childLinks}, multihash.SHA2_256, -1) + if err != nil { + return "", fmt.Errorf("couldn't make cbor node: %v", err) + } + ds.Nodes[b.Cid()] = b + + if err := car.WriteCar(context.Background(), &ds, []cid.Cid{b.Cid()}, f); err != nil { + return "", err + } + + return f.Name(), nil +} +*/ + +func TestIndexRT(t *testing.T) { + /* + carFile, err := mkCar() + if err != nil { + t.Fatal(err) + } + defer os.Remove(carFile) + */ + carFile := "test.car" + + cf, err := Load(carFile, false) + if err != nil { + t.Fatal(err) + } + defer os.Remove(carFile + ".idx") + + r, err := cf.Roots() + if err != nil { + t.Fatal(err) + } + if len(r) != 1 { + t.Fatalf("unexpected number of roots: %d", len(r)) + } + if _, err := cf.Get(r[0]); err != nil { + t.Fatalf("failed get: %v", err) + } + + idx, err := Restore(carFile) + if err != nil { + t.Fatalf("failed restore: %v", err) + } + if idx, err := idx.Get(r[0]); idx == 0 || err != nil { + t.Fatalf("bad index: %d %v", idx, err) + } +} + +type mockNodeGetter struct { + Nodes map[cid.Cid]format.Node +} + +func (m *mockNodeGetter) Get(_ context.Context, c cid.Cid) (format.Node, error) { + n, ok := m.Nodes[c] + if !ok { + return nil, fmt.Errorf("unknown node") + } + return n, nil +} + +func (m *mockNodeGetter) GetMany(_ context.Context, cs []cid.Cid) <-chan *format.NodeOption { + ch := make(chan *format.NodeOption, 5) + go func() { + for _, c := range cs { + n, e := m.Get(nil, c) + ch <- &format.NodeOption{Node: n, Err: e} + } + }() + return ch +} diff --git a/ipld/car/v2/carbs/index.go b/ipld/car/v2/carbs/index.go new file mode 100644 index 0000000000..b139a24a4d --- /dev/null +++ b/ipld/car/v2/carbs/index.go @@ -0,0 +1,89 @@ +package carbs + +import ( + "encoding/binary" + "fmt" + "io" + "os" + + "github.com/ipfs/go-cid" + "golang.org/x/exp/mmap" +) + +// IndexCodec is used as a multicodec identifier for carbs index files +type IndexCodec int + +// IndexCodec table is a first var-int in carbs indexes +const ( + IndexHashed IndexCodec = iota + 0x300000 + IndexSorted + IndexSingleSorted + IndexGobHashed +) + +// IndexCls is a constructor for an index type +type IndexCls func() Index + +// IndexAtlas holds known index formats +var IndexAtlas = map[IndexCodec]IndexCls{ + IndexHashed: mkHashed, + IndexSorted: mkSorted, + IndexSingleSorted: mkSingleSorted, + IndexGobHashed: mkGobHashed, +} + +// Record is a pre-processed record of a car item and location. +type Record struct { + cid.Cid + Idx uint64 +} + +// Index provides an interface for figuring out where in the car a given cid begins +type Index interface { + Codec() IndexCodec + Marshal(w io.Writer) error + Unmarshal(r io.Reader) error + Get(cid.Cid) (uint64, error) + Load([]Record) error +} + +// Save writes a generated index for a car at `path` +func Save(i Index, path string) error { + stream, err := os.OpenFile(path+".idx", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640) + if err != nil { + return err + } + defer stream.Close() + + buf := make([]byte, binary.MaxVarintLen64) + b := binary.PutUvarint(buf, uint64(i.Codec())) + if _, err := stream.Write(buf[:b]); err != nil { + return err + } + return i.Marshal(stream) +} + +// Restore loads an index from an on-disk representation. +func Restore(path string) (Index, error) { + reader, err := mmap.Open(path + ".idx") + if err != nil { + return nil, err + } + + defer reader.Close() + uar := unatreader{reader, 0} + codec, err := binary.ReadUvarint(&uar) + if err != nil { + return nil, err + } + idx, ok := IndexAtlas[IndexCodec(codec)] + if !ok { + return nil, fmt.Errorf("Unknown codec: %d", codec) + } + idxInst := idx() + if err := idxInst.Unmarshal(&uar); err != nil { + return nil, err + } + + return idxInst, nil +} diff --git a/ipld/car/v2/carbs/indexgobhash.go b/ipld/car/v2/carbs/indexgobhash.go new file mode 100644 index 0000000000..b7dedba0a4 --- /dev/null +++ b/ipld/car/v2/carbs/indexgobhash.go @@ -0,0 +1,44 @@ +package carbs + +import ( + "encoding/gob" + "io" + + "github.com/ipfs/go-cid" +) + +type mapGobIndex map[cid.Cid]uint64 + +func (m *mapGobIndex) Get(c cid.Cid) (uint64, error) { + el, ok := (*m)[c] + if !ok { + return 0, errNotFound + } + return el, nil +} + +func (m *mapGobIndex) Marshal(w io.Writer) error { + e := gob.NewEncoder(w) + return e.Encode(m) +} + +func (m *mapGobIndex) Unmarshal(r io.Reader) error { + d := gob.NewDecoder(r) + return d.Decode(m) +} + +func (m *mapGobIndex) Codec() IndexCodec { + return IndexHashed +} + +func (m *mapGobIndex) Load(rs []Record) error { + for _, r := range rs { + (*m)[r.Cid] = r.Idx + } + return nil +} + +func mkGobHashed() Index { + mi := make(mapGobIndex) + return &mi +} diff --git a/ipld/car/v2/carbs/indexhashed.go b/ipld/car/v2/carbs/indexhashed.go new file mode 100644 index 0000000000..f4c04aed09 --- /dev/null +++ b/ipld/car/v2/carbs/indexhashed.go @@ -0,0 +1,43 @@ +package carbs + +import ( + "io" + + "github.com/ipfs/go-cid" + cbor "github.com/whyrusleeping/cbor/go" +) + +type mapIndex map[cid.Cid]uint64 + +func (m *mapIndex) Get(c cid.Cid) (uint64, error) { + el, ok := (*m)[c] + if !ok { + return 0, errNotFound + } + return el, nil +} + +func (m *mapIndex) Marshal(w io.Writer) error { + return cbor.Encode(w, m) +} + +func (m *mapIndex) Unmarshal(r io.Reader) error { + d := cbor.NewDecoder(r) + return d.Decode(m) +} + +func (m *mapIndex) Codec() IndexCodec { + return IndexHashed +} + +func (m *mapIndex) Load(rs []Record) error { + for _, r := range rs { + (*m)[r.Cid] = r.Idx + } + return nil +} + +func mkHashed() Index { + mi := make(mapIndex) + return &mi +} diff --git a/ipld/car/v2/carbs/indexsorted.go b/ipld/car/v2/carbs/indexsorted.go new file mode 100644 index 0000000000..d16d10d7db --- /dev/null +++ b/ipld/car/v2/carbs/indexsorted.go @@ -0,0 +1,197 @@ +package carbs + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "sort" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" +) + +type digestRecord struct { + digest []byte + index uint64 +} + +func (d digestRecord) write(buf []byte) { + n := copy(buf[:], d.digest) + binary.LittleEndian.PutUint64(buf[n:], d.index) +} + +type recordSet []digestRecord + +func (r recordSet) Len() int { + return len(r) +} + +func (r recordSet) Less(i, j int) bool { + return bytes.Compare(r[i].digest, r[j].digest) < 0 +} + +func (r recordSet) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +type singleWidthIndex struct { + width int32 + len int64 // in struct, len is #items. when marshaled, it's saved as #bytes. + index []byte +} + +func (s *singleWidthIndex) Codec() IndexCodec { + return IndexSingleSorted +} + +func (s *singleWidthIndex) Marshal(w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, s.width); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, int64(len(s.index))); err != nil { + return err + } + _, err := io.Copy(w, bytes.NewBuffer(s.index)) + return err +} + +func (s *singleWidthIndex) Unmarshal(r io.Reader) error { + if err := binary.Read(r, binary.LittleEndian, &s.width); err != nil { + return err + } + if err := binary.Read(r, binary.LittleEndian, &s.len); err != nil { + return err + } + s.index = make([]byte, s.len) + s.len /= int64(s.width) + _, err := io.ReadFull(r, s.index) + return err +} + +func (s *singleWidthIndex) Less(i int, digest []byte) bool { + return bytes.Compare(digest[:], s.index[i*int(s.width):((i+1)*int(s.width)-8)]) <= 0 +} + +func (s *singleWidthIndex) Get(c cid.Cid) (uint64, error) { + d, err := multihash.Decode(c.Hash()) + if err != nil { + return 0, err + } + return s.get(d.Digest), nil +} + +func (s *singleWidthIndex) get(d []byte) uint64 { + idx := sort.Search(int(s.len), func(i int) bool { + return s.Less(i, d) + }) + if int64(idx) == s.len { + return 0 + } + if bytes.Compare(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) != 0 { + return 0 + } + return binary.LittleEndian.Uint64(s.index[(idx+1)*int(s.width)-8 : (idx+1)*int(s.width)]) +} + +func (s *singleWidthIndex) Load(items []Record) error { + m := make(multiWidthIndex) + if err := m.Load(items); err != nil { + return err + } + if len(m) != 1 { + return fmt.Errorf("unexpected number of cid widths: %d", len(m)) + } + for _, i := range m { + s.index = i.index + s.len = i.len + s.width = i.width + return nil + } + return nil +} + +type multiWidthIndex map[int32]singleWidthIndex + +func (m *multiWidthIndex) Get(c cid.Cid) (uint64, error) { + d, err := multihash.Decode(c.Hash()) + if err != nil { + return 0, err + } + if s, ok := (*m)[int32(len(d.Digest)+8)]; ok { + return s.get(d.Digest), nil + } + return 0, errNotFound +} + +func (m *multiWidthIndex) Codec() IndexCodec { + return IndexSorted +} + +func (m *multiWidthIndex) Marshal(w io.Writer) error { + binary.Write(w, binary.LittleEndian, int32(len(*m))) + for _, s := range *m { + if err := s.Marshal(w); err != nil { + return err + } + } + return nil +} + +func (m *multiWidthIndex) Unmarshal(r io.Reader) error { + var l int32 + binary.Read(r, binary.LittleEndian, &l) + for i := 0; i < int(l); i++ { + s := singleWidthIndex{} + if err := s.Unmarshal(r); err != nil { + return err + } + (*m)[s.width] = s + } + return nil +} + +func (m *multiWidthIndex) Load(items []Record) error { + // Split cids on their digest length + idxs := make(map[int][]digestRecord) + for _, item := range items { + decHash, err := multihash.Decode(item.Hash()) + if err != nil { + return err + } + digest := decHash.Digest + idx, ok := idxs[len(digest)] + if !ok { + idxs[len(digest)] = make([]digestRecord, 0) + idx = idxs[len(digest)] + } + idxs[len(digest)] = append(idx, digestRecord{digest, item.Idx}) + } + + // Sort each list. then write to compact form. + for width, lst := range idxs { + sort.Sort(recordSet(lst)) + rcrdWdth := width + 8 + compact := make([]byte, rcrdWdth*len(lst)) + for off, itm := range lst { + itm.write(compact[off*rcrdWdth : (off+1)*rcrdWdth]) + } + s := singleWidthIndex{ + width: int32(rcrdWdth), + len: int64(len(lst)), + index: compact, + } + (*m)[int32(width)+8] = s + } + return nil +} + +func mkSorted() Index { + m := make(multiWidthIndex) + return &m +} + +func mkSingleSorted() Index { + s := singleWidthIndex{} + return &s +} diff --git a/ipld/car/v2/carbs/reader.go b/ipld/car/v2/carbs/reader.go new file mode 100644 index 0000000000..8accf0a842 --- /dev/null +++ b/ipld/car/v2/carbs/reader.go @@ -0,0 +1,20 @@ +package carbs + +import "io" + +type unatreader struct { + io.ReaderAt + at int64 +} + +func (u *unatreader) Read(p []byte) (n int, err error) { + n, err = u.ReadAt(p, u.at) + u.at = u.at + int64(n) + return +} + +func (u *unatreader) ReadByte() (byte, error) { + b := []byte{0} + _, err := u.Read(b) + return b[0], err +} diff --git a/ipld/car/v2/carbs/test.car b/ipld/car/v2/carbs/test.car new file mode 100644 index 0000000000000000000000000000000000000000..47a61c8c2a7def9bafcc252d3a1a4d12529615f5 GIT binary patch literal 479907 zcmdSBRZtz;(yooW>%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� 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 Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ z*eQTV_~wpPza$N;iF~qTt9(-U8?Ha){td4GT%5MRg*Z2duf6>MQGARCBcHJWbpUH^+{uK~fG)irF2I@#40d52k zF^uCx;189qqdV^>>5k8vtkAV&`I)V_C6xvbbYMeQLIdQ1HIAao)|W;J*~VL;vj}Yr z3BDbI4T6}j8IS+QUyKNMtbNPy}!6Jf6!jWK!#e`B3md?ulTSI1pvQVLqJ zy1?>!$rs6hNmkR#C+yOQ;1nMs2+!Li(WpM#gT~R^t&*URHe2G&zzARMh`pu4yJ)C$ zr8#R8Nu^i>m}3YEQa11np*0(Fw=uR-hn8#{x)GZV8-;By6^@d?Ud|y-@VBFCHC)oc zI%j46hO3RlpX+wR`jG34ntx=Ab>4#8VR_u+ioKt*NQ=;>l?DsypKu+)e15!5+_o#r z{kSC+FSs_&&ZI{3#AvpWpT;JaI&b|37g-Fz?j12l>%a+`3kUIO`la*}Hor1ijrqU1 z^C4_8(UHHH+&jL1i1_b=%jylTW=Gc-)3n{~A!yCbxyPNUYg1!>C&IfOC84V}2#4nI zKp#c_^&lRD1^+EEm^6S72we;?v$90->Nb9vUdRMWndUO#L!TEHCsrFnN-Hk9ASS^; zS7N>JbJ_T5MRCUK-xcJaa{m`x@y6(J0@rDR1r%Rb)U=zaTa%I4B&#S@+d#m|$2Afn z<&FKv6%0Lv-D2EIqY#Y;!C?GBUAFer44j{0@;S)}Wtr^8Y`(yl=En~f^S=z2jq@6# zV3p^{ZqsjRpX9JyzQGmd*JCEI>FCqqun^J`5n0#ySooQ<5WN-MwM3z?=xHGdJ(_Ib z=s3`VexA(*6h5rhd`9y;XEhkLWHTr`{G~Jdh}>Zg5&wyUf)GA zoQr;0SF>M<=&nzz#IA5h0J&ARs9-q=&c}Q=mC{q;o`c>yH`fv~6yI-9#K8OCN~1v3 z^y+9LM5aq%>m&S4CfJU69rZPS;wsKSrUw7~Dx^fma;)yCf(?T7iBC=PJDmi61TYj) zJat9T@GAo+t_sKxp2y<2)&o-JGnqe->rZ!xT)zv8TH$sla7^==&z$r1ftVQlAb2q_-PlLeoF8z^xwzf?>EZ9ZM*eh z$j;qF#Bo}A7kR-y!B)CUt0@Kcnpjq*^Y%<#wD-Y&My(r67;Kf`vwc=UofJ2R_q%!3 z&Rv*w1o{vy_<#t+sCf-URUYBZBQTrS^0+mh$Hkt0X)JHz%uy=vcdzwN+w>QQafqeC z>zA0q2&o&^KY`t4P}JuJmi821H8+;(kc3W^1IjS+o<`~D(?8E zVxssb@Dnq`_jI-l#S!As?}w|7OMrQ|cE+8_O!JJbkVO%0GlCS@IQX(hLF_a8392%D zm-C71YN&kZ@kbs+o0u-=VDwR`IsNKZFxL2A7BZC6wN+2Mt)+{P8G{I(%1-HqoDz9Sv z_$1LGq5T35wZ43XPPoe67>d&&2>&sK+rX)WTTPr?a{po@sGE~4@o^uT!Hn`{9!W_T zNl=YwJ5dTs`I&ReSX2?F!ghY`gF&lCR?s=>jxiQ(Z?$duS=3w=x7iwzk~-r}gG8)t zNULHRGQE6$dK-{onL5i6*DeZ@6oyX_{lOI$@WuP$fV>2v93SD8gs}Di?aAq|zqnB}uLrFLjrFw$azwR?q_;|@S z2&%1xDFa4RGKZ^SJkUzz)7?wC!cK(;bN7bE@v?3Q&p95!6TD7i)<=g;B?c%;!nW(? zEY*dM)ETLN_gY8qRPlWsMsWRhn0&{TM6(h%L`o(6gtz6WXeefKE~T*u|1*uauAtbA z_N)z18U9?V$2lj5IY7pJ%iGOSnAR!}Sce7t&t5CuqUJ$CfPhY}dX{%;O~YJ;<=H?G zGZAXrG;c$FiK+gZhztt>Y~CqDwP~Oq_!fd7Lenplo)%U#(wo;!5F&gyQsoJH?4U{B zccK425kY+u5#Mg}F+4*=2d)5RWT7749W-_R%V;nlPHbt%VcCK$zXMD=qwk@bkG7)& ztBX6;ToAgPP*MD;H{J9iiJ2hMS$GnIH-20|Cpf-6^p#AzUBkiB>gIJ+Tp3S_KTJJ~ z4}b#y+Yb6u?%zb@KdZS8&?+OpO<% z7X6OV32@SSm1g-U`^BBB+BVuVm@^%g6tq!VQg1JyA&g@3g&gKxmsU|#!!hDuh*h{{ z3B#Dzgs;`#%)FtdN)3-+*tU7|!$&;g$32=T<)@R$na-S zTqpt9DUV6ow=__opF_oc5N@na%v?Z;;J=kbwyBM5wlCRU$VV3V(G*z`k&EnXKb}+` zhib)X@PL@Lv+}In!}#Kg_k8l_z5{O8we3d|V12iC)Sa_p%ju1vQf9PzD{)%v-~3eN6t}U}i*}-&G2!gj ze9o>uAPw=+(4IH&!-;CpMXohk{|eESzJ!hNoGb-k?8~|Jcl*Vb?cbF$_8BBv)w0zr z=}%5LCPZ1~%gvBrYBN0dq+U$*%1Aibe-8SVoL2XJ%TS&(z^uHIO*Qc5_%dsFXUqB2 zLbCZX>rOB94wMwK)V$Nk9&OaC*p#8 zE8QAUASI))Qmjy;Fe;oOpQ&pINzUHC687iyfk}QB`mdvqZ2yhIC_>1V+b0q)jSBY3 z^Gi9!N71v|j2HvDmFkwe1(U{3P`jBpM@2@2548#sf%+L;!EYJ`_|F-f!ZPQ;??jMtL3+_@>LBD+d zY9<1%Sr4g8hbHsNqWm2fLy@FvS5B$q1cO%%7KD^PKihE40g>OclXFVya}LL6?$uu{ zPa~UoR*sn8z;R_@*pw9b+gHb>kbEIkq0Gq63;mdTKr`iet35>q-o6(*dLdIWv0S5O9@&P557hX$AFgQrQio!ROW z?`?^#Q5!v!>g7M=d(TfugS4?9Tij1;r(qw+I@<)f!L?jDwTlNiv$b+r{@7}lZYE!x;3oTynF7lh5D zri@5DUaa5h(n7k{hk%Lo#h?pLa$7c4+99|?`yxj;De6LTv3c6I~%u#@(wUk4~ovzJoyBE28qBn zFZ$SlTCX4-5VnKgG$DZ+2w3Y0%l#_Z21CkM=`1$lXQ*sgz8@s?k;6>H&M7~@_ih9H zx0S2oE|{eP5SI@(gG^`Omy<-g&!!85BA-DyH!Z@J8dR}x|8s3xWSV4LX(jY4R$ehe zJV|K$(3B&_|G;5rgx$LJ`rhs z6On*bo#vYvS#9dWNOTY6rQX2Q?m0Py0msx_q*|Hq(ni&Dw!;^?4hPe%eM0e*3n>M#iAy!rOcA>RUm5H9TXrk{tnY}b=&dEUq5r1q+ zVYyLjV;0(r1bM=RW40@Z-@u+DoVuNAB&6N%(6j7-Y)xLUBQ!JaAOs>Uthj3TFVpWN z|Kb4+%uZrJtJtm(e&6||AuRs(z+P{*oB#o+QK+O{5rydL04p_hjoQZwnp(S~ZLEJ& zh#e)G^wq|8e!2}-wafffmzI%@?8iQ}t#0eS6XQu1X!G0X0nqqm73w-|LVoR53a~JxB@YJ=-_$c z?Kd7Xs4=a44gx@Dz~mFEwL@pLE;I7f;m;Ln&)1v8@l#0~p&8VYJ_9l!VZQO9eh|2D z9RQ0abL7`H&i9}CNV(fty@Oifr@RS9V0%^x62b1TMg~VnruMf(|0(x>!BzG=CgWJ4 zmOft!^@(L@Mh;Q2h`7W%hlb`zOm`+F^)+RnoWHW_nB8dixjHrvNDu3m^dNHOqha=V zzNDToM{nDbJsKJSENkQ>Ku*`2hsWK-Fu*&VfPg@mFOba;gr%@Q;QV9W$9c3Us!t6o4+0jqRNMxSx`; zd=1K^x}L+iML7CZHoAdo%Je2@|Hevw4ICe-?O50@ z!>P61f|RU1S0CXM)@I{l?Asn$9>y@@Tavb7;>!A2ZV&@}AvD>eSJk;f-M;V%OkS7m+mv&>41O&7j#vY?L{}E4$lXTP%0<_ z<8=oQo2|8c;DaVt?y)r@_0K7r0y!mj(R{L|b2rkL{`<>qhGi3bi#C1BqBpI9;4;C4<-CCCu-)MX%ovUn0$fd7mhV=S!4TO6pBN`{X(u`%{GY#j@wryPZ~l>buBgdub(t8PrA1Nx+L)dv z75NUYt4H62_f13)1_2iDL}U>tF#8h}LY?c&@~vX-+RleOTy0yXm2wQeo3%`$&b@b` z|2`4XeG`#lQ9fa}AQ-rnDrkmOKRsltZE5xl=WNz*P$cg1u-in2uCODBc1~7?4wX~lS7IY~URD-kQTn#&fhuxB+H_hLI9T^A1?>X8M6#1DX0JYxA>?4u zn?w7y2f9BuIs*F#-B(+}Tx^o@Kd2vJ(GddE&{EITyI~?0yGC3o(((ELPllvI1Suuh z>lA$QG*+%#d$!m5wPOwpWOYgJvN{i6StA2cc)~ZlOUxhz`Cap<-}?GNx-=Trce;|S z0^M|m7G#mKPy+xi=NDz~IsIs8x1mG@jUbC|l#7Ray+h6ZSejm9YgbqNX^5O_YzDh$ znehB4sRhf`VX${EPZn=y7u}81x>qrdjH3NSk%oL!%3)2!5=^<8UbBTbf#u$2?``xZ zYCb4j^r8N>^No$;akQsYC?7tn_F#`k|-M+c_P%D-U-9CA{ zwsxU!#O*Y<>U6@M@)N20+xY)KMvo`(3i~lMj(umXh1$6tyob5ZWU1MCu0Pe!E6xZF zLemKYp=?zE%-<377_}krvxF^oi^0fP?deIzH$33hTug6p z`F)85XbV}Lp0UF90<6xF3#YQ&EbIr>U7tWQs8F7oIXuhiGv#81WWN6&>fR|l)9h*c zj;)SuTOHfBt&Wq9la9@fZQHh!j%}-B+nq1@AMebUd3I)d_Uq0@UHP3=YgMhP%2+IC zLPI27jEN{Q4K>Y3d`v*PWi1jEzI^k_?b`L&U)`2N4) z`ZwSI1y`Q5_E8c@N~}?f)gN$&Ibu_yU!k$fxNG5v%OX1>6K63iylQ#aW-U`jzRE=L zH}y!BGAbaNykFYQySD826DOw^Rm&RP`r&0eIrt+U>>ap@aAY3b_ds_g>Jj~_Xg!;-jK4y%v-r~rd9Lu`HV(+7!e-OKEU=ehm<@Z9Rpl zGYfF2a;YJeJo(OzLm1{&mT@#&k3Z9qt=SeLXR15!0T+3+FK?G#3VI5n`Np_$u1zfv zE%~t^y80EGzP?iS5C#SW$Uo-PVj4ggtQD!9(g_%#i2JXRJ;?vDQG0QRKi3F2Bt(xV%OB0H=qPk%&(0>{((08~(o*vRqMx|&FSndKbOo_5oMPRkKsFK!zex$!)Y9jI273L6B z!SNJ+F!@7Y8)Kvl6bk@fZijcU@;rse_SS3;*_=HnMc|+VpT^vh%vJ8bK_(2na?LP= zu>g>AnKpb;t#PHoSoECPuC@&smK?3&ul-S%cOtPbdY9}B9nD5j0(5eRwXc52l_834 z`dF*7ww}I`{YY;mDPfLZB@!dkk6iGbQ0 zrA@KV*D9lBulHbs(DnNJ&{vlRih#~U#H|@PA8XU1|2M6II%z5gdA|6KzeMXS@|B0R za$CEcCjR*yw&(ywsXU+VKZiR*yGur6rI-vf6vw9O6AFufuXE)<^bbG)o($|Z zY5nN51)cs#f5UhLS3)DfiuZvyXHEyFQBE5j`&(l=N?Fqy7g8_I-$cJqHvZGADfM}r;FT>N31&N1E8{phTMYh|)-5ppVO;VdvxZAla z6Z|bA|K|I@M5LKwiP7=KnTX=cW|vB#kWvUYip(Jij||F;vu|sD=z_xnAG>A4)GlY% zCM-V&?kf`XZ^@{tz>7zdTHW!NO-EqWCG@#;jalkmrrVpJDV{VwuC8qa!YE@;Hg}xx zV*(!{;_pd)0fCnPkZR-WaLaM%ztJh8$UR6^dDG(fn0pek=pG`3FKm-q)s$|ST)Wgs z+ZfaRd+y2BE^X>z&FOwF%oSo5^&Bp#W(EITo@-9QGiD6)wQ ziW+0Fu!dG`lSH_!XmnS;E#s=0xlHxrqnIG#OL@!FLjUS4bky-jv8>F%GF}y@=OMio zlu{Mh?y9q{Vo*%%N?On^oS=!~9~3PGKQKmMGOp?L2xuQC>EgIhC|j(!y~==Wy@-Zi zIil`hjC6=*x3Q+froT3JeoreDs>SUSQrqp62M(SfeL!p54A8_uyR)tpA~J`B59=c% z7c!WP#L*7HdGa)*Yet&Bx<-5x!d%^k0dk$&MDgoElO~Nw8ec&D?o^qXRheaJzqOpM zo5KqsdA|rOQO{RjmKBBG(@3C3_#q;u2G4vUI4GfA5`)r@J6hkhOy#QT5}qXpl!fw*)d{pCNFMLMC-XuMInmKuqgXtJl}jJ6bz7mJ`SRe0g0VJ z2Hk5;QcL&m{9)>E%}X46f8Y&H8O{N^=PUazA{zSWCZCjn0n5>OSr1J#J)mD-1>=jl zg@#gBB-*i93LQ;5+iFG9C)59)h?KsINWk|*WK@CsE*we5`~t*NnEfrIGZvVbVtiZS zHK^v$RX#sWnYZp9EUEzjrstM{H*{x#Y@NkNol|jn0=LQStVhtfNld`h|)O zdBRpUOl^Bz^AN$!3Ate!_%XY^NCI5XV*9X4ScsG+`;Q|O2o|sB?3`vyuD{Y%^|n|d z1w5rI=#TkuirOuLxS<5j#=7@3KX^yODV<22^M@cBxjFYJ+y-oN2?2OGR>ZT@pE2+B zdx89fMZ3k)W%A{389=dJU9=6! zt0d7mY5D3Z@M_Gn)#xiY=c{Aukmv1R6WK7Te>Pt;w(?ZvQvaa@#_d|0Ah(%SGLDt> z)bQlRzC69m8EaVZ5uhb9m~@;=EHD<%Qxi&EQv3DT7_>@Am)*TeRf10XJ^)h%kNf8h zh&%vd?x32Uca6+i>1w8bf6qP@By!6n&JQ+m4>JOPpDn`tig=4`t_ z9%FSNDZVTWN`9D@gLbF@@dLNq;yYY4V(7-7h=IEr5fIn=iDm0lfxz>0q6WiVgVuh@ z;>)XW7(?eR^Z3d1zXzA+J6wT-7&W1#$Fbn|QP|k{iN^b@H>8b77r+2$OGRfyPTIF> zmcL5=yq+~xT0&Qir02u?A#LzVr|WE}vM`obQbs5Ax}=_-g@D-_#=e3K*^3P0`TBy8 z1Mb-<8r-%r5a0hDq5sYIf5Fw{2;b-}HS)F|SSp$^cX#;0$Ipkw4?r=FZh)gVJxt+r z4xF3zu!Zkag!bj>O+R;;Oq5g?(`x_P`LO-w{)1OP?mDWhARbC(XUIfq%kj4cG#)(6<@X%_}iL6{N?W&)-`Sc#q{713Jx( zXgs0Y6dL2KIEfmKTT`g~$HlH8mU)QPO3aN67_!JzFDnn(9a#~Wde9OS2nF{F;-bVa zNA7CSlw!6sWXVX_p2(?@D{gU`kjX=o98BlE>~fSR$36V>!f{f1A98%ZLE6CSq|rL_ zYcXqSFmg)5it+@3+Z=+!u+>co^fh7ob(`1IboI`JwConHb`Xuu;iHbZDeuUa7fA_=Qhs!Xe_ z-0T#^R^XP7I{Qdosfw2Enmee7OU*`6;is9P00Fll`B_jkRQ3S0?Ao&R)p`H@pk$&# z&mcsZQ%N-3+~n$R#;c(MCBwq2J$J%=+4S*PojyQBhj2B$`kykB%Mh)+EZ-}J!R6l0 z^ld|sCc9i!%t2AFhM1o9a8>FmVH{^K&XFlc2n$%{zvvr9d}kRl;tr2!o5XP3# z^Dk6;^XsI&?tzQ*-`YqsUY0d3Rgp^gD+`oKY8QZ&yo(3`9s0LV%0TkEg2$rWXarTz zF~L{Pq%Ab)BlAL!Z;4lJ#>mOuQvJ#Fzb7K{?;?`tEUw}!UJ`y*H2Cs^>*uxb9j$lb z1Bt5Ck#Mu-GY1Mb)^$}>F49UvQ+z#Y_&f!$ZzdEU^+=Upxs;DDEH|~%$$~X{8?wn- zV)5H#e(iQ)gvs}MR)em~l|nG2N|E8eMdaUn|CfkFbfq%O)dDuUT*M}lM<*RDoOATU zjdN=Bj_vGc83IThn{Uq%)-OW@oD#cse@0GJJmV0<~-^^TyUq}!8)`C+R8e+ zTa#sJjjb3$rR)SygYfcnoV$&u(LVC|5Rr;pYTF4xxe^BXR~u{0jSl(J)(CjORsYgs z%Pikklzk%|+d$tSP%LD-Ev-UU%Evh7NRc0CGb)=y+O|_B_^;FyArR82Tem3h!0_ECsR}z2dAg zdDr^glPWlsY9I2Q{vJu>6?$M%a&=$?{u1jZ&Lzw<1_z*7^^G^(KZSI?Hw0z@o*&`X zOm>V51csCb2v#vc7YaGpO=E#To*I1?DCNt`$jbGV0U4Mylkj zDoq8A`qQNZd^2p^+ZPue!L-tkbut!&@Z207n^Z6~Y9>Xzv(C-ja2lPdrrVjh{HKWfn7&7`TAy%?T~N44AW7c%88o<`t?Oxbie4mFO@a# zjG7{ML(9za5fMuvs5)H)Yc~1RdjQ;fILW<8eg;rtUTa zxC3_TV$GmN*;JA2t&90j#rL~%&1sYTzh;)M(LSZh$Y6a?g7|b{<`D5w9@)75?&aQA z+GRIuVQIGiJEcd#N$I{=Bd#NjVaLl%8iE*v&~{Fs$56`EVK-UR5vE88;L8juy3r?P z=-FUpa3R|XXS3Lie48tR^kn!hiWv7To*%#Qqg5E`lj(m?M2O!-q^yyLKU?2`={0rU z16mq|#|dZw|EAykvdXoddC+tGS^Q1L#J6s4sSUk5xC!T+83>!+(C>w6u%w|@l zA?+?wF-A33y%?7U{?vjPo_x|w)CI!C$0?Up3%9ceQmOVe?SeJTrx-<8;gkc3%xsQ! z54@E~LSOgTHfFk=ZquZ3I3-oDkHrf}b1f9|Qs&FSpZE}wcu7$dN zBO?|V6$^j)$?~~4h%F1V);aI|h=*_aEO}%Mz)of$fZbzKOCb~`t z5?LqIU#A)bj?#mK4VM|GveG4)lE0*l$6MXo;DK^pb;{f77Hd7SjNVq5?W4VQfp!?3 z8Wj^e8{fxONp~VbIW^g)c4`uo9kFEF`q`)PR?o-!0z5)`CjhECmj-P;r1zmNcq*Hy zr9U^u6UkSqD9s_{$xAS3+xB9S_|VEGM>pb{gR#%rElP=)R~e_Xe~L@`WNJK6)pdCJmJgBw#_P7 zswO6#J2k!1$^`2fUxgfO&lbNW$eFr6Pmfs(v9lRbfZyRgR=nOC5O)=i6hJ|c6Zkc~ z^eO#cpm{);IFLtg{br>KzoHv{Qig}bC$pJxY*&aEG^F2AI3f?(rs<9|lg%xw#+csEh6|re|8pWj zbpI|Q)n*yBIV88T7&on$E?G8s?yN8o5cq>JjWvIm(ALX&-hx}}xyY^9Ja+a5h@w#! z!H)e$gI@^&>gc7?7^67ZnPSRaQO3zB>~4Ej?&Yaqy)QgXUKPVtrOkiJ7Mo}W|NTOeTis!&Hai z6GH`tZTNC$$xHye0GW06yf1tlAOwE_{gnoKo?xBIe}MY+{FR`QQq}m341nBwT;Tn# zAcp#o71~SC`<0>WA9I z_)YE9m2YTKYs)&2%&ZIsEKLgU&>JzmsfMcYD+FR#Aa%H8!V1q1ucebCXTX6$&2R(U z^!dTC`Mvcl@zx#U=7|x;Z3*YueEtV4%k5zMSZN02w@s;;HN zH0AaI)z_N5ILY=7U~x9f_|_Jf+ihVlu5b}BsIOr-YC}`*58C8H;=<+ ztCF={znC3Z(5xI|wkh%MsI2{`qov0f*r30PE5!-b^G_7-M@XNcMkve$fU4MDdbcWOGPAV*;%Cb z;s@QB`BVS?`O$it*!E7W1cp#*F5N`?&bmemT`)LFi0b5?T^xY6-gcrxRNANvH!em+&O;N&UZtl zHOtV={c7Vy^z+N6P2#~XtN?OdLPu+VZrB-ov-anfMe<^VUlX92M98FzO|>=Xkh!1 z7r4Sg7K@QmMXFF2JYf!EfGYthWz!)*!KBk3Uc^`q@0lBQribN9Al&BlwLRCLP|0X( z;|rajm?17PQx=%r!GN2asOiNTNnyVb>fLx%j>q&5hkmpp3e?Ks?3ARR2GGo?Jk%A$Mt^su&3Wyt8Z5 zB=uJ1O~Q&;tBMz=Yfi}gz3BVftdIZnam-)yOcq zT-!jr`(2vsE4yQC^J>`)^8Opt27WUb#y|~n$-r-4`&19-|i<>U#PZu zeH0!?#A=qTh7h&Sn)~=AipbHlEo|jEQmh}2ku$a@k&65owcUHq zYCnag@+d&>?Z#%lDt<6zfC+aZ%1-(nuJ{YBx0?O23aS{Gq47G3B*6}Ir6el)n;d+Vbt3Scy2vd(J`uZ~jigYE4Ah~TplMNlQ$bSmfk7g3%DgXAf?^a_x}`X)L~fdVi(C}gRg6nDgc-c1Gc7o|0?dbm_QaIVmHxIw z>1r~U9XetF;eHGfg$7Ai&>}JA4g^^;P|bXfhyl;|)mTQW0-fuS4Mc3*MoT$+rmVM^ z`X-#_1FjZt?z780>uK>V20F9z{qXP8vi@-)-f^3F#L#{~ z4pp}=W2Gw3@053SKq=)uw`{Sx5QZ)0{0wYWK~E6G(BcMJ8lay)(9;7hFH{#TAJnxdCqM`TcZD%KMZ-`_>qf;~$|;&9r^< z&41VfpE?=bW8uKxsU|k*5sQPnpFJV=I{M)PBzGIGEB31-*LBX`~d1 z5;X53rR8oFa@eixOj`5~SAZGcK_n>p{UtOEgmy2%D`q*+!XyC2zJG@sxzzf+5^$W| zu7#8)W`u^$+^AvSls{VUA;a^FBKo90ognO-i8?5MX}a<&WGygriKC2 zw#=Q0Ih9>T^3tFf)Kmxn;nWLV_Y*NJxqDy1%wp)()6j(FGJfhQ{JF`5ctQ~UCB)vG z{i{yQyb(#KH2e&>P{nFsOFfB;x# z-H_UO%Y;`Yhw?*AYGR&34CPnkE-L}nFnAwtrlbpnIR{@@O|1QvE!AQoX5<$&vV;v-_z$^Qh>e^4U-V|WFftRa@+ljYX zYxc)$@xsDi6ML4!Z^T|Y@CGZ=d?84Plp+fz5rx4ii0yoqhfW`G4Vy!ytQU<2|9JdtWp3kI1Du`2&vtFCI-ULV2n?vmyEFXJtUJGaY;o z4S;&WU~ShsTwTWKI-iJvj=VQI5hgIWbS`(_oC&buD{<3h454z~g&RQ_#qs{!m-XL+ z3-ukYfK)r;#0VPn!qK101yU^#l-l`2>3Kyb?2P42q z18V-B*zrdqMH!sCQH$n-cv@V+;;r>BSKXmmQ9xX0iriRIa0o}nXbFs^y1Fx0`5Uf( z^Zj3N`TAkdGzzcy5nYAIns1ZB>+_(HnI zq8aPLZJZ}Wk3{mP*&CuK%!;`fVn@B&ZdYm26DehG-VrSN@EO|v*DEq}kU@q*Q_vDM zZO)>B)|L*N_0z{YnshV*f~ss?ATd)AynSC2h73Oo2_pr&BS)LDY#%($1g?SUhYhw= z{c|Rt=S}sSjQja+2-|rt~96g?zZ|KwMnQ1*3 zdmc;Kh9!1Y@x9uX1|xP1x3{?Q(OF&TR^uHQE`4be1yR9!%eZdfY!JRWO&61-ic*#^ zi+C6Dcaj(m@dmMzUpRazS|Ccj3LJfzj<=MbJtlt5D1&or{Ix(;_yEQ7vbSYS~wp* zN$;aRkPs+|aw{{ehRf=h9Bo@-NKQMnOF*p`IDh+2vA=+|EBZAV?<+9~K}J2NSZbe^CJ>c6WK|K|I@;EL+B z*#@mkz5wisaNZT;5Ni$E6hw2Z(VowfRv{$EUKEODpL&Zwvyv4M;6@rsnM2faB|>w@ z>(lk}u>~mpvx)Fsg`5fqZ1h_4+La_IUuyzgw51d}Nrk0_d0l?T4gI4!Q8^MYc0cD4 zq~7LdHdUvuK>icP4L>enRop}-SD^m*w)@9U1vhadB#@O}2X#ahay+D|)`&DqIlQFr zkBsHAX;UT#&wYEp%!C??bPEXZ7Z1PhQw@q8VlLxsN>{hJ#-kL+W`(DC_H z&+snMXf**bJH94!%Y-(dRPCRur<(2|mMW(UM!v}oNEG8`p7A0Tbp3`odvMG0Z5!)x_Ym#t7GiAY{|L8d;Xqc z(D}Mx<0Fwb?r=NIt#H-Bt! zr-jb#NB4Yr$pliS4##t>x3PYy)MtQRpkUTv{XLJZ$2aPA%brvCg7BjNZ*<^A$wQw7 z`9KD1&{Y+uGY-t1%KY18ekEzWDt=zsnx9o{6`Xczr)f`OG|w0ZZNL4kprtDeb0~(&0`SC4gVPdF zR6D1N5IEykgC0R#fZ%p^EQ}eh8uxB;gSVT$^!OU*z#BiMUkkmFLZ!pe?42}Ty$Ogy z=+WW;A2>2Jpnp^+h)J|NDMJH=;b_E`29?Kz?q`}B>kmGD?zl(@5GrHC%W!V3@b&`B+4XX_20H(M;VSH=Ykz;?&!2OxK9C6NO^C zF8*3A2em9&OGu!JHn{jDOhXP6qfr7{J%L14n(dCF!o(Wi_}EK&sN7CqCGz>OpWyjC zY^pz1931|R(EsN9zeFUO80_j~^-2ZMR14FfJSVFh6X4-h=tRa3RrbMc>^?uyI4~QN zpIApNIIjpM^g$DK;yWZ!S<-LfGl~+LMF*-h{inQT(Wz0NDDuvy9ko}a>PHnv3_x#D zruV-gr``7+p#xi-1WRap4~Q#+*9L}N>UL^5H(A9yszL4_J6zqI5Q~k&p==*^pMvyd z6EDk`+oNK`EH3BBARkq5ld);*#oVL=&&s~&Hu{^;BQK7?rNYuixH+lC1Ln!uT6;qP z62jLsO%;VsRhGcX=y`K7LA^1Fvur@vQqc3!cNSSU*@#wG(OiDR1Q7owF{xg&!Pw-w zzwCAq=t#PQ8Q~bfJw#7+VorGH`G5lV(%mzvB$+i*?D|JGh!dWv8tt{wG{Vtd;r`m zjeW{9YV_F2s|f0;l7$eeTpGFfu&H~E7NHhc64Hu$h-rw?y-^N6BDA-3caPeVhP?2w zWan%1`?~eYo_XKi0!SOF;#XWKGF=Q};D4mQp5YR9KtGQ_Fyi6naet6=AP(W0XJ=h| z>>h8LhNa_+yu-zgg|7JtE;l28Ls9-vFN{>)sjLfN!UFhx-LTMT4M5Q}Sj4Ezl~1Ps zJ-8y@;R8!a4?Xpv)GE1D)up!f2?2V5wp%#Mge z5e+0n5y?L~3kj$@WCwW1s#K$NME{JeTGRpXzSZ8>y;i!M9q;vbM?<=T+$zP_fPlZ@ z`ZwSI1y^xd4&t}83!45|Q?djzNQqS87aEC^BOAGJ3X2;8>-G}6?PO4x1CP9_)(blctc#5~Sn3$kpYgzM5- zKU6=Hll26&RGG>QJ$haI<&#J4o6>W*zTP7eJNdg_@vewNOf|iN)3KI4nvQ8q3Dz00 zcmUN=i#COeThquzU}bUM_C}zVl;*?A)kwTaXT9RyE0cQgp^c+`c{TiYptR#knX9+1 zYz&gY`5{QvfW#fX+~wbzJFmm#@lt2Gi-MTm5*NQd;F6bzYaDsKp}m!9N=G+qm8L~1 z{qjKX*jTOG0Cd^)gn&W(&-Ax+O&<3y5?9~EXAHo9Fh2VzGfUg43=hK&8n$y+uW9}r zE*KzmjZefZRAH9$2r9Wn%d(cr5VXkRhHq(nKSxg8te2?2chDgGWcuHO%itZZDm1@j zkBzL{ZZ3A(CSn$a$>0H0rzQL&AO6QMt6h*uBnvVHM+3xLO$o7Hqk)=xhbtkNY+69C z_(ge&+w;Y1gv46LS+7%72mlR#}sdR{O_|@!e`!{p?pH+*T zP_yN>(w>AqD!NPH351X+JmvrjTj1orV@3xFrsp)m90SQEG|qwq4~M>$IJ+JC^ijHj z0L>}+1G4=btt?SU>CCGUbS%9@(6R(ZgAYZsj3a7#(UAeu@_mPffBaiJcfyH2`Ef$kh%?ox+s#m6HQ%N z@0#425O4b`)DJap1BWaE1Wm%z0F$y6e!wN5Lw?gmTO+dv>Vq z->03og{5$%nYT(L?#BY-!~Fal1pW>mY;zAQ`^ogb z2kh5(VDs*8f5YE4pB%2TyfVA94S=W8->{R5$=saQ%oyP`6d8e~1s#*^Pj4Igvjt-s zcp)zzW6m?(Av6$=g6}fp#IYOxg5A(I*#IglrZo0<&9;y^)@ZS-o-@_lk-Z-j=KcG; z`fp$KFJSYJ&=!)AcjZWL0-CYp#8185>YKp|fz*2wd#FsUY>nu6esaKCLrhfrjW1g2 zuHGY}Mq<{1a(XaebQ!hTdtBh}iy{@cei^)jNwRCOvAaP)#wu<}GBcHgbB&#Xy#L?g z`ljv-Tb<}cSZY8uPm5Q(tB$uZj+iA;`uN@H#Dlzt$mSE;kz&4==?BjV6v!ij?Fv@_ zRih;mB*!m4>594G@4a+<;ARKsoQfd)RYBm>T71ZrPLi#j$0JS>xCE1Rn8-;zyw^ca zZCVobLCf*qGUsk3rD!%!C8=4_)|t9C*oC~Ra~@w^E-F33kr?q|l6{UNMv@erwotth zAZn^$sMoQlf^Wsh~875rEO3mK_qEGDkN5czo);IeV{6v0X#W^*jX$s+! zYhj1mnG~UmM!M=oYIV}Mnf!&Ter;PK*C>7=%JTEdh^fTkwNzo1POk5SH?`QH9GQe) zuao^s!Z-T>e50hfjud0~5qr2)ZIYZqe2|u4iw0ioVhrIf2T3RO>!mw{+^QR9gb}72 zYFS(&JIXy{?eV-wgFMx#&=GI@)$-~*H+HsJXSo;c?Tcxz=QDlwHyD$kwB{{yXC+i| zX^v+>2~~o)#Q`Dqy?MAfW`A3w9ac3xA45FEH8F6)iL)Oezh6jGHSAY3>**NQgL069 zeh-qt1chfCmTb_K>^nT=$rQ~*1NY~JNx5bs`!M#D?EgGRrzDt|`9=1U5{Wj3%V@F` z&{&z2{q(Kv$L17hY^Bz(r;qch(7~AJVGc}0(=|R*)>oUcOE9W?eZ7h z|F@P)Sr;gHNNqV9wxc=EFV%6D)`BiR;CLN_8yrY(tUjOjW2f)u^D6587z8fA6{Cm7 z_8<&5a+Jw_uu~f^P>|bf!*47PwXJs%DNaXM`=kud^7FTxW#kFW_=opg4+uvH zp)A-eZIyyaEsp9uMVH%T2#^X4PGaFHmwiNjfI_cNYCGa}VSHN;{yhYjG{dQYwJsZz zipF7#LX=|CJ>(n;m{4VkTLez@*M#Qo3ncCF;u4?-$|IpkdR?!*mJaL^g`EeeQ$LjH z-+0@#b`ZU%702TZsqW68Dl?F1j3LZHKu^VWaSli0(=lr=C9f-I(vgYP_`liu(L{8{ z0t-d6-n)48g)HPGcws>MJsU(QQ+IP(eAmT+1b#8+(Rs*SH?_DbM`&HQeD6`3@2qDs z!2*P+%)ROP{k)UO+f|d7W?{n&%}F&%=VSAr1wMprL@T9rOro9Wbs->Iz2iv~a_{gF z^^-#zSFM~d+dao)B9WAj+rx`rSjwY@6>En>#mKkpBtNFteBK9_zf)D}3SIoi0-ei&{*VmMy^VtcylFqu0{us7 z^HrGCD`f0NgB!2JJx5{V-4s4)5Ok-lofy3(cAid#zb#Mx&*RQ-0LX9qXn!AV|Lrrq zbNIjICUu1IWi7|W6<7X3id%=QQ~b4aF}$|j+bxedLxxJYvC#yr5(n+s`GS=p#19K% zWk7u3QMHz8X2fU)Eq`uhu<*QK`*sTb*y`R`&q}yK^ZLVHeadJ9W(b(}RZF8QVd{>e@h#O&IL&C&cIrR z{8e)5={3Yu1;BcKKtGRc`?jAP%uNmSm1>oVh4Q#6X%Nnj5Tr~yk`Ch>U|ucR!2r8W zL_M8+HKcSTi8a|W{tErE@i70$S{TAlXI%*Z5yzI)K z4jv5VX2YkkIki-b-#g3y5#-u^L+xN$bK_nigxW!gU6g9io=h#JV;KOSfeOEp$@#v+ zB`J@t@(C`ZYrZ<_)&lI^Mue{l&Nf~H&f@^9fhtRLP6~j%Jc*wp=zkBcig&p3`zxdc z+WC({xtKJis3TS~fh&fm^DCEuo|V)@5f$_VEhsB+H?TLb7nNs|>tytx=v{%8^>ZOa zMSl|+mg|T~BbMnX;AZ}5qWdv;#!%)PfHPL0N^~k{NZ!Kn1h!om>jsI}+nw9ACRlCI{s?lfD3yx-rESON zNZmn9`=We8osE(s*HTtvM+c{>_R}s=I?&)P8sMJG-&S$Gyf!Oplp24I_E23uaJoyl zql?6s!)hp)L9o=yjQ$3{^XnRD%W=L%QEwDtk^I>Lxo!qgyz?g<#5(2H_JxoTyG$S= zftZXb-o03EX0=%5RYu>G)5wfd4{Iz_U#=IJE|4X(%}6iw8?3hJiCJBoMg7CN5x@W{&jUku_cZSlqcltoZ$9A4IggmVQI5~zF5ebdOei`5{^Q}Mj+7v;EE{t= zv&;^Nf&5Rn2J{lMke7062oC4&s)bZDVeecN3UkgxXCiz$D!)Zzzr%&ghOYbxE^0h$ zdn{_jn=+6d9c1c1t&-SWCz{bY;?3+8o#Uee;?(CTVbq0Br2pi0+}_L zdqHzeE`G4K4A52RX3YFUc6jA|&qj&Z0DMw>FBZTF1fXr!Zy@}C^Zj3NmDAl5Mu@_C zrQ}Uq>XY$0iT-%nY0g)nS!o*&#O(?;9f~+S_Lp3-+o58Be#J(a0Iee{Kg6|SvZ;EB zi%B5|h^IOVDv!WbN?DV^)ONa6kpO7n0BcA`i%!?6>GB|b_$Zqet+i|~tD7KwLjtr9 z5R#DQ*)N{e^)_@#Ph)x9;p>F3^Z&CHm}#ejDGdUy_j~MFECb1L0&0M? zb|cYu3SNqza#gPdN-H9&Z#Pog2BTjJ*sY)q;5s3V>U%yzQ9;~F)uEPO__LrUeSX9> zc89pSPmt_R!jHR-#V6jn{1RawGoshFGG1XJ+kOJ~N4J;_)PNtVC46%a!Y+SaUyBZ% zog?Jp6$gN>c$ky8Ep+I*~G^nxIraj7s72?>v;epMDc- zG;JAyP>0o&Ro5JDKo3d%3-}{W4V!Xwg;wp>dV$~-*jRSo^IvddQK|HqEhAT91xbWE zK)K0XJ;D}EnUSpP-k+2^LBvnm%~Qq^;|E)X+TIpUn4rZr*y5}58X|s|`kKXz3}W>Z z&PDSNH~o+AFFfj%M+BkS z7fe_rog@EA5YAjqvk4mWMPI?#|2|ehnrO4TycIE z$ExK{uubSC_q6WDXnRH^%c=OI0*DaDCKTg_u_~YBzc0Q1?KAzQHi;A&_#pVW)iG|R zMNmqu62BQn3OY#6+bK4)10SW+fiXO2Pz0fPd{EP6J z?(92SCq(?cWmqXOvTnJ~*Z#TB`&Sblhyc7jV$Mu7Z4=4W(M=s9WHM$f(@%u- zt~Xup9d(%W2kf%6ON8ah5M}`1?=`oRx`a!+ho+P+&tV3d^- z;UuO}tM7}}CMH6D_~FAu!s^7t>R8~PbO~?a=^D>uW$xz~c|m3CGb<$+_03YCW$P7{ z^rC(R4pZ?c;j3qmx0j`CTET_Se)a9+1MY1PYGAh$#CwDa3Eyq4amA+|=b0x$2vvgp z-=$Yk7SkV1eG2Y|xEMt+`7A3`r-bO#^%bFkVbVU#B0PKH%fm1_K0NYtbEU(z^$xh(EO(7 zJ;<5^^)4cvC+LcwL`1amvR?#p29yj_fo5heAjXst82nk@HgC!{6R51H?(@>o|DK4D zy^BadC(vDfeTg65W2=A~PmTGgfv-yY%Ui;}^uo6?+tta`l;NP`Zunnk>#%gU3$y57 z>V$vp!JOZdPzW07pS;4VvG^+ZcWF=HEY{6c3AmmiA-`6VsY9q|;K)i*pN?Yxed+aY zzJC{y|Gln>^`kY5?G-Iajbin-28wfI(k3#f^I!Aa#itpQ8wsddg~!~M{6oKt(L75i z5sO!cX3}w&gn_kt`ppcX-yH8@+DNw~H&XJbQ~DRvUI(r6J{h(m^@*MjS+2KZgG+bk z`+1-$nJq`{Si4ZNVD33_Imq^xN|%9MQFQj>M#pQ4bzHz>*sTyjoBi32jF^or6)RS_^(hR$__o!C`mi`=Mj8^+v3!jxe@04hxI^_J;V=RhfRDM<4eT5vz{EqfE)BrWX;*cot)Vfw6rI zMw2$%c>jWxva<1~qLAEdJ1_f~V3qj{yq-K)X#mG7SEkNf*%tIW`Th>vsz6S6z|-#R zeeXBxLemOHKWAeT-R$U?<1Xqh2>c?Dt-4%q)?~(nT&s`sfU3;mROTe+QaIm23Om1q zxf2vBq>o&zD{?W(40U18dypf3fio`rT17p z7l>nhq6&+&a{3(D&C*N9?2vV$Izp(h*gY5p#s-pIDFmfY0`Z;0m>G11Prs;nd#Yk{ zGXvuI|EN2s@I1F@ZO69Vn2pmUjcwaTW2dnj+qP}nZfvV@8aw^J?tia!W$lw5_k6r3 z+wK8e;4}KaY*>|n?nx}mMR0$dmUK* zwHbtNsfSev9p`#Ze$Y%MT^+)4X@Q$rz%l-iQ^f$cBi0Vv9K@=jKhlDL)>Yv zP^5~IDPaD=c{KW?tM;YP+(OWymmA*Cxx`8p$58}c-hWN6KmDeEig{21Q$p_&9>?65 z29FmpNPVd5{Gh({&HIt~dZ|P%=McYhdM__$4)yS#!N3OMY(GFxHju}HPK)Pn`|9Y^ zGaiZHeyph+d#ZS=ehFr5L)baK-0uY&y0ycG!DY-(azVi`pjW+9j5xWXQTW z5VJ#Ai781S4kS(r)0*DbG^Y%VY@&X2xCd!79sBzJ=x!`8`o;xJ`!_S21KF536fy?# z=Pp}U#Q}o$+h8$rQ?J?I9IEi8Q~YA6YhSzdSIxXzDB47VJQ}#SFkp=X)*EQ7WBh$# z@x3qQQQcq_1TQKgw(gZx>J|?}C}Cq%X4RMy=85F5gn8ZzfJ;9c>;2l=#zSRHgD@hw z{K(}jU>|0?5w^6brIrq@Dr2%xU!!~NuZC*^09QcinD%x{YJ2a)bL7)x{u`~+dnlFgn~o?T*o$RU9bVTsPe88lWB zKyAD2=O14xGGmhYU!V9A(bi>J5>0{GYaJfmxNnYARiAA1>7{yZt9KSOmIj-L`L;DR zs`0N{LT%jY*az?|aH{E*TOqgAK0U)NlKwK5=FWcEy|!G$Z02<9+%>%5_2Ndswl}}) zxWIUj?iFUnV+oB$!6i!9P|XGArV^(;stYLX#@ebQK*}*sr&&h0*f6{q+G0iYcBA;w z>78={`5c(?8!pFl0<`KRK>@h$0fnV79w8W}_`}$9cBhYb_Ojciq2m7?mfejJs{d79 zE}ydjF66!fREulyJJ4DSavNF6Sd|$W+dTj+mLM#-cf53Q$9lT(@hcZ=7;G%VcE{z7 ziafOI`Xc0UN}!jfE$zJv{rl=xHUO<+oQ;0bDIE(hcSXNvp~**1!#1;P)Gyi({+QVj zx<)dLenBD0b8xFKI5LnXD>KnZiKQrBFV51O{8mwmki7fR)kKtJ8p+@l(^p2PUFaMi zHPX3~&}8;f6pc^TcOixTty_P}{Xb|`j*Z=i8yVOe`6Ah&ZsqkM9!6t%i3}_#XWpC| zWq`-GJiK6_;-QLKz>lic@!{JeGG^FfUOVQD@U2cVN7vRy*1#Vr1)|?OyCgbnC=zlF zaQoDpdrU(ynJg!6@WRvp(8~P<6yB}BMV?}mR55>usqJHHT|Tb z=-D52rW+5LOZ;$}BjAqwg%Pt=n*9+5a=on8t$*i%`ol+SUTq*c?m=K{%!GdPqn}OR z?hrTtb<2dkXGb5cDxN@OK*dl?NeHcIFv);7sQ^B;Bms+TTZl3r;*{mwLS{$&RoG9e z0;)~dEEcG@p;NP9*~$Hu2$l$>cejrZat$()6z45Dzlk9c&cUxIC9}r*O$%`|{x@3R zo)i)fqM!V>*J2lEBoh=PHBQrHg*4q;AUuUlm|ul|Hza&tw}g3G@A3j`k%6Y>6E5f_ zhBUp_O;_|3c|+}*2J60lk_W&AX^r*%u2NVU*Jo^jI>tVFqef(=nLQq~H^I z3N`#A3Wv!NRnrTpg#61SBxGu=SxbF&eC$Vs5?}veP`Zy0e?;p2opl6sw#g_ltGT`+}+skY&}Vx!#?n>q)H^{vTDiVGFyI zB>3BE(NY9pw9EWqyQD)rA1pED!W*gsIN8<@qfgZAP8AlY{VMORcUnh#@;s?T$Ld&g zjILq7u5zvXpvtX_(5=kl;TJFEoJ9|$!?Dq*aZbd>o7A$4@gL}as$hC)l+uM53L`D| ztb%RjfZM1^$(I%H5s-u8Fqb8S1hqH-qP6z=RrDzxjN^P!1$}&NK>-00!m{jHYcOo+ zhLWKb_))erf+9`+B@?=B5~3;2HJAKg$Sfq!Rv`TsV`O^FBaN+f_ChHmz=Rmzz}U=MnogHtvdI4E!EH~@cH}Xkv?;y4 zrbq=T>K+^HsA3EnLWy_$T8t8zvrH&NCebex$!zL_`205_~<&p+T}Fe_fZ0T0$1!P8;@3cs3J8yzfv?LZb)HN_07+0*J>j}*)iExBW zEOqW5Y(ZmE0MOguXvk4jegP9uY#X)T^Uks;3Q1GSP$3x*uSiD*3t z&rFk#e{}$!0SYlIgKKi^1o`s$^^AmMmChHH*|H@+WjRIhE}(|nA9w7oh=~I^4)83I zCEdb%rd6tBQbOf2E_pXyODDN-X!kGh9~tR84%-!RH&B*%?xJ)QR!sEZCD~oDi5F!>2I639bEv>U*n6G8aaFZ)l8o`bk}C2oZa`W$xus+ z@l6F3Y^47#^9>w~2}Q0YrShu@-OZ1O0%m)u%McnV=Tbhk%k|+^rSn;OIdV>1qUg~Ubl^5PFRXSRCp@)uFtt?L)@B4s3F5Rt;VP8G1@EJShOjrH4@0Gi_Ycz>&6p z@T?K>SQdBmdBgqOPknHP<{RE=`~yx^0Zb|sWBa-1XLZ2_R}&tbE4~*0ioyqzoP_9d z+p*1dP6#E~&d6_>R~c6}>fCSsst}+t1Cl`|r?-AD#Smssz{btRgz9F;YLch?v8@zg z*dC>CU13TIeEtQp zTgaws?9S1T?%AEXUAvbOgjCWiKZVdwfx{zcJTpM|(UN!9rVRK3c85vA(+6y}MVL6L zxFE6$!iGGGkw|MxPV3878V6m)aeU{v;`#pMnvS?yG}f(~f3#se<)X51L?+WxDIT`i4kLSADQ@+ zUe~o?L~+-UoS95M;i(`Pz@ZNbmejj10-e?$y*#7YB(Te-WypAJz5+uSePq%OKwV^Y zZMKHGe;4}KarogBz+vbjP24pZsaK+(tMW$;59AIZh5EFNB#^C7A$H?Mj=NLVH)g*O zqr_Eow&M$5CvPx2mp^KtNb*{*h7^#s1n{( zq?roL0Hcg`+4ApV9#BFg@@N$lmKd>{xj2lICZ2qbI0ALWzn7rEc)%Ij)7?*MfMokd z(yr8Wj_jZ=7QueK@4QwzX(kkn4s*=NzIRt5aDgKgfPoJ;^tcdwe}!c_?#H?)%z1Fv z{B#jA3+56mZovJ_<{|%)KF-UHktL?F5z&J{-(;J@J&|tH6L$(IfatEXJ2GY3p>5}F z)&J~mq;|&OrQ5yRk_!pAVW!swb9idM?yIv6KKK^YRT{BOqiGfg(2XH&Kf%WoU7vYA zUKA)U8}qqJ`A6ms9A@P0{)!ok-q_Khv*e_6^&yWdK3G>9w}3poQ-eFC_NeJwp7c?` z_B37i-gm#f*+p$GvHS?n9rk47lf2Q~#cjpP)1X zvn2(4RS0S&VPkowi5%B6}Q(OP;$kAJL@8v$%*~p zEKelUMZ-uDh!;#R%icvRH)3xXhLq-C$L>$L2S~{OT(7R6WTlTiPrUoiT4K0aKrP#P zF$@@xsYtVj#!pv%Dy4dV-ww3{Kl~=)8iRV9EsG5Pnb0Ee;aDPm{#Fn-Hww5nF3|TC zd}Pg)1%6~(UM(vh!qrSJd^NO+l>=-n{RL2@y2DwRd5rZy32_#d#i*X6B= zeqr5Pz}s0|8}{n0tJkZ>=hr|i5#fFcAu1ha0Dtpx6X1I(7HRNn=vFVM2ZaF2q#8wJ z_SED;dnmz)SXh)iEWpbKOgGW_S_+GADHki?52LE53YO&yonaWxJn~WpvdZixrRt#h z;%lNw3WUV`G3@{-G~o6goi7ACM8fkfE_D_bG=i|wR2qn(-RW&y{yLoo$OBOs3{B^XsKM$XWQQ4mCUI0q zA%~Q74G|SgewT6@JJUJTn6Fz`c2evtpg;$BgwLB@(B`yOOp7GFjpc^_ce9z^@tmM&vxsv6%PC;xqL#RA}}d8)72GCu7(gBOYN!1SlV_NEg~IU$e|rM0o( z8<5?j2?r6OJl(`RHR;;~YeBHXoa5Aj+QOKqto(9>Y6=}V=tzEDIEH+}d5`|&NGzz} z;8Xp0aXLj!5%Wj09GBLU#~@HVzl1BRvqdrH$BkFn&#A=$Y#`Q3dnMRtxLucn+z$q0|& z7O!%Br(#eNN;`;XRWNx>4Y;Is6DO$Bnsi8gFJ}Zi!qR>2m?^(JY~VWlC(bU+d@Y4q zNp=$zsGibP&`-O7)qU0?txp(Qy0*7f+xK70*5y?b)sL2a&K|Adl?0Y~5ju9jFCucj zSSMb~?w{#Dj=11jP8M2vX65#xGkK(PdV|?xN(Yv59X*cSziEBSy zW4E;5x4J=sV|iz41VD%_lL+cDm&IHQS>Bk1GUz?eUE*LKqhb2RUVY@l9e-M{`3=`< z0G_lYWLYWA{S)0|LqWmfG$&mu*f(V$#gfXJQcG6A_3HoF;SpU_yHw%aQ0|6ty{Y_y52Z@-Y?9E$X!!Gq*xl3ykBl5d*8GIje8T=b1?b*>Ls?k53?< zqWOp1J!iEfWr?q>F{aj!>W)17)4?*3iB9}F3_dFq@Bn$)Z(^~J^0XBgg;gvBv93NtN|Wo(C@GANv~=k`FbsaBaU|g1;B0K~`xzf{ z5wQf8<%P!4(voM>$IWvQu?7O+V_DCmPqeXw&WO&erchsf*1g)FVcdi2qDK)Q!*(_{ z)S;6K`ojZJ?e48+vMtB3){JZDcsPoXS#)JFuhMC&^u=)J7E|JF=m*87e=08>sB#`_ z?Mm7x3awJJ?IJ|V3@W8~k!{+@_ESvbaf_&ck=u+zW&|mj?zEa-H2IEfgI+FF)DyAb zM7*TZmvu>0k_saKN-43$(Iw;nCOUe#tB&c^C^uXtIvU;NjKy#n32yq}`qNsp)cjO| zgV((V&O~sy%7K0Zo$|T`V)-%sht#<5G@9Piz>y0rKR1D{@y$nh!M!?!5(+U^VQh6n zMSf8bEGaWN2iysNBWSqz=&tIP3vPMvmDX#M>y%idvqGUvJz`??+u+dnqt4QB*vD$E zLf!VBEXgKm4{OXWf`Wh$v~K(K_QV~VJxBISt;w9e41gVOgTm(HcnxSGnUHnBNxT&% zo5{k3_Qh<_tS=tc=M;niS#^EI7bV;p`%+|zu&o=ul@gik#mF4U4SW9v(Y29q{4sk| z|LsHE%V)Q*f~{vj+mmx2WOGD}?U)q;l4c}+FMe@EtnE>}Sp?i`#FJUw;MZ&R@<-j= zy`Hb1gWL$6AL0LZ@JyKX$lH z!_TZvgxb2`g->K8JUVR~=!2QIcd=}QwPM%S98}f-5g97L5__i%?oKtkC{vo>wH$)P z4N)2Qr65CEXz{Cx9|H&_j#@7p--Z5tBEk+3k(|b*%L_a{**=S&vPVhugZx{e|CIZGL?k|oHP#;bn1*)7!se4`umrw7DHi?t z17B9X(&rlLuH2cNKnS6?p>67)+onYQmcV1;Vti=jbO* zf#ik_6K)hE1JKBdN%u2VO%_Q_w1x7P(SS41ao zgP%jF$TJ93qINRLRbYrX_I7FH2h|AD&$hRXnRMTnvbHs-Vd(S)W7gf^E>7`vJ55Qx z4==YEVsaGk_fzeD`^8BigN!I4v;R1)Z~E$$cfXDlAfxjWR{rATLs3Z&$V7=!@pxD_ z7t@DsoA}tNxiGWu=_w6g`h&_%KVDOOAiWS!pPdQv3^?1XJ=GM7#2wYKt<`>^q!H#h z**wJGEg1$At$k6`%d^UglhI&3v)tu31M@$p=mc^ER<8Gtg&hC17ZzYC9$V!1K>xay zhru^|Eab3N0Yv@fQXpF-&%r?PxS8Y=j!qmKzXJNRO3%hx-JD7^1PXyO4HyXrv-EJ6 z$=o`J)b9$t!9qxqJg9Xd)20=#RyKgw@wMirwh;;6a{g!E616V#|9wKQ3S&=3NzsPN zE)Bhb%pfPVrTE$Q;j1QE*0izSfcNUxwtbR@(FiG1EmyxYQ3rJfU%L6 zQ6eupg4;n5unH|{c^CTk!37I|s{*NlV!?rJZ5WZGy4cMJBXDF%l&^RQSRLMT==;DT z1(3LY8bOr3J#ss6yh43Y6AY?|Q)L#bp*#H5JR9iUn@QF&jX{o&YASSDlt86Km?cL7 z`lsDPGz)oYSd7qcmVa&HKjr=(xSEo3ZnM|IOIKV_5{`RfWTo z1leX;ECa}-$+x|7bzXN8Q?N|EIF+Vtn0l8ztbE3|S7@(j3gW-T11(OFl-*30tM9ji zLPBwSm!z5Af^2YJ*!gK4%dgi_HrJrh7NOX4Nb)n#g19*D zRaLu%Q%Vc1?#SJ?rpL)lIEO5{fz5@o+oQPBscYhcL5u!vq`{*TnxQBe`uxw<{Rk9nmDlar$h zurqyl(Z4#n8V}`sY9+)*E5z5Uq+96kMN*D$s(o)4909^otne8p4br9IwVd?(#!vwT z;B{g1Gw1oIWdtQvVmIn};EoKL%u+l@VY}km_IgnW9}p&u+5{aTJ*x4G7!zEC>M{^Q zISrZbFcN8|xr&Pf!mY!D3pHJU~{&gG@YydcnRP)3f zCVbT5KfH?9hHHF@Or8s!#tSJzmlq5vsWlfZQOfH+YekS0a&V`+EPeikYVAmk2rQl! zoamC5t{I@R6dbc$XJ$bRt*HbNq0=bpa@ns9Tw62?}f)d{U!j1|I^CC z|MO37C98Nx2FC1k3(T>q1w^!V>EaxQY`(^?Xf?U5$E7yt>rPN0_Q9DMa=B3=*vV%& z1pIkLbRq}%IRV<_Y-*aUW0*`_)0o{59;tIqJz1o8-t~P2M$swu#Me2By*cK@Z#NSkD%0>Td4f8COzw`9=?gwd7Q*K^F8Fdff9R zy{1ZsloRjC*f^;%f|Llp6$Y$0^-Rn+E8_gkFZ>kXvU)#36nbzD`@Tjz1bwptl3Q+Y zBv(8ho!aC{DZR_yuQ(-GmP`Ys)F#o$=7%R4Lja~uvuaIh)&*4^^tq%UW|D#TxkXIy zfcvPepEIDR+NV3+Jlh+zK=}M!VyvIN>oo>mrI-N6h zUyZ+&V-rI8?;BrF7JY`FWVgL1!|KE;B}TCL%0Z_Q*j%_=m9|_?B>RB7#Bf}ESiyisL3E7BY40`Hj^2(V9NGLS~E zH^f_5liIb%iczZEJ}}Zz#>n#L5n91DFA&EFB-x3|@qgEkf6DzoB`yr2L*08A2v{b$ z860aN-u)+^hu~Zs<;y8S7-WHiTR-Y{%Gq-?7?32eib1fz+BvFB)q_DAzCl@SftB|i z{1tcV-3NkWjB%$E;2CP<8yRSCE$Jo3qE^e#{nu_^#su@eI~B8#+0%Pt8K8}(nB z(g;U{XlN*^Dv)7on(`K6E9uS`%Ck;#k|jQuRGuu@MIwn&q8wLl%@$u5bBuwG7b zw8J=L6w(}jH=s&~=7I)`ag%~U#Z;A`zAO}XFzie@SF z5Me%p*wyBGx(`W4r4y_QuxOGnV0dahE!zc7&&mN2w%XEd=8dvE+fL$U&dLc@E;O(1|$3T*M>Rirrr;0b>VE ziEyk~ziAxml6AAemW#OlMoTdi*cjJSJnADgzUlICdwF^Xp)`F4x`$<+vdiLTEyn-7 zs9(FP``LWz(|PZkKZabq3WY#>rA<-YqXes?eA_Xy>ni{*L<%gScf^$SxI4zjc0*X| zosSN>ZsvUb&`P-7>xp7ud1jwN1IP0&^zVZ!0037@bOVU~k~^w4Fk$2((hqy>5I9MvfYw z0whLQa{5yjIfiI1NNURZUvF`bec+e=b$I@i`+wl_Yd+)vanweH{6RP9)H%MPMLIrN zd#7v>kvNb(dh7x&%k9;h@EyK=%qxl{c34jisa+%r>iko}Gyjl=?2!~Hr5riL9 zha)b}GUOIftO^cob% z+u2)@YS}nz=^p5`H@v*~L4rU;^dMk1*v*w&I5NUf*`!pp0%( zR(eQRH#yJm+r^DV=6Z>n*s;2Q-fvFtpX8$Ou>KZerV@+XNDiLvZa(!$*3!DySio(z z7rCAplGS5Ml`lZ|OBNL*f4jCWL#Y)C(2vnysi}n_ zo}j=B=Hk^lH$OsvNjGB(_q>v_Hu*CjlE3<|7N1oT^O-4?-gRVTa}Q^e!fe4Kzyo^! zn!DL7qio(sp&JAfBe5<$d|z&@IO+K&;Aya0GfJ;lOj9`7h2FLCut2yrM>EW4Kb*S^ zBIi+~tF3JkC9)4s$)H9)PRw70zp1GJO?L1YSh1yM)N#a~{_0Ld@EZ8-@Bo8Xi)Lzo ziMmqm1;nbANO2n))Q4-ns>!6S98kGXK>Y9V=2q-W28VJ{Vfs8)sygx_s!-Vqgxa7@ zdAEX&XK}PlYTYqKYuDxw`(t&bop7PKcqK4wBdusaA? z@0*F+aZ)Si1T>Hv9}nzlhM{Xpu*Ut=0V8-J7og)C!l)?zt3^Z=AR>k1)b>$)GR>Ln zFrFza-|_Ok%wsfjN4isufaC%bUCFGy1aiuLhb)ujpbwbXRuO`CVg%-bHa7YC!VUxu z3sQoDXBwzMZK`O=V%Fn9Quy^jFsenNHK5uy;Rko`3JcS}o6tYy9v~wB^Q5($D!-}Y z#a~jSkLtF{M>KyToZsRi1+}iKYw84Rt2BkF!;Blf-}KBIvu6oc2I35LyxAP*(FZc1p@+l{NVX?7jQY>?sY z0ckjP4oLS>BrGlWEyun!)hha19c*^~@J=(mK^}+ZIR~LD#CY1^8EPmYFv8PaH!61i zd|JDjAFr3HgcSj;%X&Ya*~h3z=7{`~ay7ktu3$pYOIP*Rjmc|yU@R{m{S8dOx!ZFEO$gqk*`b+aR9TJvVQoXqWZExdmQrB>jMuN zJ7|D!OsUwTjZ5)V(<~jPHTlNrFu) zsT9i1bE2iR%)^4i^uH_RKmDeEig}|%KPM2U&%(q{jtOoeedp}_+ex_qjvZ9_+&a$^ z?`0PCA;7}7M0V7u(QCpneHSKs_LJv@gZ2 z9h?&N36}iE6ZQ~*Uck?6?H=lmwzu7|2tw3!;G|_wf2-_D2*K$!&rq=|hHn;A>G>WJE z{?eTbo_~wnpK||Ckt>X%YpOd2hl*E`;hXk5;em8({gms^M|q|p;qZYUj5eQl`1MZ6 zVxZ1i<&fU5zZr`73x^CxfDSg3k)uxi*a@jzaXjh34oezIE5@$Q)e5Wn@_>tC8NZI| z*B|;;0zLq^f^+w{!BO0XgX~=S>~Lv>L19g_OoPf z3;UuUQVH|sVo;P>{K7J5O%oar+z!{vHEpQ}UPl>koYel^o5 znDtCRP;+-1+o?E*sIPNNY&*j&C>&ZhX)T)oBfJYgcx_pzZ}h+}&KGkQz*N-OLd6cz zd$%N8u&pd7QVSG>$#dYlJaR)c-E8fuK9|MyrMqvE`xZx}mX~Rw9htQ7eP|hD(ZyYTd->gvzsT zR#(npKI6rrvr-MtLcGV_8roWfgZejI?=RZm!ym`xKlSS=OWxc~UYO(T4BwCn7{sI} zb|*|rJYBzU0nGfJ+t&+-L~+fKPNrs3;wvgH^{?uIIRuGWH*OoY-p>FIRZ6h<-obT< zcIP8pzPGK!D2H|BzUeFGTZPn4pf7&T(%NbV)b+l_!{5i@6oA8IG;Pn&Pr$Erf$^4{ z;Sj%`;CR_m5NC`mzKF$0I7dyM4)4Hi_`~}32$sULI4P5Qk*8o5c85;4KHPpQy7)fUnKjII>8erB?bmsN z#1*;EyW!d~l&pCEh*!hLedDgY4x?*?yg3@=+#3OV(7t40iMnL8T#^ir4jLOyX~AK! z+AZtE;<bF=*h6JS&;!#?~)O zn@>kc<{~4U2b*w<2T+(bu@;9h0j^-sHKz9cbVAvSL)+hy{ z2W?Ky6sz@eoLmIT%C$TORF;C1)Hhkc#`^5M{djfexnqwJp4>>kXEpZ^r z8Wu%l;OAnKMlaQQZ$sFS8T^R^7CX|hYk|00jR!gwXbq9G;jKJ5y<=^;H{y$*Jf`MB z=#Sn~eJ8H{zHg%in;J+)4zS02jhyBC`q_I3JXWv?m3Rvq2t|`b@nojujd61-5tbYs zA{`~qI;Y*L=@rWbw>qGUs^*n)$`0m+m3;hK5!;!!T96o08g7c%Ye-rx)Q-xyqem=( zo=4ue5DZph$8Er-C2J(o?^`mBsJ4wgCEUhsY5PR_&B+PIYaOb1#no`vy*b~p@R=Cm za_RSuH8;@=lFw`B<%}_&5@cJVzoHwQC|XUsdJps^7jvf?=6~MSIJRRx($>xxnlo`W4F%I;aC{EeAbR zAQUpwDr3CazuD#-sJP*$wKa*{oD{}!`&0Enq_VOiD4k1orjo0?MwS0oY2)8j@}F}5 zkAOtWDzRLQAu1h<%E(~k0ufWV^L$|t!SJyH8nWD*T9h*Z-50{(OkWCaj^PvI2Li14z$s`{dVVf0bolA5o zyco5aO{xN`)NQ z>2UwX`=I}8dI*Zszf#viyu4*u#BSu?7twisIT8jBas?Wf&AKAkLR9BaxT#2O;8&;k z6)E|Y(dH2BH{a6u8fs$6AbQ$B@jQ&?XYO?~HVwUJ4m!Y{AW5_~g$1H*vzbsk)`>G; z*BR8;s31WH8X*<=e@{Nz`jPObu>K|om=CjWvi z&Q&Gr1)gNfzSbUKTFV&Vu4m_`b$`n+qzcA2kjxnw5Dz`yhE+?lK*d7tWRysQ&^yU- zUXkA(?)&v-k__KqWmBpk#$_$*3L|S6#e&v@>`d6Xib70O6I=YdIu|h@7;vuOIk@%0c?47>`Hv zq=D-__ag>IJ5Oiew6c92?A{MGm(}Lq?CSz$A#f3$%UC{OB3hWNiVz96Q{>zTZOPD| zuq5)Fyv4W;)r4G&D-dm5r=3OoWcroVbwHDaf0%`8L#KqV0%|@C=<+u-WbT(P0iV}RG1UhF8b|>ODXK8D3>hK!dDrRqy~De;up!R zG#@OCn-f;n?2SS-y((aBa(boDH=wu97nvKFIG-uqy-@WYk;bg3e(g?9^QXYf+&xNG zBj%agf#R2Zuf_z|M5PA0`>rIOxFz`(1vhJq@$9|kewu5`Mn&1zvv;ZF!B*%}$32S! zE9AM<+4Z|RhXev`MA&}f*KG=?d%mCAYz<=gn&#nev5)$zSJT^H9~p}5pE1B-0J}>T3bM>YM?U{JQgaEcA0hg`Ez{lSs!>WDWY>>su?b~uFss400FVdN#@7xQ-ZB;GN6 z7y9>!h&Vt*;-*HbH2e6bWv#{eApHjSHC7b1E6bZaZP^STb|wSoL>dY{<|xvNBJ>FN zriQ<5z+U)(0ShQTXxOBoZ!4PZQyO04-UW>PX@BYM_FcS-cW`FkB4ol>oJJ@?ez z|0^PY%Kbkg5+S2HBIXhK-1F7ws~>w4*F)I;EBol)Wlwkz2J4j5IZQZmQNg(WJxDJp zRFwKw0y1yE`mEe9WqW__OC}8I-3H!$f_$yzX$Bqi5haJ>rn?(5(}y2zHDk8!9|^-^ zp?-@WPw;iuJc3Z+>s?fuwE*%E=aYknovB22EEG%Kuyc~RzB(;G#rE<%LlSXM)?5kcn zJ;edbZ!y(N5Fhxi4mV!$6dTx}5dB^EJ?R$(j$MpUQ8{b$)Sz2UK2Syz<)CPrRx#-& zw3Ed%ePcC}ESKzQz@p{<$js0|DgAlm<_?2|Us9?sdO||4HBLh4Yguhj`(QscT!5a4 z}ZxA+9$;i8Zq4VSuUK*osch3_aLaY z4JBk$^qkCcAG#-l!05Bd&TI6%*RI{vWI`8lOd5i{u)*>1gYLxfR9UwmJQDgEl+kPA zw-Q_=sG#e~l2iSDoiP^S;6J0m2X3V`iU>I$$c5Lq&Z6=j)za*L`GI`6QA@#W2ZrFI zz_zxmF`lWls1CuAk8N!rF(`A{Zc|h2f(>}QYsP!b?z;Is$v9tZ&sc`1w+$?xC%Y;d zq-r?q68vF3JLf^#V@Z1SMWsdGPO7g0ZcXuO{=}G5! z+71smZTkJ@Zt2Gu)1Z_uUh$WRbhK)G(QMe*C+#o$0qDqIu(mQ#2>Zo zeVWrlg9B_Km021WJ*~vVjYxsi)YOJ>T8GV~Cwn8KWw1cp5wMdFRn_GZNe)Fq9E9@Y z3}U*&%!ricnDG)k(#$?0`V-Chjswv7|M~=f%Kbkg5)7DYmZNk=TTdrz)_{+;nu-~I zsES3)+sGJEeMlD0PA~Cf>z_3i4}~fY0#?lE4@VE%eg=C_##jN0#zum?8-&zq|0Qp= zETnJlt;1uEDyfWY2Pw{v@5f6G1g9_*$!`%!Wne2A)^G!uE*%xM`ZZU5T<`!sdb!zw9FJVdB@p1>5dHX1ms)ASO}oIJ!bP z@YbO|{YEJx1cH)Qe^j`s2JKfHVBrXdun79x@(;x`E88;ila`&yhd6CYAB4zN;=S%yZThGn_V^BnoMMp};f zX-=&MUenuqHHNgX$}4(Hw)2WPkj3_R*f_K_In_q;AZan}#EMfVOXmoU`@gC3G2 zh_3gv(EEjaF4dJEYNPQJIii>`z`|2Jd>GpFJ8q`sN7;T(f7us$yHV568BU$YfPG4- z1KB!0)-sr=+U_Eq@v5DX_V}klHiK={hsW$I-q9JcMV{B^N;%BUU6kBV7@a2Uj%I_i zYY0OZND`t%7H(()v^TvF?1h!EVxG4yVcGBao~-A)`o@}jhDtG0czv4S6RC zMM0pJqyFCZX+{O<-E)}ecHb0V#5dp88V2gag)P&Y0eWMHg2nmn3#Pc#^}e)D^h8bN zO&Z~e^!oOEUE`pfB^OAy204`snu^m(ki}6sEJM_E3=)MJV z^e~LBxa=+N{iFL&fs!qhFv8RwAD@-}wH5yKn*e(AKZoGKG%qDgt?aPb>=d#c{#P2` zyu%vo9?h?~uX}m@XWTOJ!Ny9JoRF0)U=RG{U)}kFULlSu-Fg8GeS?Chv z0fV>j161ZVRg!ON)HUGDK~bqDbqb1=94#1EL*aV@NbzSO{_M#bvxQszoo#Dp^U~V6 z!fNqErY4BtR!5Gu$df04tpKJlvl5FLwPmY8_5y0Sml2MC%#1(_-sBG>jgGyM5xg2_ zvfAdOw>sdec--?S1o9!EHB<9>B^nzYvCemMrqJ%luj46!R|fB_d888usGydvL5izh zY_tq+$anjv+HYII?nfH?VxqRrIcu&Q0)E4W$#k9voz}9zxB+?s?m-PNHrRhXJ5F=Q z#lhW6+%bD~Gw5);6rI$A+lt-Lng2)KImPF-Z4W!PZJUi6v$3tlb{gBZokopq+cp}z zaT*)n+w=eS{^gvTeZB9;do$PjJY%e}<{We0i>LCF8QL8HF8)m{t`EdO)io@@a8K~U zqCJbrDgFNZ6740BM@`rBS-vVIQ)7zcL+HN`uHHXzwZOwRSoVA5j9+^ezWi!=gT>q~ z-KB{|iB$0NT|+DVwXS82!r^4O+h_uCL>Ep_Ccqkrl}+IZbdo{@g9 z+9g%^{v0Ve+{XjdcZbVC`C_>E^~uEIZ@B(x?f**Vl}3xGqEw$nflq6s^mE&)>Si8| zPbY&b63?v4SQL=3xABP0^R^}(>_#U|OmaFoq3MvSZJPvtTE6#&yjch#w;y738{S;i zdS3WYeiiX>!Ehhzhv^5*JC_h5O%6QgO3XpJHmuD4R>X6A zOu^S97@tZ4xX*Q14y^wLw&3%3eH7its*D&bR%F%bEPjm!)`f0KUzpwAy$7o^De|wPjdsp_ zXYF~%N`K%AbuC!r+O5|l3tYtYnT+hOQVtG4uQ$(C!mBFr&QRt3-%5EL#JPNQvvD4g zh3v-jHvzFQhHu6F&<2FZbb3XVUq~GQE|ftm&JV;a*Mx|)KT|=*?U_K??P||Fl>h!| zAfI4$BW<{L;{EN{htPi?TuA`90x&eoCo~n&u0lrJXUMhQL+FpCCh<<`PSLO2xY+gY zDzH#XeVxK~e!Vp{O0tMBLhMP5m!R>Zc61gvugHTtuOCO~a*VCshh#aFfDfPgh9IC|yociq(8}dzu~t7LibD zneeQanaI2*JDNyn+iZpoCu{%$`h9TS`&wP1HU{$~fE3J~c+XY}oVIs3XV*6+0U3(M zzR5IEb#(r8!3!&lQcqz;C4wAOX|1$9+{L2$_D;WR?{Gq(*`2tbmfWxGz%hq!S{tpF zA}p1U#*DZe8=9(@H9z^ZXWIPY+0$^Jh@DVlN9&ZM*2`-cm@1#A<=O74@uq@0z(TEr z6-sUu&Ck6x2t6?wk{Ot-OYDRcE4=TxpE6%mS4egj{~(C2WLKwyv1O2rq>DWHvw5YV&*BKAnY zt=KYrcgdiuY*TUt96a=nE~{=KLR$4oxo5E79lIt~;JKNtUOpil&&Tb^i!S%XiblDGcH$J*DZq&6r!VuhP;m(lufsnpkQeTL@4C?Xy*Bz?Jm?roPYP!uJ6>zQMk}E=FOi<`6yFs&?fm^5eh@ksxBpE zEH&uuL1k5ajRK9)1$d-a3vdYPPozd^@9R5V86NYayI z?Joz|rUo4Ffpf1{YJ_9R(^&L2E*%2fa<~I43SssVEXw0slUs}QW1kP92HaxbnHI5A zTRr;TX+#_*zTyc4i#BZ=tifAvaI)q_eh59q#VSxb^9{A`D52AxT{-8n8;S?|#E%zz zjWPQUn}{V01ifE@#qr@6Cz4|sJrh59VoM#|j)@Q~k#}+s=X-+-_}c{Y5lNa(K7{`J z)MN%wQ}bA5;Z?(F5P`oU(TFOz9UH%2GsLo>f!9SO($1s8WoHMHya`h}8a3YF&kg%` zMv!$_Vj~y=tKDlnUN8j)+F$zolxy9(tdJITU%SBF1Or0bgJr7&k!*Ph1rUI|cK_}K z{?ilvOHHYb`I)gQxR?E- zrn*{H+x)jO8-7F?qdJB+i&~Fu!o4rHc5KhcVF?{QmKD|}U`?B=%SY7R1K{4smN`4% z`kE%j6&X}esyiaY5N@XO(H*n?>OZqzx%c<&$%62<>v8 zVGdQl&dOa*Q7RB_VbVEv&L|D??Mr`q-X5f@@}WGezBu(h5H|i?{i$d#I1j<^djK!Q zAd4YjDycPr9EDU6*L;z?zT8*-#wiqkg~PI&o>V-f>;cX?+0=yb3w->hwXU%XZ<*u0 zA1%I0wne=CEe+(3#vQFmg|DA_anCk=dd%eyZ9$36ux|>>y24(w3@b%5(Os582z@oDig zbWcl+oBh?VmNe^_P!8If7e1UMvQEm8Y_%I<$A=#yu=+v{U61bRXfN~Na)xghkh;k^ zl_Fyyhd)17b6m~xl+3P4*az-H>kIgtP`gN@>Xogng00&w=vQOD;-gdy0*21@^vF`9 zB*}vEl7DL3GT6!2r^iTgg1WX-fY~gKF&plrVBjsqkUrL`hOw=vr0y)LS`(|Jdfqv< z2iH%{{yi2^ioZtROY1jc4QbCGRay+&%bD@mALmg}-H1l8PhDTXEN*fC z|IZ29u8qQKer|?2+#1-R?aw`ZN}qe5h7xp}V}>{1&rEp#+=TpbsCPo7LNtoYJy2pA zEzSW%KJ#^OCjMdvBq^>eT&iws+!R0})%sZMAIzZ8D|gi&M^@9cnb(lZzPlfgbx_Be z`U+A_xHN<4fCT;_^xr2DZh%B`T;OS$m-XFrWpcv@qOFX&yYUEy!RMD zr0_hD6ra{fBMIEJV)pC>x~?i#L|z;W#b7zS1I<^9}Vf&Yn$H|tWLn_ZIJS! zwlxeTbP7dh1$BdyoP|Iqlr2F;0=zsVXo7x{Wm{#K+sd<(lTllD)~6bK`J(V#_`Y*g z?6Op5&OmAE^x~CXhb*kHpT(?P;!Pd$U8U`IRK4f zWGA$vd4w&(t3S)kjlqT85r6Lq8gl}#^whY}ksvM9*m-&EbdPoK1bLizR+DFyd2?ET zjsIu}`$Bx=yeson%%A(W?TPib1@=upZ--Z20{t7Ol29u#55+7fQohRpf0{mQkmS~L zY%1}(iLZT+R_c0*CE*L8j?DAmVDAk}gTg`_E1;oxIkm=h-%Wm)Ok+r4vt^S<5$X;a zkRmWDWiDyM=bOC+Eu=~Y%3W+#NeOYX;F_|ApFqi_lBw3xHYAty&--4N-)d*N@Jhr3 zb4C8=Z01GN{$-`S2ExK(5%F%()^bkAA>ISbD|96msj|SJ4>dLtppo|B|G%++k&l4J zz|$^}MaAPuC9_XA&@g&zp2kgycLJsm(DNa*DgimEv0*j)Yqwk520FK;fHq;`qR77vV@U0uMDC<1P59-m-ibw&V0C}tqPjvBpg46{`-(c03ciS-aO9+(E!?0`$Tl( zkAjF5!URX*|N045Q|BAlBk_P{Jz5d9VE+2sHj|xYNQnT*vr>-_%sUrbo#?gt{&^0J zS#Fr~62x4%8-a}NSCpaj+~e+2vrr&!U0zL-2)*#X^Zq|QQ2?_4X{PF`uA3bmmfuisw~?&^=f>-x-jX76bzlK9x|(g*0Q+b9^cI6aogk$#Za`UL5AU{2_;MXp$w1DubbJ?v+BcRKEd4U3y&SBf{5yFPA@wT zhKzOV+_glkcXY_xjuUwBoaF|PoKXd`ug7W2v*Qh5!?`&a}lIfeR2BR6avKgPz#9bLx48g<(;&@%$7j>+W7<)@Bb66rk z{^b3zuLw@mz10Q05uQC%p|`X$=1{M$(C{oc(tZM#8a@)(SpV5Zkno%o^>&r*X+9<;rN7@+rOhO!rhad2Y$xlb#0DWpt&x0|8eV_0l^j{|t z+--nFVvnI9IX|zUgA(660!G@HtcVS%5Vh@dbHtZ?w}xHuFSgYmITKY$7RJZJile_@ z1CwME!Id0wW{x(W0^=R@L0i_})-jD^H90xa8h279o`w6WuO<96;zRoRYOfu4|L>;Z zKdt>=5{c3g4CoB2Wfknp>lKe-ygI#7#fq7M@!=}8dvKO=+Q+7VI*jM7c!I*;I^P2^ zIYlq|Hlh2vqRT2XCla=$sA~M%lIT{fPYvqTgcKWL`@4`pNcH8g#tKh9ltTpeh$F`GS`ReQXc<}9l1kS-1OQSAR z%zPoJwZrzcinBtf(>@hCMYw&nm;xXq15m%N!0Iu>#d2&|n&~SiBvbwoO5` zjs-fxc6vYvEG;HORyx>t|Gm+Vp_Rz2ah1Rq4oH#1<=4$n$oKNFO(o#3?vRh@gNIF9 z0Jw;Du~MPMJemWRZECFa+;jJL%k)JE@gHyMw`KBBWdR{ui~ zkd^=QT_-plFU)dLu1rD++r3OOwoBk|35t%NW#F}(yd65{b_!R~fq!S{e_H#$;7S^- z;w5(KNXecu#hG3&g5=o8#Xp;ZLAAZCuI0YBaLW**u)4k7&Na(b6z2a4;ea6SwtePh z*I|lL!D+)x>#F?4ZCObxGC%wC>K+@FT4m98?1Orq0_O=EM6$kM#L}M(U7c+UM01^> z-RWWXFn?7X*{IRBDV#^;-EZNyn{m7ASwpwzTf88JvoFn@>9WIoy+L z|3Y&Y*i3pn(ZbD@Z9#i>2cCn$sAZ%C2P>{(k=Dc;)1Y*EABeo~w*7q7y5}7a?5DW0 z2?$c3q3p1b61jzO#V?ja44OHArbhW`*)FXF=5#ZbOatqW7e%SNEfg^Q5!Qygk&_{6<#BVd zQ~BN5xk*O{&yPW6XTC!UA{e95h-)^)juaFYQQOD7m^dgtOXYK%#*NTYc-A{66+TrA z!JRptlEDgpTIRJcO|<)67mx6^xrVd>BkB%ER{*OtgCl#xBxs>Pq=SA3qwNRVnY~cR zf@?QUrVVO&UF0#oKz{GKRoc)_;4JLpBC9je0*lf$B;wcRnx1@hFtwL)-d^kV4#Lvs zPc0uwZ&TAmY4>E(oT0AQJbJ{gzXt3-487lz@%MRq? znu%qO`u?kd@|$&fsEzSi!nPMyIKE=J?>sDwAU58A9~@_7qi_4>Vqdzv_h$5p{9?B(0hOzwC6uPz3LJy&g^yQtL9sf|cg z8#v5oc$NoP=p-?0as4X^ve3CA^44W&v4Ne{GmmwD9IS<76AjC=;bV%iGX*Y}{A4yO zB%RSo!=?(>HM$>a)=yT)Z51xJQm+~nDg8-*v)W4F|>mw^7sCpfXU0dc@=8gc_5zhQ0B4QU%WshjpShhwGj zI=R`cA0}#76C_Eb!wH4JBk^8YeoA>mB1cX?uxSMP$c(L7_oz9nYSK0&{63$B(s$8f zPqr z(*0>={`8<`2SX`cfT2O(%Ie$Eeuh~q*%1`X?_N-VL326{BQoFk1$S(1 zv_1a3IN3}7WHO~Q0&+p!)Xp%%htPkYL^J^sX+h{~mm5euup6}HQ;%G{vTLIF-8vqY zk=gtc(Ji5Z=T3W&{}>GiN~D3yOfC+Q4%vJ@09-K&W|qG;FS2ms6G>dkyl3?e>ojPs zQxdFqT}D(>@`^X`8szkRcwy19``=~$Kdt>=5-Fl}^aXn_tDO~zrKxfaX=RK9N?tWURzs|b%H^MiSaqdYAH;G`|9@F&$L0rru0U)UanrT zt}`da?px+g77^V-vGi?dqs1Hsp)H`ykGra8)gvtL3}K22Q0*l5lVfDJEQ7z`)&JCCa)&H)Khl9*~P6A1EZ1 zMSOMsQ|2=Rjx?C>wQT;lOX3o~l}&dZ=vFuHJq2^%C%WC^TXgp25@SS5aHQ#FUEhf` z09#CQ9+1KWs`uG>pnSo&??e?@x4{ztYOxo4-P{jfA1YBu2I`iFxGvG#A+AU&O~tn2 zR4eWek%8!_9K7c92KxK@n}1sSzu*d*-0cGnv|B36Xb;SGNh=6hb{lNE@0}BteMDH` zqHYj^J=>|I-D&))V{s1oDaaIP0e_iM7Y5V8#BW7N-mW#~=o7c`cV?G<1P7K8RT&uI z(IP6im&v1Nn1#yKejWcm89J$eMfDB^kAp30bRz!|SKZU5m>5x_I}%bo7Io<-6=`_b z&CisG&Wrt|zN{3aW{6bn+RG*91k}uxnugu2`^0hao^-g$i`Y3#+F(N(s=}3LY*~Ni zVkJ)reD@Ve;zD#8LKdWb7+2{2#!v?KNBYZq{_-AN-*xRUUwC5^LXiDDo^gV-89K?Y zFBM6wHMiGM_ZVa9^Zjw3}dXuc4*^FKMHUf(T_|;Kunl zAv-bzvnT5_zp4=(F(}FDtUlj$wgbIO&3h&_Q6}{ZS=>@jUcGJLrn_a^9+V>^j4Od% zz`coM>LacnSK7H`QosDZLkUPZU zWQ&bfl)q}jHhOfI4XYb_8N($qNk00_htPkYLVy4YDKifxBc0HPf=)ggM=rFEMR#jH zJLd9o<)>0nvi5|=k46fKw<1*t@p0g|kWkDB1-@{}VPyUNY$X%})vJdb2t*NN%`Q`8 zIE#SWdu6rKQhfG2`qjv6atAFw(PCq@>A2Q8N4PUK>SfiUGP9g>Z>b(D(1ptSgyJiqqS(p9R=`r zP8xi0&XRf<<6qyUHE8wu{U&)4HJTqHN{QdKwW7I<%w(^CHE}s1ocdjf;F5`|rf#u& zTPDg$g4e%nBxEAFpA5^kjvQQKz%uF-=$#mK1%OR(ts9#!kqA|8GJWG`*V)ZQ^!N5r zE>ED0(vtQ_rL~?5L-vP8&jCTz%J}uA8tHp_{v=m44t>oTNE&q%eLd)$B81PvuDC9H zQQTB5q@&jl#b*upk;c6HY4D43%ah=gl-$9!33H7j2i!Ue&P=O9+#|d|t)cix-B%Iy z-*AuPi$TipFmXu~Hfs*wOuVC;vj#>LohPtkYh-aHS@-dY=-j!RrAWmd12!Y)*e^bwp zicY1CpQpOMlbHh+A$Nl*t$;+_>iF{HR`|%G&WOw=q8<4kg=pvEyFgP4 zZfC({r)R0gUS+D9{bps8W)mUrlEnf!Rl|n+k3^)9KdBr68K}v{$=RT;o?BCtWnxIP zLy2gRUl?-gKVV=({dcDz)A+&wY`;Z`6ahI{*G8a~5WXnk`1iHl(a}KUMA7;vKqCAm zSd1Ucz;jHMOv2{qxhO~oJw+2~gU-kVy9)%J05n(mb0P{M<3s4bP9ivS0EyHtH$*eE zPHSQ+Dl=c0%wsI8lzgAMb*AmL8u7o6w)q`sy5f0Fwm5>LV0pnhp7jeYph1z_7nPEe zseBV5OS@acR4sNvi~YK0>%P@;o-NYvTJ7SDodI#YT0_YZ61(K@BLAP({x69Hz?w7& zuYSHj_R!JZi8{h$<^aCxZ&>Vr4xA~>ghZicJuhLEvMeC~K1R^5a2L=HQI&97)(LYe zb04sypS9MU5&)uV%KRn;J(laU9D=7l)Th#bpgsE;-@Q}kK1J>`pvW(lPF7J05XG=l z%?;63*%$!799p6)W>(E?34^70-a4GOLMRchDAgsMla9bhn3iek^+d_{vD>BvcV4RV zyYsTZnh?U^hP{da48|SXU>KYFy%4kG35$7(8*}`D-&sd>jcM81G%797I7*~q){AuPFoNPkY+WEK9n5>4`D=VXMVNEu{J;9bYMep8}71TlY!#d|U zeJkvI<{(^aKMDPI_FOg}$LGsVr<@3!9V!dmoc|te^-pULfa^aSWRe`Se@n)hi;^71 z!3iDSi*F&i!-S41a|cU+3M;$oT;2C-uXz7rKXIm0)E3Uo}7UsOG#kcZwJ8D#P5qjfO6lM(&?6nL5z3K z%|iE3^V5>jt%)lghPh>M6AluZ0!8cK$`~69neHdCTe}ri>8eVlc#&Z znZQxkYH&w{#~Vra2_&2xYlx^E#cr-TF&(`rFgK`R*o$q15oODUjZ|S-9oY?s-1z3W zY_Dmsgg_m-@LpdkzxT?z8PEakDM#qU#+lN{LLTVRig9OeH*6eRwzPoHx3E0KP_({W zY|#aV#aaUc+;&FY^(#cVoQsGhfccGtPfEREcZ6++H-u_utlwO>FA#hn5@Tc7-c9$68h zWR!z=A$UvUNJL6gBPjmBmGgzB5U&ER%PS{7hb=pX+Mc936J!Xt|Moa^On?N1?$7S} zk4MNpCuh+?hBYE8z-YbIR>3=I7lDUCg+c3LM^^*%mi)2qH8(nk{{(i79$|Ub^vEy2 zV^=82?rz!|u;9NS@$HgzPZ$VtpB0P#1BZHptdD+sL^oSIx86BQKPP1&j@Pr4_(HQ| z8zi?9YvDeG{`-=g8-T-lK1VfA8{~*aiwK>wBM;+Vq~tL@)=-)2PTAHW1tKSc*+XYP z;r&iB+EC#F5vNR)tfwb4R-0UuDAmaq=Af{#@ybCYV!ln5{&k3(!>mubs85aVIy5uf zOtH4%z)Ii#u9W}jG5wX~qF%qmekYI_r|StjPzBy%#P%;;c3Lh-p=n}IEu-$j+v5c* z49x*1NlKdco*Vv&162n0#X^bBZ1u=Uw<2RLOYC!d6ngF^{S#U}Sc>gb^(*Y-mA`;u z`k9cI&nRV){+~)Y6|do|09maNyEN1Qp=nLl;I>Yiz80|NXD`^Bg-IB&+@H?Cnpd(N zIHIGIf_#hC@xJ576YPpjfGe+^{Dl`{bT}9mMtW?`Z3@U$mflpHASl+t!i}q0u-`P+ zdP>5O;Q1(Z)woDJ&sC>j;G+xG5iO0dEb|q2H5$~iOE|3r^E z80c$F%JF3^TA&HX;2r5m-j~06!^E%Z9BAu=8qa-o`faDE7==GWtqhmJ8~aA(a}^W# zKua0YaGp5L>SJGcx1K08RlOL^6Aqm)#85J@U%iu5a#dswuwRYD@QZ6Hc1wv8C8vAz zx77Dr<iOd6)Ko!R=`hg0Ie*ZD&SjRyP!wwKmxgA z{;V~7mm#LS5Z|0*)0cwILez0GinhHB`mW`gst6eT^RQw>_WCd6?we%v`EgEb&Uujg zSz#ct0X#9Py=!f+BHaFD%D%FKQ4~KZxr+Db9k9?PmDKerq_N^d2`p^DQOmA%v9)4U z#1iL9#oI++1|58X_GZq^i)1jxF-fvdC%Q~!D!ze3Er;787Hr^UpQ}`=Ar?2dgt~Og zP#=F?4G~f~Z&5mz1p(7vThfH*Vz#f6bUfPoQz;vKhZ79PTc;%IME7ZPP2-O{+>Z1+ z@rw`Y2`L)I%uy< z!z-&2>1`NRo3zvszaKwo-*X46w)|Zw|I^z4C6Q!2yHE#;(s&->gAJ*jj&idy5Y@1k zHdgCJtkrY)9W(GRzs7AlDnMX<;Rs438La{ZC)4?-KzLRZ{W7!>R|=j9r-DK1RveK_ z-?{+5O7Sp>P^|06M@==+jW_g~^!l^gwRQ;9$y!87s{J|mtx7lkZU+UjQuYBTN7hur z+%@SdMe$<@7`L)tM(PYwTkOfMRMTpg<_LxUig!%+^sPmwn^u-py}qt@zhujuhi;o) z_7vT`)?&Zmw9Rfy!`9UvA~G$i*ERa9k>_Ts^Wh$<9r>dVPls*J}5j zcneNLkZRL??=%CJ*!ZpKlTT!OhjiEVTfqFyBPR$q?cSj#MG+8O8uU7A1{XTX=*dVJtv-qkc<=3JDj4zQ^4+ZU3U&ddH z@HepBzYBYmA>`GQUV@?(;-dQgaTfXf6XwtbQbb7j1j!E*VRQ?I3>5! zQ_VB2$ZvD{M^3?Oo64o-WX7}9jG?up{l>kFNHKZ#&*Hp?J%m#O;EHo(oG*13&L}*A zfbYGfn++Eu$i9AYlM{#fAR(Jtmh*CPQYh8mwG(N?%Y_1P5XKmK^QGC`#G#bwYBxYx zTl(rTx6AC&gBd?Bq9Z*`<@?@rwg*t|wbcnfJcpVX2`|KNFjOrbf+dNSxt9EaYtO#l zJcVjRH@Ey zn8Kp@@QcU|{83Cj5A>sF_8;%ogc`CNu{dVB!@!pv<%2M^;UDM4{_8lzz5;NVN0SQX z7e2Fp{gsV?NqJH0o;o6?g6%4#OlVQG8z+ta>L%hbh~$^Xw#nB9X1!lq2rQO|%KJB( zSic6>p45DWVwIek$I;xaf-n>(4#TN$VP-uUtE66e8-=b#mF(wy{;rh&=`sDqVek;A zS0b$;7!1=Knux;Ep|3EONqc*)uNcd#x@%ac$;>nZC_0@0`15nSB^Lv+7f8lZTER2C z&EZti@o(=>as*jnNIYfWf_&(*r~$@55cMz1(EW{|A6)bB?!AV6_x@DM^&;vkoFY?v z#&39>o+-?;L{X1+A*P>gWAf{_;`761v{`8BCz!8dh-#67+Mb?|8f}Z$JQI|s@!#Lg z_z=Tk5DSGhE9}yhNm2TyF-I>XbiUNcr(8PxTn+Y0iW%6uUU`ZtGg0VJeERYI4F1}Q*=xUc#&9A$zrgS8P6YY*=9RRezBtZUPLk~e z6uNO50h|}8mNj7n63om`=y;P8W|jdeNnvu z-Ec*W%9YZ6c#;3C?bs8Xg?igCvB=W^^RCJ6q85{z3~Ys)zp$D|Cr zj_C7~8u5rsk-Xt?suqFDXe1mgMns@*5psGZ)lUTDCb69fpQX%Zo^#m0`k~xG&Brbv z(a&f1cW+8X`mIE{A1R6sBE%p0a7nQxjG zT%vori&&y~e=23TWf3`rmC%)=LbK^czGf*fUCEBgrK=}Aa2L`RWfrafKOxGT9bQvH zHiu{~{UTJicjt82_`W#YS~VN=Z0pkAN}J$+he~Ws?6H*^evi@NEJ7Ju5;jkEm7pzO z4{8h60TOr@HDdrsgent@`h!G*DV44#Ph~aO5PWB#2rIEsUlTJxl?>+d_o&h;g%(vuUCXOx{5Qy-~5j%4i?XGz5^t?Nbax>x*n zrTkB850J=z+FngB3&QF4(=r9Gjw4W8sPnp z3a&{6KtZ5iV`)29C4yE~rW-HKmf4bTqatAH_~P>wnt|otuXkA=dTT9f#h0Tc8O2#D z3I6nyQ?XBB1Qep@iQF2+-RDS`vA0)$DJoDi*IIo>M|b;#M-3ldmjXwZ&)jG5voz0vjP@O zIGOD%4l6wM6U$Qp84E#WP&?HGYqf5FT-{VSP-uk19>UZ(|N2|Yj=Ek;kKx|FEH>rX z*DZEsQp9wbaK0}qu15-wOE`fZ;5$f+aXt@ljouPfX3bfF`1*D5?53Hguk~KXq$g0u zoMNb3^y^RC{!DiN@^(nn%EYlDY1!LWcW|YRj4)yG6Vc zQ94IE>l-k6S(&*k@53BoIR!S+e_tKHJQSIc%Ma($60NdPI(n~5V-buGerM1ms8$PI zhD&N73;y$j&bHgF*oi%DCbC-(^1^ADl_z6_lT zfU7`-i=WcI)hyQFt~3EL{?`T~-{AsT!8<%C$bz%ZtEg)b@F@~}5!+7GowR|jR4YWR`tjcx`k&VR zFSuIlb#h!O3Si#l$KUfybeV!4%~s1ePJfh%lbP!(F8sPHr@^I&(mfTE@23waJzB{ z$9X#b@yCMr)va)6z>gR(h4hJPy+>Q4K$)g;-^Ojy}G$l?@6(&P4>mr8}hw=B}uRL`Ceoe&!aE+)7NXu(+0TU3lOO` zgkDbB@F%50Xsz3X5yA`*e@S({ym|j8UyK;}4aMMNVH7Wa>PYc(cZUqbk*+1U0ixVQ~Xaa z3t7><2|4Ip3_3nWO}fz^eqDxsrtS6KW4~_&QKVLy{eMqz*|evmM~Fk`i=}wACfy^% z!wrAaz3|ae=HwWq$yd_upMkb-OH!*Xy0VIb9YKwFaNm;&hz4wd@tan_% zXg=Wsr-IG%-wz^#2s?&iO_}GZhpdWKeN#=4Y8q?^Gu0s7!bo0fpHMbgYp zg^>()HH_F@qOE@Q(p{*93_7cbG7}8ZOB~z7ptc;;4;Jk?2k+i5h93P|SU}u+(jGH8A|+_D&_LqR&!EXSpOH zO~REUU!!Y`7MB3N)#EYtluj4(p%~F@LTm`6Ch_NIr_s=NwWTH*c#Qx*()l{ohko{G zu;&%_3EyFE+(irHFSk6FoZHIY7P+$8@=s=Sid}+2>#Is6FQw~P>Plb4H2rcsZm>lR zd$-s4$UTy+a@|e%QJ#EzBeW4U*}Fx%{8~8nMh}ykw%;UzF@J{EJ=fY{w8#uaP#5YV zg4z<6`i*SJT9W*FzHd+7!H1|F10^Jgen+@3_jDam*K24Cg^s^wdhh~W*S68eCk+AZ z{3H@*fEw?>Cn)}CG?LAT0-s)l#`t`2D-4N2{26usvIA9{aVxhy*x6}OoZ(VL=VG}? zqUH4z@+Ac|M~_K{uN2%I)ELD~KthmbHsOy%WJgv=ue9`Bp{40PlOFi-mC z1p@EHFvq1Ku)+Q_nTDWi4~8qGDRYSL6}@gK3kT%K4XLEp3W$PPNZ?h8C%Cd&4$zOA zX?2j=h-Ix!U4nG@ARmXv2n%SBy?kHeaGRxxG(Cr{#0H`Tf z0gK{;Z_+(DlUs-{&RF5YKp&tv?h;R^E`sXzp59cf9)a%DK2C=G_o;~wpr%j|xt+)| z1?^azu$o^nxP%#CI5>|4TUs!$T8ZBC22~s9CS*DsL|{C*7PHXE5_RzIo1a7A^>K}j zou>0U^btZ1xjFSJbRq`+L2r)UQij00+=NJeL+ibX)ZtkR0`z9)$RczH;6gxR3$X(%BR ze-`j1Q*@==%CP(q$InX5$~8*WfY)xD-`Hc?%i97UAYWRTp=MoRFl4{*V~8}vA#)17 zQ)#z6m&+8YfhNN!C_(m|2=6S~XUhqm0P;|4gfqfd2iXmdV<6aPU9ZoEBgRU2jSCA} z-7;mLEmNDTY-zMMv>j@63mZun@b}zOJTT~U=Fh0I`4JQsI8=P7o2ddpUum{TN1M*L-+Xpz{UDXPtJ{gg8E|`0W;c_83aZ$$+$iBYL zx&7mT)MGLoGu)TlVt7nVgwPwi7fSGn5=Qq3d{`Wkk^ERjJ@$0>u0bLCZ5j9-BF3-B z`zJLah1QRQv3~wvXBifBoncP&Dt}%c+~l$h%uYIpj)F{GNWL90vKIe}4-y+f#=IUy z$Rh|ebh1#1rgR}f$8owGcjRajjb$O{He z(4%&R+#a~q!)V_HDzMTR0j2k7dPg!Fs3^t{QCT2T7fdi>kAq)D#oXq*X49V|7{hi+ z8Z>0Y1H_%m6ex=!*lolFQ(t@aq9P9~=X1FA6#jr9HL9A9R1-M9My7Tz|z?@+f2;tSyF|M}k3AGG4rO)66i-ES-<3 z{Di9n{v^xCP4U`u9%6te?`2Bh_ZgCa0{hFlCe#m)jk|UMU;@>MmO;7_au4v?sX5#W zpLsj;0Y=LC@+AAv?y^g}UzLv@z}56LS&g4ww3om_2VC01H&7acW@4MWpMhPp?=4QFe?E4Ya>ESBUP9ofLkQ@za``D1Y4 zHMAlY()=>432inn!wm`dLm zx)D8-zZ$2ze0n#MRne09N!S>6I(Z=GI@{`mNvkn4r8@0(Awc|h9Se;mAwKlG;v}5X z7=GYBZ*Ap`rvQ*Uh(O1iRCESd+Vj^p`pHn4BwPqJ2~Cl4yptlC*^KG3&@R5=FUg0- zkQ1(0wa*hOMFX=)tucE^qEWGAhqZSJntz?E1j>lSye13>#!vv^+DrWgWFQFK&8V65lsr^*KQgbLy z`SpGIWx#WtH3tldS72sUox^Snu?*&nai>v{GmWq`ldVDSLNuOA**u?PCD_z};THJ> z$U5f*S^f`Pp%5uoivmU$Nrv4<+BQv=nIjQV#NR;-5-}h1taJM?v5EeBpK_=x?!FY{ z@@b_{_*;ZfZ6!S2Jjl{3jw>c=tW-z*<~9H>yb?wr$&PoW{0on~l++v2CldZU4LP_n$NJzBlLkTxN{5b20aLp0(#(Yt0GF6nQj# zH%7e*Asp)wz3RPL1QLGbJ_H-Jr~R*nD+~Zvlh#_&WNb~TN;K2skfRY|Iny2tP#x)g z4b6F$Jx&iwm)$7rIXWd>$&Ej`F-6ucIu!rCbc~YD!sxpO?&gjKWb6xtx-aeijv<3= zVKSkYF^_tTg$!mwnEhY~c^i_;-(|0Viv3@3m1(ntV+1-a&Wdvm+C_A!=X6#s7nYTd zl)H{%XH>J;x*v>z>Q!BViU47!nv-wJ!pH0J@b;F_$^}K?Ng>E<)+nkI(Dsr*$3Sd| zG`SdPoRjVC6EoT-9QH2K)~N7}{>fNl!acm*`Xmqo$?2C*^Wr-m4ZE75$Wp5zVT;`Y z-Uxrk80O!pW;n^E6U7k4`@y`|C?}QQhQxo$6p5oWFd4UXRoQdBPLhs_e7LIvzkEQb z?;sPex-6A70Zy~*iAjdWj^Pbjam6jk?7)xwUJQZR)|I;Qy>ta%M%a6WufPpMM^x}c zSakZk5@V#Ogm{YI zwe+Y!KI?qRWUOfZt_l{GE@VT?<1I^~UZPnTIS?E=L(O1cl~gcPh^NN0eZa7!Pw$JO z*PCuJwO8ZD9MW!y;!>CmRw!kWCw2U((5?MZH1|v)-(_J4Q>r`rpWmjY;=%1CdE>Y0Fa^yS2Q2eT=HN^}b1!!D!n5#Tc zLW&rM>!WQb+hxEtP@@7KGDE+~#HkYb>IM+Q16sKhrDGil5Md64Zont`o)oWUS&NWya~Q$BK&u zpy9QOQwpF$>w>%-#QgU?g735M1eE{3EdpE*f>7Korv8O=DM@V2p%O(pPWDzMb8j88 z&5LYKhQa?nx{UEqrfCVaHjq@WTbPj8zrfuJP=_Hr5~pI@E>i!f=8pqW1pQQl5kl0p4e9}g7(b2* zC;j8Rqg;zsys{cvetV>xb;1c3NzwoFdMA?+QboY#S)Uh z*B+2$g~j9u7xfk>p*x;uAPHM=_sjgv>BF0q#PTo0<({m5#W0rp<0-Y$tgwNA-*&w5 z!Bsgaq48Bb1|x1!#%qO>WzBHQ+a19TBHNr9f|D^7eM)wu)TpxX zQ?)9p#p|lCZ(Dj-p9`_iUuqx7enfRC=%KR&!a2*nQ4m%CCNcz`fwjfB-9cMMp6Q~^ zf{C6bTT?raOvA!af!T#{h>HW)98(kLL>~hP9lDTvrt8+7|1-uX7V~_4ebsFYo76d0 zva>cB%1(t+%g=?nj}nZ@C{<)D$Ofi4@wIf@MIAw~StdYG;+&)JSf$uRU7Bg{Jutc3 z_~Mm97P$k)g$pD8J7!(1Dx7Lp<;QRhcw?Wt)qJQf{lS>bq2?xz?%cQH)}o( z%!be(apJ3D8$CLSpsZ`MewRPgEvtfH)b`EJ^gY)Amcez*z1s<;?$#Ua(6{PXs-r4! zfW-D1=O+m^4~Z6__qv`kb;lNs!E@M;+PV>juj3%Q8h}z%!O}gFalWgqi?b1);pBxW zd{&`e1|&IbaSL@IGVhB#17ZrS_w8Km&LR0vpNph!{7x_fgTxoA{Y((il#s_q0C{Zd zq#CgBJV|nzy>cfdKXS$oWMinujjq8d0$lp?OPyapcQu%dKoqjcB`}Z=GIiq<60mr)c$8#mI%W+`F0wQ>+<&)42>J?SJod8t2I+-N!x&4TjHwcBi#ahGM0c%1 z9j0LbINb*SnTtjE!3?-@yGN+dy%`5EF;ejr#doL`Tjnr^7L7e1F-r&_8vGxY{`(|C z50FSQSV{0ki-!J=ueh%$k|LGDLW@T?u&av^u|zO06|sVd-xwxgwx=D(S0uRjTSs8{gr&T1p2PvmmF_n2C69ULjc3T9=X#DWJP8;%9_qvSvJ54E&p-O7 z*#9Mw24*mHU$Jw};=8)O{tCV+`eEhT(aXh18%*h%-;W;MY8arS*6fEhv=;&wHWLH^ z*mxFu>`Zpb{t00ZTqhS&iYiizZfU9bs~jD|$p$wXJh;E;=c=<>7iH$L$7v`~)=Q4vE$Mo1?*t5h98l?aQ(fjEXwKHtbL|<-w*g zZx*jpw<(#JnscI5TF0%<{TMLAajSVV366S^@=B@99Hi;kvE!wl=22EKyvybE@upa4wOsLBI;JV^Jo=I z=uZwdlWBQLr;}-vz6#VKzVBGhWd4>MwFN^Pu^vAlt2!wrI*7QaEr8a%?*Z$YQC5;d zdndZ1?jR?pAZN*#veoMF&I}i;rk5lYYgN#1lE$7n7s_d4J#GB^CrE(4?eToXv!!Nz z`hy}>C&8n9otet2iv{Ln@E)evQaIsK3ZZzn;O`L{HU4^*l05UAURNiPiRv7&I#^yj zA`f^?&~EsV<|>wWdhWPR>&0`=_uLzx7g=3z9_4A+F`Nu1H%Is8FcRozOgNa?z z4~Y<4!6-{b;3FQ2NlM8BvhS#2YC8&zQ!9k`~G){NGWMk3h zyQl$t6BdF+@IfMP>PMXXq<$PPD6=6~qbb1sUC}$+49qC<_#vw|$^jpT#Qyty0}Aj> z#OGJcUH0}#nwScge8fT2)ac=>V-m5~xXhvxxr6$8Iwr~3tp&K{^gYxns2OWjXX>Ix>e*v32wck0zdJ?z)7SKu zZ~V|9b=3#?&G$#8*5Vqpx$!lM+5O|2pTtk} zB8@TLLAUbD!VkIvZb)nERhz=K8I5JDS?aUu_)a(QSQNkI!#2W3&66Ed{ZNQ|>2)L1 zotuj&86KlGPN79EY|qMAf`~H_V!&5@$>*Pz%TQJB*UZgG1Jxmt+g~>s)LcKN}5f1hP!wQ~Y+C;E|f>e*Ur3nRk>gN~DmUG|wx6=*=9o20v z-K_eW+f^^O#KS^Wb%3Voi z%XOTHE{4_*sXz7ZBF{Nd_to_L^Y&zvVZ|<(fJP240auluJ%zzaYF$UN1}tS+k6E=5 zTRrz6^KQ*y<)yuOJlq_>5x9K~;qAr}I);Ec;^N~P7a^I}@n^~c9l>Fj_%@8nqWYRx zNgH0T+l#!3A`PPc_$KutDe2;!QmuIfA0l3wpU2mj`WlmEG*W>%w_5Htj079*Kfals zA`#2+-=ey3mU5ippBh{Iv}%5!RRLebg!ciznXid8@Q~-`J;3Pxn%bgD3`l zf0o)QBEHbOe9i2K)Q4L+{y7n5Eon(p}-P zXEhW)EdAFh1alssklGqcgg%Wp10dDWKG@1YM`D!}O<)(Y-7CkE%2d{|CAtlC0po6N zwY<8q?+l&aEwGm}XsDmVM&3>x`}uK>9Bq!TJ`r{HNFh6!M?0&z0zh(vVEnIl+#(?04Y<5B5sv2*TFqHymVV_u&v_8D4Hd zt}c?|pEgKKcqQ4;l@L2+XCgh;>E@lo2U?0c;qw`<yaZGxD^_#SIWuV$ubCP5IxCoibCxZ#N;en{?uqH^=h!p^?qEQ+)Yv~2 zJS455-)9^rm6!6fuDUXxOI%e;+tM#46BiDB%Z)BBKdodp4xhYSqZP3jMafQ@I_;?S zCNpri#z08KDo~BSJJA^_CJ*4Wk>vN^mEEK#>Y2}ee9^6NEDDEd3~+Y$ygrjl-tZLN zC=ELcNN6`ONGoi!Li%E!g&iLDfyxHzGO8H%5<~jt~3((2Q z`Xokw)ffqE=oglxRVkF_DyQrEGeS9F63*c1C)Zi0=WjEId;F-!7i9RhRjJ8fCOGY@ z+6u5GTy$gnJw$s5j`O63p5H2pf)OQ;m1ZF;@|XC&y}AM8n^Tu6^%G^>gf7P^_X!0b*C~+F# zjg8?TiyYX7@Z$k=asvOtg@yNl7%V(jYYdQh0WXNEk zN~9$;q*+JA@+AZMxnN?3pU}@7n@`q(PcdUPRCYGC@RQF1Zgi7H4%WcQf*!s(G&eaL zf5Y`pvHuIMoCJ1dSy@BDPiWs#t_5q@mU&0pJ_B2qGTSU=xsVN^e%^JQZ+y{r84n^) zeA?;g$K-q~j6GF{f0|mbq>pJJ+d@bjZn=^-QlaQ@!{61yeWOGae4|2?uIu1@NO8n6 z0KgU9T5aW_*aI}Mza8L*6L7I3!*%xhRW7eA0*Q_gv6Cj=R*>n5BKTRpq!2ZAxF|nf z$A?kndZ{=TB}2^>V%BaRUMKKd>A@d^s0b zKAGfZfN0jg*fivAX}1uqOjl;M23g-}?1xv++C>pule!v8ais>0HK{C$d1(Q2AzO*D zJ%b}sl4?4t3%<9r;<#@cdhk+th@pzZC~u@vJA8ore9F*JHEuTvxa}IcPD?(WE!=

hRd> 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

vHSrHL`4Y!))mrU|+My=J*?NCLvdFH?W^2AuF;5 z;H^HAd%^lZMQrJb*-5nbPf&?pd+=T^*X121D!XTq%{*6JEFWU=;F+tYu?UAjkMY#2 z_9sGRCvFW^MH99shL0Yf>5~Qh{TcKETfSaucsJA?dq_JPhI zPm<(TUpm*yla4 z=0y*|Q`IdP?pb3TiYFo-X$Ac#{AC2$7ghqnsemwzu9~v{Z2dAwHhG9EZDazpei;a& zRq?Y{w69vPnqR#MgGeSEyxr25sUJM29N6)7FT8xxZ*ysx=x+GD!*!Ywij>XW)rLOk z3c#+lB^6oOWtQR)m5q_BOj_65h3>*V2%R`fu8woBdnX7z6(kRW)76fe@?^vTfp(W_ z2LJetzcugkruskoc!XaT)8%ZK<*%I);z{X1R_P>}O9eP*pICS4EmsZa0;^eaac&EZ;X>`UEO_7o5)N%Leic*5Ih^iR;MoSu9bOhPL1GBY<@NAIXn6z|P1aigxdofdul_ zZ&!i}=xR#ngaR}re>b5V;JC^GL=SYb1s_Uid4kPP;FswPJcBC{Sw*}XyW<)jFPM&Ws&D)LwIgU z>jz0*!?enm=Obo=PoKs45z+ZeXn%IOwxA!`C>KiB=DlA+yV{JA9bmiSMtyQ+FeiIj zI~B8q&x)(PQtvj& zr`$^BNaRt%|6$!OnOJ0T_*|R%$7I2Pglrt1fkhI#J3@h9u}N7ZQTvsX_hHn zAt$eM@E(UD$3RDNw06Vd02=@QkNP@;K(4zJdI@-K_ZQ^QFO*@_FI}+r*3}IWvNzHO ziKHC)7=N?n=oxN#oJSpw($rJ}zl>oJ{$&_{F2f-H!Z3=j(RklcOFjR1zYL@OV}DZi(?o;uE=|1lzh|n4!QQ_eb98cx4k-yACGY%P@eRaDyZIB@Dwi%j#u=1OXNj zwcjm{<=FLk%&`WmVs62k#8q>7w_d#aR_+T(@V6`XbpVEOfEW=F8M%k{m5pSfOT8&o zRVl-@tXJcPLx>1<_7k$ezJKy!u~1Yx$ansi@dcuN^93|K5(jqA)0DULsm)rV!rl8D zGF7nB)`cHE-0Iy7pcwcOiX6@s8>CtSN8js&^EY?bY=5`dKwo=N`?aH_30AUF2qWZq z(v7W3oKs6h43$I=ZqL?>mYz#DB_a7j7@ngr&od;(B!v7W3@)y}U%jt`5(c#yDrFGI zH149}eZE*K+-R=UEzIdaA2Cq7R#tjUndN_wFaXUh-w}obY@8iH^gw%T5YGH{1OoZu zCkaEKfg7JtN*?WpAc7AL>f~da50D9oc1~2|y=6ZtlX_+nOthCU06pObNBm0&gL8E- zGNJ3TB~g}8$#q{A+mnt$;kqSpx)|t7!(5lnWD*I7Gq#qcuQ@ zMe*sZj?4w2zE2z`MAp~o=&?j*19|iA+8s$KO7`dZ+9iR+T*V>53~1dlNH7CRNg@cE zBUF4^5KotTu3J!25(JY`waX;f+rF;5;TRYw+z^0{r^2m0;#glpcE`#5&oBwSewJ=&Cr}c0+KT$Yk~?QMaA>@u+_eyv2zrvwZX58B31?h;6%>2+KS)V{=9b@$k`O~F z36L*-l9C|QkrOb_Ny=l4Eaomx;$)aonX65gI6uaqt`_d4q`3+vhEkHOU2X*Qgc}?Q z$uHp~nGd43{B@CJ#TmAXD@w4|A79NH6zj-}m`{73zj+1m>bLY6Ai>`*edd3ilN^BQ z0P&Kl-SZ1Mu>lvW)WSG>BV9QSvRrSi4Y^8&uHAoR!bgbHWr^hpJsXs!?n&Cm9XD|| z`#Mc!VE%iofWr0cSQ~LaZ5AxeV_^w(^tF=aehi-~@7Y`3WG_MnEp}ely|8T27p=~} z7#O)tv_r!~c2a`AgjDe)TUzkAuey=LtI#0EH;Nb^>sFr}|9pNSKtm|XFO)XvIu@b3 z3?<@y@(Z^D3i(yUH4A##6ncq5sFe|-*z#6c&py`%4|P~LCDOM&U|MzXoULgWcX;Mc zKd!HhM}>HyU)1iUVLFkC2FVNO6Sr6DI_!2(ZyOUYu94u}o6n!Gf#1aAKm6JX_$_(ZG8SbC#*-=HWNj8Z#e#BpSH3 zy`(CckV#pq_3jQYIemxYw%uK8eMrrqFvn_d>Pz@4L&5xt@@?lk&Ws#mO{J~~_KhQ( z&)CQ+rBvjXH-TeM-iMwHqq!f!iKnP;KBqT5lABcj(i2mmeK@rtK4ngIJ_CL=BW}sy)NRn z#<4C}Gkk^FN_~06ughWLmi02;x&F%*GbAMYUd_-TH2#zjdhv&M-X2bX6;f73f9jgGZ1dmM%mG(39!jo%v&qqW<4p%?v6{ zsBE|}dA!{1KEGQv0^Gq82ew% zP`{7x`$Q0Pom2Q0yihx>Q~pDf0GvAAiAd39(8D5w4?hY}9O@y4!d)&7G`IY2s~Hj! zuicgdIpZgnGsc8ZR@?Ke76lQ?EX8IB1WmWNd|YLwhiUxo5myOs?0`u~pzE3b-If47 z0$Ivspp13!T1fl|gvNal8zA^lHAucLo5 zO=QeQUDuF%7NNU&Gi0Cy&COU*YLKr#{|ICm(hLtdM*SqjTo`=CAx`8h4-eJ^Tukr< z2AVBg;TybSa}LW^!kuPn$0t}c(`kImL*$ETOqkG~T*0Lm${=`j@`5)u+bxV)_`pdq zJwL*e2(LV<k^D@NUqE(Nl(YS5XL#BdLO!uNzQT~IF z3iS?@&4^j@ZRb~7G%Z~Q;m-%$sGJSk5^eA2B(bZDymTn>w&xs59Ve0DX_rgV{LuB3 zhxrw@!!$To|9*F^jdt{?;0KXe3D3(QWR!G_=1V=?(}NhXG>y)JowL}u%(0YDZKy9y zu0I#tZcp@;2-`*uFXEg$FO_edyRDA#y9K6SD(4OM9pj8+rKR$47Od{AQ{|=QlXNuj@XddzYL;tb$MBvKy7eW?jj$N<6{gd$6RVTcDhZ{XV{u4y~y>NCG+7M zW5={ELyl3Wjgc(boG|qINbEH8Vz3JV3Efh3r{_u9` zUWNn+iMg*xgVxtDyarSTjHb@DTQ27_WCY}m8k3Z&Vpx&lAbehi5I*qWCcDH_AE_8h z#_uJ4&ENCwN%D0QOUuoNX-hlIvZOQ!aIj^c|e{KpX3FVg+OFEVMT9@R^)qUrX3bZ_KUY4<}detZr zCf~s}>AF;Ca%>=g_4;zdJP!T3#gCZm?|VvRIB#&K#ol#v>o;AotRD5=)?j;Y9I@Lz zpd8Y;o8H;?NpmRe2D9%@CRA29Y51}d(h}&EZ?Io^aKu_~|0|XX!t{GWi2gdsl+RSF z5pd*aCs!d^+S+S7)k`>DV7B@9M z-S8Y&^xZEPzplAWC#x;-0)^KDojjHcBkr|Nrn|c(l#p&jx*G&(Bm_hWX(5}@P z_rWhZe4fqrKF5!q8~$UAx%RmBUh7)#*PLt4xuzo@_x6!75?0&!2x@01?_}WTJZWIr z{;Q+}nA!IwB`0|o-~OGJl#GI(DJeO1oq)ZG-@E`I_(LF=g62Bmo(5m{-DvI@ip&j| zx)eJkysq}Q)z&`hWtw|h2m$pBaxc37*P#sF1ZCen>uV@bziG zf|w!Fz#(G&dbu<@HNLsE;(CowSvts!!B=Zp^ii3rr7-oxjtcm4;TLG$LzQo}lh%&$y+&4Qp2z&y9DQGBzcZxE= zCgUej2I5uJHJ-?xCj1x9sgN|*-rFAKH}hdcU5z*78;|Hv@F4+s-=hq$Atxw<|GS|K zv^C%Mn1-KcljSb;teuz-?2-a{Qk!XsW2SNTEKUE+*RHd`3{SFTJkQ?30|Lr`kRbxB zcI8yw(Vse9r3i8A>(#0<)gzFtn3`>*;s_z_dN)aKjmr`7`q#-j{)N2%i{+j8i@ZP8 zuxJn6lr0`Uue?9sSX!Zd-|N>NV~XuEvc^%`RZ9Rd!876OaJ> z@8uoXkP~?)_}%1vQ2>HL(+bc9=>hQT$*v*nkPYnh#|)7aBxu+Agl^&CdD;*RnBmD6 z^E`WM{Ljcc2&tOqB>@1Tixq?_uIekBM!&+wQ5n1%bS=J&%Tn*8?{~z@ps@Tp5+JxB zfqyXxkbEJ5^j$|@fr11(z4MYl865t|R(Rmjv$2|+O*yl-LNFXIWyJC8#uwnHh_iVvo4Rx(JY zLjnlCCjnqXPDp_8cOwBR@+T7x6D;qwZ3x1>8oboK#YcP}M})fs2vg8)V0RRJ?ZXAk z@cTYo|K}v|4?u%b0u1R3#+3&lmy=KyT3$Be`tR^8amw5&igUSy+oN&)*Fgi}1vLDN zp@H-ZG^9p(WOk^j^zxk-8qkDp%`$q)bWgUutNSEmnsD<)YxTxk8n!p&Ef1T_{h}_Q z;qMF|9OWCy=n;4xoF-xbOLX`kz7IhNgR1O*BN<1Lp#O1JszW=c&;FIwR?V$&hFS4H z5E_6jxZe&M2v4B_*kt@9G>{VwjwD(#=?t^=Jn!)W)4b18&{YJ}D+NiD2H0OtON0av zeh&@6hMYhH(eDNgG&#X<@SZy;$&Bx{SY)H!Ag;|9#I728G_8$ae7WK_)z{Wqzzn}{ zt@S5C!{5jkr}d*u@f8M3JMz(+hk?b*9+9dNNJy}c^GdsF5t0B*sm^Mr_h0$T@`-Dj zfR%Sq;dSscIZh|VCg<9t~@4slrnLmmFmFa>k=>3j|k(!gP z$*>Toy$b0A%EGh3Tic7868TV^@-kHfl&zih2c4z0s?Im`bGSr4CvImbBQv2KaG*|v zDR@P}Yf8*kybE1jcA}!qD(Q(nIc{#uiqJCg^sX4jhmjMs;irj(q6{vzXcG@#Z8Y(P zdaAO+zXS#Cfi%??tCsB2Jy;jv==c6mA5Rvdi$Sm~Y4J6d>6+-f#?YVxr1j4CDe#Iu z;enfh)Ncf?2|3(QV7M{B9ot)pdV zx~R%g>Qe3z==9q~ZdgxiRI_&c;#!`9g(!!;L5Q>%<&DHG&vpX&CE_l?{vw{%uD_%J3Q=_#cEi?dLj6$Ni z!k$!&Vh>~TUul2xPTXSNVNh%S1HFwNcZ$JZBbO?@igl1ZH zhNo+*apvV@C-OD($6BeidR0b(c%2Y@EBFz31WJs%hB|-$ryibS}$*zWiQGSMPA+-Fu(iJ{5|g{wA|FmMULKPPUJ z=^#u-wfAwW^snwh45-pO(*&SDp%V0K^28go!|)}*h5ELB^zL!WFq57oPnYTPY;2}4 zP0PKqEL$a%1C~2fl=o4H0!6@})s+E6XY!VSq4gQlKQ0>^r|%>i+t0q*dHw+v;F!>d z?|M!ji5h>R^9Q((TGNr+;RSNxVMEH#)oyl2v~OMKpX21mEkwJCuNO-JfQQH=r9!2+ zc@VE!t=mnkgxw=&$fQ!E{ZOuQX1A-FECN2)!@UD+7wKy@_L2GmpAf};-UIX=zxB2l z@R#)ur>9ss(G`jOhBuYHul_nGh3MQ)%29)}g+4j^158^+E|DzR2V*34CiASXyAx+_ zJBEXU(f^2C|DO8MfsnmT=br0P!GDE*E*kWmw?QBI7bhjLE6lslJD#uVyiN*~GkGfp zp+x))WZ4xdy>Z0}t@VSjE3|&t-teoJstB-uG}RZ8&7X{HVuGV;jI8+TNLHUSvWY_} zcO05KnGlRfpl=P`sLV9$?8&&T0xO&U7ESRoYV9BBqyXDnzHw4O=(-1jDM$_a07R#0 zw7_1KZ=M{4j4BXJK})_OI(4srEu)`wulx-cQ=vrZFQb;<6II~!ZVd3%X7$BR zhI)?jq8AcC^u2opY{-dwMf|(DSMdUEYA!Pw1A1zou2vzal^NiG_pDo=T%=W3g7=sa z$M0(+LSTmfMI%BGlCMB;g#OP>iOAae;xzIuA+)UP5b&Nrmh?YrA-3?`XS@2MX&A-<8%NW`Wiv1aj`bnV@Lq;_hbWX$O+ky{BC5!f|%LDkjNsoU@d^V zi7UbpMK2B;pRtgqhW~mXZmu==Ydb?=hW|x7!+$B+084y36F>tNWk02s)ygO)o3L^k zt6`0dS8rk`o=Y^R&d<>5M%>`-W}LYD@Yhib$+@XTO5}z~hjB;Zm`IbbRC1HeqFN#> z|MeLCO+Wh({wOB%e~wyCT1jEBLxW8gTu=*$)I#}%S{@^(8E-2sul%PkwISH(=X=@} zK3_+V_@C?OZ<$*e+nrg)z}({>P4z`2=~qRPNEEa5-c8 zoN2@MJS-zd56f~GEi544UV79`F;Q7>J6Q+PN;?SB$e#!1N}3d$Uvy;f1S&0iZ+Rg#HM_?syWqm zcQy+}#9dSE!W990#D0~W@K@~@Yars!PVB#fqBF_1kYRhA~ zuYd#WGcN$A2TnNv>C8_H0^k4(GyT;$4%lRU-#PBQ+!T~jjP3`{9=y=C-A+FYBO2!R zaQN;-21MZco!5vVv)aFon@G>kO*^Ca=%TtlLfj$TaBJimd|~rq5)uuvAX%!7GvaX^ z^N+;z?}_%DG_-kgwSsRz{eqi7w+!URg*T0Zdyti@@?fExEdv=F!Rojm<1o{03gfK;Rz~b`LMT zrsL;?1dx8uO~8hna1+@d%uPZ3BC68c&ma})AKzUff|#4Xl|?jNxO&uAGBAtfbX)ps z>m*=?-?vTzh5~11<-GE)xU=Q|TKsy-#*mkC)0h{O1!k?sSb4A$ZEym5=vaKmuakGO zbIW@}kXupuY%PW}hWyN4bdwJ<}TxJqS-k`#Tn)8p(4UYicM(H#(gRkv`fEj+@ zP6z~f2f_5bBrrbT4Q=@rA!OP&IrLQ`cCxl3E3HW@!j0*@b|X3Q3e9U;_;?~dI}ggI8WZ<`s0%G`V#V^*rXMFR;S|DFVZ z4LKnJia(eHKB>**W)FXwZq1dlO{2iew?(_(>&jyxo)h8{P!1sH{n~j5nBhrk%Qu~e zK#=!;#uOMdoEG_!Zz!S&C89zXL@wgXY1zZ?jm18L)kevwEUOh_AcR;vy(IFPZ0!=; zpgm?^j!u8I6q=~-@J+Lkc^YYUI(8MRta3*Cc;ycgeiJpc4=B@KfDIAic3Rk&ILyfF zIA+F=@yB$RG7h&B$%$y?^&& ziHdQe&I~CCBeiNmQ$8=NFx}d3Zsp)^sj9KyrPdBa!%AUqxDQ+7>ow0A z@h~gZ9o(8q%(db1q13LFqj3`J8NMj^PUx|lZNg1aFBszjw8IS=uDF? zV2yoZ_R#Bt{_9Wz#&b9P2(E4I!iGF_ZS*hXF)uSCFLETI7uXGQ?yFg+sB`u`@x5YBgjXpm zFPE!Z!kZ`Hy7KKN=8wH*xdj5h(*kF5AbBXM0>C4OUhf2awh6ZFlpuIea30t|-fKxM~RPbj0%h z0NW?S;}h$oql~a|Mv_mqM}3HwT3sx4l3K7LYFbEYno#8f-@?xPS;4nC`*#K$lDu?D zBo?SY)V;imktm|2j#ojMSL<7jAS)N(@WRL8pmUq>ouW&eXWol&cs}#&5Wn}=6{yf5 z<(R+2txrmPFx_Q(xcJt&!!%q<}DO zH4`+DMWz%`3avmdBXB#Sn`4z)f{vZbYe#P9I#^27$WzdJKhYeZN|V^@p)v^n(oOAI zwI2puA&&KtnqEvwSln%FPfQi=$*gVXZU|8?yK4URqnFp*YpV4ZGv?rgP$FS)_VKNf z?h-#!634+plh+Q+uSvwHbFi0^qgNZm)*ShKNhvT5vH&~51}Ku+dA3FJ9zPOeS5{g>3?`S zgkN5+2NF-)jQV7Z7t!dU-DU6dwzw5HUmDr6>#p#O{-l%TVOC~ozh1o`%T9+#{o+znowi3g*4PfX79-{d5(Gr*;=R!lRHUvx*9oo?)PAj? zeSC)}s)BTNnd6c_?Z`!%Ri9_mpN^C*_rP{y`F@>SPjPOyUi)fQ4>xD2Vb!c*6bL?BUM@+fm?>IeKbq>_H}CP}e8`#G^g)g7 zU(4q=yMYxG(oWG_&CZaV^{?jx*5*gkfERAsYWioGZ~iyy7?KT<))py%K3owxkaJpUtIf`RQVznxo8 zaautO>{a>Z$w4TK1;G@w3R;TOfCR8*^pgPzDD4Kw&c={eg4@dwTauSfB{M`Z)AS8DdUwTQP=t7fkrcs~t^yr3lV^D5wn*3$_ zNdb{7t=E@JzBWJvW_a=&c%Ge=eF2VRnFEk^1825wKpvLM6@6xv+RYTF{ zI%Z<~-LW>O?-Q^s>6`Zngxi`xFa_;mCyLWrmS-&zc~}SQ`CX8B)TGlT9+qp9!#k{Z zIF{di^aB%2NB?z9M|r_?|36DQY>4n`jp?eQl=mjgX|!y+uRH(`i=LCiS3?c%KZ-!i z0h0y;*V9h?;7g4P2KJXA8=jPjT>o4qa_K*1BKn4g)}VpdUy^J9_POHZzxyZv!xyIW z*PD%S#EE=#^M8}+rkz{C4g=LJPj6QE)0s{W!D9H5>APT|FtYA91+A7-r8^EqnnQEX z6^}%iD>xU=&vd4pVa}v-D>oqo|9zV=E4X6PlP>*;$YLi*2;z(4b; z!1`&N`C7orb5BtO*qij*p$OF}itypl4?YZEG-BK}YEsLz*INbmtAZ9*zWLVr?kCqk zxsSgNMW`-NzzOBa>X&pW2*EtURx^)Rk3LK4(rEyfb*lsd$GikMs&-BwjT7f`}gLn2K|!^Frcx8 zxY614r{dUwseM&99)Jhv~m*mtrkv-qR5$EG#97e`L zDjD>oP1D5Y3->+!b<{$AK`sAcYGL|9E#C2a-hnX`6z9%$1s3W5k(&L!hhiW2%TeYK zT1L?SigEqv)MDF~urQvQ93$)pQGo!f5+l!L6erx<(pgBB$8zsQasK(Kg`BZ$AS+N3 z+z|Wtp%TYyfF)Oy++;A*c55^)U(L2D@{i{EZ#@({4WIz~N6C_#DStxZEln9@FdadgZTNb6n1F2|#JBQM5z zuv4j~WycNeJ5Ck7)(K0j!dpp4k4MF*51Hb>^9+LZ;PR+EGXQP*JpxIbiEuL68vj_Z z=t9TVrIL@g_n7*dwMSxJESkRh*z`Fsw`^JBZ38#aPPGy*-st=GmFff--26xwMAurX z8O6}NJE_7_OTuZwviFbga|T&!!ljz-CGw<*q?@}yZ=lFNh9mE+BGEX)^-|W(yrMuh z$q|pQSw8Y<0Xp(s1X`KZ8U^IgttDY3{&%j=xhfpyjz%j!ZZ*s6v>#mMoPunEm?h}B z@i1njfA0m2DK{77Zkk)fYk3yys%!jihHE^_b?>8gs96*mtS>Q?(~exDcI{F$AcZYO zfAVnOPx<9#zP#&A#nie-;HqxTZZ}*_C}VTedg;UsU^Zu+fn1^AOj zhBb8i*u3?I@-Dcd^)4ZoZ95dg+;Wgmtqsw;g|ayGI^68_f&!@ZgZJ0&1&_?fbMf7m ztZHGDFo#SkI&5uhAL7{zi(+2?gAGlvSJVBZ($?{GJW|`+t&XS#TsWS2 z6+WS^oDZlVi`RUmUu@IUw79Gis;<)FJeT1c-IRV)LY@&~g1-k)QENcqm(D1WyLw$s zFYjt`9&)rLAtNg$wSq@RgXT(=I4P@9TgrCXB~nxfO>O=fISH;EQG&gMeWH=Fd*H(n zF=T>6BuqL=#w=BbuZ-7g!hF;%m1|_L%vav2V$rnIwOSTRQ@JHb-B!8zKI3y<;hsWq=XWw`vx79&#Q zrR1bo68TOA;N8Y6{YQwnNZ+Ooj<-*9W*a6v)ZxiQZ|xT`k8+lz&mHlp^3?$oJQg!! z5&?syi3iZ0**Af){Tb6gE-x3i`6Ms5h=ZZ-^|Oh(_`O(EEmv5q<_JYcM6Qe{LpRAX zM@e60lrYo^PZ3Yvqa0eOHI#44!Lh@3H{!=slCY+E>xYOOjr3IhX7+6_wG=y5F{6t2 zyPRxz*wb@#?3mR3avN4JhQFo`28|NF!O^>e^0WPD0l>AbOBo!l4Ih?c#~rape2V?HS~&Iju7oW^*G9Zb z@YwDZn?9n@cM!tJe&lJtLPR3*@_OI-y(EOC``mQc$WhrUX;g`F_eP{r53`9}JqC1l zRTas?E+hVEp8r-a=@bWnEx2!R5QMBc5KKW!z@t6&?|@ClPx^Q89qWCbZOPZ3x|2pF zz2bXlE6o|p(<_)Y&&=Y=^!5P?B!KpN{|?xY6aS9(5BBd`IR-HWn+Ne2^mT_C_$ofy zGhe2~dYPpgFV~Z6HGI|b419$7(^ecS2#@i$*Pt{HeW1L9xGaEMYJEjZRZh8^Gt=a& ze+TR{|78CTSe@aS-wT-227>85@c$GZ0h@yVqW@BBN_XmH)9$9(bmZgSJ~BqaYC9i6 z?d;^84E&rY4J_NIdkV14?VCLXgv>$^OhHRRq&B8NtUPqKf>88ZPMYXB%q6ab2|^k$Z2%a!xv&eM~sj7<6?TKBp49 zCu=|U&Jn+;#01L$VX`R#Dg|EYA4rG57Th;GIS9xHf+=WpNOwwyz$W7-=}Em-^R8+swo*H!?@75+!ky^5Wt0E;h4JX%t(0xybz=oXAA>IE59TGyOXvYjTE?!P> zg14J4A8>}gp21>toE=;JT5u)eK;dhXDPV^GMUyFDc>rgK3s^tvQ)p0-7}ZVJLl@fc zakjNm8X?_kq$oxkhK<`zuAWNGf@k@4&_H(q4gX?jVEY0Mcd&?U4BBFwAkGU7B8ziJ zmsjtN)$j*@j9GCp!%!3`L#ciRzx>cFt?J>v(gif=vwm(@W%OUymOw~@VIstE>}XHQ z60n^U8X%0}qc0bQ3t4YLxOwlj%yePkbXthkBsy0{33$Pc3jKl50BphicF;h73Jt&} z<0qk^DHnsRoN;+y1sV|GT-BsDG(3kqCswqlH?pyV*6UUT384QT8h{Nsfd=~j4QTij z?Ei$)0pSR1t!cZq!uvQyeY$t7cz+A0_kkqMVsYR9>qiB|@W1Fs1p+jH5YYj~7xi%e ziRhRceZ0g1wSX09v0Sz%pfG8hA;s-#TItn$zgN;`jbr+>A5|9WC64xWc06rsF6caR z$aimqV+EY}MTh9QkjqLLbmCaX--3se6Q@&mJbWZ*@J1&XbR`Ji7%d zFR`ga6CPDgm?{4Cu#24~Bg*SHIZqp@hh{YM%Ev7}pL<79Ux89b&XQ)Sc0{>P!7HX8 zu4WJ8Z^2I%y|oX>2)q?ivN%<6UzB5dupG)MAT>%a4YGWef!GQqsFX=XjF9MobOf~} z(vta2xEJzyto`O1_c$V+>xmd17a{4;2_AH*ciDP46{&Bu8@7q1&%E?r)k)ILn{9YA ztl9B?sEiL-G~k*)u64fVQzI7qASV4B z8SOp~ITYLm5+KV1xsvUrgtpvbp2n;2UZvg}Y_$nO6>D99I%yEoyt~M0<$fz!6rw&%P^M$%-uTLR zV-11#IU#QNNlHJ2E2-b9I7+>uhW}M=Ms?A+&(%BOlt_%+oGW?9ODb!@dOCrQ;s`rvnv#Dq)o?Wy;IDoQ?~C>Eq=nBp+a$OHV$n&B*e9fJmD zr!h)~r#4WiOOCyJ(#bW(c9oB+gyjXu z2JNqJU_XdmakC|vAjsbT3*ijFOiw=N|KELie|~W_#}^mnwj!Gax#ykL((}3~-E$Tp z!)#R2)A#YEj*dGV!Bk@;gR-?m=EAPE!;NEb{b;H$;+j7j*F^e6?{es;1wC+17llJX zis#vlH}Zhqa%cfKwVUha0hU*fE6EKCj|!fNOeFq+E()-{#E`IM6&8lFX@-2K=3pCfaHo3Io$6_HAq1k` z*?jD9PI!8mmX;+i4;}(aySUNf9&K+tiX)r5DWSv+DjNb{&+CDG=AV3C4}#0~H=5r6 zt2hAI6#N%mi~bq66@=daSSjCW1l@?4xy%SPQNWw_rZMN;{u@mRwg|^5=7-@;RQ|W) zdw$_H4Gb4}`Y(>4bAG{7`xU+g(dX=kGyj`-`VlN0d!Dt3JK`_=``t>{K*iIL8~7i+ zXxB2A$E3VqxdYb_Anun=cjPl(H!)O&?PW*SKV8fWDO+3MhYjmx@?A zst{-0t(TNFvCjQRZph!7lzECuz`oK?qEdoI!5VEaOYG)=4GRj)wR@dQyU6`k z&K(%Gf%%XC#_v%H*pL%cV*KAgrBFLZ+t$KM^t9}<^o%mlRyRb3J8RwsTcI`?4n2f{ z^Zwc<2$NedEx8kO2sSDQL-pjHlGXzd#d3#_8bJCqxY2K^J6& z#mtI01qL4h)}24l9*i9K>w;>G7u51ErWUR*)M6VfdQ+lbZan_H)S|SjO+mjy%LASB zIJKA&KoJngF&Vptw7YeSrk@)&xdgWyonn!9(HjCMDBal@Wi#aB=T61?+6omMBW^Em^$YrRqlaGtRxe7 z!9ss03t^HV?)3$)U3wbROmw!p?k;Wg$ zLckW>Z^uF`rz`|)GJcYUQsKP1oH^LN9i)}bwm9f9qS!1m#*%u4_jNKmu4! zS!npL)DK`oPFRTLBzkeSk@KI_7ZO;`&>3XuoOTRWwSx3B((V{+E2ucc(tkjQRffNN zpQ{dimv>v>jHwN+9gOj%eSDb-^v1$V`DnLt*<@_g++BZD z;{u!hPHe)y5ABd+olEWUZw?0!?gt;}aCyic@y9(O?k zSii?6U_(x@iS+`TzQd-3=*GTyrLmAh4?m=|Zpr30C-vb_dD@B{QPC||4M-QyQ^mF; zo`9$?a%=EIb#E%Wo3z?01lXs)F<}~*MpK78b-C-;DK_hcV*iU3oA--in?YOH6QzfW z&z@JY594ar=Wt*YQF~Z@l)YU7c%>dZOtvX+M4;}g!Y&`$y-@7GQ?UV4`)d@@$5s93 zRP4az0|l-%!-tRLV;)Y=2WAlg;5^2gOtTevUqQZ_o*4N96&u)s`|T8)?NqUWO~y|u zwmTv_UY}41@msCcIh@U0e!sw#sd;wZ^4FdF4e>@#aUlV0-zzq-At#E>cA?ncDRzOK z0`cZ5^#^6N*zrAMYx*f;6j%Sd%n*D%We*}9qXfPx_U@{4))%lhsFW7VJMzv4JhP-%hdFPZb;3Wc;LJZ;(S4huB`ZlE`DX$bIjF zpMAyV5`5h(WltVq?mlm_G$erid&LGePJB6b%-OtK=~Yg$&Qg9`yK91#II9q z_6x=S7b`aZ7sWOd@^I@Q*b8MpuVRZy5(X8c6kWORMYPL;P|uwv`^uvBQCY%TlXUvR zQZMy|V*j0rO_(`9=Rgu=A8<~^Hu<=bL7nnu9^YoB@$H+5op)3)S%o_^Vq5}qXwl}y zxk$qL2T(5~*;Wp(~-f0`<0HjoeedMql&0J?{>%WsRx^gh z4=Yj`G_T~O<4fE7Dh&EnBt2fX?HA#Z+`N+eK0SkaStII2q>1Pun!=}hVBBy=A%gVF zA~MhD78dgRP*hrVa2@Qgp=>kpzYaRe?r27yN?^Ma*=y^5f0=gi61X4IXzf_k$7!Q} zsMRJ8P7^}Iz~(AM`DiX`O-#OS>y44v8gqbG0PoU)a~zTuw)X&>KXLE_)l|tT8-xOG zio>_5hRx-9FU`m&^4_l-N!Lc_Zp2|xxTnrQV2`MV8ct_6W$@|a&4vs(1Xt5%yAPN3 zXSwszWd}OF6=)&2k9Lq(-nZdI)QaBx0?`kq6eGU96-9JT{>IUWz6LuCI;W^dn^=iy zy6=^8)A_)$L%*dC$j(@^ns_<~#i-JjUW!+Zcmp>cOIT6fbz-rKY&U8yp6UzLe%b+> z(wMD0pfWEpTF@pdCQ6mR7aQ;fHepP-!xVGk<1U|lN}g5M>r!)6Df8obqKYo2_(Uc{ zj#pj9GHQAOD!D6prOIBfF0-u78NlRNl(fx@8%%C67W(9m;bO|Xi_*RAY@B6`h)Aqy z?D}p9BL5D*om%1b;u%PHBa=<}l-;e%podnbHPxJLS}zj(L&E%b;isew5cEZ-`)9 zkQ1-W7|?QG7XgEpJpgc?$qE4mt7lCAxGs$N6DM65Gbqt{n9D~P=*zSU1*-8}xYqg+ zlRiehj@A(9f~Mc%7NQjuC42c0+gU_TED2UH3{FiU<*{KaE^05kZwWjutWai7>CuqF z^ILk@ay#VD`?9HIi#o&jJ0kU}>RhaQ9KlXE5iR$C`E5!4&X616T#s`z@5_%$kE+?( z& zTZ5y>6na zSPB;?y!yO}l=7mve!H#_BX>JkiXGyo@d@zgeT@-Hv zNt8(^9d6Ry0xQF=Cy8bvtwaeP$8ay71X}+6V-2U|{p|xWgbB8U)`oav`_-79KCT{r zC745$60Jw}rtMh`_QoSdw67B`;|p@W)#{shii$DK_E0|*4$7-ZV$KfPwC`TqKC3rP zT%)#$u+Qw(2TlSbtSlo}1D-ddTA-{w(LGLoFtQk9;8w$*o~wx9oi^>6Ia~LHGvm5$ z;`=fHyJdf{2~Ap0#8cEpF_&wR%DG9Ns^}v5v%SSbynvXJV?8Ij4>2G7S!CZ0T}9Nd z9v-}bDdS(2<3=dN%t)yEVzZOo%?kfY6;6iT%d1cIj~~c!zZxBMgK3F^k1y|eIclPu zbdUM!vlYn_>x4x*(&b>?>-Gd`w}k@&RZD6YF_Or&QtsE3m=_Yxv^Fru$@wcS-S(h4 z=9VJLJFuJ@zwYNBQZ!)k=E;&zBF{4z<*I=?kt<0N`UoeDTZTU!g&2^dK%#Uea& zpB@U$Rco^G6qyliR$NTT&#hcnmGb2MpN=I%ag6AcGK!mfrje{mX)_&7H4tjK&e-X5NfU*8=QGwvwW9V#xlLEThf8+*ToJv z&K*18y=TEP_>`dk5Iw)G*{uE%T+-dd=v|xCp%(u>;%7-sKXNGlo>s0WH&62x)2U6K?EG;^%o}5!~ejoJ!HsmDg!TG-t^$21#?8BOlywWCahSAgP zL;u>h{r;Zj4E^CND}h@B(L!hN&3!)R(|#+nT{hNXQ@t(myxj^b^rk(VZh0({D0Xl} z+SgWQzzqN7R%XB|7|#4&`nv?qXb}37fnW++D>Kg1q)){L`SqqSilVZyl;=;lr)Ino=bmxI96}Tl_rF82M}_`% zP|0}#mH*-cDZwvLsa}y512AHlJ@g!dfbJ}jk9&ZrE&Gdw?3PU%%bJ!{NYqX@MPxw`k^$+RWo_L`SP znW}96Z-vTJzybD^eiCq+bkUJ0RPqV7-am&Qf|aG;7O}~uD1oHhe<~bW z+iI!^3E=u3aDWXt0UWLi!1)evwB_z;WIA*?xT{8Xk)cG~j4`$6vkf$hYJMbN$6AU1 z|9Xc3Gd%fa|JdGPAOH@qKJ8~%2w0o3Qx*dDD*tvY#B~~_H{cUbVC}<=sl3y8OebpJ zu1O&1D^@mPFnsCOC=W+U&`Fej6#Sl=UJ5qPwG~yZiF8Z{e?;j82=bMYFbTsqB}I;$ zWjTXklUSnOG8$g`?_YgVG4w|eEduy6`M)GU@~Om#GcIAXH!s{ZDxXat=&0Zb7Roj( zc6rA#cR4+7@xA)x#KR{j{ntN7=?ih99Y1B~y)g+UGPGRsB^hdHd_n-V#;UARtwlw; zArMK^>b@~P;^Nx@tzZ<38~s$#v(lDy)l~pZZdG}zN@H{_o&{&s-W%*TMtTl)Dhh8% zEvvc9xGTz%wELug(p7M$z8Bi!=prAlNKbZ`9>MX(ay^12MBqUYRSKPDKR%qhFO*_M zuMz?Mp$v*W^h!#3&y$>^j2ae9#1tM_-0jQSCKC35rtoV@EHZ^$aC*J(Ahz~W_7JDC zyJ$R$!5p2+ZNqB3H+QI0RbpfI7AGv4@wZbR8^DiA=h%|HO3^fUJKC;8M%;wCS&b57 ztY%bHy6Nk|O0;q%71lCNCcfVqew=R&v1Vx8xuv!^cBW2z1#5_^WJaE`uU5 zICZ&EB*w`BsvQ!Q&8Rvevi^Y*ke@K^w6$Et8|JbfH8GYV?a>Zah#-&Aa@JaO#>~K7 zdTOI|mBfz|Kq$u=S_a!j&7Ddsu}qy-uVf`62aBm`(|7YK^BuN5A{SPCGBv%JC-WVq zifMDM=ASSeN-=LcnrUlJ)Vmq8V892EsqWtDUrXk~C}VZ%8ISf$pG5tDViV(qO>~X_ zPC1O%ZG?%%wfk}eZ52#7b&*d5iscEx(|mG$ogOb(?paJ)uOLu;{2Zl+6B4{C)eya( zE>TL>eCwu)lT&Ay>2b`yL-25+lms;^F4UI`*9J2ZirnBhnikiif_f!6$qwcn83iH> zUL%#}!r?)Z8{krWd(;;Zq!@zkv;!6yOOH1Plg75;8&B9*151uChZLh*8P;)f)K{nv z;6C$m19Jn;nEr7`eFS_bM}27Act9KCUEWuKU zV>)^rWBQn5N!HL|1CiYw%N`;dKRD*t(ocKfghDB3u|kaRfD}G)bJi8_ZTRO+BwS2_ zaTfO3ZRvbx{_FN9J5j{eA7cy`<~m-Wpm0CrCqk~?SnM-tCJmJe#BzV6 zN>dm+nLS|K4XvCW##Nd;Y+DN-mov9&c4*hWPGV$DjE|_ixH~10&4;VHkwz0r6jgPj zezQXa);`|7FVIHFT0P;z*i8D;n@pD4z?5N#2HmvQNtdA|;>7XsmP$Tu#BA^( z9oq<(UGW^Q=pkQtcOa_zsU~x7?i#Uw4LuzF{jcM~icMur|pb$JjTQIr0Y+S3*A$_^8Jyq+&6PH&#^iO0w zgXqEsZJs$UY#;|w`!dOyJMCmxOj}qUMh5VNY>c$_ZY_5Xzqs09KxcRFr5~}zu4zt= zJariPBKf!=8GtoHd3_U&+|B$xvzlH`OJGm*Iw~cX$&TKW1bALwfpMb;ok*`WI-g7V zizBo}>*UZCKQ(J^@i6DBPZS-%*=;gX68vN&{P6WA5s|JcuOdRaZi$Y2!V+s49rVb8 zxspoXo=t~fW6I5XM5tVt&zpz>UEA);GfK}Hf z4*F0LIN_ALomkCP2kh3tvpq;YwHZG3xt6WRi4X1UfW`dnG2g`oa(c=Kc5=-3WdjLw zoN)~Z9&tf~i-U%moy+u4TF0|3;$UsNtWzDssipF%&L5E$F6#xMYS(ZN=|^M0gHBtj zmUU^@EfFO)_cZ=we5Y0bBe=_L5BaCeT%Hd}UNJ?= z-K>D`XZ19LV2;g*;rij3z~6Js_Yo|ue0tl<>ET~4^~EvYd5`(7eTm3ikErJ)F#;b5 z{_^`7o&YY8t11I;nxs;7Ssd`a*Oq4#mmg^x&cxqAd_eH7t|WD$GK%9zp8D?_k=dg1 z^APE&^}q9*Mr6n=dixXT1F6jadOnEnX2-_b$=L1;oT&#{WYu%$s1}M9wNUksU?tt@ zNx6N+2Fi-SkKEKYp7s0@8CE%NDGpWBem#o(Y;Z%SWtVrU3fm5F zzcnIr>Q4gOTfXrpK{&eu!4$M(KJL>e2llFb^W-3$-GN{V+A$yZX%r0DGWy9V7}~g| z!7wuNLrwMe$!qTF6I2B6(6=~xp=6uEWoo_QDf-$%iK4LON|abHBizKep5M~+)W z;$LCzUd;_?Ac3LLiBr^uQ@k8*CoLtx8bC7owNn}}!#}xG8VFIaf5!B@hljv++HbZA z5KaU^Fa_=Kko)xTaIolsX!Sk~Y}m93ob8nMyWQNC0Rx2y%S{=>;YAOd*7u_XysZzl^-zy7331DzLrfo1Gj4{sh4kG*rEO3VgsO<0padsUBviBt-0v5EWxu z+jVyFh46;RT5?ou5*})9Q*(i6NWkS&;Ct{_&wF4)PJr+736`8~d;c$OjSE6v*QSVc zC76Nm-tc7I4Ho3l@C~diW7iwVYbmnouMS?KLZ9&vyCZIgZme9r7KKm2GpOQ-FfJcU z6->OuPQ*DMQ*C)n_mvHSedYxl_P{9zAf5SXLBPn?lBz^uePoP;)pkCD z+S$oF8TdI*8d$bZ2@CGo^IinL_A7{(`{MFSD_jR5?t&?_4E099b~_bur8mQ zu$1m4-R)kh5MFZ{n@K|X_*`H+cHojMn-28`j*FtR>(zgbuud}Bk__MKd0*uGTZ2eg zqF)G0wxdVxNf1S=^#x)58wg7vHos1eb=a`5_4akl-}rH{tq^y0IYv zJl_)*upuXe#dASe--XnG(a;$T2;vt}mEL{^sYw6$?h+Bi-2AO9qUplbqrQ@XSuCg9 z(qCH(WIlkjeB*W3F3u=DzODxc%hpHfKyvJ8_<3_lkf>#R`Ds;fxSN($${H5`17XG) zON}-JA^ACjNwxsfGIe~le3Gc*cfq@``LrS)BrO#>FkX`7P$`4xajf$Yj_UoohA#sO zX3MP%VoZA!hV-iUat*AVg4v@tmm48^xZ!Tm-@eV)@;Oz&)Bdf&3e?hoCKbHPOI9Q# ziCdng&urzQda%$+HCRswtHFxOWQ+(k&PU3g`s9>hB0E+QtGT%oJDMB2zwCPekjLitHQPcib2GdV@t8!Z)Gbypv-b)hLyK|jmBY2|IBY5PG-t}F;_R&^aep0y z;}kivj-dw05ZWL^$6rjfb*Z_#jR`-X;Qm~3;~`Ia{U+9Bjb?{C>6iGg^&)OwZ|LqP zs7ukc1)G7adv9XgkAn1uHXJ@Lf6`X}buqV9^n9RJ)h9K&8CPFQRX>3Zg&>SanJ()^ zvpX*^vLretGu2lH;b1=a!pz)C6wp*C_l@2p&qo#~t)9JZ(<;oOvqoZrbl`wn=)y~{ zQlYe%%UapyzgD+qc){-$o4%W)x0xCTHbfsBic z`?Yr@l60L9zM5WSgiz@niULuF;x$>lNB8}a-Ba1&lbLPhWObpZawX&_u{xATXP?jAWmTHHi&5!IJipsj5Ia_}1a)KPR0L+9j zXAIH&=TrfPhpxw%MBdZ)bDI zc0ZD1O_sm1WS@j382JkOi%=;i4Kx^^GR%|W;cu#f`EEM&znogbZ?#i5hYzxi)pjR`9|eAf~cvKud6}iCZszrfT|8*I+AF>T9|HTg2m$#<&+)QJTtQ^rG$YCDm!V z#t)thIK#R=S5TQV`gI-x&$&H>dgc$;LmzKt(PxeyNp{^rw|y1qWk7U&X^`Rwywk0Z z=%4ctPR@JL6#InD@GoMpAbJSmUpxd!#JQWzZu(FW=k*Z!k>%|$qZTpLQOjA`UBw^j z$`s42@CD;=Nmbu;&k2J1(Ntfg3$QZl8ybS>!$C-cVN$fWNd~gtf* zUT0<(ga9@Orod{9PGj8xPb%tR1;}sN`S;HGxxWno%X#^!O^&eFv~70%p?X5&uOnIB zbCax?YXo!k9Uato{yjIh5LEkc4Qx2VCMksC{zoF!$G*rv!kfP*n_$P!so&~Sg$v@nRta?*m@>^R^&(g zK_%H_TJArPWP$B1-|!#^DFPsvf|gCdcglmnCgUf0aQb5p>-AT&f?STzmy!1607>!v#pf^ zK%Rn&{PiMZTgSl7zGJF04-k5tPCk8ESK`K%_s1+2*p9a36v%?h;oKz;TuC!3vmTyO ziyK><3Emx;0Fo4~X3?6GNbjPogV(GAb(0%6 z7dmM}K9}+!=lAoumD;bo0LOn__n)Jdy# zWznsV!>1&qOKt>_MnI5|k`R#Y?(XiA?nY@)1nExcMi7)#LP9_(2}P8U7GCrSKV&`c z8sI$oRS*1q?ODUUXJ&t{ooxGR0ji8sszpFJO07b}6p3YZVWU2hMM%X@rNhUVe3h@+nHKUuNKysMb7B_qKw+=d?nt6OGc1uuaZ`MF3}-yuK}oRhdj`wZ30KQI2?r)qmjU z0cTspSe-s9S%3dbm$UWA$6b#s^bI&%(@?mRwczK7&w%qc0LP)AW(K=Z)PO`JV?wYx zrZF}cZ5_^|x+ki9E>EpBaq15Q4$!ydw*wCQK5&34<8Vt>+t?wu13Sv~VPqDbxDk4ahauY6JaHD#)A%w>UFaTR?u?vaIo z1hF3l4p5Oj;IN+o=k&mlZ85t-Lj8EwpLJ2Jxjl_B-OP#`g%-Yi)ZK5IaH(PXV4q*0 zz+bb^@6Q3wPq2`cKD~He!s_k*S9(jPU6Dq-!Hjm-a!V=e*I9`D*cN)6 zv5tZ6Z&K^@>U_YH66a~noLFr*i<(a_U2B9!mmV(sJPYmQYj)IZmPBlwb@rUFvq$)w zg>HHy7AVOt3>KeR=x?x4#KP*{!LO0FcMHd%eItxgtQRM=-Vx&6E?N+TSXBG);t#YC z(6{Bcvk=F=g@7vKlooO{5s6qok7O-XC|C`J!oXbsmsr?#u`R-q9MPQJ|6(a5h~ub* zfQsx{h~vycr?*fuF?EEA0%2<<44SF#GhRyTGAhHG3lSPd$@uTNsnk#ixqVV*`wo7>=rinAqn+5vYRtCvg~iHTeQfz zb=%d(;NFr_NSzSTADi<^1=v zZ)XW^gVF2M>H55(U%{GGA|G`y3t8z@YZvP^EDx{FYOz|VKud0iLz=cjr@v!F#Y|Ll zi7xYXPQ$tRbYuQ6bczm6F-a|J9HUJ|DoAdLZG7D2Y70XTA3uC3s3TLNx~i{WZQ<}_ zTK`>aQ5mJlO-9RUG}mDhVIzpl?Sep4zTp0>Pw>pjsY;8`#XSU$lMGfLkX$tJdD%5X zgkOQz-UTP0E}CSsT_aaT;3QDc@Vdk@_gYa-^Q8dJN;mEm=Vg>)U#^;qqU4DZdfMl4 zi!BsGhCVi}U%v}p2Zzxb$_5=Oz0k$JID1*|zH9DeW%yQtY%fRE+j7$hXl}b9GkM_} z4mvc*(3`k=NzOiyNY}nDbopczH(BFi(Yk@|(Se5TZY(Twi5dMF=w^sk9IHL_78}&p zWiGi7K=~2n74Ejz)_4nEIbWwob5Z7Qd_mp-m3R)lXImc|^y4xYQC#xQQEbJft|GUE z58ST#Yl9L-BV~jruW%VZyc!!_=K+H!u>;*alo|u&3x`7gxMePvQuda)_=wCdJsFBD zT(qHUZU~-)rXX(y3#d{J>+iP$Vp$|!}{^*!&(xCrGhacd(THoE~;<2MHOWbCLb(wnZ;Oj=gplOm!Xva zEjhjM5b!8Ksln3}T)19UWeHX<_3JV(j$>zDD~qJn)FSmf!Svk_P_U^2uZz!p9KKYY z3jv2PUo~mu_R}&i3~&?$7F{c2__VXk>x7w?=(o)4Mgx97oPo!j`f)R_Pv~*l6xHsd z?w3{35^|zvXI7L{g35a*B8b%xiR!jq{Zmz+Eps{TG8eLIjb-#XL>?aBA8(*tF`XOS zpN}f~$6G8CZ}J5_;~-MEaDV#f!qgypmSmaJqRAhac>#S}e!I-;=KeAlpjYLGJ3qnN zl@o-XY?;f={hSf#F*;?=n27sPOTRdVi0TT&OHZ`V(;q6{y+X7X)ov(pMe)3LNd^*h z^JvZpRAet_ym^*0o<3)cXi9D0!R9XVufi;U`v}_Jf~aCWa+boYiJd>Y4YlOl!7>-1 zz`aUvKfG1d36iU`(Bm!~K@QSU-5S61tVWO|jbl=xZ)4TgT;yi5gavaXWR>29cfZbt zH_vSN7oTA#_RWUOY81wV?Vb#p$F*S-%r@#=mB2zNYLfq(ja@1QjN4FT7qyCDv3$O9J+B-J z5$>vPP41UCP0lMbxi~`N51Z#^kCAS=7%De zKS_=wFO9TBPvBZ@x`1idV~P9Z!1ioP8{;Y7)FG;PZE-~wl~!%0_%l-k-@a$&8rE@mOxbJJ_TTprFx-UcVcHpnG4#H-eD^w{$hOjap%%6UU^BvNw+h zf~+cJc)48k#`q?yR6Z2xI9bqZ(HNx{UU4pd{jyh{BwHs%G!2wzOw4&sW3gvK)gjT= z3E}71#o-&hn-I7n7(;?cgyvYCk^P8_-W#3C^}06893|N9j3>0=ITtF!^dONimRmQ} zue^EU(yVYxl8=cz^lhW`t98%B6+3YkL*hn9rD~*i5uQUrney=C@oGNfVim+|;EMvn z7Yqux(YD>X**>LbJ+g;ZH+lXFYYeF~&%_wn^x-om^;$~f-CV(U&RB?AujPDaj16eV zdOYDx6_Pu7$ezYWOvc%~xTm5?-YawG;hbhFnWY7T!sUw!B=yWD>un_2B%D|r7X6jN zuFOGi;3THt6*RmT0~)smAO=*w;CcjMwKu%c`+^aVGl+f#56Mlzm#sD+Go2vY`PSAP ztT5$#GtKllPJ3a z^y%_%49#ZLcJ~G+yc$7=4}%9vn+~;Cf8OB4m#>2pD8y@yfhhU5)TARbgGF^+X}9LE zr_l7sskV3)%SSRl*L}`J!WvM*=)UtlHRK&UifxtlQ-Q9r_#PX~_d23Rb>YbYIkPqK zN_XCKlgYrDjbF=v;`3dMRv9?gp5c+Je=s-!dpC4suH*h7)YZk9s)9PDIKD>xsoIr|&t6YE^kS3J$Pg%*o zvU33jq?K5TF(C?M;dh~*O_4wD6nTkn2`K%2_82S^W?tdrCZMa3`5Kd2csI!NNVnD+YiA{IBfz-xm*x*dF>h1{t6JF!HKkV#$7(z2}$B0 zb`cdW=q)ItT0u!qBACdt}}nLjxsIgHYF-|hz8eAiyT|w58KA(u$IJb2*USmVQjR<5 zV981$o8(lNuD>HYqnX=hF*$nZM3_TxaI{iw7MfznPDgnV-5h zM&L5fam|M%8-LgKwmWH*clAfRX){#0%sJYcdt)XlSIa<-7bp16%=d2&I#A1|Eo*mF zNQWJB&_O!_Q3)T5r2?!jkToOciw{f9U1ss~l3m5xq{cF8%-8=w^8r0@znz@8_vHjs z8K;yJGweL)YL~)Dr+l-64s#`&d``gw-?@+7zE4{VX=QZW$dnei@FK!?H~bCJXDyD8 z)|m|c&p+wBULg0o>~+X)u^1c@4w?x zzmvA_$*H!KQn4T3d_WKEpfP{JG5Xnvly=E?WnQ!fv3s=?@(*m^fxaz2wC^YAs5wFC z$@+9|?YHkhm2t}UT@LPgtabz0V6rkzx}=M8)PRIyH6bJu!!_@b9e>jC8<3z|N85Lx zB75!ot<$&f)*Ch^OrUXGv<5NRWjCQ9+(4u!gN&T zv)^I@RmLe>Oku`LnPWvuFJU>Wp?Jn`zBA`B+pWc%sHU@dORWCLV;T~~bF{?-DzevN z@|?cKd>SQ~g|=MECro4CDkQOJ_!e2TSWdJnOzjC@f}?u?&wh&u6nCV>1ll$5VDeF* z3a=m(`AQP_{kDKWv>tIGmF0l&K?K{En?w^YC5c|{9{?CS*c|a`CQ!UTPir)Bh0C_O zD!4@|&uuzIw%;O!mmH6o9vo*YRo#t)1zoIXS33zM2pqmhg8pL;qzcL14DVz{TW`z2 z=dmtXtz-ydY*3hLo4b}G5!h-@lpRGz8&;RUPCiQ8QeBK)S|wvLuzt%-^9eqKxSqf= z4*JXF`g$iyy3HihxuPMH#!{n8*f-7eyqb|q-Rm0`&0ypNLIWq>v%p&1HGnEK?ig5l zPo*O8yn2299^?eJ?kBYfe2eWq->sk*u%lS30tg8b4vHX;b+XIw4Vl+h7RunL%}H-j zKqL!O>OyeJwvM33EWxx|$ih59&n%YDG3kzbF4jl&jEHr*KL~QVBQqOesp1LxbK7`P zTp3|jp?CeZR19x$__6Za-ulAksZ41iepa)cti>Ti=Tzr}UPKV;x$~K2t~J5ZAI9}@ zAWi8*F)opsR}r9)eupBvwmcp+V`dLAayd7>t~to{_%8itLf5)3WhlIr6g8cmnz}VY z`GL!TCry!O#ro;F7BZ7rS9`<)w*+I;8YTh#JbR93aMeQH+B#sg={T}u8KW-Z>~R?q zya8x1;$T;zJ0$E;E(tH+?!+f+)Asf;TP7PN;xsHBtbJeD)_HknZO<@u~q z(I(xXf<_9)`7b{6bQQ0u9zK6()l^Nk`3bIbw>7+$yp#P)t?Uq$tp}FCIT60>$xcQ> z7ONO+)N|F*?5ev2-fu1bbI6_d1CkJpj<&C3@S)1BSrH3F1A}4FiP5L!p1%eo z+7Dy|cIhIvi7iu!XXEIJB0m*rNR(Z~Y^v%!A7Vu?CS(`1fKsuXzhRBazngu(r?@VQ zyJmUU*7i9y)S@H}I_pZmZ&+v2)oU;64<;WWK)6e6Y z?O#NBO5JnD?h~?G_8Ys&3QePOL7U(?PZFQyw zSK<$-Sd=g>m4%#O>HjRK+rMO(SkRaZp>;@{b$8O#u>@GBE$9u&ryslTq z+kVSSb%<3O?Rwr^#}?5S#4$(7@WaQ9O@QUSHgc6R_)_Lu=bBs?XO-#{yOb)M!NM(a z^-eGo%E1&KplhB@;W;ve2WVc-p?3=?bU*h2s;ncqkB`J%dDbKkPNCb=h+#0D0@FxP zHhA>`<2~<$+1`M4k{#?k3KaN1vh!#FL|}jP@_kIxkaH8!?LOwT4&4K71GyI2{m~4T zG)!0bziL3A*B`!W3ZMx%!KCz$5oXXy@UC@n8X)+!?elN6@Z(Bus`$GmA9@T<5DL45 zf{ft1a`9%!b9i1a1+_*(qbBx8$j`JpK?}T}5c;^l!1%$yIl=b+K7)aH_q&BZu4)mu z2RmUvUg6!WS}CqQZ!Re&Lz**(FoDEv8kkl2>wL<4Y@cSj3RMeXuju6RI2Og_VTekO zO)@55%bm|=UgMjZl867N#_Zo2NJZGZa^nT}hI!4IPfzGmxovLUEPM4Lw%__-i6zP8=7-74A+8+%Ccjc->6$2l^D~+w#Mg`~;(;CkQ>+ zKq}w9Pk}1qls+BEl5M(Ir|BP1pVn4s*4>JlN0=BpK!#Eim?4zzauFF4#CO!EKt=X^ z%6EF7uD*MwB0tgC7e|U5Y0Us~Mb*s>T;WYGU?PYYx93q6-KP;y+z}dCKE6sw6Eht} zFx$S35v}^nFlZ=Z9^V@cOBBwBWy(SHU?WnX!2gkrNP$i{KBO3+`Ct1Mak4F%M0=t9 zT*cZ?P#c~0tq4p&CDo;Z%nfgRp}DHXvR`KrzGGWNS)zzZO|gD?^4)+{LOCNV8LABS z275ruRD8-9^CH7f9HK^6!He(m>kYnnW|0$GME;va26K$USyi!fYrfyrHa zOC%F0-^oo@#I}1~lpLj#aqew?h*>jU`zJ^c|51wo71^^0|LHB_HfezYT51)s#1DS2 z$YMOgCXjmWDRElPyoEqQKeTx9a9kIv`@la$(6t)-pr5F9dD&Dew^-nVbG^zTw zsE9ax(|QLEy5N8Uf6d+}K$Er)QQ!niy-pB%0*jpBs>fx^49P@F5=-o}TLtXfa$uVT zJz^UYkJxRngPV8yoHa)5ejQu*kIfco;YaSkYuvPIz=Tkrz|O^Q14?8wj9@;9)Bi?Pq1S3Uv1p?RD7 zp=~ZRwUt8Y8dfRTpcNG9=BrYcKaedz-U3aJva1i4NUB*LS%&m2^?h$P?0^h2%MfRC6|=OdC+&uWfUu6?`$pUtoP>$ zpahDpE_e9Q)UbZRK3K^O6nNwox-Yi0ES7P>K0=r?fFD@pMG#z$G#d_KbfDOZGb6vP zatH#w5q6==A31JC;}Ek9$=RMC6Te3qa(SK}k6|{p#$=b`fH*+cJR=T5%m6{np`Z2y z!~qK3@0rmID#Me{B1?0a(jLH`m%Q(7o9!At%5vfH;9?kML7C^TBaXnaiQ@$)tIN~Z z8UuaTj)QMj;5=Pe7^qDJLOSTYfv@ z2<{UHs4`AToK_lwan>^5?1}c(r5!cbdt=j=AG&@llW@B;iA1D5vIYqfJW3p(B74LU zJmL$W8Apd2!BWpoB$7oY$kojlqs?u0LOm=j%OMDLYG(K$ZSqH-3(1Bp~@JlWt^90?p zKS>jd+5@Vl+df;xf#IP1CvXU1j|t)bz}6M$+w$ABu0s2*D^O*evUSB1i>Zz!4^u|U z+6irTdUw6+nZ`=;OClK2`zc|mTU7TTK|)7cSD+$$t*g+HrtJi+YyWL1!4Fr|X}KQa zrH7SpHdJIUt;I+sF6Y0^WA)bNAUkMXfdY@*!U zuUl*|Gk0u0R^>attPIc1#(s||@M#Wqn94Ae-yKFHWl-bZl5w#DdWT@mmB6pd_@e0K zp5M#pGKaPHh;!bQZb596&wpIxZ;NgkjfudATtx1;(x1Uk%0E)G?G!guT2{7q5@V!a zLK>R6c5rNw?GhESwvyFG?3H&BY1H;E)7X6jRA?Q97b<6$5fN_Jk5`@B5i6i%^@cO_ zZAx6&++k5+dRfly2)byh_QX?#FxRf9rzt#dvMY=-w3?0obL(=_5L{%`xaqtTuXcvV zoLB-Fk!-3eQT6=u?Qw@(pA@>@qF69^ z{JwHoJx)I}C^*}Vn~d{X7`9xmODdQg<#di)uB|(l(>qNk#dLOJ>Y-v^ZjB_D5qzp% z0Z*Z&hqhQN1zqg2J9nds$vwFPq+Tg#WQcNW5h%g_)H5GJ;fnyeoc ztf2GZN4sKs;lxGoO4KdPD1KeWhopUZRTt{{t%hZ0T-HYgNilHEW9E32uB9ywTiYf# zQNAtWyOmm}#&Lm}r+R%M!H(5;D7-?kI@#!&iO5KHF6rtGiM@@+f7n6XyR)L!4Ki4i z0)kE+na)mp8EmIJA1<<1Wc#Ap9MViZ4;Z4@Cy?+Ve*&@cQ0O1Gi0^X!-XgyI*n-!i z7eKR}Wl-=LqV9atNjvDH*boCqW#+0i_bicNk!y9fEUxw#5us!HqmzN3&x*!I#U!Zb zkh{Ac|r=$+h#|8SQyIC7V+l~U) zn+J_MoFQ+g&OgJHxtJ^_0jqF>vlE*1pQ`%z-9h|!@BX4Ge<1Z*g#l;k`{NC?gLuuV zXLSGFoB16Axdp|p!m+JIp?BU(nHODHS4+LFH;eiQ0v+hv^4kGjcz>Y|(5v#pou6P^ z@DqfdYzJ}S{nP^JF*;>xp+2q-_9SJbr-PF%G*9B?^>j1qvRa%3u)2juYB$wULf%xwEzk{ zatn9vkGKS5fdw;Z&yHKrfK}Ao@aTTpZcO{&!@GQsuIjQw8p315owbU_g7k~4l};5r zAIuOHrzC7SIRK%qI+8>sVsS7w33Sb0Gd2k{h z3bxJr2dWe3f&1<3CbDlgpvpL<-HJ$srd%WX-E{o8g*HkdyFIn-Chj~(b3|lty;-f5 zQwj+ZIchhcB71fdIRd8>*bN=oGl5Jc!-Q)+N7dzd_=cKfFe!>jlabWN*7IT48DAaP z4Jh!)EdV*|P?O!;9!7bw&=$v1q|S9Rpn5Ba9v0qpy(agrkA$-D`Sl8SPl*2JaJ~GWvjJ#2`k@~IXhQz}w60f9)XCK_Q^D#XWMIj$)&iSi!3??|%;`9ASU;%I zaH0GU}!^kW=$<2Ud;>y{SFuXhsLwVLt z&X*t14mBhk>*YMg7vo+rYy>pR@j9yC>g=NJ+z9 zbWIRk^Q9c}!;}j8%+h_6)q3M`p^r;9SO(L<9!fxgzh)1m6S(CB!Z^WIkBgC@I5rk+ z?qb>mNu&#RBJb;u-CB&LuzW|wtx_J=9vPAJ>o6jEhLOJ*BkJEUf;`gsGTx#hB<;8u zNt~?nQzX|sRghy+f6k@<5eU!c{C z4OlB43c)&GdnG0UgTGkHemk7dpv~^`o9Y9M00kbog%e=pq20YJsgYTmLvK1TpftG_ zACc2x(qicB4k_h7Q2lZ@_+Zx}puk_VYtaeV@N+P7f*UxlTk6@vT^u;MWHXhtUJt=H zM5MIwt=Fh~Af>PhVP?k-PW?K!h@H9RFLsN@H@6@<4mkRG&DrxE*DbE~1u$8ZPnzgK z7VK2_Q^Z+9&0(APpTy(ipe&RpB?g?i<==G6jhnVloE}=?rXJHRQkO05A?TXeS#`tA zCO)p;&A6`AwkG0q*@v{&mTSApa<@^bBp-CTYxI#lx|78gGA5QlXL->Y@4Qf zWdltC4V6hc>O%wfVoWLy@??=%;V&W9X2>YZPN`)ij zeB#FEn01Unk&LP+(VP5q;1;03Be!q@x8zf#MH$aJGH@cZdL2*%UXq0;C z+G^0dl^sm60Sf%>rr7+SyX7Yk$J|-2T?s!VHh0e9gZJC><7M#K8I_NyF$&_l(lwPD zMt&V}#LtNH7ZXSG8*$)4kE=KN#FC61GplX;c}1wJDaWubddX3QeY3~Dn3@g0V# zFOg_#Nuy`P`8SEfmSlogm@qGL{+Ps3{t#rAd*4JPA!~8k0fmt;5ISz7Guzalz(B%U zoj_Oh49et*YlQ3h$a1ZQPWH>6ri#psT#cZVDmTeXjS__|8+rW82bD4q}}jm9{pYo z!y9uJOUZ@B8ihPB*vBwXI7jEAF!6Hu9_qD;Ml$r8=6i@(N15R|JezL722Uu}Ld@l< zCux0FYTolqA+z=izD4`nGHZJlLeMK1%;(fx(zQdJXxMa4+5Isz_u{1Ove;GWbi1E< zjWdPnImFu<4i>Uq`(WO1m)PF1fQwXL?PdmjnS5li$%FDf72zwy{ZdtNoGA6i4exZL z1TBKbQ9{NP;~ou?#u}q0HrX^TaFYeR&3GE&ZxG)?dC%r448<^Rfa9k}&8L`qd_2%j}t#PaSP&w<1Ri$uKWmOOc;!s1N)20^cCS z;t3T=npr5fK5AP8>mrlwFwlcY!$((K{qt&L#LVcV**D@sv{&jVg4NaBh;`$(B&bq%jL z9O`DGuHcNB#9c};Kf*$E= zb|mh?mb+OyX%IT=%q2*iHH1*RT;uEOb(%@#m@*0OO>S?xAlx8w63hi1{oK+VpaV1( zxAiw2*Hg47@tdn-Cbq+GXjiClH?GkunhKUwg}%|7S&}_iQu=6jvyj=z5aI@niTdzG zM2tR+aBNXIg4#?xa*W5$mMN_^auYCz;Xy4ZLS z9jx5)dgA7KFnoPkSh}hMqJg($NaSOme>~RU_gzxD@y?c=W%=2r!uOQrcRLsICZv`F zR1xZo!7)>+{FhhJDfnmw@<`tgFI;jTv9`f#mRDptM=B!{er-hX?eSA8`@(B6QR9A< z<%IG2AFy8Yr<)CW^AS6kqOo(K1xXB}{!{AwQ_h71dfIixmym6J zaHfU4tsCrX-_RH?m3#F0eViMNvuwH#>g&{k1W6uEsep>?rBsrqPpR%1@sOtH1({IE z?#i~I6uijPW)^>Kt8LSV(p+E=T-|$+QUL`XNvVL2Iy__wp!DZZXjlMw<*o$xn;V_A z$j!rZBu?-jZeo|2v#ZF9$@Vg2=(+E&2nKoset7Usa30?YLQi&dpXC1Reg1;QWT7fWpI4VLczd>fGB*!Z*;kHA;jfX&y8FE)U=!tg7%uGwzm1n_6>m$2E*Xe zRd^mfdl&5fF$n?DBUQ;UEh@E4Up(C|mDMGVh#C2+rY1hcl()XLAcOD^Bm~d{_rsU` z1RFM-AoOH=J4o#l0;n=hNr=9?DFQTh$cm;mG3mACBXRhsEQ~jkxbrB5@+nj2L=_-G zQb!2^RAi43Ql}>b9Cu-Fr@`mza+(Z<63M7H3DEoPK|z!bMgv30R4rvQ2ZR6${3V3A z1evTFJJj@^Io=-5YN}$;;ha=Djm~cNy_#0ej}hAv?)x2r3yu+DU1l%mH%h`FEv1#{ zZt_6eDmP~&2C>BJ4BG~V?so_#-_v8y9+-L{FTRe8wdE{HdP!)8fFh3|PAnWV8@!x(ay0J=L&D=7Eu<~XRHI#kpft5e@V%6s{=)5vxL9+CDx|HX zrL~h2uAU)z!+~5IFWi+Deq;JAK}-F|wN9ckErjb0i5E@LmmCIe@UJg?mMv%C`JzB$ z2Ya8~oJeiHNpyT_; zUcah(fs5TA&~rB0s72zaV%OMHxpHkDlXte7G)xwV*P*G# z8M&5V4)|U4ke%^y=HzdDue$QQA)FXyhNg{SGMi#Ec!sY*ObxnlQ4M|Rc7!*i5KYk`W%TYB=Ubw7=(ZxyxL!EF@QrD@y*0|C6V)MRLum=_3=0X#Ak4x{)1n^a zRhdZIn9tSUE&`jK#ssEoMJq1V69fap-f(G|=yd~v(2Skt1@f;Qf|FAGjEzZtP<>sZ zX~~Tme!*~;?qzT}MUY2;U>?g7kzpf%f9(*2RIrI^5RksX+3agjh4sSiEqxKhjdbw5 z8VHUYR$2^IY}g+=1P!g1+g>XKJThkbBU z-XWNh+{e)_HHsZI2P4&V;mgpKIYO{!Fd;WX?{M_ifiunWY7~mVo*$LHKHcYh!}R^N z(O3)-Vd8%AWcGs&L2U44&8ud{=&s4qcJQc*fim3T;dp4(kQB0yNHl9EHGZAurOq_} z7i(VctLAZbub~>0MoMq39#`{|H?oHNIz9)QN-d^D;i=B{m{@^9LLg`Jz9)RdB7`LM zXK8+0#5m$T`;vp)cP;;J9fG%>ijxY^Rk+3;Q}Y5To1PwTHewmTqM-+OMKSy)U@UbW zvP<{#j6O9yBW;uVmObujHXQ`NQi4#aUF_Hng^xpZDbf~1dX z9;nEk=A}=s`G+O(u5C1RpWVqgG7N;!?b+>_TXP3rAt_ae zlg5zK>NN(s$%l%oTRd|mJLMY}5GXfL1|KOxuDI{-Ck7Pw+wCXTp4^)<@nn6)U@G$d z%YH(tOEp$LoT}N0Yw0*SNsTnC`#qgNul5gb)CsyTPY`;to=)lgp3bY6&8%^*O0VQX zHL77;-KiGFWt&$HOBSqT$*=Env=RGtHj+NG(O+yM{ckqf?!-ZFA?`+U`rowCPQK>s zt2|rX?!UxFg@JUVo+acvkJr*C85FTqRpqs?nwh<+Az}<&W(nUA9N$Jv>0>H5RZVc< z=bGZ260f1@Y)~gGMGm5N`ows<=o8ERbQ|pp1L$8mr7-GU(=p0=iCd&!tMSnIw`g4L zqEHaB^bc&OdEXX!v7HkVBy&_4Kt=Y1A#-|R%*&xVeo4T=8y&g==U?AD&Kp^5*y|O9 z%sd1OiFTEv{y-Q&fxko;{P(*kuL@SyZPhi@NNs_c!jm~rBH%su$Fug&U1CJuKG=E; zDDb!2dJJeO?BQOX)IcnyIZn>q5-NBR4c?9L*GQ00pYBj;)Ng(}r*a=7K=0CThY^{5 zjOZ+LmRzk)$9-Fl!XRaD;r7(N3v9$!Zs?_*6nGOyjK{CTh|C#A{$h+6e8b45F>!r) za$=ME|0G5*z)=*~=B}j`e)vl;QdT5Trj?+H|8S`hJPtDg@s92JbZ>KqVPYar{zR;a zrsHD-g_<+7?V*AKYFe)EPMCglsc!(-E3KXf&#~-<6m5%$ei=r9{-sl5L>|kKZsx%Z zo-ct2n9a-A2#+5OJE1QJDVmX(a@O?WLqmdOk75L<$R0*yPmhtZUGA$6WOs-L9zRg3 z_Rv~&w!xz}NE{GPk8AGs9_3m+zz9&_FTu#5$RJW#iuUXqs&xtcoQ+YV7uhNsQ%~C@ z&{134_}lGLb^?q5t>`#~TYz4>-_9+v!_fa(wnFKF)70{%mR1G>{+kGfb=Km7-wdN0{5USIjdN-e@=Duda2~hIU0fbh^{=z^UE6CQm(n zAd5-MA5Br5G&!f4%)9NiU&8ty;zCWnq|T6hc3oiA z6MH4XuPR`Gqsm^F>OIR1UbRLIw&VdJZk0>Pv1Xj~9 zyjZ{7n#Jm|H+rjg;qmK8YY`R8=7p`gG#4n4xP zo`F_~Oev9HV0miK5j)5fFKil^h(^$YZ$V@*kV$y*fFTP@g5(aB0HW-n&_8eVHh*vQ zw%9u|Dtvx0zhY6z_<8XInIuDF3(3I>VlvYGjm4$gvF?- zK2g;J|IizJg3x+yI0#(q@%GEb;S^nbLQ(KcBpeq_*tOQTHdA`=6Fa!T!1%$yIl=b+ zK7)be_WM?WD&v&t%*%?5IBwiteQ<;27GGS?!10`AcV7)#p}~vlN#+sq?T{e3qv;G# zk-c>QS z4t#!wq)(z*XszGUju62-{0^bi_*8P4MguAIl#l4p5Oj;>e$#I5)}8Nh{sO&59As@L1~{ zO~i(^%z_sRE>Pv2;!z#b^*kUBP~a~i4u*mE`v^*$7om1JX>Y+smZic3)1Pz?LwUuQ zsYwZ=P#kOm1Qht&Z36UvPMj0$$q00o>Ar@5UgqCUL-PCmz?aC}mR;-QXVxBktO>f$ z_Q0rGC}t)C5+5O0x4%uX{pwyna1G>MKX7Jdn$ont@7R_*f1S-^MZd8wa3B22&GYoe zH!&N0yE3R^#X3TIyb{TXg1h4BW6+Se1p}hTBI(rC#i%cDw?#qI_g@ZZ zj2mHs9xwY3kAUob5<{TK=jk73x_nml!8C>8;dj!yvA4HT{Q$EjVY`mq4FvYj)(dl} ziq}h~ZZNM5UP|YGA?LeXfcT-C9ojHI^j#(VsG0bssWDfZ>Vb-xxaz0y_u(i;FFZ=> zy3>IL{z4v+pNcANR=fDs9GE!i)O zL@QxS<{y6T2Yz;|>oRNdJ-6+hLNKL>UOAU{@*(9bJl5r+SZ4Zq_)^%g5I^(-Uzq8H z#p5!ib5WXjhQN*ysz2jzS924>&#A9$3dcoZe6JrkD;}h9D60kn@}bZ_t{<2X_G>?| zG#3QumeF&J;ewqCPB^h(QhjaAH077i2V^#(+?LHcJG~3X^-3FLUxJ)2JNRs$V~g6{ z(A^!M+zny9DV-F7xtT+jb8S~`FhY3RdCa$zn_x`FqHy42?-bXn{s4GB*#1&LV@_Jf z;obqASt}fYx{LyD_Dlo>=1W*vi+s@FTP4JA_Xk;WDeAr2&1)B2eUv-=&

ecf&5+ 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 [codec]\n") + return + } + db := os.Args[1] + codec := carbs.IndexSorted + if len(os.Args) == 3 { + if os.Args[2] == "Hash" { + codec = carbs.IndexHashed + } else if os.Args[2] == "GobHash" { + codec = carbs.IndexGobHashed + } + } + + dbBacking, err := mmap.Open(db) + if err != nil { + fmt.Printf("Error Opening car for hydration: %v\n", err) + return + } + + dbstat, err := os.Stat(db) + if err != nil { + fmt.Printf("Error statting car for hydration: %v\n", err) + return + } + + idx, err := carbs.GenerateIndex(dbBacking, dbstat.Size(), codec, true) + if err != nil { + fmt.Printf("Error generating index: %v\n", err) + return + } + + fmt.Printf("Saving...\n") + + if err := carbs.Save(idx, db); err != nil { + fmt.Printf("Error saving : %v\n", err) + return + } + return +} diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 3e90817bc2..c8708bbd84 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -6,7 +6,7 @@ import ( "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" carv1 "github.com/ipld/go-car" - "github.com/willscott/carbs" + "github.com/ipld/go-car/v2/carbs" "io" ) @@ -123,7 +123,7 @@ func (w *Writer) writeHeader(writer io.Writer, carV1Len int) (int64, error) { } func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (n int64, err error) { - // TODO avoid recopying the bytes by refacting carbs once it is integrated here. + // TODO avoid recopying the bytes by refactoring carbs once it is integrated here. // Right now we copy the bytes since carbs takes a writer. // Consider refactoring carbs to make this process more efficient. // We should avoid reading the entire car into memory since it can be large. diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index caf7e66680..7b9595d776 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -7,8 +7,8 @@ import ( format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" + "github.com/ipld/go-car/v2/carbs" "github.com/stretchr/testify/assert" - "github.com/willscott/carbs" "testing" ) From cc45766eebabef2295aee6960df8c5e65d317c90 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 15 Jun 2021 11:11:06 +0100 Subject: [PATCH 051/291] Address `staticcheck` errors Remove unused structs, convert error messages to lower case and remove redundant `return` statements. Address review comments - Reoder imports using `gofumpt`. - Use consistent import alias for `carv1`. - Rename structs for better readability. - Add TODO to fix logging, tests, etc. - Move test related files to `testdata`. This commit was moved from ipld/go-car@d5e22dd9711992ad168866df48d621c3400b3d59 --- ipld/car/v2/carbs/carbs.go | 92 +++++++++++----------- ipld/car/v2/carbs/carbs_test.go | 31 +------- ipld/car/v2/carbs/index.go | 2 +- ipld/car/v2/carbs/indexsorted.go | 2 +- ipld/car/v2/carbs/{ => testdata}/test.car | Bin ipld/car/v2/carbs/util/hydrate.go | 4 +- 6 files changed, 51 insertions(+), 80 deletions(-) rename ipld/car/v2/carbs/{ => testdata}/test.car (100%) diff --git a/ipld/car/v2/carbs/carbs.go b/ipld/car/v2/carbs/carbs.go index ca7256f815..6891d925d4 100644 --- a/ipld/car/v2/carbs/carbs.go +++ b/ipld/car/v2/carbs/carbs.go @@ -10,47 +10,47 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - bs "github.com/ipfs/go-ipfs-blockstore" + blockstore "github.com/ipfs/go-ipfs-blockstore" "github.com/multiformats/go-multihash" pb "github.com/cheggaaa/pb/v3" - car "github.com/ipld/go-car" + carv1 "github.com/ipld/go-car" "github.com/ipld/go-car/util" "golang.org/x/exp/mmap" ) -var errNotFound = bs.ErrNotFound +var errNotFound = blockstore.ErrNotFound -// Carbs provides a read-only Car Block Store. -type Carbs struct { +// BlockStore provides a read-only Car Block Store. +type BlockStore struct { backing io.ReaderAt idx Index } -var _ bs.Blockstore = (*Carbs)(nil) +var _ blockstore.Blockstore = (*BlockStore)(nil) -func (c *Carbs) Read(idx int64) (cid.Cid, []byte, error) { - bcid, data, err := util.ReadNode(bufio.NewReader(&unatreader{c.backing, idx})) +func (b *BlockStore) Read(idx int64) (cid.Cid, []byte, error) { + bcid, data, err := util.ReadNode(bufio.NewReader(&unatreader{b.backing, idx})) return bcid, data, err } // DeleteBlock doesn't delete a block on RO blockstore -func (c *Carbs) DeleteBlock(_ cid.Cid) error { +func (b *BlockStore) DeleteBlock(_ cid.Cid) error { return fmt.Errorf("read only") } // Has indicates if the store has a cid -func (c *Carbs) Has(key cid.Cid) (bool, error) { - offset, err := c.idx.Get(key) +func (b *BlockStore) Has(key cid.Cid) (bool, error) { + offset, err := b.idx.Get(key) if err != nil { return false, err } - uar := unatreader{c.backing, int64(offset)} + uar := unatreader{b.backing, int64(offset)} _, err = binary.ReadUvarint(&uar) if err != nil { return false, err } - cid, _, err := readCid(c.backing, uar.at) + cid, _, err := readCid(b.backing, uar.at) if err != nil { return false, err } @@ -100,60 +100,61 @@ func readCid(store io.ReaderAt, at int64) (cid.Cid, int, error) { } // Get gets a block from the store -func (c *Carbs) Get(key cid.Cid) (blocks.Block, error) { - offset, err := c.idx.Get(key) +func (b *BlockStore) Get(key cid.Cid) (blocks.Block, error) { + offset, err := b.idx.Get(key) if err != nil { return nil, err } - entry, bytes, err := c.Read(int64(offset)) + entry, bytes, err := b.Read(int64(offset)) if err != nil { + // TODO replace with logging fmt.Printf("failed get %d:%v\n", offset, err) - return nil, bs.ErrNotFound + return nil, blockstore.ErrNotFound } if !entry.Equals(key) { - return nil, bs.ErrNotFound + return nil, blockstore.ErrNotFound } return blocks.NewBlockWithCid(bytes, key) } // GetSize gets how big a item is -func (c *Carbs) GetSize(key cid.Cid) (int, error) { - idx, err := c.idx.Get(key) +func (b *BlockStore) GetSize(key cid.Cid) (int, error) { + idx, err := b.idx.Get(key) if err != nil { return -1, err } - len, err := binary.ReadUvarint(&unatreader{c.backing, int64(idx)}) + len, err := binary.ReadUvarint(&unatreader{b.backing, int64(idx)}) if err != nil { - return -1, bs.ErrNotFound + return -1, blockstore.ErrNotFound } - cid, _, err := readCid(c.backing, int64(idx+len)) + cid, _, err := readCid(b.backing, int64(idx+len)) if err != nil { return 0, err } if !cid.Equals(key) { - return -1, bs.ErrNotFound + return -1, blockstore.ErrNotFound } // get cid. validate. return int(len), err } // Put does nothing on a ro store -func (c *Carbs) Put(blocks.Block) error { +func (b *BlockStore) Put(blocks.Block) error { return fmt.Errorf("read only") } // PutMany does nothing on a ro store -func (c *Carbs) PutMany([]blocks.Block) error { +func (b *BlockStore) PutMany([]blocks.Block) error { return fmt.Errorf("read only") } // AllKeysChan returns the list of keys in the store -func (c *Carbs) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) +func (b *BlockStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{b.backing, 0})) if err != nil { - return nil, fmt.Errorf("Error reading car header: %w", err) + return nil, fmt.Errorf("error reading car header: %w", err) } - offset, err := car.HeaderSize(header) + offset, err := carv1.HeaderSize(header) if err != nil { return nil, err } @@ -162,14 +163,14 @@ func (c *Carbs) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { go func() { done := ctx.Done() - rdr := unatreader{c.backing, int64(offset)} - for true { + rdr := unatreader{b.backing, int64(offset)} + for { l, err := binary.ReadUvarint(&rdr) thisItemForNxt := rdr.at if err != nil { return } - c, _, err := readCid(c.backing, thisItemForNxt) + c, _, err := readCid(b.backing, thisItemForNxt) if err != nil { return } @@ -187,21 +188,20 @@ func (c *Carbs) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { } // HashOnRead does nothing -func (c *Carbs) HashOnRead(enabled bool) { - return +func (b *BlockStore) HashOnRead(bool) { } // Roots returns the root CIDs of the backing car -func (c *Carbs) Roots() ([]cid.Cid, error) { - header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) +func (b *BlockStore) Roots() ([]cid.Cid, error) { + header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{b.backing, 0})) if err != nil { - return nil, fmt.Errorf("Error reading car header: %w", err) + return nil, fmt.Errorf("error reading car header: %w", err) } return header.Roots, nil } // Load opens a carbs data store, generating an index if it does not exist -func Load(path string, noPersist bool) (*Carbs, error) { +func Load(path string, noPersist bool) (*BlockStore, error) { reader, err := mmap.Open(path) if err != nil { return nil, err @@ -218,7 +218,7 @@ func Load(path string, noPersist bool) (*Carbs, error) { } } } - obj := Carbs{ + obj := BlockStore{ backing: reader, idx: idx, } @@ -226,8 +226,8 @@ func Load(path string, noPersist bool) (*Carbs, error) { } // Of opens a carbs data store from an existing reader of the base data and index -func Of(backing io.ReaderAt, index Index) *Carbs { - return &Carbs{backing, index} +func Of(backing io.ReaderAt, index Index) *BlockStore { + return &BlockStore{backing, index} } // GenerateIndex provides a low-level interface to create an index over a @@ -244,11 +244,11 @@ func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool bar.Start() - header, err := car.ReadHeader(bufio.NewReader(&unatreader{store, 0})) + header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{store, 0})) if err != nil { - return nil, fmt.Errorf("Error reading car header: %w", err) + return nil, fmt.Errorf("error reading car header: %w", err) } - offset, err := car.HeaderSize(header) + offset, err := carv1.HeaderSize(header) if err != nil { return nil, err } @@ -258,7 +258,7 @@ func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool records := make([]Record, 0) rdr := unatreader{store, int64(offset)} - for true { + for { thisItemIdx := rdr.at l, err := binary.ReadUvarint(&rdr) bar.Add64(int64(l)) diff --git a/ipld/car/v2/carbs/carbs_test.go b/ipld/car/v2/carbs/carbs_test.go index 5ec52e972f..7af483e9b2 100644 --- a/ipld/car/v2/carbs/carbs_test.go +++ b/ipld/car/v2/carbs/carbs_test.go @@ -1,13 +1,8 @@ package carbs import ( - "context" - "fmt" "os" "testing" - - "github.com/ipfs/go-cid" - format "github.com/ipfs/go-ipld-format" ) /* @@ -56,7 +51,8 @@ func TestIndexRT(t *testing.T) { } defer os.Remove(carFile) */ - carFile := "test.car" + // TODO use temporari directory to run tests taht work with OS file system to avoid accidental source code modification + carFile := "testdata/test.car" cf, err := Load(carFile, false) if err != nil { @@ -83,26 +79,3 @@ func TestIndexRT(t *testing.T) { t.Fatalf("bad index: %d %v", idx, err) } } - -type mockNodeGetter struct { - Nodes map[cid.Cid]format.Node -} - -func (m *mockNodeGetter) Get(_ context.Context, c cid.Cid) (format.Node, error) { - n, ok := m.Nodes[c] - if !ok { - return nil, fmt.Errorf("unknown node") - } - return n, nil -} - -func (m *mockNodeGetter) GetMany(_ context.Context, cs []cid.Cid) <-chan *format.NodeOption { - ch := make(chan *format.NodeOption, 5) - go func() { - for _, c := range cs { - n, e := m.Get(nil, c) - ch <- &format.NodeOption{Node: n, Err: e} - } - }() - return ch -} diff --git a/ipld/car/v2/carbs/index.go b/ipld/car/v2/carbs/index.go index b139a24a4d..1f381308c8 100644 --- a/ipld/car/v2/carbs/index.go +++ b/ipld/car/v2/carbs/index.go @@ -78,7 +78,7 @@ func Restore(path string) (Index, error) { } idx, ok := IndexAtlas[IndexCodec(codec)] if !ok { - return nil, fmt.Errorf("Unknown codec: %d", codec) + return nil, fmt.Errorf("unknown codec: %d", codec) } idxInst := idx() if err := idxInst.Unmarshal(&uar); err != nil { diff --git a/ipld/car/v2/carbs/indexsorted.go b/ipld/car/v2/carbs/indexsorted.go index d16d10d7db..eca593c9f6 100644 --- a/ipld/car/v2/carbs/indexsorted.go +++ b/ipld/car/v2/carbs/indexsorted.go @@ -88,7 +88,7 @@ func (s *singleWidthIndex) get(d []byte) uint64 { if int64(idx) == s.len { return 0 } - if bytes.Compare(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) != 0 { + if !bytes.Equal(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) { return 0 } return binary.LittleEndian.Uint64(s.index[(idx+1)*int(s.width)-8 : (idx+1)*int(s.width)]) diff --git a/ipld/car/v2/carbs/test.car b/ipld/car/v2/carbs/testdata/test.car similarity index 100% rename from ipld/car/v2/carbs/test.car rename to ipld/car/v2/carbs/testdata/test.car diff --git a/ipld/car/v2/carbs/util/hydrate.go b/ipld/car/v2/carbs/util/hydrate.go index 7461458112..d0fb50ae17 100644 --- a/ipld/car/v2/carbs/util/hydrate.go +++ b/ipld/car/v2/carbs/util/hydrate.go @@ -2,9 +2,9 @@ package main import ( "fmt" - "github.com/ipld/go-car/v2/carbs" "os" + "github.com/ipld/go-car/v2/carbs" "golang.org/x/exp/mmap" ) @@ -45,7 +45,5 @@ func main() { if err := carbs.Save(idx, db); err != nil { fmt.Printf("Error saving : %v\n", err) - return } - return } From 7d429419da0183786175d436a11c563f767ac23b Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 16 Jun 2021 10:44:12 +0100 Subject: [PATCH 052/291] Implement `OffsetReader` Because `SectionReader` requires the max number of bytes to read, since it implements `Seek`. We need something like the `SectionReader` to read the index at the end of a CAR v2 that does not require the user to know the number of readable bytes. This is because, we do not store the size of index in CAR v2 header, since it is always added as the last section. The `OffsetReader` works just like `SectionReader`, except if `n`, the number of bytes to read, is set to zero it simply carries on reading until the underlying `io.ReaderAt` returns EOF. Consequently, `OffsetReader` does not implement `Seek`. This commit was moved from ipld/go-car@7cbf448577b17fa4a1712c5d8f33c99a7cfefea2 --- ipld/car/v2/offset_reader.go | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 ipld/car/v2/offset_reader.go diff --git a/ipld/car/v2/offset_reader.go b/ipld/car/v2/offset_reader.go new file mode 100644 index 0000000000..80cd38e813 --- /dev/null +++ b/ipld/car/v2/offset_reader.go @@ -0,0 +1,59 @@ +package car + +import "io" + +// OffsetReader implements Read, and ReadAt on a section +// of an underlying io.ReaderAt. +// The main difference between io.SectionReader and OffsetReader is that +// NewOffsetReader accepts zero as n, the number of bytes to read, with +// the trade-off that it does not implement Seek. +// When n is set to zero, it will delegate io.EOF errors to the underlying +// io.ReaderAt. +// This is useful when reading a section at the end of a io.ReaderAt without +// having to know the total number readable of bytes. +type OffsetReader struct { + r io.ReaderAt + base int64 + off int64 + limit int64 + limited bool +} + +// NewOffsetReader returns an OffsetReader that reads from r +// starting at offset off and stops with io.EOF after n bytes. +// If n is set to 0 then it will carry on reading until r reaches io.EOF. +func NewOffsetReader(r io.ReaderAt, off int64, n int64) *OffsetReader { + return &OffsetReader{r, off, off, off + n, n == 0} +} + +func (o *OffsetReader) Read(p []byte) (n int, err error) { + if o.limited { + if o.off >= o.limit { + return 0, io.EOF + } + if max := o.limit - o.off; int64(len(p)) > max { + p = p[0:max] + } + } + n, err = o.r.ReadAt(p, o.off) + o.off += int64(n) + return +} + +func (o *OffsetReader) ReadAt(p []byte, off int64) (n int, err error) { + if o.limited { + if off < 0 || off >= o.limit-o.base { + return 0, io.EOF + } + off += o.base + if max := o.limit - off; int64(len(p)) > max { + p = p[0:max] + n, err = o.r.ReadAt(p, off) + if err == nil { + err = io.EOF + } + return n, err + } + } + return o.r.ReadAt(p, off) +} From 068af1712fff21a954262800a0b191ef0c58150a Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 16 Jun 2021 11:24:15 +0100 Subject: [PATCH 053/291] Implement CAR v2 reader with API that provides `BlockStore` Implement a CAR v2 reader that allows access to each section of the CAR, i.e. CAR v1 dump and Index, as well as a higher level API that provides a `blockstore.BlockStore` from a CAR v2 file. Address Review comments - Simplify naming of section size constants - Simplify OffsetReader by removing the dual SectionReader functionality - Improve read efficiency by reading header in one chunk - Add TODOs to improve write efficiency in a similar manner This commit was moved from ipld/go-car@1d3cbb33f41f68e5bd5403c4b1f303280f3b8545 --- ipld/car/v2/car.go | 69 ++++++++++++++++++++-------- ipld/car/v2/car_test.go | 81 ++++++++++++++++++++++++++------- ipld/car/v2/carbs/carbs_test.go | 2 +- ipld/car/v2/offset_reader.go | 48 +++++-------------- ipld/car/v2/reader.go | 61 +++++++++++++++++++++++++ ipld/car/v2/writer.go | 5 +- 6 files changed, 191 insertions(+), 75 deletions(-) create mode 100644 ipld/car/v2/reader.go diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index e3f7c7b79a..99c5fa7d99 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -6,11 +6,13 @@ import ( ) const ( - // HeaderBytesSize is the fixed size of CAR v2 header in number of bytes. - HeaderBytesSize = 40 - // CharacteristicsBytesSize is the fixed size of Characteristics bitfield within CAR v2 header in number of bytes. - CharacteristicsBytesSize = 16 - uint64BytesSize = 8 + // PrefixSize is the size of the CAR v2 prefix in 11 bytes, (i.e. 11). + PrefixSize = 11 + // HeaderSize is the fixed size of CAR v2 header in number of bytes. + HeaderSize = 40 + // CharacteristicsSize is the fixed size of Characteristics bitfield within CAR v2 header in number of bytes. + CharacteristicsSize = 16 + uint64Size = 8 ) var ( @@ -22,8 +24,6 @@ var ( 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // "version" 0x02, // uint(2) } - // The size of the CAR v2 prefix in 11 bytes, (i.e. 11). - prefixBytesSize = uint64(len(PrefixBytes)) ) type ( @@ -47,23 +47,35 @@ type ( // WriteTo writes this characteristics to the given writer. func (c Characteristics) WriteTo(w io.Writer) (n int64, err error) { - if err = writeUint64To(w, c.Hi); err != nil { + if err = binary.Write(w, binary.LittleEndian, c.Hi); err != nil { return } - n += uint64BytesSize - if err = writeUint64To(w, c.Lo); err != nil { + n += uint64Size + if err = binary.Write(w, binary.LittleEndian, c.Lo); err != nil { return } - n += uint64BytesSize + n += uint64Size return } +func (c *Characteristics) ReadFrom(r io.Reader) (int64, error) { + buf := make([]byte, CharacteristicsSize) + read, err := io.ReadFull(r, buf) + n := int64(read) + if err != nil { + return n, err + } + c.Hi = binary.LittleEndian.Uint64(buf[:uint64Size]) + c.Lo = binary.LittleEndian.Uint64(buf[uint64Size:]) + return n, nil +} + // NewHeader instantiates a new CAR v2 header, given the byte length of a CAR v1. func NewHeader(carV1Size uint64) Header { header := Header{ CarV1Size: carV1Size, } - header.CarV1Offset = prefixBytesSize + HeaderBytesSize + header.CarV1Offset = PrefixSize + HeaderSize header.IndexOffset = header.CarV1Offset + carV1Size return header } @@ -89,26 +101,43 @@ func (h Header) WithCarV1Padding(padding uint64) Header { // WriteTo serializes this header as bytes and writes them using the given io.Writer. func (h Header) WriteTo(w io.Writer) (n int64, err error) { + // TODO optimize write by encoding all bytes in a slice and writing once. wn, err := h.Characteristics.WriteTo(w) if err != nil { return } n += wn - if err = writeUint64To(w, h.CarV1Offset); err != nil { + if err = binary.Write(w, binary.LittleEndian, h.CarV1Offset); err != nil { return } - n += uint64BytesSize - if err = writeUint64To(w, h.CarV1Size); err != nil { + n += uint64Size + if err = binary.Write(w, binary.LittleEndian, h.CarV1Size); err != nil { return } - n += uint64BytesSize - if err = writeUint64To(w, h.IndexOffset); err != nil { + n += uint64Size + if err = binary.Write(w, binary.LittleEndian, h.IndexOffset); err != nil { return } - n += uint64BytesSize + n += uint64Size return } -func writeUint64To(w io.Writer, v uint64) error { - return binary.Write(w, binary.LittleEndian, v) +// ReadFrom populates fields of this header from the given r. +func (h *Header) ReadFrom(r io.Reader) (int64, error) { + n, err := h.Characteristics.ReadFrom(r) + if err != nil { + return n, err + } + remainingSize := HeaderSize - CharacteristicsSize + buf := make([]byte, remainingSize) + read, err := io.ReadFull(r, buf) + n += int64(read) + if err != nil { + return n, err + } + carV1RelOffset := uint64Size * 2 + h.CarV1Offset = binary.LittleEndian.Uint64(buf[:uint64Size]) + h.CarV1Size = binary.LittleEndian.Uint64(buf[uint64Size:carV1RelOffset]) + h.IndexOffset = binary.LittleEndian.Uint64(buf[carV1RelOffset:]) + return n, nil } diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index 6e1c877718..9a6a2b4238 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -1,16 +1,14 @@ package car_test import ( + "bufio" "bytes" - cbor "github.com/ipfs/go-ipld-cbor" carv1 "github.com/ipld/go-car" carv2 "github.com/ipld/go-car/v2" "github.com/stretchr/testify/assert" "testing" ) -var prefixBytesSize = uint64(11) - func TestCarV2PrefixLength(t *testing.T) { tests := []struct { name string @@ -37,10 +35,9 @@ func TestCarV2PrefixLength(t *testing.T) { } func TestCarV2PrefixIsValidCarV1Header(t *testing.T) { - var v1h carv1.CarHeader - err := cbor.DecodeInto(carv2.PrefixBytes[1:], &v1h) + v1h, err := carv1.ReadHeader(bufio.NewReader(bytes.NewReader(carv2.PrefixBytes))) assert.NoError(t, err, "cannot decode prefix as CBOR with CAR v1 header structure") - assert.Equal(t, carv1.CarHeader{ + assert.Equal(t, &carv1.CarHeader{ Roots: nil, Version: 2, }, v1h, "CAR v2 prefix must be a valid CAR v1 header") @@ -97,8 +94,60 @@ func TestHeader_WriteTo(t *testing.T) { } gotWrite := buf.Bytes() assert.Equal(t, tt.wantWrite, gotWrite, "Header.WriteTo() gotWrite = %v, wantWrite %v", gotWrite, tt.wantWrite) - assert.EqualValues(t, carv2.HeaderBytesSize, uint64(len(gotWrite)), "WriteTo() CAR v2 header length must always be %v bytes long", carv2.HeaderBytesSize) - assert.EqualValues(t, carv2.HeaderBytesSize, uint64(written), "WriteTo() CAR v2 header byte count must always be %v bytes long", carv2.HeaderBytesSize) + assert.EqualValues(t, carv2.HeaderSize, uint64(len(gotWrite)), "WriteTo() CAR v2 header length must always be %v bytes long", carv2.HeaderSize) + assert.EqualValues(t, carv2.HeaderSize, uint64(written), "WriteTo() CAR v2 header byte count must always be %v bytes long", carv2.HeaderSize) + }) + } +} +func TestHeader_ReadFrom(t *testing.T) { + tests := []struct { + name string + target []byte + wantHeader carv2.Header + wantErr bool + }{ + { + "HeaderWithEmptyCharacteristicsIsWrittenAsExpected", + []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }, + carv2.Header{ + Characteristics: carv2.Characteristics{}, + }, + false, + }, + { + "NonEmptyHeaderIsWrittenAsExpected", + + []byte{ + 0xe9, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xea, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x63, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x65, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }, + carv2.Header{ + Characteristics: carv2.Characteristics{ + Hi: 1001, Lo: 1002, + }, + CarV1Offset: 99, + CarV1Size: 100, + IndexOffset: 101, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotHeader := carv2.Header{} + gotRead, err := gotHeader.ReadFrom(bytes.NewReader(tt.target)) + assert.NoError(t, err) + assert.Equal(t, int64(carv2.HeaderSize), gotRead) + assert.Equal(t, tt.wantHeader, gotHeader) }) } } @@ -113,20 +162,20 @@ func TestHeader_WithPadding(t *testing.T) { { "WhenNoPaddingOffsetsAreSumOfSizes", carv2.NewHeader(123), - prefixBytesSize + carv2.HeaderBytesSize, - prefixBytesSize + carv2.HeaderBytesSize + 123, + carv2.PrefixSize + carv2.HeaderSize, + carv2.PrefixSize + carv2.HeaderSize + 123, }, { "WhenOnlyPaddingCarV1BothOffsetsShift", carv2.NewHeader(123).WithCarV1Padding(3), - prefixBytesSize + carv2.HeaderBytesSize + 3, - prefixBytesSize + carv2.HeaderBytesSize + 3 + 123, + carv2.PrefixSize + carv2.HeaderSize + 3, + carv2.PrefixSize + carv2.HeaderSize + 3 + 123, }, { "WhenPaddingBothCarV1AndIndexBothOffsetsShiftWithAdditionalIndexShift", carv2.NewHeader(123).WithCarV1Padding(3).WithIndexPadding(7), - prefixBytesSize + carv2.HeaderBytesSize + 3, - prefixBytesSize + carv2.HeaderBytesSize + 3 + 123 + 7, + carv2.PrefixSize + carv2.HeaderSize + 3, + carv2.PrefixSize + carv2.HeaderSize + 3 + 123 + 7, }, } @@ -142,9 +191,9 @@ func TestNewHeaderHasExpectedValues(t *testing.T) { wantCarV1Len := uint64(1413) want := carv2.Header{ Characteristics: carv2.Characteristics{}, - CarV1Offset: prefixBytesSize + carv2.HeaderBytesSize, + CarV1Offset: carv2.PrefixSize + carv2.HeaderSize, CarV1Size: wantCarV1Len, - IndexOffset: prefixBytesSize + carv2.HeaderBytesSize + wantCarV1Len, + IndexOffset: carv2.PrefixSize + carv2.HeaderSize + wantCarV1Len, } got := carv2.NewHeader(wantCarV1Len) assert.Equal(t, want, got, "NewHeader got = %v, want = %v", got, want) diff --git a/ipld/car/v2/carbs/carbs_test.go b/ipld/car/v2/carbs/carbs_test.go index 7af483e9b2..2aa48fe708 100644 --- a/ipld/car/v2/carbs/carbs_test.go +++ b/ipld/car/v2/carbs/carbs_test.go @@ -51,7 +51,7 @@ func TestIndexRT(t *testing.T) { } defer os.Remove(carFile) */ - // TODO use temporari directory to run tests taht work with OS file system to avoid accidental source code modification + // TODO use temporary directory to run tests taht work with OS file system to avoid accidental source code modification carFile := "testdata/test.car" cf, err := Load(carFile, false) diff --git a/ipld/car/v2/offset_reader.go b/ipld/car/v2/offset_reader.go index 80cd38e813..c0f09230f4 100644 --- a/ipld/car/v2/offset_reader.go +++ b/ipld/car/v2/offset_reader.go @@ -2,58 +2,34 @@ package car import "io" +var _ io.ReaderAt = (*OffsetReader)(nil) + // OffsetReader implements Read, and ReadAt on a section // of an underlying io.ReaderAt. // The main difference between io.SectionReader and OffsetReader is that -// NewOffsetReader accepts zero as n, the number of bytes to read, with -// the trade-off that it does not implement Seek. -// When n is set to zero, it will delegate io.EOF errors to the underlying -// io.ReaderAt. -// This is useful when reading a section at the end of a io.ReaderAt without -// having to know the total number readable of bytes. +// NewOffsetReader does not require the user to know the number of readable bytes. type OffsetReader struct { - r io.ReaderAt - base int64 - off int64 - limit int64 - limited bool + r io.ReaderAt + base int64 + off int64 } // NewOffsetReader returns an OffsetReader that reads from r -// starting at offset off and stops with io.EOF after n bytes. -// If n is set to 0 then it will carry on reading until r reaches io.EOF. -func NewOffsetReader(r io.ReaderAt, off int64, n int64) *OffsetReader { - return &OffsetReader{r, off, off, off + n, n == 0} +// starting at offset off and stops with io.EOF when r reaches its end. +func NewOffsetReader(r io.ReaderAt, off int64) *OffsetReader { + return &OffsetReader{r, off, off} } func (o *OffsetReader) Read(p []byte) (n int, err error) { - if o.limited { - if o.off >= o.limit { - return 0, io.EOF - } - if max := o.limit - o.off; int64(len(p)) > max { - p = p[0:max] - } - } n, err = o.r.ReadAt(p, o.off) o.off += int64(n) return } func (o *OffsetReader) ReadAt(p []byte, off int64) (n int, err error) { - if o.limited { - if off < 0 || off >= o.limit-o.base { - return 0, io.EOF - } - off += o.base - if max := o.limit - off; int64(len(p)) > max { - p = p[0:max] - n, err = o.r.ReadAt(p, off) - if err == nil { - err = io.EOF - } - return n, err - } + if off < 0 { + return 0, io.EOF } + off += o.base return o.r.ReadAt(p, off) } diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go new file mode 100644 index 0000000000..b3c729e855 --- /dev/null +++ b/ipld/car/v2/reader.go @@ -0,0 +1,61 @@ +package car + +import ( + "bufio" + "fmt" + "io" + + carv1 "github.com/ipld/go-car" +) + +const version2 = 2 + +// Reader represents a reader of CAR v2. +type Reader struct { + Header Header + r io.ReaderAt +} + +// NewReader constructs a new reader that reads CAR v2 from the given r. +// Upon instantiation, the reader inspects the payload by reading the first 11 bytes and will return +// an error if the payload does not represent a CAR v2. +func NewReader(r io.ReaderAt) (*Reader, error) { + cr := &Reader{ + r: r, + } + if err := cr.readPrefix(); err != nil { + return nil, err + } + if err := cr.readHeader(); err != nil { + return nil, err + } + return cr, nil +} + +func (r *Reader) readPrefix() (err error) { + pr := io.NewSectionReader(r.r, 0, PrefixSize) + header, err := carv1.ReadHeader(bufio.NewReader(pr)) + if err != nil { + return + } + if header.Version != version2 { + err = fmt.Errorf("invalid car version: %d", header.Version) + } + return +} + +func (r *Reader) readHeader() (err error) { + headerSection := io.NewSectionReader(r.r, PrefixSize, HeaderSize) + _, err = r.Header.ReadFrom(headerSection) + return +} + +// CarV1ReaderAt provides an io.ReaderAt containing the CAR v1 dump encapsulated in this CAR v2. +func (r *Reader) CarV1ReaderAt() io.ReaderAt { + return io.NewSectionReader(r.r, int64(r.Header.CarV1Offset), int64(r.Header.CarV1Size)) +} + +// IndexReaderAt provides an io.ReaderAt containing the carbs.Index of this CAR v2. +func (r *Reader) IndexReaderAt() io.ReaderAt { + return NewOffsetReader(r.r, int64(r.Header.IndexOffset)) +} diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index c8708bbd84..cd68c548fc 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -3,11 +3,12 @@ package car import ( "bytes" "context" + "io" + "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" carv1 "github.com/ipld/go-car" "github.com/ipld/go-car/v2/carbs" - "io" ) const bulkPaddingBytesSize = 1024 @@ -74,7 +75,7 @@ func (w *Writer) WriteTo(writer io.Writer) (n int64, err error) { if err != nil { return } - n += int64(prefixBytesSize) + n += int64(PrefixSize) // We read the entire car into memory because carbs.GenerateIndex takes a reader. // Future PRs will make this more efficient by exposing necessary interfaces in carbs so that // this can be done in an streaming manner. From a5b8325e9e3eb09627fa36302ddb3ba94b9e76e7 Mon Sep 17 00:00:00 2001 From: Will Scott Date: Sat, 10 Apr 2021 11:25:04 -0700 Subject: [PATCH 054/291] initial implementation of the 'fd' based carbon This commit was moved from ipld/go-car@0f1e5aaca365ba9806fa42182aefd3803afb0826 --- ipld/car/v2/carbon/LICENSE | 202 +++++++++++++++++++++++++++ ipld/car/v2/carbon/LICENSE-MIT | 21 +++ ipld/car/v2/carbon/README.md | 14 ++ ipld/car/v2/carbon/carbon.go | 58 ++++++++ ipld/car/v2/carbon/carbon_fds.go | 55 ++++++++ ipld/car/v2/carbon/insertionindex.go | 148 ++++++++++++++++++++ ipld/car/v2/carbon/poswriter.go | 14 ++ ipld/car/v2/carbon/reader.go | 20 +++ 8 files changed, 532 insertions(+) create mode 100644 ipld/car/v2/carbon/LICENSE create mode 100644 ipld/car/v2/carbon/LICENSE-MIT create mode 100644 ipld/car/v2/carbon/README.md create mode 100644 ipld/car/v2/carbon/carbon.go create mode 100644 ipld/car/v2/carbon/carbon_fds.go create mode 100644 ipld/car/v2/carbon/insertionindex.go create mode 100644 ipld/car/v2/carbon/poswriter.go create mode 100644 ipld/car/v2/carbon/reader.go diff --git a/ipld/car/v2/carbon/LICENSE b/ipld/car/v2/carbon/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/ipld/car/v2/carbon/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ipld/car/v2/carbon/LICENSE-MIT b/ipld/car/v2/carbon/LICENSE-MIT new file mode 100644 index 0000000000..c69ae66e44 --- /dev/null +++ b/ipld/car/v2/carbon/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Will Scott + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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. diff --git a/ipld/car/v2/carbon/README.md b/ipld/car/v2/carbon/README.md new file mode 100644 index 0000000000..dafbb49eed --- /dev/null +++ b/ipld/car/v2/carbon/README.md @@ -0,0 +1,14 @@ +💎 Carbon +=== + +Carbon provides a [blockstore](https://github.com/ipfs/go-ipfs-blockstore) interface with saved blocks stored to a car-compatible log. A [Carbs](github.com/willscott/carbs/) index for the resulting file is tracked and can be saved as needed. + +Note: Carbon does not support deletion. + +License +--- + +Carbs is dual-licensed under Apache 2.0 and MIT terms: + + Apache License, Version 2.0, (LICENSE or http://www.apache.org/licenses/LICENSE-2.0) + MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) diff --git a/ipld/car/v2/carbon/carbon.go b/ipld/car/v2/carbon/carbon.go new file mode 100644 index 0000000000..de60dd3c5c --- /dev/null +++ b/ipld/car/v2/carbon/carbon.go @@ -0,0 +1,58 @@ +package carbon + +import ( + "errors" + "os" + + "github.com/ipfs/go-cid" + bs "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipld/go-car" + "github.com/willscott/carbs" +) + +// Carbon is a carbs-index-compatible blockstore supporting appending additional blocks +type Carbon interface { + bs.Blockstore + Checkpoint() error + Finish() error +} + +// errUnsupported is returned for unsupported blockstore operations (like delete) +var errUnsupported = errors.New("unsupported by carbon") + +// errNotFound is returned for lookups to entries that don't exist +var errNotFound = errors.New("not found") + +// New creates a new Carbon blockstore +func New(path string) (Carbon, error) { + return NewWithRoots(path, []cid.Cid{}) +} + +// NewWithRoots creates a new Carbon blockstore with a provided set of root cids as the car roots +func NewWithRoots(path string, roots []cid.Cid) (Carbon, error) { + wfd, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + return nil, err + } + rfd, err := os.OpenFile(path, os.O_RDONLY, 0666) + if err != nil { + return nil, err + } + + hdr := car.CarHeader{ + Roots: roots, + Version: 1, + } + if err := car.WriteHeader(&hdr, wfd); err != nil { + return nil, err + } + + idx := insertionIndex{} + f := carbonFD{ + path, + &poswriter{wfd, 0}, + *carbs.Of(rfd, &idx), + &idx, + } + return &f, nil +} diff --git a/ipld/car/v2/carbon/carbon_fds.go b/ipld/car/v2/carbon/carbon_fds.go new file mode 100644 index 0000000000..fb25cfd7b0 --- /dev/null +++ b/ipld/car/v2/carbon/carbon_fds.go @@ -0,0 +1,55 @@ +package carbon + +import ( + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/ipld/go-car/util" + carbs "github.com/willscott/carbs" +) + +// carbonFD is a carbon implementation based on having two file handles opened, one appending to the file, and the other +// seeking to read items as needed. This implementation is preferable for a write-heavy workload. +type carbonFD struct { + path string + writeHandle *poswriter + carbs.Carbs + idx *insertionIndex +} + +var _ (Carbon) = (*carbonFD)(nil) + +func (c *carbonFD) DeleteBlock(cid.Cid) error { + return errUnsupported +} + +// Put puts a given block to the underlying datastore +func (c *carbonFD) Put(b blocks.Block) error { + return c.PutMany([]blocks.Block{b}) +} + +// PutMany puts a slice of blocks at the same time using batching +// capabilities of the underlying datastore whenever possible. +func (c *carbonFD) PutMany(b []blocks.Block) error { + for _, bl := range b { + n := c.writeHandle.at + if err := util.LdWrite(c.writeHandle, bl.Cid().Bytes(), bl.RawData()); err != nil { + return err + } + c.idx.items.InsertNoReplace(mkRecordFromCid(bl.Cid(), n)) + } + return nil +} + +// Finish serializes the carbon index so that it can be later used as a carbs read-only blockstore +func (c *carbonFD) Finish() error { + fi, err := c.idx.Flatten() + if err != nil { + return err + } + return carbs.Save(fi, c.path) +} + +// Checkpoint serializes the carbon index so that the partially written blockstore can be resumed. +func (c *carbonFD) Checkpoint() error { + return carbs.Save(c.idx, c.path) +} diff --git a/ipld/car/v2/carbon/insertionindex.go b/ipld/car/v2/carbon/insertionindex.go new file mode 100644 index 0000000000..0c87953b54 --- /dev/null +++ b/ipld/car/v2/carbon/insertionindex.go @@ -0,0 +1,148 @@ +package carbon + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" + "github.com/petar/GoLLRB/llrb" + cbor "github.com/whyrusleeping/cbor/go" + carbs "github.com/willscott/carbs" +) + +// IndexInsertion carbs IndexCodec identifier +const IndexInsertion carbs.IndexCodec = 0x300010 + +// init hook to register the index format +func init() { + carbs.IndexAtlas[IndexInsertion] = mkInsertion +} + +type insertionIndex struct { + items llrb.LLRB +} + +type record struct { + digest []byte + carbs.Record +} + +func (r record) Less(than llrb.Item) bool { + other, ok := than.(record) + if !ok { + return false + } + return bytes.Compare(r.digest, other.digest) <= 0 +} + +func mkRecord(r carbs.Record) record { + d, err := multihash.Decode(r.Hash()) + if err != nil { + return record{} + } + + return record{d.Digest, r} +} + +func mkRecordFromCid(c cid.Cid, at uint64) record { + d, err := multihash.Decode(c.Hash()) + if err != nil { + return record{} + } + + return record{d.Digest, carbs.Record{Cid: c, Idx: at}} +} + +func (ii *insertionIndex) Get(c cid.Cid) (uint64, error) { + d, err := multihash.Decode(c.Hash()) + if err != nil { + return 0, err + } + entry := record{digest: d.Digest} + e := ii.items.Get(entry) + if e == nil { + return 0, errNotFound + } + r, ok := e.(record) + if !ok { + return 0, errUnsupported + } + + return r.Record.Idx, nil +} + +func (ii *insertionIndex) Marshal(w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, int64(ii.items.Len())); err != nil { + return err + } + + var err error + + iter := func(i llrb.Item) bool { + if err = cbor.Encode(w, i.(record).Record); err != nil { + return false + } + return true + } + ii.items.AscendGreaterOrEqual(ii.items.Min(), iter) + return err +} + +func (ii *insertionIndex) Unmarshal(r io.Reader) error { + var len int64 + if err := binary.Read(r, binary.LittleEndian, &len); err != nil { + return err + } + d := cbor.NewDecoder(r) + for i := int64(0); i < len; i++ { + var rec carbs.Record + if err := d.Decode(&rec); err != nil { + return err + } + ii.items.InsertNoReplace(mkRecord(rec)) + } + return nil +} + +// Codec identifies this index format +func (ii *insertionIndex) Codec() carbs.IndexCodec { + return IndexInsertion +} + +func (ii *insertionIndex) Load(rs []carbs.Record) error { + for _, r := range rs { + rec := mkRecord(r) + if rec.digest == nil { + return fmt.Errorf("invalid entry: %v", r) + } + ii.items.InsertNoReplace(rec) + } + return nil +} + +func mkInsertion() carbs.Index { + ii := insertionIndex{} + return &ii +} + +// Flatten returns a 'indexsorted' formatted index for more efficient subsequent loading +func (ii *insertionIndex) Flatten() (carbs.Index, error) { + si := carbs.IndexAtlas[carbs.IndexSorted]() + rcrds := make([]carbs.Record, ii.items.Len()) + + idx := 0 + iter := func(i llrb.Item) bool { + rcrds[idx] = i.(record).Record + idx++ + return true + } + ii.items.AscendGreaterOrEqual(ii.items.Min(), iter) + + if err := si.Load(rcrds); err != nil { + return nil, err + } + return si, nil +} diff --git a/ipld/car/v2/carbon/poswriter.go b/ipld/car/v2/carbon/poswriter.go new file mode 100644 index 0000000000..5b9444e5ae --- /dev/null +++ b/ipld/car/v2/carbon/poswriter.go @@ -0,0 +1,14 @@ +package carbon + +import "io" + +type poswriter struct { + io.Writer + at uint64 +} + +func (p *poswriter) Write(b []byte) (n int, err error) { + n, err = p.Writer.Write(b) + p.at += uint64(n) + return +} diff --git a/ipld/car/v2/carbon/reader.go b/ipld/car/v2/carbon/reader.go new file mode 100644 index 0000000000..bc346557a5 --- /dev/null +++ b/ipld/car/v2/carbon/reader.go @@ -0,0 +1,20 @@ +package carbon + +import "io" + +type unatreader struct { + io.ReaderAt + at int64 +} + +func (u *unatreader) Read(p []byte) (n int, err error) { + n, err = u.ReadAt(p, u.at) + u.at = u.at + int64(n) + return +} + +func (u *unatreader) ReadByte() (byte, error) { + b := []byte{0} + _, err := u.Read(b) + return b[0], err +} From 086d199c7fcd5629054783e7021a9863261bdb55 Mon Sep 17 00:00:00 2001 From: Will Scott Date: Sat, 10 Apr 2021 11:36:49 -0700 Subject: [PATCH 055/291] close write handle on 'finish' This commit was moved from ipld/go-car@7593b75c95b3301c6f890665c2a110e3c909189e --- ipld/car/v2/carbon/carbon_fds.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ipld/car/v2/carbon/carbon_fds.go b/ipld/car/v2/carbon/carbon_fds.go index fb25cfd7b0..c4cfdd6093 100644 --- a/ipld/car/v2/carbon/carbon_fds.go +++ b/ipld/car/v2/carbon/carbon_fds.go @@ -1,6 +1,8 @@ package carbon import ( + "os" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/ipld/go-car/util" @@ -46,6 +48,12 @@ func (c *carbonFD) Finish() error { if err != nil { return err } + fd, ok := c.writeHandle.Writer.(*os.File) + if ok { + if err := fd.Close(); err != nil { + return err + } + } return carbs.Save(fi, c.path) } From 89ff975cc9e1449d38d1e4a83bf32216a190ee8d Mon Sep 17 00:00:00 2001 From: Will Scott Date: Sun, 11 Apr 2021 09:49:54 -0700 Subject: [PATCH 056/291] add test for expected round-trip behavior This commit was moved from ipld/go-car@3e05a31e1b8732a2a63fbdd1951237c870651061 --- ipld/car/v2/carbon/carbon.go | 14 ++-- ipld/car/v2/carbon/carbon_test.go | 92 +++++++++++++++++++++++++++ ipld/car/v2/carbon/insertionindex.go | 2 +- ipld/car/v2/carbon/test.car | Bin 0 -> 479907 bytes 4 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 ipld/car/v2/carbon/carbon_test.go create mode 100644 ipld/car/v2/carbon/test.car diff --git a/ipld/car/v2/carbon/carbon.go b/ipld/car/v2/carbon/carbon.go index de60dd3c5c..3122504f07 100644 --- a/ipld/car/v2/carbon/carbon.go +++ b/ipld/car/v2/carbon/carbon.go @@ -2,6 +2,7 @@ package carbon import ( "errors" + "fmt" "os" "github.com/ipfs/go-cid" @@ -30,27 +31,28 @@ func New(path string) (Carbon, error) { // NewWithRoots creates a new Carbon blockstore with a provided set of root cids as the car roots func NewWithRoots(path string, roots []cid.Cid) (Carbon, error) { - wfd, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND, 0666) + wfd, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) if err != nil { - return nil, err + return nil, fmt.Errorf("couldn't create backing car: %w", err) } rfd, err := os.OpenFile(path, os.O_RDONLY, 0666) if err != nil { - return nil, err + return nil, fmt.Errorf("could not re-open read handle: %w", err) } hdr := car.CarHeader{ Roots: roots, Version: 1, } - if err := car.WriteHeader(&hdr, wfd); err != nil { - return nil, err + writer := poswriter{wfd, 0} + if err := car.WriteHeader(&hdr, &writer); err != nil { + return nil, fmt.Errorf("couldn't write car header: %w", err) } idx := insertionIndex{} f := carbonFD{ path, - &poswriter{wfd, 0}, + &writer, *carbs.Of(rfd, &idx), &idx, } diff --git a/ipld/car/v2/carbon/carbon_test.go b/ipld/car/v2/carbon/carbon_test.go new file mode 100644 index 0000000000..1d90eae244 --- /dev/null +++ b/ipld/car/v2/carbon/carbon_test.go @@ -0,0 +1,92 @@ +package carbon_test + +import ( + "io" + "math/rand" + "os" + "testing" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-car" + "github.com/willscott/carbon" + "github.com/willscott/carbs" +) + +func TestCarbon(t *testing.T) { + f, err := os.Open("test.car") + if err != nil { + t.Skipf("fixture not found: %q", err) + return + } + defer f.Close() + + ingester, err := carbon.New("testcarbon.car") + if err != nil { + t.Fatal(err) + } + defer func() { + os.Remove("testcarbon.car") + }() + + r, err := car.NewCarReader(f) + if err != nil { + t.Fatal(err) + } + cids := make([]cid.Cid, 0) + for true { + b, err := r.Next() + if err == io.EOF { + break + } + if err := ingester.Put(b); err != nil { + t.Fatal(err) + } + cids = append(cids, b.Cid()) + + // try reading a random one: + candidate := cids[rand.Intn(len(cids))] + if has, err := ingester.Has(candidate); !has || err != nil { + t.Fatalf("expected to find %s but didn't: %s", candidate, err) + } + } + + for _, c := range cids { + b, err := ingester.Get(c) + if err != nil { + t.Fatal(err) + } + if !b.Cid().Equals(c) { + t.Fatal("wrong item returned") + } + } + + if err := ingester.Finish(); err != nil { + t.Fatal(err) + } + defer func() { + os.Remove("testcarbon.car.idx") + }() + + stat, err := os.Stat("testcarbon.car.idx") + if err != nil { + t.Fatal(err) + } + if stat.Size() <= 0 { + t.Fatalf("index not written: %v", stat) + } + + carb, err := carbs.Load("testcarbon.car", true) + if err != nil { + t.Fatal(err) + } + + for _, c := range cids { + b, err := carb.Get(c) + if err != nil { + t.Fatal(err) + } + if !b.Cid().Equals(c) { + t.Fatal("wrong item returned") + } + } +} diff --git a/ipld/car/v2/carbon/insertionindex.go b/ipld/car/v2/carbon/insertionindex.go index 0c87953b54..c748576781 100644 --- a/ipld/car/v2/carbon/insertionindex.go +++ b/ipld/car/v2/carbon/insertionindex.go @@ -35,7 +35,7 @@ func (r record) Less(than llrb.Item) bool { if !ok { return false } - return bytes.Compare(r.digest, other.digest) <= 0 + return bytes.Compare(r.digest, other.digest) < 0 } func mkRecord(r carbs.Record) record { diff --git a/ipld/car/v2/carbon/test.car b/ipld/car/v2/carbon/test.car new file mode 100644 index 0000000000000000000000000000000000000000..47a61c8c2a7def9bafcc252d3a1a4d12529615f5 GIT binary patch literal 479907 zcmdSBRZtz;(yooW>%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� z965i2%8!C%(kHBXdxJeHpNsmF;4M#B>LHQl#4RQxu93AWiEUAA7$+dPnXfSLp2x(` zYYYaJ^d&i|2BAH2+WRanfKLA_B7e&LUm{WzXzYI8B>c!p3s`I^yj3JkSsVG8M z_wrHS_lkO6fD)IaoG5Y&4Gh7XCol*yjX}6W(4<8P=kqvyRXZMY2hSN-(Dth|mij@& zM~u57|8e|KwOTogb!+VJUE%A$MI^{Sy^QbD4>ea@%w-Z+WeGcApXJhJIErnrU3~jq zL5io@qP>M1B5WQ@CkJ>+D!jNNiY4-tA8fgvTg5#vBCd)oRgb=L)fR3qwL0c=k(=1$D^NE|@MD%RncXH?6ABr6 z4`n(sB0~77tyG-1vevR+btC^Uv&$+l#9Gwy4cs;SWSE>Cttljs;HM3Jyq#g%?WZ7W zxyQ=q0a#MxkP!tJkL&DAux&EGdw^%}VpogKj^=mHIA1vNR$Ott{V;GiM&iwE@e0Vt z-Fc0Xd;uI^8vPo?p3KMf*H%wwimOKLtq_mUVS?8}T}a#yrMs?u%DZy*8Y-pdtyCPg z`jC5Gsb9Y*l=9_6JSveQQx@h-(KRJb;68dXF4qkJTC9T^DooLo=tR`?3YZk6FEu>p zLE0E>IdAqE55yVs4PL&i<~KN?E<*QU79jW`D=7(V3x0WGmy0m$fgCtzu<3*2-_0yx z<-R@rmi@FB=^+`@Vc3=B(&)y4BhRf$6|vq=WdBnHQ2~=f`L~EjRpXf)=QZX8`aYkm zAx7R{&x`hdKY`dj|E|9+Yq7xc9|_Ur>h`h5)~N{A`*saxOo5SX-}srOt(dFc4Bd1A z8XN|T6d2h59px_K;j$)*zzB!}cWHrGSw7)x)S|G6)35Eg#jBCEr7Q13yG@?6Qv5bq zd~kbmHJehRZGlX7qcw5^%MOu;`i=r_-qa>g8Q}eH1wur=n{3iHM0~%RX@kX$6A;K{s*yB>u~{sEuf6~1$#R`p9Y}b z|EkTOw&_i6CP3cK14)j&&e6#8C-hMSFW~1Z{%9~r*JOPrSmT>Vz2X9lW~LYtsHH1> zp%d64UMoR8cj%OHrHJcAj~<`6dXg0*>RoL#vGQ#?O*HD42&lW)$m%t8m7$zlr;~Lx zQF>qS(|)T>Y&3~TC7Y|X$aKZneYFERZr%&s=y8eoNZG9vw^0uOJ^P34=Pz(RSUVK* zQ(JJAA?wbpQIjT^{HY6mA0tPo;$I;`p_?4 zAN{Lnkgou}CC68L3-x)3ZQRShN<@$Lr^W|60>CDZ{;93X4XFu;T(Y}s2Nz z#y4rMVMWku`@_b?MV48)Q;DjArS(BUCP*E{g_B&vO!aE`2^2PlHpwK=x#*o}m9%Yw zsGP-oyizcjA$lK62m#Lq@d=;CbVVf=6bgsbUz~b<9I*w(-_IX~zt~k~m9wI;;VoRJ z1X+n(rDeMLA%IZ!9HVI8qbIC^9HSLWuz>W%o~r4^UbA!CpHa9ZtIDL;rk6Z`z0f>M zyFlCLbxJ_1viwri zs4Dx2xgP9G3V0)>gBid&<&c5VATu=oRvUt8ymaunQOgao>yjvPwUQsNl!frRCS7Z5 zObK}m18N|@3zm=shR!gSyVJ@2BwEk|Ig{Evbixh0;=rGzJ38JZ*xFc7|14N$4QQal z$Z*J^cbxEp8VXqp8$=}=@kZ@^Doi1Ll3mH(Bt(%I;Pno6I#vaQUrUYt;{vq#k#)vW zGM_oCLdTFXkKqiXwIwOQ-i7`z3YKLuEwyh^qQrv*di4NhuMT*vFaeshZ=>MU>f?}l z;~(G94vnqosU1z%A?=s5ukW2qw1Q62F?`y5WO3(0co@x)E|hR=3o7ZY{g8JUXGu;x zh^jmF%@?Zh3JJW90Q5u;ZBZM01$F3`{8;ZtGeqb*msi#JntTc6L3DCgnOU?ib8n{} z%CO(3o^yje0+O??ljzjbLh$XUVBE;!G&UE$g1i9@hsG%%{Ie-{Ki!HgGk;_<7na2~ zETX-!k}Ko(5ggitd2_P zjvEXRsapb5ua#AFlP>7~Tp-HKkBzd#nDoerKD0s3%tj%HriaGw3JU<0s7=_+{H8o#nz|3(|%lzYmP9 zFK$zY8?$X9c6Bx-x}#db^x1F{W}}s{>ny%VPpVcWsPwy}Mv#g9cx{rG60<4n)eFy@ zwsVO%;<5shUNS1{`LAX51>quX+^Jn=Sl-%b!`-HGA3W}8ZZ*9`_T$7e^@`8o3#P6u z=0io>$0}>GFZU~uUKoe;7fYO^+G7}X=G#%eb!z1j2{GR2OI_d>g8L$zgnMlv)Yho8 zFurw8yQfy?uwIO*5p&84FhWQAbr(g6B^1woUqlvvw-n+KJk~@gV7B`{zR(jXINLK@ zlmvKVxH!0}m;4&I)gi`01OCTpKoL8r%>VXZ-h=v0#((I>tDL3|I74Ng1_di7oC{{pW6NBmjq z4X)%``B7Ol*4t04S&=JDh|x;xFa_yXbWF(oL$ zTChI}QOlZ+E_=NqR91j$+aFqQd1N{r^RdXUvWwFZNw_FoH=kWk7|F>WF zr`-PqS1W^re+iAA*9B0ix-GHe@Zo43s$hQ7M8b7clHgslO}rSwGQTjfsui-?Bk;EC z7F2?cFA7jYSi%@OeMb+1nou#xvkxpacmp}0Mm)}ipop{W?*gO(x_GWnB%C)auC z5NM;X_N(oI3IoL^Ndgn_Xw8+#ZZ7UkFUyg$XC4`6uS*o`s3q-eT<2gNT=OQ(FOiiO z9>n5R%vXWF6(7VCt6i%sFt&7_$E?R>j>&<%f(3?fuv!BYHNC2=c`zQ*ZO%m~SCSym zuC#f@(NsdkzUMZ+aGVHY?SW`9c>U$PV|jAW2*QMc>?$bC2bWr-Y1+FF%_kh@Cp2MZ zlu~r<;pUo?@cL38c_HNYqRSLxEBo1@ZwfJpDzARS6%k~c?|0pr$aYM*_}Uzwb=#>L54U*L6)T!-a4Qp(*}KqxA6oQpXvL!(3Nnn^ zv#;Q0yNyVhBxNfwmtaza)j3RlET`xci%1!?zPC(_vd`0ce#vAVgvCm%;QVS1+q!8B zY}%(aCoc8Y_9}}f|7uN5PeL;AOd=n?G_(0U3G&@Yd;n(U3lq@!e@ov#<^C_UV#~h^ zSJh{-KJz&TARJ4VV9K2TGP?~@(lD^J{Y)puA)a&N5V@MH3d5q~Uy<2RggJfl5_c{c zb-Z#T3@{bb?EjU^go|b&>MGan6yHqwQuCdbY(~SRbCuceuoaUx_;=}>z$_RPMVn_V z#33wb$OD^(Vc~&M@ku3wlG7*xBT{#>8rer-C`?ERyjI@({M*+edl~Ca3VTtfEtU=pTq! zvF#PZhYD6uR49_1Z@a;%h5RYt7U7Wb*f@s?{6S3UkX_i&ui_dOaaT*4vRtg?IW1@Y z1%trAaK(c_?TI;$MZMkmb1$-T91zCs&eeg_EGLy(Z1C~V3Z=`!jesdAke?F-IV&ki zSRCS5#&D5=y^HyqpI24WldHC6Rte@>Z#*V^=#Q<%sAsP|ut#a=vo#nS+`V7-b18_H!2mE^eZOfqJc3|aHi zQnaRNw?gAwmT;VCf{osZRe){ZEvmPwq zg**gKC|&n=>5Kc-`E36Z|BL+B2ss=zcL4pmnP6MIs^UgV(QI?S8V42{=8hhtKaMVQmWJ2HYK)x3U%d#Dt$U2WBI1DSMtDlH^3l|NI!C^G$EY z7Xj|?R!j|caKII=3?-{1fz_*JddFs-Sxv`93Bo;Kf0(Q$c^~@k(;JXCz47~`T2axC zWX5lvXIb03#j+WY4brqTHdc!jp+aNaaIKNd>Dd;k@N-}h>ltA{;0ekbhn}FE@|Hs@fbU#O=InR7dmh0q_Mz&|MKrT`A^&QrZ*j+Z%6Iu z+(=Jjm8(~?W@5WLryI&;{fPO(Fg6_X#_#-0+u)hr=wKXVY3I>FJJV8E3!hEUSt9w= zjRplaT5+fIY?4Wx$A2l#*1+*m{;-0@t8h8dQ+pg5FKI$(wIe28LNfdCX61QubfX14 zUPXj30CqB#k|}I$>`ClG_?rvl`!O%Z4~N%@yJ_Jz_34n*_8=0BRiR7&m})z@Bx zIT^hs^$TEKg(+eQXYn!QWv6n}vH{MpxqJIJ+xln{IS6S!tp{-Py(ZlszWW|OfDHoS zokPyih1B)6@5hCle*x+_%?Qbh@7yy9udAO{f3E340xj)x_GyJ(A+e2e{ zKOL5`N)XG71pxg=ovh{g*kOu=$}}o{47_Yb zCN~BV6Cqx7J=^HsR###_3$wn9XIUncsE=s8e95a>GnhrU0;+}lzgKU6%KcwvUV{JO z9vShw7lWEu=jdEg-e41m=XBYSluMD{Q<~f*^r`2Q)#d{)7gaMEzNy!W9%zf$HB1C0 z;o0*!<!&5#S|KRtwI{Mj%wl#^`Awc#zeb$(9g*1pLNJ6FXQT1c-Ft59Q4D4RM$)eM}RJ(0WDg+;D@%otQFA9O@jdmjby%*^ViSAZ7 z_@?`^0XXeJesY(*DTBQ*4tf;``SBt+}__M8~x-f zouMqqYlE>#L|INWXM{)Zm@P?y@qWYgl&?0IL?D&-@=7PBm?lcCO}o}|)0Lt3nMiG8 z%`_hi3keiB;NACMm!GGMRynF_Q4gy)U!OyU?!y?&nX% zh~^_136Pwk<%PrNM>`Ri&;INF>~7CWCQ0u?EBR}AEynFj-IYoO20NNelne1px}0=7 z27zO)jH&A=-;lM60Jy$efek1u-QURsOXBsPjAyrMj1C1Z@Eap};qHx=#^G(iei!=h zL$>4%*(zp%_1!RwL3rHEoE|Gg))h_~t?v^yMg6l<#eJ`)VuD{~Z0}c(qdM&DZhzXB z$G~jh4`<5Vt72&kXV1CA5&9`qfS4SFh*LgGsY2n`7;OSE+3OOiDQ5cSr9+8}GW~s9 z{-+)N3)y7wVLB%{s+o@%xaH_SU-cd}?QcGnYm~o6;~xixpFI%$B)#V?y@t!&gZ~t< z*fxMkR0i{f+O&;A4;1v(LnX#WBOFEFbInR53;P!M7tbi)%!m>lNsDH(QP~m7-4N*S z`a5(UyAMZp8sCgR(pbZCdqIoWhw0Ite#MeT2NMQQqogXq7HH*bLZabg2Psn42t*zI ztI~Rl^t~0qmBj_0-dvVu`dsgqGuHDLSBx5EoNAyK`oi7WELL;Qm-BugSs`eQS|W!4 z_QXDE;I_yt`m}C@5f6_+bQf^qK-5{sPszr1=R&Ml7IHScdH7bo8vQMxBVRS0QB_7N za-rpe5j`?En&PGNas$@r*E5acWQms&+p zf)vmD3aQ&(84a)dnkbWwpa>CpyccnC2`M}{kdvCB?+0I=5Jpm`sk=errD>mTDyTpi zo%@{m^?}=A--czmdG$Vwz|LcLh(r@lod5 zM#&wMt;aF2LMjUzw5*QwjbnR3t4}81&gK@Vk;y2VPpuDyXlH)q-kHh;Ko1OnX3YaJ zrSZXH&M2}~WHv3aH1RP*1~m%)9`O6_@S>#Mm+245mr^5o6v>l%>6nN=${&QF8Zhrx zlp}I>a@x-h52dskHG-JO=zrJWD(WcSG=^qON4i1SC5)jM!AD(J(OCJV8G0L!yjGT| zSh)XbKG$&x)_w3Uc~K5wq_Zw@&uH)Cj1G~_wR|>@7BYUgQN)7#=OO;6NOYx?@MiGv zN1te{+vZ-AOrwBQt13fAxH5?ZMv>H;h_LekT;3@|8poWwpZ-EXX3p?o)F*WIyy2%l zRtk?tZbC{>&2>M6ccK425%G8vktX6Q!J$w}YTvF)TLNbTdy~{JZ23o7RdXMiyb#?q zJo5TV_<<~RCPw($%!T+)5irq!FN_*kh^s&2h!YX>i)(cvj@cgMpapXyZU(;{!{MXMC3p7Y^hN%L$b0d%wVOMWhCfi$KqIEj(sYv;>58=;ez&RN%MeNsY0ou3+UX85R|~ zJt#i$Fk9`0HFYl@rBmKGKEC;WL}V@n{CNNJj(o<-J#;U@VMDZgk0Z}4TLpFHk=;+0KlGY;qxvyTNh=!@#QpsO8ZD)cfCx8;T-O@dDbBc~k zPHPO$h5{OKTE*e3VKf77&8L3b_8rSPkLBtL;jykL!EXBQj$?y7`h~3$F$T|hg54x})=x7gOGaI4-10QI4YWaFN2U66ftjnA=%=XrQ z`~F8qEf8Ly35JKw>w7q;@fNpOD)ZrJbd%?}{rkd~NH{DcuzwWQP0hZZEuKvL1Pa^V zlfMUI&yC6a?B(4|^$rx&wECBh+ z>*6YdO_qN<^q+G77hLu9D0L3w(4XM7p+0D$SBsdPkH0|p4GgjXp_Q!o5hmK9*-!b7 z((4^GtG~#nIEA4StS8YTnF(?6K$`51q#XDi@hja42CId{8z;(tQgTtoFJ;$6M1YnR z3&bIa^Kbp_&`~c#v^2ANYnWA@WERp9>_ou~r!Jz@$K2=FTXCP;32aHeKoLk$NA~&` zH>6y|0TM}Tl{pD`shmpusCO@$V}_%2s@=UnT^i-=oPcdLYy%fVIx=NY=_iqqljF^q zgMiMtbA=82Nh$!yqx>-D;9wpqD+hNMWfG`|%27CRkHgYZT)8-!>k26h8^V{wZHd89 z(~dhuA}#s@pn)?OF|#%%2(b-;+xZ*)d8C@yxJxShy&5`c8%=HVRP6_|@eLnEW+0=8 zSSvyN!>C#B*isTv%AIfSHs@(wxlAD-@#zM+&~_{-yf;Lks_j58`EpjW%EML~zb#PK z>0#y;s*0JM&~v>8wYeM)Z5`t;bd8BPY<@fsFC8B6$ktqfY(SX2?b89)wc^x&OrOwc zUdBhMNimF+z2(jU9;RP$(ik6nn+I#7N!AT0Y?+)r&uocPvF`M_^)7Ni)drr%T2F^C zmn-M=ziOrQBHCE-%efc&0Y{j8b#|BdQjnn%WTU1Rz|5aU1p7foWoAgnbaOmXh4KJ2 zrM4Ss!-kQ;pkT2heK;iEXVQ+6IrCF1!-6ylJbB9=mtThWuRMW(d*3?IRI}`Ii+LV{ zmGXJbhX6dm#%k5hfxRVe`mz37$>Cj^-wsXmc0plEy}S=(SZI|b6Kf#7$la_SbUOzB z<-kTMJQEuW4(xY_?D)ycK(1@_oEh*lXG-tEMemd5<9d1`P5agd0G$Ol0T%8*Pv(jz zHqL#dw!Fxn()~DHyZwX7{M4Wc)UfjlIzWL%mk^%&gK3CcJm=?-=6Ms21Z0>8qwiYX z+FwUyr-kT}jDCdNl5xbRaYhVs+P^t;9HlG4x%V$+?55L<#sP-t$V$>$)-SY{To5Nd z>bKN*7rO_yNGL{xZg!y*YN*5awM8{oxov?P(Ssaw-O( z&pmr19f_4f|K`vIB*Ly6=y5SdnFK$khcP`Or;mRe8*rt=pi&P7>lp5nkYqX_i`7R~ z=`s^O+=;=98+b+)5G*HFFFG04ZzP`veCHy)t?h?AY*Yn0M!})7Qy@5M=j+(vG{knqLk z;K{NoK5M=3%l1^Ed5s|w%4C>{})_E5nP||Uu~lw4VuY0Bwb-LmmDe%XEj2?rafX` zeb+L#nw6>wkR&L=ufj8Z1*xwAh~%|!oIuaT{$J3_oD@Z6Mr-(RjhOR??|!6iXNS)eUQ1h z1^+HEQk1iHL>>9F`_Xgl2gOEB+$0VeL2u-f`7H>)QV{0A#JWk``)%x}v}xbbudz*<_9uho@RdWojZp%w{U-Y`*>jD-X z{UjCX#z5dh;SKxs-0LQfRUh&lDtZ2Bo?m-D6XYPVqh<$LE;P9&s^)Lhn{UCSDnrn9 zIKFWh@EzdrZpG(NivdfIgBbaydiXRGmf}v!$F>ErS#1j%8Tbi}u8w!1{|l3=X>&m% zd^sSF@cV_TJyXk4`7=8vlVvJY6%TKaUiG8s8z1?&WxX*!)tcbwUvj}iLe-gJ&idt{SeArGv*^`{UP8(T>BXSQ>@4bY9C;qx!I8~@KrQ$MK<$|)OAwDp zZR@R0Rh%;>Tv}ZWA#P>)B9yWs7J%MnpvZ^cJ|~agr3e$Ttr*%|KWj289ZE^n#xdg| z4zDW0o{sbZShf?0wz#&fJtB^y6(#ZG5dubft84rh|1@kLbS2PqmZAE*9|(4S$c^(v zU&z};FJYYqKl7Z`9U<+x*9Nx(db$5Lxj${wUmVszm80YCBlh*}Mnyh6%1|8G&;7L7 z_{Q`N$gbh_)Zl}7hh^pFvcrQm)gC=+{uC70rw+G#ntpW-VSHw`*Ulh64_LK?U&L+m zB!@Inz@z@(;OczJU85P0N8`R)V|+7z1<@^(PMLl*X&?|&IUxChJN+2 zvuN4Utkw?A6j6S~4T=j~mg>y+T>PlX#3%M0D=!tQj+-5U4K2xf>T{#N7KZ#%2>99{ zZ$6)=)~p(%RmY2(UaAABR^h`s_8vGd_XaTVSUB^SnsCgI(4U{2v`$i`(I%cR)N-CA zzp{D80p)?x^I-r=nu6y+puE6wZ>Hd?XOlDNj)=_nA>BK_zK&AQTso4G$A#giS7`9? zr`r49b^PWq@W$){$@3c2ObCj5y9?xMJ#C%Sov8@sM1-P*MJ*T`3-_NU7k~ilJJp6S zT3N|{uB>2e`iY8@y}53)`G Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ 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

hRd> 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

vHSrHL`4Y!))mrU|+My=J*?NCLvdFH?W^2AuF;5 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

ecf&5+ za*nJ(?RH>2uXwAoWLi;B+-X1-K%(7pps8lkBEI+#K|Ey|-3% zev_WAOZZ$2>g^}~X)L#S-#4o<3>(>JV}D%=7@NGZ?lAYnDbyK!8GM2IBil=J<;v7c zwHa*&P?;p&c70j_3XCl}8H&(oSx`^Fj8nj)=sdp!1bLOFa=QzeZ<*`pRPs1aybeLl zXjDy1jKryM2*k*{G=!TO=)&Dr!3;Bx(w*2*AC7sUl$;}3nzd0ThqZsFf!6!{70nqh zE<-zuCWeAHO>f`y(v+@%oS=(^RV>M^>)vYMfGWsra3A59x7W|ihb5WLUwAG8ht=*i z#%VYZ-Smos1_x?FpZ<$qXmN|2$^1mJ32Fxc#Js+W^voOg9y7`yoz08G0?9?Fnm2Pk z*lcmZ($o1mlddFZpoCz>D)D3srJ~uw=56RMi!68EdU~s`g_5_t->vz2f{e(!tK5ne zUjmF}<*(S>fqdJqR#2j6;i2n2sa4jBcpMiO4pBvIox!4?Vba?j^+V+nU_bFT(>3TLd%^~aAKvYeE zNv)s}l6}qRW*_KVsZ*A+;iFPchiuR=Qy7G@KK-FWOwJ5k=Q^(OjWO0jeWVXnyiqIUJ+gwn4| z0hRGWd3M2-yKrVdao&35*Up$E_XbWm)`v__ILYMhgatMNoS%fI_1`Vu(d_XpHdcN>SvUyAJBVC3fWR6IZ8xb4 zVFgo?NtHPaTkV1Spi(}yYRbnAAM*<@57AW2WANUknUeDdgGrb8h%#95lB5^KCz+B~ zq@vSnYn^)aeO7s zfqi@Im00gTB{%=hVA7?GsV8OSF(otK6V9{2r0)ma_ZpF6r&|f~L5<^-@V7sgqMci+ zB5Ax3X8)m;t5zb<1Z4KDzheHfTEB$+rzgi=3W#7G0H@BL8-kP&<`&a3CwwQpTs|5; z>t0o0fd%_4-8mFRysuCbY-*Ow7-InoY$^rH;JP3`~5*28RN7txBp>` zljfoCYmLW{uO^vr-dj8+NbzSuF4kRSrqHtsn7E7KrFs1RikfEWEzO-_7gMmzfjE%H zeY{uljv1Qc!eQLQIX4A`82+iAf68DI(6{A>TQ#GfZMD`6ChJLaMvFp5`H&7#C=UAVCU8 z`-p&w?DY{ToW76f#UkWW$i9DkPcXLib3$+Kf~edhVm5099}MS{i>EdEbv)R)6YlAR!ZRW$J0j~Y_n&ED zKyhAN;O>WK8ELNLj*Y%NM}-!~_ypVAv^N$#DW2Hi+Yc@)Rzm8uDXZ-q$ zhl|X=@oTQuz~4Y>i|EC-_Fa2#>+mBD{Lx8Fv6WW4Tq!ceNT=62F9eNWzm3 z9Scz~$d*S8`ln|3j9;hSS4|*@^+`2Ubo=lz`Gqm&0~%&!CdToeiL7WWQbARM+{J9oDwm7%=~UIo9F{7#g@U?8r4-^ z`C6cYajnwS%3dw7uf{8f1SuXx3{a6h#3-H~F^Ub1&W&A;6NW->8tzEP45o6!_R<#i z%+VHkmIlA2xp#mVpupb^G3Gp*9^*?wBuyjeHE^&DPu(xk436|1?M+7hAp#P5Mk_!M%llr#S4)h6234<`Nq1@2A!aX)A1o`l(`sPfmB7v(hiH_E*qy?HEfuEkF)$fn3V^i^R4>ZXTtb5g&`__ zMRk@hoHPEI!kDdh)$m-z#6gH$SQKo6B$}IA)&)yxvrl`s)oTiWPv#F42G9fd+X+K? zUl>4@aY|uWXnZc>i;|36gPOo(ERDr(2!LrF{8+?$SM(PhqIZkgy7k8X+$O_W_>dlBpJ5;Gp*!GF*(0TlSV2?HX-Y!jx_ge=3b zVI#BS+?w1~?9@w8Yw zSzIf=koKY~mEkNEBLS}w&Hc-yzYZhHXBhd5F=F)%BNH(`DW7{c815byBWvC=5_9uZ zlM*fGn}=WCy}~+aJ8LiFqC+m?D>7@9sxXW>oi_x%uAVz>5xZe&VD*G4#s*F=&WKD^ckOhK5Zif|q%FFxe1bdwyHoGKlj8P>D_YEcIav8CUU1&+QRTBH2(~X{GoB?y_2G7*t;}?-Xoqra0^i2 z@8%ZK$R7I}s2C&$A?b2O=9eSaIB$3F%0BN*-un2MYdzZfV2?_mz@vLq{z-263B+Mc znPz}C45v|ifW`a&v3HjNRc+e?=r`RV4T6MpBT7jth_oOb(jna`r63|9-Q6G|E!{{r z2ugP=2+}KbSb?Ux_o+9g2)dI6(5(JM7Lq30eD7~Gb(eYz#_f7g!I#u% zRtZ;+mLEvSKD!{!ze$|?7H}NsH;S765~vyg*#047-6uU<;T|h`%v{ZpYj;Sy#YqF- zV2Bm*%FkZQ;Ea3r8xjZD1NX}jNBM|2z;4Dli6hS$Oaje5Dzf(yTgEYn>=i55O%oY5 zmPM#t=hkj8W$#iP?UiOA&ajX zdS?>Xp!c$Tj{md+KCw?duXx=bR*)bCS3S_?mYvF|ZVL_1d>Zm}_j>5{j|mqV`ZqNs z-4EhgnAQ!cdCCDFEIwtz2O@9Wse_5%=BC0oYwdrq$B+sCpiiPg5}A^$_#0{n*aP>= zY3RX`hJf9Sb7}~gmDv^$+pjPT*W$>i3%7EFWl_ z)b@vvAuUSPi?nHW^cKdgt!;OugWVI=z1 zP__)Jdqh%OD*K!fxt)A1TvpE&qma7Lt#|~O-SjTt^l!pRj^%T!&9!KX7pH_1>b~=g zpp`fq27_TF7eL}M%%I4in{z*O`CX13aep)X-w;l~9=Kl)PAW%m0(LXb2`8)Rq{^<_ z@m{`-{{91aly;yvYfD%7KC7EP=5CVzBoqXOrSd(TfHgUUlgjzwRMxjitpp{pY=ora zFF#xq>O+U4AtRyiEVu{?qI&@h^>`r+u)xEGFem>eKc+&<8Lmof(gX>2k1%|nzWq+G znAs-Q#|7QeL#fF|$h>6mk}-Sir7%VXPi#DG&G)f7&w`3s~q;y-<2KsAjdzni{eRluxZsG*XfT5<_Oo6YQ^p z*P3F7_!=D63mpbIEXMnikd4YZT4^PetnwY07c2QdG2HD5N7{<6C$Ao4uqEqARu?_v zv~uGxrf7M&A$W-MigB0+ld()2jN&(&*4}ok)I9E(t z7;EK<%c>3UzSIj%Gn9EFySX@*E3?IFQljc_ZE1DE&H5afE0?J0)dghtOE_R3UG+k*vR zd`M<0M#Z6&`Rn*^z=un;Q9`K3mG-8?T)3VhkNT8KDL?R9X7?u~DpxnTWTg3GX+Bnu z3uR3-2CBmVG`i?c&r2j&LFFI@f$l=_x#Y1ucRFh9!KJO5K?+F27cQy^CG)nfa?ukx z)7V~n)w$KofhZ2P8+fz>@!e9YTdOS8m^ZthhL_Xj+n_GLmQ~3|wNn@)2gG%>?BV!< zi$R#j-ej9o&yG-Su@-*v`L>3i&E@@?dWY_jTLG6->fO;`>XMo4>0+p(CTs5!(hXJ5 zjokO&S2ufsZYPts6}J3hmq3D}lJ63V+8x|i`sMTCU+RV0$Mpch8=%uoc#3A9>Z{l$ zajFZOT)P@mC$77Jz_ZDPP5iZ9D9&`@(~&8#&F;8iH1X9tLBe0`I) zqo**byXL5`E+qO}rf#n20PFiKJlmBl1!7_PQ!Aj*+R2TRI`gg8wQ8zr&{i14iU--+ z+`=P#V#oDDAI1+ol{7JBTgH%Kc{lOK+o&#+$PGE$u}W%Cf|8VV_va;QDi?{`ADpOJ ze@)bSgepGHu57?FpEgl*fEarLcm(tC3c0aBq={Z)JEuAJAUfY}#*8;gAUi7F@0F;* zhA>z!O%oTXd`pDD4tH=7C6Q67ip@RlZ<&x9K;!UA5?`)_@Yj`8)zytG3{PGPSVh+_XDcd%Eu+b`;<>q; zGEuw!*uf|?0%d!#ZCDKUJ+ngOKwLt{3o)^f5;GiP6^Q=RCu**r;wjJ}95wm_uQwwa zunQ-6KC*Qv#lMkveF!%7{m3BT3)#8jwr)c z&T*m!Ebv!L)FMp^B}aP*Sd>KfwkrEFr>3hEN%DN6!%&rTIvB23Zk)I&zRxILABgjg zu(g&U)xDb|v4dOU!Y(l4P?Qms>b{Yzb)2XHyXNr>89qx1}+XDer_dQ_KeGk9CKyA9VCdKVLQ@&z9MW~@%6MR&*R;`drOPCM%TKTn6M zr`DkxJ9lh1EAHpU-H5mj&dbKF7)sr3l-mpdGSvrA(@dTJ^eXvxs>u_;yo2ervZK2D z?e%$4P5!jiiKIip&H- zRI*!qdx4QZ0VDR5;y4K8TB3V9CDIEVp$|;KZl)7AjYOl2MkO7dyD{Ua)=T2 z^JApW<)Euxm0kn0jCO=la{u+qEbnL404O&=9vn}Q!zjWrMt}wW#uzbqddGUFt5AsV z;UtOV)z}{Og}!$RtV$$t@%8VsKe9bNUXThb@Nhxu->RMc7>u0Zs;6}e$=*ABCZ$3R z*rf7y?hHny+i#pjcj*cA@n7j)yUAMo>gTyd{nT#ByRONHdChJ^#L$m#*Gvr!iOgyF zDda9#QB3mUCH6`7ALkb0)&^f=Iz`a-i$RpLP2gbJeRWGK$q)&hHAA`Bgoi z9tJ5%dzLkmhS_|IV|;HP)b5l^uuXI97GQzDv0GZmU#oo>8V?ylhuA=3RAW`S5xQJY z)15GkD&~HfX2#qv+N_23zx*4ADxrv*3To3#;J+(ToN9bZUunFVH_- zym$Mmt!x}-kG=Uude|Q5kH)oz>e>lJ%xAB0k!r6b)3v576K#+M zkl!tTw{cuU4Orm!HPp@^jQ?}uoZ+*cRznPzEvyDrFz-aV)~QNkUy{?4zhCKtxgZcm zM^nb~J~QO!X-M#zAO5k{xh%GDU$kO-(Wc1@Kr zc=2Nm0So+%HN;gKEnPG@c!S1_SbpXmGEu0)5vb?WAj2_=oVh6;xMWym73~YzAbc;>JbkxFFn(X{MIBA_4PDJwJ zZak!~KfEl9gJ;b4nM5oy#cy4+6T6CAA$0FuYtN5^6V+OpG>@3+($xz%oe@qBU*S}D zu&tsji_^Vx0jEC!PFxX?R1Kljw`Cm*BviKgQ>z|2)U0Ff8rw;RLM7 zA)K_&52ufVQ5j_|6G;RvG5iOdBaDOS)*KGdTV`{QLE{d{=(~>L1T643hSLq3{q06C zI9ZWqb)~XEf_G?zu-tsw0a0U==z&Cg>%_;K(SQYhzZvZe!uUhsbcVmcX^o}poG*W6 zwl{eDQ$s4#0_CD0;}cq0{m<4#=t`1u>hYjI&sf^0HkPnRBDYpt7tZ)fkID;ldEa>P z%<5Ebe3DsAvdb$k10;T&v1p}Yf^ZzYjV=bK&bXZ%%kitRYNEgiT*(gZT?|eg7N-0C zw{+cowGb!t!d2An^RD62a!6p@wB_dN3lsb}YIiN>Of|9J&{)9wmS4_TI!DF=b~Dat zEF4Kd94{OpJUJiK@HW5az!md|j#|(WQMB#a8Ip>;W-u(B?~Mhl$)T}y&Tp*#ozXU< z_08l3>MO1z>2cPfYL%}^5cTekquDU=wzlsbPg4O4Je;O-JFzSM3_Gg+&qKA-?v`G0 zKL~%)Iw1n#Nr;U)ge#OH)uGIwSFVwZsTokfq2^1jf))kik6o1C$ZM=DDVR9LMMjgL+Y_ zAD|aq40)Y#$jj*~$Ew&7#yqEJ)3q*!y#54^btrhr&SYe-DVxcf3o(;pO(?b9PIwxs zsR<7%kVU-n;5XzLu)gJ&7@IY`^wEh*n1q*}T4asGWORcEhBEm);{DlO^ZZ$aucv z2y)CZV1fS|9P?zqEhDs%3wDnx%yIDw=*+A^>Xpgt5Bq(2Z>Q0%EQOqoB+w6d5-k9Y zm}9*^upt>cG(j7Ot7Lp_Fdc(yeJFykZ0hGpK=;%VxGv}^;>p2*rRUq>Zf-UEE)#&5 zuAmSzy1+C6_FfH3?Z-)g5!(xu5_5`C?m_}*lz{VB36u|F##{KLc-37<;7^diyFp8e zdO6aB68n!NW)CBs?oEm6GwNI(t{sK<7in}qL&<^V6wt$&9qTAp`#_w^*Q54^P)8Bnu>^nx{%=UYLFF15FLlNp>Eb*| zL8@-6ulZ2M@Lyp@d6n#}X(DC%5Wa9j-=j!MY62P3}(oKAfN&7K7U%7FOqe zA%geMlfc7MOF%k-N=PfM3%P`jBqq95Ov`MQ(c#1@~gjyN% za4dV?grH!<;Tg<&-%9{klS2vUonHduGuc+`j@LcO#u%UU4AaQyG_$x<`kA{Kr{8(L zRLhZnECFDF{~Hq6mYsT;_I`V^`K6#q0x?de$t9OwTUNb0>E1RTMOatajwK+_nLA;E zP5nrI=z-lHoA;JssSLq`&2E1ky1eF3#q`&Ho&@wxEdfNd6p`mltWR{UiH2Q6cQaXN zCH!1iN%93GTT-ZRG#LLl2`~@Yc;@e*5&$kFa7GC{{wjfD3@VDbm$tE2E+p_LNZ_W@ zyVR0-oXv$U+7%Iz6@ji&ceTMm0oN|*N&}a$V2|HW0>Ju~Urqx0M-l*bGtMai3h&0w z`XF$EyJj~kNt$`Wdy5h}bxkPSuI6V3-Bj~M2gB0;UIM_H97;g{{1V_E8y8+gUK1^C ze{s)ajpkVlxzejo%><%Tdo(GuWXU4O5&#zXzafDj+U=J-eoKynYy1;D1qN<_wEWe@rN_rK?8=-UX9{#4UPODxt1+Zyp$pHnPrz z)6L`aY`o1Yf<-oh7k1&*^ZyhUDr6q&mfaaJ^|eBGtZA=PZdEqP&Blv`(x#{AgS+zbHwl8 zvU8eUl+CVKnuU8|>u27}*AZvqPvEv-hAYb8c)R50=>7)F^QTPaI?=8S0d$@c&5tp` zypSmfz%4cnNyCjkHTDi)TP(wT(t5?7G&^E1bJ$tnpw$tRCmjk60&wlUgnD}h*PA+( zcYIQU*fp~=@t_>3V~ETFi-J&>>iv|A$#itqxKmiu+tPBMnJ(N5WxA$+c$aT#%`0i* z)vTtn91v5IK|)Q=*6xOFkm$6PiDekQ+uRqQDQEZNiQm$@Ws_nb$$1HRMyCyt$PODL zt6u9(i(eAEiV;P=+m4e<^(5c?>yD??pNz{8AgRM4p2s)5RqXw8;9UurS4(dzQBAot zK}%3y1sst=`bTf_@zGXm=CI|VE?sOiP{3iBC@;LwrKQNnOK-8==J6Ui2Gr-fU2GJ? zA#7);h+aXEeShHHvw8lu1NUnxLQWypjAajXnJosI$7tVXzT|sYI^W{ccpHGK^BK$F z#G)=>iNJ}_-|oOWlGld^-eq2cY`2ms@E8uzLi678yvo! z*in<<5FgRe7x#`2ypvQ(9jlcfCLuGBd?`z&k=RArm6P{0D0+JJwhN~tY|zh3sq{~s zQW>+SZRTR7qZGOi=*BBk(1n#PfUz%X_nAELTkBdn0R3?(6&sh)8%p<*fZ2WnFs z>(`VjUxG6i;7d^``oEb{!Ecl0shPFX{8g#z_h0`m*34!&wrRpG#hsraMQFrg9EDjH z1+hfH*|tun8>4cDX;w*Ga1e_mPB~?y;-Qx>ndPXS1)IQI&L_=_my+LtOxpUVpY#uz zW(C%_{BkLk!BI*D>}H%ZrTWZkl&w22nYGZ(VcRDWG`T@=J>*uNz5n}4+IYo5p|@aI z2H&Stz?vMUR0iiysZhPMAYpc(h@f7&#NKubrPQslW@4cZV{aVkpWVX}@k~L~8mJx)gqHA{2!wzAfzSbxFbGZK!&o2bZNO1mI*1{bB zJ%1~75FXF1bjy;$moU{!+{f$RfnD>zSpV)UMA;icgIdr_f?j$ncYP%bD?{8yOyZTM ziN*6zeFvl`KJJf6sebvZW`Kg2#7u$`0J~d<&#cqQO&h=b7dzjreFo~$7qk|JZr}wm z9@Ok=#9-ao-_-xoIIS<}h6z10_0ds+z+M}F>#fglKHwQb&vulc!O`-@ES)FL5MzkE zhy|M{l|@Ww#Onv6poCZ8iKSZUa9rCsf1bMyF5LYGA2jXu)!jKC1Z+fxQA>^gH{Fe- zh_{SRIzdkQEp7YVj}lZ~deA`-xu6`6xfgnW634tqa*1yleFyEfu@@y7KR~w+A7BBqa?mp5hu&;DZy}C?nYLMUYmkx}4 zb?IGkry8tE=oNi(QR6}%B8&*%q69E3!|(M9tjVEX4bQLFjMQqs_DjW$SxTnR^+WeH z9xMS=Da_sMCVi+YL-DQDkM#;H@P9+E1qHI?*+Jo`MLhRd^zWgILg!B`sOtl!E<=p% z;=9!G9#O z2}_TR1+0ts<&0%`JV8Plm=KbEJ0iPcco#n9j^N|v5Bvne_1eru_#~-U2-S`zND8#? zi1&re^l_8dsZ`YndhGi7(3M*JYN0 zLFBhegofQql3rn2Mexkr5T}f?(hP;~+h}nDOn!vzYud(t{2E=#!ff@+1WEqOC~e*+ z==4&9yZ5J*u(|e8X!F^ilR+RbD3P)=9O@VHHINxqCQu@TZo&v}y2hCGK2ga-rDamtJ6hw;Dr6skL4Nu3 zLz$;+`73+A1`ca|hi8dF;XQ4o2y?)8gafozK6Uo#@}Hk+Dp}Ajxem^B=V4ZqyfbGO zq1`*BUP8QhV9gH@B#btV#l_Hc@AOi#SM?O4Lni2$_dMSGRnLAyDtfD0Q+g z)bBMsDl3ZsXzoEfjW1awUFJgIz53P&=5fDZfLZ&iO-Hqb%^Jr9-yu&3_mQzB@3nDl zW-AMSA4l5pt88q;_veJZOpxGg6PCQer)#FF(^2T|nYyL@%yp8r@TH3ySz1kuMIRG3 zJjj;`61~c|1=~nHhPuH-X}E=DbfN{Y^x{P5Z#O|gih4Lf^0eEp z*z$&1f1bCF@UFX*ZUqN*AR>E*Xt-YPV|JGy#=!ib@U-n*jf(6A?S`-?{PuebwqB2lqaz)UfI`C!AUh^9BO_~5Avk7CDBS`)cbgX1X681yBwv_ zd4xvfg|J{v4rKs{GXSo8J;>(yUmTze{((%%;xSOLtR`skNMIB z!hjg(8y*H@Xrm;+1l&ZfASG})C|HPODL_T*NEZ-uTHQym1^f<)?7WluBVphoFTZMbH z^-ew(3dC`q2k!>+=`R$us!ANCObI7YvOJz30T%dQOpyFeDdvy)tfw9E2i6b%t@}B{ zF#Z`r&o<(3bTr~WHw@yh&^O~3s+vmIYP1=_q{-c3%Y`_0jfQkzd&^w#=Rwlw)Q~KH z%@Y=+O7hXjc!*=n)c9^ZC2rxB!iIUXW;sGBKH9c_dh7f)yS9?0JZwAry^FCh6RY+kreU{mR#X^Q(=$0Z`^$@U1SlIh*lbd!uUE(}{ZwgK+n z5R$<9mcRAnXVBppLeBF>~udEp}u(Iwg z>F_U`mV#j!e-BAuO%5Sxe11qa<36mF;3Qafjm>W)tYw=@t+_8w1NHJ+f6ny1;+txA z$Aj;{0uKk@pPm?T`adrpd4>-FY*oyOuZ1?PQyr=5pP&6HSA~7h9v)iE=Cbf}ifA`n zH(kPjh@BImK#Niyp+RyiX~uR7hDylk^2@O$F++Wu)S&f+QhPl_NRL_X&u@Po8jMek zhG4np_FzHLIIKfW@x3>2`sOw}+>NgBf4r~n5ImBDvHj!Fz@sT)c$2y064V76&WHw& zuV_fsF$&Yu<$oV^frdW;4d5Bf;_-cM@1mE}^AT+z@mo~kP`$xWi-bR2+mI(*Tm22u z0IYBM<{@xEZ`F(0Lyk2^rmli<#jV4Zs2~(C{4^ zfR;{t2l>|`(l{41`<6;A{1{{w)M!`zz5?q2nfQ_1hhwlIQjj#I=7;m%rC^5}17FwI z3>!uUFJ|)f7TT&N^j~9Y8w9PVyIT!G3nTp6VypQ&tu3Mi>DU&cSXC82%O)s@CUWny zx8AG7agKC0UP1ir_p`LGAq7QVtJ#&GwTE(6Y3wjEcp^sG{_GWhW9!ai*-8(y6i$8V z?lqE_c2oi+F=FohampR~o6Q_Kt0I()V2v^uV#{(9m=m|^pVQToZ9pflD~!C%#(WE{ z)pAwtiLG1*fSgDa4$*L`1ktgR4JN9nqC!xXp5dcodNaji1BYNNuO(rZK!KrGE^A>> zExHctxv=8zZ3*ts$kgeqtq3~z$CdH4ucsw=NLzw#d&cdDL{%(if-@aZ9vCkWkP7GA zo`gk0(wUVlXiZEjpK-tY@-zMBD0@2W!|TEolS!iRvMP_m!)o{6*bnGc#e2nQEhI{5ad5iP=A;KsGsv}@a!)M)>5ciW9-@hUF19<* zDvTmj6hXo4!0TR2UX(JW{<@|8p^|sBAn8yf6y^TCf1L7du)ukdGNuq;bSXzB=1u(1^8j25ob8$G-;@VeDuqiZ?T6wOv7ZyebZPVT@>?9M6|c{$03iT~ic-{^ z8yPYH@a;xx9$r)8%QX?SboJ>N=v39!&v;}Gs1N|{SDz17+MT?cJk)GW|Lt3Ye)pF& zHnP$@*$v-ceF{O!AB-sOnJ%47AJ)QJ)YO*8Y$SwX^p1V(g~IjUP~^b+mS0Zfrbi1K zf&Ene_Q_4EJ~HdIqdp&mw$gX=(xfiwCRUw(Hkjz-?uo}}F&22@1$u^1KS>%9Yin9C zJUtCPJaUOg3op?1T>92qWElyqRpV+t@c;mK03h6e!~fO+Sf)q01hB{GoVmnOOn>?g zd)e(t(i1{UG;=&w{FyVweiV1I__x$`EfQrD~N8K0tkBCfZ)%i!VF z(aGH4cRBIg8yFNwFWO?QEhj>O8B^}Be;wEr z9|J%h^ME*=Tq+56RdHAHile551kcDoO34ir7iTyoid80KBicn+L%jMJkiRYsEY4k) z0W581_qJKmqnUTJc}Z2>TIL&M>$Wk5#M|JKjIE?b!EvNexXAu7b5b5o#;UtsI~9jb zVxCU=SkcOMX}-~>p2+feiz=E`;rc2TY*k0JPNv82i5Gf_Px^c*?u@+K)6s7qJ>Z`@1)~fv(}--sr|xa4Iw@OM;}f#&pRQO}aUrg7^l{h+dXO_UhmF zhfDt_8>#=-Hah-zApiBHu)MxPGmh7D_tvGRP4&}4(_16ob4^ZiBD!x1w%B9jy*Vg; zdwG%6y|)Nc>V=Ut^8X1DY-SwJtNvz~0Qv+i%iSE;``Sz!IZ$@g1TQKGPsy+q|i9vCBxp{`x* z&SO9jj^(pjZ*Te~8c;QCu(_6R+qi*+y2#=Z002&aZyoj_@4ozp4F1>u0j^)4Y-iJA-w;t;H>XeDc-9+t*Y zORu@cg$68jcS1|!RrTlcdIuk43Jl1u)46U!^>DVO;ODvUBSn?7#aFlIyx_xAr^ov1 z3@i&!%DeME7{QpY_@oeG0rJ+fpR9*hv2>3vd)D_GmgZ5*v%XBR!Df%34y>#B<)CgR z|JTz6tTS^y2QrRw0ARnxbLIdf_*dU^?CLlR-H6d}ZQ&7MBBa-2=%x<7O;w0N$V2en{uZs$7n3y{5+SNUAX-J zbIkJhce(dhmq(8VH3q-=D2V>`J*o~E{v!VWzrorkT-SoCu+z)uQaL zUrKE&e4WhYE}sJ6!sWld%hf77vPKl^$fUmBbTt4lm(w;+I6K0=NTas-EHQFB5m?$!8E@MQc z?sU5Jlnt;O<8uR|SA!nST#6U`-BTW`2H{!Ce|uX{t1K+nh-AMIk> z6>MOEKc@=z8DMq>83Hrh5y#M*A}A7E#j)>>6%Zw>7qunLM0-^ADYN-zqSy@h=HvW4 zj+tL@><{Lc&sUB`I>ZFG8YqP;pO#~U3VtiopR2u_MYZjh-RZ#Vylg*`L2V@&bFDuG zmFly(;MlLvF-@ZSvD6~jKKN5|jFm}aZzO`h{WC{bcoz@VOSUHU4celJ+bMx6`KmnB zw|_&90eg&pIgVKzaSYhaI48$$>$}79>qwahrV>8Ku~bF8<&F;aoRUaps?8ljK4xGT z49ntsjsa_O$T5rabL=`Nf@*ZiZ?%y|up$?(LQTw`3czz@JmR^~Z4R2gogy zwa3<3weN96Ov9{~06I%t!)`SbncWUM0Xdk$u(R6Flbgkb-2Py>z4$7(aNIPli36li z;nT{^q^&tQH0@KYZNLtf?jYM9>dW4yfYBmXfh9YhNbm->3%ULJa?^fNhT>0}^!BeK z{D2RnjVB)vNtQaLLqExRjOPaB^hm3FbIvdJQlE6kOx_S{=Wi%CV2|-HCpXI@xdFQw z=agF*_DGhPRAvp<;xz%wq*on+J7pwQa&uuh4IWdp86(MHSeD<*4Oo*yxmliHZW~k} zpJqBn%8a06oMPqY+Iz(O6`%>H=bYm#>QgA7gpcJ0Ebu~Z-^q<8^7%pNt{Z&pI;@3u zW^)exU|56#jmO*A25SuB0oCy1zbvr8!@sQ4$sO!x(9jt||Ig)ihR+IYclSCYCkMY?WiJ8x5IJh~W^g9XbF;PLR*Rc~o_VMY30oGf zC`h(oRGxw+uK8-(PpN4|f{?ad*=OmkCco5R&GzN?n%bgWhz5siv2TW@H7x1_zzj>) zY98o~NignCjP~PEDGa>vR+kB9lOGfW&r<(nyR@RQ)8#X(BZRdHZ_27om7f`xYq)~g z7(-Evy!M!UmWOuT5rd^5Qh_HqO=x~aL9)%e4_G6K}2g)VRq zYOAn5zUIG-hD#f4samz!RMoh{gf*5SJhxqrX5A_+##3|$OG0GnKGu5=ShL`Zv2(wQ(>osn^$NTx}iLF?XoS$0N$B8&TZ*UK@81eyp`nQ{M}R zF>GrJ@-z_9dIdl7Iak=2u!6?O0I3e)%6^RyQpn=HF$x*8ajZCWInugB|!VEa9c*mVTI zk5*RxIc5-dT^JXleTk4m0$FVWpt34xk+Ffkx1>i!M8R;?%B0f3)$;>5NRZT*8myyx z0hxXX{IvXdWKl@+IzEpTH9IWX9yu4e+}(R~Rf-Io`0F8cHSV~9#bG)#O4C_Ewo3Uc zeknAz4N$L|uavok@!3ZzV-tY?ZO`3Y68~T-!Ft{7N;Urro6)HP(R?1^*E00s9#U5Y zF_MID1JGHxu&hojg8-J_oCpQB9{NP6mk}b&C?At+k+V)t|tdjR2vh`Z;S+HS5^iV>aMX!paKGZzYc@@_y`RccS6ZMgm58TZZtuIo!}%C>Hht58(|a&x5$=2B#0#!35{J2UsT`;(JFTY)qTD^R$ScAd$mIElG$WM zTi%yW#Q%{rb6UgGusWam>gf>0n?w-)$g>SM-*X2xSv@2$;rGL-WSX#re@79A^B_dT zhai-5ly3`~7IGS_)+=Z5?TDC(xumOsWYR+HeIoX-q34IEX)@EROonB#y7pSZgePJ? z3UF(0Ex!JwEp?Cbj;wxFlT@FAHeU8(OOXfu6_fE)4#|%Q+F8pkjCLUGNG$q=zNx4v z)?Mre@|>j@CLorc$OTI_4YmWgd~De$$ZWlkw08(PqP<{^wGG1n)x&)`l@_n6v0FPx zwTWv;Jj?x~-wZx9a`W1cAXlnmYJO;<0}1}du7NsvL_>;W+@AiUODOjw8cTADGrFp) zXWlDkXIV#R@G01QWcT|el_-2DX-_D~F?=$UmBkyc@Kj7cMuESdIP0x|yPMxTG#|GH z9Rme5v=t6!`terZ8#b~|M)|~S#^36eTeE-F(K`-tx-pwHXm=`S8hY{VQ zf>gJnKd{j#^oqtMn+()ZbY|A@k%#14H{_?rASALXp_pj%Wq8l1rInQ1ME0E%rLKKY z6%p6mlM0OEvqy%Ecpx0X6_g~vcByc~`z@WD24ZlRqrJns`_)n}rmot8m=U|IWzACa zviEmX3Wbiml8$b4d||9=eGrH$Mpe~7k?^Gx>2pWrPs*E{_4DFY-cj|O1S6I#;x~L7 zC60k{t(L0~^IIoEJjp;K>?K7+GsT5>&5fLD#iUG+sD4+_2Vu*n^4X{uHtydy?p|~v z$9>XKC*qtt>huK3)>rz2!_C`y9E3E^`zxPgJt*g_=vdHU7C$DU_p}#7h`UEL(D2_G zZhwk#jc|hiA=e7!J{tDyG^D<4O`24s5nrZ3;zd$xVHCg*6;PN~Sp}xON|Xj!s*^ z(L^B*R$}V(LsWBEm(TH2Ol`qPh=$@M#6bV&7=#+&->JXHPr=#k+h~%G@@>uoaJcN~e^@M|V1xH#|AZ$?n_d1Ga9< z1)+XLGF{EaW*z_TbLvsp+Q+C)vSZXMdR}ZFJ9G_kPe0t$-d=JK0b^R?aO3yz<-&BH z6mc%u=dbi83k$QhE7aZ&{^!%92KXW6p@H>Se;aN(L#e(qgr2RcpViTdGhjc!zkTvE z?D0KA=-IXdTOAFQ0eg(jIZ(FO=qmI$xrT~_zYCd3Hex*RUT!~l?nIXCtws{!HKYbG zEUWJa%78UF94NCo|3I0>=(x-{U0OVUXkHI#yb-XH3TI(jIMhjV}h z{%;JFonhzjXuzmmz-11)?hh|LstF*;mBa5TLEmHXGZhjPVDKOqb~^fz0PAP|_9Hn% zP31F$o~`_{)e-+lYK8C_0Trzb2u1ADj1;dog<9R1Tz-@ztWufLY4NH5_-+nl9x*ocLoAZt4lU%U1TH zBo&4WNipz1Cikh+jI>4cSS`SQ9>1JgtgE2^+M+nr4!RCudxH9h(K=O9*4eqEim^eIdMbbBH)x{vjyW$?a z&=Aabbv~>yY%pL7RWe!|W9%$I)jd@d-9~}+jBhV9F{o6VAJg{qc{S;YCuv_J)j3!u z-=0{zqg6NIA?;s6%o+y!W#rr0k*lgLXg)j_#O^8vIN3+jIXt+JZ=<5Str?$l+o=$? zRanQ;&v)_>D^~6B($15`^^Sx3+bTzuM_^;P>_9s2qZxQj=grYP>WHE*e2w1TvSW~> zGpj{I%B^4sinUdYR<{;@O>IZIkUSdEvznWu-Cjw%NLo|PF=vQY;6`MicMVv83+Jb) z@{GGHV=R}ZLpR)0IntndrMX9x(`gB?clxvZDQ-o!3?K*TMv2wGVUY=J-}J1R?sv=N zrQ?42-ou6ina~Xcma>K_t=XWWN0pf}e7*L7tqdfA>bZMfz(eA~o4a8Spz3HPX&G)- ziy@?P;O#HLtrVvH`6$dDi7M@vR)FjH)^UbVfi1dUIWW9oNh(g4J-PS@Nzw}~_c9o2 z*`-`(e|@d@Y0;c;M2LOD&T`emHEd37uZPeV5E`0o`c3t^CUk4e9a-QPrXEQ zt9JxSt#!pUYoJ#s2FZLs~?N2(OU^HHyECd`ICkJf9zKh*>uH zv^925F}U?A2$RqIo!w%qX|s&+4hpa@2N<@cBe_Vx89VVt4}0*d8^6LV9Ox=CScAQ< z-$z|Al7Hv$oWs9Oo+;Yzl2n(TudCmJZQBimzJxqLWz7M6fz zbKvju^T_6sHq1)8DJ*D^O@I$7^bT7LlO8 zI2%3}CR~QIcRcRhURTNrpEy__BpnEzD;wePB4Wv))7Og^ejA6jE$gc7&r5u)Po4O1 zR5JwYmh3*-V)=}it|2a(S!bLj?RsCuq8eFzQi@36pFH#LjC%__zX8Aq)MjA$X3UFm z?{Ay>yC0HbCz;;m2Oqnfcc-~sf?;m8n4}uR*J`Per%XIUA1ml<;^WydC9Xi(anN&- z`22f`52`ht#7m#IqBeg>;?v+F>H9`H#S)8$voGl}9MpldY^tQnC$*tLQNDMyLx!hM zd|uQxX>h@lkjDt&^P6-g=|hdc%d){Qr)Rf_eXc_BDE_C8;)jfT1M9K=M$9t|W}hMS zY~$WGM=2Yyn{m#RZA)UwT!V5TkK=w-b6ZtD0#g-~M5W*5MMNm<^4@M-OE4^(?^8Bl zO%78woAak^H?wD8qrLY#Lb#G$KZ$in9J|_ zU!i+X*TCC_TyOrv-F>82U|;E+dL0~Wx!s*Cc>}$_0e%h%S$U z@ItTW*Q+ZRGejIU=@h&Xe`Gt$n39bvLP(zD<31KAmP!t)bSS5n-{1r+@G%dR)8q5m z?F^fQKoUGK<>$D$5AtZ>gjM*q{Du+18XER#R4TUNgZl|fj}~TTT{6AQ*-ff~-Q#0;Y%E}b7sfiju~H$O$49Z1Ct#Zn7?{!Qmsq{u%U%j`E8d%_eQI+}(#yZ20*J(Kh?05gmam?C<>h?{ToU7C5Q<2`dUg+;a4 zGToalVa~&=dn*Z%f&V;?*0IE-d8dH&lc<+=h&w!oU_ex){E2Lw;G0cKfb?!;RB(tlG~=F1Klj+=D{~b(ZZG z|BBEjR0fZC5v1My+(`_nb3ct^z`oKsIYxLLMV1;WA%@z-EQ7WA#x>;IZH&lBAPli0voF5E~CtdrN)b`6T_OoR%U)X<`LKxkIrE^aph2M zW11e`q7MhdWzIV0kq9f*oudw{J*ks-tf*h4x-#u=D73N8HL-g@RyjV}SOgw|wlAwAoAb)pL*6k7}EQ9I*R-VitdOpf8e& z(WLaob_Z4s%rkDC7y7-}Cpzpmr0nQTV<05{`X(doG`$WcSo&S-pYc2u7OzDnG!)K0 z?vyc=1|JH8fS}9OaN;W>;YodzwjJO5GV=wCZvzF2=9d-T$u;!zjJGx$a4@v*MSi>z zQ0oF_FJ~K?b3>wDyPZ^2f+0-hv33meC{C)_b%8mvjw1T9-Npf!{klDeAew50;_~tC z0wFL6!Rj4>p-}RdwjEED{ZJsaV;sXGQyzB6C?OF0y-{x!%Z#?AMPyxTPt?&8ZKGL0 zXGW$;WE9I;C43XL|Ktg&fIB5Mk>n2EmE^#WMI&~!mt?sd1oZA^z#&tm~ePzbKj%ja!6zL-OV=WxQU7`$_*lpIe{I%t}K<+E&{&T zYLs-$WSz_VwG}<_21qZSC7R4`%HP4%%yz1<2JZ^(O>iW-g*i%!dj&<&Z`Id6 zPOEdBi71UialFXkwe8y%Ai>I`!|f15tPlo`G@TOlJh8W!!_%o}MtKqRhcq>z4Xv6( zVYOeSv@#*4+FME(6+-5~=$J_Edzg!)bP6HG&+PEbEZN_}6{|z2M$0n8Elj44)5X?A z5_#tU1>FM*C#&LO7N|Q+uUYzJsMCj4EOo>>bsArYBJ2{pmAkU9B+6?f-3R3N5wBUU z33Nntf(K_pb2}PJh~{iP1AWDT?aIBth6t4iyPo@~3`0cAW~nM_Mj;fjUNxZ?*SH{V z6io~J%L?yHW?6xyUIWBn1Xl<*ha^#0@H(D-3S-3Nv=p<}z6?o@jrDbn_tK-c9jkPB zOQBWnQNJ)I=<9_atyL2xedUOB*J=h;ztZDT|5yAtS31U3al?XTzl>Q;$`Kl#xP~#8gbLy zstFk#?AWblM>aTK4|Z7f=*mThRDGOTTnsk zSHc-Yt>3)YRCPla2`0ztK*ldSUQntIozz;i8!f8mu(k>Lv%`PKGt*`DiJCK+Sg)y( zYSh$a_B=F*>Eg}?VfDc487))PVi^?P`{yOz_7{owADnoHe@(odW-wgLo59INPMdf?V;thU)V`ZP zHtAQ7$heEbb6MqKMP#6;z`pYa*IG{Ef2L0VR&A4;vA%F?JlO!iw{-T`JS!Gl2!J}> z#3}f4=7FNvFPeDk3#2|Q>68+r{`UER)i%K`AI(NS!m=pvxjs@&fihpPnC7+XUES z{5Rg8q3p*QLeEwqjzRuGM~e@z(U_NFa>;s5r*j>-__}K%J_Cl zGO)n^;=F|a^C5#Xj4m|Cb;pm_Zhq343<&G(Bcj18HFf1yN{jiNbU8h?nttPGnGUdi z@NeDE8P+$SA@poZp&uR13%x3Y$i0&H(Kna0JH zr;jdp`Umqg;ww)bTnQ90?8bx1Ps`J}x@}I{j4Eo#LYvB`DWr>9Gu5w{1G|Ds3_kh5 zZ`l9CCbA2j{%WQ74AY{0aR@Bm-8&^ua}4=xbJmRJgW0n~51`298CTzxl7=h~%f znNKitD_O_zTJX?Jc=MEy!qMg_`OQ;C!)a3`8W)_o9l=x~FMsO>Z?M9`z{h`rPBUxV zKiw|L1wTcQkU|PBg&0cBQ$ov@kcM6fT?zqf4~E>RfA8R<>fgNad+y!54$D~wYkhN$ z`z&-ltRj8d^2#OpwFm1)+k?ZnJvbNK9;{K;GMg#IRFh6@dr)z^_7AyNGVATl=kyh> zoXiq$T34JX{OGk=(rl$5(|r+`15Tsu!NJxZEaaKGZF*DKH`w{*goZ1hORdZI{Cj!5 zu5VeN-&*_jgPha1U5kxQST3FKto-KI`ytpKB$SK?U3;(|)*d7j8DzExWf_!A+=^#Z zwd_>;|Fg^Is&Lwqq@~e^{>pvMTIZS(t<5Y^kKP_6qy^X>tS7%cc=fFMZt3Ia*T%0n zd2n&y-nn;+9?N|{6@F#m9l0o{C5v9c+k=D@jz}~3;_<)_8 Date: Thu, 17 Jun 2021 11:54:02 +0100 Subject: [PATCH 057/291] Address `staticcheck` issues and remove dedicated carbon go module Make the copied carbon code part of the go-car/v2 module, and address `staticcheck` issues. Move README content from the original implementation into `doc.go`. This commit was moved from ipld/go-car@c7430a143c30e8d2c276f4d7d56394ad652c6c62 --- ipld/car/v2/carbon/LICENSE | 202 --------------------------- ipld/car/v2/carbon/LICENSE-MIT | 21 --- ipld/car/v2/carbon/README.md | 14 -- ipld/car/v2/carbon/carbon.go | 6 +- ipld/car/v2/carbon/carbon_fds.go | 4 +- ipld/car/v2/carbon/carbon_test.go | 8 +- ipld/car/v2/carbon/doc.go | 5 + ipld/car/v2/carbon/insertionindex.go | 2 +- ipld/car/v2/carbon/reader.go | 3 + ipld/car/v2/carbon/test.car | Bin 479907 -> 0 bytes 10 files changed, 18 insertions(+), 247 deletions(-) delete mode 100644 ipld/car/v2/carbon/LICENSE delete mode 100644 ipld/car/v2/carbon/LICENSE-MIT delete mode 100644 ipld/car/v2/carbon/README.md create mode 100644 ipld/car/v2/carbon/doc.go delete mode 100644 ipld/car/v2/carbon/test.car diff --git a/ipld/car/v2/carbon/LICENSE b/ipld/car/v2/carbon/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/ipld/car/v2/carbon/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/ipld/car/v2/carbon/LICENSE-MIT b/ipld/car/v2/carbon/LICENSE-MIT deleted file mode 100644 index c69ae66e44..0000000000 --- a/ipld/car/v2/carbon/LICENSE-MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Will Scott - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -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. diff --git a/ipld/car/v2/carbon/README.md b/ipld/car/v2/carbon/README.md deleted file mode 100644 index dafbb49eed..0000000000 --- a/ipld/car/v2/carbon/README.md +++ /dev/null @@ -1,14 +0,0 @@ -💎 Carbon -=== - -Carbon provides a [blockstore](https://github.com/ipfs/go-ipfs-blockstore) interface with saved blocks stored to a car-compatible log. A [Carbs](github.com/willscott/carbs/) index for the resulting file is tracked and can be saved as needed. - -Note: Carbon does not support deletion. - -License ---- - -Carbs is dual-licensed under Apache 2.0 and MIT terms: - - Apache License, Version 2.0, (LICENSE or http://www.apache.org/licenses/LICENSE-2.0) - MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) diff --git a/ipld/car/v2/carbon/carbon.go b/ipld/car/v2/carbon/carbon.go index 3122504f07..3ba853208f 100644 --- a/ipld/car/v2/carbon/carbon.go +++ b/ipld/car/v2/carbon/carbon.go @@ -3,17 +3,17 @@ package carbon import ( "errors" "fmt" + "github.com/ipld/go-car/v2/carbs" "os" "github.com/ipfs/go-cid" - bs "github.com/ipfs/go-ipfs-blockstore" + blockstore "github.com/ipfs/go-ipfs-blockstore" "github.com/ipld/go-car" - "github.com/willscott/carbs" ) // Carbon is a carbs-index-compatible blockstore supporting appending additional blocks type Carbon interface { - bs.Blockstore + blockstore.Blockstore Checkpoint() error Finish() error } diff --git a/ipld/car/v2/carbon/carbon_fds.go b/ipld/car/v2/carbon/carbon_fds.go index c4cfdd6093..856eb9bb32 100644 --- a/ipld/car/v2/carbon/carbon_fds.go +++ b/ipld/car/v2/carbon/carbon_fds.go @@ -1,12 +1,12 @@ package carbon import ( + "github.com/ipld/go-car/v2/carbs" "os" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/ipld/go-car/util" - carbs "github.com/willscott/carbs" ) // carbonFD is a carbon implementation based on having two file handles opened, one appending to the file, and the other @@ -14,7 +14,7 @@ import ( type carbonFD struct { path string writeHandle *poswriter - carbs.Carbs + carbs.BlockStore idx *insertionIndex } diff --git a/ipld/car/v2/carbon/carbon_test.go b/ipld/car/v2/carbon/carbon_test.go index 1d90eae244..a3c7d6d98e 100644 --- a/ipld/car/v2/carbon/carbon_test.go +++ b/ipld/car/v2/carbon/carbon_test.go @@ -1,6 +1,8 @@ package carbon_test import ( + "github.com/ipld/go-car/v2/carbon" + "github.com/ipld/go-car/v2/carbs" "io" "math/rand" "os" @@ -8,12 +10,10 @@ import ( "github.com/ipfs/go-cid" "github.com/ipld/go-car" - "github.com/willscott/carbon" - "github.com/willscott/carbs" ) func TestCarbon(t *testing.T) { - f, err := os.Open("test.car") + f, err := os.Open("../carbs/testdata/test.car") if err != nil { t.Skipf("fixture not found: %q", err) return @@ -33,7 +33,7 @@ func TestCarbon(t *testing.T) { t.Fatal(err) } cids := make([]cid.Cid, 0) - for true { + for { b, err := r.Next() if err == io.EOF { break diff --git a/ipld/car/v2/carbon/doc.go b/ipld/car/v2/carbon/doc.go new file mode 100644 index 0000000000..a1ce3cc6ac --- /dev/null +++ b/ipld/car/v2/carbon/doc.go @@ -0,0 +1,5 @@ +// Package carbon provides a blockstore interface with saved blocks stored to a car-compatible log. +// A carbs index for the resulting file is tracked and can be saved as needed. +// Note, carbon does not support deletion. +// See: https://github.com/ipfs/go-ipfs-blockstore +package carbon diff --git a/ipld/car/v2/carbon/insertionindex.go b/ipld/car/v2/carbon/insertionindex.go index c748576781..2e5d943a32 100644 --- a/ipld/car/v2/carbon/insertionindex.go +++ b/ipld/car/v2/carbon/insertionindex.go @@ -4,13 +4,13 @@ import ( "bytes" "encoding/binary" "fmt" + "github.com/ipld/go-car/v2/carbs" "io" "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" "github.com/petar/GoLLRB/llrb" cbor "github.com/whyrusleeping/cbor/go" - carbs "github.com/willscott/carbs" ) // IndexInsertion carbs IndexCodec identifier diff --git a/ipld/car/v2/carbon/reader.go b/ipld/car/v2/carbon/reader.go index bc346557a5..4e37554917 100644 --- a/ipld/car/v2/carbon/reader.go +++ b/ipld/car/v2/carbon/reader.go @@ -2,17 +2,20 @@ package carbon import "io" +//lint:ignore U1000 The entire carbon package will be reviewed; this is temporary type unatreader struct { io.ReaderAt at int64 } +//lint:ignore U1000 The entire carbon package will be reviewed; this is temporary func (u *unatreader) Read(p []byte) (n int, err error) { n, err = u.ReadAt(p, u.at) u.at = u.at + int64(n) return } +//lint:ignore U1000 The entire carbon package will be reviewed; this is temporary func (u *unatreader) ReadByte() (byte, error) { b := []byte{0} _, err := u.Read(b) diff --git a/ipld/car/v2/carbon/test.car b/ipld/car/v2/carbon/test.car deleted file mode 100644 index 47a61c8c2a7def9bafcc252d3a1a4d12529615f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 479907 zcmdSBRZtz;(yooW>%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� 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 Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ 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

hRd> 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

vHSrHL`4Y!))mrU|+My=J*?NCLvdFH?W^2AuF;5 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

ecf&5+ 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 Date: Thu, 17 Jun 2021 12:16:12 +0100 Subject: [PATCH 058/291] Remove the copied `carbs` without history Remove the carbs copied without history This commit was moved from ipld/go-car@93bd6235b295cebabf9757c1f2d66b47eb38da2c --- ipld/car/v2/carbs/carbs.go | 301 ---------------------------- ipld/car/v2/carbs/carbs_test.go | 81 -------- ipld/car/v2/carbs/index.go | 89 -------- ipld/car/v2/carbs/indexgobhash.go | 44 ---- ipld/car/v2/carbs/indexhashed.go | 43 ---- ipld/car/v2/carbs/indexsorted.go | 197 ------------------ ipld/car/v2/carbs/reader.go | 20 -- ipld/car/v2/carbs/testdata/test.car | Bin 479907 -> 0 bytes ipld/car/v2/carbs/util/hydrate.go | 49 ----- 9 files changed, 824 deletions(-) delete mode 100644 ipld/car/v2/carbs/carbs.go delete mode 100644 ipld/car/v2/carbs/carbs_test.go delete mode 100644 ipld/car/v2/carbs/index.go delete mode 100644 ipld/car/v2/carbs/indexgobhash.go delete mode 100644 ipld/car/v2/carbs/indexhashed.go delete mode 100644 ipld/car/v2/carbs/indexsorted.go delete mode 100644 ipld/car/v2/carbs/reader.go delete mode 100644 ipld/car/v2/carbs/testdata/test.car delete mode 100644 ipld/car/v2/carbs/util/hydrate.go diff --git a/ipld/car/v2/carbs/carbs.go b/ipld/car/v2/carbs/carbs.go deleted file mode 100644 index 6891d925d4..0000000000 --- a/ipld/car/v2/carbs/carbs.go +++ /dev/null @@ -1,301 +0,0 @@ -package carbs - -import ( - "bufio" - "bytes" - "context" - "encoding/binary" - "fmt" - "io" - - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" - "github.com/multiformats/go-multihash" - - pb "github.com/cheggaaa/pb/v3" - carv1 "github.com/ipld/go-car" - "github.com/ipld/go-car/util" - "golang.org/x/exp/mmap" -) - -var errNotFound = blockstore.ErrNotFound - -// BlockStore provides a read-only Car Block Store. -type BlockStore struct { - backing io.ReaderAt - idx Index -} - -var _ blockstore.Blockstore = (*BlockStore)(nil) - -func (b *BlockStore) Read(idx int64) (cid.Cid, []byte, error) { - bcid, data, err := util.ReadNode(bufio.NewReader(&unatreader{b.backing, idx})) - return bcid, data, err -} - -// DeleteBlock doesn't delete a block on RO blockstore -func (b *BlockStore) DeleteBlock(_ cid.Cid) error { - return fmt.Errorf("read only") -} - -// Has indicates if the store has a cid -func (b *BlockStore) Has(key cid.Cid) (bool, error) { - offset, err := b.idx.Get(key) - if err != nil { - return false, err - } - uar := unatreader{b.backing, int64(offset)} - _, err = binary.ReadUvarint(&uar) - if err != nil { - return false, err - } - cid, _, err := readCid(b.backing, uar.at) - if err != nil { - return false, err - } - return cid.Equals(key), nil -} - -var cidv0Pref = []byte{0x12, 0x20} - -func readCid(store io.ReaderAt, at int64) (cid.Cid, int, error) { - var tag [2]byte - if _, err := store.ReadAt(tag[:], at); err != nil { - return cid.Undef, 0, err - } - if bytes.Equal(tag[:], cidv0Pref) { - cid0 := make([]byte, 34) - if _, err := store.ReadAt(cid0, at); err != nil { - return cid.Undef, 0, err - } - c, err := cid.Cast(cid0) - return c, 34, err - } - - // assume cidv1 - br := &unatreader{store, at} - vers, err := binary.ReadUvarint(br) - if err != nil { - return cid.Cid{}, 0, err - } - - // TODO: the go-cid package allows version 0 here as well - if vers != 1 { - return cid.Cid{}, 0, fmt.Errorf("invalid cid version number: %d", vers) - } - - codec, err := binary.ReadUvarint(br) - if err != nil { - return cid.Cid{}, 0, err - } - - mhr := multihash.NewReader(br) - h, err := mhr.ReadMultihash() - if err != nil { - return cid.Cid{}, 0, err - } - - return cid.NewCidV1(codec, h), int(br.at - at), nil -} - -// Get gets a block from the store -func (b *BlockStore) Get(key cid.Cid) (blocks.Block, error) { - offset, err := b.idx.Get(key) - if err != nil { - return nil, err - } - entry, bytes, err := b.Read(int64(offset)) - if err != nil { - // TODO replace with logging - fmt.Printf("failed get %d:%v\n", offset, err) - return nil, blockstore.ErrNotFound - } - if !entry.Equals(key) { - return nil, blockstore.ErrNotFound - } - return blocks.NewBlockWithCid(bytes, key) -} - -// GetSize gets how big a item is -func (b *BlockStore) GetSize(key cid.Cid) (int, error) { - idx, err := b.idx.Get(key) - if err != nil { - return -1, err - } - len, err := binary.ReadUvarint(&unatreader{b.backing, int64(idx)}) - if err != nil { - return -1, blockstore.ErrNotFound - } - cid, _, err := readCid(b.backing, int64(idx+len)) - if err != nil { - return 0, err - } - if !cid.Equals(key) { - return -1, blockstore.ErrNotFound - } - // get cid. validate. - return int(len), err -} - -// Put does nothing on a ro store -func (b *BlockStore) Put(blocks.Block) error { - return fmt.Errorf("read only") -} - -// PutMany does nothing on a ro store -func (b *BlockStore) PutMany([]blocks.Block) error { - return fmt.Errorf("read only") -} - -// AllKeysChan returns the list of keys in the store -func (b *BlockStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{b.backing, 0})) - if err != nil { - return nil, fmt.Errorf("error reading car header: %w", err) - } - offset, err := carv1.HeaderSize(header) - if err != nil { - return nil, err - } - - ch := make(chan cid.Cid, 5) - go func() { - done := ctx.Done() - - rdr := unatreader{b.backing, int64(offset)} - for { - l, err := binary.ReadUvarint(&rdr) - thisItemForNxt := rdr.at - if err != nil { - return - } - c, _, err := readCid(b.backing, thisItemForNxt) - if err != nil { - return - } - rdr.at = thisItemForNxt + int64(l) - - select { - case ch <- c: - continue - case <-done: - return - } - } - }() - return ch, nil -} - -// HashOnRead does nothing -func (b *BlockStore) HashOnRead(bool) { -} - -// Roots returns the root CIDs of the backing car -func (b *BlockStore) Roots() ([]cid.Cid, error) { - header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{b.backing, 0})) - if err != nil { - return nil, fmt.Errorf("error reading car header: %w", err) - } - return header.Roots, nil -} - -// Load opens a carbs data store, generating an index if it does not exist -func Load(path string, noPersist bool) (*BlockStore, error) { - reader, err := mmap.Open(path) - if err != nil { - return nil, err - } - idx, err := Restore(path) - if err != nil { - idx, err = GenerateIndex(reader, 0, IndexSorted, false) - if err != nil { - return nil, err - } - if !noPersist { - if err = Save(idx, path); err != nil { - return nil, err - } - } - } - obj := BlockStore{ - backing: reader, - idx: idx, - } - return &obj, nil -} - -// Of opens a carbs data store from an existing reader of the base data and index -func Of(backing io.ReaderAt, index Index) *BlockStore { - return &BlockStore{backing, index} -} - -// GenerateIndex provides a low-level interface to create an index over a -// reader to a car stream. -func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool) (Index, error) { - indexcls, ok := IndexAtlas[codec] - if !ok { - return nil, fmt.Errorf("unknown codec: %#v", codec) - } - - bar := pb.New64(size) - bar.Set(pb.Bytes, true) - bar.Set(pb.Terminal, true) - - bar.Start() - - header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{store, 0})) - if err != nil { - return nil, fmt.Errorf("error reading car header: %w", err) - } - offset, err := carv1.HeaderSize(header) - if err != nil { - return nil, err - } - bar.Add64(int64(offset)) - - index := indexcls() - - records := make([]Record, 0) - rdr := unatreader{store, int64(offset)} - for { - thisItemIdx := rdr.at - l, err := binary.ReadUvarint(&rdr) - bar.Add64(int64(l)) - thisItemForNxt := rdr.at - if err != nil { - if err == io.EOF { - break - } - return nil, err - } - c, _, err := readCid(store, thisItemForNxt) - if err != nil { - return nil, err - } - records = append(records, Record{c, uint64(thisItemIdx)}) - rdr.at = thisItemForNxt + int64(l) - } - - if err := index.Load(records); err != nil { - return nil, err - } - - bar.Finish() - - return index, nil -} - -// Generate walks a car file and generates an index of cid->byte offset in it. -func Generate(path string, codec IndexCodec) error { - store, err := mmap.Open(path) - if err != nil { - return err - } - idx, err := GenerateIndex(store, 0, codec, false) - if err != nil { - return err - } - - return Save(idx, path) -} diff --git a/ipld/car/v2/carbs/carbs_test.go b/ipld/car/v2/carbs/carbs_test.go deleted file mode 100644 index 2aa48fe708..0000000000 --- a/ipld/car/v2/carbs/carbs_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package carbs - -import ( - "os" - "testing" -) - -/* -func mkCar() (string, error) { - f, err := ioutil.TempFile(os.TempDir(), "car") - if err != nil { - return "", err - } - defer f.Close() - - ds := mockNodeGetter{ - Nodes: make(map[cid.Cid]format.Node), - } - type linker struct { - Name string - Links []*format.Link - } - cbornode.RegisterCborType(linker{}) - - children := make([]format.Node, 0, 10) - childLinks := make([]*format.Link, 0, 10) - for i := 0; i < 10; i++ { - child, _ := cbornode.WrapObject([]byte{byte(i)}, multihash.SHA2_256, -1) - children = append(children, child) - childLinks = append(childLinks, &format.Link{Name: fmt.Sprintf("child%d", i), Cid: child.Cid()}) - } - b, err := cbornode.WrapObject(linker{Name: "root", Links: childLinks}, multihash.SHA2_256, -1) - if err != nil { - return "", fmt.Errorf("couldn't make cbor node: %v", err) - } - ds.Nodes[b.Cid()] = b - - if err := car.WriteCar(context.Background(), &ds, []cid.Cid{b.Cid()}, f); err != nil { - return "", err - } - - return f.Name(), nil -} -*/ - -func TestIndexRT(t *testing.T) { - /* - carFile, err := mkCar() - if err != nil { - t.Fatal(err) - } - defer os.Remove(carFile) - */ - // TODO use temporary directory to run tests taht work with OS file system to avoid accidental source code modification - carFile := "testdata/test.car" - - cf, err := Load(carFile, false) - if err != nil { - t.Fatal(err) - } - defer os.Remove(carFile + ".idx") - - r, err := cf.Roots() - if err != nil { - t.Fatal(err) - } - if len(r) != 1 { - t.Fatalf("unexpected number of roots: %d", len(r)) - } - if _, err := cf.Get(r[0]); err != nil { - t.Fatalf("failed get: %v", err) - } - - idx, err := Restore(carFile) - if err != nil { - t.Fatalf("failed restore: %v", err) - } - if idx, err := idx.Get(r[0]); idx == 0 || err != nil { - t.Fatalf("bad index: %d %v", idx, err) - } -} diff --git a/ipld/car/v2/carbs/index.go b/ipld/car/v2/carbs/index.go deleted file mode 100644 index 1f381308c8..0000000000 --- a/ipld/car/v2/carbs/index.go +++ /dev/null @@ -1,89 +0,0 @@ -package carbs - -import ( - "encoding/binary" - "fmt" - "io" - "os" - - "github.com/ipfs/go-cid" - "golang.org/x/exp/mmap" -) - -// IndexCodec is used as a multicodec identifier for carbs index files -type IndexCodec int - -// IndexCodec table is a first var-int in carbs indexes -const ( - IndexHashed IndexCodec = iota + 0x300000 - IndexSorted - IndexSingleSorted - IndexGobHashed -) - -// IndexCls is a constructor for an index type -type IndexCls func() Index - -// IndexAtlas holds known index formats -var IndexAtlas = map[IndexCodec]IndexCls{ - IndexHashed: mkHashed, - IndexSorted: mkSorted, - IndexSingleSorted: mkSingleSorted, - IndexGobHashed: mkGobHashed, -} - -// Record is a pre-processed record of a car item and location. -type Record struct { - cid.Cid - Idx uint64 -} - -// Index provides an interface for figuring out where in the car a given cid begins -type Index interface { - Codec() IndexCodec - Marshal(w io.Writer) error - Unmarshal(r io.Reader) error - Get(cid.Cid) (uint64, error) - Load([]Record) error -} - -// Save writes a generated index for a car at `path` -func Save(i Index, path string) error { - stream, err := os.OpenFile(path+".idx", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640) - if err != nil { - return err - } - defer stream.Close() - - buf := make([]byte, binary.MaxVarintLen64) - b := binary.PutUvarint(buf, uint64(i.Codec())) - if _, err := stream.Write(buf[:b]); err != nil { - return err - } - return i.Marshal(stream) -} - -// Restore loads an index from an on-disk representation. -func Restore(path string) (Index, error) { - reader, err := mmap.Open(path + ".idx") - if err != nil { - return nil, err - } - - defer reader.Close() - uar := unatreader{reader, 0} - codec, err := binary.ReadUvarint(&uar) - if err != nil { - return nil, err - } - idx, ok := IndexAtlas[IndexCodec(codec)] - if !ok { - return nil, fmt.Errorf("unknown codec: %d", codec) - } - idxInst := idx() - if err := idxInst.Unmarshal(&uar); err != nil { - return nil, err - } - - return idxInst, nil -} diff --git a/ipld/car/v2/carbs/indexgobhash.go b/ipld/car/v2/carbs/indexgobhash.go deleted file mode 100644 index b7dedba0a4..0000000000 --- a/ipld/car/v2/carbs/indexgobhash.go +++ /dev/null @@ -1,44 +0,0 @@ -package carbs - -import ( - "encoding/gob" - "io" - - "github.com/ipfs/go-cid" -) - -type mapGobIndex map[cid.Cid]uint64 - -func (m *mapGobIndex) Get(c cid.Cid) (uint64, error) { - el, ok := (*m)[c] - if !ok { - return 0, errNotFound - } - return el, nil -} - -func (m *mapGobIndex) Marshal(w io.Writer) error { - e := gob.NewEncoder(w) - return e.Encode(m) -} - -func (m *mapGobIndex) Unmarshal(r io.Reader) error { - d := gob.NewDecoder(r) - return d.Decode(m) -} - -func (m *mapGobIndex) Codec() IndexCodec { - return IndexHashed -} - -func (m *mapGobIndex) Load(rs []Record) error { - for _, r := range rs { - (*m)[r.Cid] = r.Idx - } - return nil -} - -func mkGobHashed() Index { - mi := make(mapGobIndex) - return &mi -} diff --git a/ipld/car/v2/carbs/indexhashed.go b/ipld/car/v2/carbs/indexhashed.go deleted file mode 100644 index f4c04aed09..0000000000 --- a/ipld/car/v2/carbs/indexhashed.go +++ /dev/null @@ -1,43 +0,0 @@ -package carbs - -import ( - "io" - - "github.com/ipfs/go-cid" - cbor "github.com/whyrusleeping/cbor/go" -) - -type mapIndex map[cid.Cid]uint64 - -func (m *mapIndex) Get(c cid.Cid) (uint64, error) { - el, ok := (*m)[c] - if !ok { - return 0, errNotFound - } - return el, nil -} - -func (m *mapIndex) Marshal(w io.Writer) error { - return cbor.Encode(w, m) -} - -func (m *mapIndex) Unmarshal(r io.Reader) error { - d := cbor.NewDecoder(r) - return d.Decode(m) -} - -func (m *mapIndex) Codec() IndexCodec { - return IndexHashed -} - -func (m *mapIndex) Load(rs []Record) error { - for _, r := range rs { - (*m)[r.Cid] = r.Idx - } - return nil -} - -func mkHashed() Index { - mi := make(mapIndex) - return &mi -} diff --git a/ipld/car/v2/carbs/indexsorted.go b/ipld/car/v2/carbs/indexsorted.go deleted file mode 100644 index eca593c9f6..0000000000 --- a/ipld/car/v2/carbs/indexsorted.go +++ /dev/null @@ -1,197 +0,0 @@ -package carbs - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "sort" - - "github.com/ipfs/go-cid" - "github.com/multiformats/go-multihash" -) - -type digestRecord struct { - digest []byte - index uint64 -} - -func (d digestRecord) write(buf []byte) { - n := copy(buf[:], d.digest) - binary.LittleEndian.PutUint64(buf[n:], d.index) -} - -type recordSet []digestRecord - -func (r recordSet) Len() int { - return len(r) -} - -func (r recordSet) Less(i, j int) bool { - return bytes.Compare(r[i].digest, r[j].digest) < 0 -} - -func (r recordSet) Swap(i, j int) { - r[i], r[j] = r[j], r[i] -} - -type singleWidthIndex struct { - width int32 - len int64 // in struct, len is #items. when marshaled, it's saved as #bytes. - index []byte -} - -func (s *singleWidthIndex) Codec() IndexCodec { - return IndexSingleSorted -} - -func (s *singleWidthIndex) Marshal(w io.Writer) error { - if err := binary.Write(w, binary.LittleEndian, s.width); err != nil { - return err - } - if err := binary.Write(w, binary.LittleEndian, int64(len(s.index))); err != nil { - return err - } - _, err := io.Copy(w, bytes.NewBuffer(s.index)) - return err -} - -func (s *singleWidthIndex) Unmarshal(r io.Reader) error { - if err := binary.Read(r, binary.LittleEndian, &s.width); err != nil { - return err - } - if err := binary.Read(r, binary.LittleEndian, &s.len); err != nil { - return err - } - s.index = make([]byte, s.len) - s.len /= int64(s.width) - _, err := io.ReadFull(r, s.index) - return err -} - -func (s *singleWidthIndex) Less(i int, digest []byte) bool { - return bytes.Compare(digest[:], s.index[i*int(s.width):((i+1)*int(s.width)-8)]) <= 0 -} - -func (s *singleWidthIndex) Get(c cid.Cid) (uint64, error) { - d, err := multihash.Decode(c.Hash()) - if err != nil { - return 0, err - } - return s.get(d.Digest), nil -} - -func (s *singleWidthIndex) get(d []byte) uint64 { - idx := sort.Search(int(s.len), func(i int) bool { - return s.Less(i, d) - }) - if int64(idx) == s.len { - return 0 - } - if !bytes.Equal(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) { - return 0 - } - return binary.LittleEndian.Uint64(s.index[(idx+1)*int(s.width)-8 : (idx+1)*int(s.width)]) -} - -func (s *singleWidthIndex) Load(items []Record) error { - m := make(multiWidthIndex) - if err := m.Load(items); err != nil { - return err - } - if len(m) != 1 { - return fmt.Errorf("unexpected number of cid widths: %d", len(m)) - } - for _, i := range m { - s.index = i.index - s.len = i.len - s.width = i.width - return nil - } - return nil -} - -type multiWidthIndex map[int32]singleWidthIndex - -func (m *multiWidthIndex) Get(c cid.Cid) (uint64, error) { - d, err := multihash.Decode(c.Hash()) - if err != nil { - return 0, err - } - if s, ok := (*m)[int32(len(d.Digest)+8)]; ok { - return s.get(d.Digest), nil - } - return 0, errNotFound -} - -func (m *multiWidthIndex) Codec() IndexCodec { - return IndexSorted -} - -func (m *multiWidthIndex) Marshal(w io.Writer) error { - binary.Write(w, binary.LittleEndian, int32(len(*m))) - for _, s := range *m { - if err := s.Marshal(w); err != nil { - return err - } - } - return nil -} - -func (m *multiWidthIndex) Unmarshal(r io.Reader) error { - var l int32 - binary.Read(r, binary.LittleEndian, &l) - for i := 0; i < int(l); i++ { - s := singleWidthIndex{} - if err := s.Unmarshal(r); err != nil { - return err - } - (*m)[s.width] = s - } - return nil -} - -func (m *multiWidthIndex) Load(items []Record) error { - // Split cids on their digest length - idxs := make(map[int][]digestRecord) - for _, item := range items { - decHash, err := multihash.Decode(item.Hash()) - if err != nil { - return err - } - digest := decHash.Digest - idx, ok := idxs[len(digest)] - if !ok { - idxs[len(digest)] = make([]digestRecord, 0) - idx = idxs[len(digest)] - } - idxs[len(digest)] = append(idx, digestRecord{digest, item.Idx}) - } - - // Sort each list. then write to compact form. - for width, lst := range idxs { - sort.Sort(recordSet(lst)) - rcrdWdth := width + 8 - compact := make([]byte, rcrdWdth*len(lst)) - for off, itm := range lst { - itm.write(compact[off*rcrdWdth : (off+1)*rcrdWdth]) - } - s := singleWidthIndex{ - width: int32(rcrdWdth), - len: int64(len(lst)), - index: compact, - } - (*m)[int32(width)+8] = s - } - return nil -} - -func mkSorted() Index { - m := make(multiWidthIndex) - return &m -} - -func mkSingleSorted() Index { - s := singleWidthIndex{} - return &s -} diff --git a/ipld/car/v2/carbs/reader.go b/ipld/car/v2/carbs/reader.go deleted file mode 100644 index 8accf0a842..0000000000 --- a/ipld/car/v2/carbs/reader.go +++ /dev/null @@ -1,20 +0,0 @@ -package carbs - -import "io" - -type unatreader struct { - io.ReaderAt - at int64 -} - -func (u *unatreader) Read(p []byte) (n int, err error) { - n, err = u.ReadAt(p, u.at) - u.at = u.at + int64(n) - return -} - -func (u *unatreader) ReadByte() (byte, error) { - b := []byte{0} - _, err := u.Read(b) - return b[0], err -} diff --git a/ipld/car/v2/carbs/testdata/test.car b/ipld/car/v2/carbs/testdata/test.car deleted file mode 100644 index 47a61c8c2a7def9bafcc252d3a1a4d12529615f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 479907 zcmdSBRZtz;(yooW>%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� 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 Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ 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

hRd> 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

vHSrHL`4Y!))mrU|+My=J*?NCLvdFH?W^2AuF;5 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

ecf&5+ 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 [codec]\n") - return - } - db := os.Args[1] - codec := carbs.IndexSorted - if len(os.Args) == 3 { - if os.Args[2] == "Hash" { - codec = carbs.IndexHashed - } else if os.Args[2] == "GobHash" { - codec = carbs.IndexGobHashed - } - } - - dbBacking, err := mmap.Open(db) - if err != nil { - fmt.Printf("Error Opening car for hydration: %v\n", err) - return - } - - dbstat, err := os.Stat(db) - if err != nil { - fmt.Printf("Error statting car for hydration: %v\n", err) - return - } - - idx, err := carbs.GenerateIndex(dbBacking, dbstat.Size(), codec, true) - if err != nil { - fmt.Printf("Error generating index: %v\n", err) - return - } - - fmt.Printf("Saving...\n") - - if err := carbs.Save(idx, db); err != nil { - fmt.Printf("Error saving : %v\n", err) - } -} From 22af2e3857cf44373cb8f834068665e402eca7cc Mon Sep 17 00:00:00 2001 From: Will Scott Date: Wed, 7 Oct 2020 20:09:38 -0700 Subject: [PATCH 059/291] original willscott/carbs history in squashed form partial finish interface license / readme add cli for hydration indirection extensible indexes fixed int sizes additional typed int expose car Roots fix issues with index restoration add gob-based hash index typo in util/hydrate Create go.yml Create codeql-analysis.yml Create dependabot.yml work on testing round trip test passes mmap idx fix issue in sorted index gets add progress bar This commit was moved from ipld/go-car@efbdc872e95deb81238b263f3689587d4a06a200 --- ipld/car/v2/carbs/LICENSE | 202 ++++++++++++++++++++ ipld/car/v2/carbs/LICENSE-MIT | 21 +++ ipld/car/v2/carbs/README.md | 13 ++ ipld/car/v2/carbs/carbs.go | 295 ++++++++++++++++++++++++++++++ ipld/car/v2/carbs/carbs_test.go | 108 +++++++++++ ipld/car/v2/carbs/index.go | 89 +++++++++ ipld/car/v2/carbs/indexgobhash.go | 44 +++++ ipld/car/v2/carbs/indexhashed.go | 43 +++++ ipld/car/v2/carbs/indexsorted.go | 197 ++++++++++++++++++++ ipld/car/v2/carbs/reader.go | 20 ++ ipld/car/v2/carbs/test.car | Bin 0 -> 479907 bytes ipld/car/v2/carbs/util/hydrate.go | 51 ++++++ 12 files changed, 1083 insertions(+) create mode 100644 ipld/car/v2/carbs/LICENSE create mode 100644 ipld/car/v2/carbs/LICENSE-MIT create mode 100644 ipld/car/v2/carbs/README.md create mode 100644 ipld/car/v2/carbs/carbs.go create mode 100644 ipld/car/v2/carbs/carbs_test.go create mode 100644 ipld/car/v2/carbs/index.go create mode 100644 ipld/car/v2/carbs/indexgobhash.go create mode 100644 ipld/car/v2/carbs/indexhashed.go create mode 100644 ipld/car/v2/carbs/indexsorted.go create mode 100644 ipld/car/v2/carbs/reader.go create mode 100644 ipld/car/v2/carbs/test.car create mode 100644 ipld/car/v2/carbs/util/hydrate.go diff --git a/ipld/car/v2/carbs/LICENSE b/ipld/car/v2/carbs/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/ipld/car/v2/carbs/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ipld/car/v2/carbs/LICENSE-MIT b/ipld/car/v2/carbs/LICENSE-MIT new file mode 100644 index 0000000000..8c6ca2a25b --- /dev/null +++ b/ipld/car/v2/carbs/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Will Scott + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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. diff --git a/ipld/car/v2/carbs/README.md b/ipld/car/v2/carbs/README.md new file mode 100644 index 0000000000..9aec21a2c5 --- /dev/null +++ b/ipld/car/v2/carbs/README.md @@ -0,0 +1,13 @@ +🍔 Carbs +=== + +Car Blockstore provides a read-only blockstore interface directly reading out of a car file. + + +License +--- + +Carbs is dual-licensed under Apache 2.0 and MIT terms: + + Apache License, Version 2.0, (LICENSE or http://www.apache.org/licenses/LICENSE-2.0) + MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) diff --git a/ipld/car/v2/carbs/carbs.go b/ipld/car/v2/carbs/carbs.go new file mode 100644 index 0000000000..c59e5d8922 --- /dev/null +++ b/ipld/car/v2/carbs/carbs.go @@ -0,0 +1,295 @@ +package carbs + +import ( + "bufio" + "bytes" + "context" + "encoding/binary" + "fmt" + "io" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + bs "github.com/ipfs/go-ipfs-blockstore" + "github.com/multiformats/go-multihash" + + pb "github.com/cheggaaa/pb/v3" + car "github.com/ipld/go-car" + "github.com/ipld/go-car/util" + "golang.org/x/exp/mmap" +) + +var errNotFound = bs.ErrNotFound + +// Carbs provides a read-only Car Block Store. +type Carbs struct { + backing io.ReaderAt + idx Index +} + +var _ bs.Blockstore = (*Carbs)(nil) + +func (c *Carbs) Read(idx int64) (cid.Cid, []byte, error) { + return util.ReadNode(bufio.NewReader(&unatreader{c.backing, idx})) +} + +// DeleteBlock doesn't delete a block on RO blockstore +func (c *Carbs) DeleteBlock(_ cid.Cid) error { + return fmt.Errorf("read only") +} + +// Has indicates if the store has a cid +func (c *Carbs) Has(key cid.Cid) (bool, error) { + offset, err := c.idx.Get(key) + if err != nil { + return false, err + } + uar := unatreader{c.backing, int64(offset)} + _, err = binary.ReadUvarint(&uar) + if err != nil { + return false, err + } + cid, _, err := readCid(c.backing, uar.at) + if err != nil { + return false, err + } + return cid.Equals(key), nil +} + +var cidv0Pref = []byte{0x12, 0x20} + +func readCid(store io.ReaderAt, at int64) (cid.Cid, int, error) { + var tag [2]byte + if _, err := store.ReadAt(tag[:], at); err != nil { + return cid.Undef, 0, err + } + if bytes.Equal(tag[:], cidv0Pref) { + cid0 := make([]byte, 34) + if _, err := store.ReadAt(cid0, at); err != nil { + return cid.Undef, 0, err + } + c, err := cid.Cast(cid0) + return c, 34, err + } + + // assume cidv1 + br := &unatreader{store, at} + vers, err := binary.ReadUvarint(br) + if err != nil { + return cid.Cid{}, 0, err + } + + // TODO: the go-cid package allows version 0 here as well + if vers != 1 { + return cid.Cid{}, 0, fmt.Errorf("invalid cid version number: %d", vers) + } + + codec, err := binary.ReadUvarint(br) + if err != nil { + return cid.Cid{}, 0, err + } + + mhr := multihash.NewReader(br) + h, err := mhr.ReadMultihash() + if err != nil { + return cid.Cid{}, 0, err + } + + return cid.NewCidV1(codec, h), int(br.at - at), nil +} + +// Get gets a block from the store +func (c *Carbs) Get(key cid.Cid) (blocks.Block, error) { + offset, err := c.idx.Get(key) + if err != nil { + return nil, err + } + entry, bytes, err := c.Read(int64(offset)) + if err != nil { + fmt.Printf("failed get %d:%v\n", offset, err) + return nil, bs.ErrNotFound + } + if !entry.Equals(key) { + return nil, bs.ErrNotFound + } + return blocks.NewBlockWithCid(bytes, key) +} + +// GetSize gets how big a item is +func (c *Carbs) GetSize(key cid.Cid) (int, error) { + idx, err := c.idx.Get(key) + if err != nil { + return -1, err + } + len, err := binary.ReadUvarint(&unatreader{c.backing, int64(idx)}) + if err != nil { + return -1, bs.ErrNotFound + } + cid, _, err := readCid(c.backing, int64(idx+len)) + if err != nil { + return 0, err + } + if !cid.Equals(key) { + return -1, bs.ErrNotFound + } + // get cid. validate. + return int(len), err +} + +// Put does nothing on a ro store +func (c *Carbs) Put(blocks.Block) error { + return fmt.Errorf("read only") +} + +// PutMany does nothing on a ro store +func (c *Carbs) PutMany([]blocks.Block) error { + return fmt.Errorf("read only") +} + +// AllKeysChan returns the list of keys in the store +func (c *Carbs) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) + if err != nil { + return nil, fmt.Errorf("Error reading car header: %w", err) + } + offset, err := car.HeaderSize(header) + if err != nil { + return nil, err + } + + ch := make(chan cid.Cid, 5) + go func() { + done := ctx.Done() + + rdr := unatreader{c.backing, int64(offset)} + for true { + l, err := binary.ReadUvarint(&rdr) + thisItemForNxt := rdr.at + if err != nil { + return + } + c, _, err := readCid(c.backing, thisItemForNxt) + if err != nil { + return + } + rdr.at = thisItemForNxt + int64(l) + + select { + case ch <- c: + continue + case <-done: + return + } + } + }() + return ch, nil +} + +// HashOnRead does nothing +func (c *Carbs) HashOnRead(enabled bool) { + return +} + +// Roots returns the root CIDs of the backing car +func (c *Carbs) Roots() ([]cid.Cid, error) { + header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) + if err != nil { + return nil, fmt.Errorf("Error reading car header: %w", err) + } + return header.Roots, nil +} + +// Load opens a carbs data store, generating an index if it does not exist +func Load(path string, noPersist bool) (*Carbs, error) { + reader, err := mmap.Open(path) + if err != nil { + return nil, err + } + idx, err := Restore(path) + if err != nil { + idx, err = GenerateIndex(reader, 0, IndexSorted, false) + if err != nil { + return nil, err + } + if !noPersist { + if err = Save(idx, path); err != nil { + return nil, err + } + } + } + obj := Carbs{ + backing: reader, + idx: idx, + } + return &obj, nil +} + +// GenerateIndex provides a low-level interface to create an index over a +// reader to a car stream. +func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool) (Index, error) { + indexcls, ok := IndexAtlas[codec] + if !ok { + return nil, fmt.Errorf("unknown codec: %#v", codec) + } + + bar := pb.New64(size) + bar.Set(pb.Bytes, true) + bar.Set(pb.Terminal, true) + + bar.Start() + + header, err := car.ReadHeader(bufio.NewReader(&unatreader{store, 0})) + if err != nil { + return nil, fmt.Errorf("Error reading car header: %w", err) + } + offset, err := car.HeaderSize(header) + if err != nil { + return nil, err + } + bar.Add64(int64(offset)) + + index := indexcls() + + records := make([]Record, 0) + rdr := unatreader{store, int64(offset)} + for true { + thisItemIdx := rdr.at + l, err := binary.ReadUvarint(&rdr) + bar.Add64(int64(l)) + thisItemForNxt := rdr.at + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + c, _, err := readCid(store, thisItemForNxt) + if err != nil { + return nil, err + } + records = append(records, Record{c, uint64(thisItemIdx)}) + rdr.at = thisItemForNxt + int64(l) + } + + if err := index.Load(records); err != nil { + return nil, err + } + + bar.Finish() + + return index, nil +} + +// Generate walks a car file and generates an index of cid->byte offset in it. +func Generate(path string, codec IndexCodec) error { + store, err := mmap.Open(path) + if err != nil { + return err + } + idx, err := GenerateIndex(store, 0, codec, false) + if err != nil { + return err + } + + return Save(idx, path) +} diff --git a/ipld/car/v2/carbs/carbs_test.go b/ipld/car/v2/carbs/carbs_test.go new file mode 100644 index 0000000000..5ec52e972f --- /dev/null +++ b/ipld/car/v2/carbs/carbs_test.go @@ -0,0 +1,108 @@ +package carbs + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/ipfs/go-cid" + format "github.com/ipfs/go-ipld-format" +) + +/* +func mkCar() (string, error) { + f, err := ioutil.TempFile(os.TempDir(), "car") + if err != nil { + return "", err + } + defer f.Close() + + ds := mockNodeGetter{ + Nodes: make(map[cid.Cid]format.Node), + } + type linker struct { + Name string + Links []*format.Link + } + cbornode.RegisterCborType(linker{}) + + children := make([]format.Node, 0, 10) + childLinks := make([]*format.Link, 0, 10) + for i := 0; i < 10; i++ { + child, _ := cbornode.WrapObject([]byte{byte(i)}, multihash.SHA2_256, -1) + children = append(children, child) + childLinks = append(childLinks, &format.Link{Name: fmt.Sprintf("child%d", i), Cid: child.Cid()}) + } + b, err := cbornode.WrapObject(linker{Name: "root", Links: childLinks}, multihash.SHA2_256, -1) + if err != nil { + return "", fmt.Errorf("couldn't make cbor node: %v", err) + } + ds.Nodes[b.Cid()] = b + + if err := car.WriteCar(context.Background(), &ds, []cid.Cid{b.Cid()}, f); err != nil { + return "", err + } + + return f.Name(), nil +} +*/ + +func TestIndexRT(t *testing.T) { + /* + carFile, err := mkCar() + if err != nil { + t.Fatal(err) + } + defer os.Remove(carFile) + */ + carFile := "test.car" + + cf, err := Load(carFile, false) + if err != nil { + t.Fatal(err) + } + defer os.Remove(carFile + ".idx") + + r, err := cf.Roots() + if err != nil { + t.Fatal(err) + } + if len(r) != 1 { + t.Fatalf("unexpected number of roots: %d", len(r)) + } + if _, err := cf.Get(r[0]); err != nil { + t.Fatalf("failed get: %v", err) + } + + idx, err := Restore(carFile) + if err != nil { + t.Fatalf("failed restore: %v", err) + } + if idx, err := idx.Get(r[0]); idx == 0 || err != nil { + t.Fatalf("bad index: %d %v", idx, err) + } +} + +type mockNodeGetter struct { + Nodes map[cid.Cid]format.Node +} + +func (m *mockNodeGetter) Get(_ context.Context, c cid.Cid) (format.Node, error) { + n, ok := m.Nodes[c] + if !ok { + return nil, fmt.Errorf("unknown node") + } + return n, nil +} + +func (m *mockNodeGetter) GetMany(_ context.Context, cs []cid.Cid) <-chan *format.NodeOption { + ch := make(chan *format.NodeOption, 5) + go func() { + for _, c := range cs { + n, e := m.Get(nil, c) + ch <- &format.NodeOption{Node: n, Err: e} + } + }() + return ch +} diff --git a/ipld/car/v2/carbs/index.go b/ipld/car/v2/carbs/index.go new file mode 100644 index 0000000000..b13baa9bb8 --- /dev/null +++ b/ipld/car/v2/carbs/index.go @@ -0,0 +1,89 @@ +package carbs + +import ( + "encoding/binary" + "fmt" + "io" + "os" + + "github.com/ipfs/go-cid" + "golang.org/x/exp/mmap" +) + +// IndexCodec is used as a multicodec identifier for carbs index files +type IndexCodec int + +// IndexCodec table is a first var-int in carbs indexes +const ( + IndexHashed IndexCodec = iota + 0x300000 + IndexSorted + IndexSingleSorted + IndexGobHashed +) + +// IndexCls is a constructor for an index type +type IndexCls func() Index + +// IndexAtlas holds known index formats +var IndexAtlas = map[IndexCodec]IndexCls{ + IndexHashed: mkHashed, + IndexSorted: mkSorted, + IndexSingleSorted: mkSingleSorted, + IndexGobHashed: mkGobHashed, +} + +// Record is a pre-processed record of a car item and location. +type Record struct { + cid.Cid + idx uint64 +} + +// Index provides an interface for figuring out where in the car a given cid begins +type Index interface { + Codec() IndexCodec + Marshal(w io.Writer) error + Unmarshal(r io.Reader) error + Get(cid.Cid) (uint64, error) + Load([]Record) error +} + +// Save writes a generated index for a car at `path` +func Save(i Index, path string) error { + stream, err := os.OpenFile(path+".idx", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640) + if err != nil { + return err + } + defer stream.Close() + + buf := make([]byte, binary.MaxVarintLen64) + b := binary.PutUvarint(buf, uint64(i.Codec())) + if _, err := stream.Write(buf[:b]); err != nil { + return err + } + return i.Marshal(stream) +} + +// Restore loads an index from an on-disk representation. +func Restore(path string) (Index, error) { + reader, err := mmap.Open(path + ".idx") + if err != nil { + return nil, err + } + + defer reader.Close() + uar := unatreader{reader, 0} + codec, err := binary.ReadUvarint(&uar) + if err != nil { + return nil, err + } + idx, ok := IndexAtlas[IndexCodec(codec)] + if !ok { + return nil, fmt.Errorf("Unknown codec: %d", codec) + } + idxInst := idx() + if err := idxInst.Unmarshal(&uar); err != nil { + return nil, err + } + + return idxInst, nil +} diff --git a/ipld/car/v2/carbs/indexgobhash.go b/ipld/car/v2/carbs/indexgobhash.go new file mode 100644 index 0000000000..51ba4c6997 --- /dev/null +++ b/ipld/car/v2/carbs/indexgobhash.go @@ -0,0 +1,44 @@ +package carbs + +import ( + "encoding/gob" + "io" + + "github.com/ipfs/go-cid" +) + +type mapGobIndex map[cid.Cid]uint64 + +func (m *mapGobIndex) Get(c cid.Cid) (uint64, error) { + el, ok := (*m)[c] + if !ok { + return 0, errNotFound + } + return el, nil +} + +func (m *mapGobIndex) Marshal(w io.Writer) error { + e := gob.NewEncoder(w) + return e.Encode(m) +} + +func (m *mapGobIndex) Unmarshal(r io.Reader) error { + d := gob.NewDecoder(r) + return d.Decode(m) +} + +func (m *mapGobIndex) Codec() IndexCodec { + return IndexHashed +} + +func (m *mapGobIndex) Load(rs []Record) error { + for _, r := range rs { + (*m)[r.Cid] = r.idx + } + return nil +} + +func mkGobHashed() Index { + mi := make(mapGobIndex) + return &mi +} diff --git a/ipld/car/v2/carbs/indexhashed.go b/ipld/car/v2/carbs/indexhashed.go new file mode 100644 index 0000000000..62bfce4004 --- /dev/null +++ b/ipld/car/v2/carbs/indexhashed.go @@ -0,0 +1,43 @@ +package carbs + +import ( + "io" + + "github.com/ipfs/go-cid" + cbor "github.com/whyrusleeping/cbor/go" +) + +type mapIndex map[cid.Cid]uint64 + +func (m *mapIndex) Get(c cid.Cid) (uint64, error) { + el, ok := (*m)[c] + if !ok { + return 0, errNotFound + } + return el, nil +} + +func (m *mapIndex) Marshal(w io.Writer) error { + return cbor.Encode(w, m) +} + +func (m *mapIndex) Unmarshal(r io.Reader) error { + d := cbor.NewDecoder(r) + return d.Decode(m) +} + +func (m *mapIndex) Codec() IndexCodec { + return IndexHashed +} + +func (m *mapIndex) Load(rs []Record) error { + for _, r := range rs { + (*m)[r.Cid] = r.idx + } + return nil +} + +func mkHashed() Index { + mi := make(mapIndex) + return &mi +} diff --git a/ipld/car/v2/carbs/indexsorted.go b/ipld/car/v2/carbs/indexsorted.go new file mode 100644 index 0000000000..6378df34c8 --- /dev/null +++ b/ipld/car/v2/carbs/indexsorted.go @@ -0,0 +1,197 @@ +package carbs + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "sort" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" +) + +type digestRecord struct { + digest []byte + index uint64 +} + +func (d digestRecord) write(buf []byte) { + n := copy(buf[:], d.digest) + binary.LittleEndian.PutUint64(buf[n:], d.index) +} + +type recordSet []digestRecord + +func (r recordSet) Len() int { + return len(r) +} + +func (r recordSet) Less(i, j int) bool { + return bytes.Compare(r[i].digest, r[j].digest) < 0 +} + +func (r recordSet) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +type singleWidthIndex struct { + width int32 + len int64 // in struct, len is #items. when marshaled, it's saved as #bytes. + index []byte +} + +func (s *singleWidthIndex) Codec() IndexCodec { + return IndexSingleSorted +} + +func (s *singleWidthIndex) Marshal(w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, s.width); err != nil { + return err + } + if err := binary.Write(w, binary.LittleEndian, int64(len(s.index))); err != nil { + return err + } + _, err := io.Copy(w, bytes.NewBuffer(s.index)) + return err +} + +func (s *singleWidthIndex) Unmarshal(r io.Reader) error { + if err := binary.Read(r, binary.LittleEndian, &s.width); err != nil { + return err + } + if err := binary.Read(r, binary.LittleEndian, &s.len); err != nil { + return err + } + s.index = make([]byte, s.len) + s.len /= int64(s.width) + _, err := io.ReadFull(r, s.index) + return err +} + +func (s *singleWidthIndex) Less(i int, digest []byte) bool { + return bytes.Compare(digest[:], s.index[i*int(s.width):((i+1)*int(s.width)-8)]) <= 0 +} + +func (s *singleWidthIndex) Get(c cid.Cid) (uint64, error) { + d, err := multihash.Decode(c.Hash()) + if err != nil { + return 0, err + } + return s.get(d.Digest), nil +} + +func (s *singleWidthIndex) get(d []byte) uint64 { + idx := sort.Search(int(s.len), func(i int) bool { + return s.Less(i, d) + }) + if int64(idx) == s.len { + return 0 + } + if bytes.Compare(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) != 0 { + return 0 + } + return binary.LittleEndian.Uint64(s.index[(idx+1)*int(s.width)-8 : (idx+1)*int(s.width)]) +} + +func (s *singleWidthIndex) Load(items []Record) error { + m := make(multiWidthIndex) + if err := m.Load(items); err != nil { + return err + } + if len(m) != 1 { + return fmt.Errorf("unexpected number of cid widths: %d", len(m)) + } + for _, i := range m { + s.index = i.index + s.len = i.len + s.width = i.width + return nil + } + return nil +} + +type multiWidthIndex map[int32]singleWidthIndex + +func (m *multiWidthIndex) Get(c cid.Cid) (uint64, error) { + d, err := multihash.Decode(c.Hash()) + if err != nil { + return 0, err + } + if s, ok := (*m)[int32(len(d.Digest)+8)]; ok { + return s.get(d.Digest), nil + } + return 0, errNotFound +} + +func (m *multiWidthIndex) Codec() IndexCodec { + return IndexSorted +} + +func (m *multiWidthIndex) Marshal(w io.Writer) error { + binary.Write(w, binary.LittleEndian, int32(len(*m))) + for _, s := range *m { + if err := s.Marshal(w); err != nil { + return err + } + } + return nil +} + +func (m *multiWidthIndex) Unmarshal(r io.Reader) error { + var l int32 + binary.Read(r, binary.LittleEndian, &l) + for i := 0; i < int(l); i++ { + s := singleWidthIndex{} + if err := s.Unmarshal(r); err != nil { + return err + } + (*m)[s.width] = s + } + return nil +} + +func (m *multiWidthIndex) Load(items []Record) error { + // Split cids on their digest length + idxs := make(map[int][]digestRecord) + for _, item := range items { + decHash, err := multihash.Decode(item.Hash()) + if err != nil { + return err + } + digest := decHash.Digest + idx, ok := idxs[len(digest)] + if !ok { + idxs[len(digest)] = make([]digestRecord, 0) + idx = idxs[len(digest)] + } + idxs[len(digest)] = append(idx, digestRecord{digest, item.idx}) + } + + // Sort each list. then write to compact form. + for width, lst := range idxs { + sort.Sort(recordSet(lst)) + rcrdWdth := width + 8 + compact := make([]byte, rcrdWdth*len(lst)) + for off, itm := range lst { + itm.write(compact[off*rcrdWdth : (off+1)*rcrdWdth]) + } + s := singleWidthIndex{ + width: int32(rcrdWdth), + len: int64(len(lst)), + index: compact, + } + (*m)[int32(width)+8] = s + } + return nil +} + +func mkSorted() Index { + m := make(multiWidthIndex) + return &m +} + +func mkSingleSorted() Index { + s := singleWidthIndex{} + return &s +} diff --git a/ipld/car/v2/carbs/reader.go b/ipld/car/v2/carbs/reader.go new file mode 100644 index 0000000000..8accf0a842 --- /dev/null +++ b/ipld/car/v2/carbs/reader.go @@ -0,0 +1,20 @@ +package carbs + +import "io" + +type unatreader struct { + io.ReaderAt + at int64 +} + +func (u *unatreader) Read(p []byte) (n int, err error) { + n, err = u.ReadAt(p, u.at) + u.at = u.at + int64(n) + return +} + +func (u *unatreader) ReadByte() (byte, error) { + b := []byte{0} + _, err := u.Read(b) + return b[0], err +} diff --git a/ipld/car/v2/carbs/test.car b/ipld/car/v2/carbs/test.car new file mode 100644 index 0000000000000000000000000000000000000000..47a61c8c2a7def9bafcc252d3a1a4d12529615f5 GIT binary patch literal 479907 zcmdSBRZtz;(yooW>%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� 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 Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ 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

hRd> zxIQPp`+1js7cr;A2G9Sz#Cb*_ZChlr4a28WU<}i_e zuBDVaJs))P(_~?NiyW>e0sKw|^8J9ysCp2xuN>~eXfYP}L34T0&Vl@^Y>%3Ak2Gc2 z=2WYe5D?fq2^Q{$PoR`I^N>b4&(CSKn(E<7H*fFteEu3mG(ELNVuKBMD)3?Hzi;RA z0KgT9_q}C4#xxE125~RlKd(fbZcWBo(;Ny0G{(BW#;AQLH}OsrS0;jtccn82Pi`8m zEVJKD9UY>B$9?dWF8`W1|0ti&FbF%@J+DSS*eufMZM#DeoKiQ_{s0Lx8W~@KF;K$S7sZKXveAYNqj>VEwyjh zygOO+_X9ij@pQ*bD0J)s$?U!zguw1HfBgabp*jpTv(UqwVa5Q9f*2)ETS{+U@ z(91|c9d%iy-5|-dTY~SupRGgCbbb;L)@nP3bQs(wvRJUogv;<93B(NK;WogNHfyx?;$PWbpeo}586beLoy^){uPPtQkWyUmd z$P0x9GS9s>RIS~?8t659XIsut5g!zI3esTO)QVSY0pzL?RS^tp7o2;#rMVYqy|@*e zfHvkiYLHiGQIL?JY%H@7m`u!Q--ErdL68Fk2>QifGy2$u5FWF68d%;$_o}+ro(L8;f zRQlq(ABEhnECtxP{tK?lY5d0~_5Gl(SFi}^g3m4pz$y^*@%E83Zv?g%A(46jxTayS zK7AnO*@ME}P-uVA)V0<3)Pf;5(p!REzqxxf=6nUn;5|-`IX@a^sSo17!$JpG^R2QK}`xx z^{s*JMhXeM>udPHaGON`n6^Yyph50n-(ocNRFGySSY%w`;&C;}3tWx2z4UA> zC(;!N_9`ZAJ!xG)-RtIwUsxGxlt~$sI}V{$Z)#vkHHs`M{0r0GG%N%OGIu`?lZj29 zf3`$-Ji{ss+T|~KnNQvVrueu&JuXcTi7nrF^<|ulh6{qI9=-z z=)o<%q>sL2=1SOT|H`s2yq;#oJF5EC=d>Q>Q=IhGv9{GttrrT`0mr7#fXOm136)>5IZ;*)F#M_@$iNDNk95?mDo+;AV^ku7jA=+UawDfSY|N3OeBf2{0HWD@7 zx3fO~OVTQ_JSBv7Y=$#McEod(Sq{+Fi!S84MOeTVJrKF0b4wp)B z{3ef03I_DC!c%0MoVLgdr%0HkhW~pQ97Kw^l7LqT-FCvR&uuOVBfzL zlh;5$s7o}Y={-9pt^R`XHlyxO?YkP- zjT(^9R%uOhZrSs2Y6nWx)5V{vKQ06{5`~= z>bfv67RUT}avLGuIriNh+!5YA@m_CKAL-fD+$SBP0(=YCO&Ato_1$P4JBRbg&$Wt%a9Uo};AOy= zkLW9zov-(9_7(RL5yCr@Y1^;RjYOw@$bupO8!DVS^ z3wjK42M;AL^jkYb9}ck;Yh*Oi{z_MDq;nqDnjZ2A)6ez$i5wlXs!uksGA*j5dvN$Z z%QuBH>z1KT%w#BTYMgZ;(YQnQ!^YmIp+{PQnBU@T(ZXB00sgGDt)l(UoMO?Okh;DE z(IK|4er36?Xv{E5oYjsDpHvxFF`bEkFwbs)n$ncKBi0=Cup0ljBJI z*_ckYK#r_*PAVxXgcu7wJ7?_BI1bZ|E?^Xl2XkH2Y4^E%f{eJp&K;@Jxq<{C3*yn+ zAcL$Y_0h-Gr#<=+yYsMKkIFDC$`#ut41*0mf|^sFi3G)HIhl-QgwV07U5RaS+jCVg zjT`dIvM`tI4>UUi|!zZX`8mL$1=8 z2uRgaaBac_7`k--bu!_0(IFe+vimP9f=QYZTpQbmQW@5VqYzT3>RW08A#2Q}K3sSd zpX}2bSrzfzHd;=1`pA(*O!yr|wFOPJ#J|6Wshfh){bv|ouzXYNxH4oaQkq~NU4heg zF54)FraK(TOA%@}3out?z@hb@itG<^zXRp_HU{Kc+6kGW-*=WAXW&P&@C z&LU_h0gv3VMDAtxVd=llHyF17-}pz_WkVc=;>EEvy%9F@cU>^if?}dV&D?(XI3MdBYt7nYA+ly-rrcsZoXiR zzUk9bD(*mPJ2$r~Q6#B-eIhlAd z&zG+{_)-(o(;5?ExiqpmDL_q};XxfytFoL2+!BU}bGH)g%LGGV_Fj<@stcD6DVCE|)zLgX zlIf~j0z^sQMndT!Vqz03irvU|_a6rruVO^&IuhM2dO72~RU06TNSfji#(294L4aX3 zh({ns1A^BV93lF59`!Qn1&v06ahLmmI&vh`Zt#FDGGbi0bQ@o7cEsrWJnLb!(+Cn4 zPLnluPN^I!h|o>fMer!+cWf;U5R)t)^TNKK6O$mC5rgpP%jWuh>y#F)O**V=js|-; zEluRNBBd#2K>Ae0U!gz=1ZW0Ww*ULWFkevc8MD3&xUhr-xTLjc(16VW@}^N5-Rss` zF)!=LGhk?)Nj!!QYH zhSf2gYdD`mH0rbfRj$ZPc{QZ}KbN?jVcT>nrlAoeregry6r&CD3fvsav2bUA?UbFu z8k+Hc+aWOwv@pcFXK2!)KgQB@A0fqS`W{|L- z#EjbD^c8EGE0@OTGTV87WUyAK)56#Mkn7VM_of#DN|7}nc}S5`F-x(yo_zD(4b(LG zguj9xkzQBoVq*xr*&2LmRgV%&Z3%y6GZtn1>z39*bhc!Li_LSUpr z4MmmU=D}U7G*0SAlv)}yF&C62@mS4{UlIRyurln@uWVWTB2w|N2y{2=vdA`dX{~e) zhPP8mnXf-4#hZH5Y$@=Rr(l|7#VJl3)@-AoRP6OA1y^P8n#BoqR4TJygu6GW$7_dM za0WC@34UP+0As1AA~d8I313LZP6DO_1iZ~I4jdFwoFDVUM~efowTJ`zXBkjz8XSn; zRda$rh4y=H{s_OtDB#gUb8d>{L4!y^zH{NSaTHX{4eq<3<|z_B(umKEsiu-C?KcW# ztp?$oCq81*ROKQ~-4j$jrZwvqzx;^FxV5(t7ez&P8 zcOcsisWJhBr6v1BaN8h8F>AJRXH@`&pMO~T?_(<+z}C+h2_JVl!?9=~;vf)^T9%sa z(EIQj3E6Ln#Y1t06Whk%U4;-)1R|rp$V7?ty33%P z2#;xYUJe$7^-HVA*S^-rPyi>-iX)2LJpeVQ$fxm}c>y>}1A z2&>OmCW&>9r1XZPb4jm}!Qxn6-#;b)B0e2W*y#km3JxQi(2XN^JK;jlI=Ee*IN1kn z=r3JcGxnN#*)2OYIfCk-ixQXZ1+`@C)R6+Pm7E|F?1YxtL*U4^ZlK$fX(noL5F$te zL}qvLQ)-2SXT!7GbLczyuSw0R$*PuWnqZz`sf$hEqOaF*kx%&cRJHikq`3CC-$aCv zW>GaLnI+X?aT!PV9C2_vaqHyrq);(R;42hx#vDUohsTh(d&8E@cA2t`FLr(}=Y-fB zSv+Yoi6YqOb`Y_UtsB}kOcfPm(x1I$WDpGtVgQE5R|xyg0da~~Uiv>GWIm+o9V8`5 zu+5J{3DhRE?o#i~<*DhF`yMIQ9JJX>UND+BfrZA8rfrwj9z0WqN2-aWj%zcyCbHsn zbm?%h)VRKMeNy9r)FZ%QA5Sa&DpPei+e0W{);d|5a3@tP%)CxxfvT4_R?r$TF3;E3 zBmq1BTJD?lhbE*p1QSWKdALjwjy+e(Ta7*1|`31W%-o{T?zKYtTs6(u_2?2o~sAGHpX6Q=EJyZ{{SG${N1M!s+ zJry{Pf0kmBL`t9a6eYm_SHCaBum-S|>bgVd**FmHL)M8dSQ-%5nXJj8r4TQn06!yt zzp=$v+1B6)y$G#y)33aUMItZ(YCI+<9hiW!%Dq;f{tJ9#!}fd#-!vwX_$-{@l!zCP z#m*ee`~^*A-~8(Rg>bg@Z?^s^^nWGW4G(+Ac3ekb%@|ehn-SSBhS-`J6g_Ufxp$H< zhreB}zebXrqizWajoOHS=i7vLKu;%e$d#E~n`u|~ny^jiohr%>F$^$EC(&6w7*|;Gv*sxa131!ZQkQk%_#oPde z3Cba563H-89FEX9uyq(P$%6k}3!AM01+n7IrdNUR6OU376sntGlM#!uGHP^`v|prl=vg>mE;;^U*AmUEud3oY}>kq6wT)#t2vs?BRyFeIsdSw zWo4jS)byoQGU9~wjXK@WDi~5IvoDAe=yx{w!3s0Q{|>KAfR%wVSLi!q(o4#UJlL^` z3uFxzHG^28Z*i0S^kn@8V5^l6{R3MpiPm3HJ9O7;ySXBUD3Ag{DHYt=;T=TgG%AZ) z>lL#R;DfEA_)tJn@b;E~c`J#C@KqM&eEf>AR^tuS0G8T7_BUPVHEesj{ zi^%;1sH!mNF0u+HoewK7b zvHk|cL=n(I^Pj}BieR(gBI3yTZSAO8)7%n)sZUXTc60WOIUY5JkJSnOfjdv8DIPNu zmPfHSl1L%6)VV*Rb#*zR^m#9y>BZsUu*_qsJQYKhU7I?avbEKo_wGgk={2Ko)bjxg z_5QvdB&&SzQ*Tq9S4p3>rJtlx1#<8KrP4tR9i4wyHUYb{5x;aCP_G+QB%Oi6X^OK? zVctD<+ybrsF8GCQ_bwwQnuXIH5I5bC*JZX$z7<1rQRN0+b&}#T%ddr4fA@01yb@s_ zu&)?gmp(T#%n+kN)MRKuT{NX8E7Q4JSBRJElB$IkcZ*sMqWt$X1WlRup<^2^4GZJG z{O5KurEncsW5+qbmoVg#6md6MU)hdjt%$H6E~%o1)n_jUh0hZ-pfV^cxB4ri8lM!u zE-y=uJukTWup{@;P59bT0h7i`>3_$?sT>CVOliCL#1c!uo;SoiQf%ny=zCS|duHK% zi@-U2D*_AUv?ztWE1cM-1%mOlF?FZW;J$AFZZK5_a8V+JD7YbNNk|ve*;-@vMTLwMDLX>Jz8>5dn93HN{xosZg~ge!1aXeZ#N#kAG~ z@qHL>w%vC89dgmr?hS;hW_K4N$d4%dlfC1g#4_`Xm-)|Jh5^x@fv`5~6CkM7mkWZx z*2t3k{=z1S z@AkbdtYSkXGxkix&Pe&-j?mY7;Fo@hk=9TIKb7A4`hbau^gtq%u=(@#!(8NoP=J@B z$Un9Wg@6D{R|TP=mE0pzn>1|P%h~+)opjxscs+3A`i`a7>cjv3_jw5o;H6Tit)r+4 zWt$Xr!-^J$J!kUBXJhQEsgSGw{K2)$^rmd|w~$X@Vp8GD=n4tLwyU0)*+KY;D~BNFuhvK-4y6kJEZ*P{T+CYPP&9(KVmz2(vA5y=C)DRiROS z5)FTE`wiih=J6d`%AH>$!$(FSAlhoP9KGB-iI|2rusI_nvMw1RN4+Izt5Ml)xh zh^mVu6$xin0&qFCUNVj!;1IPH)DL1nBU@Oa8ceroAt7vdV7V{cF4c3r6-GVsIUfnT zXt$O6u=HQ&5cGY3Ln0OEoAeE-2cX-Pl!i>HDq{8Q_ejIZ1_x@mTnW4|r?YuMq*1RF zgo`>R${wyXjZh%SV-g;ggr%o&s${R4uzF5JboXu0@VQ}g**#KxA1f6)!j1XKM?;Uf#`j)U#_h-lOb# zv9RUys2p;U=v}GI)U$1zxbNGY-uNT-aaky}h8~YX7=1UwUV$a^5bU<`U)1!13F_o& zA--C_HY=KP$5CAM?Vei21yh1w!YzVtx$xxb+Ns$2PQyD$*hA7Gr{Q_ROHzXg(FxF~ zznWpgnZF5_cPMd>Bv=L?Z{EBt#7(P=D5*>5ULy&=l~U)oNPYQF-VjU4mxO21&Iy&( zr_}2t26hHYtWWdFuu!utVCr2S&lJ!x^uKcQK|5ot>pSRR>em>AHlz5z0aci4^zf!)CgW%)|-r6RMrXI$wKMdJtO# zc5r<37sU-FS^ajh>PHm9_2m@=%4Xp4ZZ@wW``pt{4+l+%K$%+lB;S0Fv%#qTX6v6q z{})?{8!&nE!pc|ejFfp8hJ~uIiig)W;lzz%O_vMf-+MD(J+(erAe+^vRXF`U@NTg zNj;EP;ieu<@>>?vC@DPrBXLmWICkil>>{&*Jt#Tn^vn#T4X)Hi08kO3n8x$(g7Uev$@WpaWF1C@+2r6cIe&Ze1p>9{F& z@0^+_iyH$ND}1mrpgFP$a$Yjj+jG2+s|n?4>h}4R>syuP!}sAwIf*-1+ZoRTZ9fJ` zk^No3*bOl5-D;3Og9PgRP`gJw-(X|?bt>jPdmaZ&AJxHiE$JBGnto(rB z;vt8<6iRa+UeR2%)#EL}PKL99Y^v6gs>3AGTk z3M_(g*(qX=%}-;X2R#*61d#5i6s=!2H3w!Q{^aCy^CXGNEA+_{`e~4bpr4&_3$jFR z`86@(pi(=yeHsb=v;_QEOJ04J);|@z4e-2y^iP!g9FSTl(;%Y90mK?csz3EwD=Gv8 z@>Yuc0hoBD-{m%$ck3a#Z=NB~ zt4u1VAyrC$o%p6;rkIqdzaEO(?#PVCLIX>1^KEPvOgIHD#`7%w&DKAK{x7z2T#Hg` z0=&T5@Dr!PjfMt(N#q%&I^4WVarTSOuGGS}4@p(-?;FjPJxLKkmp?H8OB}0Xac71l zgo8_GNUVEp5N|YtwL&d`lalr_BsGAv-M#maR!{Mg_cb;Y4N{2%*z#3W2(coWz=T;O zZRKQKk7(BKp_)Zv{rw-df-^#?X8RVpsBBewm!ZN#VTGwS|z{R-lHxv6IE3x z^STA5-e5<)HK-UrcfV{=Mxo_YqG8dcrLZY1jYU^Gm4c7|)~&^{VtnAuLO(^R2Al11z%ox)ZKi$Wue*C}sxr&HrJIy7-*QwVCznW;Bsf;2%m*Kk;>8ymYtnXyYA>Al zWt8u$2nQI3V&uLqaNPIZN-A!41no2_!00f&#O*sMV=*yK-hjmRN(9y~(z3D^d!d9& zcsz_{QC6`qS)z4j?7^ewDi1r28)OCgV5I&fc-dGPeTMG%@BO+(w(qlM4Ce4RH98Ts z1yHkv!)n%Fn1A)a`{!sC)wlTL{b7re8BB0&m=%F7;UoyPDY44GA<`0gFwHO|yfBj3 zft2Nc-vaxksxW7!mH3cE>aG3Qtfp1)V{@6NRg4>Fc_M2rS=a-xH5KzQXSE&@*%-}n zpq&FSwvtg5#qs4Z=+z*0U z@*7>WOGPk1p9htdH8~1QTn;kii}05_2*-&%+Z}=4*ujqvvcLWk-}KV#%jYGub+dOs zfkI|Btv1bME?_Brb66%ZJgN53iLox0#_;@`t$zyrUu=~>h6#u%AE*>7Gh-LV>ka;J zCvIP4)Tb4*rplWba|_x>C7a19@5o`;wExnv>K=*QLh@tXJ|#@&<{-6zUJZBbQR?i6D!V-Kx{kr6N zaePdVDo*Bf*$%fB>9`fpB2%LV0_J|$M@(6*jk_vZt@vKA9~E&tWvkAysgXTi6WGg5 z82D2;ND@Vb!0iz^DX8hiIDo&~&e=B`jY%u}YN)r7(LoNgT{Od_>F04t0zFUz7Qt0k0g+C-+xQmW3Rc4S3 z=U0jZJ8o;`ju5X4@jfVUvA@~+r_le!Rw4|XMA(HuQF~sBz6elg6VG;aD>&?YqO(_w zus9goOsi9_qe1KiH&4a>FK+EQ9N=vCm|s?SkD~O15@4pJpfsmS+>Yy@;H~`p6m`BM58krS5?ic=c!`l;McZc=_i$f06dO@uCd$IDd zf)F3Rz?7%41F<0!iKqCi`Jj)Sm^B*}SN(Gm20BiYbYMJ@sCe7W@p^>|wm*8Xm_wkCNrcrp2{n zp#F0l{9aE{i4N(xR-WX{c@iz0`gcbfT`gYH7N0Nl;QJlpr&b$mj^0B+p^zd+GCF@> zk;WSUI=sehCKu_@&z}v`Oe!y3mxcI zx}dk3B=d1J9vI64QTsQ*n{p6m9fS|SP`lp{<~h`sgCm1=U^*m6O#DnqVRh&UrpN1K zBB)77{;>4l#})*Dtwj3qPF}WJgfpjDs9{*zpUX$Q_7~2!E^LT4xL+H{oENxgYCmvG8A8j1IXpB9OaINAL+Hta3)3`WF}j(yVLmhbFc+6YCD_^3%k(>vGVlNZggr zD?y-HJXC#y~amX z)89=mNmy$TLr-X^bR@!`vNAAD(uz&(u->EN>XUto!+m!7_#Z--BnS!FDY>>LvX0$> zposj$;eDj79=>PZsb_4~pj$HP=q+F^@YW;DVdI(NJB{NZ`djj#=^aF^NY<|*x7o-h zM9M|K+>DzqI>l%ot7S?ie&^G~ZNbNg%F~D0vMQK(N97d+6^>`_PcD_K22syPU!2s>ftWeNLrt zFcLO1WHz+%A~Rk9TN1(_FS`NV8KBJ8NyH}yNz)z_nTINx1mmTox}pCze(3KUw0NJ+~HaM^fRU7B08jJK(u36tmdL^Q!3BZqees4u($89G3n z|NpGvpF;l^TS<;3?JZ9`vg(e8y`~o529dt0q{M$&hRLo`2@9T$JPowFht?6<@!MdI zs-kMgnFj7k{CU&ft3y&{uTP{rWLziA1f<5efvF>l*X5-hRZl%lc6h%6CNDYU6lf8~ zNQ{h0sKn-$HGlK*t%$i!OggQZQ6(n+fHXaN#u(%H>4@Q29vQqHDIq;r`%rjPui*q%OF^p zeTq#pQ|M7XZw(ZA{i6ED5aXkFrZEJ+Ap)2;&hm9K8@szz3C~AQ3mvWr^}tYh|6T%Buz{yQ#gHb*Jm`=_itcF z0Cg_yW-^!khWp#7&>wZcEb&FYhNVa!ml?3}is+)m%3cd*xdBoxuYDMVemj-j(pR)#NTERo;8z7WkrsNEts(e=Qy5(K(ynxm-N#buM zHIt10zsJ8DH&7@{dZZ=PPoti4$TptTzMy%!o;+>8kBz6&{_? zObwvMfSAUZsRg2BCurA7?qSq^mUIi>;`aE#d6rmjpeJK3(+8PhY}q8kS<6FUSF-qc z&_Yo|D6ny~@V`$h|0(o;86@FM0wkc1aKjw~>NQfcCrfY=c5q47pM|JCV&+&yi7bBb z!X_)s`0|t*o9C7Z!w4wE>=S}&XTtk8HS5Cm+7ivMx2dvYU8kzR42x2JJhI{|GTB^7 z9hf`a&-z7LM=^g4l7eddYGl46uWCD2iz|=N6kZ?#5~vyqr$jM{;H-Dj{L&iu%Ux{S z*7i}NsASNSJ1`IP2-QGx>0KI;T}tDs0VKBPCqzxSU6wdZ-EW(9HVpY;48n&1VGGOS zurn3mb2L(oG|nit?lf&|7EMQkVOw-05_?7G%w`&VXhJUf-WnyNT@HGI z<5i5i_>YWlY5|BM(JjBpW`M)+LDT4tY}4yCR>(2ISyx3Xw|94gw%*#d+t>*iDntAp z*S@AGNdWhzv3k<)h!>R6zuM84=8%~H$#>~ENq!nycIpeYB-$Eo+)sF(Ff7+0`1Bxt z{?rqKNecu{~%#PLh3cNZOZm-vHb^PX%P*mvfq(sQ((ZZCkms?NH%yoxfWdvB{uxzm+UC814`DC-qo$Gp zhPY(q%ei6!QzFgW@;5W^M(0cJ28e4al?wmr_df=~LKNn>^Gu}g&muu)Sh}dNsn!u{ ze-^?vMMR*07V&Ye&w&^QiQyvSSKs2f7n&;QwuP zX^MoxEwwcxiOamOLt$O%v*h-Lo7|pgusI4LjnL(7O$Z410u}ay7>d-M1tLh_-K$Or z@gVlBTdSQw-+W#4v~aQoI1=z@Bt9(t*Zsmr^9DF1VjGryXS-x|c7k60ad84)-vKF8 zX>$+=v9VAdB5I@)2HoT*+6bS612k#M#I80FRwUb6vR}%I6#NSYQDje^f|Fwm&^peK z1xHt3aMhp6kym)vYO;1uGkY1>!#LjRXT@~XqLSQ|AavcokLk(kdU{n<=^ zml(Z-)A;3~rjhRU5by2CII^8`r$#H`8$GV5U=OmAfY4kQuJx*$P_ODn78{nwE& zVkzi&c~1G>r6RZPuOf6>U)6gxJ#x>B{c%YB!Ds3U2EK}Kf(d8mHKbHOU~n}ge?>|< z%f+@<;9z3t%J!$g0Kxl%EOSar;fzb}v0NZ&|W!Is)mj2QSKk zcO;J@Ec7sp=-Rg+F$D0SyDqAOL-8V$G*D7lXTb08fmR9=$Po@^F)-L}xLA(Qxv=1* z{6Fg6I;yH~{~A7YclV*C8v*GM>4t+K(%mW2-JpPgbax5TNJ@8yfPkcQNXK)ySA6;U z-Z6OZ^NeSV_jiVW0M0Y_+G~E+Tx)+md#_Cc!(E9C~bP15UHNkura# zYr8M)8}g?yRfSkvv@GR#OM}?zv7Eyjd|0sT?e(mLXnnOYZOkZw{>NO6IAd_d2dwY0 zp&3?drcGf#Z08LmSzD*#HmLwBAnb;X(yvl$;h(^>Olv@l&*Z(SzQrRN))#J}@>WDG zfHrL8LcrD2NOx!+*jRC)GF5YDUYS5i!`ln}?rS%W-uvWJl)=ES9W@&qMYf%B1LA~i z1aaR8w^Q!fQDW>9;Cc+!PyDX6xz%=%?x(yHKk*>D{&>-7^JU(6NaaLssqF*6{uD$< zpqQ1SyAM$VrfLk1K6f@tsU|P}7~ae;6$^n^vL$LHq2%9X>AT9PsZ3lkM$6Z~e)}rL zGJv*`_G2XFG%mJciFcwQkn_$%GUK@goZhk}qaSwIh`X-cgoWU=?KpN{XZ$j85cU0t zMnL?pM+(c)OP?={Q}&wE&@I>a67%h|Vm?QrKU*x+TlfhNvmt+XFV@2h+=<&^c5_6! zy#2R?+EF~}mBwU9NyBrMR%v*~8_%>Syg ztzI3I=b_KR?>?-tSa<;apXVXUU=Mi*ktoI`NG=*6CWI72?z{IvNl>a_7w2OPYh$aM z+ZDSq&miTC{QCC=ArcF*bc3Sd02zK3rNDEmet5Cn%KzH zJA2hSBO^26`8hF7G^jkwvmXZEsWW|j-~c{@>x$DMS(Vy6RBa-dFw2J^D-63Tk^ zNq<3Ph9GJ&;r@2Wb2KD_*TfZFihP{4QWye}GMP9`RC?4#7`XhRnFHvOVxOe=G4_TA ztkRfkRLl>hTzGitZL%O7faI5vBYAyPfVY;A0;!xxb~y*SMr~5wM&NhBMnt=L<4F^@ z4Ley^EML^WH#7HSLr%_>wc2t zRJwwGSh5m+HEL(e&Bm${g$DimU9er$K349Y%yb<^xEQ+fq0f~lrX71^bbl$K{;>E& zTt^T64+q&-wrZgSJW*Yi3z(%uw|XXvz#`ok+*(@aMaW7((Bvs?YCb`{@Tifd0>O5Nfc4_+^j)bBru!MKjmv@ae10 zI(DAmyze5z;1kltkPS(4rhwz#iL`#d*=6GRPRS^h0?H7gOgilwO3%eXQ$I*;x`CAj zu^_M7jk_$J0e!o9cpDAfj678Mt9kR$`kA1x+Mhh*V_kpmAl_PRiCFya0^vjkgqLIX z-nFMdzC?w)3@p&;Wjv?ifapKf(>>_J6#r77O%{ap?HO+PUVIA_h0VSxU|j1>Y<_;< z%U<|%#T!HkiS!J?$)R(;jSK{hNLJF}-0uyZ6ocSAW8U$e3jXZodljdb8^)yam7S?_ zO_T_x@~It0JUyuMK_R2$14xqH$6ChmRl?A3=8A*BZ-;XSQIbE+d~I5I;8t{#ndNzy z{OWn%>GP4%schA4@hrS|cwQ*w+R0zhUbbN$(EhS=M*%|nY}WDLoM`Ny%O8hssb;}C zy`xkt-|x`hNOCu`i8Y z3;~3qc#s0u^hlBh>?B=~v1Qr~|6st3e{=MuHXe?+ zCeV(1j#PI0n%!rXkfXf1c}Bk9#P94O^ncvg!a4(wtzfyG%vlYqB*bJ-iM0H+(1<)J zCgdiW&uLC6h|a^&e93g=09ZGRp(Ktee}6IE9z259-Kwmc4#}^Cx8dnXSOcxzVsnxR zk#;YXvDo{RmirFT%ef| z(NoS#bfR528P9V6l}&BO{Gblq*>zlRB2o~AMG^LrYWO0Y`gh`MnO|PS))c3JE)yb~ zJUEx`u3~*xxA_iU9me6_0FfKvnXZkT^nX2T5pk`}F`{?SSS|4};rKO@+CuKBoP#E6 z@&G4+5U)M5h65eC>W`hXeQ`ubXlc3`_&8-7Sy-`)oNTq%*Cp%7SWycs#}xT*k&d~7 zLyx0ES@k2T0T9Y|3B+aqlw_uC%VG`~p)v+~kD z3Is)Y8#!zU`b|t3V-~Bksa7&(Bdnh3%$VhaUr-K0q+cvB`lyOmU9g^2tMz?@JG_dwtJ8VP+QBKa=AR)rHQ2KOzhfpxXDRdzT=C<$Dc@4vj$O$6qj4@P= z@hLwpTo6v=)L=&Eax+7p3Co}Ns&~JPRWPe^P{E3O(n*s&wOUhy&diyN=W#bXzj#d) z=jFqnr8o7YSTFZRYzhviqd{7TF1G#0`|p?=kOcVu+RI+{GiArI@qUvtj2is`^vhu9 z2u*j=oCb=hD@2QixqLM6do%tqKZ~nobnp_h`q^SDO=rs|Opz0@Xvu5L^(JJQCrIgW zBY{2SSsB~|4@uBBGpd;`zmljGP$Cq>8|D)#*YyFVP`rsbV?Q6Q}PqgU>3h*FlFBtfzmufgf2Glyw)XKVMoPxcu&z5>O zM1qXgEeWL=eDY00%Cjn93an>xMnYgZGpTboawkNj>R8j%+iW}RZp{_4gj) z9qC1TEy#zAE80YrdRju<8??Y?ZI^)#KIJ=J{EKFF`{;_Hr;W z5#vt9ddNN*W86UzQJQ;A)UJOVHmCA4C?8`L7F{m*6R&KA(60mJfeS|acPo4!sCn3= zUK5x=>L<^a;S4@eC;~OotzR<6m(xyf!J{wr&Ia_oeXcvNYxW}ch5Res*pUOh;MR|6 zku;|6jZ z&V_tVMAr#gkbB$6lvegFFNO8>%f$wJ_FwWd(Pvq!IY{YR-0xNARpVW@w!}3*b&)4g ztlpmBXX@T2*U+#|D$TY^8&ML=g^nAHErkiV~dx(VA5jC_Uk|2inf+^f4d{W$I6ed&S+ieN+iTqNLXc zK7{xGRQK7h*JYbm&SoPC+J{u?!EExOTvD^8?wv_8Mkkz+XV@S`-^*iEv#1Fp2>P;O z_d(a_%df8zNcIkc(WUK8mM455-H3s)p~F|wIo;eC^Dp5XpxbzQlPb1-?eWM?Fg=!4 zw>0$L_?`B&zF6&tf0rIg0RoUqno3wS?piinRS_v%>9CU}MPj$XhngPgFO>v^=*GU^ zbkAqt#MO|)RWVZ^NVxM5ce7z-A?w{Q1FxXNWnD7QNBBH-pJ{NNpF%p}tLmM}{eCWD zT$j$sG_M^|?(<;r`KeIjQO$PVxcX$r!Y~TeaXbskKOH3cD|ZMUFWC=Cv*ltM#E)8J z)hpVc6q--dRJ!Orc8p}e4uTp7`@j~kVRqp@Vz(?)s;4gHoMsK;iO?R^Q_FkQUFmT3 zR+m297wvzZgQS8TBo8iAHy9i0#dJ4G8e|={>rzNKH(bp~RM#orO7FBPKpyMH>TC4N-X*Xq4}xxm_0=g>%ub5I`zPe$$9YIuJ3ULv?KnhQlkIad$2cuH^mi3I zaN%HqB)qP@h|Y5=3{QnW;P~!=B(CZxpxD6!w9f(0sdTu}<2OBK?-JQ3>khThWt>Pp z^-OMP79*p)tMR8UMLaL34gU@wN8!Kp{3ou}CLLSzsvqD`C)bsI>McXVa6_qOtp0WP zDkMT^;r@P1>uE-8QBVo^dGlq@>!&#QDLxd)zTP>iheR+cJuQA+w(d=g*{O~OgIAZ& z!w>-Ss&O*GlcGGoFllT&r8d%X(N?ucC-Bdj*@X_47=~Un{$xZPL4fz~tVS@1&&;uN zSk_YRiUwJz8!;%r>DVFHqAJ=PFf8E%%yT~XrKz1KMr!~H{KMe4NWC;a4tv-(4n`JG z_z6sX5_v)wDa#nv=Fy^iMpUQbw~#jGd)4iOmv9@b1g=#)Z9hu!r`;3u3FZxp!kGja z(Kz=F0Uh{C5mnz$lRvMltozxDDG+u34G@<@gZ_8UTz>){d0|Pl{38P#4uo(X}D8R*f zywv~2lSVJob*}Soi{(DFpKS6TKvgx>dlM>^Sbr5oDR?#JMz;<{=Qn2uLKxZLz7j`= z{QLkKVS|dWD8U_2>tp(1Ks|4lWl$GDvrAbl{_4#>u(ekhJg{2;|93xxIwV+j;Nyf5 z^-W+S397#aM)T9I_w41B<^E`@f~mAzPC`~Tt8y{kqI*rY=N1*+Ra^JozJd+)10k}L z9!BgW>R7&aFSSN*#+o8Zb1DW%Pyc4IZVIh*1NzH&4N2gZCxIUf-W9{{!T%MK? zB`52g61UTSF${|DP$JfT>eD4u<@muWMAK|KZO2cHyJoZ9a_xK>LJq-Ap`}V zR8|6_1lZ7egjuxUas$l_YcEEWTv_n|vPuam&0$^($^!dN4}lSrALlUKb$X05s&HT?otFC^SS!s`)_5&H~s2(FVNHlnq}nB zyxmwEki59?ULzv^y^WCli2Hm9+6$SYP>{3Xt3l>L3OfRVa9RJtCJQGZb&RkW;Ikq0 zgFzsyl4C@G@BiAs&3=EPQ0fQ;EBNA7tFZN_Sx;r0hMTt!p#R0({AL`)W8g{5N94a5 zE9P!nl&JA!|Ai<#?oBms%g0+XCUD4CJPZCDBuVkix>nI~=@j@3@+ua(F00WAp^SfU z0sE~X#I3NTR6?t~!XyOMUSMoD!VlW^ZZi^ezRz8CC7^?GHC|GdS4@jn$^-K zv=GdllysX^aFEBRRCot1?uM2)_&CLHBfd?OYLG}cD3_wbg1BE#VkntM4(vM9RCOOA zQC5~9YMk8ua_xqB(#df#%t?-f8lO491>91>_cq6x# zOGy`IU1l4uoEm#jh^etrG=x8V&~Ir`q>>#!srKkokEj7*Kd4}D=?^+g}nHg1*im7Z%Y`$&N6xs+{B(nZv%GqQC+4?}9U%OqpS zK&=FSV*dNjX??bhJkz?E-%cY~aO9(kqrJFR-*Yx^FuyVVbdw<$-*RYGlyKNDDUV8A z*IX>O#RM&641FTSST!$E>4WQ7cHtl~cj$g-H}DDXtNf}tF_D}P@2!Mm+K7MIxgO-O4eMajpHbgZf;l3+2JeF z9S)ztwpEF3B%izw6r$L_GKsi0t&_|(e0i{tg z4zcO|n$TaE<@070m@BwmT8$$!ao;@_28M;;;VoaCbiO;-|86t7p8O+{NQ1IW%#QhM zUX=mTK?`{I(SPnlmPH6$IR=hoyd)`&QaQOs`|#$R*H4q2&!dtHHJy;ILJSZj1NFcc z$`p`|CN%~)ZrjSB8DmhE%42mQZj=t@GOvq=ak6**XD{#Zz!s9J29f+Ki~WY3=xVg5 z=S$T^Cq-Q9rUT7+t4&N4Tt1hV{e~6$F(4y0S4{_|)=zT-u zBe^$G>)$V60&)-`}t`Xf2siug0rT7KH9xia4Sg8O9D&dCw}5_b7vtr_3c>G3FAh}X;-fAcw@>jNG zh&^MXSaLoYr|s02gDgspm{w7trHh#iq+OCiS@owi?&EBQhc+#|qVqiLVuz6vP5D3t z%k?rG%3^>my?YE?UEf3Z6* zArdTkBkH^xHkdcqzTW!~h^jgE!LP=CH;B^k07=QFOM;El-V-u`=aV%L#h1^<-&oNG z!xMYMRdhx8ZBAV~NM%5=MT^V_rKQ+?)XEnfVE?ungPe5x*0Brc=+$j;1hN zXe=Y%Gr8F6%oZ*3JaGJ%RV|u}>Pkg7Z4U4Bg@vI=fG`I0%hn+q$@IJ~RqwZ^$xCa` zxiQ3jt6qV0OV6)@qoC8h1O)47(699Nwxw186yoR;CBZ2t!u0KC zTd^%tVM~h>cR#ZO2pG%F&Q&mnZc`T39TS_L@oNO?-~}7b9YSveGFu&sd2e4Ee%y*^ zxU&$itD1I*pPP?l4c?xP-+aCYo^=G2hjh2EbuXZf-R)bX%^xeWZCT{uvRt| zaDb~u&ThF6e*%kv`HF8@BbWzISpMeIcT|(c!FFP9(II|p-?UqWIs&!NaTsr|EAaDN zElJsm(-Dt!-!B6y$C6ZPdr;f8X+CBgnucIeBT=#cv5?1l{@y~0bSuJUmARzW%~OCx zo7OfR3~u&n_{(fa;t-Kvx_(sG@@Hbt$*yjiSWaD@QSRyDG}@$cT#p1K3f(GHyQJLM z!Bcob{W#D~DATF#juy4EA}PZ%=|uC9l(oR}`e=rDXCb9V0ooS{v`G~wN)ods%1&b& zLA*J$s{k`WbUD5%qtw&x`9WWI%!;(oFXJD=?2y{ zxYg5gPhp+235eo}rMdRk*p(SLcD*$vE}G#fffWT^bg)D(2>Q_a4?-g8mOa|F*4{(g zuq`2X&sbbQL6~>6iZMI^o*fKRo;}wQyztn(x zk2_na%GFt{_87yHb53OM!fmmjniWKBTHfB6B?bkyIf z59C9zEngpJWC>*hpH6;UXO;R|yG$;J%zmTL(eDHK?5bOwueby?z~{<9jlGs`HK|c+ z1^SEgPrEduLX(gVZNk0jFz?0?;`G-B(qq8wos;yoJ-%v2w^+1ja$doP{(7U-@yauM zeNl1nJ9oodsfC1Z_&*=&7i2SEIfT`4SL6W%I8o6czRRn$KXa9}KpdTE?m6{KsFJ3b zSnGwA6nVXhkmbz;zvc$sE9%xMrf+#twKcOfoYYy$S#W#WG}pChMOE<&pOUr4md3%I zg;bO&C?>O}Z}dJFw_#D^_^AEVQ}?KxM&#qhyBd%)!z=Iuw09N~_U)wu*(83Z2NNEC z{VQM55>J^>(?X#E(2}Mup)WTlI2HMiItBB9Wt41!*C|eSFr7HCa602v65rg^#r_A-|KdVM zx_su%o+%$gT?*~U7oA-}q_u=Yi9@FIYGZjOu?JOO`$OAe;qlFxXcaly^t-UE}pczp08^f0O-+*uz4U&vs70UxJqMJAiM7&21{>ILmk_7L@( z#=LbTQ{ks(wd%!3M+zoIjJkLvG9l@uMg7*HYY{^P5JZgR5IL_$=obc|k&sK2&xNQY zcPLHf{LGG4Uw4gV%SzOU^t+*DBsqk$qs=w}3L*c!ebZw-A0Y|=_<(@U_QpV4dm9^L z)_;Hg{@qW}+Qt#62edcRv-~#@L~I5$aWZmn06LnQIRL@`EX=G8!3aHTLmMk^Z(+a# zk^b-K`Ph^o2kBsDVy)-sWN!ri+;#ms6-a$c8v_gA!~Xz;BmTRu9&4p9hzz|20CAs1 zg247h7ykDz(BMBpcmtrLGW6JdeDwj){?qgd00=bzqOqAJ3mc1py^ZyQ?vN-SY6A{# zYq=Bae;p?f;91$qjts`JPhNkWj99+X+5!;%7mopW7ich%;vEsvzlp5Otc~m+Fg5kN zE^WsVF7Y$jjuhdfnXeLLe?3&$z$%^6HB$TXwhKZA0HOOTbu*9_9RT>lgZMwEk^{gx z6CXa6(oWd+{@r~B;AaLOId43f386_y)00s?cXr=*>rx>vLAzK;7%8!9@M~3^5a<6% zX|qQ-pR0LSmy5kjx(%m9Wp*|W&6yI!wck0b+npwK*^4+m{7-#p^ncs||0#Oz;#}!2 zCXs)~xt_g+(Zl%B>f3~Kg*&QfcM{!;=TySY09!hgmr2{29>$e>43(qH|@E z?N7F5wkD;=knJmpuew`#Jlq3e2q-dYP@UBUO8=A2rhm2WZ%%Kn?Hh+A3^@I(+0QcJ zo+_*=!{1)vpL*ak`TLT5NWu7r&iMbdt^dcsX#ee!kC~S@;a_(3z`1H5{>|~2ZH1;k za3h3dCEvml=)5(36C7+2S|w8nq*B!UW%e(ZS%Zrx45iK- zmRJODH&Txdc$aSC2T0J0wut}$k^qQ*`TH>dQ2x8&kF~`Q8vlu(2%O)8Ho0i*{gU$s zM3VRf*yyV4rva?=)**2LJh3gB7$1CtiXT8}DPZ={Z2Jjek}oCtHep()JzIF5y-%%+ z!3IG)S{yyRd-A)DRk9w-M>M?aaCP{zBK1%qzPZw>y0bKyc5>Hu#6I3rzgzs+;r&F^ zz|jMu1^io?A#!x}Z-(`=>NXUwW(Q?D_A5g|qy?c~6XC68R5rUhJ%FNWuqJX8aBK6R znK`EUGS+R=|Ga7!-$kIU6l1&8kb1aH@NPX*&G(L3KQ(3~MilXo!0$iUzOAonu>UNz ztpAE@9`%5APjyZnvb>Tdy&}FlUJd)Ok?GZy6!_+g?FRqn2{!NM-|Mi)fPNyR-u}KIpn)>ooR-gk@DW>%Zd2F|r?x_D zE3fLy&blI$QzWHYHV{xQrXb($*Ds8VqVLNqhfWjae)vwcX3nS+QU}c5kd|m*5(l8d zH|ro3TK^yrc6>7KGVEtD+sTaRWWf+Cu6GerufREgxvw!kUQDcVqRiV?n{AdkjWHYS z=>gN0@wPL=b**Gz5^j2hUGf%uJiA{KbI7>?&0M{6!BJtyzE^Iu9PvTnPe8zCDKQsf z;;qYD_tk4h5^wk9^kU=YPSN^eQC}3j)r-f_{c_>3y$$GBJU8xrjOZ%w-1vyE%mkJeRzV@n!lg_hZ5Kmo8ef z&QKg#DZ6SV!B;_63Z+^ypotRF%4tngzN!t26R0ndL!SP1V7aW1Veb-t^1el^op3zm6?x-U=}1^%KYp zD2;5v4^WPKRaWxPb?(2|(L5EF0|8~B$67G=<3i9usqEqy>dA%KmG9c$|Db6xp)aif z?z4kQ{6cGp-y|CuqM`-}Y{U`F0Ri=mTlHvs_2U+wk#X%< zd(UB79;K(D$C2aHrB%sD?8!Wqxh=vt%s!xfUy zK|mVv8EuTj3wi6P5%)F)76aQxYN~p3$|IhE-ZFD`dc5CqKV}$z>Ea?@jf5g;wfu^A z)c(y%5DXo`86a@x{~8R>dL0QDl0?pTv8v}m5lOAJV zuvu)&MPCDPmo_6}ko^-~=s-mGIX8P_I5G}5ENXBkV=MN^9~Rngm?~O)7g#`}{^{u^ zVdE#9ztodf{@&mX*dlaPBB#(ShKm;33E4&taTY2&pPxLb`8lNc#lUlOyX9_R!igMt zmlmT%zAI!)_v1-*(-3y4gUmBx`ayA16J}jP3ckj_3g1T>iRv{6Scgi>m;Peh;k~ji zpQAY^EZ%{$mnH&}K=pTA0Fx`BHYv}G-gQrJu$6=udZO$$8^rDYd~50k2?EY**Wy)% zOvn-E`AcQWdYuLSve0tfqGSD5@sl%{c?{*Z+>e?5U%Jd0?PY1i7us#+Ew{PpX^g)N z!csL$ii~L|hLip#QlEA&_hY!fXlL^qZ&VXl)rHNk0o^ z?tVF*aU2?+_!R@o2Jfz~GPy1g6mh_vJZ}=~-Rf6LGF0w&m zD4Sq2+WoO0i1&Q}9bWY{A<*k;)+++o$qQt+p3`p89BR<8#VbeUTwAKxa+? zqUB@*lL)>@=++^#B^Kn=RPq=<7}ZdU-LYvNe9~35UUNnJiwOi=CD+dleu2y(xIa(S z2tYkf3RuEo(!UIc_`zR6SqO`GzbJWZq4U?GM9Vb8Fi+aL&ux&-$9Jfr)UhOBwAnqC zi_fRmMD;`V%7f*DQ(MSK)w&xskAt^ zZatcArW6ATg4#I-*`k~Gi(5#%-pQT=1L24Hs80V(9Y@2XDFBPEF-J%vH38o_8Z(Ek z{nK%nFXU>TvG)P@SVR8`xOlv}6b#DoybkTq96c=&F2`J){Hj>~p$;brvsZ*4J??2A z!~I3O*(5kNha(&)Zv84w`j|$#Yw{@Y67j<1Ej9(51}i_@pE!ODcpt|K#hZ@vDPQE$ z7b7!{pMYN}79kATnvN$a)&t|}0zU%)PeBDf3~@Tu!D)GKqIuXqYh1#qa}*QdbPa)? zI2%8kh6hJa<~P1Rfa<=0$xGDBgXaLWZo;nTiq<>66FsCQ@?0$3ARq}EsUfzt{8K3b z;k9LG2XBYpWLu^F-(491F<(`)mO4e7d#Uvuf zR~MC>AmBxE)cZLCIIs8dX*bV)#80O&byBVAw8ZCyIiFH~V0#88kzP73T3HPZbekzc zeU>GzxhQ&^lJz3XjdZ2#@DeHw7X*~5+zG3?+3`o#wWxq#sB7459PHl7A)+yF9kDdU z9D4fOwjK+%zjP^t$sb~|8x6Z?#FO{(XM({ zhJ4^kO;x-uFS5l7>UJya8&?z7P6GuU2Ga{HA^nm2G2p%2p~(ds-&WcyM$4Whbn{fB zEbLpTJDxz9Nv=(Lw3h-TK){>28;0H>$kOQ}^5edKK(Joiy!q_()wElv;9-qTKR&pV zWjx=HB3LLL6vOSfWAfrZEh+X)p4SBLD^^LYiz1K(2LjR-wv2d9AR`w*xq>V8=F&kyzU$)pjdqqBFb@Ac(EXS7tb`xkNWzo?1jOm+ ziw@K=x;`iips8<`dbL4859FrEW0eqVwQNmtV*-=ROKuYv5m#8f(4emCEHV?>C;uW6 ztc=i-THwdCkY#2A0y-7FM5LYNP_*`~NB@Qnf2z76h>Ghj;oN91sQX?_i|)7Fj~T{a zy7-E%@BNy5{pO5fAyvXWJ)D4SNfOdVLtl)GI2={d$mm|~$8dkqS^z(Au8~4=%;!m+ zRK){XuGPXo;LD=M3@0JN1E)!}KXN|?yqDY9w`f;~8S@~mejrFhm05hsAd4vDId$3$ zw0J~R*}NtQNP3INnKqp-Sl?N4LPB*_=xIJyz#M>#yc&9kv@PQb~6;{{mVs(vah}#>z`hNfJ*C_?~*-(TJ(Ka z<~1xJ!e?=|)Xxp}L6;o?s&Cq8)D$Mv# zY8}9UfM@N3#>>`?J$qf8>4)crW+Oej+dCnxQGdNQioSM5gyx57*H-ykdm$D~We({nN-G zU^Hcz5yJY{|%gQ{H21gfJn8Vl5e4>lPAEO{Apzh=@PzAd8Qscx{V|ystN@2vr z$IV-6GoHm#jU+WO;qZZgJbfA@(ju>ya%on$y~iivKhU8uQn#@pqTKo^elfSB2a`DK zWOO*4nl~O$EAm7|GGYCskM@nIJ-x28g_SW@P6J;YemYoaCw6cYZ&w46%4=P{Ot^l^ z!b*YiJTa)>USY(A2TT(48W%!s$Q{VG-to%(sewYGn0`dhOo_gla~Lr_N~jiU*DQpBi{II68QXl4@5Z*$u(d^D_xYVEc`T*kCj#U9VEMq&;33Q z2wp`~|3yikVhFJ^R;kFpBsqvYafczyY&PRT=doqXuAU;?lAGd1)B6F* zzNsjg7}CYI{3iv+%m-4>j)Z81+wuuOKrsZK=J(p(>EmWMeWk_tI$Hyc(ix7{ zs3fkXqE&ppcO)>+@&uaoTK8hRWF&P~N4~{@M!ZxOphQaEgg7TD!7+gE$lWiLUv@Qa z3E{r0JJ1)v>OLl=$z zNofdb33xui8CzR_6QUv~&Tzs#PZTlt!I>uXEk!3%5{X##hX2 zjg0IaSvfg5iNMuM_`3=w>Hn)5_>a=y@67*WWx@0ZrGvZj-@iHTep35)&i1h);6r=u zzP>d-!V7Ga5P91Z8FKxruKF~6+qT@1AvFj|kf1q-0BR%hcW(7Ds4s}X8}P48{@uC$ z>w^yc`v?Dj6-D2$LFMk8_I@FOJ#C(*2au;t>3>x#U+uMuWZv zCh9z5fP4#Yj|INr{Wt$x$?%Q;^-U*8da0jLxbb+OB-Btc059Kc9KTsO#5cRAO%Ga$ za$yFI8g-j6{wNEb#*!~<4)~M-b!aNzp)nrqAA!>lRrIrwc->uwc%a3S1>fktNby*S z^dHrg|Kk3M{{jjM?aa5q6P-mJ5S+3AC9=Op+pjX;-Y=bP)GQ#T!-)o0SA@Z36{dg3 z#bd;N0DzycxA*-xdW`&T9R0U){%?Hc_c&r_Xa6&f?(18R6>I;YfL-gsQ;Q+2uvvH) z%fJXD#)dH}y|s|p9Pi+85)P-O4-Xa+{{{51PWsRFt;Y`VAL?5s7{A1C;*X(5Z}%}t z+hv7WT*q|ky}#N(uNytr8abMM(EKEm(`ZBBE?rtms~$8~Ha3~4oZkT%G?evZOBdql z{KJ>F5^y9b4$7eQE}mUlCGEA1xCk;KC?WdvoqmX-B6fIp@u$A^*x~<5C4Dxrrme&A z8`@el&-<0Q9adZKBTE{PSBKlE0`>CJz~Vt`FFzTF3Dk8ACK`0_HF}=WJDQ8qm7gQg zyTYu?E4wzne*kR`n0ada!6Q+(Kmq|f7WS$Yfb|07Frm2QXNOGr8sOo*9|efV3R$H> z@_xz*zE*B3%+9FtUKjkY4`LGa_eK@m z)J^EAGqyHLQOk9X_57apG2CCYTFvDt*d0i^(RDc=kEO)}leF)C7)*QZvA+WGb4yGku3|;Q zl|13@V)G}OPHvLmuaK`xI#RBe$xw-GLKEpCu{jML&Ae!O+nl|I-5iPk^3>= zz1(;OzZ74?=HeTw@+ssaRNpw&AH?`os#-E$ETCr|vFU-INnC94K!&4Uq|G>pB#sGm zyDokHx+|O1e))_R|10eNol>t$pN$nqNWhjiMSXV_D`kBnc$B}_xAdI`!Ar%qDpv&r zR6HFS_^!2cCVJ)&s(B`oj(mz)z_HPf&x1=>U-F@l4otF2w!7PON*2rwv6v=oyz)~n zncF+!OwZ7^jG9z`o)28`n8((V86X#cr#t2(aD`eVTi-GF_p!vTncyP${N70o8%&~l zEX79MC3=klVerer7ac7Lpts6MGzN1!_11@~XP*oNM3GW-uJA@QbQ82dQHZx6F~bz=ihQInt7=!8b+y zUhc4Xu%v}I^LplG*{TT3G?z|s1x*3=v zdfM}mztU?<6+DQPVk*z4sIjscLZ%%p$U#6+HS5XJwE1_Rg^8(>hn8UtCR9q~Q)mR* z=)|ZJT}C*;o%|$}@QR0eK0ZHRng~RLgBdhavg?FmEEP}sxd*2bhXMqAE=&jA{U+#g zKrAp^2bC0u;DcNZol$iEzH>Yg^wIWRC31-1mM?j*S@LI7Fr5-24+JGq0AcYPh1h1& z8`PyXLvUdg*Ym1F!@gS{J7~M5DDnG%0Q%*pFSe(;JT-^8h0-c_Rg)ZptlgTFBZkd}7pza)%BIA5#;6)x~teuza?zL)zk++Vav zd~^}CbP>}NFx4gp86~hp+u9|}#FP5->LRE&)&_2WCRM|S?4rAYboXL#$+&pNjSqpd@1%v4_0Haqh>x2@iKPLf8JRx(99vtS{)7+1Q%9! za3nlAP4`@g+R!HBh+it)=yu9@TZDzP|O? zLg%kV$>Q7GR0&+n)Oh!k9)|Ax4#QJTk@IuR&4~;}nJF3Ti1Rd3LJrF(d-nnNSVR8`xSzM9)8cwX zg;?f${9bdJIJ<4l4vjF%R8MrgHcn&{!tQAw!~I2@AoZdo815`zXO)pT=HdsNbmd!Z zlOVrYf0Nm5`0K{!f8zKt;C&pMTZS#^%|Y0tL%8}=MX7J1*!3Iw$ubas0dRKK6Tb#m z)HlTnF@JSA$6UL1{rriCn7UJzZ0`jQ$jzdKC#It&S00eK%@l35j|`M+A(HuCu3|$je8e&B6bO9RIs|9o@Xc%>VrC= ziECg)$!a17z;b84!yLImUxd^f=<7hv43ar#M$I^NVTfa>g#%?&blnxn3PxdDqQv$R zV=BEr!lhsgagOB#yr&@f6mKA90c6!9eBTmS?_E6 z3D4bd$VJ{8-kFx`unl>p|7DyhvLohixgRr(zjVQt+3TrGR6hM8IHSLIvQ`2EKWmB+ z-A3>^J;KDn4?6i??#FO{(NgXUq$Md?$O&WRZBSoWHF#*zSlS&8pbNm0Y`&cQl<-IH z$AI^8a~v*pt@3k(0W3y0V0eYPG3FzQ7hm8Sx@n{*CT`8rf`HbCXXo1_tleLxOHYz? z)7^R!s$w0=IIl7Qyy+vE-zmVI%na;}`R(2PGO-yfsW4)Klb@%bzxUUc z-!|i@knvI%sx%krk;MEgPXhv$X<$sl39`p97mo$asx7?4J{$INIVRFPtI0py$P-b(k7hCYL-<pR8-jA!1`|Wb-@--otdnYcqFa<<&fXlsR4elET@4ra!1#kICT=L1{S2gxU zyPI#+6Gf_ZiAmgzDLGA33$$djGV(xno}h2j-%jUru4OqSH=in%D`wEb*+Vhd)?8 zV8AQxI62*tH!#n?ESp2HW{;>Ma&jU)YB_fD@?A&K?`a>y{Y7i4uu`0TL{k2(uvNb*#imKw7Lcf25LU#Y zh1HSEsUP_#;vNIuM;rzJ@d$R`Sp+~YTq)}-))l*5LQJnU!Uu&QWWHYm@4(AoD~2B& z+tWuLspyn5m?p{Q5=WK}7^8OB@;j$kBMaAe!`e;3>3FTlih=EPWlUV7aW)Rl7-Ra~ zN@HY^CNXxo4>S1I$++{hv6mdw$fGwCn<PyQXsFOgGn5u>hwb=GTwe~waF5In*Ywr{|guOB05B@z~Pp>YZL63=(Q#Eeh!R~ zNo7(TA|j`Ua^DY9hu&j{)typsD7VN9{x+D$!s0Jo4n9C?CG$nsdMCNBGnTAr$s9u$ zgt>B*bfh{Iuze#sx|jPg++VaNZ-`O6!;_l$HZ+u(k!0vEN`B~D2tQ>e{zSGl1?>W}TlK3kgqL{%9+`Yr+J%K3I+W%i+l z@vfXn+__>Ho)jla67#>lsw$L`Dyq|*(a#Kuh&47I-4nBI)wqAwJd+*a#V(j3q{Mvb`9Sx+6jlqFKt*V1sOVtB5>oyR1Ex5Mh{?2G)>2|!;J;sj?eZ{RTd=RGat1;`&iu`~Ah z3Pe&mCdj`cB5)8}K+*Wav;`EjKTKOdvHioe1r#TTwG~uR5y7nB8IC#h&VHe(Wa5Zo zo>;dIoJ1>`V8dhq^3i|}9fwORx0TwQmt*D%TUGb^3n-j^n6`kT?uTg$C^&y;wxEL% zXx@SjMxdxTY{QD+z(MBlaVH(SlwZ8@_m^~JR^ae2vnKI%Kl%b=@ns^SS;29abSBf0 zJN$PbXlJqu z(B)aoQ3ykx$&tU%gf@@i_i4OTy4I=BU<6xFGtN~^7(?ev|jyT$#J;8MH~<2!Tp=sx*7M_3c`<}Rj`5!Jr!A4gKN_pVG- zaWw1}-0NJRknmyJ0*V|TrY)cV^I_To3f98f%DLi9N3Ff=K+ntgFe7C6$^7HdBZjo< zC>SiAGx5v|m9#2D6BsWU2cK&hat9SzlJ4~vP)zqQZ2^UT57QPz_H-6bb?GBX`<<`!|bvO5voG0QP37BT(&l35%?<_F}-VDyw z-0NJRXy;+t0t$*ArY)e@>7m*Jh(p5K^6zq~%Ifr_InrEQI>=cq=7G!DiivhofsEP_ z{*%(F>Bb^-X8dHddvCg#qZ>)k(f0ZaC|r4%wt%9VhiMBa_<3lypo0-8egqwik2KE8 z=J+o@q}4aZR1v*bm#$G6Ytg>BeDbIHX)&G4`RoK6cn0=sS$byJ5C z%Igy?z2$Ob0g;t&OB-8Nj&@teA3IE2Xo$!+28b6kHgQv_PnM5&;ZCYc%-Q$vh$nq~ zb{t>f^kLdMH=+FI65_MW4ddi8b)*?FQ?BuOexL58tGVr+?Ih-ShiMCK>PnVLL(&aT zeDo_J+KR5lTDnDoIPx1gsp+$C>xB7MSdoEisD94vL6O8$EgS0^ zyc=27-}&pG+KTngk3BBbbX(nd*^J15z@(wAK6{9>r-}BA!F)2Y$9MhkpW4d0J3{=- zHV?h7@Jy9al#=%4q-d!jzObm5om)=Sex5jo_6?zf5hy|n9gMT?Si$*$-X;v#F|KWw zwd>{S=8hVdW~!#@;@je@yPbkS5Z_u8hwoRvRNG>iKwFK$$Gj&flB;%zpO$JV9e?R#o zl*g}|=}R{l;xS(ndl;{U3Zrc%n(DriN317*sc8KAe7^?%fCy4yrv9BB%e~I6<~{q) z&x_H7SA6C@G5SLdDS|pbA3lmyy|JUt)o^6wupi-{+L{cMP1oIOpA(TbMku9YL)$|i$Uhul~0TXUN^`WTrPyPn!>*2A;Y*xA)ySZJo_DW2c!ulHs|&abPN?aA{PPIw9nX$pjZ zNZuy4E>*t{k#Lf;#)o~ce`<^F{mFzZi@vC*We;AowN;(Sio`y;i8KA0$Y2>4mBAJF zFm0)u1&AY#vy;)R`6d`i1izofK#UO}-EqD{?Hk4ak{q37>P332;GsfyfOEE|?1W4Y3T*+d0U@PSY zyUfo?JInITks#o9>fJHqt#A-oN2NG%b)FD!Gcs8oMr(tDZDHEDd#L1_ilC<>_O_yx z#Bs9<awc4>MH$u;KK8Z zo8(uC(1h@hnJSDmNWQXMKX*k41=i)6D?-eyD;;fVZ5}^3aVyX= z#qK!LVcH6%2#>zQaYURHUZcjlJ7?sA(1f z_K?PVrPP_OtItqsl3Y<#ipF1!#~nRPTY78yrnY`sx}^U3FV8QNe+YT?Zjo{&0;Tno z#IbIlG!U$<+yEhd;n5dGY4L&=6zR?m+1+q$yqTj&UX4k(6MMDUZi?Swjd5B)o!od#)w58!jNXQghz^mUPE=u=G!DnFP~0$%9Qm zlUrdHjIkB6lunJQOY#=(0)A1BoG+`to4)_4tp`tK@1qDm$Fri5 zr;%Zu8}>oRVN`;eAp-s`YhGaTy&S>yRbJvXTSOYsro8cCql75brD;P)g*i7ikf89! zyS>gmgL&+>5>H~muizw5OB^c&tQr!FI$k}^y^JLoTE5lLpGby@87*UPxk*w^jk zzGpP}NhGc;qE?IZM!FR>Yf<#3ZIGQa|8A1`r?#$A(JCK5%e7)m&~9B@vz1(D$8Ucn zj!+A?_0rK^d^_61d@oVDIvJM!j08gl31$H<6x#Tyu=H_{M>EnD+&gSksNb#p|IvFv z2O|*NLkA;JiCox^aM0=v;*g^Ju%R)aO1g(>3#j1kVcG(!%X^r%fNI>r+DeZkLVQbR zM5A=}f-8>uET`3*ZKq7b=1d%8@8jF#8>S%<4N9G7eZ5>|yW6hQ5cKTz7f>nO!?Xod zG50WS0TtFgG+WTY2t=r%gAu5rEo{TW>3o-tj4d);P#-AlR2dU4!}zFLRK;}deyt__ zRC2vX6RrqznG1baQwXWNHA$$$UgrW8zCBD^K=pAC(-u$}-NUp6RFW3fmN%0dLatdx zlhmBgu)&b=;&q1Hktg?61aGIXnvJm=t_lU@I@yz=TME^@Zv=Q_!M}n9`yeb)!61!q%9r z7Ny5oGsfs_*_IsGbKc;c5}wgs=K_^;Jxp6bRbLO&7Ep26!?XodXcg90qE!97aDUCX za+Rdv=-=J83Kr7y&h`s=;z4Sq%$&vx#U9SnW z0ez+T@M10w%AVXxqF(PnQ0ZRh0##o5>g{Uv$5k5cRkC;S@${@s8Z=++5#$QdYHC=>Yg5^Eub2ru(pCuxt^-pqO=oh z{$PBci8!8;#%9W?y}P_DVLqs;b!e6j&6ol|#UU)Zu)D|Uox9dve*u*mJxp6b6-f`% z7Es~RL$d{QCD@mn)4m?J{POee?iog)8`L~tc3nyIg1A)dZP2GfE*RBr9hqq{adS#&n3?GOC%{^j>TJXiDR3bt^z}D`2v?i zj=6!1zMh{0((S5F<@{M3`j0d8$Cv*?{^s_jfTsZ@Oa!<4YX}bds~H|n-uo+P4>g z{%VF_gVwkNx~T5E)4u8?Z&77qAj8cZDF*${i$}U7=pVMwhV^M4#%U z>TlmjeH`){)I45eU4=u&hSY@A9gXwE^-XG=9IICDk84~48Tdhs%ZK=s;inzNPTxoo z@0e{Dw-UVG|K6p+#_eiH?V29a%TSo+*X)Q}yQB`GGnKC&^=)nx{)9cLjg3v}be?wh z?9j9sY}o{4f3<1J!DsRMeePzAlU=wjQ>A(i76yb7KDIo`(h>IGYFq-D-&0O%;^TEr z)sn!07L7+s0X-ywD59N}m<~A3MBH;_8D$PKFs;3DB4&{}DJ+o(-V@E9k1VytI1Let z+Nmpr3LO(I)Po#GRL;Ce<|=!qb4*?Q`8$=by0c3HlaAy}7=9Q&xkLtU0b6-0;^ap@ zxAl_x5lSsBL{JAUo%#KTdK>!B6;*lNdy^h)LQnDoHFVEO{>zVBqLFNZz+%`N<|fZx zs;e=$t(To$*EC<%6X#FW(u1l6lG~%UJN4Mg$+6C&<~D*3$?C~%GS-~x2rkj6Ctk(5 z_@9jbr@j!7AhbY0Sly1Q9Qr8VlqWv@n|ck`_`67n8DoQd{M5BCX*9#&)Ml;Yoj^X@rY<>%$2MW-0Czy*_IF`ORn`9-RH55D7w8~ujJ;m-J@APlZmrq zW3n{EH|y0+1}VP?kVd&%4+5yO?eU97FcwX*0q>dm)tWV`8A=+?>ynNo7C7}XzLCl8 zqPS0>?go2xusm(A|9Uw8!dOJqRE{sJk6J zv5wzhdFS?Y=S+Cx`PL|$Zlc<4bi!=@yK6^gb#@@tNP$v6As_iod!wq}s*&xmzoKb` z!Km&jW$!D9ZYbwT_n<%C=Xqf(jvG?P>k&1M!vZlND%67_PT7V4kw-KNbc%w+B;;M zOz+7Z3Lci-c23<1_>?dRDgO_Y3<~+k6}@LCaV*bLw&)JjT>Ge0t2p~8b&4_`8*-WY zJL;BqJAUPsm=}$vBfy8JTx|3|*^SRhK&V3 z$i??#rUDiY0V%wi%+aYIY#rEH14xb?Yet6=UB5qiTm)5-Bvo{Uq+;+?NkXL3WQWc1 z(tFM>lp#X>7oi;vsG)m0T>M;D3_QhzXAQh&h8QDG(d}2DR~p(foVOp!DR=}U!9&#o z$?Z`)sM_FPpO~*d`uYPNzMaWH5fAbzy;FDL>T-XP>~X@)Z~YiZ5ZaH6>0F5Uj0bGv zc6_+q#V@MY&c0zAOr`I=*KjG%q^9~h~v{^{+ITj&UY9pLIxI=v~bNWRK7T7v~?D?kS8?z9Ee+4lHF zmg5eyolwQ?q4q8tN$}up@10TK^Ld{26PsJnsW_NcA*K^2M;wD*L!>mXZ1 zJVQ`N(I=4>OcilFO}G+o^r6!?cLNDR-QBg3^IkzYKChZpuqjK0{;}CriH{wIC7Nlr zg&!j8^AsT{1(0?7*;C8X#*>IF?IZ7*6W0uB=(Rd>D+riOE7CP*33`a_kE_!FrPD%` z@HK%DFh|2FFi^^`(lnw3Dln);<6W*BpgN`SHvNO4kU9-eo-9O383|h+FWmmEF#g| z4}vjOZ)cy(@h{Hbqky^_NN$f>QYNW2B4Hw;^zAYv*TI{idJG6YNo%ts&5Es5B6!0m zzquPo5bAC?K~$|RbA2;mf`|RJd|gtXh-Go{$QS~&IRkYV92{jJ>j{8tQOHNWdbyUN zThVWYZp{>5`a|pls{a%sQSjmh;&k6;Y8$>F1RDU-Kp{%veXeoLtDHV3(=dOdSxu<0 zChRKGxfqlIReGY2=vs7;JKTWsJ0VJxN&>+#@flr57Vn(SVSQW&ny77Sc3fz9ckdp8T$5YxTq&!ZUeR`ERqnxf@6j>TX5o_iXZz9;p_&RUCuryGo0ZKN7d}Pd=vOrJMI%jFjQ0bB(S~~DGcDVa7 zt&~Rzy_q$yFhP_6WsyRZR4Szisn@R_Nj;aPk8$KO9LBw?H?6XBD|7F3^Qlho%$KC{3MDruKo9{a@6%;BiU<@^*;C`P z5XU`4m+mCS(w$?vCU>{)n={g|&-sP)*3k)tqgx`4!q5i+I$Sxzf(pmnY)(3y`+6&_ zgc-Eih$N22ii4=Xv1Ff~91EX2>Ji%(!%tU0^KvrYHG z-zej7Hm~mGEL*i*-4LKBm&(GYXJD&T^-9Um+ci*i)rp=Wd_XHu%@C8>-i_fcv z6>Xz)e3jz$%PB+df{=?YfD&jSANgus7x#>&Ded;if}G=Sn`In`* z6JT{3pv+i^5+mXiq<48ex=dQ55*K0yAmtUJga*X# z&Sg2iio139NxcwNNX8lQCOvV(`5+;o?4XwCkfjq)E-UozDWH(^=ews4dfNIY9@)Kn ziktgOcKw$-+@MuBolnl}+<1hGtiRQlC+~MhnM3(izc$GSM-opb)5O@vQORTLwiy4SvYD5 zOS?B*h7dp8H@L{U$Fv6~xhAUwpBTGndF zW+|>WF3z}%zjWSJof@Yc1P9$ygClUp5&8NT1jzq{a|R*!f7z4L2O+rkuqc8cbzITD zZYCO7S-@ViC7IP)Nz*cO(Sbt0x`R~c)Vufb_q_%- z`d39}SIsZO9vpkoWIHkwLN+4EYT1@b7=3MwG`@OCc@jUPTlca`sa-9cJP1x#J8H_4 z5eEd?ZJZHAr_yJgm|u1385meto8N@#A3%30LbT#HJ!Se(-D>$wD`?Lvf4*4}MkUbg zilDErtPi>Z@Ww#*2ZDa^#s>fRjlVSy?jNgyQve~|qL}Yg%0QdYe`@n>7Zc#US$l4? z(3jw_`aF@Mz<5&v9(#j^ha{A=>s102Ms`<{-m=+&*|n`&yBy6O=mcX?pFB8TdV4@=JCAO!L|~k8udL zx}RR9kl*0z(eS5z`0-a`JBh{g(E?eO@R8+PbQmi$RfIfj8625OASEIC38)t z2gnT7;^E(*` zOT=?m=QByt&)F&}WnaE-?Yq+T>8Qk4PzDlnJ@5P#ga7AGmv3|G+R3j7YcwP%1- z+5ZJVJx&)_ebC{oh}#=6K}|$vs!jU@z~rst&9zROsIvyBjspBP@hZ+H5 z!36;hFc=DeeY*e%^n@D-!A%s~8Nz(k89`r{ZvPGd9wJumhU#rP)K3}Qq2MQXpL!_n zV3``B#Rq$bjFacykD^P{zsa0u{=uS$X5>TLT%B+ z*C(@-1WoN@Ad_3t9Q&g`LWWzRnei+v0=JUFH z9EY<=FQ+VD@ga?@?Lbd@Qj_X6H z%QnPRr?eSvznWZRqMp1|G819per99A_&AhVZLs??j|aaAt-kAV+q@&gU52T|$#~-A zM*o{E7shOh#9zj&5cbWiZZkjY!BSzWG-9pr!Er>Td1s-P;5-^Z8>3-3QxT^7FYPsa zUVQ!CYq)C4{xd6AgfGl$GSfUU!MIE>c)!f*^bMp9?-knA2O{c~baZrfUtG-;% zNr9pomGP_dt=jkY&#V@$Cwf#xH#9IF_7EB#2N5c2QJh{Z)I1f>C@TNz6yna0{QR%% zH2_U5-!UuH$n&l#=?IDA`d0`EL>bQ;OQL6BCB5Y;-Ha=HqU{2E-QfX5%fwLODM&{e zsdEEaTBPKH(Rmq8lbH(XuWx>gdrkAC31+Wh1q6cbH2^*Jd9UH1xbhQ^K=&Ge(C6D; z19X!IXrzDl;SaELa{y7G?HZU5zY0QtQd*!v@RO9*l&j~`kOtUenI+HsNnofs*Mqtw z8QavGrwy(mgc<~pf)SvU)?>HSEYK5fAVfD&YG>H?{I65mLu9|ujs!H7eAkhH4!OcG z&jW~}Ea6~dc&?BM-)lvebW^`nNqenIC+o&L$|FY0OH@=7Xg@ukQ+}?^LHE+$| zFAFWEfp3=%Z+hDqS8>XlNa4}DnsS7yrF%`XF|W^!#%G-W5j*)kH@N-_EVSqTN41Jr z7Y@07vd~BL0=DwqeCY*JZXhH#QDJ9K7$g5BGIWT%2^t3q)4zJTpeJifvQX$e*=gce{K8pU?owW0 zYT+aD47vRFzbp=f^cR8uTm(k?g21EiU6iZ}0-ikGF9N^KVwxeMQ$e6-QEg5|$EA5r ze1|ey=Bax^?%AO3l&QaQpk1syoE^5lg+;jc2s%0!_ldyIjGhyW5EHJw%{+bH_xKcP z2Y-Y#R;P7s$nzu5j|d?&{09*jsKfnk2#f?pU?5-oBm$e9Q&(iy?O+{|e>hGQ>CIha zo}>{8kkv+~P&&o*6gTSxh2V2%72tp8lFM*afUBi=sAEaLPo*Y^w7!?@2r zZMqFxAVw3qJ(+1a1{NepmfBiHF^AWTq2Jol{kvQ~%ygJRrHNE-o5nlP6JL9eb?iML zLNANyiKlN@O~m<-Sq@PP6ANkaxumE%PBK`Rddw%_g6iOWyqmt)FT-_|zqtPA z;yTI~Tpwn92&yb$p=R1IuG>$$>s8!Vqn>^$ZS2k7I*ZaqcGt2z3_Oqv0o|4op6_^bDGRA`X zm$Cf0j0N=zV;QJCsT=OT-)XaQ?PH7=EAyFK z4{$OSDCg@(e*Ra|M}a!r@5WeAp^OE{7eC2Zbdk!uEt!W;Z0qDacd9vKqmTFrSB!rL z{X!jSJHPau3^3|m#sc(&8ywotGnU7pRb7$F!}quEcwxl1%hojA)))v-WPbiZQgYQ! z6Kw13vU@xTNN{hp2_O)Lf%g3ZYl8Zwm>>No`^*^{N~Ce~;;dWX9Tyv2EwX4a{)bfU za=#2%(f$JKp9`#LUx2mGAR#B@mbYZkeu0(u3KBkhpdrVk2Z@i?r)M_%7cyFEf(S-XqBkpGo&V0_;duP)aQ!8myWVTOWJ)fXjFUF`?@XVDMUtjRS)17vc zfMI$8d~=nx1d*=R>;J(}08oef-GCJh3amiB_(@>py}tcC)om_@1^giN#wEEMnC!Kw z(`gTfh`Q2!s4w1}0;BB(R-h-`;Lv{_STn88QLij8PO0FAzuCBKacuN5ww<>VJG?+g z!QF?}A;QjSyTA%0xVP%W0f6-YqWi^QFK^Zs32H3#(AIMs2(QxIv9_%jteKbu7iOh2 z>?jp$e;Ed&|Ha@x7lYBiU~msy7DijaTPDN(VsK4Kc^m1NNxk0Y$IVX-CubwNXb4D$ zh?|TCH*Rw2Ej9hc;KN+pj`vkc`ypUP>1{7Fc=+*!9c$FNes@owIaQh{V4Kbi)wxSxLuY6mF}=>gZ{&` z;AyZ(vP>5keJ=(BJ>dq2@$(pLLG4J6*C`cnnhawrz~2LFnkcaMlCJkm+$3>;&L>H> zZ}%hu3GOX?aR6ZbcSQFK*Pu0hypCMCpioZ*w#kI6-bxzlIL`T{!QL3brc~Mz<-ZKB zG5*5!p9|L*U*Nhk`uUaF@l*%R{lfLr2@{-zjJ)~_3Hga*)E#(USHk>XHw{SRKgOVI z$o5V83)hDU*HeOpGZvi^ZVCH@Ypf-?!MVY;%1;5KWc4>ez9$_5${T!+W)2QA>v9=Q z+x!RN8mPnlZg7nOg=-*R{3KjA-W69*_A1*LvdOFuH;8ScKKn^2HlDBR-j0>Lc~DFm z7-KJ713lpehxzkxZDMoT@K1}ZtCoZp}!2-G5;d_ zpNs66Uy!|BsIuxk{JS~#{USRi(={984~>fFHt7+2rAf|t9k=GFyql22YA!J%<8!>~ zFR~vdvcHeY46u5Rj_a^bWWQETBR-H(UGIvdXDNx25175A6J?l5ygpfxY)Bd1vSJU1SFm+;s@Ycf9$u9f2K^sMZGw;!Ey8a)3K6< zjvtY|Lrpb;+cNzeMr7($TaPdCGno}YopYmAI&*(~S1P)<=YioB{b;~|tgmxb(QTlo`5J=2b zo$CK}@6rFj5M_><7c<%tBK-KnOYV@swu%PGj%-{FpPAP@L<%ne9z?=iQ3hXMSrYdru3IY3*1Xb6;A01b}cjapzssYTF* zH>nnn*M_`w7BF;a8oZmO!zr7J-=10#OTSVtueMd7 zDHs(i;QE~B2<~?9JIwu4i%%Gqs>?c6*{6KCQ({~OTaR@GI+|Whvmxm9$$V_g8~h_b z|0~fApjY}yYQYd~5+>iQa%9YCOD!STXu>%V3_3HCgV=?J+_OBu;|a!rQVZx!i9k=d z!Qn!3)?qJ71Ul^wvji8bbD$7a+eyQ*@`(@}h!R&z#(8ONV+MXG&i=~gnEf=2=#h1k zGhXWDop=cakK~k*=oCz+l4;vkoJTZP+HM?eI{S@<0O|b8LcUz;4D)FZz(RmTcMsbG zIcx7>`~8v|pyBzuksBN+xl#6u~~=ELm)xyO34VK(6kGVI8LnH}=cO z4enoZ^XHNq+%M#&{@J8pQW3*7wf~!O6UZe_PRJze?yP?ZxtZxCr?Qr(pLX!QEPNKU z!2KpbkZJ7XD=Zq8fpfH#ipu*ZHv;C)YWMlfgJhyRiUMT9XAPA+kl&w8GPJ@=W2LC! zxb@4(4bUt7B)P$Ak~WkzuOLet+#c)NhL;!iuzH~K8Rusrk2Fc|MI z2LHJjjQ0hDYn2xtquKg|>ipltV8|6VR8Q3#)ENE{44yEF%HAAV??bymho?cn9>4WY z+C`bc)`ewSD$X=!V|M=-JRQ!+R^w$EDw9Nj+!}SFokmn=OlvMVKM&b0^F->+A713} zuLKx?Ug;+>Sg?ZAX!CVSSjPkQE!Clr3i))EQ$Z`eGQ|-*c8x}MAHjHgF&O9xH#q!X zg27@gvPg4-3@2vfhvpDW!(W{ zjn{|tHSgjb)#Z`rR;vP=4-~eHv+74SIVxRNpJtxIX1MGaOd1(>@AGBRF6z4Jz9AFu zK0L19wys4-^MBldK+$V-mT&lmALZQ<25cmjc+)xyQ_5W4zD)Np4t$~vOy8pZhq@T! zGMQUo*9Z5V=l!r@$)6ct{SC=qAI#m)~mfxigZ9+4-sR3;U&wZV8%8Zx{UlX$Qs#+;@bL6%pwu6 zsc&4O$GO)jAw&zq8vXCfweBv)D4hUvG4aYy9J|-wtF*ua#`UKnbRF16wcZBRnL};> zwED#5Un=P~nQ}P^yp$4!ba9B@xpSQW(W(T5TV(SwA$!7xrg2{A)#tHjFjX9>cJKTckf1r^t}q4 z=3S;pCKu<`L?fjE!@0l^PXDM)Ec$eso-C(YyOoPiCXTRF(1qiWb;#W_!^)En6qNvh z)%)>b9snY6Frq-0wZn+sv%o@qBgx}Y=wck)teQB--l7XSI!Bi#p>4B3Q_&!zt_NTK zT|Up@|Ap)lCE*0bj;E<9i&wc8c~IxLMEh~!0@Sw_(elr5&Ldq*Iuq{}rHpAW8;3cM zW!qk;Vlo6*setG?Dd%2El1Q41o!nCGXP1oh(bPO$OOT7~SV%%LDp9Xc!gv1}gLyGt3|aP|KQZx}>3rOS)l?VyrwP?j23sf?VNktW0~i z`+a=9Z{>NYc+mx&$P;KEYnl3_%q67Jz8`LUqLlbL-{s}bnew+RM7;Ao=588r&dJ@_ z?;3@BZX;?7t|&6sD>-=zsW~Gymdp$%P@OPZTMyK5Cq#Vu&Mm%Qozkb!N8L@({TvBb z;t7L|Cgb6BD#p5}M`n6%iVWRAvGsdILwEKm_SK6=j$TQ>!E6o}qn>mV9Xx99o`Qoi zm(^&>jwL*C>GV$TSwsP)bmh~`@2og4tDn8b*7t&*xGk8l<@E}#bd2(oU@my94_

vHSrHL`4Y!))mrU|+My=J*?NCLvdFH?W^2AuF;5 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

ecf&5+ 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 [codec]\n") + return + } + db := os.Args[1] + codec := carbs.IndexSorted + if len(os.Args) == 3 { + if os.Args[2] == "Hash" { + codec = carbs.IndexHashed + } else if os.Args[2] == "GobHash" { + codec = carbs.IndexGobHashed + } + } + + dbBacking, err := mmap.Open(db) + if err != nil { + fmt.Printf("Error Opening car for hydration: %v\n", err) + return + } + + dbstat, err := os.Stat(db) + if err != nil { + fmt.Printf("Error statting car for hydration: %v\n", err) + return + } + + idx, err := carbs.GenerateIndex(dbBacking, dbstat.Size(), codec, true) + if err != nil { + fmt.Printf("Error generating index: %v\n", err) + return + } + + fmt.Printf("Saving...\n") + + if err := carbs.Save(idx, db); err != nil { + fmt.Printf("Error saving : %v\n", err) + return + } + return +} From f016c35c868a5e73db3d10f1340ff1b5d971e03d Mon Sep 17 00:00:00 2001 From: Ian Davis Date: Thu, 14 Jan 2021 12:08:54 +0000 Subject: [PATCH 060/291] Update version of github.com/ipld/go-car This commit was moved from ipld/go-car@260b871d171ed80c48800eed6b2adb60dcaba579 --- ipld/car/v2/carbs/carbs.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ipld/car/v2/carbs/carbs.go b/ipld/car/v2/carbs/carbs.go index c59e5d8922..303a9779d2 100644 --- a/ipld/car/v2/carbs/carbs.go +++ b/ipld/car/v2/carbs/carbs.go @@ -30,7 +30,8 @@ type Carbs struct { var _ bs.Blockstore = (*Carbs)(nil) func (c *Carbs) Read(idx int64) (cid.Cid, []byte, error) { - return util.ReadNode(bufio.NewReader(&unatreader{c.backing, idx})) + bcid, _, data, err := util.ReadNode(bufio.NewReader(&unatreader{c.backing, idx})) + return bcid, data, err } // DeleteBlock doesn't delete a block on RO blockstore @@ -148,7 +149,7 @@ func (c *Carbs) PutMany([]blocks.Block) error { // AllKeysChan returns the list of keys in the store func (c *Carbs) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) + header, _, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) if err != nil { return nil, fmt.Errorf("Error reading car header: %w", err) } @@ -192,7 +193,7 @@ func (c *Carbs) HashOnRead(enabled bool) { // Roots returns the root CIDs of the backing car func (c *Carbs) Roots() ([]cid.Cid, error) { - header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) + header, _, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) if err != nil { return nil, fmt.Errorf("Error reading car header: %w", err) } @@ -238,7 +239,7 @@ func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool bar.Start() - header, err := car.ReadHeader(bufio.NewReader(&unatreader{store, 0})) + header, _, err := car.ReadHeader(bufio.NewReader(&unatreader{store, 0})) if err != nil { return nil, fmt.Errorf("Error reading car header: %w", err) } From a20f777ebbab4f1738fb537b524a445d35d50af6 Mon Sep 17 00:00:00 2001 From: Will Scott Date: Fri, 9 Apr 2021 19:33:49 -0700 Subject: [PATCH 061/291] add 'of' for creation of new Carbs This commit was moved from ipld/go-car@4c5f1ae563fa7eb6e99a3c43dffc7bc05163d2b0 --- ipld/car/v2/carbs/carbs.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ipld/car/v2/carbs/carbs.go b/ipld/car/v2/carbs/carbs.go index 303a9779d2..e29d755529 100644 --- a/ipld/car/v2/carbs/carbs.go +++ b/ipld/car/v2/carbs/carbs.go @@ -225,6 +225,11 @@ func Load(path string, noPersist bool) (*Carbs, error) { return &obj, nil } +// Of opens a carbs data store from an existing reader of the base data and index +func Of(backing io.ReaderAt, index Index) *Carbs { + return &Carbs{backing, index} +} + // GenerateIndex provides a low-level interface to create an index over a // reader to a car stream. func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool) (Index, error) { From 17ed81bf1d88959016a047caab6c20e4ba73d674 Mon Sep 17 00:00:00 2001 From: Will Scott Date: Fri, 9 Apr 2021 20:46:46 -0700 Subject: [PATCH 062/291] allow 'Record' Index to be public to allow for other implementations of Index This commit was moved from ipld/go-car@4e343d75979e9dd7f59711ebc67299f2cc1086a5 --- ipld/car/v2/carbs/index.go | 2 +- ipld/car/v2/carbs/indexgobhash.go | 2 +- ipld/car/v2/carbs/indexhashed.go | 2 +- ipld/car/v2/carbs/indexsorted.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ipld/car/v2/carbs/index.go b/ipld/car/v2/carbs/index.go index b13baa9bb8..b139a24a4d 100644 --- a/ipld/car/v2/carbs/index.go +++ b/ipld/car/v2/carbs/index.go @@ -35,7 +35,7 @@ var IndexAtlas = map[IndexCodec]IndexCls{ // Record is a pre-processed record of a car item and location. type Record struct { cid.Cid - idx uint64 + Idx uint64 } // Index provides an interface for figuring out where in the car a given cid begins diff --git a/ipld/car/v2/carbs/indexgobhash.go b/ipld/car/v2/carbs/indexgobhash.go index 51ba4c6997..b7dedba0a4 100644 --- a/ipld/car/v2/carbs/indexgobhash.go +++ b/ipld/car/v2/carbs/indexgobhash.go @@ -33,7 +33,7 @@ func (m *mapGobIndex) Codec() IndexCodec { func (m *mapGobIndex) Load(rs []Record) error { for _, r := range rs { - (*m)[r.Cid] = r.idx + (*m)[r.Cid] = r.Idx } return nil } diff --git a/ipld/car/v2/carbs/indexhashed.go b/ipld/car/v2/carbs/indexhashed.go index 62bfce4004..f4c04aed09 100644 --- a/ipld/car/v2/carbs/indexhashed.go +++ b/ipld/car/v2/carbs/indexhashed.go @@ -32,7 +32,7 @@ func (m *mapIndex) Codec() IndexCodec { func (m *mapIndex) Load(rs []Record) error { for _, r := range rs { - (*m)[r.Cid] = r.idx + (*m)[r.Cid] = r.Idx } return nil } diff --git a/ipld/car/v2/carbs/indexsorted.go b/ipld/car/v2/carbs/indexsorted.go index 6378df34c8..d16d10d7db 100644 --- a/ipld/car/v2/carbs/indexsorted.go +++ b/ipld/car/v2/carbs/indexsorted.go @@ -165,7 +165,7 @@ func (m *multiWidthIndex) Load(items []Record) error { idxs[len(digest)] = make([]digestRecord, 0) idx = idxs[len(digest)] } - idxs[len(digest)] = append(idx, digestRecord{digest, item.idx}) + idxs[len(digest)] = append(idx, digestRecord{digest, item.Idx}) } // Sort each list. then write to compact form. From 19d771b9d68cd0583b744fd288dc30540a3a54cd Mon Sep 17 00:00:00 2001 From: Will Scott Date: Sat, 10 Apr 2021 17:14:52 -0700 Subject: [PATCH 063/291] update to go-car v0.2.2 This commit was moved from ipld/go-car@0509247d9d32d72ba07752ea1ebe45bee3c3c914 --- ipld/car/v2/carbs/carbs.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ipld/car/v2/carbs/carbs.go b/ipld/car/v2/carbs/carbs.go index e29d755529..ca7256f815 100644 --- a/ipld/car/v2/carbs/carbs.go +++ b/ipld/car/v2/carbs/carbs.go @@ -30,7 +30,7 @@ type Carbs struct { var _ bs.Blockstore = (*Carbs)(nil) func (c *Carbs) Read(idx int64) (cid.Cid, []byte, error) { - bcid, _, data, err := util.ReadNode(bufio.NewReader(&unatreader{c.backing, idx})) + bcid, data, err := util.ReadNode(bufio.NewReader(&unatreader{c.backing, idx})) return bcid, data, err } @@ -149,7 +149,7 @@ func (c *Carbs) PutMany([]blocks.Block) error { // AllKeysChan returns the list of keys in the store func (c *Carbs) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - header, _, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) + header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) if err != nil { return nil, fmt.Errorf("Error reading car header: %w", err) } @@ -193,7 +193,7 @@ func (c *Carbs) HashOnRead(enabled bool) { // Roots returns the root CIDs of the backing car func (c *Carbs) Roots() ([]cid.Cid, error) { - header, _, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) + header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) if err != nil { return nil, fmt.Errorf("Error reading car header: %w", err) } @@ -244,7 +244,7 @@ func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool bar.Start() - header, _, err := car.ReadHeader(bufio.NewReader(&unatreader{store, 0})) + header, err := car.ReadHeader(bufio.NewReader(&unatreader{store, 0})) if err != nil { return nil, fmt.Errorf("Error reading car header: %w", err) } From 6977b265b02b3a12cae336d6d321d7305e043238 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 15 Jun 2021 11:11:06 +0100 Subject: [PATCH 064/291] Address `staticcheck` errors Remove unused structs, convert error messages to lower case and remove redundant `return` statements. Address review comments - Reoder imports using `gofumpt`. - Use consistent import alias for `carv1`. - Rename structs for better readability. - Add TODO to fix logging, tests, etc. - Move test related files to `testdata`. This commit was moved from ipld/go-car@94190bc83ca291d393111d21519920632ca00ed8 --- ipld/car/v2/carbs/carbs.go | 92 +++++++++++----------- ipld/car/v2/carbs/carbs_test.go | 31 +------- ipld/car/v2/carbs/index.go | 2 +- ipld/car/v2/carbs/indexsorted.go | 2 +- ipld/car/v2/carbs/{ => testdata}/test.car | Bin ipld/car/v2/carbs/util/hydrate.go | 4 +- 6 files changed, 51 insertions(+), 80 deletions(-) rename ipld/car/v2/carbs/{ => testdata}/test.car (100%) diff --git a/ipld/car/v2/carbs/carbs.go b/ipld/car/v2/carbs/carbs.go index ca7256f815..6891d925d4 100644 --- a/ipld/car/v2/carbs/carbs.go +++ b/ipld/car/v2/carbs/carbs.go @@ -10,47 +10,47 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - bs "github.com/ipfs/go-ipfs-blockstore" + blockstore "github.com/ipfs/go-ipfs-blockstore" "github.com/multiformats/go-multihash" pb "github.com/cheggaaa/pb/v3" - car "github.com/ipld/go-car" + carv1 "github.com/ipld/go-car" "github.com/ipld/go-car/util" "golang.org/x/exp/mmap" ) -var errNotFound = bs.ErrNotFound +var errNotFound = blockstore.ErrNotFound -// Carbs provides a read-only Car Block Store. -type Carbs struct { +// BlockStore provides a read-only Car Block Store. +type BlockStore struct { backing io.ReaderAt idx Index } -var _ bs.Blockstore = (*Carbs)(nil) +var _ blockstore.Blockstore = (*BlockStore)(nil) -func (c *Carbs) Read(idx int64) (cid.Cid, []byte, error) { - bcid, data, err := util.ReadNode(bufio.NewReader(&unatreader{c.backing, idx})) +func (b *BlockStore) Read(idx int64) (cid.Cid, []byte, error) { + bcid, data, err := util.ReadNode(bufio.NewReader(&unatreader{b.backing, idx})) return bcid, data, err } // DeleteBlock doesn't delete a block on RO blockstore -func (c *Carbs) DeleteBlock(_ cid.Cid) error { +func (b *BlockStore) DeleteBlock(_ cid.Cid) error { return fmt.Errorf("read only") } // Has indicates if the store has a cid -func (c *Carbs) Has(key cid.Cid) (bool, error) { - offset, err := c.idx.Get(key) +func (b *BlockStore) Has(key cid.Cid) (bool, error) { + offset, err := b.idx.Get(key) if err != nil { return false, err } - uar := unatreader{c.backing, int64(offset)} + uar := unatreader{b.backing, int64(offset)} _, err = binary.ReadUvarint(&uar) if err != nil { return false, err } - cid, _, err := readCid(c.backing, uar.at) + cid, _, err := readCid(b.backing, uar.at) if err != nil { return false, err } @@ -100,60 +100,61 @@ func readCid(store io.ReaderAt, at int64) (cid.Cid, int, error) { } // Get gets a block from the store -func (c *Carbs) Get(key cid.Cid) (blocks.Block, error) { - offset, err := c.idx.Get(key) +func (b *BlockStore) Get(key cid.Cid) (blocks.Block, error) { + offset, err := b.idx.Get(key) if err != nil { return nil, err } - entry, bytes, err := c.Read(int64(offset)) + entry, bytes, err := b.Read(int64(offset)) if err != nil { + // TODO replace with logging fmt.Printf("failed get %d:%v\n", offset, err) - return nil, bs.ErrNotFound + return nil, blockstore.ErrNotFound } if !entry.Equals(key) { - return nil, bs.ErrNotFound + return nil, blockstore.ErrNotFound } return blocks.NewBlockWithCid(bytes, key) } // GetSize gets how big a item is -func (c *Carbs) GetSize(key cid.Cid) (int, error) { - idx, err := c.idx.Get(key) +func (b *BlockStore) GetSize(key cid.Cid) (int, error) { + idx, err := b.idx.Get(key) if err != nil { return -1, err } - len, err := binary.ReadUvarint(&unatreader{c.backing, int64(idx)}) + len, err := binary.ReadUvarint(&unatreader{b.backing, int64(idx)}) if err != nil { - return -1, bs.ErrNotFound + return -1, blockstore.ErrNotFound } - cid, _, err := readCid(c.backing, int64(idx+len)) + cid, _, err := readCid(b.backing, int64(idx+len)) if err != nil { return 0, err } if !cid.Equals(key) { - return -1, bs.ErrNotFound + return -1, blockstore.ErrNotFound } // get cid. validate. return int(len), err } // Put does nothing on a ro store -func (c *Carbs) Put(blocks.Block) error { +func (b *BlockStore) Put(blocks.Block) error { return fmt.Errorf("read only") } // PutMany does nothing on a ro store -func (c *Carbs) PutMany([]blocks.Block) error { +func (b *BlockStore) PutMany([]blocks.Block) error { return fmt.Errorf("read only") } // AllKeysChan returns the list of keys in the store -func (c *Carbs) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) +func (b *BlockStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{b.backing, 0})) if err != nil { - return nil, fmt.Errorf("Error reading car header: %w", err) + return nil, fmt.Errorf("error reading car header: %w", err) } - offset, err := car.HeaderSize(header) + offset, err := carv1.HeaderSize(header) if err != nil { return nil, err } @@ -162,14 +163,14 @@ func (c *Carbs) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { go func() { done := ctx.Done() - rdr := unatreader{c.backing, int64(offset)} - for true { + rdr := unatreader{b.backing, int64(offset)} + for { l, err := binary.ReadUvarint(&rdr) thisItemForNxt := rdr.at if err != nil { return } - c, _, err := readCid(c.backing, thisItemForNxt) + c, _, err := readCid(b.backing, thisItemForNxt) if err != nil { return } @@ -187,21 +188,20 @@ func (c *Carbs) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { } // HashOnRead does nothing -func (c *Carbs) HashOnRead(enabled bool) { - return +func (b *BlockStore) HashOnRead(bool) { } // Roots returns the root CIDs of the backing car -func (c *Carbs) Roots() ([]cid.Cid, error) { - header, err := car.ReadHeader(bufio.NewReader(&unatreader{c.backing, 0})) +func (b *BlockStore) Roots() ([]cid.Cid, error) { + header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{b.backing, 0})) if err != nil { - return nil, fmt.Errorf("Error reading car header: %w", err) + return nil, fmt.Errorf("error reading car header: %w", err) } return header.Roots, nil } // Load opens a carbs data store, generating an index if it does not exist -func Load(path string, noPersist bool) (*Carbs, error) { +func Load(path string, noPersist bool) (*BlockStore, error) { reader, err := mmap.Open(path) if err != nil { return nil, err @@ -218,7 +218,7 @@ func Load(path string, noPersist bool) (*Carbs, error) { } } } - obj := Carbs{ + obj := BlockStore{ backing: reader, idx: idx, } @@ -226,8 +226,8 @@ func Load(path string, noPersist bool) (*Carbs, error) { } // Of opens a carbs data store from an existing reader of the base data and index -func Of(backing io.ReaderAt, index Index) *Carbs { - return &Carbs{backing, index} +func Of(backing io.ReaderAt, index Index) *BlockStore { + return &BlockStore{backing, index} } // GenerateIndex provides a low-level interface to create an index over a @@ -244,11 +244,11 @@ func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool bar.Start() - header, err := car.ReadHeader(bufio.NewReader(&unatreader{store, 0})) + header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{store, 0})) if err != nil { - return nil, fmt.Errorf("Error reading car header: %w", err) + return nil, fmt.Errorf("error reading car header: %w", err) } - offset, err := car.HeaderSize(header) + offset, err := carv1.HeaderSize(header) if err != nil { return nil, err } @@ -258,7 +258,7 @@ func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool records := make([]Record, 0) rdr := unatreader{store, int64(offset)} - for true { + for { thisItemIdx := rdr.at l, err := binary.ReadUvarint(&rdr) bar.Add64(int64(l)) diff --git a/ipld/car/v2/carbs/carbs_test.go b/ipld/car/v2/carbs/carbs_test.go index 5ec52e972f..2aa48fe708 100644 --- a/ipld/car/v2/carbs/carbs_test.go +++ b/ipld/car/v2/carbs/carbs_test.go @@ -1,13 +1,8 @@ package carbs import ( - "context" - "fmt" "os" "testing" - - "github.com/ipfs/go-cid" - format "github.com/ipfs/go-ipld-format" ) /* @@ -56,7 +51,8 @@ func TestIndexRT(t *testing.T) { } defer os.Remove(carFile) */ - carFile := "test.car" + // TODO use temporary directory to run tests taht work with OS file system to avoid accidental source code modification + carFile := "testdata/test.car" cf, err := Load(carFile, false) if err != nil { @@ -83,26 +79,3 @@ func TestIndexRT(t *testing.T) { t.Fatalf("bad index: %d %v", idx, err) } } - -type mockNodeGetter struct { - Nodes map[cid.Cid]format.Node -} - -func (m *mockNodeGetter) Get(_ context.Context, c cid.Cid) (format.Node, error) { - n, ok := m.Nodes[c] - if !ok { - return nil, fmt.Errorf("unknown node") - } - return n, nil -} - -func (m *mockNodeGetter) GetMany(_ context.Context, cs []cid.Cid) <-chan *format.NodeOption { - ch := make(chan *format.NodeOption, 5) - go func() { - for _, c := range cs { - n, e := m.Get(nil, c) - ch <- &format.NodeOption{Node: n, Err: e} - } - }() - return ch -} diff --git a/ipld/car/v2/carbs/index.go b/ipld/car/v2/carbs/index.go index b139a24a4d..1f381308c8 100644 --- a/ipld/car/v2/carbs/index.go +++ b/ipld/car/v2/carbs/index.go @@ -78,7 +78,7 @@ func Restore(path string) (Index, error) { } idx, ok := IndexAtlas[IndexCodec(codec)] if !ok { - return nil, fmt.Errorf("Unknown codec: %d", codec) + return nil, fmt.Errorf("unknown codec: %d", codec) } idxInst := idx() if err := idxInst.Unmarshal(&uar); err != nil { diff --git a/ipld/car/v2/carbs/indexsorted.go b/ipld/car/v2/carbs/indexsorted.go index d16d10d7db..eca593c9f6 100644 --- a/ipld/car/v2/carbs/indexsorted.go +++ b/ipld/car/v2/carbs/indexsorted.go @@ -88,7 +88,7 @@ func (s *singleWidthIndex) get(d []byte) uint64 { if int64(idx) == s.len { return 0 } - if bytes.Compare(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) != 0 { + if !bytes.Equal(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) { return 0 } return binary.LittleEndian.Uint64(s.index[(idx+1)*int(s.width)-8 : (idx+1)*int(s.width)]) diff --git a/ipld/car/v2/carbs/test.car b/ipld/car/v2/carbs/testdata/test.car similarity index 100% rename from ipld/car/v2/carbs/test.car rename to ipld/car/v2/carbs/testdata/test.car diff --git a/ipld/car/v2/carbs/util/hydrate.go b/ipld/car/v2/carbs/util/hydrate.go index db800da7d3..d0fb50ae17 100644 --- a/ipld/car/v2/carbs/util/hydrate.go +++ b/ipld/car/v2/carbs/util/hydrate.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/willscott/carbs" + "github.com/ipld/go-car/v2/carbs" "golang.org/x/exp/mmap" ) @@ -45,7 +45,5 @@ func main() { if err := carbs.Save(idx, db); err != nil { fmt.Printf("Error saving : %v\n", err) - return } - return } From 2d6cf75d688d8e660f223198c3a25ae38d14c615 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 17 Jun 2021 12:21:31 +0100 Subject: [PATCH 065/291] Remove carbs dedicated go module Remove the carbs dedicated go module, along with CI configuration, licenses and move README into `doc.go`. This commit was moved from ipld/go-car@9e407d6bed12e2e1b2c42fd7d8b6e665b1937e53 --- ipld/car/v2/carbs/LICENSE | 202 ---------------------------------- ipld/car/v2/carbs/LICENSE-MIT | 21 ---- ipld/car/v2/carbs/README.md | 13 --- ipld/car/v2/carbs/doc.go | 2 + 4 files changed, 2 insertions(+), 236 deletions(-) delete mode 100644 ipld/car/v2/carbs/LICENSE delete mode 100644 ipld/car/v2/carbs/LICENSE-MIT delete mode 100644 ipld/car/v2/carbs/README.md create mode 100644 ipld/car/v2/carbs/doc.go diff --git a/ipld/car/v2/carbs/LICENSE b/ipld/car/v2/carbs/LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/ipld/car/v2/carbs/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/ipld/car/v2/carbs/LICENSE-MIT b/ipld/car/v2/carbs/LICENSE-MIT deleted file mode 100644 index 8c6ca2a25b..0000000000 --- a/ipld/car/v2/carbs/LICENSE-MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Will Scott - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -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. diff --git a/ipld/car/v2/carbs/README.md b/ipld/car/v2/carbs/README.md deleted file mode 100644 index 9aec21a2c5..0000000000 --- a/ipld/car/v2/carbs/README.md +++ /dev/null @@ -1,13 +0,0 @@ -🍔 Carbs -=== - -Car Blockstore provides a read-only blockstore interface directly reading out of a car file. - - -License ---- - -Carbs is dual-licensed under Apache 2.0 and MIT terms: - - Apache License, Version 2.0, (LICENSE or http://www.apache.org/licenses/LICENSE-2.0) - MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) diff --git a/ipld/car/v2/carbs/doc.go b/ipld/car/v2/carbs/doc.go new file mode 100644 index 0000000000..bcff12ac39 --- /dev/null +++ b/ipld/car/v2/carbs/doc.go @@ -0,0 +1,2 @@ +// Package carbs provides a read-only blockstore interface directly reading out of a car file. +package carbs From e5b0008807a888ce24ecd48501e9b2d0d921f538 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 18 Jun 2021 14:03:41 +0100 Subject: [PATCH 066/291] Implement read-only random access blockstore Refactor the carbon and carbs packages into a read-only blockstore. Move the packages to `internal` as needed and remove duplicate reader implementations. Improve serialization efficiency for car v2 primitives. Note the readonly blockstore will be altered in the coming PRs to understand car v2 format and work transparently. For now we want to push the refactoring and changes to unblock other parallel workstreams. Address PR reviews - Sort imports - Rename types for clarity - Add TODO for future PRs This commit was moved from ipld/go-car@d336f84d1a2121486619c9aafff9a0ffdc6dd2a6 --- ipld/car/v2/blockstore/readonly.go | 189 +++++++++++ .../readonly_test.go} | 7 +- .../{carbs => blockstore}/testdata/test.car | Bin ipld/car/v2/car.go | 54 ++-- ipld/car/v2/carbon/reader.go | 23 -- ipld/car/v2/carbs/carbs.go | 301 ------------------ ipld/car/v2/carbs/reader.go | 20 -- ipld/car/v2/{ => internal}/carbon/carbon.go | 23 +- .../v2/{ => internal}/carbon/carbon_fds.go | 13 +- .../v2/{ => internal}/carbon/carbon_test.go | 6 +- ipld/car/v2/{ => internal}/carbon/doc.go | 1 + .../car/v2/{ => internal}/carbon/poswriter.go | 0 ipld/car/v2/{ => internal}/carbs/doc.go | 1 + .../v2/{ => internal}/carbs/util/hydrate.go | 15 +- ipld/car/v2/internal/index/errors.go | 9 + ipld/car/v2/internal/index/generator.go | 81 +++++ .../car/v2/{carbs => internal/index}/index.go | 66 ++-- .../{carbs => internal/index}/indexgobhash.go | 4 +- .../{carbs => internal/index}/indexhashed.go | 4 +- .../{carbs => internal/index}/indexsorted.go | 33 +- .../index}/insertionindex.go | 65 ++-- ipld/car/v2/internal/io/cid.go | 52 +++ .../car/v2/{ => internal/io}/offset_reader.go | 18 +- ipld/car/v2/internal/io/offset_writer.go | 16 + ipld/car/v2/reader.go | 3 +- ipld/car/v2/writer.go | 9 +- ipld/car/v2/writer_test.go | 4 +- 27 files changed, 513 insertions(+), 504 deletions(-) create mode 100644 ipld/car/v2/blockstore/readonly.go rename ipld/car/v2/{carbs/carbs_test.go => blockstore/readonly_test.go} (92%) rename ipld/car/v2/{carbs => blockstore}/testdata/test.car (100%) delete mode 100644 ipld/car/v2/carbon/reader.go delete mode 100644 ipld/car/v2/carbs/carbs.go delete mode 100644 ipld/car/v2/carbs/reader.go rename ipld/car/v2/{ => internal}/carbon/carbon.go (73%) rename ipld/car/v2/{ => internal}/carbon/carbon_fds.go (86%) rename ipld/car/v2/{ => internal}/carbon/carbon_test.go (91%) rename ipld/car/v2/{ => internal}/carbon/doc.go (92%) rename ipld/car/v2/{ => internal}/carbon/poswriter.go (100%) rename ipld/car/v2/{ => internal}/carbs/doc.go (81%) rename ipld/car/v2/{ => internal}/carbs/util/hydrate.go (73%) create mode 100644 ipld/car/v2/internal/index/errors.go create mode 100644 ipld/car/v2/internal/index/generator.go rename ipld/car/v2/{carbs => internal/index}/index.go (55%) rename ipld/car/v2/{carbs => internal/index}/indexgobhash.go (91%) rename ipld/car/v2/{carbs => internal/index}/indexhashed.go (91%) rename ipld/car/v2/{carbs => internal/index}/indexsorted.go (90%) rename ipld/car/v2/{carbon => internal/index}/insertionindex.go (57%) create mode 100644 ipld/car/v2/internal/io/cid.go rename ipld/car/v2/{ => internal/io}/offset_reader.go (71%) create mode 100644 ipld/car/v2/internal/io/offset_writer.go diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go new file mode 100644 index 0000000000..57a1e0f760 --- /dev/null +++ b/ipld/car/v2/blockstore/readonly.go @@ -0,0 +1,189 @@ +package blockstore + +import ( + "bufio" + "context" + "encoding/binary" + "errors" + "fmt" + "io" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + blockstore "github.com/ipfs/go-ipfs-blockstore" + carv1 "github.com/ipld/go-car" + "github.com/ipld/go-car/util" + "github.com/ipld/go-car/v2/internal/index" + internalio "github.com/ipld/go-car/v2/internal/io" + "golang.org/x/exp/mmap" +) + +var _ blockstore.Blockstore = (*ReadOnly)(nil) + +// errUnsupported is returned for unsupported operations +var errUnsupported = errors.New("unsupported operation") + +// ReadOnly provides a read-only Car Block Store. +type ReadOnly struct { + backing io.ReaderAt + idx index.Index +} + +// ReadOnlyOf opens a carbs data store from an existing reader of the base data and index +func ReadOnlyOf(backing io.ReaderAt, index index.Index) *ReadOnly { + return &ReadOnly{backing, index} +} + +// LoadReadOnly opens a read-only blockstore, generating an index if it does not exist +func LoadReadOnly(path string, noPersist bool) (*ReadOnly, error) { + reader, err := mmap.Open(path) + if err != nil { + return nil, err + } + idx, err := index.Restore(path) + if err != nil { + idx, err = index.GenerateIndex(reader, 0, index.IndexSorted) + if err != nil { + return nil, err + } + if !noPersist { + if err = index.Save(idx, path); err != nil { + return nil, err + } + } + } + obj := ReadOnly{ + backing: reader, + idx: idx, + } + return &obj, nil +} + +func (b *ReadOnly) read(idx int64) (cid.Cid, []byte, error) { + bcid, data, err := util.ReadNode(bufio.NewReader(internalio.NewOffsetReader(b.backing, idx))) + return bcid, data, err +} + +// DeleteBlock is unsupported and always returns an error +func (b *ReadOnly) DeleteBlock(_ cid.Cid) error { + return errUnsupported +} + +// Has indicates if the store has a cid +func (b *ReadOnly) Has(key cid.Cid) (bool, error) { + offset, err := b.idx.Get(key) + if err != nil { + return false, err + } + uar := internalio.NewOffsetReader(b.backing, int64(offset)) + _, err = binary.ReadUvarint(uar) + if err != nil { + return false, err + } + c, _, err := internalio.ReadCid(b.backing, uar.Offset()) + if err != nil { + return false, err + } + return c.Equals(key), nil +} + +// Get gets a block from the store +func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { + offset, err := b.idx.Get(key) + if err != nil { + return nil, err + } + entry, bytes, err := b.read(int64(offset)) + if err != nil { + // TODO Improve error handling; not all errors mean NotFound. + return nil, blockstore.ErrNotFound + } + if !entry.Equals(key) { + return nil, blockstore.ErrNotFound + } + return blocks.NewBlockWithCid(bytes, key) +} + +// GetSize gets how big a item is +func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { + idx, err := b.idx.Get(key) + if err != nil { + return -1, err + } + l, err := binary.ReadUvarint(internalio.NewOffsetReader(b.backing, int64(idx))) + if err != nil { + return -1, blockstore.ErrNotFound + } + c, _, err := internalio.ReadCid(b.backing, int64(idx+l)) + if err != nil { + return 0, err + } + if !c.Equals(key) { + return -1, blockstore.ErrNotFound + } + // get cid. validate. + return int(l), err +} + +// Put is not supported and always returns an error +func (b *ReadOnly) Put(blocks.Block) error { + return errUnsupported +} + +// PutMany is not supported and always returns an error +func (b *ReadOnly) PutMany([]blocks.Block) error { + return errUnsupported +} + +// AllKeysChan returns the list of keys in the store +func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + // TODO we may use this walk for populating the index, and we need to be able to iterate keys in this way somewhere for index generation. In general though, when it's asked for all keys from a blockstore with an index, we should iterate through the index when possible rather than linear reads through the full car. + header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(b.backing, 0))) + if err != nil { + return nil, fmt.Errorf("error reading car header: %w", err) + } + offset, err := carv1.HeaderSize(header) + if err != nil { + return nil, err + } + + ch := make(chan cid.Cid, 5) + go func() { + done := ctx.Done() + + rdr := internalio.NewOffsetReader(b.backing, int64(offset)) + for { + l, err := binary.ReadUvarint(rdr) + thisItemForNxt := rdr.Offset() + if err != nil { + return + } + c, _, err := internalio.ReadCid(b.backing, thisItemForNxt) + if err != nil { + return + } + rdr.SeekOffset(thisItemForNxt + int64(l)) + + select { + case ch <- c: + continue + case <-done: + return + } + } + }() + return ch, nil +} + +// HashOnRead does nothing +func (b *ReadOnly) HashOnRead(bool) { +} + +// Roots returns the root CIDs of the backing car +func (b *ReadOnly) Roots() ([]cid.Cid, error) { + header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(b.backing, 0))) + if err != nil { + return nil, fmt.Errorf("error reading car header: %w", err) + } + return header.Roots, nil +} diff --git a/ipld/car/v2/carbs/carbs_test.go b/ipld/car/v2/blockstore/readonly_test.go similarity index 92% rename from ipld/car/v2/carbs/carbs_test.go rename to ipld/car/v2/blockstore/readonly_test.go index 2aa48fe708..a2d38a2825 100644 --- a/ipld/car/v2/carbs/carbs_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -1,6 +1,7 @@ -package carbs +package blockstore import ( + "github.com/ipld/go-car/v2/internal/index" "os" "testing" ) @@ -54,7 +55,7 @@ func TestIndexRT(t *testing.T) { // TODO use temporary directory to run tests taht work with OS file system to avoid accidental source code modification carFile := "testdata/test.car" - cf, err := Load(carFile, false) + cf, err := LoadReadOnly(carFile, false) if err != nil { t.Fatal(err) } @@ -71,7 +72,7 @@ func TestIndexRT(t *testing.T) { t.Fatalf("failed get: %v", err) } - idx, err := Restore(carFile) + idx, err := index.Restore(carFile) if err != nil { t.Fatalf("failed restore: %v", err) } diff --git a/ipld/car/v2/carbs/testdata/test.car b/ipld/car/v2/blockstore/testdata/test.car similarity index 100% rename from ipld/car/v2/carbs/testdata/test.car rename to ipld/car/v2/blockstore/testdata/test.car diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index 99c5fa7d99..8dc30a8a44 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -12,7 +12,6 @@ const ( HeaderSize = 40 // CharacteristicsSize is the fixed size of Characteristics bitfield within CAR v2 header in number of bytes. CharacteristicsSize = 16 - uint64Size = 8 ) var ( @@ -45,17 +44,13 @@ type ( } ) -// WriteTo writes this characteristics to the given writer. +// WriteTo writes this characteristics to the given w. func (c Characteristics) WriteTo(w io.Writer) (n int64, err error) { - if err = binary.Write(w, binary.LittleEndian, c.Hi); err != nil { - return - } - n += uint64Size - if err = binary.Write(w, binary.LittleEndian, c.Lo); err != nil { - return - } - n += uint64Size - return + buf := make([]byte, 16) + binary.LittleEndian.PutUint64(buf[:8], c.Hi) + binary.LittleEndian.PutUint64(buf[8:], c.Lo) + written, err := w.Write(buf) + return int64(written), err } func (c *Characteristics) ReadFrom(r io.Reader) (int64, error) { @@ -65,8 +60,8 @@ func (c *Characteristics) ReadFrom(r io.Reader) (int64, error) { if err != nil { return n, err } - c.Hi = binary.LittleEndian.Uint64(buf[:uint64Size]) - c.Lo = binary.LittleEndian.Uint64(buf[uint64Size:]) + c.Hi = binary.LittleEndian.Uint64(buf[:8]) + c.Lo = binary.LittleEndian.Uint64(buf[8:]) return n, nil } @@ -101,25 +96,18 @@ func (h Header) WithCarV1Padding(padding uint64) Header { // WriteTo serializes this header as bytes and writes them using the given io.Writer. func (h Header) WriteTo(w io.Writer) (n int64, err error) { - // TODO optimize write by encoding all bytes in a slice and writing once. wn, err := h.Characteristics.WriteTo(w) - if err != nil { - return - } n += wn - if err = binary.Write(w, binary.LittleEndian, h.CarV1Offset); err != nil { - return - } - n += uint64Size - if err = binary.Write(w, binary.LittleEndian, h.CarV1Size); err != nil { - return - } - n += uint64Size - if err = binary.Write(w, binary.LittleEndian, h.IndexOffset); err != nil { + if err != nil { return } - n += uint64Size - return + buf := make([]byte, 24) + binary.LittleEndian.PutUint64(buf[:8], h.CarV1Offset) + binary.LittleEndian.PutUint64(buf[8:16], h.CarV1Size) + binary.LittleEndian.PutUint64(buf[16:], h.IndexOffset) + written, err := w.Write(buf) + n += int64(written) + return n, err } // ReadFrom populates fields of this header from the given r. @@ -128,16 +116,14 @@ func (h *Header) ReadFrom(r io.Reader) (int64, error) { if err != nil { return n, err } - remainingSize := HeaderSize - CharacteristicsSize - buf := make([]byte, remainingSize) + buf := make([]byte, 24) read, err := io.ReadFull(r, buf) n += int64(read) if err != nil { return n, err } - carV1RelOffset := uint64Size * 2 - h.CarV1Offset = binary.LittleEndian.Uint64(buf[:uint64Size]) - h.CarV1Size = binary.LittleEndian.Uint64(buf[uint64Size:carV1RelOffset]) - h.IndexOffset = binary.LittleEndian.Uint64(buf[carV1RelOffset:]) + h.CarV1Offset = binary.LittleEndian.Uint64(buf[:8]) + h.CarV1Size = binary.LittleEndian.Uint64(buf[8:16]) + h.IndexOffset = binary.LittleEndian.Uint64(buf[16:]) return n, nil } diff --git a/ipld/car/v2/carbon/reader.go b/ipld/car/v2/carbon/reader.go deleted file mode 100644 index 4e37554917..0000000000 --- a/ipld/car/v2/carbon/reader.go +++ /dev/null @@ -1,23 +0,0 @@ -package carbon - -import "io" - -//lint:ignore U1000 The entire carbon package will be reviewed; this is temporary -type unatreader struct { - io.ReaderAt - at int64 -} - -//lint:ignore U1000 The entire carbon package will be reviewed; this is temporary -func (u *unatreader) Read(p []byte) (n int, err error) { - n, err = u.ReadAt(p, u.at) - u.at = u.at + int64(n) - return -} - -//lint:ignore U1000 The entire carbon package will be reviewed; this is temporary -func (u *unatreader) ReadByte() (byte, error) { - b := []byte{0} - _, err := u.Read(b) - return b[0], err -} diff --git a/ipld/car/v2/carbs/carbs.go b/ipld/car/v2/carbs/carbs.go deleted file mode 100644 index 6891d925d4..0000000000 --- a/ipld/car/v2/carbs/carbs.go +++ /dev/null @@ -1,301 +0,0 @@ -package carbs - -import ( - "bufio" - "bytes" - "context" - "encoding/binary" - "fmt" - "io" - - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" - "github.com/multiformats/go-multihash" - - pb "github.com/cheggaaa/pb/v3" - carv1 "github.com/ipld/go-car" - "github.com/ipld/go-car/util" - "golang.org/x/exp/mmap" -) - -var errNotFound = blockstore.ErrNotFound - -// BlockStore provides a read-only Car Block Store. -type BlockStore struct { - backing io.ReaderAt - idx Index -} - -var _ blockstore.Blockstore = (*BlockStore)(nil) - -func (b *BlockStore) Read(idx int64) (cid.Cid, []byte, error) { - bcid, data, err := util.ReadNode(bufio.NewReader(&unatreader{b.backing, idx})) - return bcid, data, err -} - -// DeleteBlock doesn't delete a block on RO blockstore -func (b *BlockStore) DeleteBlock(_ cid.Cid) error { - return fmt.Errorf("read only") -} - -// Has indicates if the store has a cid -func (b *BlockStore) Has(key cid.Cid) (bool, error) { - offset, err := b.idx.Get(key) - if err != nil { - return false, err - } - uar := unatreader{b.backing, int64(offset)} - _, err = binary.ReadUvarint(&uar) - if err != nil { - return false, err - } - cid, _, err := readCid(b.backing, uar.at) - if err != nil { - return false, err - } - return cid.Equals(key), nil -} - -var cidv0Pref = []byte{0x12, 0x20} - -func readCid(store io.ReaderAt, at int64) (cid.Cid, int, error) { - var tag [2]byte - if _, err := store.ReadAt(tag[:], at); err != nil { - return cid.Undef, 0, err - } - if bytes.Equal(tag[:], cidv0Pref) { - cid0 := make([]byte, 34) - if _, err := store.ReadAt(cid0, at); err != nil { - return cid.Undef, 0, err - } - c, err := cid.Cast(cid0) - return c, 34, err - } - - // assume cidv1 - br := &unatreader{store, at} - vers, err := binary.ReadUvarint(br) - if err != nil { - return cid.Cid{}, 0, err - } - - // TODO: the go-cid package allows version 0 here as well - if vers != 1 { - return cid.Cid{}, 0, fmt.Errorf("invalid cid version number: %d", vers) - } - - codec, err := binary.ReadUvarint(br) - if err != nil { - return cid.Cid{}, 0, err - } - - mhr := multihash.NewReader(br) - h, err := mhr.ReadMultihash() - if err != nil { - return cid.Cid{}, 0, err - } - - return cid.NewCidV1(codec, h), int(br.at - at), nil -} - -// Get gets a block from the store -func (b *BlockStore) Get(key cid.Cid) (blocks.Block, error) { - offset, err := b.idx.Get(key) - if err != nil { - return nil, err - } - entry, bytes, err := b.Read(int64(offset)) - if err != nil { - // TODO replace with logging - fmt.Printf("failed get %d:%v\n", offset, err) - return nil, blockstore.ErrNotFound - } - if !entry.Equals(key) { - return nil, blockstore.ErrNotFound - } - return blocks.NewBlockWithCid(bytes, key) -} - -// GetSize gets how big a item is -func (b *BlockStore) GetSize(key cid.Cid) (int, error) { - idx, err := b.idx.Get(key) - if err != nil { - return -1, err - } - len, err := binary.ReadUvarint(&unatreader{b.backing, int64(idx)}) - if err != nil { - return -1, blockstore.ErrNotFound - } - cid, _, err := readCid(b.backing, int64(idx+len)) - if err != nil { - return 0, err - } - if !cid.Equals(key) { - return -1, blockstore.ErrNotFound - } - // get cid. validate. - return int(len), err -} - -// Put does nothing on a ro store -func (b *BlockStore) Put(blocks.Block) error { - return fmt.Errorf("read only") -} - -// PutMany does nothing on a ro store -func (b *BlockStore) PutMany([]blocks.Block) error { - return fmt.Errorf("read only") -} - -// AllKeysChan returns the list of keys in the store -func (b *BlockStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{b.backing, 0})) - if err != nil { - return nil, fmt.Errorf("error reading car header: %w", err) - } - offset, err := carv1.HeaderSize(header) - if err != nil { - return nil, err - } - - ch := make(chan cid.Cid, 5) - go func() { - done := ctx.Done() - - rdr := unatreader{b.backing, int64(offset)} - for { - l, err := binary.ReadUvarint(&rdr) - thisItemForNxt := rdr.at - if err != nil { - return - } - c, _, err := readCid(b.backing, thisItemForNxt) - if err != nil { - return - } - rdr.at = thisItemForNxt + int64(l) - - select { - case ch <- c: - continue - case <-done: - return - } - } - }() - return ch, nil -} - -// HashOnRead does nothing -func (b *BlockStore) HashOnRead(bool) { -} - -// Roots returns the root CIDs of the backing car -func (b *BlockStore) Roots() ([]cid.Cid, error) { - header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{b.backing, 0})) - if err != nil { - return nil, fmt.Errorf("error reading car header: %w", err) - } - return header.Roots, nil -} - -// Load opens a carbs data store, generating an index if it does not exist -func Load(path string, noPersist bool) (*BlockStore, error) { - reader, err := mmap.Open(path) - if err != nil { - return nil, err - } - idx, err := Restore(path) - if err != nil { - idx, err = GenerateIndex(reader, 0, IndexSorted, false) - if err != nil { - return nil, err - } - if !noPersist { - if err = Save(idx, path); err != nil { - return nil, err - } - } - } - obj := BlockStore{ - backing: reader, - idx: idx, - } - return &obj, nil -} - -// Of opens a carbs data store from an existing reader of the base data and index -func Of(backing io.ReaderAt, index Index) *BlockStore { - return &BlockStore{backing, index} -} - -// GenerateIndex provides a low-level interface to create an index over a -// reader to a car stream. -func GenerateIndex(store io.ReaderAt, size int64, codec IndexCodec, verbose bool) (Index, error) { - indexcls, ok := IndexAtlas[codec] - if !ok { - return nil, fmt.Errorf("unknown codec: %#v", codec) - } - - bar := pb.New64(size) - bar.Set(pb.Bytes, true) - bar.Set(pb.Terminal, true) - - bar.Start() - - header, err := carv1.ReadHeader(bufio.NewReader(&unatreader{store, 0})) - if err != nil { - return nil, fmt.Errorf("error reading car header: %w", err) - } - offset, err := carv1.HeaderSize(header) - if err != nil { - return nil, err - } - bar.Add64(int64(offset)) - - index := indexcls() - - records := make([]Record, 0) - rdr := unatreader{store, int64(offset)} - for { - thisItemIdx := rdr.at - l, err := binary.ReadUvarint(&rdr) - bar.Add64(int64(l)) - thisItemForNxt := rdr.at - if err != nil { - if err == io.EOF { - break - } - return nil, err - } - c, _, err := readCid(store, thisItemForNxt) - if err != nil { - return nil, err - } - records = append(records, Record{c, uint64(thisItemIdx)}) - rdr.at = thisItemForNxt + int64(l) - } - - if err := index.Load(records); err != nil { - return nil, err - } - - bar.Finish() - - return index, nil -} - -// Generate walks a car file and generates an index of cid->byte offset in it. -func Generate(path string, codec IndexCodec) error { - store, err := mmap.Open(path) - if err != nil { - return err - } - idx, err := GenerateIndex(store, 0, codec, false) - if err != nil { - return err - } - - return Save(idx, path) -} diff --git a/ipld/car/v2/carbs/reader.go b/ipld/car/v2/carbs/reader.go deleted file mode 100644 index 8accf0a842..0000000000 --- a/ipld/car/v2/carbs/reader.go +++ /dev/null @@ -1,20 +0,0 @@ -package carbs - -import "io" - -type unatreader struct { - io.ReaderAt - at int64 -} - -func (u *unatreader) Read(p []byte) (n int, err error) { - n, err = u.ReadAt(p, u.at) - u.at = u.at + int64(n) - return -} - -func (u *unatreader) ReadByte() (byte, error) { - b := []byte{0} - _, err := u.Read(b) - return b[0], err -} diff --git a/ipld/car/v2/carbon/carbon.go b/ipld/car/v2/internal/carbon/carbon.go similarity index 73% rename from ipld/car/v2/carbon/carbon.go rename to ipld/car/v2/internal/carbon/carbon.go index 3ba853208f..4394f78878 100644 --- a/ipld/car/v2/carbon/carbon.go +++ b/ipld/car/v2/internal/carbon/carbon.go @@ -3,12 +3,13 @@ package carbon import ( "errors" "fmt" - "github.com/ipld/go-car/v2/carbs" + carblockstore "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-car/v2/internal/index" "os" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" - "github.com/ipld/go-car" + carv1 "github.com/ipld/go-car" ) // Carbon is a carbs-index-compatible blockstore supporting appending additional blocks @@ -21,9 +22,6 @@ type Carbon interface { // errUnsupported is returned for unsupported blockstore operations (like delete) var errUnsupported = errors.New("unsupported by carbon") -// errNotFound is returned for lookups to entries that don't exist -var errNotFound = errors.New("not found") - // New creates a new Carbon blockstore func New(path string) (Carbon, error) { return NewWithRoots(path, []cid.Cid{}) @@ -40,21 +38,26 @@ func NewWithRoots(path string, roots []cid.Cid) (Carbon, error) { return nil, fmt.Errorf("could not re-open read handle: %w", err) } - hdr := car.CarHeader{ + hdr := carv1.CarHeader{ Roots: roots, Version: 1, } writer := poswriter{wfd, 0} - if err := car.WriteHeader(&hdr, &writer); err != nil { + if err := carv1.WriteHeader(&hdr, &writer); err != nil { return nil, fmt.Errorf("couldn't write car header: %w", err) } - idx := insertionIndex{} + indexcls, ok := index.IndexAtlas[index.IndexInsertion] + if !ok { + return nil, fmt.Errorf("unknownindex codec: %#v", index.IndexInsertion) + } + + idx := (indexcls()).(*index.InsertionIndex) f := carbonFD{ path, &writer, - *carbs.Of(rfd, &idx), - &idx, + *carblockstore.ReadOnlyOf(rfd, idx), + idx, } return &f, nil } diff --git a/ipld/car/v2/carbon/carbon_fds.go b/ipld/car/v2/internal/carbon/carbon_fds.go similarity index 86% rename from ipld/car/v2/carbon/carbon_fds.go rename to ipld/car/v2/internal/carbon/carbon_fds.go index 856eb9bb32..000379a83d 100644 --- a/ipld/car/v2/carbon/carbon_fds.go +++ b/ipld/car/v2/internal/carbon/carbon_fds.go @@ -1,7 +1,8 @@ package carbon import ( - "github.com/ipld/go-car/v2/carbs" + "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-car/v2/internal/index" "os" blocks "github.com/ipfs/go-block-format" @@ -14,8 +15,8 @@ import ( type carbonFD struct { path string writeHandle *poswriter - carbs.BlockStore - idx *insertionIndex + blockstore.ReadOnly + idx *index.InsertionIndex } var _ (Carbon) = (*carbonFD)(nil) @@ -37,7 +38,7 @@ func (c *carbonFD) PutMany(b []blocks.Block) error { if err := util.LdWrite(c.writeHandle, bl.Cid().Bytes(), bl.RawData()); err != nil { return err } - c.idx.items.InsertNoReplace(mkRecordFromCid(bl.Cid(), n)) + c.idx.InsertNoReplace(bl.Cid(), n) } return nil } @@ -54,10 +55,10 @@ func (c *carbonFD) Finish() error { return err } } - return carbs.Save(fi, c.path) + return index.Save(fi, c.path) } // Checkpoint serializes the carbon index so that the partially written blockstore can be resumed. func (c *carbonFD) Checkpoint() error { - return carbs.Save(c.idx, c.path) + return index.Save(c.idx, c.path) } diff --git a/ipld/car/v2/carbon/carbon_test.go b/ipld/car/v2/internal/carbon/carbon_test.go similarity index 91% rename from ipld/car/v2/carbon/carbon_test.go rename to ipld/car/v2/internal/carbon/carbon_test.go index a3c7d6d98e..a7cc3de407 100644 --- a/ipld/car/v2/carbon/carbon_test.go +++ b/ipld/car/v2/internal/carbon/carbon_test.go @@ -1,8 +1,8 @@ package carbon_test import ( - "github.com/ipld/go-car/v2/carbon" - "github.com/ipld/go-car/v2/carbs" + "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-car/v2/internal/carbon" "io" "math/rand" "os" @@ -75,7 +75,7 @@ func TestCarbon(t *testing.T) { t.Fatalf("index not written: %v", stat) } - carb, err := carbs.Load("testcarbon.car", true) + carb, err := blockstore.LoadReadOnly("testcarbon.car", true) if err != nil { t.Fatal(err) } diff --git a/ipld/car/v2/carbon/doc.go b/ipld/car/v2/internal/carbon/doc.go similarity index 92% rename from ipld/car/v2/carbon/doc.go rename to ipld/car/v2/internal/carbon/doc.go index a1ce3cc6ac..e620f4d000 100644 --- a/ipld/car/v2/carbon/doc.go +++ b/ipld/car/v2/internal/carbon/doc.go @@ -2,4 +2,5 @@ // A carbs index for the resulting file is tracked and can be saved as needed. // Note, carbon does not support deletion. // See: https://github.com/ipfs/go-ipfs-blockstore +// TODO to be refactored package carbon diff --git a/ipld/car/v2/carbon/poswriter.go b/ipld/car/v2/internal/carbon/poswriter.go similarity index 100% rename from ipld/car/v2/carbon/poswriter.go rename to ipld/car/v2/internal/carbon/poswriter.go diff --git a/ipld/car/v2/carbs/doc.go b/ipld/car/v2/internal/carbs/doc.go similarity index 81% rename from ipld/car/v2/carbs/doc.go rename to ipld/car/v2/internal/carbs/doc.go index bcff12ac39..099875379b 100644 --- a/ipld/car/v2/carbs/doc.go +++ b/ipld/car/v2/internal/carbs/doc.go @@ -1,2 +1,3 @@ // Package carbs provides a read-only blockstore interface directly reading out of a car file. +// TODO to be refactored package carbs diff --git a/ipld/car/v2/carbs/util/hydrate.go b/ipld/car/v2/internal/carbs/util/hydrate.go similarity index 73% rename from ipld/car/v2/carbs/util/hydrate.go rename to ipld/car/v2/internal/carbs/util/hydrate.go index d0fb50ae17..e0a39f930a 100644 --- a/ipld/car/v2/carbs/util/hydrate.go +++ b/ipld/car/v2/internal/carbs/util/hydrate.go @@ -2,10 +2,9 @@ package main import ( "fmt" - "os" - - "github.com/ipld/go-car/v2/carbs" + "github.com/ipld/go-car/v2/internal/index" "golang.org/x/exp/mmap" + "os" ) func main() { @@ -14,12 +13,12 @@ func main() { return } db := os.Args[1] - codec := carbs.IndexSorted + codec := index.IndexSorted if len(os.Args) == 3 { if os.Args[2] == "Hash" { - codec = carbs.IndexHashed + codec = index.IndexHashed } else if os.Args[2] == "GobHash" { - codec = carbs.IndexGobHashed + codec = index.IndexGobHashed } } @@ -35,7 +34,7 @@ func main() { return } - idx, err := carbs.GenerateIndex(dbBacking, dbstat.Size(), codec, true) + idx, err := index.GenerateIndex(dbBacking, dbstat.Size(), codec) if err != nil { fmt.Printf("Error generating index: %v\n", err) return @@ -43,7 +42,7 @@ func main() { fmt.Printf("Saving...\n") - if err := carbs.Save(idx, db); err != nil { + if err := index.Save(idx, db); err != nil { fmt.Printf("Error saving : %v\n", err) } } diff --git a/ipld/car/v2/internal/index/errors.go b/ipld/car/v2/internal/index/errors.go new file mode 100644 index 0000000000..c45c24020b --- /dev/null +++ b/ipld/car/v2/internal/index/errors.go @@ -0,0 +1,9 @@ +package index + +import "errors" + +var ( + // errNotFound is returned for lookups to entries that don't exist + errNotFound = errors.New("not found") + errUnsupported = errors.New("not supported") +) diff --git a/ipld/car/v2/internal/index/generator.go b/ipld/car/v2/internal/index/generator.go new file mode 100644 index 0000000000..18e25edfb9 --- /dev/null +++ b/ipld/car/v2/internal/index/generator.go @@ -0,0 +1,81 @@ +package index + +import ( + "bufio" + "encoding/binary" + "fmt" + "github.com/cheggaaa/pb/v3" + carv1 "github.com/ipld/go-car" + internalio "github.com/ipld/go-car/v2/internal/io" + "golang.org/x/exp/mmap" + "io" +) + +// GenerateIndex provides a low-level interface to create an index over a +// reader to a car stream. +func GenerateIndex(store io.ReaderAt, size int64, codec Codec) (Index, error) { + indexcls, ok := IndexAtlas[codec] + if !ok { + return nil, fmt.Errorf("unknown codec: %#v", codec) + } + + bar := pb.New64(size) + bar.Set(pb.Bytes, true) + bar.Set(pb.Terminal, true) + + bar.Start() + + header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(store, 0))) + if err != nil { + return nil, fmt.Errorf("error reading car header: %w", err) + } + offset, err := carv1.HeaderSize(header) + if err != nil { + return nil, err + } + bar.Add64(int64(offset)) + + idx := indexcls() + + records := make([]Record, 0) + rdr := internalio.NewOffsetReader(store, int64(offset)) + for { + thisItemIdx := rdr.Offset() + l, err := binary.ReadUvarint(rdr) + bar.Add64(int64(l)) + thisItemForNxt := rdr.Offset() + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + c, _, err := internalio.ReadCid(store, thisItemForNxt) + if err != nil { + return nil, err + } + records = append(records, Record{c, uint64(thisItemIdx)}) + rdr.SeekOffset(thisItemForNxt + int64(l)) + } + + if err := idx.Load(records); err != nil { + return nil, err + } + + bar.Finish() + + return idx, nil +} + +// Generate walks a car file and generates an index of cid->byte offset in it. +func Generate(path string, codec Codec) error { + store, err := mmap.Open(path) + if err != nil { + return err + } + idx, err := GenerateIndex(store, 0, codec) + if err != nil { + return err + } + return Save(idx, path) +} diff --git a/ipld/car/v2/carbs/index.go b/ipld/car/v2/internal/index/index.go similarity index 55% rename from ipld/car/v2/carbs/index.go rename to ipld/car/v2/internal/index/index.go index 1f381308c8..c0afdf3091 100644 --- a/ipld/car/v2/carbs/index.go +++ b/ipld/car/v2/internal/index/index.go @@ -1,50 +1,54 @@ -package carbs +package index import ( "encoding/binary" "fmt" - "io" - "os" - "github.com/ipfs/go-cid" + internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" + "io" + "os" ) -// IndexCodec is used as a multicodec identifier for carbs index files -type IndexCodec int - -// IndexCodec table is a first var-int in carbs indexes +// Codec table is a first var-int in carbs indexes const ( - IndexHashed IndexCodec = iota + 0x300000 + IndexHashed Codec = iota + 0x300000 IndexSorted IndexSingleSorted IndexGobHashed + IndexInsertion ) -// IndexCls is a constructor for an index type -type IndexCls func() Index +type ( + // Codec is used as a multicodec identifier for carbs index files + Codec int + + // IndexCls is a constructor for an index type + IndexCls func() Index + + // Record is a pre-processed record of a car item and location. + Record struct { + cid.Cid + Idx uint64 + } + + // Index provides an interface for figuring out where in the car a given cid begins + Index interface { + Codec() Codec + Marshal(w io.Writer) error + Unmarshal(r io.Reader) error + Get(cid.Cid) (uint64, error) + Load([]Record) error + } +) // IndexAtlas holds known index formats -var IndexAtlas = map[IndexCodec]IndexCls{ +var IndexAtlas = map[Codec]IndexCls{ IndexHashed: mkHashed, IndexSorted: mkSorted, IndexSingleSorted: mkSingleSorted, IndexGobHashed: mkGobHashed, -} - -// Record is a pre-processed record of a car item and location. -type Record struct { - cid.Cid - Idx uint64 -} - -// Index provides an interface for figuring out where in the car a given cid begins -type Index interface { - Codec() IndexCodec - Marshal(w io.Writer) error - Unmarshal(r io.Reader) error - Get(cid.Cid) (uint64, error) - Load([]Record) error + IndexInsertion: mkInsertion, } // Save writes a generated index for a car at `path` @@ -71,17 +75,17 @@ func Restore(path string) (Index, error) { } defer reader.Close() - uar := unatreader{reader, 0} - codec, err := binary.ReadUvarint(&uar) + uar := internalio.NewOffsetReader(reader, 0) + codec, err := binary.ReadUvarint(uar) if err != nil { return nil, err } - idx, ok := IndexAtlas[IndexCodec(codec)] + idx, ok := IndexAtlas[Codec(codec)] if !ok { return nil, fmt.Errorf("unknown codec: %d", codec) } idxInst := idx() - if err := idxInst.Unmarshal(&uar); err != nil { + if err := idxInst.Unmarshal(uar); err != nil { return nil, err } diff --git a/ipld/car/v2/carbs/indexgobhash.go b/ipld/car/v2/internal/index/indexgobhash.go similarity index 91% rename from ipld/car/v2/carbs/indexgobhash.go rename to ipld/car/v2/internal/index/indexgobhash.go index b7dedba0a4..ce2768a9fe 100644 --- a/ipld/car/v2/carbs/indexgobhash.go +++ b/ipld/car/v2/internal/index/indexgobhash.go @@ -1,4 +1,4 @@ -package carbs +package index import ( "encoding/gob" @@ -27,7 +27,7 @@ func (m *mapGobIndex) Unmarshal(r io.Reader) error { return d.Decode(m) } -func (m *mapGobIndex) Codec() IndexCodec { +func (m *mapGobIndex) Codec() Codec { return IndexHashed } diff --git a/ipld/car/v2/carbs/indexhashed.go b/ipld/car/v2/internal/index/indexhashed.go similarity index 91% rename from ipld/car/v2/carbs/indexhashed.go rename to ipld/car/v2/internal/index/indexhashed.go index f4c04aed09..c64e5cd844 100644 --- a/ipld/car/v2/carbs/indexhashed.go +++ b/ipld/car/v2/internal/index/indexhashed.go @@ -1,4 +1,4 @@ -package carbs +package index import ( "io" @@ -26,7 +26,7 @@ func (m *mapIndex) Unmarshal(r io.Reader) error { return d.Decode(m) } -func (m *mapIndex) Codec() IndexCodec { +func (m *mapIndex) Codec() Codec { return IndexHashed } diff --git a/ipld/car/v2/carbs/indexsorted.go b/ipld/car/v2/internal/index/indexsorted.go similarity index 90% rename from ipld/car/v2/carbs/indexsorted.go rename to ipld/car/v2/internal/index/indexsorted.go index eca593c9f6..b14334d2c4 100644 --- a/ipld/car/v2/carbs/indexsorted.go +++ b/ipld/car/v2/internal/index/indexsorted.go @@ -1,4 +1,4 @@ -package carbs +package index import ( "bytes" @@ -11,18 +11,25 @@ import ( "github.com/multiformats/go-multihash" ) -type digestRecord struct { - digest []byte - index uint64 -} +type ( + digestRecord struct { + digest []byte + index uint64 + } + recordSet []digestRecord + singleWidthIndex struct { + width int32 + len int64 // in struct, len is #items. when marshaled, it's saved as #bytes. + index []byte + } + multiWidthIndex map[int32]singleWidthIndex +) func (d digestRecord) write(buf []byte) { n := copy(buf[:], d.digest) binary.LittleEndian.PutUint64(buf[n:], d.index) } -type recordSet []digestRecord - func (r recordSet) Len() int { return len(r) } @@ -35,13 +42,7 @@ func (r recordSet) Swap(i, j int) { r[i], r[j] = r[j], r[i] } -type singleWidthIndex struct { - width int32 - len int64 // in struct, len is #items. when marshaled, it's saved as #bytes. - index []byte -} - -func (s *singleWidthIndex) Codec() IndexCodec { +func (s *singleWidthIndex) Codec() Codec { return IndexSingleSorted } @@ -111,8 +112,6 @@ func (s *singleWidthIndex) Load(items []Record) error { return nil } -type multiWidthIndex map[int32]singleWidthIndex - func (m *multiWidthIndex) Get(c cid.Cid) (uint64, error) { d, err := multihash.Decode(c.Hash()) if err != nil { @@ -124,7 +123,7 @@ func (m *multiWidthIndex) Get(c cid.Cid) (uint64, error) { return 0, errNotFound } -func (m *multiWidthIndex) Codec() IndexCodec { +func (m *multiWidthIndex) Codec() Codec { return IndexSorted } diff --git a/ipld/car/v2/carbon/insertionindex.go b/ipld/car/v2/internal/index/insertionindex.go similarity index 57% rename from ipld/car/v2/carbon/insertionindex.go rename to ipld/car/v2/internal/index/insertionindex.go index 2e5d943a32..d64be077fc 100644 --- a/ipld/car/v2/carbon/insertionindex.go +++ b/ipld/car/v2/internal/index/insertionindex.go @@ -1,10 +1,9 @@ -package carbon +package index import ( "bytes" "encoding/binary" "fmt" - "github.com/ipld/go-car/v2/carbs" "io" "github.com/ipfs/go-cid" @@ -13,60 +12,56 @@ import ( cbor "github.com/whyrusleeping/cbor/go" ) -// IndexInsertion carbs IndexCodec identifier -const IndexInsertion carbs.IndexCodec = 0x300010 - -// init hook to register the index format -func init() { - carbs.IndexAtlas[IndexInsertion] = mkInsertion +type InsertionIndex struct { + items llrb.LLRB } -type insertionIndex struct { - items llrb.LLRB +func (ii *InsertionIndex) InsertNoReplace(key cid.Cid, n uint64) { + ii.items.InsertNoReplace(mkRecordFromCid(key, n)) } -type record struct { +type recordDigest struct { digest []byte - carbs.Record + Record } -func (r record) Less(than llrb.Item) bool { - other, ok := than.(record) +func (r recordDigest) Less(than llrb.Item) bool { + other, ok := than.(recordDigest) if !ok { return false } return bytes.Compare(r.digest, other.digest) < 0 } -func mkRecord(r carbs.Record) record { +func mkRecord(r Record) recordDigest { d, err := multihash.Decode(r.Hash()) if err != nil { - return record{} + return recordDigest{} } - return record{d.Digest, r} + return recordDigest{d.Digest, r} } -func mkRecordFromCid(c cid.Cid, at uint64) record { +func mkRecordFromCid(c cid.Cid, at uint64) recordDigest { d, err := multihash.Decode(c.Hash()) if err != nil { - return record{} + return recordDigest{} } - return record{d.Digest, carbs.Record{Cid: c, Idx: at}} + return recordDigest{d.Digest, Record{Cid: c, Idx: at}} } -func (ii *insertionIndex) Get(c cid.Cid) (uint64, error) { +func (ii *InsertionIndex) Get(c cid.Cid) (uint64, error) { d, err := multihash.Decode(c.Hash()) if err != nil { return 0, err } - entry := record{digest: d.Digest} + entry := recordDigest{digest: d.Digest} e := ii.items.Get(entry) if e == nil { return 0, errNotFound } - r, ok := e.(record) + r, ok := e.(recordDigest) if !ok { return 0, errUnsupported } @@ -74,7 +69,7 @@ func (ii *insertionIndex) Get(c cid.Cid) (uint64, error) { return r.Record.Idx, nil } -func (ii *insertionIndex) Marshal(w io.Writer) error { +func (ii *InsertionIndex) Marshal(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, int64(ii.items.Len())); err != nil { return err } @@ -82,7 +77,7 @@ func (ii *insertionIndex) Marshal(w io.Writer) error { var err error iter := func(i llrb.Item) bool { - if err = cbor.Encode(w, i.(record).Record); err != nil { + if err = cbor.Encode(w, i.(recordDigest).Record); err != nil { return false } return true @@ -91,14 +86,14 @@ func (ii *insertionIndex) Marshal(w io.Writer) error { return err } -func (ii *insertionIndex) Unmarshal(r io.Reader) error { +func (ii *InsertionIndex) Unmarshal(r io.Reader) error { var len int64 if err := binary.Read(r, binary.LittleEndian, &len); err != nil { return err } d := cbor.NewDecoder(r) for i := int64(0); i < len; i++ { - var rec carbs.Record + var rec Record if err := d.Decode(&rec); err != nil { return err } @@ -108,11 +103,11 @@ func (ii *insertionIndex) Unmarshal(r io.Reader) error { } // Codec identifies this index format -func (ii *insertionIndex) Codec() carbs.IndexCodec { +func (ii *InsertionIndex) Codec() Codec { return IndexInsertion } -func (ii *insertionIndex) Load(rs []carbs.Record) error { +func (ii *InsertionIndex) Load(rs []Record) error { for _, r := range rs { rec := mkRecord(r) if rec.digest == nil { @@ -123,19 +118,19 @@ func (ii *insertionIndex) Load(rs []carbs.Record) error { return nil } -func mkInsertion() carbs.Index { - ii := insertionIndex{} +func mkInsertion() Index { + ii := InsertionIndex{} return &ii } // Flatten returns a 'indexsorted' formatted index for more efficient subsequent loading -func (ii *insertionIndex) Flatten() (carbs.Index, error) { - si := carbs.IndexAtlas[carbs.IndexSorted]() - rcrds := make([]carbs.Record, ii.items.Len()) +func (ii *InsertionIndex) Flatten() (Index, error) { + si := IndexAtlas[IndexSorted]() + rcrds := make([]Record, ii.items.Len()) idx := 0 iter := func(i llrb.Item) bool { - rcrds[idx] = i.(record).Record + rcrds[idx] = i.(recordDigest).Record idx++ return true } diff --git a/ipld/car/v2/internal/io/cid.go b/ipld/car/v2/internal/io/cid.go new file mode 100644 index 0000000000..932b63bec0 --- /dev/null +++ b/ipld/car/v2/internal/io/cid.go @@ -0,0 +1,52 @@ +package io + +import ( + "bytes" + "encoding/binary" + "fmt" + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" + "io" +) + +var cidv0Pref = []byte{0x12, 0x20} + +func ReadCid(store io.ReaderAt, at int64) (cid.Cid, int, error) { + var tag [2]byte + if _, err := store.ReadAt(tag[:], at); err != nil { + return cid.Undef, 0, err + } + if bytes.Equal(tag[:], cidv0Pref) { + cid0 := make([]byte, 34) + if _, err := store.ReadAt(cid0, at); err != nil { + return cid.Undef, 0, err + } + c, err := cid.Cast(cid0) + return c, 34, err + } + + // assume cidv1 + br := NewOffsetReader(store, at) + vers, err := binary.ReadUvarint(br) + if err != nil { + return cid.Cid{}, 0, err + } + + // TODO: the go-cid package allows version 0 here as well + if vers != 1 { + return cid.Cid{}, 0, fmt.Errorf("invalid cid version number: %d", vers) + } + + codec, err := binary.ReadUvarint(br) + if err != nil { + return cid.Cid{}, 0, err + } + + mhr := multihash.NewReader(br) + h, err := mhr.ReadMultihash() + if err != nil { + return cid.Cid{}, 0, err + } + + return cid.NewCidV1(codec, h), int(br.Offset() - at), nil +} diff --git a/ipld/car/v2/offset_reader.go b/ipld/car/v2/internal/io/offset_reader.go similarity index 71% rename from ipld/car/v2/offset_reader.go rename to ipld/car/v2/internal/io/offset_reader.go index c0f09230f4..03f7e547f3 100644 --- a/ipld/car/v2/offset_reader.go +++ b/ipld/car/v2/internal/io/offset_reader.go @@ -1,4 +1,4 @@ -package car +package io import "io" @@ -15,7 +15,7 @@ type OffsetReader struct { } // NewOffsetReader returns an OffsetReader that reads from r -// starting at offset off and stops with io.EOF when r reaches its end. +// starting offset offset off and stops with io.EOF when r reaches its end. func NewOffsetReader(r io.ReaderAt, off int64) *OffsetReader { return &OffsetReader{r, off, off} } @@ -33,3 +33,17 @@ func (o *OffsetReader) ReadAt(p []byte, off int64) (n int, err error) { off += o.base return o.r.ReadAt(p, off) } + +func (o *OffsetReader) ReadByte() (byte, error) { + b := []byte{0} + _, err := o.Read(b) + return b[0], err +} + +func (o *OffsetReader) Offset() int64 { + return o.off +} + +func (o *OffsetReader) SeekOffset(off int64) { + o.off = off +} diff --git a/ipld/car/v2/internal/io/offset_writer.go b/ipld/car/v2/internal/io/offset_writer.go new file mode 100644 index 0000000000..495190a173 --- /dev/null +++ b/ipld/car/v2/internal/io/offset_writer.go @@ -0,0 +1,16 @@ +package io + +import "io" + +var _ io.Writer = (*offsetWriter)(nil) + +type offsetWriter struct { + wa io.WriterAt + offset int64 +} + +func (ow *offsetWriter) Write(b []byte) (n int, err error) { + n, err = ow.wa.WriteAt(b, ow.offset) + ow.offset += int64(n) + return +} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index b3c729e855..da8be152a4 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -3,6 +3,7 @@ package car import ( "bufio" "fmt" + internalio "github.com/ipld/go-car/v2/internal/io" "io" carv1 "github.com/ipld/go-car" @@ -57,5 +58,5 @@ func (r *Reader) CarV1ReaderAt() io.ReaderAt { // IndexReaderAt provides an io.ReaderAt containing the carbs.Index of this CAR v2. func (r *Reader) IndexReaderAt() io.ReaderAt { - return NewOffsetReader(r.r, int64(r.Header.IndexOffset)) + return internalio.NewOffsetReader(r.r, int64(r.Header.IndexOffset)) } diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index cd68c548fc..d49674c9e8 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -8,7 +8,7 @@ import ( "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" carv1 "github.com/ipld/go-car" - "github.com/ipld/go-car/v2/carbs" + "github.com/ipld/go-car/v2/internal/index" ) const bulkPaddingBytesSize = 1024 @@ -21,7 +21,7 @@ type ( // Writer writes CAR v2 into a give io.Writer. Writer struct { Walk carv1.WalkFunc - IndexCodec carbs.IndexCodec + IndexCodec index.Codec NodeGetter format.NodeGetter CarV1Padding uint64 IndexPadding uint64 @@ -30,6 +30,7 @@ type ( roots []cid.Cid encodedCarV1 *bytes.Buffer } + WriteOption func(*Writer) ) // WriteTo writes this padding to the given writer as default value bytes. @@ -60,7 +61,7 @@ func (p padding) WriteTo(w io.Writer) (n int64, err error) { func NewWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *Writer { return &Writer{ Walk: carv1.DefaultWalkFunc, - IndexCodec: carbs.IndexSorted, + IndexCodec: index.IndexSorted, NodeGetter: ng, ctx: ctx, roots: roots, @@ -129,7 +130,7 @@ func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (n int64, err error) // Consider refactoring carbs to make this process more efficient. // We should avoid reading the entire car into memory since it can be large. reader := bytes.NewReader(carV1) - index, err := carbs.GenerateIndex(reader, int64(len(carV1)), carbs.IndexSorted, true) + index, err := index.GenerateIndex(reader, int64(len(carV1)), index.IndexSorted) if err != nil { return } diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 7b9595d776..120f7efa70 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -7,7 +7,7 @@ import ( format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" - "github.com/ipld/go-car/v2/carbs" + "github.com/ipld/go-car/v2/internal/index" "github.com/stretchr/testify/assert" "testing" ) @@ -61,7 +61,7 @@ func TestNewWriter(t *testing.T) { dagService := dstest.Mock() wantRoots := generateRootCid(t, dagService) writer := NewWriter(context.Background(), dagService, wantRoots) - assert.Equal(t, carbs.IndexSorted, writer.IndexCodec) + assert.Equal(t, index.IndexSorted, writer.IndexCodec) assert.Equal(t, wantRoots, writer.roots) } From 0e7f7c51dbfac61241ebe77d757d1b06d8ed6640 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 21 Jun 2021 14:13:06 +0100 Subject: [PATCH 067/291] Run `gofumpt -l -w .` Format code in v2 consistently using `gofumpt -l -w .` This commit was moved from ipld/go-car@8b5f18d17d91ef92cfbcc797c96d09cf993a3e73 --- ipld/car/v2/blockstore/readonly_test.go | 3 ++- ipld/car/v2/car.go | 18 ++++++++---------- ipld/car/v2/car_test.go | 4 +++- ipld/car/v2/internal/carbon/carbon.go | 7 ++++--- ipld/car/v2/internal/carbon/carbon_fds.go | 3 ++- ipld/car/v2/internal/carbon/carbon_test.go | 5 +++-- ipld/car/v2/internal/carbs/util/hydrate.go | 3 ++- ipld/car/v2/internal/index/generator.go | 3 ++- ipld/car/v2/internal/index/index.go | 7 ++++--- ipld/car/v2/internal/io/cid.go | 3 ++- ipld/car/v2/reader.go | 3 ++- ipld/car/v2/writer_test.go | 3 ++- 12 files changed, 36 insertions(+), 26 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index a2d38a2825..2ceb11a26a 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -1,9 +1,10 @@ package blockstore import ( - "github.com/ipld/go-car/v2/internal/index" "os" "testing" + + "github.com/ipld/go-car/v2/internal/index" ) /* diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index 8dc30a8a44..92c01c7398 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -14,16 +14,14 @@ const ( CharacteristicsSize = 16 ) -var ( - // The fixed prefix of a CAR v2, signalling the version number to previous versions for graceful fail over. - PrefixBytes = []byte{ - 0x0a, // unit(10) - 0xa1, // map(1) - 0x67, // string(7) - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // "version" - 0x02, // uint(2) - } -) +// The fixed prefix of a CAR v2, signalling the version number to previous versions for graceful fail over. +var PrefixBytes = []byte{ + 0x0a, // unit(10) + 0xa1, // map(1) + 0x67, // string(7) + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // "version" + 0x02, // uint(2) +} type ( // Header represents the CAR v2 header/pragma. diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index 9a6a2b4238..e1795ad9d6 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -3,10 +3,11 @@ package car_test import ( "bufio" "bytes" + "testing" + carv1 "github.com/ipld/go-car" carv2 "github.com/ipld/go-car/v2" "github.com/stretchr/testify/assert" - "testing" ) func TestCarV2PrefixLength(t *testing.T) { @@ -99,6 +100,7 @@ func TestHeader_WriteTo(t *testing.T) { }) } } + func TestHeader_ReadFrom(t *testing.T) { tests := []struct { name string diff --git a/ipld/car/v2/internal/carbon/carbon.go b/ipld/car/v2/internal/carbon/carbon.go index 4394f78878..1de4d63620 100644 --- a/ipld/car/v2/internal/carbon/carbon.go +++ b/ipld/car/v2/internal/carbon/carbon.go @@ -3,9 +3,10 @@ package carbon import ( "errors" "fmt" + "os" + carblockstore "github.com/ipld/go-car/v2/blockstore" "github.com/ipld/go-car/v2/internal/index" - "os" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" @@ -29,11 +30,11 @@ func New(path string) (Carbon, error) { // NewWithRoots creates a new Carbon blockstore with a provided set of root cids as the car roots func NewWithRoots(path string, roots []cid.Cid) (Carbon, error) { - wfd, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) + wfd, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666) if err != nil { return nil, fmt.Errorf("couldn't create backing car: %w", err) } - rfd, err := os.OpenFile(path, os.O_RDONLY, 0666) + rfd, err := os.OpenFile(path, os.O_RDONLY, 0o666) if err != nil { return nil, fmt.Errorf("could not re-open read handle: %w", err) } diff --git a/ipld/car/v2/internal/carbon/carbon_fds.go b/ipld/car/v2/internal/carbon/carbon_fds.go index 000379a83d..c0be2254d5 100644 --- a/ipld/car/v2/internal/carbon/carbon_fds.go +++ b/ipld/car/v2/internal/carbon/carbon_fds.go @@ -1,9 +1,10 @@ package carbon import ( + "os" + "github.com/ipld/go-car/v2/blockstore" "github.com/ipld/go-car/v2/internal/index" - "os" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" diff --git a/ipld/car/v2/internal/carbon/carbon_test.go b/ipld/car/v2/internal/carbon/carbon_test.go index a7cc3de407..d2307abb97 100644 --- a/ipld/car/v2/internal/carbon/carbon_test.go +++ b/ipld/car/v2/internal/carbon/carbon_test.go @@ -1,13 +1,14 @@ package carbon_test import ( - "github.com/ipld/go-car/v2/blockstore" - "github.com/ipld/go-car/v2/internal/carbon" "io" "math/rand" "os" "testing" + "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-car/v2/internal/carbon" + "github.com/ipfs/go-cid" "github.com/ipld/go-car" ) diff --git a/ipld/car/v2/internal/carbs/util/hydrate.go b/ipld/car/v2/internal/carbs/util/hydrate.go index e0a39f930a..7329bb084f 100644 --- a/ipld/car/v2/internal/carbs/util/hydrate.go +++ b/ipld/car/v2/internal/carbs/util/hydrate.go @@ -2,9 +2,10 @@ package main import ( "fmt" + "os" + "github.com/ipld/go-car/v2/internal/index" "golang.org/x/exp/mmap" - "os" ) func main() { diff --git a/ipld/car/v2/internal/index/generator.go b/ipld/car/v2/internal/index/generator.go index 18e25edfb9..1d55708eab 100644 --- a/ipld/car/v2/internal/index/generator.go +++ b/ipld/car/v2/internal/index/generator.go @@ -4,11 +4,12 @@ import ( "bufio" "encoding/binary" "fmt" + "io" + "github.com/cheggaaa/pb/v3" carv1 "github.com/ipld/go-car" internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" - "io" ) // GenerateIndex provides a low-level interface to create an index over a diff --git a/ipld/car/v2/internal/index/index.go b/ipld/car/v2/internal/index/index.go index c0afdf3091..f4fd1b6fc9 100644 --- a/ipld/car/v2/internal/index/index.go +++ b/ipld/car/v2/internal/index/index.go @@ -3,11 +3,12 @@ package index import ( "encoding/binary" "fmt" + "io" + "os" + "github.com/ipfs/go-cid" internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" - "io" - "os" ) // Codec table is a first var-int in carbs indexes @@ -53,7 +54,7 @@ var IndexAtlas = map[Codec]IndexCls{ // Save writes a generated index for a car at `path` func Save(i Index, path string) error { - stream, err := os.OpenFile(path+".idx", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640) + stream, err := os.OpenFile(path+".idx", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) if err != nil { return err } diff --git a/ipld/car/v2/internal/io/cid.go b/ipld/car/v2/internal/io/cid.go index 932b63bec0..ee348e2578 100644 --- a/ipld/car/v2/internal/io/cid.go +++ b/ipld/car/v2/internal/io/cid.go @@ -4,9 +4,10 @@ import ( "bytes" "encoding/binary" "fmt" + "io" + "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" - "io" ) var cidv0Pref = []byte{0x12, 0x20} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index da8be152a4..975a47bf13 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -3,9 +3,10 @@ package car import ( "bufio" "fmt" - internalio "github.com/ipld/go-car/v2/internal/io" "io" + internalio "github.com/ipld/go-car/v2/internal/io" + carv1 "github.com/ipld/go-car" ) diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 120f7efa70..5d25a2f20b 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -3,13 +3,14 @@ package car import ( "bytes" "context" + "testing" + "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" "github.com/ipld/go-car/v2/internal/index" "github.com/stretchr/testify/assert" - "testing" ) func TestPadding_WriteTo(t *testing.T) { From 36ce87f5ac6fb2717cbdd8f47c40875d4d942a7f Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 21 Jun 2021 17:28:32 +0100 Subject: [PATCH 068/291] Rename Prefix to Pragma for consistency Pragma seems like a better name for the prefix bytes of a car v2. Rename it along with references to it in tests etc. This commit was moved from ipld/go-car@e6a626c458704cda43acc187762a02d29463b25a --- ipld/car/v2/car.go | 17 +++++++++-------- ipld/car/v2/car_test.go | 32 ++++++++++++++++---------------- ipld/car/v2/reader.go | 8 ++++---- ipld/car/v2/writer.go | 4 ++-- 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index 92c01c7398..6032daabf3 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -6,16 +6,17 @@ import ( ) const ( - // PrefixSize is the size of the CAR v2 prefix in 11 bytes, (i.e. 11). - PrefixSize = 11 + // PragmaSize is the size of the CAR v2 pragma in bytes. + PragmaSize = 11 // HeaderSize is the fixed size of CAR v2 header in number of bytes. HeaderSize = 40 // CharacteristicsSize is the fixed size of Characteristics bitfield within CAR v2 header in number of bytes. CharacteristicsSize = 16 ) -// The fixed prefix of a CAR v2, signalling the version number to previous versions for graceful fail over. -var PrefixBytes = []byte{ +// The pragma of a CAR v2, containing the version number.. +// This is a valid CAR v1 header, with version number set to 2. +var Pragma = []byte{ 0x0a, // unit(10) 0xa1, // map(1) 0x67, // string(7) @@ -68,15 +69,15 @@ func NewHeader(carV1Size uint64) Header { header := Header{ CarV1Size: carV1Size, } - header.CarV1Offset = PrefixSize + HeaderSize + header.CarV1Offset = PragmaSize + HeaderSize header.IndexOffset = header.CarV1Offset + carV1Size return header } // WithIndexPadding sets the index offset from the beginning of the file for this header and returns the // header for convenient chained calls. -// The index offset is calculated as the sum of PrefixBytesLen, HeaderBytesLen, -// Header.CarV1Len, and the given padding. +// The index offset is calculated as the sum of PragmaSize, HeaderSize, +// Header.CarV1Size, and the given padding. func (h Header) WithIndexPadding(padding uint64) Header { h.IndexOffset = h.IndexOffset + padding return h @@ -84,7 +85,7 @@ func (h Header) WithIndexPadding(padding uint64) Header { // WithCarV1Padding sets the CAR v1 dump offset from the beginning of the file for this header and returns the // header for convenient chained calls. -// The CAR v1 offset is calculated as the sum of PrefixBytesLen, HeaderBytesLen and the given padding. +// The CAR v1 offset is calculated as the sum of PragmaSize, HeaderSize and the given padding. // The call to this function also shifts the Header.IndexOffset forward by the given padding. func (h Header) WithCarV1Padding(padding uint64) Header { h.CarV1Offset = h.CarV1Offset + padding diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index e1795ad9d6..3a3606ff48 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCarV2PrefixLength(t *testing.T) { +func TestCarV2PragmaLength(t *testing.T) { tests := []struct { name string want interface{} @@ -19,29 +19,29 @@ func TestCarV2PrefixLength(t *testing.T) { { "ActualSizeShouldBe11", 11, - len(carv2.PrefixBytes), + len(carv2.Pragma), }, { "ShouldStartWithVarint(10)", - carv2.PrefixBytes[0], + carv2.Pragma[0], 10, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - assert.EqualValues(t, tt.want, tt.got, "CarV2Prefix got = %v, want %v", tt.got, tt.want) + assert.EqualValues(t, tt.want, tt.got, "CarV2Pragma got = %v, want %v", tt.got, tt.want) }) } } -func TestCarV2PrefixIsValidCarV1Header(t *testing.T) { - v1h, err := carv1.ReadHeader(bufio.NewReader(bytes.NewReader(carv2.PrefixBytes))) - assert.NoError(t, err, "cannot decode prefix as CBOR with CAR v1 header structure") +func TestCarV2PragmaIsValidCarV1Header(t *testing.T) { + v1h, err := carv1.ReadHeader(bufio.NewReader(bytes.NewReader(carv2.Pragma))) + assert.NoError(t, err, "cannot decode pragma as CBOR with CAR v1 header structure") assert.Equal(t, &carv1.CarHeader{ Roots: nil, Version: 2, - }, v1h, "CAR v2 prefix must be a valid CAR v1 header") + }, v1h, "CAR v2 pragma must be a valid CAR v1 header") } func TestHeader_WriteTo(t *testing.T) { @@ -164,20 +164,20 @@ func TestHeader_WithPadding(t *testing.T) { { "WhenNoPaddingOffsetsAreSumOfSizes", carv2.NewHeader(123), - carv2.PrefixSize + carv2.HeaderSize, - carv2.PrefixSize + carv2.HeaderSize + 123, + carv2.PragmaSize + carv2.HeaderSize, + carv2.PragmaSize + carv2.HeaderSize + 123, }, { "WhenOnlyPaddingCarV1BothOffsetsShift", carv2.NewHeader(123).WithCarV1Padding(3), - carv2.PrefixSize + carv2.HeaderSize + 3, - carv2.PrefixSize + carv2.HeaderSize + 3 + 123, + carv2.PragmaSize + carv2.HeaderSize + 3, + carv2.PragmaSize + carv2.HeaderSize + 3 + 123, }, { "WhenPaddingBothCarV1AndIndexBothOffsetsShiftWithAdditionalIndexShift", carv2.NewHeader(123).WithCarV1Padding(3).WithIndexPadding(7), - carv2.PrefixSize + carv2.HeaderSize + 3, - carv2.PrefixSize + carv2.HeaderSize + 3 + 123 + 7, + carv2.PragmaSize + carv2.HeaderSize + 3, + carv2.PragmaSize + carv2.HeaderSize + 3 + 123 + 7, }, } @@ -193,9 +193,9 @@ func TestNewHeaderHasExpectedValues(t *testing.T) { wantCarV1Len := uint64(1413) want := carv2.Header{ Characteristics: carv2.Characteristics{}, - CarV1Offset: carv2.PrefixSize + carv2.HeaderSize, + CarV1Offset: carv2.PragmaSize + carv2.HeaderSize, CarV1Size: wantCarV1Len, - IndexOffset: carv2.PrefixSize + carv2.HeaderSize + wantCarV1Len, + IndexOffset: carv2.PragmaSize + carv2.HeaderSize + wantCarV1Len, } got := carv2.NewHeader(wantCarV1Len) assert.Equal(t, want, got, "NewHeader got = %v, want = %v", got, want) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 975a47bf13..49d028dd9c 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -25,7 +25,7 @@ func NewReader(r io.ReaderAt) (*Reader, error) { cr := &Reader{ r: r, } - if err := cr.readPrefix(); err != nil { + if err := cr.readPragma(); err != nil { return nil, err } if err := cr.readHeader(); err != nil { @@ -34,8 +34,8 @@ func NewReader(r io.ReaderAt) (*Reader, error) { return cr, nil } -func (r *Reader) readPrefix() (err error) { - pr := io.NewSectionReader(r.r, 0, PrefixSize) +func (r *Reader) readPragma() (err error) { + pr := io.NewSectionReader(r.r, 0, PragmaSize) header, err := carv1.ReadHeader(bufio.NewReader(pr)) if err != nil { return @@ -47,7 +47,7 @@ func (r *Reader) readPrefix() (err error) { } func (r *Reader) readHeader() (err error) { - headerSection := io.NewSectionReader(r.r, PrefixSize, HeaderSize) + headerSection := io.NewSectionReader(r.r, PragmaSize, HeaderSize) _, err = r.Header.ReadFrom(headerSection) return } diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index d49674c9e8..7ec081f32c 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -72,11 +72,11 @@ func NewWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *Writ // WriteTo writes the given root CIDs according to CAR v2 specification, traversing the DAG using the // Writer.Walk function. func (w *Writer) WriteTo(writer io.Writer) (n int64, err error) { - _, err = writer.Write(PrefixBytes) + _, err = writer.Write(Pragma) if err != nil { return } - n += int64(PrefixSize) + n += int64(PragmaSize) // We read the entire car into memory because carbs.GenerateIndex takes a reader. // Future PRs will make this more efficient by exposing necessary interfaces in carbs so that // this can be done in an streaming manner. From 6295a648be81ee5ce60d172afbca608140597b2e Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Sat, 19 Jun 2021 21:50:44 +0100 Subject: [PATCH 069/291] Implement CAR introspector API Implement an API that allows a car file to be introspected, which would worth for both CAR v1 and v2. When the given reader is a v1, the `HasIndex` will always be false, and the car v1 size is set to the total readable bytes in the given reader. When the given reader is a v2, then the bytes are parsed and corresponding values are fetched from v2 header. Regardless of car version, `Roots` filed is populated, either from the v1 header, or the v1 dump in car v2. Add `HasIndex` placeholder in v2 header characteristics. Note the placeholder always returns true, since the current writer always writes the index. When the writer is made configurable we then change the placeholder to do its thing. Add lazy loading of roots in CAR v2 reader, used by the introspector. Add utility io function to size the number of readable bytes in a reader. Address review comments - Remove redundant reader sizer and use `Copy` `Discard` instead. - Embed two header types into Introspect to represent values One outstanding comment remains about the naming of `Introspect` This commit was moved from ipld/go-car@806cff8e373ec3c96bd3239e09e3b0b88ee6cf57 --- ipld/car/v2/car.go | 5 ++++ ipld/car/v2/introspector.go | 52 +++++++++++++++++++++++++++++++++++++ ipld/car/v2/reader.go | 19 ++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 ipld/car/v2/introspector.go diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index 6032daabf3..24edbcdfe2 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -93,6 +93,11 @@ func (h Header) WithCarV1Padding(padding uint64) Header { return h } +// HasIndex indicates whether the index is present. +func (h Header) HasIndex() bool { + return h.IndexOffset != 0 +} + // WriteTo serializes this header as bytes and writes them using the given io.Writer. func (h Header) WriteTo(w io.Writer) (n int64, err error) { wn, err := h.Characteristics.WriteTo(w) diff --git a/ipld/car/v2/introspector.go b/ipld/car/v2/introspector.go new file mode 100644 index 0000000000..917f4d8532 --- /dev/null +++ b/ipld/car/v2/introspector.go @@ -0,0 +1,52 @@ +package car + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + + carv1 "github.com/ipld/go-car" + internalio "github.com/ipld/go-car/v2/internal/io" +) + +// Introspection captures the result of an Introspect call. +type Introspection struct { + carv1.CarHeader + Header +} + +// Introspect introspects the given readable bytes and provides metadata about the characteristics +// and the version of CAR that r represents regardless of its version. This function is backward +// compatible; it supports both CAR v1 and v2. +// Returns error if r does not contain a valid CAR payload. +func Introspect(r io.ReaderAt) (*Introspection, error) { + i := &Introspection{} + or := internalio.NewOffsetReader(r, 0) + header, err := carv1.ReadHeader(bufio.NewReader(or)) + if err != nil { + return nil, err + } + i.CarHeader = *header + or.SeekOffset(0) + switch i.Version { + case 1: + written, err := io.Copy(ioutil.Discard, or) + if err != nil { + return i, err + } + i.CarV1Size = uint64(written) + case 2: + v2r, err := NewReader(or) + if err != nil { + return i, err + } + i.Header = v2r.Header + if i.Roots, err = v2r.Roots(); err != nil { + return i, err + } + default: + return i, fmt.Errorf("unknown version: %v", i.Version) + } + return i, nil +} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 49d028dd9c..64cc8a3962 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -7,6 +7,7 @@ import ( internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/ipfs/go-cid" carv1 "github.com/ipld/go-car" ) @@ -16,6 +17,7 @@ const version2 = 2 type Reader struct { Header Header r io.ReaderAt + roots []cid.Cid } // NewReader constructs a new reader that reads CAR v2 from the given r. @@ -46,6 +48,19 @@ func (r *Reader) readPragma() (err error) { return } +// Roots returns the root CIDs of this CAR +func (r *Reader) Roots() ([]cid.Cid, error) { + if r.roots != nil { + return r.roots, nil + } + header, err := carv1.ReadHeader(bufio.NewReader(r.carv1SectionReader())) + if err != nil { + return nil, err + } + r.roots = header.Roots + return r.roots, nil +} + func (r *Reader) readHeader() (err error) { headerSection := io.NewSectionReader(r.r, PragmaSize, HeaderSize) _, err = r.Header.ReadFrom(headerSection) @@ -54,6 +69,10 @@ func (r *Reader) readHeader() (err error) { // CarV1ReaderAt provides an io.ReaderAt containing the CAR v1 dump encapsulated in this CAR v2. func (r *Reader) CarV1ReaderAt() io.ReaderAt { + return r.carv1SectionReader() +} + +func (r *Reader) carv1SectionReader() *io.SectionReader { return io.NewSectionReader(r.r, int64(r.Header.CarV1Offset), int64(r.Header.CarV1Size)) } From 6e3290ef6a5b0cc871a4eb22aebcdf9a345e27d2 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 22 Jun 2021 11:40:10 +0100 Subject: [PATCH 070/291] Replace introspector.go with simple `ReadPragma` Provide a `ReadPragma` function that accepts both v1 and v2 payload. This is the smallest functionality I can think of without inventing new concepts like introspection. The carv2.Reader is refactored to make use of this and strictly requres an 11 bytes long car V2 pragma. This commit was moved from ipld/go-car@885b0d0264dde4f48b5f420b808dc4f2a361d357 --- ipld/car/v2/introspector.go | 52 ------------------------------------- ipld/car/v2/reader.go | 36 ++++++++++++++++++------- 2 files changed, 26 insertions(+), 62 deletions(-) delete mode 100644 ipld/car/v2/introspector.go diff --git a/ipld/car/v2/introspector.go b/ipld/car/v2/introspector.go deleted file mode 100644 index 917f4d8532..0000000000 --- a/ipld/car/v2/introspector.go +++ /dev/null @@ -1,52 +0,0 @@ -package car - -import ( - "bufio" - "fmt" - "io" - "io/ioutil" - - carv1 "github.com/ipld/go-car" - internalio "github.com/ipld/go-car/v2/internal/io" -) - -// Introspection captures the result of an Introspect call. -type Introspection struct { - carv1.CarHeader - Header -} - -// Introspect introspects the given readable bytes and provides metadata about the characteristics -// and the version of CAR that r represents regardless of its version. This function is backward -// compatible; it supports both CAR v1 and v2. -// Returns error if r does not contain a valid CAR payload. -func Introspect(r io.ReaderAt) (*Introspection, error) { - i := &Introspection{} - or := internalio.NewOffsetReader(r, 0) - header, err := carv1.ReadHeader(bufio.NewReader(or)) - if err != nil { - return nil, err - } - i.CarHeader = *header - or.SeekOffset(0) - switch i.Version { - case 1: - written, err := io.Copy(ioutil.Discard, or) - if err != nil { - return i, err - } - i.CarV1Size = uint64(written) - case 2: - v2r, err := NewReader(or) - if err != nil { - return i, err - } - i.Header = v2r.Header - if i.Roots, err = v2r.Roots(); err != nil { - return i, err - } - default: - return i, fmt.Errorf("unknown version: %v", i.Version) - } - return i, nil -} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 64cc8a3962..47bd17cda1 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -11,8 +11,6 @@ import ( carv1 "github.com/ipld/go-car" ) -const version2 = 2 - // Reader represents a reader of CAR v2. type Reader struct { Header Header @@ -21,13 +19,13 @@ type Reader struct { } // NewReader constructs a new reader that reads CAR v2 from the given r. -// Upon instantiation, the reader inspects the payload by reading the first 11 bytes and will return -// an error if the payload does not represent a CAR v2. +// Upon instantiation, the reader inspects the payload by reading the pragma and will return +// an error if the pragma does not represent a CAR v2. func NewReader(r io.ReaderAt) (*Reader, error) { cr := &Reader{ r: r, } - if err := cr.readPragma(); err != nil { + if err := cr.requireV2Pragma(); err != nil { return nil, err } if err := cr.readHeader(); err != nil { @@ -36,14 +34,17 @@ func NewReader(r io.ReaderAt) (*Reader, error) { return cr, nil } -func (r *Reader) readPragma() (err error) { - pr := io.NewSectionReader(r.r, 0, PragmaSize) - header, err := carv1.ReadHeader(bufio.NewReader(pr)) +func (r *Reader) requireV2Pragma() (err error) { + or := internalio.NewOffsetReader(r.r, 0) + version, _, err := ReadPragma(or) if err != nil { return } - if header.Version != version2 { - err = fmt.Errorf("invalid car version: %d", header.Version) + if version != 2 { + return fmt.Errorf("invalid car version: %d", version) + } + if or.Offset() != PragmaSize { + err = fmt.Errorf("invalid car v2 pragma; size %d is larger than expected %d", or.Offset(), PragmaSize) } return } @@ -80,3 +81,18 @@ func (r *Reader) carv1SectionReader() *io.SectionReader { func (r *Reader) IndexReaderAt() io.ReaderAt { return internalio.NewOffsetReader(r.r, int64(r.Header.IndexOffset)) } + +// ReadPragma reads the pragma from r. +// This function accepts both CAR v1 and v2 payloads. +// The roots are returned only if the version of pragma equals 1, otherwise returns nil as roots. +func ReadPragma(r io.Reader) (version uint64, roots []cid.Cid, err error) { + header, err := carv1.ReadHeader(bufio.NewReader(r)) + if err != nil { + return + } + version = header.Version + if version == 1 { + roots = header.Roots + } + return +} From 16c207a6bd332bffbd321308cecb69924d8db702 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 23 Jun 2021 10:13:55 +0100 Subject: [PATCH 071/291] Add TODO to reduce reader wrapping when working with CAR v1 reader Car V1 reader demands bufio.Reader. We want to be smart about wrapping readers unencessarily when calling the reader API from car v2. Add TODO to improve this this. This commit was moved from ipld/go-car@588d68e7d923c085b59a70aaa434f1e8b0a4b428 --- ipld/car/v2/reader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 47bd17cda1..c832570b27 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -86,6 +86,7 @@ func (r *Reader) IndexReaderAt() io.ReaderAt { // This function accepts both CAR v1 and v2 payloads. // The roots are returned only if the version of pragma equals 1, otherwise returns nil as roots. func ReadPragma(r io.Reader) (version uint64, roots []cid.Cid, err error) { + // TODO if the user provides a reader that sufficiently satisfies what carv1.ReadHeader is asking then use that instead of wrapping every time. header, err := carv1.ReadHeader(bufio.NewReader(r)) if err != nil { return From 67386bd80fe5a89d207a5e404356a96a6bd33c0f Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 23 Jun 2021 11:55:40 +0100 Subject: [PATCH 072/291] Use unsigned integer to represent width and count in sorted index Note specs need to be update to reflect this here: - https://github.com/ipld/ipld/pull/107 For context, see: - https://github.com/ipld/ipld/pull/107/files#r656749263 This commit was moved from ipld/go-car@aef90dfe9260b2110e925998347f418c618774f8 --- ipld/car/v2/internal/index/indexsorted.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ipld/car/v2/internal/index/indexsorted.go b/ipld/car/v2/internal/index/indexsorted.go index b14334d2c4..2646d08b74 100644 --- a/ipld/car/v2/internal/index/indexsorted.go +++ b/ipld/car/v2/internal/index/indexsorted.go @@ -18,11 +18,11 @@ type ( } recordSet []digestRecord singleWidthIndex struct { - width int32 - len int64 // in struct, len is #items. when marshaled, it's saved as #bytes. + width uint32 + len uint64 // in struct, len is #items. when marshaled, it's saved as #bytes. index []byte } - multiWidthIndex map[int32]singleWidthIndex + multiWidthIndex map[uint32]singleWidthIndex ) func (d digestRecord) write(buf []byte) { @@ -65,7 +65,7 @@ func (s *singleWidthIndex) Unmarshal(r io.Reader) error { return err } s.index = make([]byte, s.len) - s.len /= int64(s.width) + s.len /= uint64(s.width) _, err := io.ReadFull(r, s.index) return err } @@ -86,7 +86,7 @@ func (s *singleWidthIndex) get(d []byte) uint64 { idx := sort.Search(int(s.len), func(i int) bool { return s.Less(i, d) }) - if int64(idx) == s.len { + if uint64(idx) == s.len { return 0 } if !bytes.Equal(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) { @@ -117,7 +117,7 @@ func (m *multiWidthIndex) Get(c cid.Cid) (uint64, error) { if err != nil { return 0, err } - if s, ok := (*m)[int32(len(d.Digest)+8)]; ok { + if s, ok := (*m)[uint32(len(d.Digest)+8)]; ok { return s.get(d.Digest), nil } return 0, errNotFound @@ -176,11 +176,11 @@ func (m *multiWidthIndex) Load(items []Record) error { itm.write(compact[off*rcrdWdth : (off+1)*rcrdWdth]) } s := singleWidthIndex{ - width: int32(rcrdWdth), - len: int64(len(lst)), + width: uint32(rcrdWdth), + len: uint64(len(lst)), index: compact, } - (*m)[int32(width)+8] = s + (*m)[uint32(width)+8] = s } return nil } From d1402203184a6025092220cb5bb709efa1dc5eca Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 23 Jun 2021 12:06:09 +0100 Subject: [PATCH 073/291] Move index out of internal package to unblock Ignite team As requested, move `index` package out of internal to unblock ignite team subject to the fact that the API might change. This commit was moved from ipld/go-car@40097aa111f06840b23fa8b4d8708b24b9c4fdcf --- ipld/car/v2/blockstore/readonly.go | 2 +- ipld/car/v2/blockstore/readonly_test.go | 2 +- ipld/car/v2/{internal => }/index/errors.go | 0 ipld/car/v2/{internal => }/index/generator.go | 0 ipld/car/v2/{internal => }/index/index.go | 0 ipld/car/v2/{internal => }/index/indexgobhash.go | 0 ipld/car/v2/{internal => }/index/indexhashed.go | 0 ipld/car/v2/{internal => }/index/indexsorted.go | 0 ipld/car/v2/{internal => }/index/insertionindex.go | 0 ipld/car/v2/internal/carbon/carbon.go | 2 +- ipld/car/v2/internal/carbon/carbon_fds.go | 2 +- ipld/car/v2/internal/carbs/util/hydrate.go | 2 +- ipld/car/v2/writer.go | 2 +- ipld/car/v2/writer_test.go | 2 +- 14 files changed, 7 insertions(+), 7 deletions(-) rename ipld/car/v2/{internal => }/index/errors.go (100%) rename ipld/car/v2/{internal => }/index/generator.go (100%) rename ipld/car/v2/{internal => }/index/index.go (100%) rename ipld/car/v2/{internal => }/index/indexgobhash.go (100%) rename ipld/car/v2/{internal => }/index/indexhashed.go (100%) rename ipld/car/v2/{internal => }/index/indexsorted.go (100%) rename ipld/car/v2/{internal => }/index/insertionindex.go (100%) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 57a1e0f760..14cf74d70d 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -13,7 +13,7 @@ import ( blockstore "github.com/ipfs/go-ipfs-blockstore" carv1 "github.com/ipld/go-car" "github.com/ipld/go-car/util" - "github.com/ipld/go-car/v2/internal/index" + "github.com/ipld/go-car/v2/index" internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" ) diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 2ceb11a26a..73ed19dcb1 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - "github.com/ipld/go-car/v2/internal/index" + "github.com/ipld/go-car/v2/index" ) /* diff --git a/ipld/car/v2/internal/index/errors.go b/ipld/car/v2/index/errors.go similarity index 100% rename from ipld/car/v2/internal/index/errors.go rename to ipld/car/v2/index/errors.go diff --git a/ipld/car/v2/internal/index/generator.go b/ipld/car/v2/index/generator.go similarity index 100% rename from ipld/car/v2/internal/index/generator.go rename to ipld/car/v2/index/generator.go diff --git a/ipld/car/v2/internal/index/index.go b/ipld/car/v2/index/index.go similarity index 100% rename from ipld/car/v2/internal/index/index.go rename to ipld/car/v2/index/index.go diff --git a/ipld/car/v2/internal/index/indexgobhash.go b/ipld/car/v2/index/indexgobhash.go similarity index 100% rename from ipld/car/v2/internal/index/indexgobhash.go rename to ipld/car/v2/index/indexgobhash.go diff --git a/ipld/car/v2/internal/index/indexhashed.go b/ipld/car/v2/index/indexhashed.go similarity index 100% rename from ipld/car/v2/internal/index/indexhashed.go rename to ipld/car/v2/index/indexhashed.go diff --git a/ipld/car/v2/internal/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go similarity index 100% rename from ipld/car/v2/internal/index/indexsorted.go rename to ipld/car/v2/index/indexsorted.go diff --git a/ipld/car/v2/internal/index/insertionindex.go b/ipld/car/v2/index/insertionindex.go similarity index 100% rename from ipld/car/v2/internal/index/insertionindex.go rename to ipld/car/v2/index/insertionindex.go diff --git a/ipld/car/v2/internal/carbon/carbon.go b/ipld/car/v2/internal/carbon/carbon.go index 1de4d63620..a553200660 100644 --- a/ipld/car/v2/internal/carbon/carbon.go +++ b/ipld/car/v2/internal/carbon/carbon.go @@ -6,7 +6,7 @@ import ( "os" carblockstore "github.com/ipld/go-car/v2/blockstore" - "github.com/ipld/go-car/v2/internal/index" + "github.com/ipld/go-car/v2/index" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" diff --git a/ipld/car/v2/internal/carbon/carbon_fds.go b/ipld/car/v2/internal/carbon/carbon_fds.go index c0be2254d5..eecabf3a0c 100644 --- a/ipld/car/v2/internal/carbon/carbon_fds.go +++ b/ipld/car/v2/internal/carbon/carbon_fds.go @@ -4,7 +4,7 @@ import ( "os" "github.com/ipld/go-car/v2/blockstore" - "github.com/ipld/go-car/v2/internal/index" + "github.com/ipld/go-car/v2/index" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" diff --git a/ipld/car/v2/internal/carbs/util/hydrate.go b/ipld/car/v2/internal/carbs/util/hydrate.go index 7329bb084f..7a16fcea54 100644 --- a/ipld/car/v2/internal/carbs/util/hydrate.go +++ b/ipld/car/v2/internal/carbs/util/hydrate.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/ipld/go-car/v2/internal/index" + "github.com/ipld/go-car/v2/index" "golang.org/x/exp/mmap" ) diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 7ec081f32c..51d4047984 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -8,7 +8,7 @@ import ( "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" carv1 "github.com/ipld/go-car" - "github.com/ipld/go-car/v2/internal/index" + "github.com/ipld/go-car/v2/index" ) const bulkPaddingBytesSize = 1024 diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 5d25a2f20b..1db27f9ade 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -9,7 +9,7 @@ import ( format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" - "github.com/ipld/go-car/v2/internal/index" + "github.com/ipld/go-car/v2/index" "github.com/stretchr/testify/assert" ) From 6534b95f1b79532fd2343784f61359af1bf7dd1a Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 23 Jun 2021 16:52:35 +0100 Subject: [PATCH 074/291] Implement read-while-writing blockstore Implement a blockstore that can write blocks formatted in a CAR v2 form, while at the same time it can be used to read blocks written to it. This implementation refactors existing work from carbon to achieve this. Refactor index read/write to accept reader and writer for more flexibility. Remove progress bar from carbon implementation and hydra CLI. Remove poswriter and instead use existing offset writer by adding the abiliy to get position of the writer. Refactor index generator methods for better human readability. This commit was moved from ipld/go-car@01e55a90f14e394862789381d76e9894ca527b70 --- ipld/car/v2/blockstore/blockstore.go | 181 ++++++++++++++++++ .../blockstore_test.go} | 31 ++- ipld/car/v2/blockstore/readonly.go | 27 ++- ipld/car/v2/blockstore/readonly_test.go | 83 -------- ipld/car/v2/index/generator.go | 29 +-- ipld/car/v2/index/index.go | 28 ++- ipld/car/v2/internal/carbon/carbon.go | 64 ------- ipld/car/v2/internal/carbon/carbon_fds.go | 65 ------- ipld/car/v2/internal/carbon/doc.go | 6 - ipld/car/v2/internal/carbon/poswriter.go | 14 -- ipld/car/v2/internal/carbs/util/hydrate.go | 8 +- ipld/car/v2/internal/io/offset_writer.go | 20 +- ipld/car/v2/reader.go | 14 +- ipld/car/v2/writer.go | 4 +- 14 files changed, 257 insertions(+), 317 deletions(-) create mode 100644 ipld/car/v2/blockstore/blockstore.go rename ipld/car/v2/{internal/carbon/carbon_test.go => blockstore/blockstore_test.go} (76%) delete mode 100644 ipld/car/v2/blockstore/readonly_test.go delete mode 100644 ipld/car/v2/internal/carbon/carbon.go delete mode 100644 ipld/car/v2/internal/carbon/carbon_fds.go delete mode 100644 ipld/car/v2/internal/carbon/doc.go delete mode 100644 ipld/car/v2/internal/carbon/poswriter.go diff --git a/ipld/car/v2/blockstore/blockstore.go b/ipld/car/v2/blockstore/blockstore.go new file mode 100644 index 0000000000..00f5bde853 --- /dev/null +++ b/ipld/car/v2/blockstore/blockstore.go @@ -0,0 +1,181 @@ +package blockstore + +import ( + "context" + "errors" + "fmt" + "io" + "os" + + blockstore "github.com/ipfs/go-ipfs-blockstore" + carv1 "github.com/ipld/go-car" + carv2 "github.com/ipld/go-car/v2" + internalio "github.com/ipld/go-car/v2/internal/io" + + "github.com/ipld/go-car/v2/index" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/ipld/go-car/util" +) + +var _ (blockstore.Blockstore) = (*Blockstore)(nil) +var errFinalized = errors.New("finalized blockstore") + +// Blockstore is a carbon implementation based on having two file handles opened, +// one appending to the file, and the other +// seeking to read items as needed. +// This implementation is preferable for a write-heavy workload. +// The Finalize function must be called once the putting blocks are finished. +// Upon calling Finalize all read and write calls to this blockstore will result in error. +type ( + Blockstore struct { + w io.WriterAt + carV1Wrtier *internalio.OffsetWriter + ReadOnly + idx *index.InsertionIndex + header carv2.Header + } + Option func(*Blockstore) +) + +func WithCarV1Padding(p uint64) Option { + return func(b *Blockstore) { + b.header = b.header.WithCarV1Padding(p) + } +} + +func WithIndexPadding(p uint64) Option { + return func(b *Blockstore) { + b.header = b.header.WithIndexPadding(p) + } +} + +// New creates a new Blockstore at the given path with a provided set of root cids as the car roots. +func New(path string, roots []cid.Cid, opts ...Option) (*Blockstore, error) { + // TODO support resumption if the path provided contains partially written blocks in v2 format. + wfd, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666) + if err != nil { + return nil, fmt.Errorf("couldn't create backing car: %w", err) + } + rfd, err := os.OpenFile(path, os.O_RDONLY, 0o666) + if err != nil { + return nil, fmt.Errorf("could not re-open read handle: %w", err) + } + + indexcls, ok := index.IndexAtlas[index.IndexInsertion] + if !ok { + return nil, fmt.Errorf("unknownindex codec: %#v", index.IndexInsertion) + } + idx := (indexcls()).(*index.InsertionIndex) + + b := &Blockstore{ + w: wfd, + ReadOnly: *ReadOnlyOf(rfd, idx), + idx: idx, + header: carv2.Header{}, + } + + applyOptions(b, opts) + b.carV1Wrtier = internalio.NewOffsetWriter(wfd, int64(b.header.CarV1Offset)) + + if _, err := wfd.Write(carv2.Pragma); err != nil { + return nil, err + } + + v1Header := &carv1.CarHeader{ + Roots: roots, + Version: 1, + } + if err := carv1.WriteHeader(v1Header, b.carV1Wrtier); err != nil { + return nil, fmt.Errorf("couldn't write car header: %w", err) + } + return b, nil +} + +func applyOptions(b *Blockstore, opts []Option) { + for _, opt := range opts { + opt(b) + } +} + +func (b *Blockstore) DeleteBlock(cid.Cid) error { + return errUnsupported +} + +// Put puts a given block to the underlying datastore +func (b *Blockstore) Put(blk blocks.Block) error { + if b.isFinalized() { + return errFinalized + } + return b.PutMany([]blocks.Block{blk}) +} + +// PutMany puts a slice of blocks at the same time using batching +// capabilities of the underlying datastore whenever possible. +func (b *Blockstore) PutMany(blks []blocks.Block) error { + if b.isFinalized() { + return errFinalized + } + for _, bl := range blks { + n := uint64(b.carV1Wrtier.Position()) + if err := util.LdWrite(b.carV1Wrtier, bl.Cid().Bytes(), bl.RawData()); err != nil { + return err + } + b.idx.InsertNoReplace(bl.Cid(), n) + } + return nil +} + +func (b *Blockstore) isFinalized() bool { + return b.header.CarV1Size != 0 +} + +// Finalize finalizes this blockstore by writing the CAR v2 header, along with flattened index +// for more efficient subsequent read. +// After this call, this blockstore can no longer be used for read or write. +func (b *Blockstore) Finalize() error { + if b.isFinalized() { + return errFinalized + } + // TODO check if add index option is set and don't write the index then set index offset to zero. + // TODO see if folks need to continue reading from a finalized blockstore, if so return ReadOnly blockstore here. + b.header.CarV1Size = uint64(b.carV1Wrtier.Position()) + if _, err := b.header.WriteTo(internalio.NewOffsetWriter(b.w, carv2.PragmaSize)); err != nil { + return err + } + // TODO if index not needed don't bother flattening it. + fi, err := b.idx.Flatten() + if err != nil { + return err + } + return index.WriteTo(fi, internalio.NewOffsetWriter(b.w, int64(b.header.IndexOffset))) +} + +func (b *Blockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + if b.isFinalized() { + return nil, errFinalized + } + return b.ReadOnly.AllKeysChan(ctx) +} + +func (b *Blockstore) Has(key cid.Cid) (bool, error) { + if b.isFinalized() { + return false, errFinalized + } + return b.ReadOnly.Has(key) +} + +func (b *Blockstore) Get(key cid.Cid) (blocks.Block, error) { + if b.isFinalized() { + return nil, errFinalized + } + return b.ReadOnly.Get(key) +} + +func (b *Blockstore) GetSize(key cid.Cid) (int, error) { + if b.isFinalized() { + return 0, errFinalized + } + return b.ReadOnly.GetSize(key) +} diff --git a/ipld/car/v2/internal/carbon/carbon_test.go b/ipld/car/v2/blockstore/blockstore_test.go similarity index 76% rename from ipld/car/v2/internal/carbon/carbon_test.go rename to ipld/car/v2/blockstore/blockstore_test.go index d2307abb97..76073b86b9 100644 --- a/ipld/car/v2/internal/carbon/carbon_test.go +++ b/ipld/car/v2/blockstore/blockstore_test.go @@ -1,19 +1,17 @@ -package carbon_test +package blockstore_test import ( + "github.com/ipld/go-car/v2/blockstore" "io" "math/rand" "os" "testing" - "github.com/ipld/go-car/v2/blockstore" - "github.com/ipld/go-car/v2/internal/carbon" - "github.com/ipfs/go-cid" - "github.com/ipld/go-car" + carv1 "github.com/ipld/go-car" ) -func TestCarbon(t *testing.T) { +func TestBlockstore(t *testing.T) { f, err := os.Open("../carbs/testdata/test.car") if err != nil { t.Skipf("fixture not found: %q", err) @@ -21,18 +19,20 @@ func TestCarbon(t *testing.T) { } defer f.Close() - ingester, err := carbon.New("testcarbon.car") + r, err := carv1.NewCarReader(f) + if err != nil { t.Fatal(err) } - defer func() { - os.Remove("testcarbon.car") - }() - - r, err := car.NewCarReader(f) + path := "testv2blockstore.car" + ingester, err := blockstore.New(path, r.Header.Roots) if err != nil { t.Fatal(err) } + defer func() { + os.Remove(path) + }() + cids := make([]cid.Cid, 0) for { b, err := r.Next() @@ -61,12 +61,9 @@ func TestCarbon(t *testing.T) { } } - if err := ingester.Finish(); err != nil { + if err := ingester.Finalize(); err != nil { t.Fatal(err) } - defer func() { - os.Remove("testcarbon.car.idx") - }() stat, err := os.Stat("testcarbon.car.idx") if err != nil { @@ -76,7 +73,7 @@ func TestCarbon(t *testing.T) { t.Fatalf("index not written: %v", stat) } - carb, err := blockstore.LoadReadOnly("testcarbon.car", true) + carb, err := blockstore.OpenReadOnly(path, true) if err != nil { t.Fatal(err) } diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 14cf74d70d..849df2e530 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -6,16 +6,16 @@ import ( "encoding/binary" "errors" "fmt" - "io" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" carv1 "github.com/ipld/go-car" "github.com/ipld/go-car/util" + carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" + "io" ) var _ blockstore.Blockstore = (*ReadOnly)(nil) @@ -29,20 +29,26 @@ type ReadOnly struct { idx index.Index } -// ReadOnlyOf opens a carbs data store from an existing reader of the base data and index +// ReadOnlyOf opens a carbs data store from an existing backing of the base data (i.e. CAR v1 payload) and index. func ReadOnlyOf(backing io.ReaderAt, index index.Index) *ReadOnly { return &ReadOnly{backing, index} } -// LoadReadOnly opens a read-only blockstore, generating an index if it does not exist -func LoadReadOnly(path string, noPersist bool) (*ReadOnly, error) { +// OpenReadOnly opens a read-only blockstore from a CAR v2 file, generating an index if it does not exist. +// If noPersist is set to false then the generated index is written into the CAR v2 file at path. +func OpenReadOnly(path string, noPersist bool) (*ReadOnly, error) { reader, err := mmap.Open(path) if err != nil { return nil, err } - idx, err := index.Restore(path) + + v2r, err := carv2.NewReader(reader) if err != nil { - idx, err = index.GenerateIndex(reader, 0, index.IndexSorted) + return nil, err + } + var idx index.Index + if !v2r.Header.HasIndex() { + idx, err := index.Generate(v2r.CarV1Reader(), index.IndexSorted) if err != nil { return nil, err } @@ -51,9 +57,14 @@ func LoadReadOnly(path string, noPersist bool) (*ReadOnly, error) { return nil, err } } + } else { + idx, err = index.ReadFrom(v2r.IndexReader()) + if err != nil { + return nil, err + } } obj := ReadOnly{ - backing: reader, + backing: v2r.CarV1Reader(), idx: idx, } return &obj, nil diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go deleted file mode 100644 index 73ed19dcb1..0000000000 --- a/ipld/car/v2/blockstore/readonly_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package blockstore - -import ( - "os" - "testing" - - "github.com/ipld/go-car/v2/index" -) - -/* -func mkCar() (string, error) { - f, err := ioutil.TempFile(os.TempDir(), "car") - if err != nil { - return "", err - } - defer f.Close() - - ds := mockNodeGetter{ - Nodes: make(map[cid.Cid]format.Node), - } - type linker struct { - Name string - Links []*format.Link - } - cbornode.RegisterCborType(linker{}) - - children := make([]format.Node, 0, 10) - childLinks := make([]*format.Link, 0, 10) - for i := 0; i < 10; i++ { - child, _ := cbornode.WrapObject([]byte{byte(i)}, multihash.SHA2_256, -1) - children = append(children, child) - childLinks = append(childLinks, &format.Link{Name: fmt.Sprintf("child%d", i), Cid: child.Cid()}) - } - b, err := cbornode.WrapObject(linker{Name: "root", Links: childLinks}, multihash.SHA2_256, -1) - if err != nil { - return "", fmt.Errorf("couldn't make cbor node: %v", err) - } - ds.Nodes[b.Cid()] = b - - if err := car.WriteCar(context.Background(), &ds, []cid.Cid{b.Cid()}, f); err != nil { - return "", err - } - - return f.Name(), nil -} -*/ - -func TestIndexRT(t *testing.T) { - /* - carFile, err := mkCar() - if err != nil { - t.Fatal(err) - } - defer os.Remove(carFile) - */ - // TODO use temporary directory to run tests taht work with OS file system to avoid accidental source code modification - carFile := "testdata/test.car" - - cf, err := LoadReadOnly(carFile, false) - if err != nil { - t.Fatal(err) - } - defer os.Remove(carFile + ".idx") - - r, err := cf.Roots() - if err != nil { - t.Fatal(err) - } - if len(r) != 1 { - t.Fatalf("unexpected number of roots: %d", len(r)) - } - if _, err := cf.Get(r[0]); err != nil { - t.Fatalf("failed get: %v", err) - } - - idx, err := index.Restore(carFile) - if err != nil { - t.Fatalf("failed restore: %v", err) - } - if idx, err := idx.Get(r[0]); idx == 0 || err != nil { - t.Fatalf("bad index: %d %v", idx, err) - } -} diff --git a/ipld/car/v2/index/generator.go b/ipld/car/v2/index/generator.go index 1d55708eab..5c1ef7f878 100644 --- a/ipld/car/v2/index/generator.go +++ b/ipld/car/v2/index/generator.go @@ -6,27 +6,19 @@ import ( "fmt" "io" - "github.com/cheggaaa/pb/v3" carv1 "github.com/ipld/go-car" internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" ) -// GenerateIndex provides a low-level interface to create an index over a -// reader to a car stream. -func GenerateIndex(store io.ReaderAt, size int64, codec Codec) (Index, error) { +// Generate generates index given car v1 payload using the given codec. +func Generate(car io.ReaderAt, codec Codec) (Index, error) { indexcls, ok := IndexAtlas[codec] if !ok { return nil, fmt.Errorf("unknown codec: %#v", codec) } - bar := pb.New64(size) - bar.Set(pb.Bytes, true) - bar.Set(pb.Terminal, true) - - bar.Start() - - header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(store, 0))) + header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(car, 0))) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } @@ -34,16 +26,14 @@ func GenerateIndex(store io.ReaderAt, size int64, codec Codec) (Index, error) { if err != nil { return nil, err } - bar.Add64(int64(offset)) idx := indexcls() records := make([]Record, 0) - rdr := internalio.NewOffsetReader(store, int64(offset)) + rdr := internalio.NewOffsetReader(car, int64(offset)) for { thisItemIdx := rdr.Offset() l, err := binary.ReadUvarint(rdr) - bar.Add64(int64(l)) thisItemForNxt := rdr.Offset() if err != nil { if err == io.EOF { @@ -51,7 +41,7 @@ func GenerateIndex(store io.ReaderAt, size int64, codec Codec) (Index, error) { } return nil, err } - c, _, err := internalio.ReadCid(store, thisItemForNxt) + c, _, err := internalio.ReadCid(car, thisItemForNxt) if err != nil { return nil, err } @@ -63,18 +53,17 @@ func GenerateIndex(store io.ReaderAt, size int64, codec Codec) (Index, error) { return nil, err } - bar.Finish() - return idx, nil } -// Generate walks a car file and generates an index of cid->byte offset in it. -func Generate(path string, codec Codec) error { +// GenerateFromFile walks a car v1 file and generates an index of cid->byte offset, then +// stors it in a separate file at the given path with extension `.idx`. +func GenerateFromFile(path string, codec Codec) error { store, err := mmap.Open(path) if err != nil { return err } - idx, err := GenerateIndex(store, 0, codec) + idx, err := Generate(store, codec) if err != nil { return err } diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index f4fd1b6fc9..5ecdfbf563 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -1,14 +1,13 @@ package index import ( + "bufio" "encoding/binary" "fmt" "io" "os" "github.com/ipfs/go-cid" - internalio "github.com/ipld/go-car/v2/internal/io" - "golang.org/x/exp/mmap" ) // Codec table is a first var-int in carbs indexes @@ -52,32 +51,28 @@ var IndexAtlas = map[Codec]IndexCls{ IndexInsertion: mkInsertion, } -// Save writes a generated index for a car at `path` +// Save writes a generated index into the given `path` as a file with a `.idx` extension. func Save(i Index, path string) error { stream, err := os.OpenFile(path+".idx", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) if err != nil { return err } defer stream.Close() + return WriteTo(i, stream) +} +func WriteTo(i Index, w io.Writer) error { buf := make([]byte, binary.MaxVarintLen64) b := binary.PutUvarint(buf, uint64(i.Codec())) - if _, err := stream.Write(buf[:b]); err != nil { + if _, err := w.Write(buf[:b]); err != nil { return err } - return i.Marshal(stream) + return i.Marshal(w) } -// Restore loads an index from an on-disk representation. -func Restore(path string) (Index, error) { - reader, err := mmap.Open(path + ".idx") - if err != nil { - return nil, err - } - - defer reader.Close() - uar := internalio.NewOffsetReader(reader, 0) - codec, err := binary.ReadUvarint(uar) +func ReadFrom(uar io.Reader) (Index, error) { + reader := bufio.NewReader(uar) + codec, err := binary.ReadUvarint(reader) if err != nil { return nil, err } @@ -86,9 +81,8 @@ func Restore(path string) (Index, error) { return nil, fmt.Errorf("unknown codec: %d", codec) } idxInst := idx() - if err := idxInst.Unmarshal(uar); err != nil { + if err := idxInst.Unmarshal(reader); err != nil { return nil, err } - return idxInst, nil } diff --git a/ipld/car/v2/internal/carbon/carbon.go b/ipld/car/v2/internal/carbon/carbon.go deleted file mode 100644 index a553200660..0000000000 --- a/ipld/car/v2/internal/carbon/carbon.go +++ /dev/null @@ -1,64 +0,0 @@ -package carbon - -import ( - "errors" - "fmt" - "os" - - carblockstore "github.com/ipld/go-car/v2/blockstore" - "github.com/ipld/go-car/v2/index" - - "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" - carv1 "github.com/ipld/go-car" -) - -// Carbon is a carbs-index-compatible blockstore supporting appending additional blocks -type Carbon interface { - blockstore.Blockstore - Checkpoint() error - Finish() error -} - -// errUnsupported is returned for unsupported blockstore operations (like delete) -var errUnsupported = errors.New("unsupported by carbon") - -// New creates a new Carbon blockstore -func New(path string) (Carbon, error) { - return NewWithRoots(path, []cid.Cid{}) -} - -// NewWithRoots creates a new Carbon blockstore with a provided set of root cids as the car roots -func NewWithRoots(path string, roots []cid.Cid) (Carbon, error) { - wfd, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666) - if err != nil { - return nil, fmt.Errorf("couldn't create backing car: %w", err) - } - rfd, err := os.OpenFile(path, os.O_RDONLY, 0o666) - if err != nil { - return nil, fmt.Errorf("could not re-open read handle: %w", err) - } - - hdr := carv1.CarHeader{ - Roots: roots, - Version: 1, - } - writer := poswriter{wfd, 0} - if err := carv1.WriteHeader(&hdr, &writer); err != nil { - return nil, fmt.Errorf("couldn't write car header: %w", err) - } - - indexcls, ok := index.IndexAtlas[index.IndexInsertion] - if !ok { - return nil, fmt.Errorf("unknownindex codec: %#v", index.IndexInsertion) - } - - idx := (indexcls()).(*index.InsertionIndex) - f := carbonFD{ - path, - &writer, - *carblockstore.ReadOnlyOf(rfd, idx), - idx, - } - return &f, nil -} diff --git a/ipld/car/v2/internal/carbon/carbon_fds.go b/ipld/car/v2/internal/carbon/carbon_fds.go deleted file mode 100644 index eecabf3a0c..0000000000 --- a/ipld/car/v2/internal/carbon/carbon_fds.go +++ /dev/null @@ -1,65 +0,0 @@ -package carbon - -import ( - "os" - - "github.com/ipld/go-car/v2/blockstore" - "github.com/ipld/go-car/v2/index" - - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" - "github.com/ipld/go-car/util" -) - -// carbonFD is a carbon implementation based on having two file handles opened, one appending to the file, and the other -// seeking to read items as needed. This implementation is preferable for a write-heavy workload. -type carbonFD struct { - path string - writeHandle *poswriter - blockstore.ReadOnly - idx *index.InsertionIndex -} - -var _ (Carbon) = (*carbonFD)(nil) - -func (c *carbonFD) DeleteBlock(cid.Cid) error { - return errUnsupported -} - -// Put puts a given block to the underlying datastore -func (c *carbonFD) Put(b blocks.Block) error { - return c.PutMany([]blocks.Block{b}) -} - -// PutMany puts a slice of blocks at the same time using batching -// capabilities of the underlying datastore whenever possible. -func (c *carbonFD) PutMany(b []blocks.Block) error { - for _, bl := range b { - n := c.writeHandle.at - if err := util.LdWrite(c.writeHandle, bl.Cid().Bytes(), bl.RawData()); err != nil { - return err - } - c.idx.InsertNoReplace(bl.Cid(), n) - } - return nil -} - -// Finish serializes the carbon index so that it can be later used as a carbs read-only blockstore -func (c *carbonFD) Finish() error { - fi, err := c.idx.Flatten() - if err != nil { - return err - } - fd, ok := c.writeHandle.Writer.(*os.File) - if ok { - if err := fd.Close(); err != nil { - return err - } - } - return index.Save(fi, c.path) -} - -// Checkpoint serializes the carbon index so that the partially written blockstore can be resumed. -func (c *carbonFD) Checkpoint() error { - return index.Save(c.idx, c.path) -} diff --git a/ipld/car/v2/internal/carbon/doc.go b/ipld/car/v2/internal/carbon/doc.go deleted file mode 100644 index e620f4d000..0000000000 --- a/ipld/car/v2/internal/carbon/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Package carbon provides a blockstore interface with saved blocks stored to a car-compatible log. -// A carbs index for the resulting file is tracked and can be saved as needed. -// Note, carbon does not support deletion. -// See: https://github.com/ipfs/go-ipfs-blockstore -// TODO to be refactored -package carbon diff --git a/ipld/car/v2/internal/carbon/poswriter.go b/ipld/car/v2/internal/carbon/poswriter.go deleted file mode 100644 index 5b9444e5ae..0000000000 --- a/ipld/car/v2/internal/carbon/poswriter.go +++ /dev/null @@ -1,14 +0,0 @@ -package carbon - -import "io" - -type poswriter struct { - io.Writer - at uint64 -} - -func (p *poswriter) Write(b []byte) (n int, err error) { - n, err = p.Writer.Write(b) - p.at += uint64(n) - return -} diff --git a/ipld/car/v2/internal/carbs/util/hydrate.go b/ipld/car/v2/internal/carbs/util/hydrate.go index 7a16fcea54..c1e8533a6d 100644 --- a/ipld/car/v2/internal/carbs/util/hydrate.go +++ b/ipld/car/v2/internal/carbs/util/hydrate.go @@ -29,13 +29,7 @@ func main() { return } - dbstat, err := os.Stat(db) - if err != nil { - fmt.Printf("Error statting car for hydration: %v\n", err) - return - } - - idx, err := index.GenerateIndex(dbBacking, dbstat.Size(), codec) + idx, err := index.Generate(dbBacking, codec) if err != nil { fmt.Printf("Error generating index: %v\n", err) return diff --git a/ipld/car/v2/internal/io/offset_writer.go b/ipld/car/v2/internal/io/offset_writer.go index 495190a173..1dd810165b 100644 --- a/ipld/car/v2/internal/io/offset_writer.go +++ b/ipld/car/v2/internal/io/offset_writer.go @@ -2,15 +2,25 @@ package io import "io" -var _ io.Writer = (*offsetWriter)(nil) +var _ io.Writer = (*OffsetWriter)(nil) -type offsetWriter struct { - wa io.WriterAt +type OffsetWriter struct { + w io.WriterAt + base int64 offset int64 } -func (ow *offsetWriter) Write(b []byte) (n int, err error) { - n, err = ow.wa.WriteAt(b, ow.offset) +func NewOffsetWriter(w io.WriterAt, off int64) *OffsetWriter { + return &OffsetWriter{w, off, off} +} + +func (ow *OffsetWriter) Write(b []byte) (n int, err error) { + n, err = ow.w.WriteAt(b, ow.offset) ow.offset += int64(n) return } + +// Position returns the current position of this writer relative to the initial offset, i.e. the number of bytes written. +func (ow *OffsetWriter) Position() int64 { + return ow.offset - ow.base +} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index c832570b27..2f801bb9b7 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -54,7 +54,7 @@ func (r *Reader) Roots() ([]cid.Cid, error) { if r.roots != nil { return r.roots, nil } - header, err := carv1.ReadHeader(bufio.NewReader(r.carv1SectionReader())) + header, err := carv1.ReadHeader(bufio.NewReader(r.CarV1Reader())) if err != nil { return nil, err } @@ -68,17 +68,13 @@ func (r *Reader) readHeader() (err error) { return } -// CarV1ReaderAt provides an io.ReaderAt containing the CAR v1 dump encapsulated in this CAR v2. -func (r *Reader) CarV1ReaderAt() io.ReaderAt { - return r.carv1SectionReader() -} - -func (r *Reader) carv1SectionReader() *io.SectionReader { +// CarV1Reader provides a reader containing the CAR v1 section encapsulated in this CAR v2. +func (r *Reader) CarV1Reader() *io.SectionReader { return io.NewSectionReader(r.r, int64(r.Header.CarV1Offset), int64(r.Header.CarV1Size)) } -// IndexReaderAt provides an io.ReaderAt containing the carbs.Index of this CAR v2. -func (r *Reader) IndexReaderAt() io.ReaderAt { +// IndexReader provides an io.Reader containing the carbs.Index of this CAR v2. +func (r *Reader) IndexReader() io.Reader { return internalio.NewOffsetReader(r.r, int64(r.Header.IndexOffset)) } diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 51d4047984..fba4edcd88 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -77,7 +77,7 @@ func (w *Writer) WriteTo(writer io.Writer) (n int64, err error) { return } n += int64(PragmaSize) - // We read the entire car into memory because carbs.GenerateIndex takes a reader. + // We read the entire car into memory because carbs.Generate takes a reader. // Future PRs will make this more efficient by exposing necessary interfaces in carbs so that // this can be done in an streaming manner. if err = carv1.WriteCarWithWalker(w.ctx, w.NodeGetter, w.roots, w.encodedCarV1, w.Walk); err != nil { @@ -130,7 +130,7 @@ func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (n int64, err error) // Consider refactoring carbs to make this process more efficient. // We should avoid reading the entire car into memory since it can be large. reader := bytes.NewReader(carV1) - index, err := index.GenerateIndex(reader, int64(len(carV1)), index.IndexSorted) + index, err := index.Generate(reader, index.IndexSorted) if err != nil { return } From b4491513c900276f1a672874f900dea96852c70a Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 23 Jun 2021 17:03:12 +0100 Subject: [PATCH 075/291] Format code using gofumpt Use consistent code formatting by running `gofumpt -l -w .` This commit was moved from ipld/go-car@1e464d9e7ea76b68142d25e422fd9df07810a06a --- ipld/car/v2/blockstore/blockstore.go | 6 ++++-- ipld/car/v2/blockstore/blockstore_test.go | 4 ++-- ipld/car/v2/blockstore/readonly.go | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ipld/car/v2/blockstore/blockstore.go b/ipld/car/v2/blockstore/blockstore.go index 00f5bde853..fdc537b39e 100644 --- a/ipld/car/v2/blockstore/blockstore.go +++ b/ipld/car/v2/blockstore/blockstore.go @@ -19,8 +19,10 @@ import ( "github.com/ipld/go-car/util" ) -var _ (blockstore.Blockstore) = (*Blockstore)(nil) -var errFinalized = errors.New("finalized blockstore") +var ( + _ (blockstore.Blockstore) = (*Blockstore)(nil) + errFinalized = errors.New("finalized blockstore") +) // Blockstore is a carbon implementation based on having two file handles opened, // one appending to the file, and the other diff --git a/ipld/car/v2/blockstore/blockstore_test.go b/ipld/car/v2/blockstore/blockstore_test.go index 76073b86b9..b08ab29c13 100644 --- a/ipld/car/v2/blockstore/blockstore_test.go +++ b/ipld/car/v2/blockstore/blockstore_test.go @@ -1,12 +1,13 @@ package blockstore_test import ( - "github.com/ipld/go-car/v2/blockstore" "io" "math/rand" "os" "testing" + "github.com/ipld/go-car/v2/blockstore" + "github.com/ipfs/go-cid" carv1 "github.com/ipld/go-car" ) @@ -20,7 +21,6 @@ func TestBlockstore(t *testing.T) { defer f.Close() r, err := carv1.NewCarReader(f) - if err != nil { t.Fatal(err) } diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 849df2e530..20d46f1cf8 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -6,6 +6,8 @@ import ( "encoding/binary" "errors" "fmt" + "io" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" @@ -15,7 +17,6 @@ import ( "github.com/ipld/go-car/v2/index" internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" - "io" ) var _ blockstore.Blockstore = (*ReadOnly)(nil) From a7bd9e283c84da2ad91371ec4cb26d30762bad42 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 23 Jun 2021 17:16:28 +0100 Subject: [PATCH 076/291] Fix attach index in ReadOnly blockstore Implement ability to attach index to an existing car v2 file. Fix bug in ReadOnly blockstore where the generated index was being saved as a separate file rather than being added to the CAR v2 file. Address PR comments - Add TODOs for future iterations to unify options and add interfaces - Fix failing skipped test for readwrite blockstore.go - Rename readwrite blockstore for consistency This commit was moved from ipld/go-car@3ab265837e67932e76781a6028a52f52254a9a0b --- ipld/car/v2/blockstore/readonly.go | 2 +- .../{blockstore.go => readwrite.go} | 82 +++++++++---------- .../{blockstore_test.go => readwrite_test.go} | 34 +++----- ipld/car/v2/car.go | 8 +- ipld/car/v2/index/index.go | 17 +++- ipld/car/v2/reader.go | 7 +- 6 files changed, 73 insertions(+), 77 deletions(-) rename ipld/car/v2/blockstore/{blockstore.go => readwrite.go} (63%) rename ipld/car/v2/blockstore/{blockstore_test.go => readwrite_test.go} (73%) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 20d46f1cf8..e294f3f63a 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -54,7 +54,7 @@ func OpenReadOnly(path string, noPersist bool) (*ReadOnly, error) { return nil, err } if !noPersist { - if err = index.Save(idx, path); err != nil { + if err := index.Attach(path, idx, v2r.Header.IndexOffset); err != nil { return nil, err } } diff --git a/ipld/car/v2/blockstore/blockstore.go b/ipld/car/v2/blockstore/readwrite.go similarity index 63% rename from ipld/car/v2/blockstore/blockstore.go rename to ipld/car/v2/blockstore/readwrite.go index fdc537b39e..7bee5b7343 100644 --- a/ipld/car/v2/blockstore/blockstore.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "io" "os" blockstore "github.com/ipfs/go-ipfs-blockstore" @@ -20,49 +19,46 @@ import ( ) var ( - _ (blockstore.Blockstore) = (*Blockstore)(nil) - errFinalized = errors.New("finalized blockstore") + _ blockstore.Blockstore = (*ReadWrite)(nil) + errFinalized = errors.New("finalized blockstore") ) -// Blockstore is a carbon implementation based on having two file handles opened, +// ReadWrite is a carbon implementation based on having two file handles opened, // one appending to the file, and the other // seeking to read items as needed. // This implementation is preferable for a write-heavy workload. // The Finalize function must be called once the putting blocks are finished. // Upon calling Finalize all read and write calls to this blockstore will result in error. type ( - Blockstore struct { - w io.WriterAt + // TODO consider exposing interfaces + ReadWrite struct { + f *os.File carV1Wrtier *internalio.OffsetWriter ReadOnly idx *index.InsertionIndex header carv2.Header } - Option func(*Blockstore) + Option func(*ReadWrite) // TODO consider unifying with writer options ) func WithCarV1Padding(p uint64) Option { - return func(b *Blockstore) { + return func(b *ReadWrite) { b.header = b.header.WithCarV1Padding(p) } } func WithIndexPadding(p uint64) Option { - return func(b *Blockstore) { + return func(b *ReadWrite) { b.header = b.header.WithIndexPadding(p) } } -// New creates a new Blockstore at the given path with a provided set of root cids as the car roots. -func New(path string, roots []cid.Cid, opts ...Option) (*Blockstore, error) { +// NewReadWrite creates a new ReadWrite at the given path with a provided set of root cids as the car roots. +func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { // TODO support resumption if the path provided contains partially written blocks in v2 format. - wfd, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666) + f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o666) if err != nil { - return nil, fmt.Errorf("couldn't create backing car: %w", err) - } - rfd, err := os.OpenFile(path, os.O_RDONLY, 0o666) - if err != nil { - return nil, fmt.Errorf("could not re-open read handle: %w", err) + return nil, fmt.Errorf("could not open read/write file: %w", err) } indexcls, ok := index.IndexAtlas[index.IndexInsertion] @@ -71,17 +67,18 @@ func New(path string, roots []cid.Cid, opts ...Option) (*Blockstore, error) { } idx := (indexcls()).(*index.InsertionIndex) - b := &Blockstore{ - w: wfd, - ReadOnly: *ReadOnlyOf(rfd, idx), - idx: idx, - header: carv2.Header{}, + b := &ReadWrite{ + f: f, + idx: idx, + header: carv2.NewHeader(0), } - - applyOptions(b, opts) - b.carV1Wrtier = internalio.NewOffsetWriter(wfd, int64(b.header.CarV1Offset)) - - if _, err := wfd.Write(carv2.Pragma); err != nil { + for _, opt := range opts { + opt(b) + } + b.carV1Wrtier = internalio.NewOffsetWriter(f, int64(b.header.CarV1Offset)) + carV1Reader := internalio.NewOffsetReader(f, int64(b.header.CarV1Offset)) + b.ReadOnly = *ReadOnlyOf(carV1Reader, idx) + if _, err := f.WriteAt(carv2.Pragma, 0); err != nil { return nil, err } @@ -95,18 +92,12 @@ func New(path string, roots []cid.Cid, opts ...Option) (*Blockstore, error) { return b, nil } -func applyOptions(b *Blockstore, opts []Option) { - for _, opt := range opts { - opt(b) - } -} - -func (b *Blockstore) DeleteBlock(cid.Cid) error { +func (b *ReadWrite) DeleteBlock(cid.Cid) error { return errUnsupported } // Put puts a given block to the underlying datastore -func (b *Blockstore) Put(blk blocks.Block) error { +func (b *ReadWrite) Put(blk blocks.Block) error { if b.isFinalized() { return errFinalized } @@ -115,7 +106,7 @@ func (b *Blockstore) Put(blk blocks.Block) error { // PutMany puts a slice of blocks at the same time using batching // capabilities of the underlying datastore whenever possible. -func (b *Blockstore) PutMany(blks []blocks.Block) error { +func (b *ReadWrite) PutMany(blks []blocks.Block) error { if b.isFinalized() { return errFinalized } @@ -129,21 +120,22 @@ func (b *Blockstore) PutMany(blks []blocks.Block) error { return nil } -func (b *Blockstore) isFinalized() bool { +func (b *ReadWrite) isFinalized() bool { return b.header.CarV1Size != 0 } // Finalize finalizes this blockstore by writing the CAR v2 header, along with flattened index // for more efficient subsequent read. // After this call, this blockstore can no longer be used for read or write. -func (b *Blockstore) Finalize() error { +func (b *ReadWrite) Finalize() error { if b.isFinalized() { return errFinalized } // TODO check if add index option is set and don't write the index then set index offset to zero. // TODO see if folks need to continue reading from a finalized blockstore, if so return ReadOnly blockstore here. - b.header.CarV1Size = uint64(b.carV1Wrtier.Position()) - if _, err := b.header.WriteTo(internalio.NewOffsetWriter(b.w, carv2.PragmaSize)); err != nil { + b.header = b.header.WithCarV1Size(uint64(b.carV1Wrtier.Position())) + defer b.f.Close() + if _, err := b.header.WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)); err != nil { return err } // TODO if index not needed don't bother flattening it. @@ -151,31 +143,31 @@ func (b *Blockstore) Finalize() error { if err != nil { return err } - return index.WriteTo(fi, internalio.NewOffsetWriter(b.w, int64(b.header.IndexOffset))) + return index.WriteTo(fi, internalio.NewOffsetWriter(b.f, int64(b.header.IndexOffset))) } -func (b *Blockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { +func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { if b.isFinalized() { return nil, errFinalized } return b.ReadOnly.AllKeysChan(ctx) } -func (b *Blockstore) Has(key cid.Cid) (bool, error) { +func (b *ReadWrite) Has(key cid.Cid) (bool, error) { if b.isFinalized() { return false, errFinalized } return b.ReadOnly.Has(key) } -func (b *Blockstore) Get(key cid.Cid) (blocks.Block, error) { +func (b *ReadWrite) Get(key cid.Cid) (blocks.Block, error) { if b.isFinalized() { return nil, errFinalized } return b.ReadOnly.Get(key) } -func (b *Blockstore) GetSize(key cid.Cid) (int, error) { +func (b *ReadWrite) GetSize(key cid.Cid) (int, error) { if b.isFinalized() { return 0, errFinalized } diff --git a/ipld/car/v2/blockstore/blockstore_test.go b/ipld/car/v2/blockstore/readwrite_test.go similarity index 73% rename from ipld/car/v2/blockstore/blockstore_test.go rename to ipld/car/v2/blockstore/readwrite_test.go index b08ab29c13..81d2873350 100644 --- a/ipld/car/v2/blockstore/blockstore_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -4,8 +4,11 @@ import ( "io" "math/rand" "os" + "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/ipld/go-car/v2/blockstore" "github.com/ipfs/go-cid" @@ -13,25 +16,17 @@ import ( ) func TestBlockstore(t *testing.T) { - f, err := os.Open("../carbs/testdata/test.car") - if err != nil { - t.Skipf("fixture not found: %q", err) - return - } + tempDir := t.TempDir() + f, err := os.Open("testdata/test.car") + assert.NoError(t, err) defer f.Close() - r, err := carv1.NewCarReader(f) + assert.NoError(t, err) + path := filepath.Join(tempDir, "/testv2blockstore.car") + ingester, err := blockstore.NewReadWrite(path, r.Header.Roots) if err != nil { t.Fatal(err) } - path := "testv2blockstore.car" - ingester, err := blockstore.New(path, r.Header.Roots) - if err != nil { - t.Fatal(err) - } - defer func() { - os.Remove(path) - }() cids := make([]cid.Cid, 0) for { @@ -39,6 +34,8 @@ func TestBlockstore(t *testing.T) { if err == io.EOF { break } + assert.NoError(t, err) + if err := ingester.Put(b); err != nil { t.Fatal(err) } @@ -64,15 +61,6 @@ func TestBlockstore(t *testing.T) { if err := ingester.Finalize(); err != nil { t.Fatal(err) } - - stat, err := os.Stat("testcarbon.car.idx") - if err != nil { - t.Fatal(err) - } - if stat.Size() <= 0 { - t.Fatalf("index not written: %v", stat) - } - carb, err := blockstore.OpenReadOnly(path, true) if err != nil { t.Fatal(err) diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index 24edbcdfe2..4d8c3f50a9 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -88,11 +88,17 @@ func (h Header) WithIndexPadding(padding uint64) Header { // The CAR v1 offset is calculated as the sum of PragmaSize, HeaderSize and the given padding. // The call to this function also shifts the Header.IndexOffset forward by the given padding. func (h Header) WithCarV1Padding(padding uint64) Header { - h.CarV1Offset = h.CarV1Offset + padding + h.CarV1Offset = PragmaSize + HeaderSize + padding h.IndexOffset = h.IndexOffset + padding return h } +func (h Header) WithCarV1Size(size uint64) Header { + h.CarV1Size = size + h.IndexOffset = size + h.IndexOffset + return h +} + // HasIndex indicates whether the index is present. func (h Header) HasIndex() bool { return h.IndexOffset != 0 diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 5ecdfbf563..edaf271e30 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -7,6 +7,8 @@ import ( "io" "os" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/ipfs/go-cid" ) @@ -52,13 +54,24 @@ var IndexAtlas = map[Codec]IndexCls{ } // Save writes a generated index into the given `path` as a file with a `.idx` extension. -func Save(i Index, path string) error { +func Save(idx Index, path string) error { stream, err := os.OpenFile(path+".idx", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) if err != nil { return err } defer stream.Close() - return WriteTo(i, stream) + return WriteTo(idx, stream) +} + +// Attach attaches a given index to an existing car v2 file at given path and offset. +func Attach(path string, idx Index, offset uint64) error { + out, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) + if err != nil { + return err + } + defer out.Close() + indexWriter := internalio.NewOffsetWriter(out, int64(offset)) + return WriteTo(idx, indexWriter) } func WriteTo(i Index, w io.Writer) error { diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 2f801bb9b7..06b89936df 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -43,9 +43,6 @@ func (r *Reader) requireV2Pragma() (err error) { if version != 2 { return fmt.Errorf("invalid car version: %d", version) } - if or.Offset() != PragmaSize { - err = fmt.Errorf("invalid car v2 pragma; size %d is larger than expected %d", or.Offset(), PragmaSize) - } return } @@ -69,12 +66,12 @@ func (r *Reader) readHeader() (err error) { } // CarV1Reader provides a reader containing the CAR v1 section encapsulated in this CAR v2. -func (r *Reader) CarV1Reader() *io.SectionReader { +func (r *Reader) CarV1Reader() *io.SectionReader { // TODO consider returning io.Reader+ReaderAt in a custom interface return io.NewSectionReader(r.r, int64(r.Header.CarV1Offset), int64(r.Header.CarV1Size)) } // IndexReader provides an io.Reader containing the carbs.Index of this CAR v2. -func (r *Reader) IndexReader() io.Reader { +func (r *Reader) IndexReader() io.Reader { // TODO consider returning io.Reader+ReaderAt in a custom interface return internalio.NewOffsetReader(r.r, int64(r.Header.IndexOffset)) } From cd8a143d96bba9514f88abe6fbede74a881dace8 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 23 Jun 2021 23:56:47 +0100 Subject: [PATCH 077/291] Revert use of t.TempDir due to access issue in windows In windows clean up of test temp directories fail due to: - Access is denied. Revert the changes to push the PR forward until we investigate why. This commit was moved from ipld/go-car@8a3fdb1ab190c8c3be3952cca03e566d60401d8c --- ipld/car/v2/blockstore/readwrite_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 81d2873350..5b6de52515 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -4,7 +4,6 @@ import ( "io" "math/rand" "os" - "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -16,17 +15,19 @@ import ( ) func TestBlockstore(t *testing.T) { - tempDir := t.TempDir() f, err := os.Open("testdata/test.car") assert.NoError(t, err) defer f.Close() r, err := carv1.NewCarReader(f) assert.NoError(t, err) - path := filepath.Join(tempDir, "/testv2blockstore.car") + path := "testv2blockstore.car" ingester, err := blockstore.NewReadWrite(path, r.Header.Roots) if err != nil { t.Fatal(err) } + defer func() { + os.Remove(path) + }() cids := make([]cid.Cid, 0) for { From 645e7dda2827121a6807e7a2bd4a1c8b1174c39d Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 24 Jun 2021 10:52:02 +0100 Subject: [PATCH 078/291] Rename `ReadPragma` to `ReadVersion` and drop returning roots Change the `ReadPragma` signature to only return the version and drop the roots that _may_ exist if the version is 1. Because, this results in a less error-prone API, and right now it is unclear if we want functionality that should return roots. So, we lean towards providing less functionality unless requested. This commit was moved from ipld/go-car@6bd284c98d5cf558da326e8d68c4277ee6bc4d06 --- ipld/car/v2/reader.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 06b89936df..04b471ae8a 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -25,7 +25,7 @@ func NewReader(r io.ReaderAt) (*Reader, error) { cr := &Reader{ r: r, } - if err := cr.requireV2Pragma(); err != nil { + if err := cr.requireVersion2(); err != nil { return nil, err } if err := cr.readHeader(); err != nil { @@ -34,9 +34,9 @@ func NewReader(r io.ReaderAt) (*Reader, error) { return cr, nil } -func (r *Reader) requireV2Pragma() (err error) { +func (r *Reader) requireVersion2() (err error) { or := internalio.NewOffsetReader(r.r, 0) - version, _, err := ReadPragma(or) + version, err := ReadVersion(or) if err != nil { return } @@ -75,18 +75,13 @@ func (r *Reader) IndexReader() io.Reader { // TODO consider returning io.Reader+ return internalio.NewOffsetReader(r.r, int64(r.Header.IndexOffset)) } -// ReadPragma reads the pragma from r. +// ReadVersion reads the version from the pragma. // This function accepts both CAR v1 and v2 payloads. -// The roots are returned only if the version of pragma equals 1, otherwise returns nil as roots. -func ReadPragma(r io.Reader) (version uint64, roots []cid.Cid, err error) { +func ReadVersion(r io.Reader) (version uint64, err error) { // TODO if the user provides a reader that sufficiently satisfies what carv1.ReadHeader is asking then use that instead of wrapping every time. header, err := carv1.ReadHeader(bufio.NewReader(r)) if err != nil { return } - version = header.Version - if version == 1 { - roots = header.Roots - } - return + return header.Version, nil } From 4857fa30a282b663536d9955d842cb18e9893669 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 24 Jun 2021 12:53:48 +0100 Subject: [PATCH 079/291] Remove dependency to CAR v1 go module Remove dependency to CAR v1 go module, because that module depends on later version of ipld-prime which have not been reflected in filecoin project. In the interest of time, we fork the code that v2 needs in v1. This allows a more efficient implementation of some APIs that accep types like `bufio.Reader` while helping the filecoin team push ahead with deliverables without undergoing a big upgrade process. On the CAR v2 side, this however means that we postpone the implementation of Selective Car for v2 until filecoin is upgraded its ipld-prime dependency. This is to avoid implementing an obsolete selective car API that uses the old ipld-prime API which force user yet another upgrade. This commit was moved from ipld/go-car@ff99aafeec59dc062b7cb8af6619ae36df1af60e --- ipld/car/v2/blockstore/readonly.go | 4 +- ipld/car/v2/blockstore/readwrite.go | 4 +- ipld/car/v2/blockstore/readwrite_test.go | 2 +- ipld/car/v2/car_test.go | 2 +- ipld/car/v2/index/generator.go | 2 +- ipld/car/v2/internal/carv1/car.go | 222 ++++++++++++++++++ ipld/car/v2/internal/carv1/car_test.go | 231 +++++++++++++++++++ ipld/car/v2/internal/carv1/doc.go | 2 + ipld/car/v2/internal/carv1/util/util.go | 121 ++++++++++ ipld/car/v2/internal/carv1/util/util_test.go | 27 +++ ipld/car/v2/reader.go | 2 +- ipld/car/v2/writer.go | 2 +- 12 files changed, 612 insertions(+), 9 deletions(-) create mode 100644 ipld/car/v2/internal/carv1/car.go create mode 100644 ipld/car/v2/internal/carv1/car_test.go create mode 100644 ipld/car/v2/internal/carv1/doc.go create mode 100644 ipld/car/v2/internal/carv1/util/util.go create mode 100644 ipld/car/v2/internal/carv1/util/util_test.go diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index e294f3f63a..354f6dfb0a 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -11,10 +11,10 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" - carv1 "github.com/ipld/go-car" - "github.com/ipld/go-car/util" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" ) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 7bee5b7343..6b03ec51b3 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -7,15 +7,15 @@ import ( "os" blockstore "github.com/ipfs/go-ipfs-blockstore" - carv1 "github.com/ipld/go-car" carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/internal/carv1" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipld/go-car/v2/index" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/ipld/go-car/util" + "github.com/ipld/go-car/v2/internal/carv1/util" ) var ( diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 5b6de52515..63432431f2 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -11,7 +11,7 @@ import ( "github.com/ipld/go-car/v2/blockstore" "github.com/ipfs/go-cid" - carv1 "github.com/ipld/go-car" + "github.com/ipld/go-car/v2/internal/carv1" ) func TestBlockstore(t *testing.T) { diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index 3a3606ff48..5fea95bbc9 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -5,8 +5,8 @@ import ( "bytes" "testing" - carv1 "github.com/ipld/go-car" carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/internal/carv1" "github.com/stretchr/testify/assert" ) diff --git a/ipld/car/v2/index/generator.go b/ipld/car/v2/index/generator.go index 5c1ef7f878..5fad938ccf 100644 --- a/ipld/car/v2/index/generator.go +++ b/ipld/car/v2/index/generator.go @@ -6,7 +6,7 @@ import ( "fmt" "io" - carv1 "github.com/ipld/go-car" + "github.com/ipld/go-car/v2/internal/carv1" internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" ) diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go new file mode 100644 index 0000000000..3886527ff1 --- /dev/null +++ b/ipld/car/v2/internal/carv1/car.go @@ -0,0 +1,222 @@ +package carv1 + +import ( + "bufio" + "context" + "fmt" + "io" + + "github.com/ipld/go-car/v2/internal/carv1/util" + + blocks "github.com/ipfs/go-block-format" + cid "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + format "github.com/ipfs/go-ipld-format" + dag "github.com/ipfs/go-merkledag" +) + +func init() { + cbor.RegisterCborType(CarHeader{}) +} + +type Store interface { + Put(blocks.Block) error +} + +type ReadStore interface { + Get(cid.Cid) (blocks.Block, error) +} + +type CarHeader struct { + Roots []cid.Cid + Version uint64 +} + +type carWriter struct { + ds format.NodeGetter + w io.Writer + walk WalkFunc +} + +type WalkFunc func(format.Node) ([]*format.Link, error) + +func WriteCar(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.Writer) error { + return WriteCarWithWalker(ctx, ds, roots, w, DefaultWalkFunc) +} + +func WriteCarWithWalker(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.Writer, walk WalkFunc) error { + h := &CarHeader{ + Roots: roots, + Version: 1, + } + + if err := WriteHeader(h, w); err != nil { + return fmt.Errorf("failed to write car header: %s", err) + } + + cw := &carWriter{ds: ds, w: w, walk: walk} + seen := cid.NewSet() + for _, r := range roots { + if err := dag.Walk(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { + return err + } + } + return nil +} + +func DefaultWalkFunc(nd format.Node) ([]*format.Link, error) { + return nd.Links(), nil +} + +func ReadHeader(br *bufio.Reader) (*CarHeader, error) { + hb, err := util.LdRead(br) + if err != nil { + return nil, err + } + + var ch CarHeader + if err := cbor.DecodeInto(hb, &ch); err != nil { + return nil, fmt.Errorf("invalid header: %v", err) + } + + return &ch, nil +} + +func WriteHeader(h *CarHeader, w io.Writer) error { + hb, err := cbor.DumpObject(h) + if err != nil { + return err + } + + return util.LdWrite(w, hb) +} + +func HeaderSize(h *CarHeader) (uint64, error) { + hb, err := cbor.DumpObject(h) + if err != nil { + return 0, err + } + + return util.LdSize(hb), nil +} + +func (cw *carWriter) enumGetLinks(ctx context.Context, c cid.Cid) ([]*format.Link, error) { + nd, err := cw.ds.Get(ctx, c) + if err != nil { + return nil, err + } + + if err := cw.writeNode(ctx, nd); err != nil { + return nil, err + } + + return cw.walk(nd) +} + +func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { + return util.LdWrite(cw.w, nd.Cid().Bytes(), nd.RawData()) +} + +type CarReader struct { + br *bufio.Reader + Header *CarHeader +} + +func NewCarReader(r io.Reader) (*CarReader, error) { + br := bufio.NewReader(r) + ch, err := ReadHeader(br) + if err != nil { + return nil, err + } + + if ch.Version != 1 { + return nil, fmt.Errorf("invalid car version: %d", ch.Version) + } + + if len(ch.Roots) == 0 { + return nil, fmt.Errorf("empty car, no roots") + } + + return &CarReader{ + br: br, + Header: ch, + }, nil +} + +func (cr *CarReader) Next() (blocks.Block, error) { + c, data, err := util.ReadNode(cr.br) + if err != nil { + return nil, err + } + + hashed, err := c.Prefix().Sum(data) + if err != nil { + return nil, err + } + + if !hashed.Equals(c) { + return nil, fmt.Errorf("mismatch in content integrity, name: %s, data: %s", c, hashed) + } + + return blocks.NewBlockWithCid(data, c) +} + +type batchStore interface { + PutMany([]blocks.Block) error +} + +func LoadCar(s Store, r io.Reader) (*CarHeader, error) { + cr, err := NewCarReader(r) + if err != nil { + return nil, err + } + + if bs, ok := s.(batchStore); ok { + return loadCarFast(bs, cr) + } + + return loadCarSlow(s, cr) +} + +func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { + var buf []blocks.Block + for { + blk, err := cr.Next() + if err != nil { + if err == io.EOF { + if len(buf) > 0 { + if err := s.PutMany(buf); err != nil { + return nil, err + } + } + return cr.Header, nil + } + return nil, err + } + + buf = append(buf, blk) + + if len(buf) > 1000 { + if err := s.PutMany(buf); err != nil { + return nil, err + } + buf = buf[:0] + } + } +} + +func loadCarSlow(s Store, cr *CarReader) (*CarHeader, error) { + for { + blk, err := cr.Next() + if err != nil { + if err == io.EOF { + return cr.Header, nil + } + return nil, err + } + + if err := s.Put(blk); err != nil { + return nil, err + } + } +} diff --git a/ipld/car/v2/internal/carv1/car_test.go b/ipld/car/v2/internal/carv1/car_test.go new file mode 100644 index 0000000000..b637fcf43f --- /dev/null +++ b/ipld/car/v2/internal/carv1/car_test.go @@ -0,0 +1,231 @@ +package carv1 + +import ( + "bytes" + "context" + "encoding/hex" + "io" + "strings" + "testing" + + cid "github.com/ipfs/go-cid" + format "github.com/ipfs/go-ipld-format" + dag "github.com/ipfs/go-merkledag" + dstest "github.com/ipfs/go-merkledag/test" +) + +func assertAddNodes(t *testing.T, ds format.DAGService, nds ...format.Node) { + for _, nd := range nds { + if err := ds.Add(context.Background(), nd); err != nil { + t.Fatal(err) + } + } +} + +func TestRoundtrip(t *testing.T) { + dserv := dstest.Mock() + a := dag.NewRawNode([]byte("aaaa")) + b := dag.NewRawNode([]byte("bbbb")) + c := dag.NewRawNode([]byte("cccc")) + + nd1 := &dag.ProtoNode{} + nd1.AddNodeLink("cat", a) + + nd2 := &dag.ProtoNode{} + nd2.AddNodeLink("first", nd1) + nd2.AddNodeLink("dog", b) + + nd3 := &dag.ProtoNode{} + nd3.AddNodeLink("second", nd2) + nd3.AddNodeLink("bear", c) + + assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) + + buf := new(bytes.Buffer) + if err := WriteCar(context.Background(), dserv, []cid.Cid{nd3.Cid()}, buf); err != nil { + t.Fatal(err) + } + + bserv := dstest.Bserv() + ch, err := LoadCar(bserv.Blockstore(), buf) + if err != nil { + t.Fatal(err) + } + + if len(ch.Roots) != 1 { + t.Fatal("should have one root") + } + + if !ch.Roots[0].Equals(nd3.Cid()) { + t.Fatal("got wrong cid") + } + + bs := bserv.Blockstore() + for _, nd := range []format.Node{a, b, c, nd1, nd2, nd3} { + has, err := bs.Has(nd.Cid()) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("should have cid in blockstore") + } + } +} + +func TestEOFHandling(t *testing.T) { + // fixture is a clean single-block, single-root CAR + fixture, err := hex.DecodeString("3aa265726f6f747381d82a58250001711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80b6776657273696f6e012c01711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80ba165646f646779f5") + if err != nil { + t.Fatal(err) + } + + load := func(t *testing.T, byts []byte) *CarReader { + cr, err := NewCarReader(bytes.NewReader(byts)) + if err != nil { + t.Fatal(err) + } + + blk, err := cr.Next() + if err != nil { + t.Fatal(err) + } + if blk.Cid().String() != "bafyreiavd7u6opdcm6tqmddpnrgmvfb4enxuwglhenejmchnwqvixd5ibm" { + t.Fatal("unexpected CID") + } + + return cr + } + + t.Run("CleanEOF", func(t *testing.T) { + cr := load(t, fixture) + + blk, err := cr.Next() + if err != io.EOF { + t.Fatal("Didn't get expected EOF") + } + if blk != nil { + t.Fatal("EOF returned expected block") + } + }) + + t.Run("BadVarint", func(t *testing.T) { + fixtureBadVarint := append(fixture, 160) + cr := load(t, fixtureBadVarint) + + blk, err := cr.Next() + if err != io.ErrUnexpectedEOF { + t.Fatal("Didn't get unexpected EOF") + } + if blk != nil { + t.Fatal("EOF returned unexpected block") + } + }) + + t.Run("TruncatedBlock", func(t *testing.T) { + fixtureTruncatedBlock := append(fixture, 100, 0, 0) + cr := load(t, fixtureTruncatedBlock) + + blk, err := cr.Next() + if err != io.ErrUnexpectedEOF { + t.Fatal("Didn't get unexpected EOF") + } + if blk != nil { + t.Fatal("EOF returned unexpected block") + } + }) +} + +func TestBadHeaders(t *testing.T) { + testCases := []struct { + name string + hex string + errStr string // either the whole error string + errPfx string // or just the prefix + }{ + { + "{version:2}", + "0aa16776657273696f6e02", + "invalid car version: 2", + "", + }, + { + // an unfortunate error because we don't use a pointer + "{roots:[baeaaaa3bmjrq]}", + "13a165726f6f747381d82a480001000003616263", + "invalid car version: 0", + "", + }, + { + "{version:\"1\",roots:[baeaaaa3bmjrq]}", + "1da265726f6f747381d82a4800010000036162636776657273696f6e6131", + "", "invalid header: ", + }, + { + "{version:1}", + "0aa16776657273696f6e01", + "empty car, no roots", + "", + }, + { + "{version:1,roots:{cid:baeaaaa3bmjrq}}", + "20a265726f6f7473a163636964d82a4800010000036162636776657273696f6e01", + "", + "invalid header: ", + }, + { + "{version:1,roots:[baeaaaa3bmjrq],blip:true}", + "22a364626c6970f565726f6f747381d82a4800010000036162636776657273696f6e01", + "", + "invalid header: ", + }, + { + "[1,[]]", + "03820180", + "", + "invalid header: ", + }, + { + // this is an unfortunate error, it'd be nice to catch it better but it's + // very unlikely we'd ever see this in practice + "null", + "01f6", + "", + "invalid car version: 0", + }, + } + + makeCar := func(t *testing.T, byts string) error { + fixture, err := hex.DecodeString(byts) + if err != nil { + t.Fatal(err) + } + _, err = NewCarReader(bytes.NewReader(fixture)) + return err + } + + t.Run("Sanity check {version:1,roots:[baeaaaa3bmjrq]}", func(t *testing.T) { + err := makeCar(t, "1ca265726f6f747381d82a4800010000036162636776657273696f6e01") + if err != nil { + t.Fatal(err) + } + }) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := makeCar(t, tc.hex) + if err == nil { + t.Fatal("expected error from bad header, didn't get one") + } + if tc.errStr != "" { + if err.Error() != tc.errStr { + t.Fatalf("bad error: %v", err) + } + } else { + if !strings.HasPrefix(err.Error(), tc.errPfx) { + t.Fatalf("bad error: %v", err) + } + } + }) + } +} diff --git a/ipld/car/v2/internal/carv1/doc.go b/ipld/car/v2/internal/carv1/doc.go new file mode 100644 index 0000000000..a13ffdfc2a --- /dev/null +++ b/ipld/car/v2/internal/carv1/doc.go @@ -0,0 +1,2 @@ +// Forked from CAR v1 to avoid dependency to ipld-prime 0.9.0 due to outstanding upgrades in filecoin. +package carv1 diff --git a/ipld/car/v2/internal/carv1/util/util.go b/ipld/car/v2/internal/carv1/util/util.go new file mode 100644 index 0000000000..08048f333e --- /dev/null +++ b/ipld/car/v2/internal/carv1/util/util.go @@ -0,0 +1,121 @@ +package util + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + + cid "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" +) + +var cidv0Pref = []byte{0x12, 0x20} + +type BytesReader interface { + io.Reader + io.ByteReader +} + +// TODO: this belongs in the go-cid package +func ReadCid(buf []byte) (cid.Cid, int, error) { + if bytes.Equal(buf[:2], cidv0Pref) { + c, err := cid.Cast(buf[:34]) + return c, 34, err + } + + br := bytes.NewReader(buf) + + // assume cidv1 + vers, err := binary.ReadUvarint(br) + if err != nil { + return cid.Cid{}, 0, err + } + + // TODO: the go-cid package allows version 0 here as well + if vers != 1 { + return cid.Cid{}, 0, fmt.Errorf("invalid cid version number") + } + + codec, err := binary.ReadUvarint(br) + if err != nil { + return cid.Cid{}, 0, err + } + + mhr := mh.NewReader(br) + h, err := mhr.ReadMultihash() + if err != nil { + return cid.Cid{}, 0, err + } + + return cid.NewCidV1(codec, h), len(buf) - br.Len(), nil +} + +func ReadNode(br *bufio.Reader) (cid.Cid, []byte, error) { + data, err := LdRead(br) + if err != nil { + return cid.Cid{}, nil, err + } + + c, n, err := ReadCid(data) + if err != nil { + return cid.Cid{}, nil, err + } + + return c, data[n:], nil +} + +func LdWrite(w io.Writer, d ...[]byte) error { + var sum uint64 + for _, s := range d { + sum += uint64(len(s)) + } + + buf := make([]byte, 8) + n := binary.PutUvarint(buf, sum) + _, err := w.Write(buf[:n]) + if err != nil { + return err + } + + for _, s := range d { + _, err = w.Write(s) + if err != nil { + return err + } + } + + return nil +} + +func LdSize(d ...[]byte) uint64 { + var sum uint64 + for _, s := range d { + sum += uint64(len(s)) + } + buf := make([]byte, 8) + n := binary.PutUvarint(buf, sum) + return sum + uint64(n) +} + +func LdRead(r *bufio.Reader) ([]byte, error) { + if _, err := r.Peek(1); err != nil { // no more blocks, likely clean io.EOF + return nil, err + } + + l, err := binary.ReadUvarint(r) + if err != nil { + if err == io.EOF { + return nil, io.ErrUnexpectedEOF // don't silently pretend this is a clean EOF + } + return nil, err + } + + buf := make([]byte, l) + if _, err := io.ReadFull(r, buf); err != nil { + return nil, err + } + + return buf, nil +} diff --git a/ipld/car/v2/internal/carv1/util/util_test.go b/ipld/car/v2/internal/carv1/util/util_test.go new file mode 100644 index 0000000000..76828be9ab --- /dev/null +++ b/ipld/car/v2/internal/carv1/util/util_test.go @@ -0,0 +1,27 @@ +package util_test + +import ( + "bytes" + "math/rand" + "testing" + + "github.com/ipld/go-car/v2/internal/carv1/util" + + "github.com/stretchr/testify/require" +) + +func TestLdSize(t *testing.T) { + for i := 0; i < 5; i++ { + var buf bytes.Buffer + data := make([][]byte, 5) + for j := 0; j < 5; j++ { + data[j] = make([]byte, rand.Intn(30)) + _, err := rand.Read(data[j]) + require.NoError(t, err) + } + size := util.LdSize(data...) + err := util.LdWrite(&buf, data...) + require.NoError(t, err) + require.Equal(t, uint64(len(buf.Bytes())), size) + } +} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 04b471ae8a..f21d3fa04a 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -8,7 +8,7 @@ import ( internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipfs/go-cid" - carv1 "github.com/ipld/go-car" + "github.com/ipld/go-car/v2/internal/carv1" ) // Reader represents a reader of CAR v2. diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index fba4edcd88..68749e7242 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -7,8 +7,8 @@ import ( "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" - carv1 "github.com/ipld/go-car" "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1" ) const bulkPaddingBytesSize = 1024 From 8d4d86a2dad46e39f44f960364036be9fad8193c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 24 Jun 2021 12:44:31 +0100 Subject: [PATCH 080/291] copy v1 license into the v2 module This will allow pkgsite to render the wip-v2 module. This commit was moved from ipld/go-car@567a4c6b505d811bab9e5bcf18b9d88044bf67d4 --- ipld/car/v2/LICENSE.md | 229 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 ipld/car/v2/LICENSE.md diff --git a/ipld/car/v2/LICENSE.md b/ipld/car/v2/LICENSE.md new file mode 100644 index 0000000000..15601cba67 --- /dev/null +++ b/ipld/car/v2/LICENSE.md @@ -0,0 +1,229 @@ +The contents of this repository are Copyright (c) corresponding authors and +contributors, licensed under the `Permissive License Stack` meaning either of: + +- Apache-2.0 Software License: https://www.apache.org/licenses/LICENSE-2.0 + ([...4tr2kfsq](https://gateway.ipfs.io/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) + +- MIT Software License: https://opensource.org/licenses/MIT + ([...vljevcba](https://gateway.ipfs.io/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) + +You may not use the contents of this repository except in compliance +with one of the listed Licenses. For an extended clarification of the +intent behind the choice of Licensing please refer to +https://protocol.ai/blog/announcing-the-permissive-license-stack/ + +Unless required by applicable law or agreed to in writing, software +distributed under the terms listed in this notice is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +either express or implied. See each License for the specific language +governing permissions and limitations under that License. + + +`SPDX-License-Identifier: Apache-2.0 OR MIT` + +Verbatim copies of both licenses are included below: + +

Apache-2.0 Software License + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +``` +
+ +
MIT Software License + +``` +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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. +``` +
From f99c6aa884e01ace9eeaf3b08b5f39fc7ff303fb Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 24 Jun 2021 17:37:13 +0100 Subject: [PATCH 081/291] Remove CAR v1 walk function until it is explicitly asked for We want to simplify the API and not carry over any CAR v1 functionality we don't have to. We also want to encourage users to use the selective car functionality to achieve what walk function did once it is added. This commit was moved from ipld/go-car@2f36f2e4ada95242ac985fe87e604af0f36f4c04 --- ipld/car/v2/blockstore/readwrite.go | 1 + ipld/car/v2/internal/carv1/car.go | 19 ++++--------------- ipld/car/v2/writer.go | 8 ++------ 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 6b03ec51b3..83bdcf0dec 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -56,6 +56,7 @@ func WithIndexPadding(p uint64) Option { // NewReadWrite creates a new ReadWrite at the given path with a provided set of root cids as the car roots. func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { // TODO support resumption if the path provided contains partially written blocks in v2 format. + // TODO either lock the file or open exclusively; can we do somethign to reduce edge cases. f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o666) if err != nil { return nil, fmt.Errorf("could not open read/write file: %w", err) diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index 3886527ff1..d69f3a7b72 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -33,18 +33,11 @@ type CarHeader struct { } type carWriter struct { - ds format.NodeGetter - w io.Writer - walk WalkFunc + ds format.NodeGetter + w io.Writer } -type WalkFunc func(format.Node) ([]*format.Link, error) - func WriteCar(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.Writer) error { - return WriteCarWithWalker(ctx, ds, roots, w, DefaultWalkFunc) -} - -func WriteCarWithWalker(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.Writer, walk WalkFunc) error { h := &CarHeader{ Roots: roots, Version: 1, @@ -54,7 +47,7 @@ func WriteCarWithWalker(ctx context.Context, ds format.NodeGetter, roots []cid.C return fmt.Errorf("failed to write car header: %s", err) } - cw := &carWriter{ds: ds, w: w, walk: walk} + cw := &carWriter{ds: ds, w: w} seen := cid.NewSet() for _, r := range roots { if err := dag.Walk(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { @@ -64,10 +57,6 @@ func WriteCarWithWalker(ctx context.Context, ds format.NodeGetter, roots []cid.C return nil } -func DefaultWalkFunc(nd format.Node) ([]*format.Link, error) { - return nd.Links(), nil -} - func ReadHeader(br *bufio.Reader) (*CarHeader, error) { hb, err := util.LdRead(br) if err != nil { @@ -110,7 +99,7 @@ func (cw *carWriter) enumGetLinks(ctx context.Context, c cid.Cid) ([]*format.Lin return nil, err } - return cw.walk(nd) + return nd.Links(), nil } func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 68749e7242..950acb121b 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -20,7 +20,6 @@ type ( padding uint64 // Writer writes CAR v2 into a give io.Writer. Writer struct { - Walk carv1.WalkFunc IndexCodec index.Codec NodeGetter format.NodeGetter CarV1Padding uint64 @@ -57,10 +56,8 @@ func (p padding) WriteTo(w io.Writer) (n int64, err error) { // NewWriter instantiates a new CAR v2 writer. // The writer instantiated uses `carbs.IndexSorted` as the index codec, -// and `carv1.DefaultWalkFunc` as the default walk function. func NewWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *Writer { return &Writer{ - Walk: carv1.DefaultWalkFunc, IndexCodec: index.IndexSorted, NodeGetter: ng, ctx: ctx, @@ -69,8 +66,7 @@ func NewWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *Writ } } -// WriteTo writes the given root CIDs according to CAR v2 specification, traversing the DAG using the -// Writer.Walk function. +// WriteTo writes the given root CIDs according to CAR v2 specification. func (w *Writer) WriteTo(writer io.Writer) (n int64, err error) { _, err = writer.Write(Pragma) if err != nil { @@ -80,7 +76,7 @@ func (w *Writer) WriteTo(writer io.Writer) (n int64, err error) { // We read the entire car into memory because carbs.Generate takes a reader. // Future PRs will make this more efficient by exposing necessary interfaces in carbs so that // this can be done in an streaming manner. - if err = carv1.WriteCarWithWalker(w.ctx, w.NodeGetter, w.roots, w.encodedCarV1, w.Walk); err != nil { + if err = carv1.WriteCar(w.ctx, w.NodeGetter, w.roots, w.encodedCarV1); err != nil { return } carV1Len := w.encodedCarV1.Len() From 5ccf9fc6bd304741daeee78e4d75a564959369b4 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 24 Jun 2021 19:05:13 +0100 Subject: [PATCH 082/291] Fix the writer index codec to sorted Remove the ability to configure index codec in the CAR v2 writer to avoid unintended complexity, since in the first pass the only supported codec will be sorted. This commit was moved from ipld/go-car@a6127a0def08e0b0115c6f000d80db27c59ea01b --- ipld/car/v2/writer.go | 11 ++++++----- ipld/car/v2/writer_test.go | 2 -- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 950acb121b..1f3365e828 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -11,7 +11,10 @@ import ( "github.com/ipld/go-car/v2/internal/carv1" ) -const bulkPaddingBytesSize = 1024 +const ( + bulkPaddingBytesSize = 1024 + defaultIndexCodex = index.IndexSorted +) var bulkPadding = make([]byte, bulkPaddingBytesSize) @@ -20,7 +23,6 @@ type ( padding uint64 // Writer writes CAR v2 into a give io.Writer. Writer struct { - IndexCodec index.Codec NodeGetter format.NodeGetter CarV1Padding uint64 IndexPadding uint64 @@ -55,10 +57,9 @@ func (p padding) WriteTo(w io.Writer) (n int64, err error) { } // NewWriter instantiates a new CAR v2 writer. -// The writer instantiated uses `carbs.IndexSorted` as the index codec, +// The writer instantiated uses `carbs.IndexSorted` as the index codec. func NewWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *Writer { return &Writer{ - IndexCodec: index.IndexSorted, NodeGetter: ng, ctx: ctx, roots: roots, @@ -126,7 +127,7 @@ func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (n int64, err error) // Consider refactoring carbs to make this process more efficient. // We should avoid reading the entire car into memory since it can be large. reader := bytes.NewReader(carV1) - index, err := index.Generate(reader, index.IndexSorted) + index, err := index.Generate(reader, defaultIndexCodex) if err != nil { return } diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 1db27f9ade..2e48fc3cae 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -9,7 +9,6 @@ import ( format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" - "github.com/ipld/go-car/v2/index" "github.com/stretchr/testify/assert" ) @@ -62,7 +61,6 @@ func TestNewWriter(t *testing.T) { dagService := dstest.Mock() wantRoots := generateRootCid(t, dagService) writer := NewWriter(context.Background(), dagService, wantRoots) - assert.Equal(t, index.IndexSorted, writer.IndexCodec) assert.Equal(t, wantRoots, writer.roots) } From 13bf5d7861ccf5ec97953c7acac46c3125bc9ef0 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 24 Jun 2021 21:41:06 +0100 Subject: [PATCH 083/291] Improve documentation and fix bug in writer Add documentation where it is sparse to help folks learn how to use the library easier. Unexport the ability to select codec, until we decide what codec we want to expose. Fix all encodings of index to `IndexSorted`. Fix a bug in writer where index is written by direct marshalling instead of index.WriteTo which includes index codec. Refactor in places for better readability. This commit was moved from ipld/go-car@c954c990bac2430cc8c04909d74e0a77bb77b069 --- ipld/car/v2/blockstore/doc.go | 14 ++++++++ ipld/car/v2/blockstore/readonly.go | 39 ++++++++++---------- ipld/car/v2/blockstore/readwrite.go | 20 +++++------ ipld/car/v2/blockstore/readwrite_test.go | 2 +- ipld/car/v2/index/doc.go | 12 +++++++ ipld/car/v2/index/errors.go | 5 +-- ipld/car/v2/index/generator.go | 27 ++++++-------- ipld/car/v2/index/index.go | 42 ++++++++++++---------- ipld/car/v2/index/insertionindex.go | 2 +- ipld/car/v2/internal/carbs/util/hydrate.go | 12 ++----- ipld/car/v2/reader.go | 2 +- ipld/car/v2/writer.go | 27 ++++++-------- 12 files changed, 110 insertions(+), 94 deletions(-) create mode 100644 ipld/car/v2/blockstore/doc.go create mode 100644 ipld/car/v2/index/doc.go diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go new file mode 100644 index 0000000000..c2cf62a803 --- /dev/null +++ b/ipld/car/v2/blockstore/doc.go @@ -0,0 +1,14 @@ +// package blockstore implements IPFS blockstore interface backed by a CAR file. +// This package provides two flavours of blockstore: ReadOnly and ReadWrite. +// +// The ReadOnly blockstore provides a read-only random access from a given data payload either in +// unindexed v1 format or indexed/unindexed v2 format: +// - ReadOnly.ReadOnlyOf can be used to instantiate a new read-only blockstore for a given CAR v1 +// data payload and an existing index. See index.Generate for index generation from CAR v1 +// payload. +// - ReadOnly.OpenReadOnly can be used to instantiate a new read-only blockstore for a given CAR v2 +// file with automatic index generation if the index is not present in the given file. This +// function can optionally attach the index to the given CAR v2 file. +// + +package blockstore diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 354f6dfb0a..fa94531fd9 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -26,34 +26,37 @@ var errUnsupported = errors.New("unsupported operation") // ReadOnly provides a read-only Car Block Store. type ReadOnly struct { + // The backing containing the CAR in v1 format backing io.ReaderAt - idx index.Index + // The CAR v1 content index + idx index.Index } -// ReadOnlyOf opens a carbs data store from an existing backing of the base data (i.e. CAR v1 payload) and index. +// ReadOnlyOf opens ReadOnly blockstore from an existing backing containing a CAR v1 payload and an existing index. +// The index for a CAR v1 payload can be separately generated using index.Generate. func ReadOnlyOf(backing io.ReaderAt, index index.Index) *ReadOnly { return &ReadOnly{backing, index} } // OpenReadOnly opens a read-only blockstore from a CAR v2 file, generating an index if it does not exist. -// If noPersist is set to false then the generated index is written into the CAR v2 file at path. -func OpenReadOnly(path string, noPersist bool) (*ReadOnly, error) { +// If attachIndex is set to true and the index is not present in the given CAR v2 file, +// then the generated index is written into the given path. +func OpenReadOnly(path string, attachIndex bool) (*ReadOnly, error) { reader, err := mmap.Open(path) if err != nil { return nil, err } - v2r, err := carv2.NewReader(reader) if err != nil { return nil, err } var idx index.Index if !v2r.Header.HasIndex() { - idx, err := index.Generate(v2r.CarV1Reader(), index.IndexSorted) + idx, err := index.Generate(v2r.CarV1Reader()) if err != nil { return nil, err } - if !noPersist { + if attachIndex { if err := index.Attach(path, idx, v2r.Header.IndexOffset); err != nil { return nil, err } @@ -71,17 +74,17 @@ func OpenReadOnly(path string, noPersist bool) (*ReadOnly, error) { return &obj, nil } -func (b *ReadOnly) read(idx int64) (cid.Cid, []byte, error) { +func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { bcid, data, err := util.ReadNode(bufio.NewReader(internalio.NewOffsetReader(b.backing, idx))) return bcid, data, err } -// DeleteBlock is unsupported and always returns an error +// DeleteBlock is unsupported and always returns an error. func (b *ReadOnly) DeleteBlock(_ cid.Cid) error { return errUnsupported } -// Has indicates if the store has a cid +// Has indicates if the store contains a block that corresponds to the given key. func (b *ReadOnly) Has(key cid.Cid) (bool, error) { offset, err := b.idx.Get(key) if err != nil { @@ -99,13 +102,13 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { return c.Equals(key), nil } -// Get gets a block from the store +// Get gets a block corresponding to the given key. func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { offset, err := b.idx.Get(key) if err != nil { return nil, err } - entry, bytes, err := b.read(int64(offset)) + entry, bytes, err := b.readBlock(int64(offset)) if err != nil { // TODO Improve error handling; not all errors mean NotFound. return nil, blockstore.ErrNotFound @@ -116,7 +119,7 @@ func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { return blocks.NewBlockWithCid(bytes, key) } -// GetSize gets how big a item is +// GetSize gets the size of an item corresponding to the given key. func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { idx, err := b.idx.Get(key) if err != nil { @@ -137,17 +140,17 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { return int(l), err } -// Put is not supported and always returns an error +// Put is not supported and always returns an error. func (b *ReadOnly) Put(blocks.Block) error { return errUnsupported } -// PutMany is not supported and always returns an error +// PutMany is not supported and always returns an error. func (b *ReadOnly) PutMany([]blocks.Block) error { return errUnsupported } -// AllKeysChan returns the list of keys in the store +// AllKeysChan returns the list of keys in the CAR. func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { // TODO we may use this walk for populating the index, and we need to be able to iterate keys in this way somewhere for index generation. In general though, when it's asked for all keys from a blockstore with an index, we should iterate through the index when possible rather than linear reads through the full car. header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(b.backing, 0))) @@ -187,11 +190,11 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { return ch, nil } -// HashOnRead does nothing +// HashOnRead does nothing. func (b *ReadOnly) HashOnRead(bool) { } -// Roots returns the root CIDs of the backing car +// Roots returns the root CIDs of the backing CAR. func (b *ReadOnly) Roots() ([]cid.Cid, error) { header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(b.backing, 0))) if err != nil { diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 83bdcf0dec..909993d30d 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -23,12 +23,14 @@ var ( errFinalized = errors.New("finalized blockstore") ) -// ReadWrite is a carbon implementation based on having two file handles opened, -// one appending to the file, and the other -// seeking to read items as needed. +// ReadWrite implements a blockstore that stores blocks in CAR v2 format. +// Blocks put into the blockstore can be read back once they are successfully written. // This implementation is preferable for a write-heavy workload. +// The blocks are written immediately on Put and PutAll calls, while the index is stored in memory +// and updated incrementally. // The Finalize function must be called once the putting blocks are finished. -// Upon calling Finalize all read and write calls to this blockstore will result in error. +// Upon calling Finalize header is finalized and index is written out. +// Once finalized, all read and write calls to this blockstore will result in error. type ( // TODO consider exposing interfaces ReadWrite struct { @@ -41,19 +43,21 @@ type ( Option func(*ReadWrite) // TODO consider unifying with writer options ) +// WithCarV1Padding sets the padding to be added between CAR v2 header and its data payload on Finalize. func WithCarV1Padding(p uint64) Option { return func(b *ReadWrite) { b.header = b.header.WithCarV1Padding(p) } } +// WithIndexPadding sets the padding between data payload and its index on Finalize. func WithIndexPadding(p uint64) Option { return func(b *ReadWrite) { b.header = b.header.WithIndexPadding(p) } } -// NewReadWrite creates a new ReadWrite at the given path with a provided set of root cids as the car roots. +// NewReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs as the car roots. func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { // TODO support resumption if the path provided contains partially written blocks in v2 format. // TODO either lock the file or open exclusively; can we do somethign to reduce edge cases. @@ -62,7 +66,7 @@ func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, err return nil, fmt.Errorf("could not open read/write file: %w", err) } - indexcls, ok := index.IndexAtlas[index.IndexInsertion] + indexcls, ok := index.BuildersByCodec[index.IndexInsertion] if !ok { return nil, fmt.Errorf("unknownindex codec: %#v", index.IndexInsertion) } @@ -93,10 +97,6 @@ func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, err return b, nil } -func (b *ReadWrite) DeleteBlock(cid.Cid) error { - return errUnsupported -} - // Put puts a given block to the underlying datastore func (b *ReadWrite) Put(blk blocks.Block) error { if b.isFinalized() { diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 63432431f2..f49bdf53f5 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -62,7 +62,7 @@ func TestBlockstore(t *testing.T) { if err := ingester.Finalize(); err != nil { t.Fatal(err) } - carb, err := blockstore.OpenReadOnly(path, true) + carb, err := blockstore.OpenReadOnly(path, false) if err != nil { t.Fatal(err) } diff --git a/ipld/car/v2/index/doc.go b/ipld/car/v2/index/doc.go new file mode 100644 index 0000000000..25a38c34c3 --- /dev/null +++ b/ipld/car/v2/index/doc.go @@ -0,0 +1,12 @@ +// package index provides indexing functionality for CAR v1 data payload represented as a mapping of +// CID to offset. This can then be used to implement random access over a CAR v1. +// +// Index can be written or read using the following static functions: index.WriteTo and +// index.ReadFrom. +// +// This package also provides functionality to generate an index from a given CAR v1 file using: +// index.Generate and index.GenerateFromFile +// +// In addition to the above, it provides functionality to attach an index to an index-less CAR v2 +// using index.Attach +package index diff --git a/ipld/car/v2/index/errors.go b/ipld/car/v2/index/errors.go index c45c24020b..6d9c89c19a 100644 --- a/ipld/car/v2/index/errors.go +++ b/ipld/car/v2/index/errors.go @@ -3,7 +3,8 @@ package index import "errors" var ( - // errNotFound is returned for lookups to entries that don't exist - errNotFound = errors.New("not found") + // errNotFound signals a record is not found in the index. + errNotFound = errors.New("not found") + // errUnsupported signals unsupported operation by an index. errUnsupported = errors.New("not supported") ) diff --git a/ipld/car/v2/index/generator.go b/ipld/car/v2/index/generator.go index 5fad938ccf..b7621af858 100644 --- a/ipld/car/v2/index/generator.go +++ b/ipld/car/v2/index/generator.go @@ -11,13 +11,9 @@ import ( "golang.org/x/exp/mmap" ) -// Generate generates index given car v1 payload using the given codec. -func Generate(car io.ReaderAt, codec Codec) (Index, error) { - indexcls, ok := IndexAtlas[codec] - if !ok { - return nil, fmt.Errorf("unknown codec: %#v", codec) - } - +// Generate generates index for a given car in v1 format. +// The index can be stored using index.Save into a file or serialized using index.WriteTo. +func Generate(car io.ReaderAt) (Index, error) { header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(car, 0))) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) @@ -27,7 +23,7 @@ func Generate(car io.ReaderAt, codec Codec) (Index, error) { return nil, err } - idx := indexcls() + idx := mkSorted() records := make([]Record, 0) rdr := internalio.NewOffsetReader(car, int64(offset)) @@ -56,16 +52,13 @@ func Generate(car io.ReaderAt, codec Codec) (Index, error) { return idx, nil } -// GenerateFromFile walks a car v1 file and generates an index of cid->byte offset, then -// stors it in a separate file at the given path with extension `.idx`. -func GenerateFromFile(path string, codec Codec) error { +// GenerateFromFile walks a car v1 file at the give path and generates an index of cid->byte offset. +// The index can be stored using index.Save into a file or serialized using index.WriteTo. +func GenerateFromFile(path string) (Index, error) { store, err := mmap.Open(path) if err != nil { - return err - } - idx, err := Generate(store, codec) - if err != nil { - return err + return nil, err } - return Save(idx, path) + defer store.Close() + return Generate(store) } diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index edaf271e30..971d581de0 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -12,7 +12,7 @@ import ( "github.com/ipfs/go-cid" ) -// Codec table is a first var-int in carbs indexes +// Codec table is a first var-int in CAR indexes const ( IndexHashed Codec = iota + 0x300000 IndexSorted @@ -22,11 +22,11 @@ const ( ) type ( - // Codec is used as a multicodec identifier for carbs index files + // Codec is used as a multicodec identifier for CAR index files Codec int - // IndexCls is a constructor for an index type - IndexCls func() Index + // Builder is a constructor for an index type + Builder func() Index // Record is a pre-processed record of a car item and location. Record struct { @@ -34,7 +34,7 @@ type ( Idx uint64 } - // Index provides an interface for figuring out where in the car a given cid begins + // Index provides an interface for looking up byte offset of a given CID. Index interface { Codec() Codec Marshal(w io.Writer) error @@ -44,8 +44,8 @@ type ( } ) -// IndexAtlas holds known index formats -var IndexAtlas = map[Codec]IndexCls{ +// BuildersByCodec holds known index formats +var BuildersByCodec = map[Codec]Builder{ IndexHashed: mkHashed, IndexSorted: mkSorted, IndexSingleSorted: mkSingleSorted, @@ -53,9 +53,9 @@ var IndexAtlas = map[Codec]IndexCls{ IndexInsertion: mkInsertion, } -// Save writes a generated index into the given `path` as a file with a `.idx` extension. +// Save writes a generated index into the given `path`. func Save(idx Index, path string) error { - stream, err := os.OpenFile(path+".idx", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) + stream, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) if err != nil { return err } @@ -74,28 +74,34 @@ func Attach(path string, idx Index, offset uint64) error { return WriteTo(idx, indexWriter) } -func WriteTo(i Index, w io.Writer) error { +// WriteTo writes the given idx into w. +// The written bytes include the index encoding. +// This can then be read back using index.ReadFrom +func WriteTo(idx Index, w io.Writer) error { buf := make([]byte, binary.MaxVarintLen64) - b := binary.PutUvarint(buf, uint64(i.Codec())) + b := binary.PutUvarint(buf, uint64(idx.Codec())) if _, err := w.Write(buf[:b]); err != nil { return err } - return i.Marshal(w) + return idx.Marshal(w) } -func ReadFrom(uar io.Reader) (Index, error) { - reader := bufio.NewReader(uar) +// ReadFrom reads index from r. +// The reader decodes the index by reading the first byte to interpret the encoding. +// Returns error if the encoding is not known. +func ReadFrom(r io.Reader) (Index, error) { + reader := bufio.NewReader(r) codec, err := binary.ReadUvarint(reader) if err != nil { return nil, err } - idx, ok := IndexAtlas[Codec(codec)] + builder, ok := BuildersByCodec[Codec(codec)] if !ok { return nil, fmt.Errorf("unknown codec: %d", codec) } - idxInst := idx() - if err := idxInst.Unmarshal(reader); err != nil { + idx := builder() + if err := idx.Unmarshal(reader); err != nil { return nil, err } - return idxInst, nil + return idx, nil } diff --git a/ipld/car/v2/index/insertionindex.go b/ipld/car/v2/index/insertionindex.go index d64be077fc..5182105782 100644 --- a/ipld/car/v2/index/insertionindex.go +++ b/ipld/car/v2/index/insertionindex.go @@ -125,7 +125,7 @@ func mkInsertion() Index { // Flatten returns a 'indexsorted' formatted index for more efficient subsequent loading func (ii *InsertionIndex) Flatten() (Index, error) { - si := IndexAtlas[IndexSorted]() + si := BuildersByCodec[IndexSorted]() rcrds := make([]Record, ii.items.Len()) idx := 0 diff --git a/ipld/car/v2/internal/carbs/util/hydrate.go b/ipld/car/v2/internal/carbs/util/hydrate.go index c1e8533a6d..eead86cf16 100644 --- a/ipld/car/v2/internal/carbs/util/hydrate.go +++ b/ipld/car/v2/internal/carbs/util/hydrate.go @@ -10,18 +10,10 @@ import ( func main() { if len(os.Args) < 2 { - fmt.Printf("Usage: hydrate [codec]\n") + fmt.Printf("Usage: hydrate \n") return } db := os.Args[1] - codec := index.IndexSorted - if len(os.Args) == 3 { - if os.Args[2] == "Hash" { - codec = index.IndexHashed - } else if os.Args[2] == "GobHash" { - codec = index.IndexGobHashed - } - } dbBacking, err := mmap.Open(db) if err != nil { @@ -29,7 +21,7 @@ func main() { return } - idx, err := index.Generate(dbBacking, codec) + idx, err := index.Generate(dbBacking) if err != nil { fmt.Printf("Error generating index: %v\n", err) return diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index f21d3fa04a..c300f4873c 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -70,7 +70,7 @@ func (r *Reader) CarV1Reader() *io.SectionReader { // TODO consider returning io return io.NewSectionReader(r.r, int64(r.Header.CarV1Offset), int64(r.Header.CarV1Size)) } -// IndexReader provides an io.Reader containing the carbs.Index of this CAR v2. +// IndexReader provides an io.Reader containing the index of this CAR v2. func (r *Reader) IndexReader() io.Reader { // TODO consider returning io.Reader+ReaderAt in a custom interface return internalio.NewOffsetReader(r.r, int64(r.Header.IndexOffset)) } diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 1f3365e828..c425b2385d 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -11,10 +11,7 @@ import ( "github.com/ipld/go-car/v2/internal/carv1" ) -const ( - bulkPaddingBytesSize = 1024 - defaultIndexCodex = index.IndexSorted -) +const bulkPaddingBytesSize = 1024 var bulkPadding = make([]byte, bulkPaddingBytesSize) @@ -57,7 +54,6 @@ func (p padding) WriteTo(w io.Writer) (n int64, err error) { } // NewWriter instantiates a new CAR v2 writer. -// The writer instantiated uses `carbs.IndexSorted` as the index codec. func NewWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *Writer { return &Writer{ NodeGetter: ng, @@ -74,8 +70,8 @@ func (w *Writer) WriteTo(writer io.Writer) (n int64, err error) { return } n += int64(PragmaSize) - // We read the entire car into memory because carbs.Generate takes a reader. - // Future PRs will make this more efficient by exposing necessary interfaces in carbs so that + // We read the entire car into memory because index.Generate takes a reader. + // TODO Future PRs will make this more efficient by exposing necessary interfaces in index pacakge so that // this can be done in an streaming manner. if err = carv1.WriteCar(w.ctx, w.NodeGetter, w.roots, w.encodedCarV1); err != nil { return @@ -121,17 +117,16 @@ func (w *Writer) writeHeader(writer io.Writer, carV1Len int) (int64, error) { return header.WriteTo(writer) } -func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (n int64, err error) { - // TODO avoid recopying the bytes by refactoring carbs once it is integrated here. - // Right now we copy the bytes since carbs takes a writer. - // Consider refactoring carbs to make this process more efficient. +func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (int64, error) { + // TODO avoid recopying the bytes by refactoring index once it is integrated here. + // Right now we copy the bytes since index takes a writer. + // Consider refactoring index to make this process more efficient. // We should avoid reading the entire car into memory since it can be large. reader := bytes.NewReader(carV1) - index, err := index.Generate(reader, defaultIndexCodex) + idx, err := index.Generate(reader) if err != nil { - return + return 0, err } - err = index.Marshal(writer) - // FIXME refactor carbs to expose the number of bytes written. - return + // FIXME refactor index to expose the number of bytes written. + return 0, index.WriteTo(idx, writer) } From 3aeda868aaeb1e8eca512db9be54d9e03030adeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Fri, 25 Jun 2021 10:11:28 +0100 Subject: [PATCH 084/291] index: use the IndexSorted multicodec Also add TODOs about cleaning up the API. We can't do that right now, as it would require a refactor in the other packages. This commit was moved from ipld/go-car@48d5a7f2741fffc757e1e6be5ff829301a158a05 --- ipld/car/v2/index/index.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 971d581de0..0b96c5f3da 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -14,8 +14,10 @@ import ( // Codec table is a first var-int in CAR indexes const ( - IndexHashed Codec = iota + 0x300000 - IndexSorted + IndexSorted Codec = 0x0400 // as per https://github.com/multiformats/multicodec/pull/220 + + // TODO: unexport these before the final release, probably + IndexHashed Codec = 0x300000 + iota IndexSingleSorted IndexGobHashed IndexInsertion @@ -23,6 +25,7 @@ const ( type ( // Codec is used as a multicodec identifier for CAR index files + // TODO: use go-multicodec before the final release Codec int // Builder is a constructor for an index type @@ -45,6 +48,7 @@ type ( ) // BuildersByCodec holds known index formats +// TODO: turn this into a func before the final release? var BuildersByCodec = map[Codec]Builder{ IndexHashed: mkHashed, IndexSorted: mkSorted, From fbce43c3c404984821d4e52d0cb1479f70c1eb17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Fri, 25 Jun 2021 13:56:11 +0100 Subject: [PATCH 085/291] add Close methods and NewReaderMmap Without close methods, it's currently impossible to close the underlying mmap file. NewReaderMmap is technically unnecessary, but also helpful. This commit was moved from ipld/go-car@cacae149167772d730b41040731f594d896171d9 --- ipld/car/v2/blockstore/readonly.go | 34 +++++++++++++++++--------- ipld/car/v2/reader.go | 39 ++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index fa94531fd9..14ff75d655 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -16,7 +16,6 @@ import ( "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" - "golang.org/x/exp/mmap" ) var _ blockstore.Blockstore = (*ReadOnly)(nil) @@ -26,30 +25,31 @@ var errUnsupported = errors.New("unsupported operation") // ReadOnly provides a read-only Car Block Store. type ReadOnly struct { - // The backing containing the CAR in v1 format + // The backing containing the CAR in v1 format. backing io.ReaderAt - // The CAR v1 content index + // The CAR v1 content index. idx index.Index + + // If we called carv2.NewReaderMmap, remember to close it too. + carv2Closer io.Closer } // ReadOnlyOf opens ReadOnly blockstore from an existing backing containing a CAR v1 payload and an existing index. // The index for a CAR v1 payload can be separately generated using index.Generate. func ReadOnlyOf(backing io.ReaderAt, index index.Index) *ReadOnly { - return &ReadOnly{backing, index} + return &ReadOnly{backing: backing, idx: index} } // OpenReadOnly opens a read-only blockstore from a CAR v2 file, generating an index if it does not exist. // If attachIndex is set to true and the index is not present in the given CAR v2 file, // then the generated index is written into the given path. func OpenReadOnly(path string, attachIndex bool) (*ReadOnly, error) { - reader, err := mmap.Open(path) - if err != nil { - return nil, err - } - v2r, err := carv2.NewReader(reader) + + v2r, err := carv2.NewReaderMmap(path) if err != nil { return nil, err } + var idx index.Index if !v2r.Header.HasIndex() { idx, err := index.Generate(v2r.CarV1Reader()) @@ -68,8 +68,9 @@ func OpenReadOnly(path string, attachIndex bool) (*ReadOnly, error) { } } obj := ReadOnly{ - backing: v2r.CarV1Reader(), - idx: idx, + backing: v2r.CarV1Reader(), + idx: idx, + carv2Closer: v2r, } return &obj, nil } @@ -190,8 +191,9 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { return ch, nil } -// HashOnRead does nothing. +// HashOnRead is currently unimplemented; hashing on reads never happens. func (b *ReadOnly) HashOnRead(bool) { + // TODO: implement before the final release? } // Roots returns the root CIDs of the backing CAR. @@ -202,3 +204,11 @@ func (b *ReadOnly) Roots() ([]cid.Cid, error) { } return header.Roots, nil } + +// Close closes the underlying reader if it was opened by OpenReadOnly. +func (b *ReadOnly) Close() error { + if b.carv2Closer != nil { + return b.carv2Closer.Close() + } + return nil +} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index c300f4873c..d410ef879b 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -9,13 +9,32 @@ import ( "github.com/ipfs/go-cid" "github.com/ipld/go-car/v2/internal/carv1" + "golang.org/x/exp/mmap" ) // Reader represents a reader of CAR v2. type Reader struct { - Header Header - r io.ReaderAt - roots []cid.Cid + Header Header + r io.ReaderAt + roots []cid.Cid + carv2Closer io.Closer +} + +// NewReaderMmap is a wrapper for NewReader which opens the file at path with +// x/exp/mmap. +func NewReaderMmap(path string) (*Reader, error) { + f, err := mmap.Open(path) + if err != nil { + return nil, err + } + + r, err := NewReader(f) + if err != nil { + return nil, err + } + + r.carv2Closer = f + return r, nil } // NewReader constructs a new reader that reads CAR v2 from the given r. @@ -66,15 +85,25 @@ func (r *Reader) readHeader() (err error) { } // CarV1Reader provides a reader containing the CAR v1 section encapsulated in this CAR v2. -func (r *Reader) CarV1Reader() *io.SectionReader { // TODO consider returning io.Reader+ReaderAt in a custom interface +func (r *Reader) CarV1Reader() *io.SectionReader { + // TODO consider returning io.Reader+ReaderAt in a custom interface return io.NewSectionReader(r.r, int64(r.Header.CarV1Offset), int64(r.Header.CarV1Size)) } // IndexReader provides an io.Reader containing the index of this CAR v2. -func (r *Reader) IndexReader() io.Reader { // TODO consider returning io.Reader+ReaderAt in a custom interface +func (r *Reader) IndexReader() io.Reader { + // TODO consider returning io.Reader+ReaderAt in a custom interface return internalio.NewOffsetReader(r.r, int64(r.Header.IndexOffset)) } +// Close closes the underlying reader if it was opened by NewReaderMmap. +func (r *Reader) Close() error { + if r.carv2Closer != nil { + return r.carv2Closer.Close() + } + return nil +} + // ReadVersion reads the version from the pragma. // This function accepts both CAR v1 and v2 payloads. func ReadVersion(r io.Reader) (version uint64, err error) { From 97a018a472f629b8561ac1f80d6e837181fb2117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 28 Jun 2021 09:03:38 +0100 Subject: [PATCH 086/291] blockstore: close AllKeysChan channel when done The added test case hangs without the fix. Updates #110. This commit was moved from ipld/go-car@73d15aedda1b0c82c30df18cff9fccc595fd7abf --- ipld/car/v2/blockstore/readonly.go | 11 ++++++----- ipld/car/v2/blockstore/readwrite_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 14ff75d655..a1bc8d1fd4 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -163,27 +163,28 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { return nil, err } + // TODO: document this choice of 5, or use simpler buffering like 0 or 1. ch := make(chan cid.Cid, 5) + go func() { - done := ctx.Done() + defer close(ch) rdr := internalio.NewOffsetReader(b.backing, int64(offset)) for { l, err := binary.ReadUvarint(rdr) thisItemForNxt := rdr.Offset() if err != nil { - return + return // TODO: log this error } c, _, err := internalio.ReadCid(b.backing, thisItemForNxt) if err != nil { - return + return // TODO: log this error } rdr.SeekOffset(thisItemForNxt + int64(l)) select { case ch <- c: - continue - case <-done: + case <-ctx.Done(): return } } diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index f49bdf53f5..e35dae9534 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -1,10 +1,12 @@ package blockstore_test import ( + "context" "io" "math/rand" "os" "testing" + "time" "github.com/stretchr/testify/assert" @@ -15,6 +17,9 @@ import ( ) func TestBlockstore(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + f, err := os.Open("testdata/test.car") assert.NoError(t, err) defer f.Close() @@ -67,6 +72,25 @@ func TestBlockstore(t *testing.T) { t.Fatal(err) } + allKeysCh, err := carb.AllKeysChan(ctx) + if err != nil { + t.Fatal(err) + } + numKeysCh := 0 + for c := range allKeysCh { + b, err := carb.Get(c) + if err != nil { + t.Fatal(err) + } + if !b.Cid().Equals(c) { + t.Fatal("wrong item returned") + } + numKeysCh++ + } + if numKeysCh != len(cids) { + t.Fatal("AllKeysChan returned an unexpected amount of keys") + } + for _, c := range cids { b, err := carb.Get(c) if err != nil { From 14bc11d6dd42096b631cf8cdcedef1490551d01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 28 Jun 2021 10:45:42 +0100 Subject: [PATCH 087/291] blockstore: make unsupported methods panic That is, calling write methods on a read-only blockstore, or any methods on a finalized blockstore. The user's code should never be calling these, so there's no reason to avoid panics. There is one good reason to use panics, though: if anywhere in a program those rules aren't followed, a panic is much more obvious and quick to spot than an error which could just get logged or ignored. If a user wants a read-only blockstore that returns errors on write funcs, they could always wrap our read-only blockstore to do that. Arguably that's their best option anyway, so they are in control of the exact behavior they need. Also fix a typo in carV1Wrtier. This commit was moved from ipld/go-car@c25c2351cfe777e24f8dbbf3432bdbbfcacf83e1 --- ipld/car/v2/blockstore/readonly.go | 10 ++-- ipld/car/v2/blockstore/readwrite.go | 84 +++++++++++++---------------- 2 files changed, 40 insertions(+), 54 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index a1bc8d1fd4..eb0b6b378a 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -4,7 +4,6 @@ import ( "bufio" "context" "encoding/binary" - "errors" "fmt" "io" @@ -20,9 +19,6 @@ import ( var _ blockstore.Blockstore = (*ReadOnly)(nil) -// errUnsupported is returned for unsupported operations -var errUnsupported = errors.New("unsupported operation") - // ReadOnly provides a read-only Car Block Store. type ReadOnly struct { // The backing containing the CAR in v1 format. @@ -82,7 +78,7 @@ func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { // DeleteBlock is unsupported and always returns an error. func (b *ReadOnly) DeleteBlock(_ cid.Cid) error { - return errUnsupported + panic("called write method on a read-only blockstore") } // Has indicates if the store contains a block that corresponds to the given key. @@ -143,12 +139,12 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { // Put is not supported and always returns an error. func (b *ReadOnly) Put(blocks.Block) error { - return errUnsupported + panic("called write method on a read-only blockstore") } // PutMany is not supported and always returns an error. func (b *ReadOnly) PutMany([]blocks.Block) error { - return errUnsupported + panic("called write method on a read-only blockstore") } // AllKeysChan returns the list of keys in the CAR. diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 909993d30d..f72d01e6cc 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -2,7 +2,6 @@ package blockstore import ( "context" - "errors" "fmt" "os" @@ -18,10 +17,7 @@ import ( "github.com/ipld/go-car/v2/internal/carv1/util" ) -var ( - _ blockstore.Blockstore = (*ReadWrite)(nil) - errFinalized = errors.New("finalized blockstore") -) +var _ blockstore.Blockstore = (*ReadWrite)(nil) // ReadWrite implements a blockstore that stores blocks in CAR v2 format. // Blocks put into the blockstore can be read back once they are successfully written. @@ -30,18 +26,17 @@ var ( // and updated incrementally. // The Finalize function must be called once the putting blocks are finished. // Upon calling Finalize header is finalized and index is written out. -// Once finalized, all read and write calls to this blockstore will result in error. -type ( - // TODO consider exposing interfaces - ReadWrite struct { - f *os.File - carV1Wrtier *internalio.OffsetWriter - ReadOnly - idx *index.InsertionIndex - header carv2.Header - } - Option func(*ReadWrite) // TODO consider unifying with writer options -) +// Once finalized, all read and write calls to this blockstore will result in panics. +type ReadWrite struct { + f *os.File + carV1Writer *internalio.OffsetWriter + ReadOnly + idx *index.InsertionIndex + header carv2.Header +} + +// TODO consider exposing interfaces +type Option func(*ReadWrite) // TODO consider unifying with writer options // WithCarV1Padding sets the padding to be added between CAR v2 header and its data payload on Finalize. func WithCarV1Padding(p uint64) Option { @@ -80,7 +75,7 @@ func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, err for _, opt := range opts { opt(b) } - b.carV1Wrtier = internalio.NewOffsetWriter(f, int64(b.header.CarV1Offset)) + b.carV1Writer = internalio.NewOffsetWriter(f, int64(b.header.CarV1Offset)) carV1Reader := internalio.NewOffsetReader(f, int64(b.header.CarV1Offset)) b.ReadOnly = *ReadOnlyOf(carV1Reader, idx) if _, err := f.WriteAt(carv2.Pragma, 0); err != nil { @@ -91,29 +86,33 @@ func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, err Roots: roots, Version: 1, } - if err := carv1.WriteHeader(v1Header, b.carV1Wrtier); err != nil { + if err := carv1.WriteHeader(v1Header, b.carV1Writer); err != nil { return nil, fmt.Errorf("couldn't write car header: %w", err) } return b, nil } +func (b *ReadWrite) panicIfFinalized() { + if b.header.CarV1Size != 0 { + panic("must not use a read-write blockstore after finalizing") + } +} + // Put puts a given block to the underlying datastore func (b *ReadWrite) Put(blk blocks.Block) error { - if b.isFinalized() { - return errFinalized - } + b.panicIfFinalized() + return b.PutMany([]blocks.Block{blk}) } // PutMany puts a slice of blocks at the same time using batching // capabilities of the underlying datastore whenever possible. func (b *ReadWrite) PutMany(blks []blocks.Block) error { - if b.isFinalized() { - return errFinalized - } + b.panicIfFinalized() + for _, bl := range blks { - n := uint64(b.carV1Wrtier.Position()) - if err := util.LdWrite(b.carV1Wrtier, bl.Cid().Bytes(), bl.RawData()); err != nil { + n := uint64(b.carV1Writer.Position()) + if err := util.LdWrite(b.carV1Writer, bl.Cid().Bytes(), bl.RawData()); err != nil { return err } b.idx.InsertNoReplace(bl.Cid(), n) @@ -121,20 +120,15 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { return nil } -func (b *ReadWrite) isFinalized() bool { - return b.header.CarV1Size != 0 -} - // Finalize finalizes this blockstore by writing the CAR v2 header, along with flattened index // for more efficient subsequent read. // After this call, this blockstore can no longer be used for read or write. func (b *ReadWrite) Finalize() error { - if b.isFinalized() { - return errFinalized - } + b.panicIfFinalized() + // TODO check if add index option is set and don't write the index then set index offset to zero. // TODO see if folks need to continue reading from a finalized blockstore, if so return ReadOnly blockstore here. - b.header = b.header.WithCarV1Size(uint64(b.carV1Wrtier.Position())) + b.header = b.header.WithCarV1Size(uint64(b.carV1Writer.Position())) defer b.f.Close() if _, err := b.header.WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)); err != nil { return err @@ -148,29 +142,25 @@ func (b *ReadWrite) Finalize() error { } func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - if b.isFinalized() { - return nil, errFinalized - } + b.panicIfFinalized() + return b.ReadOnly.AllKeysChan(ctx) } func (b *ReadWrite) Has(key cid.Cid) (bool, error) { - if b.isFinalized() { - return false, errFinalized - } + b.panicIfFinalized() + return b.ReadOnly.Has(key) } func (b *ReadWrite) Get(key cid.Cid) (blocks.Block, error) { - if b.isFinalized() { - return nil, errFinalized - } + b.panicIfFinalized() + return b.ReadOnly.Get(key) } func (b *ReadWrite) GetSize(key cid.Cid) (int, error) { - if b.isFinalized() { - return 0, errFinalized - } + b.panicIfFinalized() + return b.ReadOnly.GetSize(key) } From 0d1a833586b3e3586bacd643a35165cb567d3f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 28 Jun 2021 18:10:08 +0100 Subject: [PATCH 088/291] consistently use CID hashes for uniqueness The blockstore used comparisons on the entire CID for Has/Get to succeed, and the indexes generally used the inner multihash's digest to uniquely identify indexed CID-block pairs. The blockstore interface is generally understood to understand CID uniqueness by hash, and it's what go-ipfs's datastore does, so let's do the same. Add a test case for what broke with that inconsistency. Updates #110. This commit was moved from ipld/go-car@bd5908e3899a993c3a2865ad7c0b6d8070f0e506 --- ipld/car/v2/blockstore/readonly.go | 10 ++-- ipld/car/v2/blockstore/readwrite_test.go | 65 ++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index eb0b6b378a..486e9781ea 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -2,6 +2,7 @@ package blockstore import ( "bufio" + "bytes" "context" "encoding/binary" "fmt" @@ -40,7 +41,6 @@ func ReadOnlyOf(backing io.ReaderAt, index index.Index) *ReadOnly { // If attachIndex is set to true and the index is not present in the given CAR v2 file, // then the generated index is written into the given path. func OpenReadOnly(path string, attachIndex bool) (*ReadOnly, error) { - v2r, err := carv2.NewReaderMmap(path) if err != nil { return nil, err @@ -96,7 +96,7 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { if err != nil { return false, err } - return c.Equals(key), nil + return bytes.Equal(key.Hash(), c.Hash()), nil } // Get gets a block corresponding to the given key. @@ -105,15 +105,15 @@ func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { if err != nil { return nil, err } - entry, bytes, err := b.readBlock(int64(offset)) + entry, data, err := b.readBlock(int64(offset)) if err != nil { // TODO Improve error handling; not all errors mean NotFound. return nil, blockstore.ErrNotFound } - if !entry.Equals(key) { + if !bytes.Equal(key.Hash(), entry.Hash()) { return nil, blockstore.ErrNotFound } - return blocks.NewBlockWithCid(bytes, key) + return blocks.NewBlockWithCid(data, key) } // GetSize gets the size of an item corresponding to the given key. diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index e35dae9534..d362a8b686 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -8,10 +8,12 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" + "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" "github.com/ipld/go-car/v2/blockstore" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" "github.com/ipld/go-car/v2/internal/carv1" ) @@ -21,10 +23,10 @@ func TestBlockstore(t *testing.T) { defer cancel() f, err := os.Open("testdata/test.car") - assert.NoError(t, err) + require.NoError(t, err) defer f.Close() r, err := carv1.NewCarReader(f) - assert.NoError(t, err) + require.NoError(t, err) path := "testv2blockstore.car" ingester, err := blockstore.NewReadWrite(path, r.Header.Roots) if err != nil { @@ -40,7 +42,7 @@ func TestBlockstore(t *testing.T) { if err == io.EOF { break } - assert.NoError(t, err) + require.NoError(t, err) if err := ingester.Put(b); err != nil { t.Fatal(err) @@ -101,3 +103,58 @@ func TestBlockstore(t *testing.T) { } } } + +func TestBlockstorePutSameHashes(t *testing.T) { + path := "testv2blockstore.car" + wbs, err := blockstore.NewReadWrite(path, nil) + if err != nil { + t.Fatal(err) + } + defer func() { os.Remove(path) }() + + var blockList []blocks.Block + + addBlock := func(data []byte, version, codec uint64) { + c, err := cid.Prefix{ + Version: version, + Codec: codec, + MhType: multihash.SHA2_256, + MhLength: -1, + }.Sum(data) + require.NoError(t, err) + + block, err := blocks.NewBlockWithCid(data, c) + require.NoError(t, err) + + blockList = append(blockList, block) + } + + data1 := []byte("foo bar") + addBlock(data1, 0, cid.Raw) + addBlock(data1, 1, cid.Raw) + addBlock(data1, 1, cid.DagCBOR) + + data2 := []byte("foo bar baz") + addBlock(data2, 0, cid.Raw) + addBlock(data2, 1, cid.Raw) + addBlock(data2, 1, cid.DagCBOR) + + for _, block := range blockList { + err = wbs.Put(block) + require.NoError(t, err) + } + + for _, block := range blockList { + has, err := wbs.Has(block.Cid()) + require.NoError(t, err) + require.True(t, has) + + got, err := wbs.Get(block.Cid()) + require.NoError(t, err) + require.Equal(t, block.Cid(), got.Cid()) + require.Equal(t, block.RawData(), got.RawData()) + } + + err = wbs.Finalize() + require.NoError(t, err) +} From ff6bfc1875cbb33ea2e1d3083ca92783b98fcfbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 29 Jun 2021 10:24:04 +0100 Subject: [PATCH 089/291] blockstore: don't error on Has not finding a CID Fixes #116. This commit was moved from ipld/go-car@acc013c1e000d43b4da747d1582a788e08b3c5d7 --- ipld/car/v2/blockstore/readonly.go | 5 ++++- ipld/car/v2/blockstore/readwrite_test.go | 25 ++++++++++++++++-------- ipld/car/v2/index/errors.go | 4 ++-- ipld/car/v2/index/indexgobhash.go | 2 +- ipld/car/v2/index/indexhashed.go | 2 +- ipld/car/v2/index/indexsorted.go | 2 +- ipld/car/v2/index/insertionindex.go | 2 +- 7 files changed, 27 insertions(+), 15 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 486e9781ea..3bdc57a768 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "encoding/binary" + "errors" "fmt" "io" @@ -84,7 +85,9 @@ func (b *ReadOnly) DeleteBlock(_ cid.Cid) error { // Has indicates if the store contains a block that corresponds to the given key. func (b *ReadOnly) Has(key cid.Cid) (bool, error) { offset, err := b.idx.Get(key) - if err != nil { + if errors.Is(err, index.ErrNotFound) { + return false, nil + } else if err != nil { return false, err } uar := internalio.NewOffsetReader(b.backing, int64(offset)) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index d362a8b686..ba08c9c5e8 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -114,7 +114,7 @@ func TestBlockstorePutSameHashes(t *testing.T) { var blockList []blocks.Block - addBlock := func(data []byte, version, codec uint64) { + appendBlock := func(data []byte, version, codec uint64) { c, err := cid.Prefix{ Version: version, Codec: codec, @@ -130,16 +130,25 @@ func TestBlockstorePutSameHashes(t *testing.T) { } data1 := []byte("foo bar") - addBlock(data1, 0, cid.Raw) - addBlock(data1, 1, cid.Raw) - addBlock(data1, 1, cid.DagCBOR) + appendBlock(data1, 0, cid.Raw) + appendBlock(data1, 1, cid.Raw) + appendBlock(data1, 1, cid.DagCBOR) data2 := []byte("foo bar baz") - addBlock(data2, 0, cid.Raw) - addBlock(data2, 1, cid.Raw) - addBlock(data2, 1, cid.DagCBOR) + appendBlock(data2, 0, cid.Raw) + appendBlock(data2, 1, cid.Raw) + appendBlock(data2, 1, cid.DagCBOR) + + for i, block := range blockList { + // Has should never error here. + // The first block should be missing. + // Others might not, given the duplicate hashes. + has, err := wbs.Has(block.Cid()) + require.NoError(t, err) + if i == 0 { + require.False(t, has) + } - for _, block := range blockList { err = wbs.Put(block) require.NoError(t, err) } diff --git a/ipld/car/v2/index/errors.go b/ipld/car/v2/index/errors.go index 6d9c89c19a..fba7afba9a 100644 --- a/ipld/car/v2/index/errors.go +++ b/ipld/car/v2/index/errors.go @@ -3,8 +3,8 @@ package index import "errors" var ( - // errNotFound signals a record is not found in the index. - errNotFound = errors.New("not found") + // ErrNotFound signals a record is not found in the index. + ErrNotFound = errors.New("not found") // errUnsupported signals unsupported operation by an index. errUnsupported = errors.New("not supported") ) diff --git a/ipld/car/v2/index/indexgobhash.go b/ipld/car/v2/index/indexgobhash.go index ce2768a9fe..9e61001fa1 100644 --- a/ipld/car/v2/index/indexgobhash.go +++ b/ipld/car/v2/index/indexgobhash.go @@ -12,7 +12,7 @@ type mapGobIndex map[cid.Cid]uint64 func (m *mapGobIndex) Get(c cid.Cid) (uint64, error) { el, ok := (*m)[c] if !ok { - return 0, errNotFound + return 0, ErrNotFound } return el, nil } diff --git a/ipld/car/v2/index/indexhashed.go b/ipld/car/v2/index/indexhashed.go index c64e5cd844..b24a9014ae 100644 --- a/ipld/car/v2/index/indexhashed.go +++ b/ipld/car/v2/index/indexhashed.go @@ -12,7 +12,7 @@ type mapIndex map[cid.Cid]uint64 func (m *mapIndex) Get(c cid.Cid) (uint64, error) { el, ok := (*m)[c] if !ok { - return 0, errNotFound + return 0, ErrNotFound } return el, nil } diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 2646d08b74..3f7a2617a4 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -120,7 +120,7 @@ func (m *multiWidthIndex) Get(c cid.Cid) (uint64, error) { if s, ok := (*m)[uint32(len(d.Digest)+8)]; ok { return s.get(d.Digest), nil } - return 0, errNotFound + return 0, ErrNotFound } func (m *multiWidthIndex) Codec() Codec { diff --git a/ipld/car/v2/index/insertionindex.go b/ipld/car/v2/index/insertionindex.go index 5182105782..10b83ebaab 100644 --- a/ipld/car/v2/index/insertionindex.go +++ b/ipld/car/v2/index/insertionindex.go @@ -59,7 +59,7 @@ func (ii *InsertionIndex) Get(c cid.Cid) (uint64, error) { entry := recordDigest{digest: d.Digest} e := ii.items.Get(entry) if e == nil { - return 0, errNotFound + return 0, ErrNotFound } r, ok := e.(recordDigest) if !ok { From 81641ca92cb9672f0d7778854819e833ff162542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 29 Jun 2021 12:47:54 +0100 Subject: [PATCH 090/291] blockstore: make it safe for concurrent use IPLD traversals would sporadically fail due to data races, since they do concurrent blockstore Puts. The added test reproduced that kind of error very reliably. Fixes #121. This commit was moved from ipld/go-car@307cc4cc87c5672eff4058259d12a3de33b36822 --- ipld/car/v2/blockstore/readonly.go | 23 +++++++++++++ ipld/car/v2/blockstore/readwrite.go | 9 +++-- ipld/car/v2/blockstore/readwrite_test.go | 44 ++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 3bdc57a768..2db1c9e08a 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "sync" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" @@ -23,6 +24,14 @@ var _ blockstore.Blockstore = (*ReadOnly)(nil) // ReadOnly provides a read-only Car Block Store. type ReadOnly struct { + // mu allows ReadWrite to be safe for concurrent use. + // It's in ReadOnly so that read operations also grab read locks, + // given that ReadWrite embeds ReadOnly for methods like Get and Has. + // + // The main fields guarded by the mutex are the index and the underlying writers. + // For simplicity, the entirety of the blockstore methods grab the mutex. + mu sync.RWMutex + // The backing containing the CAR in v1 format. backing io.ReaderAt // The CAR v1 content index. @@ -84,6 +93,9 @@ func (b *ReadOnly) DeleteBlock(_ cid.Cid) error { // Has indicates if the store contains a block that corresponds to the given key. func (b *ReadOnly) Has(key cid.Cid) (bool, error) { + b.mu.RLock() + defer b.mu.RUnlock() + offset, err := b.idx.Get(key) if errors.Is(err, index.ErrNotFound) { return false, nil @@ -104,6 +116,9 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { // Get gets a block corresponding to the given key. func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { + b.mu.RLock() + defer b.mu.RUnlock() + offset, err := b.idx.Get(key) if err != nil { return nil, err @@ -121,6 +136,9 @@ func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { // GetSize gets the size of an item corresponding to the given key. func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { + b.mu.RLock() + defer b.mu.RUnlock() + idx, err := b.idx.Get(key) if err != nil { return -1, err @@ -152,6 +170,9 @@ func (b *ReadOnly) PutMany([]blocks.Block) error { // AllKeysChan returns the list of keys in the CAR. func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + // We release the lock when the channel-sending goroutine stops. + b.mu.RLock() + // TODO we may use this walk for populating the index, and we need to be able to iterate keys in this way somewhere for index generation. In general though, when it's asked for all keys from a blockstore with an index, we should iterate through the index when possible rather than linear reads through the full car. header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(b.backing, 0))) if err != nil { @@ -166,6 +187,8 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { ch := make(chan cid.Cid, 5) go func() { + defer b.mu.RUnlock() + defer close(ch) rdr := internalio.NewOffsetReader(b.backing, int64(offset)) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index f72d01e6cc..fef65a6c8c 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -100,8 +100,7 @@ func (b *ReadWrite) panicIfFinalized() { // Put puts a given block to the underlying datastore func (b *ReadWrite) Put(blk blocks.Block) error { - b.panicIfFinalized() - + // PutMany already calls panicIfFinalized. return b.PutMany([]blocks.Block{blk}) } @@ -110,6 +109,9 @@ func (b *ReadWrite) Put(blk blocks.Block) error { func (b *ReadWrite) PutMany(blks []blocks.Block) error { b.panicIfFinalized() + b.mu.Lock() + defer b.mu.Unlock() + for _, bl := range blks { n := uint64(b.carV1Writer.Position()) if err := util.LdWrite(b.carV1Writer, bl.Cid().Bytes(), bl.RawData()); err != nil { @@ -126,6 +128,9 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { func (b *ReadWrite) Finalize() error { b.panicIfFinalized() + b.mu.Lock() + defer b.mu.Unlock() + // TODO check if add index option is set and don't write the index then set index offset to zero. // TODO see if folks need to continue reading from a finalized blockstore, if so return ReadOnly blockstore here. b.header = b.header.WithCarV1Size(uint64(b.carV1Writer.Position())) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index ba08c9c5e8..b4a138c930 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -2,9 +2,11 @@ package blockstore_test import ( "context" + "fmt" "io" "math/rand" "os" + "sync" "testing" "time" @@ -167,3 +169,45 @@ func TestBlockstorePutSameHashes(t *testing.T) { err = wbs.Finalize() require.NoError(t, err) } + +func TestBlockstoreConcurrentUse(t *testing.T) { + path := "testv2blockstore.car" + wbs, err := blockstore.NewReadWrite(path, nil) + if err != nil { + t.Fatal(err) + } + defer func() { os.Remove(path) }() + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + data := []byte(fmt.Sprintf("data-%d", i)) + + wg.Add(1) + go func() { + defer wg.Done() + + c, err := cid.Prefix{ + Version: 1, + Codec: cid.Raw, + MhType: multihash.SHA2_256, + MhLength: -1, + }.Sum(data) + require.NoError(t, err) + + block, err := blocks.NewBlockWithCid(data, c) + require.NoError(t, err) + + has, err := wbs.Has(block.Cid()) + require.NoError(t, err) + require.False(t, has) + + err = wbs.Put(block) + require.NoError(t, err) + + got, err := wbs.Get(block.Cid()) + require.NoError(t, err) + require.Equal(t, data, got.RawData()) + }() + } + wg.Wait() +} From 7b4ca3a1a3cdb26d80e5ed4f66b66111a9f399ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Wed, 30 Jun 2021 14:44:34 +0100 Subject: [PATCH 091/291] add WrapV1File See the added doc and example. Also needed to make index marshaling deterministic, as per the spec. This commit was moved from ipld/go-car@26113397838d1d5a5b39f98ed7434b0186137bc5 --- ipld/car/v2/example_test.go | 50 ++++++++++++++++ ipld/car/v2/index/generator.go | 4 ++ ipld/car/v2/index/indexsorted.go | 18 +++++- ipld/car/v2/testdata/sample-v1.car | Bin 0 -> 479907 bytes ipld/car/v2/testdata/sample-wrapped-v2.car | Bin 0 -> 521859 bytes ipld/car/v2/writer.go | 66 +++++++++++++++++++++ 6 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 ipld/car/v2/example_test.go create mode 100644 ipld/car/v2/testdata/sample-v1.car create mode 100644 ipld/car/v2/testdata/sample-wrapped-v2.car diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go new file mode 100644 index 0000000000..70d8ed6328 --- /dev/null +++ b/ipld/car/v2/example_test.go @@ -0,0 +1,50 @@ +package car_test + +import ( + "bytes" + "fmt" + "io/ioutil" + + carv2 "github.com/ipld/go-car/v2" +) + +func ExampleWrapV1File() { + // We have a sample CARv1 file. + // Wrap it as-is in a CARv2, with an index. + // Writing the result to testdata allows reusing that file in other tests, + // and also helps ensure that the result is deterministic. + src := "testdata/sample-v1.car" + dst := "testdata/sample-wrapped-v2.car" + if err := carv2.WrapV1File(src, dst); err != nil { + panic(err) + } + + // Open our new CARv2 file and show some info about it. + cr, err := carv2.NewReaderMmap(dst) + if err != nil { + panic(err) + } + defer cr.Close() + roots, err := cr.Roots() + if err != nil { + panic(err) + } + fmt.Println("Roots:", roots) + fmt.Println("Has index:", cr.Header.HasIndex()) + + // Verify that the CARv1 remains exactly the same. + orig, err := ioutil.ReadFile(src) + if err != nil { + panic(err) + } + inner, err := ioutil.ReadAll(cr.CarV1Reader()) + if err != nil { + panic(err) + } + fmt.Println("Inner CARv1 is exactly the same:", bytes.Equal(orig, inner)) + + // Output: + // Roots: [bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy] + // Has index: true + // Inner CARv1 is exactly the same: true +} diff --git a/ipld/car/v2/index/generator.go b/ipld/car/v2/index/generator.go index b7621af858..f65de3eccd 100644 --- a/ipld/car/v2/index/generator.go +++ b/ipld/car/v2/index/generator.go @@ -18,6 +18,10 @@ func Generate(car io.ReaderAt) (Index, error) { if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } + + // TODO: Generate should likely just take an io.ReadSeeker. + // TODO: ensure the input's header version is 1. + offset, err := carv1.HeaderSize(header) if err != nil { return nil, err diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 3f7a2617a4..b7d275103f 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -53,6 +53,7 @@ func (s *singleWidthIndex) Marshal(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, int64(len(s.index))); err != nil { return err } + // TODO: we could just w.Write(s.index) here and avoid overhead _, err := io.Copy(w, bytes.NewBuffer(s.index)) return err } @@ -129,8 +130,21 @@ func (m *multiWidthIndex) Codec() Codec { func (m *multiWidthIndex) Marshal(w io.Writer) error { binary.Write(w, binary.LittleEndian, int32(len(*m))) - for _, s := range *m { - if err := s.Marshal(w); err != nil { + + // The widths are unique, but ranging over a map isn't deterministic. + // As per the CARv2 spec, we must order buckets by digest length. + + widths := make([]uint32, 0, len(*m)) + for width := range *m { + widths = append(widths, width) + } + sort.Slice(widths, func(i, j int) bool { + return widths[i] < widths[j] + }) + + for _, width := range widths { + bucket := (*m)[width] + if err := bucket.Marshal(w); err != nil { return err } } diff --git a/ipld/car/v2/testdata/sample-v1.car b/ipld/car/v2/testdata/sample-v1.car new file mode 100644 index 0000000000000000000000000000000000000000..47a61c8c2a7def9bafcc252d3a1a4d12529615f5 GIT binary patch literal 479907 zcmdSBRZtz;(yooW>%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� 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 Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ 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

hRd> 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

vHSrHL`4Y!))mrU|+My=J*?NCLvdFH?W^2AuF;5 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

ecf&5+ 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 z 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í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 G@Bag@W-Mg@ literal 0 HcmV?d00001 diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index c425b2385d..8c9f504e0e 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "io" + "os" "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" @@ -130,3 +131,68 @@ func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (int64, error) { // FIXME refactor index to expose the number of bytes written. return 0, index.WriteTo(idx, writer) } + +// WrapV1File takes a source path to a CARv1 file and wraps it as a CARv2 file +// with an index, writing the result to the destination path. +// The resulting CARv2 file's inner CARv1 payload is left unmodified, +// and does not use any padding before the innner CARv1 or index. +func WrapV1File(srcPath, dstPath string) error { + // TODO: verify src is indeed a CARv1 to prevent misuse. + // index.Generate should probably be in charge of that. + + // TODO: also expose WrapV1(io.ReadSeeker, io.Writer), + // once index.Generate takes a ReadSeeker. + + // We don't use mmap.Open, so we can later use io.Copy. + f1, err := os.Open(srcPath) + if err != nil { + return err + } + defer f1.Close() + + idx, err := index.Generate(f1) + if err != nil { + return err + } + + // Use Seek to learn the size of the CARv1 before reading it. + v1Size, err := f1.Seek(0, io.SeekEnd) + if err != nil { + return err + } + if _, err := f1.Seek(0, io.SeekStart); err != nil { + return err + } + + // Only create the destination CARv2 when we've gathered all the + // information we need, such as the index and the CARv1 size. + f2, err := os.Create(dstPath) + if err != nil { + return err + } + defer f2.Close() + + // Similar to the Writer API, write all components of a CARv2 to the + // destination file: Pragma, Header, CARv1, Index. + v2Header := NewHeader(uint64(v1Size)) + if _, err := f2.Write(Pragma); err != nil { + return err + } + if _, err := v2Header.WriteTo(f2); err != nil { + return err + } + if _, err := io.Copy(f2, f1); err != nil { + return err + } + if err := index.WriteTo(idx, f2); err != nil { + return err + } + + // Check the close error, since we're writing to f2. + // Note that we also do a "defer f2.Close()" above, + // to make sure that the earlier error returns don't leak the file. + if err := f2.Close(); err != nil { + return err + } + return nil +} From 3b981370b8eacc920696a1cc65cf7960d447c7a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 1 Jul 2021 13:11:49 +0100 Subject: [PATCH 092/291] blockstore: add option to deduplicate by CID And a test that uses duplicate hashes as well as duplicate CIDs. We reuse the same insertion index, since it's enough for this purpose. There's no need to keep a separate map or set of CIDs. While at it, make the index package not silently swallow errors, and improve the tests to handle errors more consistently. Fixes #123. Fixes #125. This commit was moved from ipld/go-car@08f751c83f924b3b52ef5dac1b32edf06553772c --- ipld/car/v2/blockstore/readwrite.go | 26 +++++- ipld/car/v2/blockstore/readwrite_test.go | 103 ++++++++++++++--------- ipld/car/v2/index/insertionindex.go | 30 ++++++- 3 files changed, 113 insertions(+), 46 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index fef65a6c8c..99fd11d2a6 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -33,6 +33,8 @@ type ReadWrite struct { ReadOnly idx *index.InsertionIndex header carv2.Header + + dedupCids bool } // TODO consider exposing interfaces @@ -52,6 +54,15 @@ func WithIndexPadding(p uint64) Option { } } +// WithCidDeduplication makes Put calls ignore blocks if the blockstore already +// has the exact same CID. +// This can help avoid redundancy in a CARv1's list of CID-Block pairs. +// +// Note that this compares whole CIDs, not just multihashes. +func WithCidDeduplication(b *ReadWrite) { + b.dedupCids = true +} + // NewReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs as the car roots. func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { // TODO support resumption if the path provided contains partially written blocks in v2 format. @@ -113,11 +124,16 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { defer b.mu.Unlock() for _, bl := range blks { + c := bl.Cid() + if b.dedupCids && b.idx.HasExactCID(c) { + continue + } + n := uint64(b.carV1Writer.Position()) - if err := util.LdWrite(b.carV1Writer, bl.Cid().Bytes(), bl.RawData()); err != nil { + if err := util.LdWrite(b.carV1Writer, c.Bytes(), bl.RawData()); err != nil { return err } - b.idx.InsertNoReplace(bl.Cid(), n) + b.idx.InsertNoReplace(c, n) } return nil } @@ -126,7 +142,11 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { // for more efficient subsequent read. // After this call, this blockstore can no longer be used for read or write. func (b *ReadWrite) Finalize() error { - b.panicIfFinalized() + if b.header.CarV1Size != 0 { + // Allow duplicate Finalize calls, just like Close. + // Still error, just like ReadOnly.Close; it should be discarded. + return fmt.Errorf("called Finalize twice") + } b.mu.Lock() defer b.mu.Unlock() diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index b4a138c930..3b66b3c4ff 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -6,6 +6,7 @@ import ( "io" "math/rand" "os" + "path/filepath" "sync" "testing" "time" @@ -26,17 +27,14 @@ func TestBlockstore(t *testing.T) { f, err := os.Open("testdata/test.car") require.NoError(t, err) - defer f.Close() + t.Cleanup(func() { f.Close() }) r, err := carv1.NewCarReader(f) require.NoError(t, err) - path := "testv2blockstore.car" + + path := filepath.Join(t.TempDir(), "readwrite.car") ingester, err := blockstore.NewReadWrite(path, r.Header.Roots) - if err != nil { - t.Fatal(err) - } - defer func() { - os.Remove(path) - }() + require.NoError(t, err) + t.Cleanup(func() { ingester.Finalize() }) cids := make([]cid.Cid, 0) for { @@ -46,9 +44,8 @@ func TestBlockstore(t *testing.T) { } require.NoError(t, err) - if err := ingester.Put(b); err != nil { - t.Fatal(err) - } + err = ingester.Put(b) + require.NoError(t, err) cids = append(cids, b.Cid()) // try reading a random one: @@ -60,32 +57,24 @@ func TestBlockstore(t *testing.T) { for _, c := range cids { b, err := ingester.Get(c) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if !b.Cid().Equals(c) { t.Fatal("wrong item returned") } } - if err := ingester.Finalize(); err != nil { - t.Fatal(err) - } + err = ingester.Finalize() + require.NoError(t, err) carb, err := blockstore.OpenReadOnly(path, false) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + t.Cleanup(func() { carb.Close() }) allKeysCh, err := carb.AllKeysChan(ctx) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) numKeysCh := 0 for c := range allKeysCh { b, err := carb.Get(c) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if !b.Cid().Equals(c) { t.Fatal("wrong item returned") } @@ -97,9 +86,7 @@ func TestBlockstore(t *testing.T) { for _, c := range cids { b, err := carb.Get(c) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if !b.Cid().Equals(c) { t.Fatal("wrong item returned") } @@ -107,12 +94,19 @@ func TestBlockstore(t *testing.T) { } func TestBlockstorePutSameHashes(t *testing.T) { - path := "testv2blockstore.car" - wbs, err := blockstore.NewReadWrite(path, nil) - if err != nil { - t.Fatal(err) - } - defer func() { os.Remove(path) }() + tdir := t.TempDir() + wbs, err := blockstore.NewReadWrite( + filepath.Join(tdir, "readwrite.car"), nil, + ) + require.NoError(t, err) + t.Cleanup(func() { wbs.Finalize() }) + + wbsd, err := blockstore.NewReadWrite( + filepath.Join(tdir, "readwrite-dedup.car"), nil, + blockstore.WithCidDeduplication, + ) + require.NoError(t, err) + t.Cleanup(func() { wbsd.Finalize() }) var blockList []blocks.Block @@ -131,16 +125,32 @@ func TestBlockstorePutSameHashes(t *testing.T) { blockList = append(blockList, block) } + // Two raw blocks, meaning we have two unique multihashes. + // However, we have multiple CIDs for each multihash. + // We also have two duplicate CIDs. data1 := []byte("foo bar") appendBlock(data1, 0, cid.Raw) appendBlock(data1, 1, cid.Raw) appendBlock(data1, 1, cid.DagCBOR) + appendBlock(data1, 1, cid.DagCBOR) // duplicate CID data2 := []byte("foo bar baz") appendBlock(data2, 0, cid.Raw) appendBlock(data2, 1, cid.Raw) + appendBlock(data2, 1, cid.Raw) // duplicate CID appendBlock(data2, 1, cid.DagCBOR) + countBlocks := func(bs *blockstore.ReadWrite) int { + ch, err := bs.AllKeysChan(context.Background()) + require.NoError(t, err) + + n := 0 + for range ch { + n++ + } + return n + } + for i, block := range blockList { // Has should never error here. // The first block should be missing. @@ -166,17 +176,28 @@ func TestBlockstorePutSameHashes(t *testing.T) { require.Equal(t, block.RawData(), got.RawData()) } + require.Equal(t, len(blockList), countBlocks(wbs)) + err = wbs.Finalize() require.NoError(t, err) + + // Put the same list of blocks to the blockstore that + // deduplicates by CID. + // We should end up with two fewer blocks. + for _, block := range blockList { + err = wbsd.Put(block) + require.NoError(t, err) + } + require.Equal(t, len(blockList)-2, countBlocks(wbsd)) + + err = wbsd.Finalize() + require.NoError(t, err) } func TestBlockstoreConcurrentUse(t *testing.T) { - path := "testv2blockstore.car" - wbs, err := blockstore.NewReadWrite(path, nil) - if err != nil { - t.Fatal(err) - } - defer func() { os.Remove(path) }() + wbs, err := blockstore.NewReadWrite(filepath.Join(t.TempDir(), "readwrite.car"), nil) + require.NoError(t, err) + t.Cleanup(func() { wbs.Finalize() }) var wg sync.WaitGroup for i := 0; i < 100; i++ { diff --git a/ipld/car/v2/index/insertionindex.go b/ipld/car/v2/index/insertionindex.go index 10b83ebaab..78dace1df2 100644 --- a/ipld/car/v2/index/insertionindex.go +++ b/ipld/car/v2/index/insertionindex.go @@ -36,7 +36,7 @@ func (r recordDigest) Less(than llrb.Item) bool { func mkRecord(r Record) recordDigest { d, err := multihash.Decode(r.Hash()) if err != nil { - return recordDigest{} + panic(err) } return recordDigest{d.Digest, r} @@ -45,7 +45,7 @@ func mkRecord(r Record) recordDigest { func mkRecordFromCid(c cid.Cid, at uint64) recordDigest { d, err := multihash.Decode(c.Hash()) if err != nil { - return recordDigest{} + panic(err) } return recordDigest{d.Digest, Record{Cid: c, Idx: at}} @@ -141,3 +141,29 @@ func (ii *InsertionIndex) Flatten() (Index, error) { } return si, nil } + +func (ii *InsertionIndex) HasExactCID(c cid.Cid) bool { + d, err := multihash.Decode(c.Hash()) + if err != nil { + panic(err) + } + entry := recordDigest{digest: d.Digest} + + found := false + iter := func(i llrb.Item) bool { + existing := i.(recordDigest) + if !bytes.Equal(existing.digest, entry.digest) { + // We've already looked at all entries with matching digests. + return false + } + if existing.Record.Cid == c { + // We found an exact match. + found = true + return false + } + // Continue looking in ascending order. + return true + } + ii.items.AscendGreaterOrEqual(entry, iter) + return found +} From 70b38cf10eca3db621a7696ecad54074a788db63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 1 Jul 2021 16:45:41 +0100 Subject: [PATCH 093/291] replace util.ReadCid with go-cid go-cid's CidFromBytes does exactly what we want already. This commit was moved from ipld/go-car@695f454f334c63296ddb5f2293b3f3a09ec322e9 --- ipld/car/v2/internal/carv1/util/util.go | 41 +------------------------ 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/ipld/car/v2/internal/carv1/util/util.go b/ipld/car/v2/internal/carv1/util/util.go index 08048f333e..9db5f8c152 100644 --- a/ipld/car/v2/internal/carv1/util/util.go +++ b/ipld/car/v2/internal/carv1/util/util.go @@ -2,63 +2,24 @@ package util import ( "bufio" - "bytes" "encoding/binary" - "fmt" "io" cid "github.com/ipfs/go-cid" - mh "github.com/multiformats/go-multihash" ) -var cidv0Pref = []byte{0x12, 0x20} - type BytesReader interface { io.Reader io.ByteReader } -// TODO: this belongs in the go-cid package -func ReadCid(buf []byte) (cid.Cid, int, error) { - if bytes.Equal(buf[:2], cidv0Pref) { - c, err := cid.Cast(buf[:34]) - return c, 34, err - } - - br := bytes.NewReader(buf) - - // assume cidv1 - vers, err := binary.ReadUvarint(br) - if err != nil { - return cid.Cid{}, 0, err - } - - // TODO: the go-cid package allows version 0 here as well - if vers != 1 { - return cid.Cid{}, 0, fmt.Errorf("invalid cid version number") - } - - codec, err := binary.ReadUvarint(br) - if err != nil { - return cid.Cid{}, 0, err - } - - mhr := mh.NewReader(br) - h, err := mhr.ReadMultihash() - if err != nil { - return cid.Cid{}, 0, err - } - - return cid.NewCidV1(codec, h), len(buf) - br.Len(), nil -} - func ReadNode(br *bufio.Reader) (cid.Cid, []byte, error) { data, err := LdRead(br) if err != nil { return cid.Cid{}, nil, err } - c, n, err := ReadCid(data) + n, c, err := cid.CidFromBytes(data) if err != nil { return cid.Cid{}, nil, err } From aec932caf62036ffd2bc0b19d1647fc4e0a21263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Fri, 2 Jul 2021 18:40:58 +0100 Subject: [PATCH 094/291] replace internal/io.ReadCid with cid.CidFromReader The go-cid change hasn't been merged yet, but it's already had two reviews, and it looks like it will get merged early next week. We can move to go-cid master before the final release. This commit was moved from ipld/go-car@5f5aa1c026457fe8a0f3ab8468e6574cd7b05633 --- ipld/car/v2/blockstore/readonly.go | 8 ++--- ipld/car/v2/index/generator.go | 5 +-- ipld/car/v2/internal/io/cid.go | 53 ------------------------------ 3 files changed, 7 insertions(+), 59 deletions(-) delete mode 100644 ipld/car/v2/internal/io/cid.go diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 2db1c9e08a..ac603001e1 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -107,7 +107,7 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { if err != nil { return false, err } - c, _, err := internalio.ReadCid(b.backing, uar.Offset()) + _, c, err := cid.CidFromReader(uar) if err != nil { return false, err } @@ -147,7 +147,7 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { if err != nil { return -1, blockstore.ErrNotFound } - c, _, err := internalio.ReadCid(b.backing, int64(idx+l)) + _, c, err := cid.CidFromReader(internalio.NewOffsetReader(b.backing, int64(idx+l))) if err != nil { return 0, err } @@ -194,11 +194,11 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { rdr := internalio.NewOffsetReader(b.backing, int64(offset)) for { l, err := binary.ReadUvarint(rdr) - thisItemForNxt := rdr.Offset() if err != nil { return // TODO: log this error } - c, _, err := internalio.ReadCid(b.backing, thisItemForNxt) + thisItemForNxt := rdr.Offset() + _, c, err := cid.CidFromReader(rdr) if err != nil { return // TODO: log this error } diff --git a/ipld/car/v2/index/generator.go b/ipld/car/v2/index/generator.go index f65de3eccd..08b32fda96 100644 --- a/ipld/car/v2/index/generator.go +++ b/ipld/car/v2/index/generator.go @@ -6,6 +6,7 @@ import ( "fmt" "io" + "github.com/ipfs/go-cid" "github.com/ipld/go-car/v2/internal/carv1" internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" @@ -34,14 +35,14 @@ func Generate(car io.ReaderAt) (Index, error) { for { thisItemIdx := rdr.Offset() l, err := binary.ReadUvarint(rdr) - thisItemForNxt := rdr.Offset() if err != nil { if err == io.EOF { break } return nil, err } - c, _, err := internalio.ReadCid(car, thisItemForNxt) + thisItemForNxt := rdr.Offset() + _, c, err := cid.CidFromReader(rdr) if err != nil { return nil, err } diff --git a/ipld/car/v2/internal/io/cid.go b/ipld/car/v2/internal/io/cid.go deleted file mode 100644 index ee348e2578..0000000000 --- a/ipld/car/v2/internal/io/cid.go +++ /dev/null @@ -1,53 +0,0 @@ -package io - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - - "github.com/ipfs/go-cid" - "github.com/multiformats/go-multihash" -) - -var cidv0Pref = []byte{0x12, 0x20} - -func ReadCid(store io.ReaderAt, at int64) (cid.Cid, int, error) { - var tag [2]byte - if _, err := store.ReadAt(tag[:], at); err != nil { - return cid.Undef, 0, err - } - if bytes.Equal(tag[:], cidv0Pref) { - cid0 := make([]byte, 34) - if _, err := store.ReadAt(cid0, at); err != nil { - return cid.Undef, 0, err - } - c, err := cid.Cast(cid0) - return c, 34, err - } - - // assume cidv1 - br := NewOffsetReader(store, at) - vers, err := binary.ReadUvarint(br) - if err != nil { - return cid.Cid{}, 0, err - } - - // TODO: the go-cid package allows version 0 here as well - if vers != 1 { - return cid.Cid{}, 0, fmt.Errorf("invalid cid version number: %d", vers) - } - - codec, err := binary.ReadUvarint(br) - if err != nil { - return cid.Cid{}, 0, err - } - - mhr := multihash.NewReader(br) - h, err := mhr.ReadMultihash() - if err != nil { - return cid.Cid{}, 0, err - } - - return cid.NewCidV1(codec, h), int(br.Offset() - at), nil -} From 473e128146139972ce5790f1384e220a0d3dbaf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 5 Jul 2021 13:06:19 +0100 Subject: [PATCH 095/291] avoid exposing io.SectionReader in the API As per the TODO. Also delete the other TODO. An index doesn't have any paddings or large chunks to be skipped, so ReaderAt is not necessary nor useful. The index package's ReadFrom works on a Reader, too. This commit was moved from ipld/go-car@eaeb24be1aef199755ac567d87adf05ff9af4bc3 --- ipld/car/v2/reader.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index d410ef879b..d8b822635c 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -84,15 +84,22 @@ func (r *Reader) readHeader() (err error) { return } +// SectionReader implements both io.ReadSeeker and io.ReaderAt. +// It is the interface version of io.SectionReader, but note that the +// implementation is not guaranteed to be an io.SectionReader. +type SectionReader interface { + io.Reader + io.Seeker + io.ReaderAt +} + // CarV1Reader provides a reader containing the CAR v1 section encapsulated in this CAR v2. -func (r *Reader) CarV1Reader() *io.SectionReader { - // TODO consider returning io.Reader+ReaderAt in a custom interface +func (r *Reader) CarV1Reader() SectionReader { return io.NewSectionReader(r.r, int64(r.Header.CarV1Offset), int64(r.Header.CarV1Size)) } // IndexReader provides an io.Reader containing the index of this CAR v2. func (r *Reader) IndexReader() io.Reader { - // TODO consider returning io.Reader+ReaderAt in a custom interface return internalio.NewOffsetReader(r.r, int64(r.Header.IndexOffset)) } From 3942e43faa351dfb7d27a05a399b44e23fdedc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 5 Jul 2021 16:38:11 +0100 Subject: [PATCH 096/291] add carv2.WrapV1 based on io interfaces This requires index.Generate to work on io.ReadSeeker, which is a good idea anyway, as it just needs to read and seek. The main advantage of ReaderAt is that it allows concurrent reads, but index generation is sequential by nature. Plus, it only needs seeking to skip blocks; it doesn't need random access to the carv1. Also make the WrapV1 example test that the resulting carv2 works, including the index. Fixes #129. This commit was moved from ipld/go-car@e54e2356d000fd860984b41ee584bc943dcf6a27 --- ipld/car/v2/example_test.go | 9 +++ ipld/car/v2/index/generator.go | 51 +++++++++++----- ipld/car/v2/internal/carbs/util/hydrate.go | 9 +-- ipld/car/v2/writer.go | 69 ++++++++++++---------- 4 files changed, 84 insertions(+), 54 deletions(-) diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go index 70d8ed6328..93dedd7721 100644 --- a/ipld/car/v2/example_test.go +++ b/ipld/car/v2/example_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" ) func ExampleWrapV1File() { @@ -43,8 +44,16 @@ func ExampleWrapV1File() { } fmt.Println("Inner CARv1 is exactly the same:", bytes.Equal(orig, inner)) + // Verify that the CARv2 works well with its index. + bs, err := blockstore.OpenReadOnly(dst, false) + if err != nil { + panic(err) + } + fmt.Println(bs.Get(roots[0])) + // Output: // Roots: [bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy] // Has index: true // Inner CARv1 is exactly the same: true + // [Block bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy] } diff --git a/ipld/car/v2/index/generator.go b/ipld/car/v2/index/generator.go index 08b32fda96..201b221cf6 100644 --- a/ipld/car/v2/index/generator.go +++ b/ipld/car/v2/index/generator.go @@ -5,17 +5,26 @@ import ( "encoding/binary" "fmt" "io" + "os" "github.com/ipfs/go-cid" "github.com/ipld/go-car/v2/internal/carv1" - internalio "github.com/ipld/go-car/v2/internal/io" - "golang.org/x/exp/mmap" ) +type readSeekerPlusByte struct { + io.ReadSeeker +} + +func (r readSeekerPlusByte) ReadByte() (byte, error) { + var p [1]byte + _, err := io.ReadFull(r, p[:]) + return p[0], err +} + // Generate generates index for a given car in v1 format. // The index can be stored using index.Save into a file or serialized using index.WriteTo. -func Generate(car io.ReaderAt) (Index, error) { - header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(car, 0))) +func Generate(v1 io.ReadSeeker) (Index, error) { + header, err := carv1.ReadHeader(bufio.NewReader(v1)) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } @@ -31,23 +40,37 @@ func Generate(car io.ReaderAt) (Index, error) { idx := mkSorted() records := make([]Record, 0) - rdr := internalio.NewOffsetReader(car, int64(offset)) + + // Seek to the first frame. + // Record the start of each frame, which we need for the index records. + frameOffset := int64(0) + if frameOffset, err = v1.Seek(int64(offset), io.SeekStart); err != nil { + return nil, err + } + for { - thisItemIdx := rdr.Offset() - l, err := binary.ReadUvarint(rdr) + // Grab the length of the frame. + // Note that ReadUvarint wants a ByteReader. + length, err := binary.ReadUvarint(readSeekerPlusByte{v1}) if err != nil { if err == io.EOF { break } return nil, err } - thisItemForNxt := rdr.Offset() - _, c, err := cid.CidFromReader(rdr) + + // Grab the CID. + n, c, err := cid.CidFromReader(v1) if err != nil { return nil, err } - records = append(records, Record{c, uint64(thisItemIdx)}) - rdr.SeekOffset(thisItemForNxt + int64(l)) + records = append(records, Record{c, uint64(frameOffset)}) + + // Seek to the next frame by skipping the block. + // The frame length includes the CID, so subtract it. + if frameOffset, err = v1.Seek(int64(length)-int64(n), io.SeekCurrent); err != nil { + return nil, err + } } if err := idx.Load(records); err != nil { @@ -60,10 +83,10 @@ func Generate(car io.ReaderAt) (Index, error) { // GenerateFromFile walks a car v1 file at the give path and generates an index of cid->byte offset. // The index can be stored using index.Save into a file or serialized using index.WriteTo. func GenerateFromFile(path string) (Index, error) { - store, err := mmap.Open(path) + f, err := os.Open(path) if err != nil { return nil, err } - defer store.Close() - return Generate(store) + defer f.Close() + return Generate(f) } diff --git a/ipld/car/v2/internal/carbs/util/hydrate.go b/ipld/car/v2/internal/carbs/util/hydrate.go index eead86cf16..8e43556cb5 100644 --- a/ipld/car/v2/internal/carbs/util/hydrate.go +++ b/ipld/car/v2/internal/carbs/util/hydrate.go @@ -5,7 +5,6 @@ import ( "os" "github.com/ipld/go-car/v2/index" - "golang.org/x/exp/mmap" ) func main() { @@ -15,13 +14,7 @@ func main() { } db := os.Args[1] - dbBacking, err := mmap.Open(db) - if err != nil { - fmt.Printf("Error Opening car for hydration: %v\n", err) - return - } - - idx, err := index.Generate(dbBacking) + idx, err := index.GenerateFromFile(db) if err != nil { fmt.Printf("Error generating index: %v\n", err) return diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 8c9f504e0e..59777c87fa 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -132,67 +132,72 @@ func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (int64, error) { return 0, index.WriteTo(idx, writer) } -// WrapV1File takes a source path to a CARv1 file and wraps it as a CARv2 file -// with an index, writing the result to the destination path. -// The resulting CARv2 file's inner CARv1 payload is left unmodified, -// and does not use any padding before the innner CARv1 or index. +// WrapV1File is a wrapper around WrapV1 that takes filesystem paths. +// The source path is assumed to exist, and the destination path is overwritten. +// Note that the destination path might still be created even if an error +// occurred. func WrapV1File(srcPath, dstPath string) error { - // TODO: verify src is indeed a CARv1 to prevent misuse. - // index.Generate should probably be in charge of that. - - // TODO: also expose WrapV1(io.ReadSeeker, io.Writer), - // once index.Generate takes a ReadSeeker. - - // We don't use mmap.Open, so we can later use io.Copy. - f1, err := os.Open(srcPath) + src, err := os.Open(srcPath) if err != nil { return err } - defer f1.Close() + defer src.Close() - idx, err := index.Generate(f1) + dst, err := os.Create(dstPath) if err != nil { return err } + defer dst.Close() - // Use Seek to learn the size of the CARv1 before reading it. - v1Size, err := f1.Seek(0, io.SeekEnd) - if err != nil { + if err := WrapV1(src, dst); err != nil { return err } - if _, err := f1.Seek(0, io.SeekStart); err != nil { + + // Check the close error, since we're writing to dst. + // Note that we also do a "defer dst.Close()" above, + // to make sure that the earlier error returns don't leak the file. + if err := dst.Close(); err != nil { return err } + return nil +} - // Only create the destination CARv2 when we've gathered all the - // information we need, such as the index and the CARv1 size. - f2, err := os.Create(dstPath) +// WrapV1 takes a CARv1 file and wraps it as a CARv2 file with an index. +// The resulting CARv2 file's inner CARv1 payload is left unmodified, +// and does not use any padding before the innner CARv1 or index. +func WrapV1(src io.ReadSeeker, dst io.Writer) error { + // TODO: verify src is indeed a CARv1 to prevent misuse. + // index.Generate should probably be in charge of that. + + idx, err := index.Generate(src) if err != nil { return err } - defer f2.Close() + + // Use Seek to learn the size of the CARv1 before reading it. + v1Size, err := src.Seek(0, io.SeekEnd) + if err != nil { + return err + } + if _, err := src.Seek(0, io.SeekStart); err != nil { + return err + } // Similar to the Writer API, write all components of a CARv2 to the // destination file: Pragma, Header, CARv1, Index. v2Header := NewHeader(uint64(v1Size)) - if _, err := f2.Write(Pragma); err != nil { + if _, err := dst.Write(Pragma); err != nil { return err } - if _, err := v2Header.WriteTo(f2); err != nil { + if _, err := v2Header.WriteTo(dst); err != nil { return err } - if _, err := io.Copy(f2, f1); err != nil { + if _, err := io.Copy(dst, src); err != nil { return err } - if err := index.WriteTo(idx, f2); err != nil { + if err := index.WriteTo(idx, dst); err != nil { return err } - // Check the close error, since we're writing to f2. - // Note that we also do a "defer f2.Close()" above, - // to make sure that the earlier error returns don't leak the file. - if err := f2.Close(); err != nil { - return err - } return nil } From 8ce2fa7495c3ff0d6d54d6f3b77abe23a076a901 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 5 Jul 2021 16:52:40 +0100 Subject: [PATCH 097/291] Replace `binary.Put/ReadUvarint` with `go-varint` To guarantee minimal encoding Fixes #135 Reflect on review comments Use `varint.PutUvarint` when dealing with writing bytes. This commit was moved from ipld/go-car@f598e5961ec244734e3cbf84c128f5b62b274960 --- ipld/car/v2/blockstore/readonly.go | 9 +++++---- ipld/car/v2/index/generator.go | 5 +++-- ipld/car/v2/index/index.go | 6 ++++-- ipld/car/v2/internal/carv1/util/util.go | 12 ++++++------ 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index ac603001e1..521f302800 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -4,12 +4,13 @@ import ( "bufio" "bytes" "context" - "encoding/binary" "errors" "fmt" "io" "sync" + "github.com/multiformats/go-varint" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" @@ -103,7 +104,7 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { return false, err } uar := internalio.NewOffsetReader(b.backing, int64(offset)) - _, err = binary.ReadUvarint(uar) + _, err = varint.ReadUvarint(uar) if err != nil { return false, err } @@ -143,7 +144,7 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { if err != nil { return -1, err } - l, err := binary.ReadUvarint(internalio.NewOffsetReader(b.backing, int64(idx))) + l, err := varint.ReadUvarint(internalio.NewOffsetReader(b.backing, int64(idx))) if err != nil { return -1, blockstore.ErrNotFound } @@ -193,7 +194,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { rdr := internalio.NewOffsetReader(b.backing, int64(offset)) for { - l, err := binary.ReadUvarint(rdr) + l, err := varint.ReadUvarint(rdr) if err != nil { return // TODO: log this error } diff --git a/ipld/car/v2/index/generator.go b/ipld/car/v2/index/generator.go index 201b221cf6..94841efc2d 100644 --- a/ipld/car/v2/index/generator.go +++ b/ipld/car/v2/index/generator.go @@ -2,11 +2,12 @@ package index import ( "bufio" - "encoding/binary" "fmt" "io" "os" + "github.com/multiformats/go-varint" + "github.com/ipfs/go-cid" "github.com/ipld/go-car/v2/internal/carv1" ) @@ -51,7 +52,7 @@ func Generate(v1 io.ReadSeeker) (Index, error) { for { // Grab the length of the frame. // Note that ReadUvarint wants a ByteReader. - length, err := binary.ReadUvarint(readSeekerPlusByte{v1}) + length, err := varint.ReadUvarint(readSeekerPlusByte{v1}) if err != nil { if err == io.EOF { break diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 0b96c5f3da..53df02fbce 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -7,6 +7,8 @@ import ( "io" "os" + "github.com/multiformats/go-varint" + internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipfs/go-cid" @@ -83,7 +85,7 @@ func Attach(path string, idx Index, offset uint64) error { // This can then be read back using index.ReadFrom func WriteTo(idx Index, w io.Writer) error { buf := make([]byte, binary.MaxVarintLen64) - b := binary.PutUvarint(buf, uint64(idx.Codec())) + b := varint.PutUvarint(buf, uint64(idx.Codec())) if _, err := w.Write(buf[:b]); err != nil { return err } @@ -95,7 +97,7 @@ func WriteTo(idx Index, w io.Writer) error { // Returns error if the encoding is not known. func ReadFrom(r io.Reader) (Index, error) { reader := bufio.NewReader(r) - codec, err := binary.ReadUvarint(reader) + codec, err := varint.ReadUvarint(reader) if err != nil { return nil, err } diff --git a/ipld/car/v2/internal/carv1/util/util.go b/ipld/car/v2/internal/carv1/util/util.go index 9db5f8c152..297d74c8a0 100644 --- a/ipld/car/v2/internal/carv1/util/util.go +++ b/ipld/car/v2/internal/carv1/util/util.go @@ -2,9 +2,10 @@ package util import ( "bufio" - "encoding/binary" "io" + "github.com/multiformats/go-varint" + cid "github.com/ipfs/go-cid" ) @@ -34,7 +35,7 @@ func LdWrite(w io.Writer, d ...[]byte) error { } buf := make([]byte, 8) - n := binary.PutUvarint(buf, sum) + n := varint.PutUvarint(buf, sum) _, err := w.Write(buf[:n]) if err != nil { return err @@ -55,9 +56,8 @@ func LdSize(d ...[]byte) uint64 { for _, s := range d { sum += uint64(len(s)) } - buf := make([]byte, 8) - n := binary.PutUvarint(buf, sum) - return sum + uint64(n) + s := varint.UvarintSize(sum) + return sum + uint64(s) } func LdRead(r *bufio.Reader) ([]byte, error) { @@ -65,7 +65,7 @@ func LdRead(r *bufio.Reader) ([]byte, error) { return nil, err } - l, err := binary.ReadUvarint(r) + l, err := varint.ReadUvarint(r) if err != nil { if err == io.EOF { return nil, io.ErrUnexpectedEOF // don't silently pretend this is a clean EOF From 10bd2b56dec1635320a5141f22d266ac2f64aee0 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 5 Jul 2021 14:10:15 +0100 Subject: [PATCH 098/291] Rename `ReadOnlyOf` to `NewReadOnly` and accept both v1 and v2 payload Rename the `blockstore.ReadOnlyOf` to `blockstore.NewReadOnly` for better readability and consistent naming. Allow the function to accept both v1 and v2 payloads and "do the right thing" depending on the input. Optionally, allow the caller to supply an index that, if present, will overrider any existing index. Otherwise an in-memory index is generated. Note, `blockstore.OpenReadOnly` only accepts v2 payload. Future PRs will change this function to also accept both versions for consistency, and drop modification of given path, delegating any writing to the write-specific APIs like `index.Attach` and `index.Generate`. Fixes #128 This commit was moved from ipld/go-car@4e863b281d91c5942c4439e25ce08182be629417 --- ipld/car/v2/blockstore/doc.go | 7 ++--- ipld/car/v2/blockstore/readonly.go | 45 ++++++++++++++++++++++++++--- ipld/car/v2/blockstore/readwrite.go | 2 +- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index c2cf62a803..ddbcc98e6e 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -3,10 +3,9 @@ // // The ReadOnly blockstore provides a read-only random access from a given data payload either in // unindexed v1 format or indexed/unindexed v2 format: -// - ReadOnly.ReadOnlyOf can be used to instantiate a new read-only blockstore for a given CAR v1 -// data payload and an existing index. See index.Generate for index generation from CAR v1 -// payload. -// - ReadOnly.OpenReadOnly can be used to instantiate a new read-only blockstore for a given CAR v2 +// * ReadOnly.NewReadOnly can be used to instantiate a new read-only blockstore for a given CAR v1 +// or CAR v2 data payload with an optional index override. +// * ReadOnly.OpenReadOnly can be used to instantiate a new read-only blockstore for a given CAR v2 // file with automatic index generation if the index is not present in the given file. This // function can optionally attach the index to the given CAR v2 file. // diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 521f302800..4125cc1aaf 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -42,10 +42,47 @@ type ReadOnly struct { carv2Closer io.Closer } -// ReadOnlyOf opens ReadOnly blockstore from an existing backing containing a CAR v1 payload and an existing index. -// The index for a CAR v1 payload can be separately generated using index.Generate. -func ReadOnlyOf(backing io.ReaderAt, index index.Index) *ReadOnly { - return &ReadOnly{backing: backing, idx: index} +// NewReadOnly creates a new ReadOnly blockstore from the backing with a optional index as idx. +// This function accepts both CAR v1 and v2 backing. +// The blockstore is instantiated with the given index if it is not nil. +// +// Otherwise: +// * For a CAR v1 backing an index is generated. +// * For a CAR v2 backing an index is only generated if Header.HasIndex returns false. +// +// There is no need to call ReadOnly.Close on instances returned by this function. +func NewReadOnly(backing io.ReaderAt, idx index.Index) (*ReadOnly, error) { + version, err := carv2.ReadVersion(internalio.NewOffsetReader(backing, 0)) + if err != nil { + return nil, err + } + switch version { + case 1: + if idx == nil { + if idx, err = index.Generate(backing); err != nil { + return nil, err + } + } + return &ReadOnly{backing: backing, idx: idx}, nil + case 2: + v2r, err := carv2.NewReader(backing) + if err != nil { + return nil, err + } + if idx == nil { + if v2r.Header.HasIndex() { + idx, err = index.ReadFrom(v2r.IndexReader()) + if err != nil { + return nil, err + } + } else if idx, err = index.Generate(backing); err != nil { + return nil, err + } + } + return &ReadOnly{backing: v2r.CarV1Reader(), idx: idx}, nil + default: + return nil, fmt.Errorf("unsupported car version: %v", version) + } } // OpenReadOnly opens a read-only blockstore from a CAR v2 file, generating an index if it does not exist. diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 99fd11d2a6..cf10a77501 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -88,7 +88,7 @@ func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, err } b.carV1Writer = internalio.NewOffsetWriter(f, int64(b.header.CarV1Offset)) carV1Reader := internalio.NewOffsetReader(f, int64(b.header.CarV1Offset)) - b.ReadOnly = *ReadOnlyOf(carV1Reader, idx) + b.ReadOnly = ReadOnly{backing: carV1Reader, idx: idx} if _, err := f.WriteAt(carv2.Pragma, 0); err != nil { return nil, err } From f93cb1b39bc8e9623149cbef966c8f5ee844328f Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 5 Jul 2021 16:04:44 +0100 Subject: [PATCH 099/291] Accommodate `index.Generate` taking `io.ReadSeeker` Accommodate to the `index.Generate` signature change, done in #136. Insead of implementng a new reader that converts `io.ReaderAt` to `io.ReadSeaker`, make `internalio.OffsetReader` partially implement `Seek`. The "partial" refers to inability to satisfy `Seek` calls with `io.SeekEnd` whence since the point of `internalio.OffsetReader` is that it needs not to know the total size of a file, i.e. cannot know its end. Instead, it panics if `Seek` is called with whence `io.SeekEnd`. None of this matterns much since it is all placed under internal APIs. This commit was moved from ipld/go-car@c0023edfedbc7fa45bd180009e47e9291f364d83 --- ipld/car/v2/blockstore/readonly.go | 46 +++++++++---- ipld/car/v2/blockstore/readwrite.go | 2 +- ipld/car/v2/internal/io/offset_read_seeker.go | 65 +++++++++++++++++++ ipld/car/v2/internal/io/offset_reader.go | 49 -------------- ipld/car/v2/reader.go | 4 +- 5 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 ipld/car/v2/internal/io/offset_read_seeker.go delete mode 100644 ipld/car/v2/internal/io/offset_reader.go diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 4125cc1aaf..6ffb6b3bb1 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -52,14 +52,14 @@ type ReadOnly struct { // // There is no need to call ReadOnly.Close on instances returned by this function. func NewReadOnly(backing io.ReaderAt, idx index.Index) (*ReadOnly, error) { - version, err := carv2.ReadVersion(internalio.NewOffsetReader(backing, 0)) + version, err := readVersion(backing) if err != nil { return nil, err } switch version { case 1: if idx == nil { - if idx, err = index.Generate(backing); err != nil { + if idx, err = generateIndex(backing); err != nil { return nil, err } } @@ -75,7 +75,7 @@ func NewReadOnly(backing io.ReaderAt, idx index.Index) (*ReadOnly, error) { if err != nil { return nil, err } - } else if idx, err = index.Generate(backing); err != nil { + } else if idx, err = generateIndex(v2r.CarV1Reader()); err != nil { return nil, err } } @@ -85,6 +85,28 @@ func NewReadOnly(backing io.ReaderAt, idx index.Index) (*ReadOnly, error) { } } +func readVersion(at io.ReaderAt) (uint64, error) { + var rr io.Reader + switch r := at.(type) { + case io.Reader: + rr = r + default: + rr = internalio.NewOffsetReadSeeker(r, 0) + } + return carv2.ReadVersion(rr) +} + +func generateIndex(at io.ReaderAt) (index.Index, error) { + var rs io.ReadSeeker + switch r := at.(type) { + case io.ReadSeeker: + rs = r + default: + rs = internalio.NewOffsetReadSeeker(r, 0) + } + return index.Generate(rs) +} + // OpenReadOnly opens a read-only blockstore from a CAR v2 file, generating an index if it does not exist. // If attachIndex is set to true and the index is not present in the given CAR v2 file, // then the generated index is written into the given path. @@ -120,7 +142,7 @@ func OpenReadOnly(path string, attachIndex bool) (*ReadOnly, error) { } func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { - bcid, data, err := util.ReadNode(bufio.NewReader(internalio.NewOffsetReader(b.backing, idx))) + bcid, data, err := util.ReadNode(bufio.NewReader(internalio.NewOffsetReadSeeker(b.backing, idx))) return bcid, data, err } @@ -140,7 +162,7 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { } else if err != nil { return false, err } - uar := internalio.NewOffsetReader(b.backing, int64(offset)) + uar := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) _, err = varint.ReadUvarint(uar) if err != nil { return false, err @@ -181,11 +203,11 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { if err != nil { return -1, err } - l, err := varint.ReadUvarint(internalio.NewOffsetReader(b.backing, int64(idx))) + l, err := varint.ReadUvarint(internalio.NewOffsetReadSeeker(b.backing, int64(idx))) if err != nil { return -1, blockstore.ErrNotFound } - _, c, err := cid.CidFromReader(internalio.NewOffsetReader(b.backing, int64(idx+l))) + _, c, err := cid.CidFromReader(internalio.NewOffsetReadSeeker(b.backing, int64(idx+l))) if err != nil { return 0, err } @@ -212,7 +234,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { b.mu.RLock() // TODO we may use this walk for populating the index, and we need to be able to iterate keys in this way somewhere for index generation. In general though, when it's asked for all keys from a blockstore with an index, we should iterate through the index when possible rather than linear reads through the full car. - header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(b.backing, 0))) + header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReadSeeker(b.backing, 0))) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } @@ -229,7 +251,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { defer close(ch) - rdr := internalio.NewOffsetReader(b.backing, int64(offset)) + rdr := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) for { l, err := varint.ReadUvarint(rdr) if err != nil { @@ -240,7 +262,9 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { if err != nil { return // TODO: log this error } - rdr.SeekOffset(thisItemForNxt + int64(l)) + if _, err := rdr.Seek(thisItemForNxt+int64(l), io.SeekStart); err != nil { + return // TODO: log this error + } select { case ch <- c: @@ -259,7 +283,7 @@ func (b *ReadOnly) HashOnRead(bool) { // Roots returns the root CIDs of the backing CAR. func (b *ReadOnly) Roots() ([]cid.Cid, error) { - header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReader(b.backing, 0))) + header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReadSeeker(b.backing, 0))) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index cf10a77501..68f8cfab13 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -87,7 +87,7 @@ func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, err opt(b) } b.carV1Writer = internalio.NewOffsetWriter(f, int64(b.header.CarV1Offset)) - carV1Reader := internalio.NewOffsetReader(f, int64(b.header.CarV1Offset)) + carV1Reader := internalio.NewOffsetReadSeeker(f, int64(b.header.CarV1Offset)) b.ReadOnly = ReadOnly{backing: carV1Reader, idx: idx} if _, err := f.WriteAt(carv2.Pragma, 0); err != nil { return nil, err diff --git a/ipld/car/v2/internal/io/offset_read_seeker.go b/ipld/car/v2/internal/io/offset_read_seeker.go new file mode 100644 index 0000000000..c92c3154c0 --- /dev/null +++ b/ipld/car/v2/internal/io/offset_read_seeker.go @@ -0,0 +1,65 @@ +package io + +import "io" + +var ( + _ io.ReaderAt = (*OffsetReadSeeker)(nil) + _ io.ReadSeeker = (*OffsetReadSeeker)(nil) +) + +// OffsetReadSeeker implements Read, and ReadAt on a section +// of an underlying io.ReaderAt. +// The main difference between io.SectionReader and OffsetReadSeeker is that +// NewOffsetReadSeeker does not require the user to know the number of readable bytes. +// +// It also partially implements Seek, where the implementation panics if io.SeekEnd is passed. +// This is because, OffsetReadSeeker does not know the end of the file therefore cannot seek relative +// to it. +type OffsetReadSeeker struct { + r io.ReaderAt + base int64 + off int64 +} + +// NewOffsetReadSeeker returns an OffsetReadSeeker that reads from r +// starting offset offset off and stops with io.EOF when r reaches its end. +// The Seek function will panic if whence io.SeekEnd is passed. +func NewOffsetReadSeeker(r io.ReaderAt, off int64) *OffsetReadSeeker { + return &OffsetReadSeeker{r, off, off} +} + +func (o *OffsetReadSeeker) Read(p []byte) (n int, err error) { + n, err = o.r.ReadAt(p, o.off) + o.off += int64(n) + return +} + +func (o *OffsetReadSeeker) ReadAt(p []byte, off int64) (n int, err error) { + if off < 0 { + return 0, io.EOF + } + off += o.base + return o.r.ReadAt(p, off) +} + +func (o *OffsetReadSeeker) ReadByte() (byte, error) { + b := []byte{0} + _, err := o.Read(b) + return b[0], err +} + +func (o *OffsetReadSeeker) Offset() int64 { + return o.off +} + +func (o *OffsetReadSeeker) Seek(offset int64, whence int) (int64, error) { + switch whence { + case io.SeekStart: + o.off = offset + case io.SeekCurrent: + o.off += offset + case io.SeekEnd: + panic("unsupported whence: SeekEnd") + } + return o.off, nil +} diff --git a/ipld/car/v2/internal/io/offset_reader.go b/ipld/car/v2/internal/io/offset_reader.go deleted file mode 100644 index 03f7e547f3..0000000000 --- a/ipld/car/v2/internal/io/offset_reader.go +++ /dev/null @@ -1,49 +0,0 @@ -package io - -import "io" - -var _ io.ReaderAt = (*OffsetReader)(nil) - -// OffsetReader implements Read, and ReadAt on a section -// of an underlying io.ReaderAt. -// The main difference between io.SectionReader and OffsetReader is that -// NewOffsetReader does not require the user to know the number of readable bytes. -type OffsetReader struct { - r io.ReaderAt - base int64 - off int64 -} - -// NewOffsetReader returns an OffsetReader that reads from r -// starting offset offset off and stops with io.EOF when r reaches its end. -func NewOffsetReader(r io.ReaderAt, off int64) *OffsetReader { - return &OffsetReader{r, off, off} -} - -func (o *OffsetReader) Read(p []byte) (n int, err error) { - n, err = o.r.ReadAt(p, o.off) - o.off += int64(n) - return -} - -func (o *OffsetReader) ReadAt(p []byte, off int64) (n int, err error) { - if off < 0 { - return 0, io.EOF - } - off += o.base - return o.r.ReadAt(p, off) -} - -func (o *OffsetReader) ReadByte() (byte, error) { - b := []byte{0} - _, err := o.Read(b) - return b[0], err -} - -func (o *OffsetReader) Offset() int64 { - return o.off -} - -func (o *OffsetReader) SeekOffset(off int64) { - o.off = off -} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index d8b822635c..6cf684c74c 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -54,7 +54,7 @@ func NewReader(r io.ReaderAt) (*Reader, error) { } func (r *Reader) requireVersion2() (err error) { - or := internalio.NewOffsetReader(r.r, 0) + or := internalio.NewOffsetReadSeeker(r.r, 0) version, err := ReadVersion(or) if err != nil { return @@ -100,7 +100,7 @@ func (r *Reader) CarV1Reader() SectionReader { // IndexReader provides an io.Reader containing the index of this CAR v2. func (r *Reader) IndexReader() io.Reader { - return internalio.NewOffsetReader(r.r, int64(r.Header.IndexOffset)) + return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) } // Close closes the underlying reader if it was opened by NewReaderMmap. From 750b3b50deba82ee44c6f006ccc7c8a61df30981 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 5 Jul 2021 17:55:55 +0100 Subject: [PATCH 100/291] Consistently accept CAR v1 or v2 on `blockstore.OpenReadOnly` Since `blockstore.NewReadOnly` accepts both versions, and the `attach` flag is never used in `OpenReadOnly` make things consistent and arguably more useful by accepting both v1 and v2 CAR files. This also avoids modifying files passed to a read-only API which is always sweet as sugar. This commit was moved from ipld/go-car@2776842e99101cc3d850916b6392076745ccaca2 --- ipld/car/v2/blockstore/doc.go | 4 +-- ipld/car/v2/blockstore/readonly.go | 40 ++++++++---------------- ipld/car/v2/blockstore/readwrite_test.go | 2 +- ipld/car/v2/example_test.go | 2 +- 4 files changed, 16 insertions(+), 32 deletions(-) diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index ddbcc98e6e..4d6a1a4481 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -6,8 +6,6 @@ // * ReadOnly.NewReadOnly can be used to instantiate a new read-only blockstore for a given CAR v1 // or CAR v2 data payload with an optional index override. // * ReadOnly.OpenReadOnly can be used to instantiate a new read-only blockstore for a given CAR v2 -// file with automatic index generation if the index is not present in the given file. This -// function can optionally attach the index to the given CAR v2 file. -// +// or car v2 file with automatic index generation if the index is not present. package blockstore diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 6ffb6b3bb1..362345d3e4 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -9,6 +9,8 @@ import ( "io" "sync" + "golang.org/x/exp/mmap" + "github.com/multiformats/go-varint" blocks "github.com/ipfs/go-block-format" @@ -107,38 +109,22 @@ func generateIndex(at io.ReaderAt) (index.Index, error) { return index.Generate(rs) } -// OpenReadOnly opens a read-only blockstore from a CAR v2 file, generating an index if it does not exist. -// If attachIndex is set to true and the index is not present in the given CAR v2 file, -// then the generated index is written into the given path. -func OpenReadOnly(path string, attachIndex bool) (*ReadOnly, error) { - v2r, err := carv2.NewReaderMmap(path) +// OpenReadOnly opens a read-only blockstore from a CAR file (either v1 or v2), generating an index if it does not exist. +// Note, the generated index if the index does not exist is ephemeral and only stored in memory. +// See index.Generate and Index.Attach for persisting index onto a CAR file. +func OpenReadOnly(path string) (*ReadOnly, error) { + f, err := mmap.Open(path) if err != nil { return nil, err } - var idx index.Index - if !v2r.Header.HasIndex() { - idx, err := index.Generate(v2r.CarV1Reader()) - if err != nil { - return nil, err - } - if attachIndex { - if err := index.Attach(path, idx, v2r.Header.IndexOffset); err != nil { - return nil, err - } - } - } else { - idx, err = index.ReadFrom(v2r.IndexReader()) - if err != nil { - return nil, err - } - } - obj := ReadOnly{ - backing: v2r.CarV1Reader(), - idx: idx, - carv2Closer: v2r, + robs, err := NewReadOnly(f, nil) + if err != nil { + return nil, err } - return &obj, nil + robs.carv2Closer = f + + return robs, nil } func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 3b66b3c4ff..808e40d722 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -65,7 +65,7 @@ func TestBlockstore(t *testing.T) { err = ingester.Finalize() require.NoError(t, err) - carb, err := blockstore.OpenReadOnly(path, false) + carb, err := blockstore.OpenReadOnly(path) require.NoError(t, err) t.Cleanup(func() { carb.Close() }) diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go index 93dedd7721..d590ab89f3 100644 --- a/ipld/car/v2/example_test.go +++ b/ipld/car/v2/example_test.go @@ -45,7 +45,7 @@ func ExampleWrapV1File() { fmt.Println("Inner CARv1 is exactly the same:", bytes.Equal(orig, inner)) // Verify that the CARv2 works well with its index. - bs, err := blockstore.OpenReadOnly(dst, false) + bs, err := blockstore.OpenReadOnly(dst) if err != nil { panic(err) } From b8de35aae0ef08dcfb2de3a7b61d8d3c4a8f77eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 5 Jul 2021 18:37:33 +0100 Subject: [PATCH 101/291] index: add support for null paddings in Generate Fixes #140. This commit was moved from ipld/go-car@6765c84a9741c56866e0891f3ace0a3112fc43af --- ipld/car/v2/blockstore/readonly.go | 10 ++++-- ipld/car/v2/blockstore/readwrite_test.go | 41 ++++++++++++++++++++++++ ipld/car/v2/index/generator.go | 7 ++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 362345d3e4..4f91a29c45 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -239,16 +239,22 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { rdr := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) for { - l, err := varint.ReadUvarint(rdr) + length, err := varint.ReadUvarint(rdr) if err != nil { return // TODO: log this error } + + // Null padding; treat it as EOF. + if length == 0 { + break + } + thisItemForNxt := rdr.Offset() _, c, err := cid.CidFromReader(rdr) if err != nil { return // TODO: log this error } - if _, err := rdr.Seek(thisItemForNxt+int64(l), io.SeekStart); err != nil { + if _, err := rdr.Seek(thisItemForNxt+int64(length), io.SeekStart); err != nil { return // TODO: log this error } diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 808e40d722..a693f384bf 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "io/ioutil" "math/rand" "os" "path/filepath" @@ -232,3 +233,43 @@ func TestBlockstoreConcurrentUse(t *testing.T) { } wg.Wait() } + +type bufferReaderAt []byte + +func (b bufferReaderAt) ReadAt(p []byte, off int64) (int, error) { + if off >= int64(len(b)) { + return 0, io.EOF + } + return copy(p, b[off:]), nil +} + +func TestBlockstoreNullPadding(t *testing.T) { + paddedV1, err := ioutil.ReadFile("testdata/test.car") + require.NoError(t, err) + + // A sample null-padded CARv1 file. + paddedV1 = append(paddedV1, make([]byte, 2048)...) + + rbs, err := blockstore.NewReadOnly(bufferReaderAt(paddedV1), nil) + require.NoError(t, err) + + roots, err := rbs.Roots() + require.NoError(t, err) + + has, err := rbs.Has(roots[0]) + require.NoError(t, err) + require.True(t, has) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + allKeysCh, err := rbs.AllKeysChan(ctx) + require.NoError(t, err) + for c := range allKeysCh { + b, err := rbs.Get(c) + require.NoError(t, err) + if !b.Cid().Equals(c) { + t.Fatal("wrong item returned") + } + } +} diff --git a/ipld/car/v2/index/generator.go b/ipld/car/v2/index/generator.go index 94841efc2d..1781f8dcfc 100644 --- a/ipld/car/v2/index/generator.go +++ b/ipld/car/v2/index/generator.go @@ -60,6 +60,13 @@ func Generate(v1 io.ReadSeeker) (Index, error) { return nil, err } + // Null padding; treat zero-length frames as an EOF. + // They don't contain a CID nor block, so they're not useful. + // TODO: Amend the CARv1 spec to explicitly allow this. + if length == 0 { + break + } + // Grab the CID. n, c, err := cid.CidFromReader(v1) if err != nil { From 5e55b0400d207045c4a1a94cf18bc38c24cc5d0f Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 8 Jul 2021 11:34:14 +0100 Subject: [PATCH 102/291] Implement read or generate index from CAR accepting v1 or v2 A user has to undergo a lot of boilerplate code to generate or get an index where there is a CAR file, the version for which is unknown. Provide a function that accepts either v1 or v2 payload and reads the index if present, otherwise generates it for the user. The generated index lives entirely in memory and will not depend on the given `io.ReadSeeker` to lookup indices. Implement tests that check index is returned for v1 or v2 files. Add a sample file with an imaginary version of 42 for testing purposes to assert that the indexing function will error if the given file is not v1 or v2. See: https://github.com/ipld/go-car/issues/143 This commit was moved from ipld/go-car@fc494d46623af156f057ef363c1ee2a7ffe0cb13 --- ipld/car/v2/reader.go | 62 ++++++++++++++++++ ipld/car/v2/reader_test.go | 67 ++++++++++++++++++++ ipld/car/v2/testdata/sample-rootless-v42.car | 1 + 3 files changed, 130 insertions(+) create mode 100644 ipld/car/v2/reader_test.go create mode 100644 ipld/car/v2/testdata/sample-rootless-v42.car diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 6cf684c74c..ae11792aeb 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -4,6 +4,9 @@ import ( "bufio" "fmt" "io" + "sync" + + "github.com/ipld/go-car/v2/index" internalio "github.com/ipld/go-car/v2/internal/io" @@ -121,3 +124,62 @@ func ReadVersion(r io.Reader) (version uint64, err error) { } return header.Version, nil } + +var _ io.ReaderAt = (*readSeekerAt)(nil) + +type readSeekerAt struct { + rs io.ReadSeeker + mu sync.RWMutex +} + +func (rsa *readSeekerAt) ReadAt(p []byte, off int64) (n int, err error) { + rsa.mu.RLock() + defer rsa.mu.RUnlock() + if _, err := rsa.rs.Seek(off, io.SeekStart); err != nil { + return 0, err + } + return rsa.rs.Read(p) +} + +// ReadOrGenerateIndex accepts both CAR v1 and v2 format, and reads or generates an index for it. +// When the given reader is in CAR v1 format an index is always generated. +// For a payload in CAR v2 format, an index is only generated if Header.HasIndex returns false. +// An error is returned for all other formats, i.e. versions other than 1 or 2. +// +// Note, the returned index lives entirely in memory and will not depend on the +// given reader to fulfill index lookup. +func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { + // Seek to the beginning of the reader in order to read version. + if _, err := rs.Seek(0, io.SeekStart); err != nil { + return nil, err + } + // Read version. + version, err := ReadVersion(rs) + if err != nil { + return nil, err + } + // Seek to the begining, since reading the version changes the reader's offset. + if _, err := rs.Seek(0, io.SeekStart); err != nil { + return nil, err + } + + switch version { + case 1: + // Simply generate the index, since there can't be a pre-existing one. + return index.Generate(rs) + case 2: + // Read CAR v2 format + v2r, err := NewReader(&readSeekerAt{rs: rs}) + if err != nil { + return nil, err + } + // If index is present, then no need to generate; decode and return it. + if v2r.Header.HasIndex() { + return index.ReadFrom(v2r.IndexReader()) + } + // Otherwise, generate index from CAR v1 payload wrapped within CAR v2 format. + return index.Generate(v2r.CarV1Reader()) + default: + return nil, fmt.Errorf("unknown version %v", version) + } +} diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go new file mode 100644 index 0000000000..c172315cd7 --- /dev/null +++ b/ipld/car/v2/reader_test.go @@ -0,0 +1,67 @@ +package car + +import ( + "github.com/ipld/go-car/v2/index" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "os" + "testing" +) + +func TestReadOrGenerateIndex(t *testing.T) { + tests := []struct { + name string + carPath string + wantIndexer func(t *testing.T) index.Index + wantErr bool + }{ + { + "CarV1IsIndexedAsExpected", + "testdata/sample-v1.car", + func(t *testing.T) index.Index { + v1, err := os.Open("testdata/sample-v1.car") + require.NoError(t, err) + defer v1.Close() + want, err := index.Generate(v1) + require.NoError(t, err) + return want + }, + false, + }, + { + "CarV2WithIndexIsReturnedAsExpected", + "testdata/sample-v1.car", + func(t *testing.T) index.Index { + v2, err := os.Open("testdata/sample-wrapped-v2.car") + require.NoError(t, err) + defer v2.Close() + reader, err := NewReader(v2) + require.NoError(t, err) + want, err := index.ReadFrom(reader.IndexReader()) + require.NoError(t, err) + return want + }, + false, + }, + { + "CarOtherThanV1OrV2IsError", + "testdata/sample-rootless-v42.car", + func(t *testing.T) index.Index { return nil }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + carFile, err := os.Open(tt.carPath) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, carFile.Close()) }) + got, err := ReadOrGenerateIndex(carFile) + if tt.wantErr { + require.Error(t, err) + } + want := tt.wantIndexer(t) + require.Equal(t, want, got) + }) + } +} diff --git a/ipld/car/v2/testdata/sample-rootless-v42.car b/ipld/car/v2/testdata/sample-rootless-v42.car new file mode 100644 index 0000000000..846b71a5a2 --- /dev/null +++ b/ipld/car/v2/testdata/sample-rootless-v42.car @@ -0,0 +1 @@ +erootsgversion* \ No newline at end of file From 793dca74963ef120afa118ffc9c0eb33d841c0ab Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 8 Jul 2021 11:45:43 +0100 Subject: [PATCH 103/291] Replace redundant use of RW mutex where a simple mutex would do Simply use mutex where reader will only do reading. This commit was moved from ipld/go-car@a6e8bc1bcce71a0f254d05d1429cfc1c1864c62b --- ipld/car/v2/reader.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index ae11792aeb..0a8aefac44 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -129,12 +129,12 @@ var _ io.ReaderAt = (*readSeekerAt)(nil) type readSeekerAt struct { rs io.ReadSeeker - mu sync.RWMutex + mu sync.Mutex } func (rsa *readSeekerAt) ReadAt(p []byte, off int64) (n int, err error) { - rsa.mu.RLock() - defer rsa.mu.RUnlock() + rsa.mu.Lock() + defer rsa.mu.Unlock() if _, err := rsa.rs.Seek(off, io.SeekStart); err != nil { return 0, err } From c3e3a0bfe75956235de12038192fae6bb321d077 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 8 Jul 2021 10:11:46 +0100 Subject: [PATCH 104/291] Implement ReadWrite blockstore resumption * Implement an option for read-write blockstore, that if enabled the write can resume from where the writer left off. For resumption to work the `WithResumption` option needs to be set explicitly. Otherwise, if path to an existing file is passed, the blockstore construction will return an error. The resumption requires the roots passed to constructor as well as padding options to be identical with roots in file. Resumption only works on paths where at least V2 pragma and CAR v1 header was successfully written onto the file. Otherwise an error is returned. * Implement resumption test that verifies files resumed from match expected header, data and index. * Implement a CAR v1 equals function to check if two given headers are identical. This implementation requires exact ordering of root elements. A TODO is left to relax the exact ordering requirement. * Implement Seeker in internal offset writer in order to forward offset of CAR v1 writer within a resumed read-write blockstore after resumption. The offset of the writer needs to be set to the latest written frame in order for consecutive writes to be at the right offset. * Fix bug in offset read seeker in internal IO, where seek and returned position was not normalized by the base. Reflect the fix in read-only blockstore AllKeysChan where reader was twisted to work. We now read the header to get its size, then seek past it to then iterate over blocks to populate the channel. * Add TODOs in places to make treating zero-length frames as EOF optional; See #140 for context. * Run `gofumpt -l -w .` on everything to maintain consistent formatting. Address review comments * Implement equality check for CAR v1 headers where roots in different order are considered to be equal. * Improve resumption docs to clarify what matching roots mean. This commit was moved from ipld/go-car@2a5f8942ddae910f4fa9d2a822b103e87aaac1c4 --- ipld/car/v2/blockstore/doc.go | 14 +- ipld/car/v2/blockstore/readonly.go | 15 +- ipld/car/v2/blockstore/readwrite.go | 174 +++++++++++++++--- ipld/car/v2/blockstore/readwrite_test.go | 98 +++++++++- ipld/car/v2/internal/carv1/car.go | 36 ++++ ipld/car/v2/internal/carv1/car_test.go | 68 +++++++ ipld/car/v2/internal/io/offset_read_seeker.go | 9 +- .../car/v2/internal/io/offset_write_seeker.go | 41 +++++ ipld/car/v2/internal/io/offset_writer.go | 26 --- ipld/car/v2/reader_test.go | 6 +- 10 files changed, 415 insertions(+), 72 deletions(-) create mode 100644 ipld/car/v2/internal/io/offset_write_seeker.go delete mode 100644 ipld/car/v2/internal/io/offset_writer.go diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index 4d6a1a4481..e31c043299 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -5,7 +5,15 @@ // unindexed v1 format or indexed/unindexed v2 format: // * ReadOnly.NewReadOnly can be used to instantiate a new read-only blockstore for a given CAR v1 // or CAR v2 data payload with an optional index override. -// * ReadOnly.OpenReadOnly can be used to instantiate a new read-only blockstore for a given CAR v2 -// or car v2 file with automatic index generation if the index is not present. - +// * ReadOnly.OpenReadOnly can be used to instantiate a new read-only blockstore for a given CAR v1 +// or CAR v2 file with automatic index generation if the index is not present. +// +// The ReadWrite blockstore allows writing and reading of the blocks concurrently. The user of this +// blockstore is responsible for calling ReadWrite.Finalize when finished writing blocks. +// Upon finalization, the instance can no longer be used for reading or writing blocks and will +// panic if used. To continue reading the blocks users are encouraged to use ReadOnly blockstore +// instantiated from the same file path using OpenReadOnly. +// A user may resume reading/writing from files produced by an instance of ReadWrite blockstore that +// on which ReadWrite.Finalize was never called. To resume, WithResumption option must be set to +// true. See docs on WithResumption for usage criteria. package blockstore diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 4f91a29c45..8ef74833ef 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -220,11 +220,12 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { b.mu.RLock() // TODO we may use this walk for populating the index, and we need to be able to iterate keys in this way somewhere for index generation. In general though, when it's asked for all keys from a blockstore with an index, we should iterate through the index when possible rather than linear reads through the full car. - header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReadSeeker(b.backing, 0))) + rdr := internalio.NewOffsetReadSeeker(b.backing, 0) + header, err := carv1.ReadHeader(bufio.NewReader(rdr)) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } - offset, err := carv1.HeaderSize(header) + headerSize, err := carv1.HeaderSize(header) if err != nil { return nil, err } @@ -232,12 +233,15 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { // TODO: document this choice of 5, or use simpler buffering like 0 or 1. ch := make(chan cid.Cid, 5) + // Seek to the end of header. + if _, err = rdr.Seek(int64(headerSize), io.SeekStart); err != nil { + return nil, err + } + go func() { defer b.mu.RUnlock() - defer close(ch) - rdr := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) for { length, err := varint.ReadUvarint(rdr) if err != nil { @@ -246,7 +250,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { // Null padding; treat it as EOF. if length == 0 { - break + break // TODO make this an optional behaviour; by default we should error } thisItemForNxt := rdr.Offset() @@ -261,6 +265,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { select { case ch <- c: case <-ctx.Done(): + // TODO: log ctx error return } } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 68f8cfab13..286b55e88c 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -1,13 +1,18 @@ package blockstore import ( + "bufio" "context" + "errors" "fmt" + "io" "os" + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/multiformats/go-varint" + blockstore "github.com/ipfs/go-ipfs-blockstore" carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/internal/carv1" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipld/go-car/v2/index" @@ -24,17 +29,19 @@ var _ blockstore.Blockstore = (*ReadWrite)(nil) // This implementation is preferable for a write-heavy workload. // The blocks are written immediately on Put and PutAll calls, while the index is stored in memory // and updated incrementally. +// // The Finalize function must be called once the putting blocks are finished. // Upon calling Finalize header is finalized and index is written out. // Once finalized, all read and write calls to this blockstore will result in panics. type ReadWrite struct { f *os.File - carV1Writer *internalio.OffsetWriter + carV1Writer *internalio.OffsetWriteSeeker ReadOnly idx *index.InsertionIndex header carv2.Header dedupCids bool + resume bool } // TODO consider exposing interfaces @@ -59,50 +66,165 @@ func WithIndexPadding(p uint64) Option { // This can help avoid redundancy in a CARv1's list of CID-Block pairs. // // Note that this compares whole CIDs, not just multihashes. -func WithCidDeduplication(b *ReadWrite) { +func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and return an option to allow disabling dedupliation? b.dedupCids = true } -// NewReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs as the car roots. +// WithResumption sets whether the blockstore should resume on a file produced by a ReadWrite +// blockstore on which ReadWrite.Finalize was not called. +// +// This option is set to false by default, i.e. disabled. When disabled, the file at path must not +// exist, otherwise an error is returned upon ReadWrite blockstore construction. +// +// When this option is set to true the existing data frames in file are re-indexed, allowing the +// caller to continue putting any remaining blocks without having to re-ingest blocks for which +// previous ReadWrite.Put returned successfully. +// +// Resumption is only allowed on files that satisfy the following criteria: +// 1. start with a complete CAR v2 car.Pragma. +// 2. contain a complete CAR v1 data header with root CIDs matching the CIDs passed to the +// constructor, starting at offset specified by WithCarV1Padding, followed by zero or more +// complete data frames. If any corrupt data frames are present the resumption will fail. Note, +// it is important that new instantiations of ReadWrite blockstore with resumption enabled use +// the same WithCarV1Padding option, since this option is used to locate the offset at which +// the data payload starts. +// 3. have not been produced by a ReadWrite blockstore that was finalized, i.e. call to +// ReadWrite.Finalize returned successfully. +func WithResumption(enabled bool) Option { + return func(b *ReadWrite) { + b.resume = enabled + } +} + +// NewReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs and options. func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { - // TODO support resumption if the path provided contains partially written blocks in v2 format. // TODO either lock the file or open exclusively; can we do somethign to reduce edge cases. - f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o666) - if err != nil { - return nil, fmt.Errorf("could not open read/write file: %w", err) + + b := &ReadWrite{ + header: carv2.NewHeader(0), } indexcls, ok := index.BuildersByCodec[index.IndexInsertion] if !ok { return nil, fmt.Errorf("unknownindex codec: %#v", index.IndexInsertion) } - idx := (indexcls()).(*index.InsertionIndex) + b.idx = (indexcls()).(*index.InsertionIndex) - b := &ReadWrite{ - f: f, - idx: idx, - header: carv2.NewHeader(0), - } for _, opt := range opts { opt(b) } - b.carV1Writer = internalio.NewOffsetWriter(f, int64(b.header.CarV1Offset)) - carV1Reader := internalio.NewOffsetReadSeeker(f, int64(b.header.CarV1Offset)) - b.ReadOnly = ReadOnly{backing: carV1Reader, idx: idx} - if _, err := f.WriteAt(carv2.Pragma, 0); err != nil { - return nil, err - } - v1Header := &carv1.CarHeader{ - Roots: roots, - Version: 1, + fFlag := os.O_RDWR | os.O_CREATE + if !b.resume { + fFlag = fFlag | os.O_EXCL } - if err := carv1.WriteHeader(v1Header, b.carV1Writer); err != nil { - return nil, fmt.Errorf("couldn't write car header: %w", err) + var err error + b.f, err = os.OpenFile(path, fFlag, 0o666) // TODO: Should the user be able to configure FileMode permissions? + if err != nil { + return nil, fmt.Errorf("could not open read/write file: %w", err) } + b.carV1Writer = internalio.NewOffsetWriter(b.f, int64(b.header.CarV1Offset)) + v1r := internalio.NewOffsetReadSeeker(b.f, int64(b.header.CarV1Offset)) + b.ReadOnly = ReadOnly{backing: v1r, idx: b.idx, carv2Closer: b.f} + + if b.resume { + if err = b.resumeWithRoots(roots); err != nil { + return nil, err + } + } else { + if err = b.initWithRoots(roots); err != nil { + return nil, err + } + } + return b, nil } +func (b *ReadWrite) initWithRoots(roots []cid.Cid) error { + if _, err := b.f.WriteAt(carv2.Pragma, 0); err != nil { + return err + } + return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, b.carV1Writer) +} + +func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { + // On resumption it is expected that the CAR v2 Pragma, and the CAR v1 header is successfully written. + // Otherwise we cannot resume from the file. + // Read pragma to assert if b.f is indeed a CAR v2. + version, err := carv2.ReadVersion(b.f) + if err != nil { + // The file is not a valid CAR file and cannot resume from it. + // Or the write must have failed before pragma was written. + return err + } + if version != 2 { + // The file is not a CAR v2 and we cannot resume from it. + return fmt.Errorf("cannot resume on CAR file with version %v", version) + } + + // Use the given CAR v1 padding to instantiate the CAR v1 reader on file. + v1r := internalio.NewOffsetReadSeeker(b.ReadOnly.backing, 0) + header, err := carv1.ReadHeader(bufio.NewReader(v1r)) + if err != nil { + // Cannot read the CAR v1 header; the file is most likely corrupt. + return fmt.Errorf("error reading car header: %w", err) + } + if !header.Equals(carv1.CarHeader{Roots: roots, Version: 1}) { + // Cannot resume if version and root does not match. + return errors.New("cannot resume on file with mismatching data header") + } + + // TODO See how we can reduce duplicate code here. + // The code here comes from index.Generate. + // Copied because we need to populate an insertindex, not a sorted index. + // Producing a sorted index via generate, then converting it to insertindex is not possible. + // Because Index interface does not expose internal records. + // This may be done as part of https://github.com/ipld/go-car/issues/95 + + offset, err := carv1.HeaderSize(header) + if err != nil { + return err + } + frameOffset := int64(0) + if frameOffset, err = v1r.Seek(int64(offset), io.SeekStart); err != nil { + return err + } + + for { + // Grab the length of the frame. + // Note that ReadUvarint wants a ByteReader. + length, err := varint.ReadUvarint(v1r) + if err != nil { + if err == io.EOF { + break + } + return err + } + + // Null padding; treat zero-length frames as an EOF. + // They don't contain a CID nor block, so they're not useful. + if length == 0 { + break // TODO This behaviour should be an option, not default. By default we should error. Hook this up to a write option + } + + // Grab the CID. + n, c, err := cid.CidFromReader(v1r) + if err != nil { + return err + } + b.idx.InsertNoReplace(c, uint64(frameOffset)) + + // Seek to the next frame by skipping the block. + // The frame length includes the CID, so subtract it. + if frameOffset, err = v1r.Seek(int64(length)-int64(n), io.SeekCurrent); err != nil { + return err + } + } + // Seek to the end of last skipped block where the writer should resume writing. + _, err = b.carV1Writer.Seek(frameOffset, io.SeekStart) + return err +} + func (b *ReadWrite) panicIfFinalized() { if b.header.CarV1Size != 0 { panic("must not use a read-write blockstore after finalizing") @@ -154,7 +276,7 @@ func (b *ReadWrite) Finalize() error { // TODO check if add index option is set and don't write the index then set index offset to zero. // TODO see if folks need to continue reading from a finalized blockstore, if so return ReadOnly blockstore here. b.header = b.header.WithCarV1Size(uint64(b.carV1Writer.Position())) - defer b.f.Close() + defer b.Close() if _, err := b.header.WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)); err != nil { return err } diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index a693f384bf..914d20be18 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -12,6 +12,10 @@ import ( "testing" "time" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" + "github.com/stretchr/testify/assert" + "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" @@ -28,7 +32,7 @@ func TestBlockstore(t *testing.T) { f, err := os.Open("testdata/test.car") require.NoError(t, err) - t.Cleanup(func() { f.Close() }) + t.Cleanup(func() { assert.NoError(t, f.Close()) }) r, err := carv1.NewCarReader(f) require.NoError(t, err) @@ -66,15 +70,15 @@ func TestBlockstore(t *testing.T) { err = ingester.Finalize() require.NoError(t, err) - carb, err := blockstore.OpenReadOnly(path) + robs, err := blockstore.OpenReadOnly(path) require.NoError(t, err) - t.Cleanup(func() { carb.Close() }) + t.Cleanup(func() { assert.NoError(t, robs.Close()) }) - allKeysCh, err := carb.AllKeysChan(ctx) + allKeysCh, err := robs.AllKeysChan(ctx) require.NoError(t, err) numKeysCh := 0 for c := range allKeysCh { - b, err := carb.Get(c) + b, err := robs.Get(c) require.NoError(t, err) if !b.Cid().Equals(c) { t.Fatal("wrong item returned") @@ -82,11 +86,11 @@ func TestBlockstore(t *testing.T) { numKeysCh++ } if numKeysCh != len(cids) { - t.Fatal("AllKeysChan returned an unexpected amount of keys") + t.Fatalf("AllKeysChan returned an unexpected amount of keys; expected %v but got %v", len(cids), numKeysCh) } for _, c := range cids { - b, err := carb.Get(c) + b, err := robs.Get(c) require.NoError(t, err) if !b.Cid().Equals(c) { t.Fatal("wrong item returned") @@ -273,3 +277,83 @@ func TestBlockstoreNullPadding(t *testing.T) { } } } + +func TestBlockstoreResumption(t *testing.T) { + v1f, err := os.Open("testdata/test.car") + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, v1f.Close()) }) + r, err := carv1.NewCarReader(v1f) + require.NoError(t, err) + + path := filepath.Join(t.TempDir(), "readwrite-resume.car") + // Create an incomplete CAR v2 file with no blocks put. + subject, err := blockstore.NewReadWrite(path, r.Header.Roots) + require.NoError(t, err) + + // For each block resume on the same file, putting blocks one at a time. + for { + b, err := r.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + // 30% chance of subject failing (or closing file without calling Finalize). + // The higher this percentage the slower the test runs considering the number of blocks in the original CAR v1 test payload. + shouldFailAbruptly := rand.Float32() <= 0.3 + if shouldFailAbruptly { + // Close off the open file and re-instantiate a new subject with resumption enabled. + // Note, we don't have to close the file for resumption to work. + // We do this to avoid resource leak during testing. + require.NoError(t, subject.Close()) + subject, err = blockstore.NewReadWrite(path, r.Header.Roots, blockstore.WithResumption(true)) + require.NoError(t, err) + } + require.NoError(t, subject.Put(b)) + } + require.NoError(t, subject.Close()) + + // Finalize the blockstore to complete partially written CAR v2 file. + subject, err = blockstore.NewReadWrite(path, r.Header.Roots, blockstore.WithResumption(true)) + require.NoError(t, err) + require.NoError(t, subject.Finalize()) + + // Assert resumed from file is a valid CAR v2 with index. + v2f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, v2f.Close()) }) + v2r, err := carv2.NewReader(v2f) + require.NoError(t, err) + require.True(t, v2r.Header.HasIndex()) + + // Assert CAR v1 payload in file matches the original CAR v1 payload. + _, err = v1f.Seek(0, io.SeekStart) + require.NoError(t, err) + wantPayloadReader, err := carv1.NewCarReader(v1f) + require.NoError(t, err) + + gotPayloadReader, err := carv1.NewCarReader(v2r.CarV1Reader()) + require.NoError(t, err) + + require.Equal(t, wantPayloadReader.Header, gotPayloadReader.Header) + for { + wantNextBlock, wantErr := wantPayloadReader.Next() + gotNextBlock, gotErr := gotPayloadReader.Next() + if wantErr == io.EOF { + require.Equal(t, wantErr, gotErr) + break + } + require.NoError(t, wantErr) + require.NoError(t, gotErr) + require.Equal(t, wantNextBlock, gotNextBlock) + } + + // Assert index in resumed from file is identical to index generated directly from original CAR v1 payload. + _, err = v1f.Seek(0, io.SeekStart) + require.NoError(t, err) + gotIdx, err := index.ReadFrom(v2r.IndexReader()) + require.NoError(t, err) + wantIdx, err := index.Generate(v1f) + require.NoError(t, err) + require.Equal(t, wantIdx, gotIdx) +} diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index d69f3a7b72..7332ebadb4 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -209,3 +209,39 @@ func loadCarSlow(s Store, cr *CarReader) (*CarHeader, error) { } } } + +// Equals checks whether two headers are equal. +// Two headers are considered equal if: +// 1. They have the same version number, and +// 2. They contain the same root CIDs in any order. +func (h CarHeader) Equals(other CarHeader) bool { + if h.Version != other.Version { + return false + } + thisLen := len(h.Roots) + if thisLen != len(other.Roots) { + return false + } + // Headers with a single root are popular. + // Implement a fast execution path for popular cases. + if thisLen == 1 { + return h.Roots[0].Equals(other.Roots[0]) + } + + // Check other contains all roots. + for _, r := range h.Roots { + if !other.containsRoot(r) { + return false + } + } + return true +} + +func (h *CarHeader) containsRoot(root cid.Cid) bool { + for _, r := range h.Roots { + if r.Equals(root) { + return true + } + } + return false +} diff --git a/ipld/car/v2/internal/carv1/car_test.go b/ipld/car/v2/internal/carv1/car_test.go index b637fcf43f..fb0b0db37e 100644 --- a/ipld/car/v2/internal/carv1/car_test.go +++ b/ipld/car/v2/internal/carv1/car_test.go @@ -8,6 +8,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + cid "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" dag "github.com/ipfs/go-merkledag" @@ -229,3 +231,69 @@ func TestBadHeaders(t *testing.T) { }) } } + +func TestCarHeaderEquals(t *testing.T) { + oneCid := dag.NewRawNode([]byte("fish")).Cid() + anotherCid := dag.NewRawNode([]byte("lobster")).Cid() + tests := []struct { + name string + one CarHeader + other CarHeader + want bool + }{ + { + "SameVersionNilRootsIsEqual", + CarHeader{nil, 1}, + CarHeader{nil, 1}, + true, + }, + { + "SameVersionEmptyRootsIsEqual", + CarHeader{[]cid.Cid{}, 1}, + CarHeader{[]cid.Cid{}, 1}, + true, + }, + { + "SameVersionNonEmptySameRootsIsEqual", + CarHeader{[]cid.Cid{oneCid}, 1}, + CarHeader{[]cid.Cid{oneCid}, 1}, + true, + }, + { + "SameVersionNonEmptySameRootsInDifferentOrderIsEqual", + CarHeader{[]cid.Cid{oneCid, anotherCid}, 1}, + CarHeader{[]cid.Cid{anotherCid, oneCid}, 1}, + true, + }, + { + "SameVersionDifferentRootsIsNotEqual", + CarHeader{[]cid.Cid{oneCid}, 1}, + CarHeader{[]cid.Cid{anotherCid}, 1}, + false, + }, + { + "DifferentVersionDifferentRootsIsNotEqual", + CarHeader{[]cid.Cid{oneCid}, 0}, + CarHeader{[]cid.Cid{anotherCid}, 1}, + false, + }, + { + "MismatchingVersionIsNotEqual", + CarHeader{nil, 0}, + CarHeader{nil, 1}, + false, + }, + { + "ZeroValueHeadersAreEqual", + CarHeader{}, + CarHeader{}, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.one.Equals(tt.other) + require.Equal(t, tt.want, got, "Equals() = %v, want %v", got, tt.want) + }) + } +} diff --git a/ipld/car/v2/internal/io/offset_read_seeker.go b/ipld/car/v2/internal/io/offset_read_seeker.go index c92c3154c0..4b701351fb 100644 --- a/ipld/car/v2/internal/io/offset_read_seeker.go +++ b/ipld/car/v2/internal/io/offset_read_seeker.go @@ -55,11 +55,16 @@ func (o *OffsetReadSeeker) Offset() int64 { func (o *OffsetReadSeeker) Seek(offset int64, whence int) (int64, error) { switch whence { case io.SeekStart: - o.off = offset + o.off = offset + o.base case io.SeekCurrent: o.off += offset case io.SeekEnd: panic("unsupported whence: SeekEnd") } - return o.off, nil + return o.Position(), nil +} + +// Position returns the current position of this reader relative to the initial offset. +func (o *OffsetReadSeeker) Position() int64 { + return o.off - o.base } diff --git a/ipld/car/v2/internal/io/offset_write_seeker.go b/ipld/car/v2/internal/io/offset_write_seeker.go new file mode 100644 index 0000000000..7e0f6ba58e --- /dev/null +++ b/ipld/car/v2/internal/io/offset_write_seeker.go @@ -0,0 +1,41 @@ +package io + +import "io" + +var ( + _ io.Writer = (*OffsetWriteSeeker)(nil) + _ io.WriteSeeker = (*OffsetWriteSeeker)(nil) +) + +type OffsetWriteSeeker struct { + w io.WriterAt + base int64 + offset int64 +} + +func NewOffsetWriter(w io.WriterAt, off int64) *OffsetWriteSeeker { + return &OffsetWriteSeeker{w, off, off} +} + +func (ow *OffsetWriteSeeker) Write(b []byte) (n int, err error) { + n, err = ow.w.WriteAt(b, ow.offset) + ow.offset += int64(n) + return +} + +func (ow *OffsetWriteSeeker) Seek(offset int64, whence int) (int64, error) { + switch whence { + case io.SeekStart: + ow.offset = offset + ow.base + case io.SeekCurrent: + ow.offset += offset + case io.SeekEnd: + panic("unsupported whence: SeekEnd") + } + return ow.Position(), nil +} + +// Position returns the current position of this writer relative to the initial offset, i.e. the number of bytes written. +func (ow *OffsetWriteSeeker) Position() int64 { + return ow.offset - ow.base +} diff --git a/ipld/car/v2/internal/io/offset_writer.go b/ipld/car/v2/internal/io/offset_writer.go deleted file mode 100644 index 1dd810165b..0000000000 --- a/ipld/car/v2/internal/io/offset_writer.go +++ /dev/null @@ -1,26 +0,0 @@ -package io - -import "io" - -var _ io.Writer = (*OffsetWriter)(nil) - -type OffsetWriter struct { - w io.WriterAt - base int64 - offset int64 -} - -func NewOffsetWriter(w io.WriterAt, off int64) *OffsetWriter { - return &OffsetWriter{w, off, off} -} - -func (ow *OffsetWriter) Write(b []byte) (n int, err error) { - n, err = ow.w.WriteAt(b, ow.offset) - ow.offset += int64(n) - return -} - -// Position returns the current position of this writer relative to the initial offset, i.e. the number of bytes written. -func (ow *OffsetWriter) Position() int64 { - return ow.offset - ow.base -} diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index c172315cd7..a7dd6c0e72 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -1,11 +1,12 @@ package car import ( + "os" + "testing" + "github.com/ipld/go-car/v2/index" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "os" - "testing" ) func TestReadOrGenerateIndex(t *testing.T) { @@ -52,7 +53,6 @@ func TestReadOrGenerateIndex(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - carFile, err := os.Open(tt.carPath) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, carFile.Close()) }) From e71a1c90584d0521e5d65b3551ab01cd80d8b695 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 9 Jul 2021 12:18:45 +0100 Subject: [PATCH 105/291] Avoid creating files on resumption in`ReadWrite` blockstore When resumption is enabled, we do not want to create a file if it does not exist. Similarly, when resumption is disabled, fail if the given file at path already exists to avoid unintended file overrides and creations. This commit was moved from ipld/go-car@8c08fbfef55c8f1463bbd4eb3004011bc076a23a --- ipld/car/v2/blockstore/readwrite.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 286b55e88c..844d04f892 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -114,9 +114,9 @@ func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, err opt(b) } - fFlag := os.O_RDWR | os.O_CREATE + fFlag := os.O_RDWR if !b.resume { - fFlag = fFlag | os.O_EXCL + fFlag = fFlag | os.O_CREATE | os.O_EXCL } var err error b.f, err = os.OpenFile(path, fFlag, 0o666) // TODO: Should the user be able to configure FileMode permissions? From ccb72111954bf90444083030f7fefe71b9ae6529 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 9 Jul 2021 17:58:30 +0100 Subject: [PATCH 106/291] Remove resumption option for automatic resumption when file exists When file exists, attempt resumption by default. This seems like a more user-friendly API and less requirements to explicitly tune knobs. Explicitly fail of the file is finalized. We technically can append to a finalized file but that involves changes in multiple places in reader abstractions. Left a TODO as a feature for future when asked for. Close off file if construction of ReadWrite blockstore fails after file is opened. If left open, it causes test failures when t cleans up temp files. This commit was moved from ipld/go-car@eca650ea663fe729f435295e23043828e4a4bb6e --- ipld/car/v2/blockstore/doc.go | 5 +- ipld/car/v2/blockstore/readwrite.go | 125 ++++++++++++++--------- ipld/car/v2/blockstore/readwrite_test.go | 14 ++- 3 files changed, 89 insertions(+), 55 deletions(-) diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index e31c043299..c41500d78d 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -13,7 +13,6 @@ // Upon finalization, the instance can no longer be used for reading or writing blocks and will // panic if used. To continue reading the blocks users are encouraged to use ReadOnly blockstore // instantiated from the same file path using OpenReadOnly. -// A user may resume reading/writing from files produced by an instance of ReadWrite blockstore that -// on which ReadWrite.Finalize was never called. To resume, WithResumption option must be set to -// true. See docs on WithResumption for usage criteria. +// A user may resume reading/writing from files produced by an instance of ReadWrite blockstore. The +// resumption is attempted automatically, if the path passed to NewReadWrite exists. package blockstore diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 844d04f892..fda3223bff 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -41,7 +41,6 @@ type ReadWrite struct { header carv2.Header dedupCids bool - resume bool } // TODO consider exposing interfaces @@ -70,74 +69,87 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re b.dedupCids = true } -// WithResumption sets whether the blockstore should resume on a file produced by a ReadWrite -// blockstore on which ReadWrite.Finalize was not called. +// NewReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs and options. +// +// ReadWrite.Finalize must be called once putting and reading blocks are no longer needed. +// Upon calling ReadWrite.Finalize the CAR v2 header and index are written out onto the file and the +// backing file is closed. Once finalized, all read and write calls to this blockstore will result +// in panics. Note, a finalized file cannot be resumed from. // -// This option is set to false by default, i.e. disabled. When disabled, the file at path must not -// exist, otherwise an error is returned upon ReadWrite blockstore construction. +// If a file at given path does not exist, the instantiation will write car.Pragma and data payload +// header (i.e. the inner CAR v1 header) onto the file before returning. // -// When this option is set to true the existing data frames in file are re-indexed, allowing the -// caller to continue putting any remaining blocks without having to re-ingest blocks for which -// previous ReadWrite.Put returned successfully. +// When the given path already exists, the blockstore will attempt to resume from it. +// On resumption the existing data frames in file are re-indexed, allowing the caller to continue +// putting any remaining blocks without having to re-ingest blocks for which previous ReadWrite.Put +// returned successfully. // -// Resumption is only allowed on files that satisfy the following criteria: +// Resumption only works on files that were created by a previous instance of a ReadWrite +// blockstore. This means a file created as a result of a successful call to NewReadWrite can be +// resumed from as long as write operations such as ReadWrite.Put, and ReadWrite.PutMany returned +// successfully and ReadWrite. On resumption the roots argument and WithCarV1Padding option must match the +// previous instantiation of ReadWrite blockstore that created the file. +// More explicitly, the file resuming from must: // 1. start with a complete CAR v2 car.Pragma. // 2. contain a complete CAR v1 data header with root CIDs matching the CIDs passed to the -// constructor, starting at offset specified by WithCarV1Padding, followed by zero or more -// complete data frames. If any corrupt data frames are present the resumption will fail. Note, -// it is important that new instantiations of ReadWrite blockstore with resumption enabled use -// the same WithCarV1Padding option, since this option is used to locate the offset at which -// the data payload starts. -// 3. have not been produced by a ReadWrite blockstore that was finalized, i.e. call to -// ReadWrite.Finalize returned successfully. -func WithResumption(enabled bool) Option { - return func(b *ReadWrite) { - b.resume = enabled - } -} - -// NewReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs and options. +// constructor, starting at offset optionally padded by WithCarV1Padding, followed by zero or +// more complete data frames. If any corrupt data frames are present the resumption will fail. +// Note, if set previously, the blockstore must use the same WithCarV1Padding option as before, +// since this option is used to locate the CAR v1 data payload. +// 3. ReadWrite.Finalize must not have been called on the file. func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { - // TODO either lock the file or open exclusively; can we do somethign to reduce edge cases. - - b := &ReadWrite{ - header: carv2.NewHeader(0), + // Try and resume by default if the file exists. + resume := true + if _, err := os.Stat(path); err != nil { // TODO should we use stats to avoid resuming from files with zero size? + if os.IsNotExist(err) { + resume = false + } else { + return nil, err + } + } + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o666) // TODO: Should the user be able to configure FileMode permissions? + if err != nil { + return nil, fmt.Errorf("could not open read/write file: %w", err) } - indexcls, ok := index.BuildersByCodec[index.IndexInsertion] + // If construction of blockstore fails, make sure to close off the open file. + defer func() { + if err != nil { + f.Close() + } + }() + + idxBuilder, ok := index.BuildersByCodec[index.IndexInsertion] if !ok { return nil, fmt.Errorf("unknownindex codec: %#v", index.IndexInsertion) } - b.idx = (indexcls()).(*index.InsertionIndex) + // Instantiate block store. + // Set the header fileld before applying options since padding options may modify header. + rwbs := &ReadWrite{ + f: f, + idx: (idxBuilder()).(*index.InsertionIndex), + header: carv2.NewHeader(0), + } for _, opt := range opts { - opt(b) + opt(rwbs) } - fFlag := os.O_RDWR - if !b.resume { - fFlag = fFlag | os.O_CREATE | os.O_EXCL - } - var err error - b.f, err = os.OpenFile(path, fFlag, 0o666) // TODO: Should the user be able to configure FileMode permissions? - if err != nil { - return nil, fmt.Errorf("could not open read/write file: %w", err) - } - b.carV1Writer = internalio.NewOffsetWriter(b.f, int64(b.header.CarV1Offset)) - v1r := internalio.NewOffsetReadSeeker(b.f, int64(b.header.CarV1Offset)) - b.ReadOnly = ReadOnly{backing: v1r, idx: b.idx, carv2Closer: b.f} + rwbs.carV1Writer = internalio.NewOffsetWriter(rwbs.f, int64(rwbs.header.CarV1Offset)) + v1r := internalio.NewOffsetReadSeeker(rwbs.f, int64(rwbs.header.CarV1Offset)) + rwbs.ReadOnly = ReadOnly{backing: v1r, idx: rwbs.idx, carv2Closer: rwbs.f} - if b.resume { - if err = b.resumeWithRoots(roots); err != nil { + if resume { + if err = rwbs.resumeWithRoots(roots); err != nil { return nil, err } } else { - if err = b.initWithRoots(roots); err != nil { + if err = rwbs.initWithRoots(roots); err != nil { return nil, err } } - return b, nil + return rwbs, nil } func (b *ReadWrite) initWithRoots(roots []cid.Cid) error { @@ -162,6 +174,17 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { return fmt.Errorf("cannot resume on CAR file with version %v", version) } + // Check if file is finalized. + // A file is finalized when it contains a valid CAR v2 header with non-zero v1 offset. + // If finalized, cannot resume from. + var headerInFile carv2.Header + if _, err := headerInFile.ReadFrom(internalio.NewOffsetReadSeeker(b.f, carv2.PragmaSize)); err == nil { + // TODO: we technically can; this could be a feature to do if asked for. + if headerInFile.CarV1Offset != 0 { + return fmt.Errorf("cannot resume from a finalized file") + } + } + // Use the given CAR v1 padding to instantiate the CAR v1 reader on file. v1r := internalio.NewOffsetReadSeeker(b.ReadOnly.backing, 0) header, err := carv1.ReadHeader(bufio.NewReader(v1r)) @@ -277,15 +300,17 @@ func (b *ReadWrite) Finalize() error { // TODO see if folks need to continue reading from a finalized blockstore, if so return ReadOnly blockstore here. b.header = b.header.WithCarV1Size(uint64(b.carV1Writer.Position())) defer b.Close() - if _, err := b.header.WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)); err != nil { - return err - } + // TODO if index not needed don't bother flattening it. fi, err := b.idx.Flatten() if err != nil { return err } - return index.WriteTo(fi, internalio.NewOffsetWriter(b.f, int64(b.header.IndexOffset))) + if err := index.WriteTo(fi, internalio.NewOffsetWriter(b.f, int64(b.header.IndexOffset))); err != nil { + return err + } + _, err = b.header.WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)) + return err } func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 914d20be18..379c48ee35 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -306,7 +306,7 @@ func TestBlockstoreResumption(t *testing.T) { // Note, we don't have to close the file for resumption to work. // We do this to avoid resource leak during testing. require.NoError(t, subject.Close()) - subject, err = blockstore.NewReadWrite(path, r.Header.Roots, blockstore.WithResumption(true)) + subject, err = blockstore.NewReadWrite(path, r.Header.Roots) require.NoError(t, err) } require.NoError(t, subject.Put(b)) @@ -314,7 +314,7 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, subject.Close()) // Finalize the blockstore to complete partially written CAR v2 file. - subject, err = blockstore.NewReadWrite(path, r.Header.Roots, blockstore.WithResumption(true)) + subject, err = blockstore.NewReadWrite(path, r.Header.Roots) require.NoError(t, err) require.NoError(t, subject.Finalize()) @@ -357,3 +357,13 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, err) require.Equal(t, wantIdx, gotIdx) } + +func TestBlockstoreResumptionFailsOnFinalizedFile(t *testing.T) { + path := filepath.Join(t.TempDir(), "readwrite-resume-finalized.car") + // Create an incomplete CAR v2 file with no blocks put. + subject, err := blockstore.NewReadWrite(path, []cid.Cid{}) + require.NoError(t, err) + require.NoError(t, subject.Finalize()) + _, err = blockstore.NewReadWrite(path, []cid.Cid{}) + require.Errorf(t, err, "cannot resume from a finalized file") +} From 7633fa073ceb6feb62b0a8f99c9d8d061d5d91ef Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 9 Jul 2021 20:21:42 +0100 Subject: [PATCH 107/291] Fix bug in ReadOnly blockstore GetSize Fix a bug where the returned size in `ReadOnly.GetSize` blockstore was incorrect and the CID was being read from the wrong offset. Implement tests to assert returned size matches the block raw data size. Fixes #133 This commit was moved from ipld/go-car@7373fe29be3dbd8bf17300976a8ee72a31cb0e4c --- ipld/car/v2/blockstore/readonly.go | 10 +++--- ipld/car/v2/blockstore/readonly_test.go | 43 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 ipld/car/v2/blockstore/readonly_test.go diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 8ef74833ef..ae3259ae31 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -189,19 +189,19 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { if err != nil { return -1, err } - l, err := varint.ReadUvarint(internalio.NewOffsetReadSeeker(b.backing, int64(idx))) + rdr := internalio.NewOffsetReadSeeker(b.backing, int64(idx)) + frameLen, err := varint.ReadUvarint(rdr) if err != nil { return -1, blockstore.ErrNotFound } - _, c, err := cid.CidFromReader(internalio.NewOffsetReadSeeker(b.backing, int64(idx+l))) + cidLen, readCid, err := cid.CidFromReader(rdr) if err != nil { return 0, err } - if !c.Equals(key) { + if !readCid.Equals(key) { return -1, blockstore.ErrNotFound } - // get cid. validate. - return int(l), err + return int(frameLen) - cidLen, err } // Put is not supported and always returns an error. diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go new file mode 100644 index 0000000000..71803dd932 --- /dev/null +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -0,0 +1,43 @@ +package blockstore + +import ( + "io" + "os" + "testing" + + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/stretchr/testify/require" +) + +func TestReadOnlyGetSize(t *testing.T) { + carv1Path := "testdata/test.car" + subject, err := OpenReadOnly(carv1Path) + t.Cleanup(func() { subject.Close() }) + require.NoError(t, err) + + f, err := os.Open(carv1Path) + require.NoError(t, err) + t.Cleanup(func() { f.Close() }) + v1r, err := carv1.NewCarReader(f) + require.NoError(t, err) + for { + wantBlock, err := v1r.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + key := wantBlock.Cid() + + // Assert returned size matches the block.RawData length. + getSize, err := subject.GetSize(key) + wantSize := len(wantBlock.RawData()) + require.NoError(t, err) + require.Equal(t, wantSize, getSize) + + // While at it test blocks are as expected. + gotBlock, err := subject.Get(key) + require.NoError(t, err) + require.Equal(t, wantBlock, gotBlock) + } +} From aec7ae84171372ca05cd3c74d2e01807d6cc9194 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 9 Jul 2021 21:18:04 +0100 Subject: [PATCH 108/291] Implement more tests for `ReadOnly` blockstore Implement tests that check `ReadOnly` blockstore functions work based on data payload originated from a V1 or V2 formatted CAR file. Implement tests that assert instantiation of `ReadOnly` blockstore fails on unexpected pragma version. This commit was moved from ipld/go-car@ba7ed7c9668c23591b54fd9672fe74f0200d6811 --- ipld/car/v2/blockstore/readonly_test.go | 132 +++++++++++++++++++----- 1 file changed, 108 insertions(+), 24 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 71803dd932..cd70844e94 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -1,43 +1,127 @@ package blockstore import ( + "context" "io" "os" "testing" + "time" + + "github.com/ipfs/go-cid" + carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/internal/carv1" "github.com/stretchr/testify/require" ) -func TestReadOnlyGetSize(t *testing.T) { - carv1Path := "testdata/test.car" - subject, err := OpenReadOnly(carv1Path) - t.Cleanup(func() { subject.Close() }) +func TestReadOnly(t *testing.T) { + tests := []struct { + name string + v1OrV2path string + v1r *carv1.CarReader + }{ + { + "OpenedWithCarV1", + "testdata/test.car", + newReaderFromV1File(t, "testdata/test.car"), + }, + { + "OpenedWithAnotherCarV1", + "../testdata/sample-v1.car", + newReaderFromV1File(t, "../testdata/sample-v1.car"), + }, + { + "OpenedWithCarV2", + "../testdata/sample-wrapped-v2.car", + newReaderFromV2File(t, "../testdata/sample-wrapped-v2.car"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + subject, err := OpenReadOnly(tt.v1OrV2path) + t.Cleanup(func() { subject.Close() }) + require.NoError(t, err) + + // Assert roots match v1 payload. + wantRoots := tt.v1r.Header.Roots + gotRoots, err := subject.Roots() + require.NoError(t, err) + require.Equal(t, wantRoots, gotRoots) + + var wantCids []cid.Cid + for { + wantBlock, err := tt.v1r.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + key := wantBlock.Cid() + wantCids = append(wantCids, key) + + // Assert blockstore contains key. + has, err := subject.Has(key) + require.NoError(t, err) + require.True(t, has) + + // Assert size matches block raw data length. + gotSize, err := subject.GetSize(key) + wantSize := len(wantBlock.RawData()) + require.NoError(t, err) + require.Equal(t, wantSize, gotSize) + + // Assert block itself matches v1 payload block. + gotBlock, err := subject.Get(key) + require.NoError(t, err) + require.Equal(t, wantBlock, gotBlock) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + + // Assert all cids in blockstore match v1 payload CIDs. + allKeysChan, err := subject.AllKeysChan(ctx) + require.NoError(t, err) + var gotCids []cid.Cid + for gotKey := range allKeysChan { + gotCids = append(gotCids, gotKey) + } + require.Equal(t, wantCids, gotCids) + }) + } +} + +func TestOpenReadOnlyFailsOnUnknownVersion(t *testing.T) { + subject, err := OpenReadOnly("../testdata/sample-rootless-v42.car") + require.Errorf(t, err, "unsupported car version: 42") + require.Nil(t, subject) +} + +func TestNewReadOnlyFailsOnUnknownVersion(t *testing.T) { + f, err := os.Open("../testdata/sample-rootless-v42.car") require.NoError(t, err) + t.Cleanup(func() { f.Close() }) + subject, err := NewReadOnly(f, nil) + require.Errorf(t, err, "unsupported car version: 42") + require.Nil(t, subject) +} +func newReaderFromV1File(t *testing.T, carv1Path string) *carv1.CarReader { f, err := os.Open(carv1Path) require.NoError(t, err) t.Cleanup(func() { f.Close() }) v1r, err := carv1.NewCarReader(f) require.NoError(t, err) - for { - wantBlock, err := v1r.Next() - if err == io.EOF { - break - } - require.NoError(t, err) - - key := wantBlock.Cid() - - // Assert returned size matches the block.RawData length. - getSize, err := subject.GetSize(key) - wantSize := len(wantBlock.RawData()) - require.NoError(t, err) - require.Equal(t, wantSize, getSize) - - // While at it test blocks are as expected. - gotBlock, err := subject.Get(key) - require.NoError(t, err) - require.Equal(t, wantBlock, gotBlock) - } + return v1r +} + +func newReaderFromV2File(t *testing.T, carv2Path string) *carv1.CarReader { + f, err := os.Open(carv2Path) + require.NoError(t, err) + t.Cleanup(func() { f.Close() }) + v2r, err := carv2.NewReader(f) + require.NoError(t, err) + v1r, err := carv1.NewCarReader(v2r.CarV1Reader()) + require.NoError(t, err) + return v1r } From 054d96d43470e68ba718995fe3e80afed1c15186 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 12 Jul 2021 12:45:13 +0100 Subject: [PATCH 109/291] Assert write operations on ReadOnly blockstore panic Update typo in docs while at it. This commit was moved from ipld/go-car@224cd8fd875cc5ecc8941cf4af37b3eae0661dd3 --- ipld/car/v2/blockstore/readonly_test.go | 13 +++++++------ ipld/car/v2/blockstore/readwrite.go | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index cd70844e94..4997481724 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" @@ -74,6 +76,11 @@ func TestReadOnly(t *testing.T) { gotBlock, err := subject.Get(key) require.NoError(t, err) require.Equal(t, wantBlock, gotBlock) + + // Assert write operations panic + require.Panics(t, func() { subject.Put(wantBlock) }) + require.Panics(t, func() { subject.PutMany([]blocks.Block{wantBlock}) }) + require.Panics(t, func() { subject.DeleteBlock(key) }) } ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) @@ -91,12 +98,6 @@ func TestReadOnly(t *testing.T) { } } -func TestOpenReadOnlyFailsOnUnknownVersion(t *testing.T) { - subject, err := OpenReadOnly("../testdata/sample-rootless-v42.car") - require.Errorf(t, err, "unsupported car version: 42") - require.Nil(t, subject) -} - func TestNewReadOnlyFailsOnUnknownVersion(t *testing.T) { f, err := os.Open("../testdata/sample-rootless-v42.car") require.NoError(t, err) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index fda3223bff..9cfd638119 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -86,10 +86,10 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re // // Resumption only works on files that were created by a previous instance of a ReadWrite // blockstore. This means a file created as a result of a successful call to NewReadWrite can be -// resumed from as long as write operations such as ReadWrite.Put, and ReadWrite.PutMany returned -// successfully and ReadWrite. On resumption the roots argument and WithCarV1Padding option must match the -// previous instantiation of ReadWrite blockstore that created the file. -// More explicitly, the file resuming from must: +// resumed from as long as write operations such as ReadWrite.Put, ReadWrite.PutMany returned +// successfully and ReadWrite.Finalize was never called. On resumption the roots argument and +// WithCarV1Padding option must match the previous instantiation of ReadWrite blockstore that +// created the file. More explicitly, the file resuming from must: // 1. start with a complete CAR v2 car.Pragma. // 2. contain a complete CAR v1 data header with root CIDs matching the CIDs passed to the // constructor, starting at offset optionally padded by WithCarV1Padding, followed by zero or From 8bec630c1ddbbd522060c74f890433572f0ef3d4 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 13 Jul 2021 12:22:33 +0100 Subject: [PATCH 110/291] Unexport unecesserry index APIs and integrate with mulitcodec Use the codec dedicated to CAR index sorted when marshalling and unmarshalling `indexSorted`. Note, the code depends on a specific commit of `go-multicodec` `master` branch. This needs to be replaced with a tag once a release is made on the go-multicodec side later. Unexport index APIs that should not be exposed publicly. Remove `Builder` now that it is not needed anywhere. Move `insertionIndex` into `blockstore` package since that's the only place it is used. Introduce an index constructor that takes multicodec code and instantiates an index. Fix ignored errors in `indexsorted.go` during marshalling/unmarshlling. Rename index constructor functions to use consistent terminology; i.e. `new` instead if `mk`. Remove redundant TODOs in code. Relates to: - https://github.com/multiformats/go-multicodec/pull/46 Address review comments * Rename constructor of index by codec to a simpler name and update docs. * Use multicodec.Code as constant instead of wrappint unit64 every time. This commit was moved from ipld/go-car@2b593c11b4c706d52fc920b92052bcbdd411f687 --- .../{index => blockstore}/insertionindex.go | 86 +++++++++++-------- ipld/car/v2/blockstore/readwrite.go | 17 ++-- ipld/car/v2/index/errors.go | 8 +- ipld/car/v2/index/generator.go | 3 +- ipld/car/v2/index/index.go | 48 +++++------ ipld/car/v2/index/indexgobhash.go | 10 ++- ipld/car/v2/index/indexhashed.go | 10 ++- ipld/car/v2/index/indexsorted.go | 23 +++-- 8 files changed, 107 insertions(+), 98 deletions(-) rename ipld/car/v2/{index => blockstore}/insertionindex.go (58%) diff --git a/ipld/car/v2/index/insertionindex.go b/ipld/car/v2/blockstore/insertionindex.go similarity index 58% rename from ipld/car/v2/index/insertionindex.go rename to ipld/car/v2/blockstore/insertionindex.go index 78dace1df2..8e664eac90 100644 --- a/ipld/car/v2/index/insertionindex.go +++ b/ipld/car/v2/blockstore/insertionindex.go @@ -1,29 +1,36 @@ -package index +package blockstore import ( "bytes" "encoding/binary" + "errors" "fmt" "io" + "github.com/ipld/go-car/v2/index" + "github.com/multiformats/go-multicodec" + "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" "github.com/petar/GoLLRB/llrb" cbor "github.com/whyrusleeping/cbor/go" ) -type InsertionIndex struct { - items llrb.LLRB -} +var ( + errUnsupported = errors.New("not supported") + insertionIndexCodec = multicodec.Code(0x300003) +) -func (ii *InsertionIndex) InsertNoReplace(key cid.Cid, n uint64) { - ii.items.InsertNoReplace(mkRecordFromCid(key, n)) -} +type ( + insertionIndex struct { + items llrb.LLRB + } -type recordDigest struct { - digest []byte - Record -} + recordDigest struct { + digest []byte + index.Record + } +) func (r recordDigest) Less(than llrb.Item) bool { other, ok := than.(recordDigest) @@ -33,7 +40,7 @@ func (r recordDigest) Less(than llrb.Item) bool { return bytes.Compare(r.digest, other.digest) < 0 } -func mkRecord(r Record) recordDigest { +func newRecordDigest(r index.Record) recordDigest { d, err := multihash.Decode(r.Hash()) if err != nil { panic(err) @@ -42,16 +49,20 @@ func mkRecord(r Record) recordDigest { return recordDigest{d.Digest, r} } -func mkRecordFromCid(c cid.Cid, at uint64) recordDigest { +func newRecordFromCid(c cid.Cid, at uint64) recordDigest { d, err := multihash.Decode(c.Hash()) if err != nil { panic(err) } - return recordDigest{d.Digest, Record{Cid: c, Idx: at}} + return recordDigest{d.Digest, index.Record{Cid: c, Idx: at}} } -func (ii *InsertionIndex) Get(c cid.Cid) (uint64, error) { +func (ii *insertionIndex) insertNoReplace(key cid.Cid, n uint64) { + ii.items.InsertNoReplace(newRecordFromCid(key, n)) +} + +func (ii *insertionIndex) Get(c cid.Cid) (uint64, error) { d, err := multihash.Decode(c.Hash()) if err != nil { return 0, err @@ -59,7 +70,7 @@ func (ii *InsertionIndex) Get(c cid.Cid) (uint64, error) { entry := recordDigest{digest: d.Digest} e := ii.items.Get(entry) if e == nil { - return 0, ErrNotFound + return 0, index.ErrNotFound } r, ok := e.(recordDigest) if !ok { @@ -69,13 +80,11 @@ func (ii *InsertionIndex) Get(c cid.Cid) (uint64, error) { return r.Record.Idx, nil } -func (ii *InsertionIndex) Marshal(w io.Writer) error { +func (ii *insertionIndex) Marshal(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, int64(ii.items.Len())); err != nil { return err } - var err error - iter := func(i llrb.Item) bool { if err = cbor.Encode(w, i.(recordDigest).Record); err != nil { return false @@ -86,30 +95,29 @@ func (ii *InsertionIndex) Marshal(w io.Writer) error { return err } -func (ii *InsertionIndex) Unmarshal(r io.Reader) error { - var len int64 - if err := binary.Read(r, binary.LittleEndian, &len); err != nil { +func (ii *insertionIndex) Unmarshal(r io.Reader) error { + var length int64 + if err := binary.Read(r, binary.LittleEndian, &length); err != nil { return err } d := cbor.NewDecoder(r) - for i := int64(0); i < len; i++ { - var rec Record + for i := int64(0); i < length; i++ { + var rec index.Record if err := d.Decode(&rec); err != nil { return err } - ii.items.InsertNoReplace(mkRecord(rec)) + ii.items.InsertNoReplace(newRecordDigest(rec)) } return nil } -// Codec identifies this index format -func (ii *InsertionIndex) Codec() Codec { - return IndexInsertion +func (ii *insertionIndex) Codec() multicodec.Code { + return insertionIndexCodec } -func (ii *InsertionIndex) Load(rs []Record) error { +func (ii *insertionIndex) Load(rs []index.Record) error { for _, r := range rs { - rec := mkRecord(r) + rec := newRecordDigest(r) if rec.digest == nil { return fmt.Errorf("invalid entry: %v", r) } @@ -118,15 +126,17 @@ func (ii *InsertionIndex) Load(rs []Record) error { return nil } -func mkInsertion() Index { - ii := InsertionIndex{} - return &ii +func newInsertionIndex() *insertionIndex { + return &insertionIndex{} } -// Flatten returns a 'indexsorted' formatted index for more efficient subsequent loading -func (ii *InsertionIndex) Flatten() (Index, error) { - si := BuildersByCodec[IndexSorted]() - rcrds := make([]Record, ii.items.Len()) +// flatten returns a 'indexsorted' formatted index for more efficient subsequent loading +func (ii *insertionIndex) flatten() (index.Index, error) { + si, err := index.New(multicodec.CarIndexSorted) + if err != nil { + return nil, err + } + rcrds := make([]index.Record, ii.items.Len()) idx := 0 iter := func(i llrb.Item) bool { @@ -142,7 +152,7 @@ func (ii *InsertionIndex) Flatten() (Index, error) { return si, nil } -func (ii *InsertionIndex) HasExactCID(c cid.Cid) bool { +func (ii *insertionIndex) hasExactCID(c cid.Cid) bool { d, err := multihash.Decode(c.Hash()) if err != nil { panic(err) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 9cfd638119..c4bb3e4210 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -37,7 +37,7 @@ type ReadWrite struct { f *os.File carV1Writer *internalio.OffsetWriteSeeker ReadOnly - idx *index.InsertionIndex + idx *insertionIndex header carv2.Header dedupCids bool @@ -119,16 +119,11 @@ func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, err } }() - idxBuilder, ok := index.BuildersByCodec[index.IndexInsertion] - if !ok { - return nil, fmt.Errorf("unknownindex codec: %#v", index.IndexInsertion) - } - // Instantiate block store. // Set the header fileld before applying options since padding options may modify header. rwbs := &ReadWrite{ f: f, - idx: (idxBuilder()).(*index.InsertionIndex), + idx: newInsertionIndex(), header: carv2.NewHeader(0), } for _, opt := range opts { @@ -235,7 +230,7 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { if err != nil { return err } - b.idx.InsertNoReplace(c, uint64(frameOffset)) + b.idx.insertNoReplace(c, uint64(frameOffset)) // Seek to the next frame by skipping the block. // The frame length includes the CID, so subtract it. @@ -270,7 +265,7 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { for _, bl := range blks { c := bl.Cid() - if b.dedupCids && b.idx.HasExactCID(c) { + if b.dedupCids && b.idx.hasExactCID(c) { continue } @@ -278,7 +273,7 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { if err := util.LdWrite(b.carV1Writer, c.Bytes(), bl.RawData()); err != nil { return err } - b.idx.InsertNoReplace(c, n) + b.idx.insertNoReplace(c, n) } return nil } @@ -302,7 +297,7 @@ func (b *ReadWrite) Finalize() error { defer b.Close() // TODO if index not needed don't bother flattening it. - fi, err := b.idx.Flatten() + fi, err := b.idx.flatten() if err != nil { return err } diff --git a/ipld/car/v2/index/errors.go b/ipld/car/v2/index/errors.go index fba7afba9a..1a10a98467 100644 --- a/ipld/car/v2/index/errors.go +++ b/ipld/car/v2/index/errors.go @@ -2,9 +2,5 @@ package index import "errors" -var ( - // ErrNotFound signals a record is not found in the index. - ErrNotFound = errors.New("not found") - // errUnsupported signals unsupported operation by an index. - errUnsupported = errors.New("not supported") -) +// ErrNotFound signals a record is not found in the index. +var ErrNotFound = errors.New("not found") diff --git a/ipld/car/v2/index/generator.go b/ipld/car/v2/index/generator.go index 1781f8dcfc..f7c5075a31 100644 --- a/ipld/car/v2/index/generator.go +++ b/ipld/car/v2/index/generator.go @@ -30,7 +30,6 @@ func Generate(v1 io.ReadSeeker) (Index, error) { return nil, fmt.Errorf("error reading car header: %w", err) } - // TODO: Generate should likely just take an io.ReadSeeker. // TODO: ensure the input's header version is 1. offset, err := carv1.HeaderSize(header) @@ -38,7 +37,7 @@ func Generate(v1 io.ReadSeeker) (Index, error) { return nil, err } - idx := mkSorted() + idx := newSorted() records := make([]Record, 0) diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 53df02fbce..4c66ac1376 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -7,6 +7,8 @@ import ( "io" "os" + "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-varint" internalio "github.com/ipld/go-car/v2/internal/io" @@ -16,22 +18,14 @@ import ( // Codec table is a first var-int in CAR indexes const ( - IndexSorted Codec = 0x0400 // as per https://github.com/multiformats/multicodec/pull/220 - - // TODO: unexport these before the final release, probably - IndexHashed Codec = 0x300000 + iota - IndexSingleSorted - IndexGobHashed - IndexInsertion + indexHashed codec = 0x300000 + iota + indexSingleSorted + indexGobHashed ) type ( - // Codec is used as a multicodec identifier for CAR index files - // TODO: use go-multicodec before the final release - Codec int - - // Builder is a constructor for an index type - Builder func() Index + // codec is used as a multicodec identifier for CAR index files + codec int // Record is a pre-processed record of a car item and location. Record struct { @@ -41,7 +35,7 @@ type ( // Index provides an interface for looking up byte offset of a given CID. Index interface { - Codec() Codec + Codec() multicodec.Code Marshal(w io.Writer) error Unmarshal(r io.Reader) error Get(cid.Cid) (uint64, error) @@ -49,14 +43,14 @@ type ( } ) -// BuildersByCodec holds known index formats -// TODO: turn this into a func before the final release? -var BuildersByCodec = map[Codec]Builder{ - IndexHashed: mkHashed, - IndexSorted: mkSorted, - IndexSingleSorted: mkSingleSorted, - IndexGobHashed: mkGobHashed, - IndexInsertion: mkInsertion, +// New constructs a new index corresponding to the given CAR index codec. +func New(codec multicodec.Code) (Index, error) { + switch codec { + case multicodec.CarIndexSorted: + return newSorted(), nil + default: + return nil, fmt.Errorf("unknwon index codec: %v", codec) + } } // Save writes a generated index into the given `path`. @@ -97,15 +91,15 @@ func WriteTo(idx Index, w io.Writer) error { // Returns error if the encoding is not known. func ReadFrom(r io.Reader) (Index, error) { reader := bufio.NewReader(r) - codec, err := varint.ReadUvarint(reader) + code, err := varint.ReadUvarint(reader) if err != nil { return nil, err } - builder, ok := BuildersByCodec[Codec(codec)] - if !ok { - return nil, fmt.Errorf("unknown codec: %d", codec) + codec := multicodec.Code(code) + idx, err := New(codec) + if err != nil { + return nil, err } - idx := builder() if err := idx.Unmarshal(reader); err != nil { return nil, err } diff --git a/ipld/car/v2/index/indexgobhash.go b/ipld/car/v2/index/indexgobhash.go index 9e61001fa1..a74e8b92b9 100644 --- a/ipld/car/v2/index/indexgobhash.go +++ b/ipld/car/v2/index/indexgobhash.go @@ -4,9 +4,12 @@ import ( "encoding/gob" "io" + "github.com/multiformats/go-multicodec" + "github.com/ipfs/go-cid" ) +//lint:ignore U1000 kept for potential future use. type mapGobIndex map[cid.Cid]uint64 func (m *mapGobIndex) Get(c cid.Cid) (uint64, error) { @@ -27,8 +30,8 @@ func (m *mapGobIndex) Unmarshal(r io.Reader) error { return d.Decode(m) } -func (m *mapGobIndex) Codec() Codec { - return IndexHashed +func (m *mapGobIndex) Codec() multicodec.Code { + return multicodec.Code(indexHashed) } func (m *mapGobIndex) Load(rs []Record) error { @@ -38,7 +41,8 @@ func (m *mapGobIndex) Load(rs []Record) error { return nil } -func mkGobHashed() Index { +//lint:ignore U1000 kept for potential future use. +func newGobHashed() Index { mi := make(mapGobIndex) return &mi } diff --git a/ipld/car/v2/index/indexhashed.go b/ipld/car/v2/index/indexhashed.go index b24a9014ae..84b0ad1575 100644 --- a/ipld/car/v2/index/indexhashed.go +++ b/ipld/car/v2/index/indexhashed.go @@ -3,10 +3,13 @@ package index import ( "io" + "github.com/multiformats/go-multicodec" + "github.com/ipfs/go-cid" cbor "github.com/whyrusleeping/cbor/go" ) +//lint:ignore U1000 kept for potential future use. type mapIndex map[cid.Cid]uint64 func (m *mapIndex) Get(c cid.Cid) (uint64, error) { @@ -26,8 +29,8 @@ func (m *mapIndex) Unmarshal(r io.Reader) error { return d.Decode(m) } -func (m *mapIndex) Codec() Codec { - return IndexHashed +func (m *mapIndex) Codec() multicodec.Code { + return multicodec.Code(indexHashed) } func (m *mapIndex) Load(rs []Record) error { @@ -37,7 +40,8 @@ func (m *mapIndex) Load(rs []Record) error { return nil } -func mkHashed() Index { +//lint:ignore U1000 kept for potential future use. +func newHashed() Index { mi := make(mapIndex) return &mi } diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index b7d275103f..a8f401aeff 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -7,6 +7,8 @@ import ( "io" "sort" + "github.com/multiformats/go-multicodec" + "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" ) @@ -42,8 +44,8 @@ func (r recordSet) Swap(i, j int) { r[i], r[j] = r[j], r[i] } -func (s *singleWidthIndex) Codec() Codec { - return IndexSingleSorted +func (s *singleWidthIndex) Codec() multicodec.Code { + return multicodec.Code(indexSingleSorted) } func (s *singleWidthIndex) Marshal(w io.Writer) error { @@ -124,12 +126,14 @@ func (m *multiWidthIndex) Get(c cid.Cid) (uint64, error) { return 0, ErrNotFound } -func (m *multiWidthIndex) Codec() Codec { - return IndexSorted +func (m *multiWidthIndex) Codec() multicodec.Code { + return multicodec.CarIndexSorted } func (m *multiWidthIndex) Marshal(w io.Writer) error { - binary.Write(w, binary.LittleEndian, int32(len(*m))) + if err := binary.Write(w, binary.LittleEndian, int32(len(*m))); err != nil { + return err + } // The widths are unique, but ranging over a map isn't deterministic. // As per the CARv2 spec, we must order buckets by digest length. @@ -153,7 +157,9 @@ func (m *multiWidthIndex) Marshal(w io.Writer) error { func (m *multiWidthIndex) Unmarshal(r io.Reader) error { var l int32 - binary.Read(r, binary.LittleEndian, &l) + if err := binary.Read(r, binary.LittleEndian, &l); err != nil { + return err + } for i := 0; i < int(l); i++ { s := singleWidthIndex{} if err := s.Unmarshal(r); err != nil { @@ -199,12 +205,13 @@ func (m *multiWidthIndex) Load(items []Record) error { return nil } -func mkSorted() Index { +func newSorted() Index { m := make(multiWidthIndex) return &m } -func mkSingleSorted() Index { +//lint:ignore U1000 kept for potential future use. +func newSingleSorted() Index { s := singleWidthIndex{} return &s } From 3945e7e4fba67b26ab22d53de9d2db54a353a628 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 13 Jul 2021 12:34:35 +0100 Subject: [PATCH 111/291] Remove redundant seek before read or generate index Assume the read seeker passed in is seeked to the beginning of CAR payload. This is both for consistency of API and avoid unnecessary seeks when the reader may already be at the right place. Address review comments * Use the file already open to get the stats for resumption purposes. On getting the stats, because the open flag contains `os.O_CREATE` check the size of the file as a way to determine wheter we should attempt resumption or not. In practice this is the same as the code that explicitly differentiates from non-existing files, since file existence doesn't matter here; the key thing is the file content. Plus this also avoids unnecessary errors where the files exists but is empty. * Add TODOs in places to consider enabling deduplication by default and optimise computational complexity of roots check. Note, explicitly enable deduplication by default in a separate PR so that the change is clearly communicated to dependant clients in case it causes any unintended side-effects. * Rename `Equals` to `Matches` to avoid confusion about what it does. Relates to: - https://github.com/ipld/go-car/pull/147#discussion_r668587909 - https://github.com/ipld/go-car/pull/147#discussion_r668603050 - https://github.com/ipld/go-car/pull/147#discussion_r668609927 This commit was moved from ipld/go-car@cb2b58de6580d25adb22a43c41d301e1e6912a95 --- ipld/car/v2/blockstore/readwrite.go | 23 ++++++++++++----------- ipld/car/v2/internal/carv1/car.go | 9 ++++++--- ipld/car/v2/internal/carv1/car_test.go | 22 +++++++++++----------- ipld/car/v2/reader.go | 4 ---- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index c4bb3e4210..3f77af18bb 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -97,21 +97,22 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re // Note, if set previously, the blockstore must use the same WithCarV1Padding option as before, // since this option is used to locate the CAR v1 data payload. // 3. ReadWrite.Finalize must not have been called on the file. +// +// Note, resumption should be used with WithCidDeduplication, so that blocks that are successfully +// written into the file are not re-written. Unless, the user explicitly wants duplicate blocks. func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { - // Try and resume by default if the file exists. - resume := true - if _, err := os.Stat(path); err != nil { // TODO should we use stats to avoid resuming from files with zero size? - if os.IsNotExist(err) { - resume = false - } else { - return nil, err - } - } + // TODO: enable deduplication by default now that resumption is automatically attempted. f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o666) // TODO: Should the user be able to configure FileMode permissions? if err != nil { return nil, fmt.Errorf("could not open read/write file: %w", err) } - + stat, err := f.Stat() + if err != nil { + // Note, we should not get a an os.ErrNotExist here because the flags used to open file includes os.O_CREATE + return nil, err + } + // Try and resume by default if the file size is non-zero. + resume := stat.Size() != 0 // If construction of blockstore fails, make sure to close off the open file. defer func() { if err != nil { @@ -187,7 +188,7 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { // Cannot read the CAR v1 header; the file is most likely corrupt. return fmt.Errorf("error reading car header: %w", err) } - if !header.Equals(carv1.CarHeader{Roots: roots, Version: 1}) { + if !header.Matches(carv1.CarHeader{Roots: roots, Version: 1}) { // Cannot resume if version and root does not match. return errors.New("cannot resume on file with mismatching data header") } diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index 7332ebadb4..d940ce25ab 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -210,11 +210,13 @@ func loadCarSlow(s Store, cr *CarReader) (*CarHeader, error) { } } -// Equals checks whether two headers are equal. -// Two headers are considered equal if: +// Matches checks whether two headers match. +// Two headers are considered matching if: // 1. They have the same version number, and // 2. They contain the same root CIDs in any order. -func (h CarHeader) Equals(other CarHeader) bool { +// Note, this function explicitly ignores the order of roots. +// If order of roots matter use reflect.DeepEqual instead. +func (h CarHeader) Matches(other CarHeader) bool { if h.Version != other.Version { return false } @@ -229,6 +231,7 @@ func (h CarHeader) Equals(other CarHeader) bool { } // Check other contains all roots. + // TODO: should this be optimised for cases where the number of roots are large since it has O(N^2) complexity? for _, r := range h.Roots { if !other.containsRoot(r) { return false diff --git a/ipld/car/v2/internal/carv1/car_test.go b/ipld/car/v2/internal/carv1/car_test.go index fb0b0db37e..e5dd680c8c 100644 --- a/ipld/car/v2/internal/carv1/car_test.go +++ b/ipld/car/v2/internal/carv1/car_test.go @@ -232,7 +232,7 @@ func TestBadHeaders(t *testing.T) { } } -func TestCarHeaderEquals(t *testing.T) { +func TestCarHeaderMatchess(t *testing.T) { oneCid := dag.NewRawNode([]byte("fish")).Cid() anotherCid := dag.NewRawNode([]byte("lobster")).Cid() tests := []struct { @@ -242,49 +242,49 @@ func TestCarHeaderEquals(t *testing.T) { want bool }{ { - "SameVersionNilRootsIsEqual", + "SameVersionNilRootsIsMatching", CarHeader{nil, 1}, CarHeader{nil, 1}, true, }, { - "SameVersionEmptyRootsIsEqual", + "SameVersionEmptyRootsIsMatching", CarHeader{[]cid.Cid{}, 1}, CarHeader{[]cid.Cid{}, 1}, true, }, { - "SameVersionNonEmptySameRootsIsEqual", + "SameVersionNonEmptySameRootsIsMatching", CarHeader{[]cid.Cid{oneCid}, 1}, CarHeader{[]cid.Cid{oneCid}, 1}, true, }, { - "SameVersionNonEmptySameRootsInDifferentOrderIsEqual", + "SameVersionNonEmptySameRootsInDifferentOrderIsMatching", CarHeader{[]cid.Cid{oneCid, anotherCid}, 1}, CarHeader{[]cid.Cid{anotherCid, oneCid}, 1}, true, }, { - "SameVersionDifferentRootsIsNotEqual", + "SameVersionDifferentRootsIsNotMatching", CarHeader{[]cid.Cid{oneCid}, 1}, CarHeader{[]cid.Cid{anotherCid}, 1}, false, }, { - "DifferentVersionDifferentRootsIsNotEqual", + "DifferentVersionDifferentRootsIsNotMatching", CarHeader{[]cid.Cid{oneCid}, 0}, CarHeader{[]cid.Cid{anotherCid}, 1}, false, }, { - "MismatchingVersionIsNotEqual", + "MismatchingVersionIsNotMatching", CarHeader{nil, 0}, CarHeader{nil, 1}, false, }, { - "ZeroValueHeadersAreEqual", + "ZeroValueHeadersAreMatching", CarHeader{}, CarHeader{}, true, @@ -292,8 +292,8 @@ func TestCarHeaderEquals(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.one.Equals(tt.other) - require.Equal(t, tt.want, got, "Equals() = %v, want %v", got, tt.want) + got := tt.one.Matches(tt.other) + require.Equal(t, tt.want, got, "Matches() = %v, want %v", got, tt.want) }) } } diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 0a8aefac44..803e61aaff 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -149,10 +149,6 @@ func (rsa *readSeekerAt) ReadAt(p []byte, off int64) (n int, err error) { // Note, the returned index lives entirely in memory and will not depend on the // given reader to fulfill index lookup. func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { - // Seek to the beginning of the reader in order to read version. - if _, err := rs.Seek(0, io.SeekStart); err != nil { - return nil, err - } // Read version. version, err := ReadVersion(rs) if err != nil { From 742f13dcac2e13b4202f30983e4a50f9f71b7f99 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 13 Jul 2021 16:04:08 +0100 Subject: [PATCH 112/291] Move index generation functionality to root v2 package Move index generation functionality to root, because they read/write CAR files. This leaves the `index` package to only contain the index implementation itself. Move `index.Attach` to root while at it following the same logic. Add TODOs to the implementation of Attach to make it more robust. As is it _could_ result in corrupt v2 files if offset includes padding since v2 header is not updated in this function. Add tests to generate index and move existing tests to correspond to the go file containing generation functionality. This commit was moved from ipld/go-car@24b825365c40b2b44ad16d64a1d1b146f40f2b99 --- ipld/car/v2/blockstore/readonly.go | 4 +- ipld/car/v2/blockstore/readwrite.go | 2 +- ipld/car/v2/blockstore/readwrite_test.go | 2 +- ipld/car/v2/index/doc.go | 5 - ipld/car/v2/index/generator.go | 99 ----------- ipld/car/v2/index/index.go | 13 -- ipld/car/v2/index_gen.go | 162 ++++++++++++++++++ .../v2/{reader_test.go => index_gen_test.go} | 49 +++++- ipld/car/v2/internal/carbs/util/hydrate.go | 4 +- ipld/car/v2/reader.go | 58 ------- ipld/car/v2/writer.go | 24 ++- 11 files changed, 236 insertions(+), 186 deletions(-) delete mode 100644 ipld/car/v2/index/generator.go create mode 100644 ipld/car/v2/index_gen.go rename ipld/car/v2/{reader_test.go => index_gen_test.go} (58%) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index ae3259ae31..f7e1c646bc 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -106,12 +106,12 @@ func generateIndex(at io.ReaderAt) (index.Index, error) { default: rs = internalio.NewOffsetReadSeeker(r, 0) } - return index.Generate(rs) + return carv2.GenerateIndex(rs) } // OpenReadOnly opens a read-only blockstore from a CAR file (either v1 or v2), generating an index if it does not exist. // Note, the generated index if the index does not exist is ephemeral and only stored in memory. -// See index.Generate and Index.Attach for persisting index onto a CAR file. +// See car.GenerateIndex and Index.Attach for persisting index onto a CAR file. func OpenReadOnly(path string) (*ReadOnly, error) { f, err := mmap.Open(path) if err != nil { diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 3f77af18bb..52878ebbcc 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -194,7 +194,7 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { } // TODO See how we can reduce duplicate code here. - // The code here comes from index.Generate. + // The code here comes from car.GenerateIndex. // Copied because we need to populate an insertindex, not a sorted index. // Producing a sorted index via generate, then converting it to insertindex is not possible. // Because Index interface does not expose internal records. diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 379c48ee35..97d1bf58b7 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -353,7 +353,7 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, err) gotIdx, err := index.ReadFrom(v2r.IndexReader()) require.NoError(t, err) - wantIdx, err := index.Generate(v1f) + wantIdx, err := carv2.GenerateIndex(v1f) require.NoError(t, err) require.Equal(t, wantIdx, gotIdx) } diff --git a/ipld/car/v2/index/doc.go b/ipld/car/v2/index/doc.go index 25a38c34c3..4c7beb1f8b 100644 --- a/ipld/car/v2/index/doc.go +++ b/ipld/car/v2/index/doc.go @@ -4,9 +4,4 @@ // Index can be written or read using the following static functions: index.WriteTo and // index.ReadFrom. // -// This package also provides functionality to generate an index from a given CAR v1 file using: -// index.Generate and index.GenerateFromFile -// -// In addition to the above, it provides functionality to attach an index to an index-less CAR v2 -// using index.Attach package index diff --git a/ipld/car/v2/index/generator.go b/ipld/car/v2/index/generator.go deleted file mode 100644 index f7c5075a31..0000000000 --- a/ipld/car/v2/index/generator.go +++ /dev/null @@ -1,99 +0,0 @@ -package index - -import ( - "bufio" - "fmt" - "io" - "os" - - "github.com/multiformats/go-varint" - - "github.com/ipfs/go-cid" - "github.com/ipld/go-car/v2/internal/carv1" -) - -type readSeekerPlusByte struct { - io.ReadSeeker -} - -func (r readSeekerPlusByte) ReadByte() (byte, error) { - var p [1]byte - _, err := io.ReadFull(r, p[:]) - return p[0], err -} - -// Generate generates index for a given car in v1 format. -// The index can be stored using index.Save into a file or serialized using index.WriteTo. -func Generate(v1 io.ReadSeeker) (Index, error) { - header, err := carv1.ReadHeader(bufio.NewReader(v1)) - if err != nil { - return nil, fmt.Errorf("error reading car header: %w", err) - } - - // TODO: ensure the input's header version is 1. - - offset, err := carv1.HeaderSize(header) - if err != nil { - return nil, err - } - - idx := newSorted() - - records := make([]Record, 0) - - // Seek to the first frame. - // Record the start of each frame, which we need for the index records. - frameOffset := int64(0) - if frameOffset, err = v1.Seek(int64(offset), io.SeekStart); err != nil { - return nil, err - } - - for { - // Grab the length of the frame. - // Note that ReadUvarint wants a ByteReader. - length, err := varint.ReadUvarint(readSeekerPlusByte{v1}) - if err != nil { - if err == io.EOF { - break - } - return nil, err - } - - // Null padding; treat zero-length frames as an EOF. - // They don't contain a CID nor block, so they're not useful. - // TODO: Amend the CARv1 spec to explicitly allow this. - if length == 0 { - break - } - - // Grab the CID. - n, c, err := cid.CidFromReader(v1) - if err != nil { - return nil, err - } - records = append(records, Record{c, uint64(frameOffset)}) - - // Seek to the next frame by skipping the block. - // The frame length includes the CID, so subtract it. - if frameOffset, err = v1.Seek(int64(length)-int64(n), io.SeekCurrent); err != nil { - return nil, err - } - } - - if err := idx.Load(records); err != nil { - return nil, err - } - - return idx, nil -} - -// GenerateFromFile walks a car v1 file at the give path and generates an index of cid->byte offset. -// The index can be stored using index.Save into a file or serialized using index.WriteTo. -func GenerateFromFile(path string) (Index, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - return Generate(f) -} diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 4c66ac1376..906be639f9 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -11,8 +11,6 @@ import ( "github.com/multiformats/go-varint" - internalio "github.com/ipld/go-car/v2/internal/io" - "github.com/ipfs/go-cid" ) @@ -63,17 +61,6 @@ func Save(idx Index, path string) error { return WriteTo(idx, stream) } -// Attach attaches a given index to an existing car v2 file at given path and offset. -func Attach(path string, idx Index, offset uint64) error { - out, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) - if err != nil { - return err - } - defer out.Close() - indexWriter := internalio.NewOffsetWriter(out, int64(offset)) - return WriteTo(idx, indexWriter) -} - // WriteTo writes the given idx into w. // The written bytes include the index encoding. // This can then be read back using index.ReadFrom diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go new file mode 100644 index 0000000000..59110897d3 --- /dev/null +++ b/ipld/car/v2/index_gen.go @@ -0,0 +1,162 @@ +package car + +import ( + "bufio" + "fmt" + "io" + "os" + "sync" + + "github.com/ipld/go-car/v2/index" + "github.com/multiformats/go-multicodec" + + "github.com/multiformats/go-varint" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2/internal/carv1" +) + +type readSeekerPlusByte struct { + io.ReadSeeker +} + +func (r readSeekerPlusByte) ReadByte() (byte, error) { + var p [1]byte + _, err := io.ReadFull(r, p[:]) + return p[0], err +} + +// GenerateIndex generates index for a given car in v1 format. +// The index can be stored using index.Save into a file or serialized using index.WriteTo. +func GenerateIndex(v1 io.ReadSeeker) (index.Index, error) { + header, err := carv1.ReadHeader(bufio.NewReader(v1)) + if err != nil { + return nil, fmt.Errorf("error reading car header: %w", err) + } + + if header.Version != 1 { + return nil, fmt.Errorf("expected version to be 1, got %v", header.Version) + } + + offset, err := carv1.HeaderSize(header) + if err != nil { + return nil, err + } + + idx, err := index.New(multicodec.CarIndexSorted) + if err != nil { + return nil, err + } + records := make([]index.Record, 0) + + // Seek to the first frame. + // Record the start of each frame, which we need for the index records. + frameOffset := int64(0) + if frameOffset, err = v1.Seek(int64(offset), io.SeekStart); err != nil { + return nil, err + } + + for { + // Grab the length of the frame. + // Note that ReadUvarint wants a ByteReader. + length, err := varint.ReadUvarint(readSeekerPlusByte{v1}) + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + + // Null padding; treat zero-length frames as an EOF. + // They don't contain a CID nor block, so they're not useful. + // TODO: Amend the CARv1 spec to explicitly allow this. + if length == 0 { + break + } + + // Grab the CID. + n, c, err := cid.CidFromReader(v1) + if err != nil { + return nil, err + } + records = append(records, index.Record{Cid: c, Idx: uint64(frameOffset)}) + + // Seek to the next frame by skipping the block. + // The frame length includes the CID, so subtract it. + if frameOffset, err = v1.Seek(int64(length)-int64(n), io.SeekCurrent); err != nil { + return nil, err + } + } + + if err := idx.Load(records); err != nil { + return nil, err + } + + return idx, nil +} + +// GenerateIndexFromFile walks a car v1 file at the give path and generates an index of cid->byte offset. +// The index can be stored using index.Save into a file or serialized using index.WriteTo. +func GenerateIndexFromFile(path string) (index.Index, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + return GenerateIndex(f) +} + +var _ io.ReaderAt = (*readSeekerAt)(nil) + +type readSeekerAt struct { + rs io.ReadSeeker + mu sync.Mutex +} + +func (rsa *readSeekerAt) ReadAt(p []byte, off int64) (n int, err error) { + rsa.mu.Lock() + defer rsa.mu.Unlock() + if _, err := rsa.rs.Seek(off, io.SeekStart); err != nil { + return 0, err + } + return rsa.rs.Read(p) +} + +// ReadOrGenerateIndex accepts both CAR v1 and v2 format, and reads or generates an index for it. +// When the given reader is in CAR v1 format an index is always generated. +// For a payload in CAR v2 format, an index is only generated if Header.HasIndex returns false. +// An error is returned for all other formats, i.e. versions other than 1 or 2. +// +// Note, the returned index lives entirely in memory and will not depend on the +// given reader to fulfill index lookup. +func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { + // Read version. + version, err := ReadVersion(rs) + if err != nil { + return nil, err + } + // Seek to the begining, since reading the version changes the reader's offset. + if _, err := rs.Seek(0, io.SeekStart); err != nil { + return nil, err + } + + switch version { + case 1: + // Simply generate the index, since there can't be a pre-existing one. + return GenerateIndex(rs) + case 2: + // Read CAR v2 format + v2r, err := NewReader(&readSeekerAt{rs: rs}) + if err != nil { + return nil, err + } + // If index is present, then no need to generate; decode and return it. + if v2r.Header.HasIndex() { + return index.ReadFrom(v2r.IndexReader()) + } + // Otherwise, generate index from CAR v1 payload wrapped within CAR v2 format. + return GenerateIndex(v2r.CarV1Reader()) + default: + return nil, fmt.Errorf("unknown version %v", version) + } +} diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/index_gen_test.go similarity index 58% rename from ipld/car/v2/reader_test.go rename to ipld/car/v2/index_gen_test.go index a7dd6c0e72..133aaf9256 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -23,7 +23,7 @@ func TestReadOrGenerateIndex(t *testing.T) { v1, err := os.Open("testdata/sample-v1.car") require.NoError(t, err) defer v1.Close() - want, err := index.Generate(v1) + want, err := GenerateIndex(v1) require.NoError(t, err) return want }, @@ -31,7 +31,7 @@ func TestReadOrGenerateIndex(t *testing.T) { }, { "CarV2WithIndexIsReturnedAsExpected", - "testdata/sample-v1.car", + "testdata/sample-wrapped-v2.car", func(t *testing.T) index.Index { v2, err := os.Open("testdata/sample-wrapped-v2.car") require.NoError(t, err) @@ -65,3 +65,48 @@ func TestReadOrGenerateIndex(t *testing.T) { }) } } + +func TestGenerateIndexFromFile(t *testing.T) { + tests := []struct { + name string + carPath string + wantIndexer func(t *testing.T) index.Index + wantErr bool + }{ + { + "CarV1IsIndexedAsExpected", + "testdata/sample-v1.car", + func(t *testing.T) index.Index { + v1, err := os.Open("testdata/sample-v1.car") + require.NoError(t, err) + defer v1.Close() + want, err := GenerateIndex(v1) + require.NoError(t, err) + return want + }, + false, + }, + { + "CarV2IsErrorSinceOnlyV1PayloadIsExpected", + "testdata/sample-wrapped-v2.car", + func(t *testing.T) index.Index { return nil }, + true, + }, + { + "CarOtherThanV1OrV2IsError", + "testdata/sample-rootless-v42.car", + func(t *testing.T) index.Index { return nil }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GenerateIndexFromFile(tt.carPath) + if tt.wantErr { + require.Error(t, err) + } + want := tt.wantIndexer(t) + require.Equal(t, want, got) + }) + } +} diff --git a/ipld/car/v2/internal/carbs/util/hydrate.go b/ipld/car/v2/internal/carbs/util/hydrate.go index 8e43556cb5..96bb4c953f 100644 --- a/ipld/car/v2/internal/carbs/util/hydrate.go +++ b/ipld/car/v2/internal/carbs/util/hydrate.go @@ -4,6 +4,8 @@ import ( "fmt" "os" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" ) @@ -14,7 +16,7 @@ func main() { } db := os.Args[1] - idx, err := index.GenerateFromFile(db) + idx, err := carv2.GenerateIndexFromFile(db) if err != nil { fmt.Printf("Error generating index: %v\n", err) return diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 803e61aaff..6cf684c74c 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -4,9 +4,6 @@ import ( "bufio" "fmt" "io" - "sync" - - "github.com/ipld/go-car/v2/index" internalio "github.com/ipld/go-car/v2/internal/io" @@ -124,58 +121,3 @@ func ReadVersion(r io.Reader) (version uint64, err error) { } return header.Version, nil } - -var _ io.ReaderAt = (*readSeekerAt)(nil) - -type readSeekerAt struct { - rs io.ReadSeeker - mu sync.Mutex -} - -func (rsa *readSeekerAt) ReadAt(p []byte, off int64) (n int, err error) { - rsa.mu.Lock() - defer rsa.mu.Unlock() - if _, err := rsa.rs.Seek(off, io.SeekStart); err != nil { - return 0, err - } - return rsa.rs.Read(p) -} - -// ReadOrGenerateIndex accepts both CAR v1 and v2 format, and reads or generates an index for it. -// When the given reader is in CAR v1 format an index is always generated. -// For a payload in CAR v2 format, an index is only generated if Header.HasIndex returns false. -// An error is returned for all other formats, i.e. versions other than 1 or 2. -// -// Note, the returned index lives entirely in memory and will not depend on the -// given reader to fulfill index lookup. -func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { - // Read version. - version, err := ReadVersion(rs) - if err != nil { - return nil, err - } - // Seek to the begining, since reading the version changes the reader's offset. - if _, err := rs.Seek(0, io.SeekStart); err != nil { - return nil, err - } - - switch version { - case 1: - // Simply generate the index, since there can't be a pre-existing one. - return index.Generate(rs) - case 2: - // Read CAR v2 format - v2r, err := NewReader(&readSeekerAt{rs: rs}) - if err != nil { - return nil, err - } - // If index is present, then no need to generate; decode and return it. - if v2r.Header.HasIndex() { - return index.ReadFrom(v2r.IndexReader()) - } - // Otherwise, generate index from CAR v1 payload wrapped within CAR v2 format. - return index.Generate(v2r.CarV1Reader()) - default: - return nil, fmt.Errorf("unknown version %v", version) - } -} diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 59777c87fa..29b04f30a1 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -6,6 +6,8 @@ import ( "io" "os" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" "github.com/ipld/go-car/v2/index" @@ -71,7 +73,7 @@ func (w *Writer) WriteTo(writer io.Writer) (n int64, err error) { return } n += int64(PragmaSize) - // We read the entire car into memory because index.Generate takes a reader. + // We read the entire car into memory because GenerateIndex takes a reader. // TODO Future PRs will make this more efficient by exposing necessary interfaces in index pacakge so that // this can be done in an streaming manner. if err = carv1.WriteCar(w.ctx, w.NodeGetter, w.roots, w.encodedCarV1); err != nil { @@ -124,7 +126,7 @@ func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (int64, error) { // Consider refactoring index to make this process more efficient. // We should avoid reading the entire car into memory since it can be large. reader := bytes.NewReader(carV1) - idx, err := index.Generate(reader) + idx, err := GenerateIndex(reader) if err != nil { return 0, err } @@ -167,9 +169,9 @@ func WrapV1File(srcPath, dstPath string) error { // and does not use any padding before the innner CARv1 or index. func WrapV1(src io.ReadSeeker, dst io.Writer) error { // TODO: verify src is indeed a CARv1 to prevent misuse. - // index.Generate should probably be in charge of that. + // GenerateIndex should probably be in charge of that. - idx, err := index.Generate(src) + idx, err := GenerateIndex(src) if err != nil { return err } @@ -201,3 +203,17 @@ func WrapV1(src io.ReadSeeker, dst io.Writer) error { return nil } + +// AttachIndex attaches a given index to an existing car v2 file at given path and offset. +func AttachIndex(path string, idx index.Index, offset uint64) error { + // TODO: instead of offset, maybe take padding? + // TODO: check that the given path is indeed a CAR v2. + // TODO: update CAR v2 header according to the offset at which index is written out. + out, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) + if err != nil { + return err + } + defer out.Close() + indexWriter := internalio.NewOffsetWriter(out, int64(offset)) + return index.WriteTo(idx, indexWriter) +} From 921e3489ec295f9078492a8b8f448b8e05fe9503 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 14 Jul 2021 16:10:36 +0100 Subject: [PATCH 113/291] Support resumption in `ReadWrite` blockstore for finalized files Support resumption from a complete CARv2 file, effectively implementing append block to a CARv2 file. Note, on resumption the entire file is re-indexed. So it is undesirable to do this for complete cars. Fix a bug where `AllKeysChan` failed on files that contained the index and resumed from. Fix is done by truncating the CARv2 file, removing the index if it is present when blockstore is constructed when resuming. This is because if `AllKeysChan` is called on a file that contains the index, the ReadOnly backing will continue to read until EOF which will result in error. Since file is re-indexed on resumption and we require finalize to be called we truncate the file to remove index. Write header on every put so that resuming from finalized files that put more blocks without then finalizing works as expected. Otherwise, because the last put did not finalize and header is not updated any blocks put will effectively be lost. Add tests that assert resume with and without finalization. Add tests that assert read operations work on resumed read-write blockstore. Fixes: https://github.com/ipld/go-car/issues/155 Address review comments Avoid having to write car v2 header on every put by unfinalizing the blockstore first if resumed from a finalized file. Fix random seed for tests. This commit was moved from ipld/go-car@96e8614d94d704c49c6a5fbde3587b1c577db47d --- ipld/car/v2/blockstore/readwrite.go | 64 ++++++++++++++++++----- ipld/car/v2/blockstore/readwrite_test.go | 66 +++++++++++++++++++----- 2 files changed, 104 insertions(+), 26 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 52878ebbcc..1ea7352caf 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -74,7 +74,8 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re // ReadWrite.Finalize must be called once putting and reading blocks are no longer needed. // Upon calling ReadWrite.Finalize the CAR v2 header and index are written out onto the file and the // backing file is closed. Once finalized, all read and write calls to this blockstore will result -// in panics. Note, a finalized file cannot be resumed from. +// in panics. Note, ReadWrite.Finalize must be called on an open instance regardless of whether any +// blocks were put or not. // // If a file at given path does not exist, the instantiation will write car.Pragma and data payload // header (i.e. the inner CAR v1 header) onto the file before returning. @@ -87,19 +88,21 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re // Resumption only works on files that were created by a previous instance of a ReadWrite // blockstore. This means a file created as a result of a successful call to NewReadWrite can be // resumed from as long as write operations such as ReadWrite.Put, ReadWrite.PutMany returned -// successfully and ReadWrite.Finalize was never called. On resumption the roots argument and -// WithCarV1Padding option must match the previous instantiation of ReadWrite blockstore that -// created the file. More explicitly, the file resuming from must: +// successfully. On resumption the roots argument and WithCarV1Padding option must match the +// previous instantiation of ReadWrite blockstore that created the file. More explicitly, the file +// resuming from must: // 1. start with a complete CAR v2 car.Pragma. // 2. contain a complete CAR v1 data header with root CIDs matching the CIDs passed to the // constructor, starting at offset optionally padded by WithCarV1Padding, followed by zero or // more complete data frames. If any corrupt data frames are present the resumption will fail. // Note, if set previously, the blockstore must use the same WithCarV1Padding option as before, // since this option is used to locate the CAR v1 data payload. -// 3. ReadWrite.Finalize must not have been called on the file. // // Note, resumption should be used with WithCidDeduplication, so that blocks that are successfully // written into the file are not re-written. Unless, the user explicitly wants duplicate blocks. +// +// Resuming from finalized files is allowed. However, resumption will regenerate the index +// regardless by scanning every existing block in file. func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { // TODO: enable deduplication by default now that resumption is automatically attempted. f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o666) // TODO: Should the user be able to configure FileMode permissions? @@ -170,14 +173,45 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { return fmt.Errorf("cannot resume on CAR file with version %v", version) } - // Check if file is finalized. - // A file is finalized when it contains a valid CAR v2 header with non-zero v1 offset. - // If finalized, cannot resume from. + // Check if file was finalized by trying to read the CAR v2 header. + // We check because if finalized the CARv1 reader behaviour needs to be adjusted since + // EOF will not signify end of CAR v1 payload. i.e. index is most likely present. var headerInFile carv2.Header - if _, err := headerInFile.ReadFrom(internalio.NewOffsetReadSeeker(b.f, carv2.PragmaSize)); err == nil { - // TODO: we technically can; this could be a feature to do if asked for. - if headerInFile.CarV1Offset != 0 { - return fmt.Errorf("cannot resume from a finalized file") + _, err = headerInFile.ReadFrom(internalio.NewOffsetReadSeeker(b.f, carv2.PragmaSize)) + + // If reading CARv2 header succeeded, and CARv1 offset in header is not zero then the file is + // most-likely finalized. Check padding and truncate the file to remove index. + // Otherwise, carry on reading the v1 payload at offset determined from b.header. + if err == nil && headerInFile.CarV1Offset != 0 { + if headerInFile.CarV1Offset != b.header.CarV1Offset { + // Assert that the padding on file matches the given WithCarV1Padding option. + gotPadding := headerInFile.CarV1Offset - carv2.PragmaSize - carv2.HeaderSize + wantPadding := b.header.CarV1Offset - carv2.PragmaSize - carv2.HeaderSize + return fmt.Errorf( + "cannot resume from file with mismatched CARv1 offset; "+ + "`WithCarV1Padding` option must match the padding on file."+ + "Expected padding value of %v but got %v", wantPadding, gotPadding, + ) + } else if headerInFile.CarV1Size != 0 { + // If header in file contains the size of car v1, then the index is most likely present. + // Since we will need to re-generate the index, as the one in file is flattened, truncate + // the file so that the Readonly.backing has the right set of bytes to deal with. + // This effectively means resuming from a finalized file will wipe its index even if there + // are no blocks put unless the user calls finalize. + if err := b.f.Truncate(int64(headerInFile.CarV1Offset + headerInFile.CarV1Size)); err != nil { + return err + } + } else { + // If CARv1 size is zero, since CARv1 offset wasn't, then the CARv2 header was + // most-likely partially written. Since we write the header last in Finalize then the + // file most-likely contains the index and we cannot know where it starts, therefore + // can't resume. + return errors.New("corrupt CARv2 header; cannot resume from file") + } + // Now that CARv2 header is present on file, clear it to avoid incorrect size and offset in + // header in case blocksotre is closed without finalization and is resumed from. + if err := b.unfinalize(); err != nil { + return err } } @@ -244,6 +278,11 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { return err } +func (b *ReadWrite) unfinalize() error { + _, err := new(carv2.Header).WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)) + return err +} + func (b *ReadWrite) panicIfFinalized() { if b.header.CarV1Size != 0 { panic("must not use a read-write blockstore after finalizing") @@ -291,7 +330,6 @@ func (b *ReadWrite) Finalize() error { b.mu.Lock() defer b.mu.Unlock() - // TODO check if add index option is set and don't write the index then set index offset to zero. // TODO see if folks need to continue reading from a finalized blockstore, if so return ReadOnly blockstore here. b.header = b.header.WithCarV1Size(uint64(b.carV1Writer.Position())) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 97d1bf58b7..22ac788c54 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -26,6 +26,8 @@ import ( "github.com/ipld/go-car/v2/internal/carv1" ) +var rng = rand.New(rand.NewSource(1413)) + func TestBlockstore(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() @@ -54,7 +56,7 @@ func TestBlockstore(t *testing.T) { cids = append(cids, b.Cid()) // try reading a random one: - candidate := cids[rand.Intn(len(cids))] + candidate := cids[rng.Intn(len(cids))] if has, err := ingester.Has(candidate); !has || err != nil { t.Fatalf("expected to find %s but didn't: %s", candidate, err) } @@ -291,25 +293,62 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, err) // For each block resume on the same file, putting blocks one at a time. + var wantBlockCountSoFar int + wantBlocks := make(map[cid.Cid]blocks.Block) for { b, err := r.Next() if err == io.EOF { break } require.NoError(t, err) - - // 30% chance of subject failing (or closing file without calling Finalize). - // The higher this percentage the slower the test runs considering the number of blocks in the original CAR v1 test payload. - shouldFailAbruptly := rand.Float32() <= 0.3 - if shouldFailAbruptly { - // Close off the open file and re-instantiate a new subject with resumption enabled. - // Note, we don't have to close the file for resumption to work. - // We do this to avoid resource leak during testing. - require.NoError(t, subject.Close()) + wantBlockCountSoFar++ + wantBlocks[b.Cid()] = b + + // 30% chance of subject failing; more concretely: re-instantiating blockstore with the same + // file without calling Finalize. The higher this percentage the slower the test runs + // considering the number of blocks in the original CAR v1 test payload. + resume := rng.Float32() <= 0.3 + // If testing resume case, then flip a coin to decide whether to finalize before blockstore + // re-instantiation or not. Note, both cases should work for resumption since we do not + // limit resumption to unfinalized files. + finalizeBeforeResumption := rng.Float32() <= 0.5 + if resume { + if finalizeBeforeResumption { + require.NoError(t, subject.Finalize()) + } else { + // Close off the open file and re-instantiate a new subject with resumption enabled. + // Note, we don't have to close the file for resumption to work. + // We do this to avoid resource leak during testing. + require.NoError(t, subject.Close()) + } subject, err = blockstore.NewReadWrite(path, r.Header.Roots) require.NoError(t, err) } require.NoError(t, subject.Put(b)) + + // With 10% chance test read operations on an resumed read-write blockstore. + // We don't test on every put to reduce test runtime. + testRead := rng.Float32() <= 0.1 + if testRead { + // Assert read operations on the read-write blockstore are as expected when resumed from an + // existing file + var gotBlockCountSoFar int + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + t.Cleanup(cancel) + keysChan, err := subject.AllKeysChan(ctx) + require.NoError(t, err) + for k := range keysChan { + has, err := subject.Has(k) + require.NoError(t, err) + require.True(t, has) + gotBlock, err := subject.Get(k) + require.NoError(t, err) + require.Equal(t, wantBlocks[k], gotBlock) + gotBlockCountSoFar++ + } + // Assert the number of blocks in file are as expected calculated via AllKeysChan + require.Equal(t, wantBlockCountSoFar, gotBlockCountSoFar) + } } require.NoError(t, subject.Close()) @@ -358,12 +397,13 @@ func TestBlockstoreResumption(t *testing.T) { require.Equal(t, wantIdx, gotIdx) } -func TestBlockstoreResumptionFailsOnFinalizedFile(t *testing.T) { +func TestBlockstoreResumptionIsSupportedOnFinalizedFile(t *testing.T) { path := filepath.Join(t.TempDir(), "readwrite-resume-finalized.car") // Create an incomplete CAR v2 file with no blocks put. subject, err := blockstore.NewReadWrite(path, []cid.Cid{}) require.NoError(t, err) require.NoError(t, subject.Finalize()) - _, err = blockstore.NewReadWrite(path, []cid.Cid{}) - require.Errorf(t, err, "cannot resume from a finalized file") + subject, err = blockstore.NewReadWrite(path, []cid.Cid{}) + t.Cleanup(func() { subject.Close() }) + require.NoError(t, err) } From c473e9dfee091bb70a0f6af01e725b84ed16ee41 Mon Sep 17 00:00:00 2001 From: aarshkshah1992 Date: Thu, 15 Jul 2021 13:18:55 +0530 Subject: [PATCH 114/291] index error should return not found This commit was moved from ipld/go-car@62be0f341082206c4c9b3f39f484762428493c91 --- ipld/car/v2/blockstore/readonly.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index f7e1c646bc..6ceba0c35f 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -166,8 +166,9 @@ func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { defer b.mu.RUnlock() offset, err := b.idx.Get(key) + // TODO Improve error handling; not all errors mean NotFound. if err != nil { - return nil, err + return nil, blockstore.ErrNotFound } entry, data, err := b.readBlock(int64(offset)) if err != nil { From 2e12923651c0d901b78d5f9d595bddadeea8d714 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 15 Jul 2021 10:28:09 +0100 Subject: [PATCH 115/291] Remove dependency to `bufio.Reader` in internal `carv1` package Remove dependency to `bufio.Reader` in internal `carv1` package that seems to be mainly used for peeking a byte to return appropriate error when stream abruptly ends, relating to #36. This allows simplification of code across the repo and remove all unnecessary wrappings of `io.Reader` with `bufio.Reader`. This will also aid simplify the internal IO utilities which will be done in future PRs. For now we simply remove dependency to `bufio.Reader` See: - https://github.com/ipld/go-car/pull/36 This commit was moved from ipld/go-car@cc1e449c9bc81b8d8ce5731406be5ebda14cd4c7 --- ipld/car/v2/blockstore/readonly.go | 7 +++---- ipld/car/v2/blockstore/readwrite.go | 3 +-- ipld/car/v2/car_test.go | 3 +-- ipld/car/v2/index/index.go | 8 ++++---- ipld/car/v2/index_gen.go | 3 +-- ipld/car/v2/internal/carv1/car.go | 14 ++++++-------- ipld/car/v2/internal/carv1/util/util.go | 20 +++++++++----------- ipld/car/v2/internal/io/converter.go | 20 ++++++++++++++++++++ ipld/car/v2/reader.go | 5 ++--- 9 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 ipld/car/v2/internal/io/converter.go diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 6ceba0c35f..658587046b 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -1,7 +1,6 @@ package blockstore import ( - "bufio" "bytes" "context" "errors" @@ -128,7 +127,7 @@ func OpenReadOnly(path string) (*ReadOnly, error) { } func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { - bcid, data, err := util.ReadNode(bufio.NewReader(internalio.NewOffsetReadSeeker(b.backing, idx))) + bcid, data, err := util.ReadNode(internalio.NewOffsetReadSeeker(b.backing, idx)) return bcid, data, err } @@ -222,7 +221,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { // TODO we may use this walk for populating the index, and we need to be able to iterate keys in this way somewhere for index generation. In general though, when it's asked for all keys from a blockstore with an index, we should iterate through the index when possible rather than linear reads through the full car. rdr := internalio.NewOffsetReadSeeker(b.backing, 0) - header, err := carv1.ReadHeader(bufio.NewReader(rdr)) + header, err := carv1.ReadHeader(rdr) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } @@ -281,7 +280,7 @@ func (b *ReadOnly) HashOnRead(bool) { // Roots returns the root CIDs of the backing CAR. func (b *ReadOnly) Roots() ([]cid.Cid, error) { - header, err := carv1.ReadHeader(bufio.NewReader(internalio.NewOffsetReadSeeker(b.backing, 0))) + header, err := carv1.ReadHeader(internalio.NewOffsetReadSeeker(b.backing, 0)) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 1ea7352caf..a0f3b46a07 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -1,7 +1,6 @@ package blockstore import ( - "bufio" "context" "errors" "fmt" @@ -217,7 +216,7 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { // Use the given CAR v1 padding to instantiate the CAR v1 reader on file. v1r := internalio.NewOffsetReadSeeker(b.ReadOnly.backing, 0) - header, err := carv1.ReadHeader(bufio.NewReader(v1r)) + header, err := carv1.ReadHeader(v1r) if err != nil { // Cannot read the CAR v1 header; the file is most likely corrupt. return fmt.Errorf("error reading car header: %w", err) diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index 5fea95bbc9..83a552ba65 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -1,7 +1,6 @@ package car_test import ( - "bufio" "bytes" "testing" @@ -36,7 +35,7 @@ func TestCarV2PragmaLength(t *testing.T) { } func TestCarV2PragmaIsValidCarV1Header(t *testing.T) { - v1h, err := carv1.ReadHeader(bufio.NewReader(bytes.NewReader(carv2.Pragma))) + v1h, err := carv1.ReadHeader(bytes.NewReader(carv2.Pragma)) assert.NoError(t, err, "cannot decode pragma as CBOR with CAR v1 header structure") assert.Equal(t, &carv1.CarHeader{ Roots: nil, diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 906be639f9..96b730f165 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -1,12 +1,13 @@ package index import ( - "bufio" "encoding/binary" "fmt" "io" "os" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-multicodec" "github.com/multiformats/go-varint" @@ -77,8 +78,7 @@ func WriteTo(idx Index, w io.Writer) error { // The reader decodes the index by reading the first byte to interpret the encoding. // Returns error if the encoding is not known. func ReadFrom(r io.Reader) (Index, error) { - reader := bufio.NewReader(r) - code, err := varint.ReadUvarint(reader) + code, err := varint.ReadUvarint(internalio.ToByteReader(r)) if err != nil { return nil, err } @@ -87,7 +87,7 @@ func ReadFrom(r io.Reader) (Index, error) { if err != nil { return nil, err } - if err := idx.Unmarshal(reader); err != nil { + if err := idx.Unmarshal(r); err != nil { return nil, err } return idx, nil diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 59110897d3..cfbc184370 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -1,7 +1,6 @@ package car import ( - "bufio" "fmt" "io" "os" @@ -29,7 +28,7 @@ func (r readSeekerPlusByte) ReadByte() (byte, error) { // GenerateIndex generates index for a given car in v1 format. // The index can be stored using index.Save into a file or serialized using index.WriteTo. func GenerateIndex(v1 io.ReadSeeker) (index.Index, error) { - header, err := carv1.ReadHeader(bufio.NewReader(v1)) + header, err := carv1.ReadHeader(v1) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index d940ce25ab..ab5753bec8 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -1,7 +1,6 @@ package carv1 import ( - "bufio" "context" "fmt" "io" @@ -57,8 +56,8 @@ func WriteCar(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.W return nil } -func ReadHeader(br *bufio.Reader) (*CarHeader, error) { - hb, err := util.LdRead(br) +func ReadHeader(r io.Reader) (*CarHeader, error) { + hb, err := util.LdRead(r) if err != nil { return nil, err } @@ -107,13 +106,12 @@ func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { } type CarReader struct { - br *bufio.Reader + r io.Reader Header *CarHeader } func NewCarReader(r io.Reader) (*CarReader, error) { - br := bufio.NewReader(r) - ch, err := ReadHeader(br) + ch, err := ReadHeader(r) if err != nil { return nil, err } @@ -127,13 +125,13 @@ func NewCarReader(r io.Reader) (*CarReader, error) { } return &CarReader{ - br: br, + r: r, Header: ch, }, nil } func (cr *CarReader) Next() (blocks.Block, error) { - c, data, err := util.ReadNode(cr.br) + c, data, err := util.ReadNode(cr.r) if err != nil { return nil, err } diff --git a/ipld/car/v2/internal/carv1/util/util.go b/ipld/car/v2/internal/carv1/util/util.go index 297d74c8a0..dce53003ba 100644 --- a/ipld/car/v2/internal/carv1/util/util.go +++ b/ipld/car/v2/internal/carv1/util/util.go @@ -1,9 +1,10 @@ package util import ( - "bufio" "io" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-varint" cid "github.com/ipfs/go-cid" @@ -14,8 +15,8 @@ type BytesReader interface { io.ByteReader } -func ReadNode(br *bufio.Reader) (cid.Cid, []byte, error) { - data, err := LdRead(br) +func ReadNode(r io.Reader) (cid.Cid, []byte, error) { + data, err := LdRead(r) if err != nil { return cid.Cid{}, nil, err } @@ -60,15 +61,12 @@ func LdSize(d ...[]byte) uint64 { return sum + uint64(s) } -func LdRead(r *bufio.Reader) ([]byte, error) { - if _, err := r.Peek(1); err != nil { // no more blocks, likely clean io.EOF - return nil, err - } - - l, err := varint.ReadUvarint(r) +func LdRead(r io.Reader) ([]byte, error) { + l, err := varint.ReadUvarint(internalio.ToByteReader(r)) if err != nil { - if err == io.EOF { - return nil, io.ErrUnexpectedEOF // don't silently pretend this is a clean EOF + // If the length of bytes read is non-zero when the error is EOF then signal an unclean EOF. + if l > 0 && err == io.EOF { + return nil, io.ErrUnexpectedEOF } return nil, err } diff --git a/ipld/car/v2/internal/io/converter.go b/ipld/car/v2/internal/io/converter.go new file mode 100644 index 0000000000..37973bbe33 --- /dev/null +++ b/ipld/car/v2/internal/io/converter.go @@ -0,0 +1,20 @@ +package io + +import "io" + +func ToByteReader(r io.Reader) io.ByteReader { + if br, ok := r.(io.ByteReader); ok { + return br + } + return readerPlusByte{r} +} + +type readerPlusByte struct { + io.Reader +} + +func (r readerPlusByte) ReadByte() (byte, error) { + var p [1]byte + _, err := io.ReadFull(r, p[:]) + return p[0], err +} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 6cf684c74c..709048b5ce 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -1,7 +1,6 @@ package car import ( - "bufio" "fmt" "io" @@ -70,7 +69,7 @@ func (r *Reader) Roots() ([]cid.Cid, error) { if r.roots != nil { return r.roots, nil } - header, err := carv1.ReadHeader(bufio.NewReader(r.CarV1Reader())) + header, err := carv1.ReadHeader(r.CarV1Reader()) if err != nil { return nil, err } @@ -115,7 +114,7 @@ func (r *Reader) Close() error { // This function accepts both CAR v1 and v2 payloads. func ReadVersion(r io.Reader) (version uint64, err error) { // TODO if the user provides a reader that sufficiently satisfies what carv1.ReadHeader is asking then use that instead of wrapping every time. - header, err := carv1.ReadHeader(bufio.NewReader(r)) + header, err := carv1.ReadHeader(r) if err != nil { return } From 767480e58cb1ab1ed32687da49e1877d3df3b09d Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 15 Jul 2021 13:17:18 +0100 Subject: [PATCH 116/291] Implement index generation using only `io.Reader` Implement the ability to generate index from a CARv1 payload given only an `io.Reader`, where the previous implementation required `io.ReadSeeker`. The rationale is to be minimal in what we expect in the API, since index generation from a CARv1 payload never need to rewind the reader and only moves forward in the stream. Refactor utility IO functions that convert between types in one place. Implement constructor functions that only instantiate wrappers when the passed argument does not satisfy a required interface. Fixes: - https://github.com/ipld/go-car/issues/146 Relates to: - https://github.com/ipld/go-car/issues/145 This commit was moved from ipld/go-car@6b085bcb2b89b012668f811f86d29715605a87d1 --- ipld/car/v2/index_gen.go | 50 ++++++++--------- ipld/car/v2/internal/io/converter.go | 81 ++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 34 deletions(-) diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index cfbc184370..223939d204 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -6,6 +6,8 @@ import ( "os" "sync" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/ipld/go-car/v2/index" "github.com/multiformats/go-multicodec" @@ -15,20 +17,11 @@ import ( "github.com/ipld/go-car/v2/internal/carv1" ) -type readSeekerPlusByte struct { - io.ReadSeeker -} - -func (r readSeekerPlusByte) ReadByte() (byte, error) { - var p [1]byte - _, err := io.ReadFull(r, p[:]) - return p[0], err -} - // GenerateIndex generates index for a given car in v1 format. // The index can be stored using index.Save into a file or serialized using index.WriteTo. -func GenerateIndex(v1 io.ReadSeeker) (index.Index, error) { - header, err := carv1.ReadHeader(v1) +func GenerateIndex(v1r io.Reader) (index.Index, error) { + reader := internalio.ToByteReadSeeker(v1r) + header, err := carv1.ReadHeader(reader) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } @@ -37,28 +30,26 @@ func GenerateIndex(v1 io.ReadSeeker) (index.Index, error) { return nil, fmt.Errorf("expected version to be 1, got %v", header.Version) } - offset, err := carv1.HeaderSize(header) - if err != nil { - return nil, err - } - idx, err := index.New(multicodec.CarIndexSorted) if err != nil { return nil, err } records := make([]index.Record, 0) - // Seek to the first frame. - // Record the start of each frame, which we need for the index records. - frameOffset := int64(0) - if frameOffset, err = v1.Seek(int64(offset), io.SeekStart); err != nil { + // Record the start of each frame, with first frame starring from current position in the + // reader, i.e. right after the header, since we have only read the header so far. + var frameOffset int64 + + // The Seek call below is equivalent to getting the reader.offset directly. + // We get it through Seek to only depend on APIs of a typical io.Seeker. + // This would also reduce refactoring in case the utility reader is moved. + if frameOffset, err = reader.Seek(0, io.SeekCurrent); err != nil { return nil, err } for { - // Grab the length of the frame. - // Note that ReadUvarint wants a ByteReader. - length, err := varint.ReadUvarint(readSeekerPlusByte{v1}) + // Read the frame's length. + frameLen, err := varint.ReadUvarint(reader) if err != nil { if err == io.EOF { break @@ -69,12 +60,12 @@ func GenerateIndex(v1 io.ReadSeeker) (index.Index, error) { // Null padding; treat zero-length frames as an EOF. // They don't contain a CID nor block, so they're not useful. // TODO: Amend the CARv1 spec to explicitly allow this. - if length == 0 { + if frameLen == 0 { break } - // Grab the CID. - n, c, err := cid.CidFromReader(v1) + // Read the CID. + cidLen, c, err := cid.CidFromReader(reader) if err != nil { return nil, err } @@ -82,7 +73,8 @@ func GenerateIndex(v1 io.ReadSeeker) (index.Index, error) { // Seek to the next frame by skipping the block. // The frame length includes the CID, so subtract it. - if frameOffset, err = v1.Seek(int64(length)-int64(n), io.SeekCurrent); err != nil { + remainingFrameLen := int64(frameLen) - int64(cidLen) + if frameOffset, err = reader.Seek(remainingFrameLen, io.SeekCurrent); err != nil { return nil, err } } @@ -134,7 +126,7 @@ func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { if err != nil { return nil, err } - // Seek to the begining, since reading the version changes the reader's offset. + // Seek to the beginning, since reading the version changes the reader's offset. if _, err := rs.Seek(0, io.SeekStart); err != nil { return nil, err } diff --git a/ipld/car/v2/internal/io/converter.go b/ipld/car/v2/internal/io/converter.go index 37973bbe33..4c723ec46e 100644 --- a/ipld/car/v2/internal/io/converter.go +++ b/ipld/car/v2/internal/io/converter.go @@ -1,19 +1,90 @@ package io -import "io" +import ( + "io" + "io/ioutil" +) + +var ( + _ io.ByteReader = (*readerPlusByte)(nil) + _ io.ByteReader = (*readSeekerPlusByte)(nil) + _ io.ByteReader = (*discardingReadSeekerPlusByte)(nil) + _ io.ReadSeeker = (*discardingReadSeekerPlusByte)(nil) +) + +type ( + readerPlusByte struct { + io.Reader + } + + readSeekerPlusByte struct { + io.ReadSeeker + } + + discardingReadSeekerPlusByte struct { + io.Reader + offset int64 + } + + ByteReadSeeker interface { + io.ReadSeeker + io.ByteReader + } +) func ToByteReader(r io.Reader) io.ByteReader { if br, ok := r.(io.ByteReader); ok { return br } - return readerPlusByte{r} + return &readerPlusByte{r} +} + +func ToByteReadSeeker(r io.Reader) ByteReadSeeker { + if brs, ok := r.(ByteReadSeeker); ok { + return brs + } + if rs, ok := r.(io.ReadSeeker); ok { + return &readSeekerPlusByte{rs} + } + return &discardingReadSeekerPlusByte{Reader: r} +} + +func (rb readerPlusByte) ReadByte() (byte, error) { + return readByte(rb) +} + +func (rsb readSeekerPlusByte) ReadByte() (byte, error) { + return readByte(rsb) } -type readerPlusByte struct { - io.Reader +func (drsb *discardingReadSeekerPlusByte) ReadByte() (byte, error) { + return readByte(drsb) +} + +func (drsb *discardingReadSeekerPlusByte) Read(p []byte) (read int, err error) { + read, err = drsb.Reader.Read(p) + drsb.offset += int64(read) + return +} + +func (drsb *discardingReadSeekerPlusByte) Seek(offset int64, whence int) (int64, error) { + switch whence { + case io.SeekStart: + n := offset - drsb.offset + if n < 0 { + panic("unsupported rewind via whence: io.SeekStart") + } + _, err := io.CopyN(ioutil.Discard, drsb, n) + return drsb.offset, err + case io.SeekCurrent: + _, err := io.CopyN(ioutil.Discard, drsb, offset) + return drsb.offset, err + default: + panic("unsupported whence: io.SeekEnd") + } } -func (r readerPlusByte) ReadByte() (byte, error) { +func readByte(r io.Reader) (byte, error) { var p [1]byte _, err := io.ReadFull(r, p[:]) return p[0], err From 30b51b4345e7cdcfe94056b0329588f36fe1bab2 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 15 Jul 2021 09:53:41 +0100 Subject: [PATCH 117/291] Unexport unused `Writer` API until it is re-implemented Unexport the CARv2 writer API until it is reimplemented using WriterAt or WriteSeeker to be more efficient. This API is not used anyway and we can postpone releasing it until SelectiveCar writer API is figured out. For now we unexport it to carry on with interface finalization and alpha release. This commit was moved from ipld/go-car@1a14cefa8e25824be305428cfc8bb51346c1e5bc --- ipld/car/v2/writer.go | 19 +++++++++---------- ipld/car/v2/writer_test.go | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 29b04f30a1..3675972283 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -21,8 +21,8 @@ var bulkPadding = make([]byte, bulkPaddingBytesSize) type ( // padding represents the number of padding bytes. padding uint64 - // Writer writes CAR v2 into a give io.Writer. - Writer struct { + // writer writes CAR v2 into a given io.Writer. + writer struct { NodeGetter format.NodeGetter CarV1Padding uint64 IndexPadding uint64 @@ -31,7 +31,6 @@ type ( roots []cid.Cid encodedCarV1 *bytes.Buffer } - WriteOption func(*Writer) ) // WriteTo writes this padding to the given writer as default value bytes. @@ -56,9 +55,9 @@ func (p padding) WriteTo(w io.Writer) (n int64, err error) { return } -// NewWriter instantiates a new CAR v2 writer. -func NewWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *Writer { - return &Writer{ +// newWriter instantiates a new CAR v2 writer. +func newWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *writer { + return &writer{ NodeGetter: ng, ctx: ctx, roots: roots, @@ -67,7 +66,7 @@ func NewWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *Writ } // WriteTo writes the given root CIDs according to CAR v2 specification. -func (w *Writer) WriteTo(writer io.Writer) (n int64, err error) { +func (w *writer) WriteTo(writer io.Writer) (n int64, err error) { _, err = writer.Write(Pragma) if err != nil { return @@ -113,14 +112,14 @@ func (w *Writer) WriteTo(writer io.Writer) (n int64, err error) { return } -func (w *Writer) writeHeader(writer io.Writer, carV1Len int) (int64, error) { +func (w *writer) writeHeader(writer io.Writer, carV1Len int) (int64, error) { header := NewHeader(uint64(carV1Len)). WithCarV1Padding(w.CarV1Padding). WithIndexPadding(w.IndexPadding) return header.WriteTo(writer) } -func (w *Writer) writeIndex(writer io.Writer, carV1 []byte) (int64, error) { +func (w *writer) writeIndex(writer io.Writer, carV1 []byte) (int64, error) { // TODO avoid recopying the bytes by refactoring index once it is integrated here. // Right now we copy the bytes since index takes a writer. // Consider refactoring index to make this process more efficient. @@ -185,7 +184,7 @@ func WrapV1(src io.ReadSeeker, dst io.Writer) error { return err } - // Similar to the Writer API, write all components of a CARv2 to the + // Similar to the writer API, write all components of a CARv2 to the // destination file: Pragma, Header, CARv1, Index. v2Header := NewHeader(uint64(v1Size)) if _, err := dst.Write(Pragma); err != nil { diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 2e48fc3cae..06d0ff42d3 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -60,7 +60,7 @@ func TestPadding_WriteTo(t *testing.T) { func TestNewWriter(t *testing.T) { dagService := dstest.Mock() wantRoots := generateRootCid(t, dagService) - writer := NewWriter(context.Background(), dagService, wantRoots) + writer := newWriter(context.Background(), dagService, wantRoots) assert.Equal(t, wantRoots, writer.roots) } From 9bac8b3ca6e7ca5b08a2b5716c0e22c88fbb56c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 15 Jul 2021 15:29:26 +0100 Subject: [PATCH 118/291] make all "Open" APIs use consistent names All the "New" APIs take IO interfaces, so they don't open any file by themselves. However, the APIs that take a path to disk and return a blockstore or a reader need closing, so the prefix "Open" helps clarify that. Plus, it makes names more consistent. This commit was moved from ipld/go-car@4827ee39c808dc1c5c5c0008362d9603b3760c9e --- ipld/car/v2/blockstore/doc.go | 2 +- ipld/car/v2/blockstore/readwrite.go | 6 +++--- ipld/car/v2/blockstore/readwrite_test.go | 18 +++++++++--------- ipld/car/v2/example_test.go | 2 +- ipld/car/v2/reader.go | 7 +++---- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index c41500d78d..7210d742b9 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -14,5 +14,5 @@ // panic if used. To continue reading the blocks users are encouraged to use ReadOnly blockstore // instantiated from the same file path using OpenReadOnly. // A user may resume reading/writing from files produced by an instance of ReadWrite blockstore. The -// resumption is attempted automatically, if the path passed to NewReadWrite exists. +// resumption is attempted automatically, if the path passed to OpenReadWrite exists. package blockstore diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index a0f3b46a07..c65f4deb08 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -68,7 +68,7 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re b.dedupCids = true } -// NewReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs and options. +// OpenReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs and options. // // ReadWrite.Finalize must be called once putting and reading blocks are no longer needed. // Upon calling ReadWrite.Finalize the CAR v2 header and index are written out onto the file and the @@ -85,7 +85,7 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re // returned successfully. // // Resumption only works on files that were created by a previous instance of a ReadWrite -// blockstore. This means a file created as a result of a successful call to NewReadWrite can be +// blockstore. This means a file created as a result of a successful call to OpenReadWrite can be // resumed from as long as write operations such as ReadWrite.Put, ReadWrite.PutMany returned // successfully. On resumption the roots argument and WithCarV1Padding option must match the // previous instantiation of ReadWrite blockstore that created the file. More explicitly, the file @@ -102,7 +102,7 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re // // Resuming from finalized files is allowed. However, resumption will regenerate the index // regardless by scanning every existing block in file. -func NewReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { +func OpenReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { // TODO: enable deduplication by default now that resumption is automatically attempted. f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o666) // TODO: Should the user be able to configure FileMode permissions? if err != nil { diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 22ac788c54..191865bf3e 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -39,7 +39,7 @@ func TestBlockstore(t *testing.T) { require.NoError(t, err) path := filepath.Join(t.TempDir(), "readwrite.car") - ingester, err := blockstore.NewReadWrite(path, r.Header.Roots) + ingester, err := blockstore.OpenReadWrite(path, r.Header.Roots) require.NoError(t, err) t.Cleanup(func() { ingester.Finalize() }) @@ -102,13 +102,13 @@ func TestBlockstore(t *testing.T) { func TestBlockstorePutSameHashes(t *testing.T) { tdir := t.TempDir() - wbs, err := blockstore.NewReadWrite( + wbs, err := blockstore.OpenReadWrite( filepath.Join(tdir, "readwrite.car"), nil, ) require.NoError(t, err) t.Cleanup(func() { wbs.Finalize() }) - wbsd, err := blockstore.NewReadWrite( + wbsd, err := blockstore.OpenReadWrite( filepath.Join(tdir, "readwrite-dedup.car"), nil, blockstore.WithCidDeduplication, ) @@ -202,7 +202,7 @@ func TestBlockstorePutSameHashes(t *testing.T) { } func TestBlockstoreConcurrentUse(t *testing.T) { - wbs, err := blockstore.NewReadWrite(filepath.Join(t.TempDir(), "readwrite.car"), nil) + wbs, err := blockstore.OpenReadWrite(filepath.Join(t.TempDir(), "readwrite.car"), nil) require.NoError(t, err) t.Cleanup(func() { wbs.Finalize() }) @@ -289,7 +289,7 @@ func TestBlockstoreResumption(t *testing.T) { path := filepath.Join(t.TempDir(), "readwrite-resume.car") // Create an incomplete CAR v2 file with no blocks put. - subject, err := blockstore.NewReadWrite(path, r.Header.Roots) + subject, err := blockstore.OpenReadWrite(path, r.Header.Roots) require.NoError(t, err) // For each block resume on the same file, putting blocks one at a time. @@ -321,7 +321,7 @@ func TestBlockstoreResumption(t *testing.T) { // We do this to avoid resource leak during testing. require.NoError(t, subject.Close()) } - subject, err = blockstore.NewReadWrite(path, r.Header.Roots) + subject, err = blockstore.OpenReadWrite(path, r.Header.Roots) require.NoError(t, err) } require.NoError(t, subject.Put(b)) @@ -353,7 +353,7 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, subject.Close()) // Finalize the blockstore to complete partially written CAR v2 file. - subject, err = blockstore.NewReadWrite(path, r.Header.Roots) + subject, err = blockstore.OpenReadWrite(path, r.Header.Roots) require.NoError(t, err) require.NoError(t, subject.Finalize()) @@ -400,10 +400,10 @@ func TestBlockstoreResumption(t *testing.T) { func TestBlockstoreResumptionIsSupportedOnFinalizedFile(t *testing.T) { path := filepath.Join(t.TempDir(), "readwrite-resume-finalized.car") // Create an incomplete CAR v2 file with no blocks put. - subject, err := blockstore.NewReadWrite(path, []cid.Cid{}) + subject, err := blockstore.OpenReadWrite(path, []cid.Cid{}) require.NoError(t, err) require.NoError(t, subject.Finalize()) - subject, err = blockstore.NewReadWrite(path, []cid.Cid{}) + subject, err = blockstore.OpenReadWrite(path, []cid.Cid{}) t.Cleanup(func() { subject.Close() }) require.NoError(t, err) } diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go index d590ab89f3..4e74466289 100644 --- a/ipld/car/v2/example_test.go +++ b/ipld/car/v2/example_test.go @@ -21,7 +21,7 @@ func ExampleWrapV1File() { } // Open our new CARv2 file and show some info about it. - cr, err := carv2.NewReaderMmap(dst) + cr, err := carv2.OpenReader(dst) if err != nil { panic(err) } diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 709048b5ce..ad231c0f1d 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -19,9 +19,8 @@ type Reader struct { carv2Closer io.Closer } -// NewReaderMmap is a wrapper for NewReader which opens the file at path with -// x/exp/mmap. -func NewReaderMmap(path string) (*Reader, error) { +// OpenReader is a wrapper for NewReader which opens the file at path. +func OpenReader(path string) (*Reader, error) { f, err := mmap.Open(path) if err != nil { return nil, err @@ -102,7 +101,7 @@ func (r *Reader) IndexReader() io.Reader { return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) } -// Close closes the underlying reader if it was opened by NewReaderMmap. +// Close closes the underlying reader if it was opened by OpenReader. func (r *Reader) Close() error { if r.carv2Closer != nil { return r.carv2Closer.Close() From ef8903fa3c08502a0335d7660c2ea2a185887006 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 15 Jul 2021 17:00:39 +0100 Subject: [PATCH 119/291] Improve test coverage for `ReadWrite` blockstore Add tests that asserts: - when padding options are set the payload is as expected - when finalized, blockstore calls panic - when resumed from mismatching padding error is as expected - when resumed from non-v2 file error is as expected Remove redundant TODOs in code This commit was moved from ipld/go-car@dbdb7428304d334d74495dc83b4fa674bed61050 --- ipld/car/v2/blockstore/readwrite.go | 13 ++- ipld/car/v2/blockstore/readwrite_test.go | 140 ++++++++++++++++++++++- 2 files changed, 148 insertions(+), 5 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index c65f4deb08..0829b2c7d9 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -184,11 +184,11 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { if err == nil && headerInFile.CarV1Offset != 0 { if headerInFile.CarV1Offset != b.header.CarV1Offset { // Assert that the padding on file matches the given WithCarV1Padding option. - gotPadding := headerInFile.CarV1Offset - carv2.PragmaSize - carv2.HeaderSize - wantPadding := b.header.CarV1Offset - carv2.PragmaSize - carv2.HeaderSize + wantPadding := headerInFile.CarV1Offset - carv2.PragmaSize - carv2.HeaderSize + gotPadding := b.header.CarV1Offset - carv2.PragmaSize - carv2.HeaderSize return fmt.Errorf( "cannot resume from file with mismatched CARv1 offset; "+ - "`WithCarV1Padding` option must match the padding on file."+ + "`WithCarV1Padding` option must match the padding on file. "+ "Expected padding value of %v but got %v", wantPadding, gotPadding, ) } else if headerInFile.CarV1Size != 0 { @@ -330,7 +330,6 @@ func (b *ReadWrite) Finalize() error { b.mu.Lock() defer b.mu.Unlock() // TODO check if add index option is set and don't write the index then set index offset to zero. - // TODO see if folks need to continue reading from a finalized blockstore, if so return ReadOnly blockstore here. b.header = b.header.WithCarV1Size(uint64(b.carV1Writer.Position())) defer b.Close() @@ -369,3 +368,9 @@ func (b *ReadWrite) GetSize(key cid.Cid) (int, error) { return b.ReadOnly.GetSize(key) } + +func (b *ReadWrite) HashOnRead(h bool) { + b.panicIfFinalized() + + b.ReadOnly.HashOnRead(h) +} diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 191865bf3e..9d254c0140 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -12,6 +12,8 @@ import ( "testing" "time" + dag "github.com/ipfs/go-merkledag" + carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" "github.com/stretchr/testify/assert" @@ -26,7 +28,11 @@ import ( "github.com/ipld/go-car/v2/internal/carv1" ) -var rng = rand.New(rand.NewSource(1413)) +var ( + rng = rand.New(rand.NewSource(1413)) + oneTestBlock = dag.NewRawNode([]byte("fish")).Block + anotherTestBlock = dag.NewRawNode([]byte("barreleye")).Block +) func TestBlockstore(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) @@ -407,3 +413,135 @@ func TestBlockstoreResumptionIsSupportedOnFinalizedFile(t *testing.T) { t.Cleanup(func() { subject.Close() }) require.NoError(t, err) } + +func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { + oneTestBlockCid := oneTestBlock.Cid() + anotherTestBlockCid := anotherTestBlock.Cid() + wantRoots := []cid.Cid{oneTestBlockCid, anotherTestBlockCid} + path := filepath.Join(t.TempDir(), "readwrite-finalized-panic.car") + + subject, err := blockstore.OpenReadWrite(path, wantRoots) + require.NoError(t, err) + t.Cleanup(func() { subject.Close() }) + + require.NoError(t, subject.Put(oneTestBlock)) + require.NoError(t, subject.Put(anotherTestBlock)) + + gotBlock, err := subject.Get(oneTestBlockCid) + require.NoError(t, err) + require.Equal(t, oneTestBlock, gotBlock) + + gotSize, err := subject.GetSize(oneTestBlockCid) + require.NoError(t, err) + require.Equal(t, len(oneTestBlock.RawData()), gotSize) + + gotRoots, err := subject.Roots() + require.NoError(t, err) + require.Equal(t, wantRoots, gotRoots) + + has, err := subject.Has(oneTestBlockCid) + require.NoError(t, err) + require.True(t, has) + + subject.HashOnRead(true) + // Delete should always panic regardless of finalize + require.Panics(t, func() { subject.DeleteBlock(oneTestBlockCid) }) + + require.NoError(t, subject.Finalize()) + require.Panics(t, func() { subject.Get(oneTestBlockCid) }) + require.Panics(t, func() { subject.GetSize(anotherTestBlockCid) }) + require.Panics(t, func() { subject.Has(anotherTestBlockCid) }) + require.Panics(t, func() { subject.HashOnRead(true) }) + require.Panics(t, func() { subject.Put(oneTestBlock) }) + require.Panics(t, func() { subject.PutMany([]blocks.Block{anotherTestBlock}) }) + require.Panics(t, func() { subject.AllKeysChan(context.Background()) }) + require.Panics(t, func() { subject.DeleteBlock(oneTestBlockCid) }) +} + +func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { + oneTestBlockCid := oneTestBlock.Cid() + anotherTestBlockCid := anotherTestBlock.Cid() + WantRoots := []cid.Cid{oneTestBlockCid, anotherTestBlockCid} + path := filepath.Join(t.TempDir(), "readwrite-with-padding.car") + + wantCarV1Padding := uint64(1413) + wantIndexPadding := uint64(1314) + subject, err := blockstore.OpenReadWrite( + path, + WantRoots, + blockstore.WithCarV1Padding(wantCarV1Padding), + blockstore.WithIndexPadding(wantIndexPadding)) + require.NoError(t, err) + t.Cleanup(func() { subject.Close() }) + require.NoError(t, subject.Put(oneTestBlock)) + require.NoError(t, subject.Put(anotherTestBlock)) + require.NoError(t, subject.Finalize()) + + // Assert CARv2 header contains right offsets. + gotCarV2, err := carv2.OpenReader(path) + t.Cleanup(func() { gotCarV2.Close() }) + require.NoError(t, err) + wantCarV1Offset := carv2.PragmaSize + carv2.HeaderSize + wantCarV1Padding + wantIndexOffset := wantCarV1Offset + gotCarV2.Header.CarV1Size + wantIndexPadding + require.Equal(t, wantCarV1Offset, gotCarV2.Header.CarV1Offset) + require.Equal(t, wantIndexOffset, gotCarV2.Header.IndexOffset) + require.NoError(t, gotCarV2.Close()) + + f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { f.Close() }) + + // Assert reading CARv1 directly at offset and size is as expected. + gotCarV1, err := carv1.NewCarReader(io.NewSectionReader(f, int64(wantCarV1Offset), int64(gotCarV2.Header.CarV1Size))) + require.NoError(t, err) + require.Equal(t, WantRoots, gotCarV1.Header.Roots) + gotOneBlock, err := gotCarV1.Next() + require.NoError(t, err) + require.Equal(t, oneTestBlock, gotOneBlock) + gotAnotherBlock, err := gotCarV1.Next() + require.NoError(t, err) + require.Equal(t, anotherTestBlock, gotAnotherBlock) + _, err = gotCarV1.Next() + require.Equal(t, io.EOF, err) + + // Assert reading index directly from file is parsable and has expected CIDs. + stat, err := f.Stat() + require.NoError(t, err) + indexSize := stat.Size() - int64(wantIndexOffset) + gotIdx, err := index.ReadFrom(io.NewSectionReader(f, int64(wantIndexOffset), indexSize)) + require.NoError(t, err) + _, err = gotIdx.Get(oneTestBlockCid) + require.NoError(t, err) + _, err = gotIdx.Get(anotherTestBlockCid) + require.NoError(t, err) +} + +func TestReadWriteResumptionFromNonV2FileIsError(t *testing.T) { + subject, err := blockstore.OpenReadWrite("../testdata/sample-rootless-v42.car", []cid.Cid{}) + require.EqualError(t, err, "cannot resume on CAR file with version 42") + require.Nil(t, subject) +} + +func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing.T) { + oneTestBlockCid := oneTestBlock.Cid() + WantRoots := []cid.Cid{oneTestBlockCid} + path := filepath.Join(t.TempDir(), "readwrite-resume-with-padding.car") + + subject, err := blockstore.OpenReadWrite( + path, + WantRoots, + blockstore.WithCarV1Padding(1413)) + require.NoError(t, err) + t.Cleanup(func() { subject.Close() }) + require.NoError(t, subject.Put(oneTestBlock)) + require.NoError(t, subject.Finalize()) + + resumingSubject, err := blockstore.OpenReadWrite( + path, + WantRoots, + blockstore.WithCarV1Padding(1314)) + require.EqualError(t, err, "cannot resume from file with mismatched CARv1 offset; "+ + "`WithCarV1Padding` option must match the padding on file. "+ + "Expected padding value of 1413 but got 1314") + require.Nil(t, resumingSubject) +} From 2a0eb4f6ab30219e5dfda50f3f1c78fb960021a6 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 15 Jul 2021 17:20:08 +0100 Subject: [PATCH 120/291] Use consistent import and bot CID versions in testing Use both CID v0 and v1 in testing `ReadWrite` blockstore. Use consistent import package name `merkledag` across tests. This commit was moved from ipld/go-car@25dc0003fa3ab4240f6e67ba5260ed5d269c67b6 --- ipld/car/car.go | 4 +-- ipld/car/car_test.go | 32 +++++++++---------- ipld/car/v2/blockstore/readwrite_test.go | 40 ++++++++++++------------ ipld/car/v2/internal/carv1/car.go | 4 +-- ipld/car/v2/internal/carv1/car_test.go | 18 +++++------ ipld/car/v2/writer_test.go | 16 +++++----- 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 267237f28c..51826706d5 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -10,7 +10,7 @@ import ( cid "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" format "github.com/ipfs/go-ipld-format" - dag "github.com/ipfs/go-merkledag" + "github.com/ipfs/go-merkledag" util "github.com/ipld/go-car/util" ) @@ -58,7 +58,7 @@ func WriteCarWithWalker(ctx context.Context, ds format.NodeGetter, roots []cid.C cw := &carWriter{ds: ds, w: w, walk: walk} seen := cid.NewSet() for _, r := range roots { - if err := dag.Walk(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { + if err := merkledag.Walk(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { return err } } diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 13f96887bf..137edd6299 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -8,9 +8,9 @@ import ( "strings" "testing" - cid "github.com/ipfs/go-cid" + "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" - dag "github.com/ipfs/go-merkledag" + "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" basicnode "github.com/ipld/go-ipld-prime/node/basic" "github.com/ipld/go-ipld-prime/traversal/selector" @@ -28,18 +28,18 @@ func assertAddNodes(t *testing.T, ds format.DAGService, nds ...format.Node) { func TestRoundtrip(t *testing.T) { dserv := dstest.Mock() - a := dag.NewRawNode([]byte("aaaa")) - b := dag.NewRawNode([]byte("bbbb")) - c := dag.NewRawNode([]byte("cccc")) + a := merkledag.NewRawNode([]byte("aaaa")) + b := merkledag.NewRawNode([]byte("bbbb")) + c := merkledag.NewRawNode([]byte("cccc")) - nd1 := &dag.ProtoNode{} + nd1 := &merkledag.ProtoNode{} nd1.AddNodeLink("cat", a) - nd2 := &dag.ProtoNode{} + nd2 := &merkledag.ProtoNode{} nd2.AddNodeLink("first", nd1) nd2.AddNodeLink("dog", b) - nd3 := &dag.ProtoNode{} + nd3 := &merkledag.ProtoNode{} nd3.AddNodeLink("second", nd2) nd3.AddNodeLink("bear", c) @@ -80,20 +80,20 @@ func TestRoundtrip(t *testing.T) { func TestRoundtripSelective(t *testing.T) { sourceBserv := dstest.Bserv() sourceBs := sourceBserv.Blockstore() - dserv := dag.NewDAGService(sourceBserv) - a := dag.NewRawNode([]byte("aaaa")) - b := dag.NewRawNode([]byte("bbbb")) - c := dag.NewRawNode([]byte("cccc")) + dserv := merkledag.NewDAGService(sourceBserv) + a := merkledag.NewRawNode([]byte("aaaa")) + b := merkledag.NewRawNode([]byte("bbbb")) + c := merkledag.NewRawNode([]byte("cccc")) - nd1 := &dag.ProtoNode{} + nd1 := &merkledag.ProtoNode{} nd1.AddNodeLink("cat", a) - nd2 := &dag.ProtoNode{} + nd2 := &merkledag.ProtoNode{} nd2.AddNodeLink("first", nd1) nd2.AddNodeLink("dog", b) nd2.AddNodeLink("repeat", nd1) - nd3 := &dag.ProtoNode{} + nd3 := &merkledag.ProtoNode{} nd3.AddNodeLink("second", nd2) nd3.AddNodeLink("bear", c) @@ -106,7 +106,7 @@ func TestRoundtripSelective(t *testing.T) { // this selector starts at n3, and traverses a link at index 1 (nd2, the second link, zero indexed) // it then recursively traverses all of its children // the only node skipped is 'c' -- link at index 0 immediately below nd3 - // the purpose is simply to show we are not writing the entire dag underneath + // the purpose is simply to show we are not writing the entire merkledag underneath // nd3 selector := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { efsb.Insert("Links", diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 9d254c0140..acc46a8d07 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - dag "github.com/ipfs/go-merkledag" + "github.com/ipfs/go-merkledag" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" @@ -29,9 +29,9 @@ import ( ) var ( - rng = rand.New(rand.NewSource(1413)) - oneTestBlock = dag.NewRawNode([]byte("fish")).Block - anotherTestBlock = dag.NewRawNode([]byte("barreleye")).Block + rng = rand.New(rand.NewSource(1413)) + oneTestBlockWithCidV1 = merkledag.NewRawNode([]byte("fish")).Block + anotherTestBlockWithCidV0 = blocks.NewBlock([]byte("barreleye")) ) func TestBlockstore(t *testing.T) { @@ -415,8 +415,8 @@ func TestBlockstoreResumptionIsSupportedOnFinalizedFile(t *testing.T) { } func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { - oneTestBlockCid := oneTestBlock.Cid() - anotherTestBlockCid := anotherTestBlock.Cid() + oneTestBlockCid := oneTestBlockWithCidV1.Cid() + anotherTestBlockCid := anotherTestBlockWithCidV0.Cid() wantRoots := []cid.Cid{oneTestBlockCid, anotherTestBlockCid} path := filepath.Join(t.TempDir(), "readwrite-finalized-panic.car") @@ -424,16 +424,16 @@ func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { subject.Close() }) - require.NoError(t, subject.Put(oneTestBlock)) - require.NoError(t, subject.Put(anotherTestBlock)) + require.NoError(t, subject.Put(oneTestBlockWithCidV1)) + require.NoError(t, subject.Put(anotherTestBlockWithCidV0)) gotBlock, err := subject.Get(oneTestBlockCid) require.NoError(t, err) - require.Equal(t, oneTestBlock, gotBlock) + require.Equal(t, oneTestBlockWithCidV1, gotBlock) gotSize, err := subject.GetSize(oneTestBlockCid) require.NoError(t, err) - require.Equal(t, len(oneTestBlock.RawData()), gotSize) + require.Equal(t, len(oneTestBlockWithCidV1.RawData()), gotSize) gotRoots, err := subject.Roots() require.NoError(t, err) @@ -452,15 +452,15 @@ func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { require.Panics(t, func() { subject.GetSize(anotherTestBlockCid) }) require.Panics(t, func() { subject.Has(anotherTestBlockCid) }) require.Panics(t, func() { subject.HashOnRead(true) }) - require.Panics(t, func() { subject.Put(oneTestBlock) }) - require.Panics(t, func() { subject.PutMany([]blocks.Block{anotherTestBlock}) }) + require.Panics(t, func() { subject.Put(oneTestBlockWithCidV1) }) + require.Panics(t, func() { subject.PutMany([]blocks.Block{anotherTestBlockWithCidV0}) }) require.Panics(t, func() { subject.AllKeysChan(context.Background()) }) require.Panics(t, func() { subject.DeleteBlock(oneTestBlockCid) }) } func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { - oneTestBlockCid := oneTestBlock.Cid() - anotherTestBlockCid := anotherTestBlock.Cid() + oneTestBlockCid := oneTestBlockWithCidV1.Cid() + anotherTestBlockCid := anotherTestBlockWithCidV0.Cid() WantRoots := []cid.Cid{oneTestBlockCid, anotherTestBlockCid} path := filepath.Join(t.TempDir(), "readwrite-with-padding.car") @@ -473,8 +473,8 @@ func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { blockstore.WithIndexPadding(wantIndexPadding)) require.NoError(t, err) t.Cleanup(func() { subject.Close() }) - require.NoError(t, subject.Put(oneTestBlock)) - require.NoError(t, subject.Put(anotherTestBlock)) + require.NoError(t, subject.Put(oneTestBlockWithCidV1)) + require.NoError(t, subject.Put(anotherTestBlockWithCidV0)) require.NoError(t, subject.Finalize()) // Assert CARv2 header contains right offsets. @@ -497,10 +497,10 @@ func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { require.Equal(t, WantRoots, gotCarV1.Header.Roots) gotOneBlock, err := gotCarV1.Next() require.NoError(t, err) - require.Equal(t, oneTestBlock, gotOneBlock) + require.Equal(t, oneTestBlockWithCidV1, gotOneBlock) gotAnotherBlock, err := gotCarV1.Next() require.NoError(t, err) - require.Equal(t, anotherTestBlock, gotAnotherBlock) + require.Equal(t, anotherTestBlockWithCidV0, gotAnotherBlock) _, err = gotCarV1.Next() require.Equal(t, io.EOF, err) @@ -523,7 +523,7 @@ func TestReadWriteResumptionFromNonV2FileIsError(t *testing.T) { } func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing.T) { - oneTestBlockCid := oneTestBlock.Cid() + oneTestBlockCid := oneTestBlockWithCidV1.Cid() WantRoots := []cid.Cid{oneTestBlockCid} path := filepath.Join(t.TempDir(), "readwrite-resume-with-padding.car") @@ -533,7 +533,7 @@ func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing. blockstore.WithCarV1Padding(1413)) require.NoError(t, err) t.Cleanup(func() { subject.Close() }) - require.NoError(t, subject.Put(oneTestBlock)) + require.NoError(t, subject.Put(oneTestBlockWithCidV1)) require.NoError(t, subject.Finalize()) resumingSubject, err := blockstore.OpenReadWrite( diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index ab5753bec8..48b4f3eebd 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -11,7 +11,7 @@ import ( cid "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" format "github.com/ipfs/go-ipld-format" - dag "github.com/ipfs/go-merkledag" + "github.com/ipfs/go-merkledag" ) func init() { @@ -49,7 +49,7 @@ func WriteCar(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.W cw := &carWriter{ds: ds, w: w} seen := cid.NewSet() for _, r := range roots { - if err := dag.Walk(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { + if err := merkledag.Walk(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { return err } } diff --git a/ipld/car/v2/internal/carv1/car_test.go b/ipld/car/v2/internal/carv1/car_test.go index e5dd680c8c..70ce7d8591 100644 --- a/ipld/car/v2/internal/carv1/car_test.go +++ b/ipld/car/v2/internal/carv1/car_test.go @@ -12,7 +12,7 @@ import ( cid "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" - dag "github.com/ipfs/go-merkledag" + "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" ) @@ -26,18 +26,18 @@ func assertAddNodes(t *testing.T, ds format.DAGService, nds ...format.Node) { func TestRoundtrip(t *testing.T) { dserv := dstest.Mock() - a := dag.NewRawNode([]byte("aaaa")) - b := dag.NewRawNode([]byte("bbbb")) - c := dag.NewRawNode([]byte("cccc")) + a := merkledag.NewRawNode([]byte("aaaa")) + b := merkledag.NewRawNode([]byte("bbbb")) + c := merkledag.NewRawNode([]byte("cccc")) - nd1 := &dag.ProtoNode{} + nd1 := &merkledag.ProtoNode{} nd1.AddNodeLink("cat", a) - nd2 := &dag.ProtoNode{} + nd2 := &merkledag.ProtoNode{} nd2.AddNodeLink("first", nd1) nd2.AddNodeLink("dog", b) - nd3 := &dag.ProtoNode{} + nd3 := &merkledag.ProtoNode{} nd3.AddNodeLink("second", nd2) nd3.AddNodeLink("bear", c) @@ -233,8 +233,8 @@ func TestBadHeaders(t *testing.T) { } func TestCarHeaderMatchess(t *testing.T) { - oneCid := dag.NewRawNode([]byte("fish")).Cid() - anotherCid := dag.NewRawNode([]byte("lobster")).Cid() + oneCid := merkledag.NewRawNode([]byte("fish")).Cid() + anotherCid := merkledag.NewRawNode([]byte("lobster")).Cid() tests := []struct { name string one CarHeader diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 06d0ff42d3..633fc46260 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -7,7 +7,7 @@ import ( "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" - dag "github.com/ipfs/go-merkledag" + "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" "github.com/stretchr/testify/assert" ) @@ -66,25 +66,25 @@ func TestNewWriter(t *testing.T) { func generateRootCid(t *testing.T, adder format.NodeAdder) []cid.Cid { // TODO convert this into a utility testing lib that takes an rng and generates a random DAG with some threshold for depth/breadth. - this := dag.NewRawNode([]byte("fish")) - that := dag.NewRawNode([]byte("lobster")) - other := dag.NewRawNode([]byte("🌊")) + this := merkledag.NewRawNode([]byte("fish")) + that := merkledag.NewRawNode([]byte("lobster")) + other := merkledag.NewRawNode([]byte("🌊")) - one := &dag.ProtoNode{} + one := &merkledag.ProtoNode{} assertAddNodeLink(t, one, this, "fishmonger") - another := &dag.ProtoNode{} + another := &merkledag.ProtoNode{} assertAddNodeLink(t, another, one, "barreleye") assertAddNodeLink(t, another, that, "🐡") - andAnother := &dag.ProtoNode{} + andAnother := &merkledag.ProtoNode{} assertAddNodeLink(t, andAnother, another, "🍤") assertAddNodes(t, adder, this, that, other, one, another, andAnother) return []cid.Cid{andAnother.Cid()} } -func assertAddNodeLink(t *testing.T, pn *dag.ProtoNode, fn format.Node, name string) { +func assertAddNodeLink(t *testing.T, pn *merkledag.ProtoNode, fn format.Node, name string) { assert.NoError(t, pn.AddNodeLink(name, fn)) } From 7f6a1c85a640e5e52c6819f3b718fa2fcc84898b Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 15 Jul 2021 22:02:22 +0100 Subject: [PATCH 121/291] Fix index not found error not being propagated Fix an issue where single width index not finding a given key returns `0` offset instead of `index.ErrNotFound`. Reflect the changes in blockstore to return appropriate IPFS blockstore error. Add tests that asserts error types both in index and blockstore packages. Remove redundant TODOs. Relates to: - https://github.com/ipld/go-car/pull/158 This commit was moved from ipld/go-car@7751d8b965a57d9be40990c140130b799bf2a01c --- ipld/car/v2/blockstore/readonly.go | 9 +++--- ipld/car/v2/blockstore/readonly_test.go | 13 +++++++++ ipld/car/v2/blockstore/readwrite_test.go | 14 +++++++++ ipld/car/v2/index/indexsorted.go | 12 ++++---- ipld/car/v2/index/indexsorted_test.go | 36 ++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 ipld/car/v2/index/indexsorted_test.go diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 658587046b..cf799fe7a6 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -165,14 +165,15 @@ func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { defer b.mu.RUnlock() offset, err := b.idx.Get(key) - // TODO Improve error handling; not all errors mean NotFound. if err != nil { - return nil, blockstore.ErrNotFound + if err == index.ErrNotFound { + err = blockstore.ErrNotFound + } + return nil, err } entry, data, err := b.readBlock(int64(offset)) if err != nil { - // TODO Improve error handling; not all errors mean NotFound. - return nil, blockstore.ErrNotFound + return nil, err } if !bytes.Equal(key.Hash(), entry.Hash()) { return nil, blockstore.ErrNotFound diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 4997481724..58a58d6a96 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -2,6 +2,8 @@ package blockstore import ( "context" + blockstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipfs/go-merkledag" "io" "os" "testing" @@ -16,6 +18,17 @@ import ( "github.com/stretchr/testify/require" ) +func TestReadOnlyGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) { + subject, err := OpenReadOnly("../testdata/sample-v1.car") + require.NoError(t, err) + nonExistingKey := merkledag.NewRawNode([]byte("lobstermuncher")).Block.Cid() + + // Assert blockstore API returns blockstore.ErrNotFound + gotBlock, err := subject.Get(nonExistingKey) + require.Equal(t, blockstore.ErrNotFound, err) + require.Nil(t, gotBlock) +} + func TestReadOnly(t *testing.T) { tests := []struct { name string diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index acc46a8d07..892b4e0521 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -21,6 +21,7 @@ import ( "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" + ipfsblockstore "github.com/ipfs/go-ipfs-blockstore" "github.com/ipld/go-car/v2/blockstore" blocks "github.com/ipfs/go-block-format" @@ -34,6 +35,19 @@ var ( anotherTestBlockWithCidV0 = blocks.NewBlock([]byte("barreleye")) ) +func TestReadWriteGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) { + path := filepath.Join(t.TempDir(), "readwrite-err-not-found.car") + subject, err := blockstore.OpenReadWrite(path, []cid.Cid{}) + t.Cleanup(func() { subject.Close() }) + require.NoError(t, err) + nonExistingKey := merkledag.NewRawNode([]byte("undadasea")).Block.Cid() + + // Assert blockstore API returns blockstore.ErrNotFound + gotBlock, err := subject.Get(nonExistingKey) + require.Equal(t, ipfsblockstore.ErrNotFound, err) + require.Nil(t, gotBlock) +} + func TestBlockstore(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index a8f401aeff..65446f6651 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -82,20 +82,20 @@ func (s *singleWidthIndex) Get(c cid.Cid) (uint64, error) { if err != nil { return 0, err } - return s.get(d.Digest), nil + return s.get(d.Digest) } -func (s *singleWidthIndex) get(d []byte) uint64 { +func (s *singleWidthIndex) get(d []byte) (uint64, error) { idx := sort.Search(int(s.len), func(i int) bool { return s.Less(i, d) }) if uint64(idx) == s.len { - return 0 + return 0, ErrNotFound } if !bytes.Equal(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) { - return 0 + return 0, ErrNotFound } - return binary.LittleEndian.Uint64(s.index[(idx+1)*int(s.width)-8 : (idx+1)*int(s.width)]) + return binary.LittleEndian.Uint64(s.index[(idx+1)*int(s.width)-8 : (idx+1)*int(s.width)]), nil } func (s *singleWidthIndex) Load(items []Record) error { @@ -121,7 +121,7 @@ func (m *multiWidthIndex) Get(c cid.Cid) (uint64, error) { return 0, err } if s, ok := (*m)[uint32(len(d.Digest)+8)]; ok { - return s.get(d.Digest), nil + return s.get(d.Digest) } return 0, ErrNotFound } diff --git a/ipld/car/v2/index/indexsorted_test.go b/ipld/car/v2/index/indexsorted_test.go new file mode 100644 index 0000000000..c491bedf5a --- /dev/null +++ b/ipld/car/v2/index/indexsorted_test.go @@ -0,0 +1,36 @@ +package index + +import ( + "github.com/ipfs/go-merkledag" + "github.com/multiformats/go-multicodec" + "github.com/stretchr/testify/require" + "testing" +) + +func TestSortedIndexCodec(t *testing.T) { + require.Equal(t, multicodec.CarIndexSorted, newSorted().Codec()) +} + +func TestSortedIndex_GetReturnsNotFoundWhenCidDoesNotExist(t *testing.T) { + nonExistingKey := merkledag.NewRawNode([]byte("lobstermuncher")).Block.Cid() + tests := []struct { + name string + subject Index + }{ + { + "SingleSorted", + newSingleSorted(), + }, + { + "Sorted", + newSorted(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotOffset, err := tt.subject.Get(nonExistingKey) + require.Equal(t, ErrNotFound, err) + require.Equal(t, uint64(0), gotOffset) + }) + } +} From bf399a86fa1333e1040d8fb92c44cd4c3e34277d Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 15 Jul 2021 22:21:35 +0100 Subject: [PATCH 122/291] Remove duplicate test CARv1 file Remove the duplicate test CARv1 file that existed both in `testdata` and `blockstore/testdata` in favour of the one in root. Reflect change in tests. Duplicity checked using `md5` digest: ```sh $ md5 testdata/sample-v1.car MD5 (testdata/sample-v1.car) = 14fcffc271e50bc56cfce744d462a9bd $ md5 blockstore/testdata/test.car MD5 (blockstore/testdata/test.car) = 14fcffc271e50bc56cfce744d462a9bd ``` This commit was moved from ipld/go-car@fd226df91b98c441bd98f703814d060317af519e --- ipld/car/v2/blockstore/readonly_test.go | 5 ----- ipld/car/v2/blockstore/readwrite_test.go | 6 +++--- ipld/car/v2/blockstore/testdata/test.car | Bin 479907 -> 0 bytes 3 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 ipld/car/v2/blockstore/testdata/test.car diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 58a58d6a96..97ed730912 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -37,11 +37,6 @@ func TestReadOnly(t *testing.T) { }{ { "OpenedWithCarV1", - "testdata/test.car", - newReaderFromV1File(t, "testdata/test.car"), - }, - { - "OpenedWithAnotherCarV1", "../testdata/sample-v1.car", newReaderFromV1File(t, "../testdata/sample-v1.car"), }, diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 892b4e0521..884edca754 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -52,7 +52,7 @@ func TestBlockstore(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - f, err := os.Open("testdata/test.car") + f, err := os.Open("../testdata/sample-v1.car") require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, f.Close()) }) r, err := carv1.NewCarReader(f) @@ -270,7 +270,7 @@ func (b bufferReaderAt) ReadAt(p []byte, off int64) (int, error) { } func TestBlockstoreNullPadding(t *testing.T) { - paddedV1, err := ioutil.ReadFile("testdata/test.car") + paddedV1, err := ioutil.ReadFile("../testdata/sample-v1.car") require.NoError(t, err) // A sample null-padded CARv1 file. @@ -301,7 +301,7 @@ func TestBlockstoreNullPadding(t *testing.T) { } func TestBlockstoreResumption(t *testing.T) { - v1f, err := os.Open("testdata/test.car") + v1f, err := os.Open("../testdata/sample-v1.car") require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, v1f.Close()) }) r, err := carv1.NewCarReader(v1f) diff --git a/ipld/car/v2/blockstore/testdata/test.car b/ipld/car/v2/blockstore/testdata/test.car deleted file mode 100644 index 47a61c8c2a7def9bafcc252d3a1a4d12529615f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 479907 zcmdSBRZtz;(yooW>%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� 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 Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ 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

hRd> zxIQPp`+1js7cr;A2G9Sz#Cb*_ZChlr4a28WU<}i_e zuBDVaJs))P(_~?NiyW>e0sKw|^8J9ysCp2xuN>~eXfYP}L34T0&Vl@^Y>%3Ak2Gc2 z=2WYe5D?fq2^Q{$PoR`I^N>b4&(CSKn(E<7H*fFteEu3mG(ELNVuKBMD)3?Hzi;RA z0KgT9_q}C4#xxE125~RlKd(fbZcWBo(;Ny0G{(BW#;AQLH}OsrS0;jtccn82Pi`8m zEVJKD9UY>B$9?dWF8`W1|0ti&FbF%@J+DSS*eufMZM#DeoKiQ_{s0Lx8W~@KF;K$S7sZKXveAYNqj>VEwyjh zygOO+_X9ij@pQ*bD0J)s$?U!zguw1HfBgabp*jpTv(UqwVa5Q9f*2)ETS{+U@ z(91|c9d%iy-5|-dTY~SupRGgCbbb;L)@nP3bQs(wvRJUogv;<93B(NK;WogNHfyx?;$PWbpeo}586beLoy^){uPPtQkWyUmd z$P0x9GS9s>RIS~?8t659XIsut5g!zI3esTO)QVSY0pzL?RS^tp7o2;#rMVYqy|@*e zfHvkiYLHiGQIL?JY%H@7m`u!Q--ErdL68Fk2>QifGy2$u5FWF68d%;$_o}+ro(L8;f zRQlq(ABEhnECtxP{tK?lY5d0~_5Gl(SFi}^g3m4pz$y^*@%E83Zv?g%A(46jxTayS zK7AnO*@ME}P-uVA)V0<3)Pf;5(p!REzqxxf=6nUn;5|-`IX@a^sSo17!$JpG^R2QK}`xx z^{s*JMhXeM>udPHaGON`n6^Yyph50n-(ocNRFGySSY%w`;&C;}3tWx2z4UA> zC(;!N_9`ZAJ!xG)-RtIwUsxGxlt~$sI}V{$Z)#vkHHs`M{0r0GG%N%OGIu`?lZj29 zf3`$-Ji{ss+T|~KnNQvVrueu&JuXcTi7nrF^<|ulh6{qI9=-z z=)o<%q>sL2=1SOT|H`s2yq;#oJF5EC=d>Q>Q=IhGv9{GttrrT`0mr7#fXOm136)>5IZ;*)F#M_@$iNDNk95?mDo+;AV^ku7jA=+UawDfSY|N3OeBf2{0HWD@7 zx3fO~OVTQ_JSBv7Y=$#McEod(Sq{+Fi!S84MOeTVJrKF0b4wp)B z{3ef03I_DC!c%0MoVLgdr%0HkhW~pQ97Kw^l7LqT-FCvR&uuOVBfzL zlh;5$s7o}Y={-9pt^R`XHlyxO?YkP- zjT(^9R%uOhZrSs2Y6nWx)5V{vKQ06{5`~= z>bfv67RUT}avLGuIriNh+!5YA@m_CKAL-fD+$SBP0(=YCO&Ato_1$P4JBRbg&$Wt%a9Uo};AOy= zkLW9zov-(9_7(RL5yCr@Y1^;RjYOw@$bupO8!DVS^ z3wjK42M;AL^jkYb9}ck;Yh*Oi{z_MDq;nqDnjZ2A)6ez$i5wlXs!uksGA*j5dvN$Z z%QuBH>z1KT%w#BTYMgZ;(YQnQ!^YmIp+{PQnBU@T(ZXB00sgGDt)l(UoMO?Okh;DE z(IK|4er36?Xv{E5oYjsDpHvxFF`bEkFwbs)n$ncKBi0=Cup0ljBJI z*_ckYK#r_*PAVxXgcu7wJ7?_BI1bZ|E?^Xl2XkH2Y4^E%f{eJp&K;@Jxq<{C3*yn+ zAcL$Y_0h-Gr#<=+yYsMKkIFDC$`#ut41*0mf|^sFi3G)HIhl-QgwV07U5RaS+jCVg zjT`dIvM`tI4>UUi|!zZX`8mL$1=8 z2uRgaaBac_7`k--bu!_0(IFe+vimP9f=QYZTpQbmQW@5VqYzT3>RW08A#2Q}K3sSd zpX}2bSrzfzHd;=1`pA(*O!yr|wFOPJ#J|6Wshfh){bv|ouzXYNxH4oaQkq~NU4heg zF54)FraK(TOA%@}3out?z@hb@itG<^zXRp_HU{Kc+6kGW-*=WAXW&P&@C z&LU_h0gv3VMDAtxVd=llHyF17-}pz_WkVc=;>EEvy%9F@cU>^if?}dV&D?(XI3MdBYt7nYA+ly-rrcsZoXiR zzUk9bD(*mPJ2$r~Q6#B-eIhlAd z&zG+{_)-(o(;5?ExiqpmDL_q};XxfytFoL2+!BU}bGH)g%LGGV_Fj<@stcD6DVCE|)zLgX zlIf~j0z^sQMndT!Vqz03irvU|_a6rruVO^&IuhM2dO72~RU06TNSfji#(294L4aX3 zh({ns1A^BV93lF59`!Qn1&v06ahLmmI&vh`Zt#FDGGbi0bQ@o7cEsrWJnLb!(+Cn4 zPLnluPN^I!h|o>fMer!+cWf;U5R)t)^TNKK6O$mC5rgpP%jWuh>y#F)O**V=js|-; zEluRNBBd#2K>Ae0U!gz=1ZW0Ww*ULWFkevc8MD3&xUhr-xTLjc(16VW@}^N5-Rss` zF)!=LGhk?)Nj!!QYH zhSf2gYdD`mH0rbfRj$ZPc{QZ}KbN?jVcT>nrlAoeregry6r&CD3fvsav2bUA?UbFu z8k+Hc+aWOwv@pcFXK2!)KgQB@A0fqS`W{|L- z#EjbD^c8EGE0@OTGTV87WUyAK)56#Mkn7VM_of#DN|7}nc}S5`F-x(yo_zD(4b(LG zguj9xkzQBoVq*xr*&2LmRgV%&Z3%y6GZtn1>z39*bhc!Li_LSUpr z4MmmU=D}U7G*0SAlv)}yF&C62@mS4{UlIRyurln@uWVWTB2w|N2y{2=vdA`dX{~e) zhPP8mnXf-4#hZH5Y$@=Rr(l|7#VJl3)@-AoRP6OA1y^P8n#BoqR4TJygu6GW$7_dM za0WC@34UP+0As1AA~d8I313LZP6DO_1iZ~I4jdFwoFDVUM~efowTJ`zXBkjz8XSn; zRda$rh4y=H{s_OtDB#gUb8d>{L4!y^zH{NSaTHX{4eq<3<|z_B(umKEsiu-C?KcW# ztp?$oCq81*ROKQ~-4j$jrZwvqzx;^FxV5(t7ez&P8 zcOcsisWJhBr6v1BaN8h8F>AJRXH@`&pMO~T?_(<+z}C+h2_JVl!?9=~;vf)^T9%sa z(EIQj3E6Ln#Y1t06Whk%U4;-)1R|rp$V7?ty33%P z2#;xYUJe$7^-HVA*S^-rPyi>-iX)2LJpeVQ$fxm}c>y>}1A z2&>OmCW&>9r1XZPb4jm}!Qxn6-#;b)B0e2W*y#km3JxQi(2XN^JK;jlI=Ee*IN1kn z=r3JcGxnN#*)2OYIfCk-ixQXZ1+`@C)R6+Pm7E|F?1YxtL*U4^ZlK$fX(noL5F$te zL}qvLQ)-2SXT!7GbLczyuSw0R$*PuWnqZz`sf$hEqOaF*kx%&cRJHikq`3CC-$aCv zW>GaLnI+X?aT!PV9C2_vaqHyrq);(R;42hx#vDUohsTh(d&8E@cA2t`FLr(}=Y-fB zSv+Yoi6YqOb`Y_UtsB}kOcfPm(x1I$WDpGtVgQE5R|xyg0da~~Uiv>GWIm+o9V8`5 zu+5J{3DhRE?o#i~<*DhF`yMIQ9JJX>UND+BfrZA8rfrwj9z0WqN2-aWj%zcyCbHsn zbm?%h)VRKMeNy9r)FZ%QA5Sa&DpPei+e0W{);d|5a3@tP%)CxxfvT4_R?r$TF3;E3 zBmq1BTJD?lhbE*p1QSWKdALjwjy+e(Ta7*1|`31W%-o{T?zKYtTs6(u_2?2o~sAGHpX6Q=EJyZ{{SG${N1M!s+ zJry{Pf0kmBL`t9a6eYm_SHCaBum-S|>bgVd**FmHL)M8dSQ-%5nXJj8r4TQn06!yt zzp=$v+1B6)y$G#y)33aUMItZ(YCI+<9hiW!%Dq;f{tJ9#!}fd#-!vwX_$-{@l!zCP z#m*ee`~^*A-~8(Rg>bg@Z?^s^^nWGW4G(+Ac3ekb%@|ehn-SSBhS-`J6g_Ufxp$H< zhreB}zebXrqizWajoOHS=i7vLKu;%e$d#E~n`u|~ny^jiohr%>F$^$EC(&6w7*|;Gv*sxa131!ZQkQk%_#oPde z3Cba563H-89FEX9uyq(P$%6k}3!AM01+n7IrdNUR6OU376sntGlM#!uGHP^`v|prl=vg>mE;;^U*AmUEud3oY}>kq6wT)#t2vs?BRyFeIsdSw zWo4jS)byoQGU9~wjXK@WDi~5IvoDAe=yx{w!3s0Q{|>KAfR%wVSLi!q(o4#UJlL^` z3uFxzHG^28Z*i0S^kn@8V5^l6{R3MpiPm3HJ9O7;ySXBUD3Ag{DHYt=;T=TgG%AZ) z>lL#R;DfEA_)tJn@b;E~c`J#C@KqM&eEf>AR^tuS0G8T7_BUPVHEesj{ zi^%;1sH!mNF0u+HoewK7b zvHk|cL=n(I^Pj}BieR(gBI3yTZSAO8)7%n)sZUXTc60WOIUY5JkJSnOfjdv8DIPNu zmPfHSl1L%6)VV*Rb#*zR^m#9y>BZsUu*_qsJQYKhU7I?avbEKo_wGgk={2Ko)bjxg z_5QvdB&&SzQ*Tq9S4p3>rJtlx1#<8KrP4tR9i4wyHUYb{5x;aCP_G+QB%Oi6X^OK? zVctD<+ybrsF8GCQ_bwwQnuXIH5I5bC*JZX$z7<1rQRN0+b&}#T%ddr4fA@01yb@s_ zu&)?gmp(T#%n+kN)MRKuT{NX8E7Q4JSBRJElB$IkcZ*sMqWt$X1WlRup<^2^4GZJG z{O5KurEncsW5+qbmoVg#6md6MU)hdjt%$H6E~%o1)n_jUh0hZ-pfV^cxB4ri8lM!u zE-y=uJukTWup{@;P59bT0h7i`>3_$?sT>CVOliCL#1c!uo;SoiQf%ny=zCS|duHK% zi@-U2D*_AUv?ztWE1cM-1%mOlF?FZW;J$AFZZK5_a8V+JD7YbNNk|ve*;-@vMTLwMDLX>Jz8>5dn93HN{xosZg~ge!1aXeZ#N#kAG~ z@qHL>w%vC89dgmr?hS;hW_K4N$d4%dlfC1g#4_`Xm-)|Jh5^x@fv`5~6CkM7mkWZx z*2t3k{=z1S z@AkbdtYSkXGxkix&Pe&-j?mY7;Fo@hk=9TIKb7A4`hbau^gtq%u=(@#!(8NoP=J@B z$Un9Wg@6D{R|TP=mE0pzn>1|P%h~+)opjxscs+3A`i`a7>cjv3_jw5o;H6Tit)r+4 zWt$Xr!-^J$J!kUBXJhQEsgSGw{K2)$^rmd|w~$X@Vp8GD=n4tLwyU0)*+KY;D~BNFuhvK-4y6kJEZ*P{T+CYPP&9(KVmz2(vA5y=C)DRiROS z5)FTE`wiih=J6d`%AH>$!$(FSAlhoP9KGB-iI|2rusI_nvMw1RN4+Izt5Ml)xh zh^mVu6$xin0&qFCUNVj!;1IPH)DL1nBU@Oa8ceroAt7vdV7V{cF4c3r6-GVsIUfnT zXt$O6u=HQ&5cGY3Ln0OEoAeE-2cX-Pl!i>HDq{8Q_ejIZ1_x@mTnW4|r?YuMq*1RF zgo`>R${wyXjZh%SV-g;ggr%o&s${R4uzF5JboXu0@VQ}g**#KxA1f6)!j1XKM?;Uf#`j)U#_h-lOb# zv9RUys2p;U=v}GI)U$1zxbNGY-uNT-aaky}h8~YX7=1UwUV$a^5bU<`U)1!13F_o& zA--C_HY=KP$5CAM?Vei21yh1w!YzVtx$xxb+Ns$2PQyD$*hA7Gr{Q_ROHzXg(FxF~ zznWpgnZF5_cPMd>Bv=L?Z{EBt#7(P=D5*>5ULy&=l~U)oNPYQF-VjU4mxO21&Iy&( zr_}2t26hHYtWWdFuu!utVCr2S&lJ!x^uKcQK|5ot>pSRR>em>AHlz5z0aci4^zf!)CgW%)|-r6RMrXI$wKMdJtO# zc5r<37sU-FS^ajh>PHm9_2m@=%4Xp4ZZ@wW``pt{4+l+%K$%+lB;S0Fv%#qTX6v6q z{})?{8!&nE!pc|ejFfp8hJ~uIiig)W;lzz%O_vMf-+MD(J+(erAe+^vRXF`U@NTg zNj;EP;ieu<@>>?vC@DPrBXLmWICkil>>{&*Jt#Tn^vn#T4X)Hi08kO3n8x$(g7Uev$@WpaWF1C@+2r6cIe&Ze1p>9{F& z@0^+_iyH$ND}1mrpgFP$a$Yjj+jG2+s|n?4>h}4R>syuP!}sAwIf*-1+ZoRTZ9fJ` zk^No3*bOl5-D;3Og9PgRP`gJw-(X|?bt>jPdmaZ&AJxHiE$JBGnto(rB z;vt8<6iRa+UeR2%)#EL}PKL99Y^v6gs>3AGTk z3M_(g*(qX=%}-;X2R#*61d#5i6s=!2H3w!Q{^aCy^CXGNEA+_{`e~4bpr4&_3$jFR z`86@(pi(=yeHsb=v;_QEOJ04J);|@z4e-2y^iP!g9FSTl(;%Y90mK?csz3EwD=Gv8 z@>Yuc0hoBD-{m%$ck3a#Z=NB~ zt4u1VAyrC$o%p6;rkIqdzaEO(?#PVCLIX>1^KEPvOgIHD#`7%w&DKAK{x7z2T#Hg` z0=&T5@Dr!PjfMt(N#q%&I^4WVarTSOuGGS}4@p(-?;FjPJxLKkmp?H8OB}0Xac71l zgo8_GNUVEp5N|YtwL&d`lalr_BsGAv-M#maR!{Mg_cb;Y4N{2%*z#3W2(coWz=T;O zZRKQKk7(BKp_)Zv{rw-df-^#?X8RVpsBBewm!ZN#VTGwS|z{R-lHxv6IE3x z^STA5-e5<)HK-UrcfV{=Mxo_YqG8dcrLZY1jYU^Gm4c7|)~&^{VtnAuLO(^R2Al11z%ox)ZKi$Wue*C}sxr&HrJIy7-*QwVCznW;Bsf;2%m*Kk;>8ymYtnXyYA>Al zWt8u$2nQI3V&uLqaNPIZN-A!41no2_!00f&#O*sMV=*yK-hjmRN(9y~(z3D^d!d9& zcsz_{QC6`qS)z4j?7^ewDi1r28)OCgV5I&fc-dGPeTMG%@BO+(w(qlM4Ce4RH98Ts z1yHkv!)n%Fn1A)a`{!sC)wlTL{b7re8BB0&m=%F7;UoyPDY44GA<`0gFwHO|yfBj3 zft2Nc-vaxksxW7!mH3cE>aG3Qtfp1)V{@6NRg4>Fc_M2rS=a-xH5KzQXSE&@*%-}n zpq&FSwvtg5#qs4Z=+z*0U z@*7>WOGPk1p9htdH8~1QTn;kii}05_2*-&%+Z}=4*ujqvvcLWk-}KV#%jYGub+dOs zfkI|Btv1bME?_Brb66%ZJgN53iLox0#_;@`t$zyrUu=~>h6#u%AE*>7Gh-LV>ka;J zCvIP4)Tb4*rplWba|_x>C7a19@5o`;wExnv>K=*QLh@tXJ|#@&<{-6zUJZBbQR?i6D!V-Kx{kr6N zaePdVDo*Bf*$%fB>9`fpB2%LV0_J|$M@(6*jk_vZt@vKA9~E&tWvkAysgXTi6WGg5 z82D2;ND@Vb!0iz^DX8hiIDo&~&e=B`jY%u}YN)r7(LoNgT{Od_>F04t0zFUz7Qt0k0g+C-+xQmW3Rc4S3 z=U0jZJ8o;`ju5X4@jfVUvA@~+r_le!Rw4|XMA(HuQF~sBz6elg6VG;aD>&?YqO(_w zus9goOsi9_qe1KiH&4a>FK+EQ9N=vCm|s?SkD~O15@4pJpfsmS+>Yy@;H~`p6m`BM58krS5?ic=c!`l;McZc=_i$f06dO@uCd$IDd zf)F3Rz?7%41F<0!iKqCi`Jj)Sm^B*}SN(Gm20BiYbYMJ@sCe7W@p^>|wm*8Xm_wkCNrcrp2{n zp#F0l{9aE{i4N(xR-WX{c@iz0`gcbfT`gYH7N0Nl;QJlpr&b$mj^0B+p^zd+GCF@> zk;WSUI=sehCKu_@&z}v`Oe!y3mxcI zx}dk3B=d1J9vI64QTsQ*n{p6m9fS|SP`lp{<~h`sgCm1=U^*m6O#DnqVRh&UrpN1K zBB)77{;>4l#})*Dtwj3qPF}WJgfpjDs9{*zpUX$Q_7~2!E^LT4xL+H{oENxgYCmvG8A8j1IXpB9OaINAL+Hta3)3`WF}j(yVLmhbFc+6YCD_^3%k(>vGVlNZggr zD?y-HJXC#y~amX z)89=mNmy$TLr-X^bR@!`vNAAD(uz&(u->EN>XUto!+m!7_#Z--BnS!FDY>>LvX0$> zposj$;eDj79=>PZsb_4~pj$HP=q+F^@YW;DVdI(NJB{NZ`djj#=^aF^NY<|*x7o-h zM9M|K+>DzqI>l%ot7S?ie&^G~ZNbNg%F~D0vMQK(N97d+6^>`_PcD_K22syPU!2s>ftWeNLrt zFcLO1WHz+%A~Rk9TN1(_FS`NV8KBJ8NyH}yNz)z_nTINx1mmTox}pCze(3KUw0NJ+~HaM^fRU7B08jJK(u36tmdL^Q!3BZqees4u($89G3n z|NpGvpF;l^TS<;3?JZ9`vg(e8y`~o529dt0q{M$&hRLo`2@9T$JPowFht?6<@!MdI zs-kMgnFj7k{CU&ft3y&{uTP{rWLziA1f<5efvF>l*X5-hRZl%lc6h%6CNDYU6lf8~ zNQ{h0sKn-$HGlK*t%$i!OggQZQ6(n+fHXaN#u(%H>4@Q29vQqHDIq;r`%rjPui*q%OF^p zeTq#pQ|M7XZw(ZA{i6ED5aXkFrZEJ+Ap)2;&hm9K8@szz3C~AQ3mvWr^}tYh|6T%Buz{yQ#gHb*Jm`=_itcF z0Cg_yW-^!khWp#7&>wZcEb&FYhNVa!ml?3}is+)m%3cd*xdBoxuYDMVemj-j(pR)#NTERo;8z7WkrsNEts(e=Qy5(K(ynxm-N#buM zHIt10zsJ8DH&7@{dZZ=PPoti4$TptTzMy%!o;+>8kBz6&{_? zObwvMfSAUZsRg2BCurA7?qSq^mUIi>;`aE#d6rmjpeJK3(+8PhY}q8kS<6FUSF-qc z&_Yo|D6ny~@V`$h|0(o;86@FM0wkc1aKjw~>NQfcCrfY=c5q47pM|JCV&+&yi7bBb z!X_)s`0|t*o9C7Z!w4wE>=S}&XTtk8HS5Cm+7ivMx2dvYU8kzR42x2JJhI{|GTB^7 z9hf`a&-z7LM=^g4l7eddYGl46uWCD2iz|=N6kZ?#5~vyqr$jM{;H-Dj{L&iu%Ux{S z*7i}NsASNSJ1`IP2-QGx>0KI;T}tDs0VKBPCqzxSU6wdZ-EW(9HVpY;48n&1VGGOS zurn3mb2L(oG|nit?lf&|7EMQkVOw-05_?7G%w`&VXhJUf-WnyNT@HGI z<5i5i_>YWlY5|BM(JjBpW`M)+LDT4tY}4yCR>(2ISyx3Xw|94gw%*#d+t>*iDntAp z*S@AGNdWhzv3k<)h!>R6zuM84=8%~H$#>~ENq!nycIpeYB-$Eo+)sF(Ff7+0`1Bxt z{?rqKNecu{~%#PLh3cNZOZm-vHb^PX%P*mvfq(sQ((ZZCkms?NH%yoxfWdvB{uxzm+UC814`DC-qo$Gp zhPY(q%ei6!QzFgW@;5W^M(0cJ28e4al?wmr_df=~LKNn>^Gu}g&muu)Sh}dNsn!u{ ze-^?vMMR*07V&Ye&w&^QiQyvSSKs2f7n&;QwuP zX^MoxEwwcxiOamOLt$O%v*h-Lo7|pgusI4LjnL(7O$Z410u}ay7>d-M1tLh_-K$Or z@gVlBTdSQw-+W#4v~aQoI1=z@Bt9(t*Zsmr^9DF1VjGryXS-x|c7k60ad84)-vKF8 zX>$+=v9VAdB5I@)2HoT*+6bS612k#M#I80FRwUb6vR}%I6#NSYQDje^f|Fwm&^peK z1xHt3aMhp6kym)vYO;1uGkY1>!#LjRXT@~XqLSQ|AavcokLk(kdU{n<=^ zml(Z-)A;3~rjhRU5by2CII^8`r$#H`8$GV5U=OmAfY4kQuJx*$P_ODn78{nwE& zVkzi&c~1G>r6RZPuOf6>U)6gxJ#x>B{c%YB!Ds3U2EK}Kf(d8mHKbHOU~n}ge?>|< z%f+@<;9z3t%J!$g0Kxl%EOSar;fzb}v0NZ&|W!Is)mj2QSKk zcO;J@Ec7sp=-Rg+F$D0SyDqAOL-8V$G*D7lXTb08fmR9=$Po@^F)-L}xLA(Qxv=1* z{6Fg6I;yH~{~A7YclV*C8v*GM>4t+K(%mW2-JpPgbax5TNJ@8yfPkcQNXK)ySA6;U z-Z6OZ^NeSV_jiVW0M0Y_+G~E+Tx)+md#_Cc!(E9C~bP15UHNkura# zYr8M)8}g?yRfSkvv@GR#OM}?zv7Eyjd|0sT?e(mLXnnOYZOkZw{>NO6IAd_d2dwY0 zp&3?drcGf#Z08LmSzD*#HmLwBAnb;X(yvl$;h(^>Olv@l&*Z(SzQrRN))#J}@>WDG zfHrL8LcrD2NOx!+*jRC)GF5YDUYS5i!`ln}?rS%W-uvWJl)=ES9W@&qMYf%B1LA~i z1aaR8w^Q!fQDW>9;Cc+!PyDX6xz%=%?x(yHKk*>D{&>-7^JU(6NaaLssqF*6{uD$< zpqQ1SyAM$VrfLk1K6f@tsU|P}7~ae;6$^n^vL$LHq2%9X>AT9PsZ3lkM$6Z~e)}rL zGJv*`_G2XFG%mJciFcwQkn_$%GUK@goZhk}qaSwIh`X-cgoWU=?KpN{XZ$j85cU0t zMnL?pM+(c)OP?={Q}&wE&@I>a67%h|Vm?QrKU*x+TlfhNvmt+XFV@2h+=<&^c5_6! zy#2R?+EF~}mBwU9NyBrMR%v*~8_%>Syg ztzI3I=b_KR?>?-tSa<;apXVXUU=Mi*ktoI`NG=*6CWI72?z{IvNl>a_7w2OPYh$aM z+ZDSq&miTC{QCC=ArcF*bc3Sd02zK3rNDEmet5Cn%KzH zJA2hSBO^26`8hF7G^jkwvmXZEsWW|j-~c{@>x$DMS(Vy6RBa-dFw2J^D-63Tk^ zNq<3Ph9GJ&;r@2Wb2KD_*TfZFihP{4QWye}GMP9`RC?4#7`XhRnFHvOVxOe=G4_TA ztkRfkRLl>hTzGitZL%O7faI5vBYAyPfVY;A0;!xxb~y*SMr~5wM&NhBMnt=L<4F^@ z4Ley^EML^WH#7HSLr%_>wc2t zRJwwGSh5m+HEL(e&Bm${g$DimU9er$K349Y%yb<^xEQ+fq0f~lrX71^bbl$K{;>E& zTt^T64+q&-wrZgSJW*Yi3z(%uw|XXvz#`ok+*(@aMaW7((Bvs?YCb`{@Tifd0>O5Nfc4_+^j)bBru!MKjmv@ae10 zI(DAmyze5z;1kltkPS(4rhwz#iL`#d*=6GRPRS^h0?H7gOgilwO3%eXQ$I*;x`CAj zu^_M7jk_$J0e!o9cpDAfj678Mt9kR$`kA1x+Mhh*V_kpmAl_PRiCFya0^vjkgqLIX z-nFMdzC?w)3@p&;Wjv?ifapKf(>>_J6#r77O%{ap?HO+PUVIA_h0VSxU|j1>Y<_;< z%U<|%#T!HkiS!J?$)R(;jSK{hNLJF}-0uyZ6ocSAW8U$e3jXZodljdb8^)yam7S?_ zO_T_x@~It0JUyuMK_R2$14xqH$6ChmRl?A3=8A*BZ-;XSQIbE+d~I5I;8t{#ndNzy z{OWn%>GP4%schA4@hrS|cwQ*w+R0zhUbbN$(EhS=M*%|nY}WDLoM`Ny%O8hssb;}C zy`xkt-|x`hNOCu`i8Y z3;~3qc#s0u^hlBh>?B=~v1Qr~|6st3e{=MuHXe?+ zCeV(1j#PI0n%!rXkfXf1c}Bk9#P94O^ncvg!a4(wtzfyG%vlYqB*bJ-iM0H+(1<)J zCgdiW&uLC6h|a^&e93g=09ZGRp(Ktee}6IE9z259-Kwmc4#}^Cx8dnXSOcxzVsnxR zk#;YXvDo{RmirFT%ef| z(NoS#bfR528P9V6l}&BO{Gblq*>zlRB2o~AMG^LrYWO0Y`gh`MnO|PS))c3JE)yb~ zJUEx`u3~*xxA_iU9me6_0FfKvnXZkT^nX2T5pk`}F`{?SSS|4};rKO@+CuKBoP#E6 z@&G4+5U)M5h65eC>W`hXeQ`ubXlc3`_&8-7Sy-`)oNTq%*Cp%7SWycs#}xT*k&d~7 zLyx0ES@k2T0T9Y|3B+aqlw_uC%VG`~p)v+~kD z3Is)Y8#!zU`b|t3V-~Bksa7&(Bdnh3%$VhaUr-K0q+cvB`lyOmU9g^2tMz?@JG_dwtJ8VP+QBKa=AR)rHQ2KOzhfpxXDRdzT=C<$Dc@4vj$O$6qj4@P= z@hLwpTo6v=)L=&Eax+7p3Co}Ns&~JPRWPe^P{E3O(n*s&wOUhy&diyN=W#bXzj#d) z=jFqnr8o7YSTFZRYzhviqd{7TF1G#0`|p?=kOcVu+RI+{GiArI@qUvtj2is`^vhu9 z2u*j=oCb=hD@2QixqLM6do%tqKZ~nobnp_h`q^SDO=rs|Opz0@Xvu5L^(JJQCrIgW zBY{2SSsB~|4@uBBGpd;`zmljGP$Cq>8|D)#*YyFVP`rsbV?Q6Q}PqgU>3h*FlFBtfzmufgf2Glyw)XKVMoPxcu&z5>O zM1qXgEeWL=eDY00%Cjn93an>xMnYgZGpTboawkNj>R8j%+iW}RZp{_4gj) z9qC1TEy#zAE80YrdRju<8??Y?ZI^)#KIJ=J{EKFF`{;_Hr;W z5#vt9ddNN*W86UzQJQ;A)UJOVHmCA4C?8`L7F{m*6R&KA(60mJfeS|acPo4!sCn3= zUK5x=>L<^a;S4@eC;~OotzR<6m(xyf!J{wr&Ia_oeXcvNYxW}ch5Res*pUOh;MR|6 zku;|6jZ z&V_tVMAr#gkbB$6lvegFFNO8>%f$wJ_FwWd(Pvq!IY{YR-0xNARpVW@w!}3*b&)4g ztlpmBXX@T2*U+#|D$TY^8&ML=g^nAHErkiV~dx(VA5jC_Uk|2inf+^f4d{W$I6ed&S+ieN+iTqNLXc zK7{xGRQK7h*JYbm&SoPC+J{u?!EExOTvD^8?wv_8Mkkz+XV@S`-^*iEv#1Fp2>P;O z_d(a_%df8zNcIkc(WUK8mM455-H3s)p~F|wIo;eC^Dp5XpxbzQlPb1-?eWM?Fg=!4 zw>0$L_?`B&zF6&tf0rIg0RoUqno3wS?piinRS_v%>9CU}MPj$XhngPgFO>v^=*GU^ zbkAqt#MO|)RWVZ^NVxM5ce7z-A?w{Q1FxXNWnD7QNBBH-pJ{NNpF%p}tLmM}{eCWD zT$j$sG_M^|?(<;r`KeIjQO$PVxcX$r!Y~TeaXbskKOH3cD|ZMUFWC=Cv*ltM#E)8J z)hpVc6q--dRJ!Orc8p}e4uTp7`@j~kVRqp@Vz(?)s;4gHoMsK;iO?R^Q_FkQUFmT3 zR+m297wvzZgQS8TBo8iAHy9i0#dJ4G8e|={>rzNKH(bp~RM#orO7FBPKpyMH>TC4N-X*Xq4}xxm_0=g>%ub5I`zPe$$9YIuJ3ULv?KnhQlkIad$2cuH^mi3I zaN%HqB)qP@h|Y5=3{QnW;P~!=B(CZxpxD6!w9f(0sdTu}<2OBK?-JQ3>khThWt>Pp z^-OMP79*p)tMR8UMLaL34gU@wN8!Kp{3ou}CLLSzsvqD`C)bsI>McXVa6_qOtp0WP zDkMT^;r@P1>uE-8QBVo^dGlq@>!&#QDLxd)zTP>iheR+cJuQA+w(d=g*{O~OgIAZ& z!w>-Ss&O*GlcGGoFllT&r8d%X(N?ucC-Bdj*@X_47=~Un{$xZPL4fz~tVS@1&&;uN zSk_YRiUwJz8!;%r>DVFHqAJ=PFf8E%%yT~XrKz1KMr!~H{KMe4NWC;a4tv-(4n`JG z_z6sX5_v)wDa#nv=Fy^iMpUQbw~#jGd)4iOmv9@b1g=#)Z9hu!r`;3u3FZxp!kGja z(Kz=F0Uh{C5mnz$lRvMltozxDDG+u34G@<@gZ_8UTz>){d0|Pl{38P#4uo(X}D8R*f zywv~2lSVJob*}Soi{(DFpKS6TKvgx>dlM>^Sbr5oDR?#JMz;<{=Qn2uLKxZLz7j`= z{QLkKVS|dWD8U_2>tp(1Ks|4lWl$GDvrAbl{_4#>u(ekhJg{2;|93xxIwV+j;Nyf5 z^-W+S397#aM)T9I_w41B<^E`@f~mAzPC`~Tt8y{kqI*rY=N1*+Ra^JozJd+)10k}L z9!BgW>R7&aFSSN*#+o8Zb1DW%Pyc4IZVIh*1NzH&4N2gZCxIUf-W9{{!T%MK? zB`52g61UTSF${|DP$JfT>eD4u<@muWMAK|KZO2cHyJoZ9a_xK>LJq-Ap`}V zR8|6_1lZ7egjuxUas$l_YcEEWTv_n|vPuam&0$^($^!dN4}lSrALlUKb$X05s&HT?otFC^SS!s`)_5&H~s2(FVNHlnq}nB zyxmwEki59?ULzv^y^WCli2Hm9+6$SYP>{3Xt3l>L3OfRVa9RJtCJQGZb&RkW;Ikq0 zgFzsyl4C@G@BiAs&3=EPQ0fQ;EBNA7tFZN_Sx;r0hMTt!p#R0({AL`)W8g{5N94a5 zE9P!nl&JA!|Ai<#?oBms%g0+XCUD4CJPZCDBuVkix>nI~=@j@3@+ua(F00WAp^SfU z0sE~X#I3NTR6?t~!XyOMUSMoD!VlW^ZZi^ezRz8CC7^?GHC|GdS4@jn$^-K zv=GdllysX^aFEBRRCot1?uM2)_&CLHBfd?OYLG}cD3_wbg1BE#VkntM4(vM9RCOOA zQC5~9YMk8ua_xqB(#df#%t?-f8lO491>91>_cq6x# zOGy`IU1l4uoEm#jh^etrG=x8V&~Ir`q>>#!srKkokEj7*Kd4}D=?^+g}nHg1*im7Z%Y`$&N6xs+{B(nZv%GqQC+4?}9U%OqpS zK&=FSV*dNjX??bhJkz?E-%cY~aO9(kqrJFR-*Yx^FuyVVbdw<$-*RYGlyKNDDUV8A z*IX>O#RM&641FTSST!$E>4WQ7cHtl~cj$g-H}DDXtNf}tF_D}P@2!Mm+K7MIxgO-O4eMajpHbgZf;l3+2JeF z9S)ztwpEF3B%izw6r$L_GKsi0t&_|(e0i{tg z4zcO|n$TaE<@070m@BwmT8$$!ao;@_28M;;;VoaCbiO;-|86t7p8O+{NQ1IW%#QhM zUX=mTK?`{I(SPnlmPH6$IR=hoyd)`&QaQOs`|#$R*H4q2&!dtHHJy;ILJSZj1NFcc z$`p`|CN%~)ZrjSB8DmhE%42mQZj=t@GOvq=ak6**XD{#Zz!s9J29f+Ki~WY3=xVg5 z=S$T^Cq-Q9rUT7+t4&N4Tt1hV{e~6$F(4y0S4{_|)=zT-u zBe^$G>)$V60&)-`}t`Xf2siug0rT7KH9xia4Sg8O9D&dCw}5_b7vtr_3c>G3FAh}X;-fAcw@>jNG zh&^MXSaLoYr|s02gDgspm{w7trHh#iq+OCiS@owi?&EBQhc+#|qVqiLVuz6vP5D3t z%k?rG%3^>my?YE?UEf3Z6* zArdTkBkH^xHkdcqzTW!~h^jgE!LP=CH;B^k07=QFOM;El-V-u`=aV%L#h1^<-&oNG z!xMYMRdhx8ZBAV~NM%5=MT^V_rKQ+?)XEnfVE?ungPe5x*0Brc=+$j;1hN zXe=Y%Gr8F6%oZ*3JaGJ%RV|u}>Pkg7Z4U4Bg@vI=fG`I0%hn+q$@IJ~RqwZ^$xCa` zxiQ3jt6qV0OV6)@qoC8h1O)47(699Nwxw186yoR;CBZ2t!u0KC zTd^%tVM~h>cR#ZO2pG%F&Q&mnZc`T39TS_L@oNO?-~}7b9YSveGFu&sd2e4Ee%y*^ zxU&$itD1I*pPP?l4c?xP-+aCYo^=G2hjh2EbuXZf-R)bX%^xeWZCT{uvRt| zaDb~u&ThF6e*%kv`HF8@BbWzISpMeIcT|(c!FFP9(II|p-?UqWIs&!NaTsr|EAaDN zElJsm(-Dt!-!B6y$C6ZPdr;f8X+CBgnucIeBT=#cv5?1l{@y~0bSuJUmARzW%~OCx zo7OfR3~u&n_{(fa;t-Kvx_(sG@@Hbt$*yjiSWaD@QSRyDG}@$cT#p1K3f(GHyQJLM z!Bcob{W#D~DATF#juy4EA}PZ%=|uC9l(oR}`e=rDXCb9V0ooS{v`G~wN)ods%1&b& zLA*J$s{k`WbUD5%qtw&x`9WWI%!;(oFXJD=?2y{ zxYg5gPhp+235eo}rMdRk*p(SLcD*$vE}G#fffWT^bg)D(2>Q_a4?-g8mOa|F*4{(g zuq`2X&sbbQL6~>6iZMI^o*fKRo;}wQyztn(x zk2_na%GFt{_87yHb53OM!fmmjniWKBTHfB6B?bkyIf z59C9zEngpJWC>*hpH6;UXO;R|yG$;J%zmTL(eDHK?5bOwueby?z~{<9jlGs`HK|c+ z1^SEgPrEduLX(gVZNk0jFz?0?;`G-B(qq8wos;yoJ-%v2w^+1ja$doP{(7U-@yauM zeNl1nJ9oodsfC1Z_&*=&7i2SEIfT`4SL6W%I8o6czRRn$KXa9}KpdTE?m6{KsFJ3b zSnGwA6nVXhkmbz;zvc$sE9%xMrf+#twKcOfoYYy$S#W#WG}pChMOE<&pOUr4md3%I zg;bO&C?>O}Z}dJFw_#D^_^AEVQ}?KxM&#qhyBd%)!z=Iuw09N~_U)wu*(83Z2NNEC z{VQM55>J^>(?X#E(2}Mup)WTlI2HMiItBB9Wt41!*C|eSFr7HCa602v65rg^#r_A-|KdVM zx_su%o+%$gT?*~U7oA-}q_u=Yi9@FIYGZjOu?JOO`$OAe;qlFxXcaly^t-UE}pczp08^f0O-+*uz4U&vs70UxJqMJAiM7&21{>ILmk_7L@( z#=LbTQ{ks(wd%!3M+zoIjJkLvG9l@uMg7*HYY{^P5JZgR5IL_$=obc|k&sK2&xNQY zcPLHf{LGG4Uw4gV%SzOU^t+*DBsqk$qs=w}3L*c!ebZw-A0Y|=_<(@U_QpV4dm9^L z)_;Hg{@qW}+Qt#62edcRv-~#@L~I5$aWZmn06LnQIRL@`EX=G8!3aHTLmMk^Z(+a# zk^b-K`Ph^o2kBsDVy)-sWN!ri+;#ms6-a$c8v_gA!~Xz;BmTRu9&4p9hzz|20CAs1 zg247h7ykDz(BMBpcmtrLGW6JdeDwj){?qgd00=bzqOqAJ3mc1py^ZyQ?vN-SY6A{# zYq=Bae;p?f;91$qjts`JPhNkWj99+X+5!;%7mopW7ich%;vEsvzlp5Otc~m+Fg5kN zE^WsVF7Y$jjuhdfnXeLLe?3&$z$%^6HB$TXwhKZA0HOOTbu*9_9RT>lgZMwEk^{gx z6CXa6(oWd+{@r~B;AaLOId43f386_y)00s?cXr=*>rx>vLAzK;7%8!9@M~3^5a<6% zX|qQ-pR0LSmy5kjx(%m9Wp*|W&6yI!wck0b+npwK*^4+m{7-#p^ncs||0#Oz;#}!2 zCXs)~xt_g+(Zl%B>f3~Kg*&QfcM{!;=TySY09!hgmr2{29>$e>43(qH|@E z?N7F5wkD;=knJmpuew`#Jlq3e2q-dYP@UBUO8=A2rhm2WZ%%Kn?Hh+A3^@I(+0QcJ zo+_*=!{1)vpL*ak`TLT5NWu7r&iMbdt^dcsX#ee!kC~S@;a_(3z`1H5{>|~2ZH1;k za3h3dCEvml=)5(36C7+2S|w8nq*B!UW%e(ZS%Zrx45iK- zmRJODH&Txdc$aSC2T0J0wut}$k^qQ*`TH>dQ2x8&kF~`Q8vlu(2%O)8Ho0i*{gU$s zM3VRf*yyV4rva?=)**2LJh3gB7$1CtiXT8}DPZ={Z2Jjek}oCtHep()JzIF5y-%%+ z!3IG)S{yyRd-A)DRk9w-M>M?aaCP{zBK1%qzPZw>y0bKyc5>Hu#6I3rzgzs+;r&F^ zz|jMu1^io?A#!x}Z-(`=>NXUwW(Q?D_A5g|qy?c~6XC68R5rUhJ%FNWuqJX8aBK6R znK`EUGS+R=|Ga7!-$kIU6l1&8kb1aH@NPX*&G(L3KQ(3~MilXo!0$iUzOAonu>UNz ztpAE@9`%5APjyZnvb>Tdy&}FlUJd)Ok?GZy6!_+g?FRqn2{!NM-|Mi)fPNyR-u}KIpn)>ooR-gk@DW>%Zd2F|r?x_D zE3fLy&blI$QzWHYHV{xQrXb($*Ds8VqVLNqhfWjae)vwcX3nS+QU}c5kd|m*5(l8d zH|ro3TK^yrc6>7KGVEtD+sTaRWWf+Cu6GerufREgxvw!kUQDcVqRiV?n{AdkjWHYS z=>gN0@wPL=b**Gz5^j2hUGf%uJiA{KbI7>?&0M{6!BJtyzE^Iu9PvTnPe8zCDKQsf z;;qYD_tk4h5^wk9^kU=YPSN^eQC}3j)r-f_{c_>3y$$GBJU8xrjOZ%w-1vyE%mkJeRzV@n!lg_hZ5Kmo8ef z&QKg#DZ6SV!B;_63Z+^ypotRF%4tngzN!t26R0ndL!SP1V7aW1Veb-t^1el^op3zm6?x-U=}1^%KYp zD2;5v4^WPKRaWxPb?(2|(L5EF0|8~B$67G=<3i9usqEqy>dA%KmG9c$|Db6xp)aif z?z4kQ{6cGp-y|CuqM`-}Y{U`F0Ri=mTlHvs_2U+wk#X%< zd(UB79;K(D$C2aHrB%sD?8!Wqxh=vt%s!xfUy zK|mVv8EuTj3wi6P5%)F)76aQxYN~p3$|IhE-ZFD`dc5CqKV}$z>Ea?@jf5g;wfu^A z)c(y%5DXo`86a@x{~8R>dL0QDl0?pTv8v}m5lOAJV zuvu)&MPCDPmo_6}ko^-~=s-mGIX8P_I5G}5ENXBkV=MN^9~Rngm?~O)7g#`}{^{u^ zVdE#9ztodf{@&mX*dlaPBB#(ShKm;33E4&taTY2&pPxLb`8lNc#lUlOyX9_R!igMt zmlmT%zAI!)_v1-*(-3y4gUmBx`ayA16J}jP3ckj_3g1T>iRv{6Scgi>m;Peh;k~ji zpQAY^EZ%{$mnH&}K=pTA0Fx`BHYv}G-gQrJu$6=udZO$$8^rDYd~50k2?EY**Wy)% zOvn-E`AcQWdYuLSve0tfqGSD5@sl%{c?{*Z+>e?5U%Jd0?PY1i7us#+Ew{PpX^g)N z!csL$ii~L|hLip#QlEA&_hY!fXlL^qZ&VXl)rHNk0o^ z?tVF*aU2?+_!R@o2Jfz~GPy1g6mh_vJZ}=~-Rf6LGF0w&m zD4Sq2+WoO0i1&Q}9bWY{A<*k;)+++o$qQt+p3`p89BR<8#VbeUTwAKxa+? zqUB@*lL)>@=++^#B^Kn=RPq=<7}ZdU-LYvNe9~35UUNnJiwOi=CD+dleu2y(xIa(S z2tYkf3RuEo(!UIc_`zR6SqO`GzbJWZq4U?GM9Vb8Fi+aL&ux&-$9Jfr)UhOBwAnqC zi_fRmMD;`V%7f*DQ(MSK)w&xskAt^ zZatcArW6ATg4#I-*`k~Gi(5#%-pQT=1L24Hs80V(9Y@2XDFBPEF-J%vH38o_8Z(Ek z{nK%nFXU>TvG)P@SVR8`xOlv}6b#DoybkTq96c=&F2`J){Hj>~p$;brvsZ*4J??2A z!~I3O*(5kNha(&)Zv84w`j|$#Yw{@Y67j<1Ej9(51}i_@pE!ODcpt|K#hZ@vDPQE$ z7b7!{pMYN}79kATnvN$a)&t|}0zU%)PeBDf3~@Tu!D)GKqIuXqYh1#qa}*QdbPa)? zI2%8kh6hJa<~P1Rfa<=0$xGDBgXaLWZo;nTiq<>66FsCQ@?0$3ARq}EsUfzt{8K3b z;k9LG2XBYpWLu^F-(491F<(`)mO4e7d#Uvuf zR~MC>AmBxE)cZLCIIs8dX*bV)#80O&byBVAw8ZCyIiFH~V0#88kzP73T3HPZbekzc zeU>GzxhQ&^lJz3XjdZ2#@DeHw7X*~5+zG3?+3`o#wWxq#sB7459PHl7A)+yF9kDdU z9D4fOwjK+%zjP^t$sb~|8x6Z?#FO{(XM({ zhJ4^kO;x-uFS5l7>UJya8&?z7P6GuU2Ga{HA^nm2G2p%2p~(ds-&WcyM$4Whbn{fB zEbLpTJDxz9Nv=(Lw3h-TK){>28;0H>$kOQ}^5edKK(Joiy!q_()wElv;9-qTKR&pV zWjx=HB3LLL6vOSfWAfrZEh+X)p4SBLD^^LYiz1K(2LjR-wv2d9AR`w*xq>V8=F&kyzU$)pjdqqBFb@Ac(EXS7tb`xkNWzo?1jOm+ ziw@K=x;`iips8<`dbL4859FrEW0eqVwQNmtV*-=ROKuYv5m#8f(4emCEHV?>C;uW6 ztc=i-THwdCkY#2A0y-7FM5LYNP_*`~NB@Qnf2z76h>Ghj;oN91sQX?_i|)7Fj~T{a zy7-E%@BNy5{pO5fAyvXWJ)D4SNfOdVLtl)GI2={d$mm|~$8dkqS^z(Au8~4=%;!m+ zRK){XuGPXo;LD=M3@0JN1E)!}KXN|?yqDY9w`f;~8S@~mejrFhm05hsAd4vDId$3$ zw0J~R*}NtQNP3INnKqp-Sl?N4LPB*_=xIJyz#M>#yc&9kv@PQb~6;{{mVs(vah}#>z`hNfJ*C_?~*-(TJ(Ka z<~1xJ!e?=|)Xxp}L6;o?s&Cq8)D$Mv# zY8}9UfM@N3#>>`?J$qf8>4)crW+Oej+dCnxQGdNQioSM5gyx57*H-ykdm$D~We({nN-G zU^Hcz5yJY{|%gQ{H21gfJn8Vl5e4>lPAEO{Apzh=@PzAd8Qscx{V|ystN@2vr z$IV-6GoHm#jU+WO;qZZgJbfA@(ju>ya%on$y~iivKhU8uQn#@pqTKo^elfSB2a`DK zWOO*4nl~O$EAm7|GGYCskM@nIJ-x28g_SW@P6J;YemYoaCw6cYZ&w46%4=P{Ot^l^ z!b*YiJTa)>USY(A2TT(48W%!s$Q{VG-to%(sewYGn0`dhOo_gla~Lr_N~jiU*DQpBi{II68QXl4@5Z*$u(d^D_xYVEc`T*kCj#U9VEMq&;33Q z2wp`~|3yikVhFJ^R;kFpBsqvYafczyY&PRT=doqXuAU;?lAGd1)B6F* zzNsjg7}CYI{3iv+%m-4>j)Z81+wuuOKrsZK=J(p(>EmWMeWk_tI$Hyc(ix7{ zs3fkXqE&ppcO)>+@&uaoTK8hRWF&P~N4~{@M!ZxOphQaEgg7TD!7+gE$lWiLUv@Qa z3E{r0JJ1)v>OLl=$z zNofdb33xui8CzR_6QUv~&Tzs#PZTlt!I>uXEk!3%5{X##hX2 zjg0IaSvfg5iNMuM_`3=w>Hn)5_>a=y@67*WWx@0ZrGvZj-@iHTep35)&i1h);6r=u zzP>d-!V7Ga5P91Z8FKxruKF~6+qT@1AvFj|kf1q-0BR%hcW(7Ds4s}X8}P48{@uC$ z>w^yc`v?Dj6-D2$LFMk8_I@FOJ#C(*2au;t>3>x#U+uMuWZv zCh9z5fP4#Yj|INr{Wt$x$?%Q;^-U*8da0jLxbb+OB-Btc059Kc9KTsO#5cRAO%Ga$ za$yFI8g-j6{wNEb#*!~<4)~M-b!aNzp)nrqAA!>lRrIrwc->uwc%a3S1>fktNby*S z^dHrg|Kk3M{{jjM?aa5q6P-mJ5S+3AC9=Op+pjX;-Y=bP)GQ#T!-)o0SA@Z36{dg3 z#bd;N0DzycxA*-xdW`&T9R0U){%?Hc_c&r_Xa6&f?(18R6>I;YfL-gsQ;Q+2uvvH) z%fJXD#)dH}y|s|p9Pi+85)P-O4-Xa+{{{51PWsRFt;Y`VAL?5s7{A1C;*X(5Z}%}t z+hv7WT*q|ky}#N(uNytr8abMM(EKEm(`ZBBE?rtms~$8~Ha3~4oZkT%G?evZOBdql z{KJ>F5^y9b4$7eQE}mUlCGEA1xCk;KC?WdvoqmX-B6fIp@u$A^*x~<5C4Dxrrme&A z8`@el&-<0Q9adZKBTE{PSBKlE0`>CJz~Vt`FFzTF3Dk8ACK`0_HF}=WJDQ8qm7gQg zyTYu?E4wzne*kR`n0ada!6Q+(Kmq|f7WS$Yfb|07Frm2QXNOGr8sOo*9|efV3R$H> z@_xz*zE*B3%+9FtUKjkY4`LGa_eK@m z)J^EAGqyHLQOk9X_57apG2CCYTFvDt*d0i^(RDc=kEO)}leF)C7)*QZvA+WGb4yGku3|;Q zl|13@V)G}OPHvLmuaK`xI#RBe$xw-GLKEpCu{jML&Ae!O+nl|I-5iPk^3>= zz1(;OzZ74?=HeTw@+ssaRNpw&AH?`os#-E$ETCr|vFU-INnC94K!&4Uq|G>pB#sGm zyDokHx+|O1e))_R|10eNol>t$pN$nqNWhjiMSXV_D`kBnc$B}_xAdI`!Ar%qDpv&r zR6HFS_^!2cCVJ)&s(B`oj(mz)z_HPf&x1=>U-F@l4otF2w!7PON*2rwv6v=oyz)~n zncF+!OwZ7^jG9z`o)28`n8((V86X#cr#t2(aD`eVTi-GF_p!vTncyP${N70o8%&~l zEX79MC3=klVerer7ac7Lpts6MGzN1!_11@~XP*oNM3GW-uJA@QbQ82dQHZx6F~bz=ihQInt7=!8b+y zUhc4Xu%v}I^LplG*{TT3G?z|s1x*3=v zdfM}mztU?<6+DQPVk*z4sIjscLZ%%p$U#6+HS5XJwE1_Rg^8(>hn8UtCR9q~Q)mR* z=)|ZJT}C*;o%|$}@QR0eK0ZHRng~RLgBdhavg?FmEEP}sxd*2bhXMqAE=&jA{U+#g zKrAp^2bC0u;DcNZol$iEzH>Yg^wIWRC31-1mM?j*S@LI7Fr5-24+JGq0AcYPh1h1& z8`PyXLvUdg*Ym1F!@gS{J7~M5DDnG%0Q%*pFSe(;JT-^8h0-c_Rg)ZptlgTFBZkd}7pza)%BIA5#;6)x~teuza?zL)zk++Vav zd~^}CbP>}NFx4gp86~hp+u9|}#FP5->LRE&)&_2WCRM|S?4rAYboXL#$+&pNjSqpd@1%v4_0Haqh>x2@iKPLf8JRx(99vtS{)7+1Q%9! za3nlAP4`@g+R!HBh+it)=yu9@TZDzP|O? zLg%kV$>Q7GR0&+n)Oh!k9)|Ax4#QJTk@IuR&4~;}nJF3Ti1Rd3LJrF(d-nnNSVR8`xSzM9)8cwX zg;?f${9bdJIJ<4l4vjF%R8MrgHcn&{!tQAw!~I2@AoZdo815`zXO)pT=HdsNbmd!Z zlOVrYf0Nm5`0K{!f8zKt;C&pMTZS#^%|Y0tL%8}=MX7J1*!3Iw$ubas0dRKK6Tb#m z)HlTnF@JSA$6UL1{rriCn7UJzZ0`jQ$jzdKC#It&S00eK%@l35j|`M+A(HuCu3|$je8e&B6bO9RIs|9o@Xc%>VrC= ziECg)$!a17z;b84!yLImUxd^f=<7hv43ar#M$I^NVTfa>g#%?&blnxn3PxdDqQv$R zV=BEr!lhsgagOB#yr&@f6mKA90c6!9eBTmS?_E6 z3D4bd$VJ{8-kFx`unl>p|7DyhvLohixgRr(zjVQt+3TrGR6hM8IHSLIvQ`2EKWmB+ z-A3>^J;KDn4?6i??#FO{(NgXUq$Md?$O&WRZBSoWHF#*zSlS&8pbNm0Y`&cQl<-IH z$AI^8a~v*pt@3k(0W3y0V0eYPG3FzQ7hm8Sx@n{*CT`8rf`HbCXXo1_tleLxOHYz? z)7^R!s$w0=IIl7Qyy+vE-zmVI%na;}`R(2PGO-yfsW4)Klb@%bzxUUc z-!|i@knvI%sx%krk;MEgPXhv$X<$sl39`p97mo$asx7?4J{$INIVRFPtI0py$P-b(k7hCYL-<pR8-jA!1`|Wb-@--otdnYcqFa<<&fXlsR4elET@4ra!1#kICT=L1{S2gxU zyPI#+6Gf_ZiAmgzDLGA33$$djGV(xno}h2j-%jUru4OqSH=in%D`wEb*+Vhd)?8 zV8AQxI62*tH!#n?ESp2HW{;>Ma&jU)YB_fD@?A&K?`a>y{Y7i4uu`0TL{k2(uvNb*#imKw7Lcf25LU#Y zh1HSEsUP_#;vNIuM;rzJ@d$R`Sp+~YTq)}-))l*5LQJnU!Uu&QWWHYm@4(AoD~2B& z+tWuLspyn5m?p{Q5=WK}7^8OB@;j$kBMaAe!`e;3>3FTlih=EPWlUV7aW)Rl7-Ra~ zN@HY^CNXxo4>S1I$++{hv6mdw$fGwCn<PyQXsFOgGn5u>hwb=GTwe~waF5In*Ywr{|guOB05B@z~Pp>YZL63=(Q#Eeh!R~ zNo7(TA|j`Ua^DY9hu&j{)typsD7VN9{x+D$!s0Jo4n9C?CG$nsdMCNBGnTAr$s9u$ zgt>B*bfh{Iuze#sx|jPg++VaNZ-`O6!;_l$HZ+u(k!0vEN`B~D2tQ>e{zSGl1?>W}TlK3kgqL{%9+`Yr+J%K3I+W%i+l z@vfXn+__>Ho)jla67#>lsw$L`Dyq|*(a#Kuh&47I-4nBI)wqAwJd+*a#V(j3q{Mvb`9Sx+6jlqFKt*V1sOVtB5>oyR1Ex5Mh{?2G)>2|!;J;sj?eZ{RTd=RGat1;`&iu`~Ah z3Pe&mCdj`cB5)8}K+*Wav;`EjKTKOdvHioe1r#TTwG~uR5y7nB8IC#h&VHe(Wa5Zo zo>;dIoJ1>`V8dhq^3i|}9fwORx0TwQmt*D%TUGb^3n-j^n6`kT?uTg$C^&y;wxEL% zXx@SjMxdxTY{QD+z(MBlaVH(SlwZ8@_m^~JR^ae2vnKI%Kl%b=@ns^SS;29abSBf0 zJN$PbXlJqu z(B)aoQ3ykx$&tU%gf@@i_i4OTy4I=BU<6xFGtN~^7(?ev|jyT$#J;8MH~<2!Tp=sx*7M_3c`<}Rj`5!Jr!A4gKN_pVG- zaWw1}-0NJRknmyJ0*V|TrY)cV^I_To3f98f%DLi9N3Ff=K+ntgFe7C6$^7HdBZjo< zC>SiAGx5v|m9#2D6BsWU2cK&hat9SzlJ4~vP)zqQZ2^UT57QPz_H-6bb?GBX`<<`!|bvO5voG0QP37BT(&l35%?<_F}-VDyw z-0NJRXy;+t0t$*ArY)e@>7m*Jh(p5K^6zq~%Ifr_InrEQI>=cq=7G!DiivhofsEP_ z{*%(F>Bb^-X8dHddvCg#qZ>)k(f0ZaC|r4%wt%9VhiMBa_<3lypo0-8egqwik2KE8 z=J+o@q}4aZR1v*bm#$G6Ytg>BeDbIHX)&G4`RoK6cn0=sS$byJ5C z%Igy?z2$Ob0g;t&OB-8Nj&@teA3IE2Xo$!+28b6kHgQv_PnM5&;ZCYc%-Q$vh$nq~ zb{t>f^kLdMH=+FI65_MW4ddi8b)*?FQ?BuOexL58tGVr+?Ih-ShiMCK>PnVLL(&aT zeDo_J+KR5lTDnDoIPx1gsp+$C>xB7MSdoEisD94vL6O8$EgS0^ zyc=27-}&pG+KTngk3BBbbX(nd*^J15z@(wAK6{9>r-}BA!F)2Y$9MhkpW4d0J3{=- zHV?h7@Jy9al#=%4q-d!jzObm5om)=Sex5jo_6?zf5hy|n9gMT?Si$*$-X;v#F|KWw zwd>{S=8hVdW~!#@;@je@yPbkS5Z_u8hwoRvRNG>iKwFK$$Gj&flB;%zpO$JV9e?R#o zl*g}|=}R{l;xS(ndl;{U3Zrc%n(DriN317*sc8KAe7^?%fCy4yrv9BB%e~I6<~{q) z&x_H7SA6C@G5SLdDS|pbA3lmyy|JUt)o^6wupi-{+L{cMP1oIOpA(TbMku9YL)$|i$Uhul~0TXUN^`WTrPyPn!>*2A;Y*xA)ySZJo_DW2c!ulHs|&abPN?aA{PPIw9nX$pjZ zNZuy4E>*t{k#Lf;#)o~ce`<^F{mFzZi@vC*We;AowN;(Sio`y;i8KA0$Y2>4mBAJF zFm0)u1&AY#vy;)R`6d`i1izofK#UO}-EqD{?Hk4ak{q37>P332;GsfyfOEE|?1W4Y3T*+d0U@PSY zyUfo?JInITks#o9>fJHqt#A-oN2NG%b)FD!Gcs8oMr(tDZDHEDd#L1_ilC<>_O_yx z#Bs9<awc4>MH$u;KK8Z zo8(uC(1h@hnJSDmNWQXMKX*k41=i)6D?-eyD;;fVZ5}^3aVyX= z#qK!LVcH6%2#>zQaYURHUZcjlJ7?sA(1f z_K?PVrPP_OtItqsl3Y<#ipF1!#~nRPTY78yrnY`sx}^U3FV8QNe+YT?Zjo{&0;Tno z#IbIlG!U$<+yEhd;n5dGY4L&=6zR?m+1+q$yqTj&UX4k(6MMDUZi?Swjd5B)o!od#)w58!jNXQghz^mUPE=u=G!DnFP~0$%9Qm zlUrdHjIkB6lunJQOY#=(0)A1BoG+`to4)_4tp`tK@1qDm$Fri5 zr;%Zu8}>oRVN`;eAp-s`YhGaTy&S>yRbJvXTSOYsro8cCql75brD;P)g*i7ikf89! zyS>gmgL&+>5>H~muizw5OB^c&tQr!FI$k}^y^JLoTE5lLpGby@87*UPxk*w^jk zzGpP}NhGc;qE?IZM!FR>Yf<#3ZIGQa|8A1`r?#$A(JCK5%e7)m&~9B@vz1(D$8Ucn zj!+A?_0rK^d^_61d@oVDIvJM!j08gl31$H<6x#Tyu=H_{M>EnD+&gSksNb#p|IvFv z2O|*NLkA;JiCox^aM0=v;*g^Ju%R)aO1g(>3#j1kVcG(!%X^r%fNI>r+DeZkLVQbR zM5A=}f-8>uET`3*ZKq7b=1d%8@8jF#8>S%<4N9G7eZ5>|yW6hQ5cKTz7f>nO!?Xod zG50WS0TtFgG+WTY2t=r%gAu5rEo{TW>3o-tj4d);P#-AlR2dU4!}zFLRK;}deyt__ zRC2vX6RrqznG1baQwXWNHA$$$UgrW8zCBD^K=pAC(-u$}-NUp6RFW3fmN%0dLatdx zlhmBgu)&b=;&q1Hktg?61aGIXnvJm=t_lU@I@yz=TME^@Zv=Q_!M}n9`yeb)!61!q%9r z7Ny5oGsfs_*_IsGbKc;c5}wgs=K_^;Jxp6bRbLO&7Ep26!?XodXcg90qE!97aDUCX za+Rdv=-=J83Kr7y&h`s=;z4Sq%$&vx#U9SnW z0ez+T@M10w%AVXxqF(PnQ0ZRh0##o5>g{Uv$5k5cRkC;S@${@s8Z=++5#$QdYHC=>Yg5^Eub2ru(pCuxt^-pqO=oh z{$PBci8!8;#%9W?y}P_DVLqs;b!e6j&6ol|#UU)Zu)D|Uox9dve*u*mJxp6b6-f`% z7Es~RL$d{QCD@mn)4m?J{POee?iog)8`L~tc3nyIg1A)dZP2GfE*RBr9hqq{adS#&n3?GOC%{^j>TJXiDR3bt^z}D`2v?i zj=6!1zMh{0((S5F<@{M3`j0d8$Cv*?{^s_jfTsZ@Oa!<4YX}bds~H|n-uo+P4>g z{%VF_gVwkNx~T5E)4u8?Z&77qAj8cZDF*${i$}U7=pVMwhV^M4#%U z>TlmjeH`){)I45eU4=u&hSY@A9gXwE^-XG=9IICDk84~48Tdhs%ZK=s;inzNPTxoo z@0e{Dw-UVG|K6p+#_eiH?V29a%TSo+*X)Q}yQB`GGnKC&^=)nx{)9cLjg3v}be?wh z?9j9sY}o{4f3<1J!DsRMeePzAlU=wjQ>A(i76yb7KDIo`(h>IGYFq-D-&0O%;^TEr z)sn!07L7+s0X-ywD59N}m<~A3MBH;_8D$PKFs;3DB4&{}DJ+o(-V@E9k1VytI1Let z+Nmpr3LO(I)Po#GRL;Ce<|=!qb4*?Q`8$=by0c3HlaAy}7=9Q&xkLtU0b6-0;^ap@ zxAl_x5lSsBL{JAUo%#KTdK>!B6;*lNdy^h)LQnDoHFVEO{>zVBqLFNZz+%`N<|fZx zs;e=$t(To$*EC<%6X#FW(u1l6lG~%UJN4Mg$+6C&<~D*3$?C~%GS-~x2rkj6Ctk(5 z_@9jbr@j!7AhbY0Sly1Q9Qr8VlqWv@n|ck`_`67n8DoQd{M5BCX*9#&)Ml;Yoj^X@rY<>%$2MW-0Czy*_IF`ORn`9-RH55D7w8~ujJ;m-J@APlZmrq zW3n{EH|y0+1}VP?kVd&%4+5yO?eU97FcwX*0q>dm)tWV`8A=+?>ynNo7C7}XzLCl8 zqPS0>?go2xusm(A|9Uw8!dOJqRE{sJk6J zv5wzhdFS?Y=S+Cx`PL|$Zlc<4bi!=@yK6^gb#@@tNP$v6As_iod!wq}s*&xmzoKb` z!Km&jW$!D9ZYbwT_n<%C=Xqf(jvG?P>k&1M!vZlND%67_PT7V4kw-KNbc%w+B;;M zOz+7Z3Lci-c23<1_>?dRDgO_Y3<~+k6}@LCaV*bLw&)JjT>Ge0t2p~8b&4_`8*-WY zJL;BqJAUPsm=}$vBfy8JTx|3|*^SRhK&V3 z$i??#rUDiY0V%wi%+aYIY#rEH14xb?Yet6=UB5qiTm)5-Bvo{Uq+;+?NkXL3WQWc1 z(tFM>lp#X>7oi;vsG)m0T>M;D3_QhzXAQh&h8QDG(d}2DR~p(foVOp!DR=}U!9&#o z$?Z`)sM_FPpO~*d`uYPNzMaWH5fAbzy;FDL>T-XP>~X@)Z~YiZ5ZaH6>0F5Uj0bGv zc6_+q#V@MY&c0zAOr`I=*KjG%q^9~h~v{^{+ITj&UY9pLIxI=v~bNWRK7T7v~?D?kS8?z9Ee+4lHF zmg5eyolwQ?q4q8tN$}up@10TK^Ld{26PsJnsW_NcA*K^2M;wD*L!>mXZ1 zJVQ`N(I=4>OcilFO}G+o^r6!?cLNDR-QBg3^IkzYKChZpuqjK0{;}CriH{wIC7Nlr zg&!j8^AsT{1(0?7*;C8X#*>IF?IZ7*6W0uB=(Rd>D+riOE7CP*33`a_kE_!FrPD%` z@HK%DFh|2FFi^^`(lnw3Dln);<6W*BpgN`SHvNO4kU9-eo-9O383|h+FWmmEF#g| z4}vjOZ)cy(@h{Hbqky^_NN$f>QYNW2B4Hw;^zAYv*TI{idJG6YNo%ts&5Es5B6!0m zzquPo5bAC?K~$|RbA2;mf`|RJd|gtXh-Go{$QS~&IRkYV92{jJ>j{8tQOHNWdbyUN zThVWYZp{>5`a|pls{a%sQSjmh;&k6;Y8$>F1RDU-Kp{%veXeoLtDHV3(=dOdSxu<0 zChRKGxfqlIReGY2=vs7;JKTWsJ0VJxN&>+#@flr57Vn(SVSQW&ny77Sc3fz9ckdp8T$5YxTq&!ZUeR`ERqnxf@6j>TX5o_iXZz9;p_&RUCuryGo0ZKN7d}Pd=vOrJMI%jFjQ0bB(S~~DGcDVa7 zt&~Rzy_q$yFhP_6WsyRZR4Szisn@R_Nj;aPk8$KO9LBw?H?6XBD|7F3^Qlho%$KC{3MDruKo9{a@6%;BiU<@^*;C`P z5XU`4m+mCS(w$?vCU>{)n={g|&-sP)*3k)tqgx`4!q5i+I$Sxzf(pmnY)(3y`+6&_ zgc-Eih$N22ii4=Xv1Ff~91EX2>Ji%(!%tU0^KvrYHG z-zej7Hm~mGEL*i*-4LKBm&(GYXJD&T^-9Um+ci*i)rp=Wd_XHu%@C8>-i_fcv z6>Xz)e3jz$%PB+df{=?YfD&jSANgus7x#>&Ded;if}G=Sn`In`* z6JT{3pv+i^5+mXiq<48ex=dQ55*K0yAmtUJga*X# z&Sg2iio139NxcwNNX8lQCOvV(`5+;o?4XwCkfjq)E-UozDWH(^=ews4dfNIY9@)Kn ziktgOcKw$-+@MuBolnl}+<1hGtiRQlC+~MhnM3(izc$GSM-opb)5O@vQORTLwiy4SvYD5 zOS?B*h7dp8H@L{U$Fv6~xhAUwpBTGndF zW+|>WF3z}%zjWSJof@Yc1P9$ygClUp5&8NT1jzq{a|R*!f7z4L2O+rkuqc8cbzITD zZYCO7S-@ViC7IP)Nz*cO(Sbt0x`R~c)Vufb_q_%- z`d39}SIsZO9vpkoWIHkwLN+4EYT1@b7=3MwG`@OCc@jUPTlca`sa-9cJP1x#J8H_4 z5eEd?ZJZHAr_yJgm|u1385meto8N@#A3%30LbT#HJ!Se(-D>$wD`?Lvf4*4}MkUbg zilDErtPi>Z@Ww#*2ZDa^#s>fRjlVSy?jNgyQve~|qL}Yg%0QdYe`@n>7Zc#US$l4? z(3jw_`aF@Mz<5&v9(#j^ha{A=>s102Ms`<{-m=+&*|n`&yBy6O=mcX?pFB8TdV4@=JCAO!L|~k8udL zx}RR9kl*0z(eS5z`0-a`JBh{g(E?eO@R8+PbQmi$RfIfj8625OASEIC38)t z2gnT7;^E(*` zOT=?m=QByt&)F&}WnaE-?Yq+T>8Qk4PzDlnJ@5P#ga7AGmv3|G+R3j7YcwP%1- z+5ZJVJx&)_ebC{oh}#=6K}|$vs!jU@z~rst&9zROsIvyBjspBP@hZ+H5 z!36;hFc=DeeY*e%^n@D-!A%s~8Nz(k89`r{ZvPGd9wJumhU#rP)K3}Qq2MQXpL!_n zV3``B#Rq$bjFacykD^P{zsa0u{=uS$X5>TLT%B+ z*C(@-1WoN@Ad_3t9Q&g`LWWzRnei+v0=JUFH z9EY<=FQ+VD@ga?@?Lbd@Qj_X6H z%QnPRr?eSvznWZRqMp1|G819per99A_&AhVZLs??j|aaAt-kAV+q@&gU52T|$#~-A zM*o{E7shOh#9zj&5cbWiZZkjY!BSzWG-9pr!Er>Td1s-P;5-^Z8>3-3QxT^7FYPsa zUVQ!CYq)C4{xd6AgfGl$GSfUU!MIE>c)!f*^bMp9?-knA2O{c~baZrfUtG-;% zNr9pomGP_dt=jkY&#V@$Cwf#xH#9IF_7EB#2N5c2QJh{Z)I1f>C@TNz6yna0{QR%% zH2_U5-!UuH$n&l#=?IDA`d0`EL>bQ;OQL6BCB5Y;-Ha=HqU{2E-QfX5%fwLODM&{e zsdEEaTBPKH(Rmq8lbH(XuWx>gdrkAC31+Wh1q6cbH2^*Jd9UH1xbhQ^K=&Ge(C6D; z19X!IXrzDl;SaELa{y7G?HZU5zY0QtQd*!v@RO9*l&j~`kOtUenI+HsNnofs*Mqtw z8QavGrwy(mgc<~pf)SvU)?>HSEYK5fAVfD&YG>H?{I65mLu9|ujs!H7eAkhH4!OcG z&jW~}Ea6~dc&?BM-)lvebW^`nNqenIC+o&L$|FY0OH@=7Xg@ukQ+}?^LHE+$| zFAFWEfp3=%Z+hDqS8>XlNa4}DnsS7yrF%`XF|W^!#%G-W5j*)kH@N-_EVSqTN41Jr z7Y@07vd~BL0=DwqeCY*JZXhH#QDJ9K7$g5BGIWT%2^t3q)4zJTpeJifvQX$e*=gce{K8pU?owW0 zYT+aD47vRFzbp=f^cR8uTm(k?g21EiU6iZ}0-ikGF9N^KVwxeMQ$e6-QEg5|$EA5r ze1|ey=Bax^?%AO3l&QaQpk1syoE^5lg+;jc2s%0!_ldyIjGhyW5EHJw%{+bH_xKcP z2Y-Y#R;P7s$nzu5j|d?&{09*jsKfnk2#f?pU?5-oBm$e9Q&(iy?O+{|e>hGQ>CIha zo}>{8kkv+~P&&o*6gTSxh2V2%72tp8lFM*afUBi=sAEaLPo*Y^w7!?@2r zZMqFxAVw3qJ(+1a1{NepmfBiHF^AWTq2Jol{kvQ~%ygJRrHNE-o5nlP6JL9eb?iML zLNANyiKlN@O~m<-Sq@PP6ANkaxumE%PBK`Rddw%_g6iOWyqmt)FT-_|zqtPA z;yTI~Tpwn92&yb$p=R1IuG>$$>s8!Vqn>^$ZS2k7I*ZaqcGt2z3_Oqv0o|4op6_^bDGRA`X zm$Cf0j0N=zV;QJCsT=OT-)XaQ?PH7=EAyFK z4{$OSDCg@(e*Ra|M}a!r@5WeAp^OE{7eC2Zbdk!uEt!W;Z0qDacd9vKqmTFrSB!rL z{X!jSJHPau3^3|m#sc(&8ywotGnU7pRb7$F!}quEcwxl1%hojA)))v-WPbiZQgYQ! z6Kw13vU@xTNN{hp2_O)Lf%g3ZYl8Zwm>>No`^*^{N~Ce~;;dWX9Tyv2EwX4a{)bfU za=#2%(f$JKp9`#LUx2mGAR#B@mbYZkeu0(u3KBkhpdrVk2Z@i?r)M_%7cyFEf(S-XqBkpGo&V0_;duP)aQ!8myWVTOWJ)fXjFUF`?@XVDMUtjRS)17vc zfMI$8d~=nx1d*=R>;J(}08oef-GCJh3amiB_(@>py}tcC)om_@1^giN#wEEMnC!Kw z(`gTfh`Q2!s4w1}0;BB(R-h-`;Lv{_STn88QLij8PO0FAzuCBKacuN5ww<>VJG?+g z!QF?}A;QjSyTA%0xVP%W0f6-YqWi^QFK^Zs32H3#(AIMs2(QxIv9_%jteKbu7iOh2 z>?jp$e;Ed&|Ha@x7lYBiU~msy7DijaTPDN(VsK4Kc^m1NNxk0Y$IVX-CubwNXb4D$ zh?|TCH*Rw2Ej9hc;KN+pj`vkc`ypUP>1{7Fc=+*!9c$FNes@owIaQh{V4Kbi)wxSxLuY6mF}=>gZ{&` z;AyZ(vP>5keJ=(BJ>dq2@$(pLLG4J6*C`cnnhawrz~2LFnkcaMlCJkm+$3>;&L>H> zZ}%hu3GOX?aR6ZbcSQFK*Pu0hypCMCpioZ*w#kI6-bxzlIL`T{!QL3brc~Mz<-ZKB zG5*5!p9|L*U*Nhk`uUaF@l*%R{lfLr2@{-zjJ)~_3Hga*)E#(USHk>XHw{SRKgOVI z$o5V83)hDU*HeOpGZvi^ZVCH@Ypf-?!MVY;%1;5KWc4>ez9$_5${T!+W)2QA>v9=Q z+x!RN8mPnlZg7nOg=-*R{3KjA-W69*_A1*LvdOFuH;8ScKKn^2HlDBR-j0>Lc~DFm z7-KJ713lpehxzkxZDMoT@K1}ZtCoZp}!2-G5;d_ zpNs66Uy!|BsIuxk{JS~#{USRi(={984~>fFHt7+2rAf|t9k=GFyql22YA!J%<8!>~ zFR~vdvcHeY46u5Rj_a^bWWQETBR-H(UGIvdXDNx25175A6J?l5ygpfxY)Bd1vSJU1SFm+;s@Ycf9$u9f2K^sMZGw;!Ey8a)3K6< zjvtY|Lrpb;+cNzeMr7($TaPdCGno}YopYmAI&*(~S1P)<=YioB{b;~|tgmxb(QTlo`5J=2b zo$CK}@6rFj5M_><7c<%tBK-KnOYV@swu%PGj%-{FpPAP@L<%ne9z?=iQ3hXMSrYdru3IY3*1Xb6;A01b}cjapzssYTF* zH>nnn*M_`w7BF;a8oZmO!zr7J-=10#OTSVtueMd7 zDHs(i;QE~B2<~?9JIwu4i%%Gqs>?c6*{6KCQ({~OTaR@GI+|Whvmxm9$$V_g8~h_b z|0~fApjY}yYQYd~5+>iQa%9YCOD!STXu>%V3_3HCgV=?J+_OBu;|a!rQVZx!i9k=d z!Qn!3)?qJ71Ul^wvji8bbD$7a+eyQ*@`(@}h!R&z#(8ONV+MXG&i=~gnEf=2=#h1k zGhXWDop=cakK~k*=oCz+l4;vkoJTZP+HM?eI{S@<0O|b8LcUz;4D)FZz(RmTcMsbG zIcx7>`~8v|pyBzuksBN+xl#6u~~=ELm)xyO34VK(6kGVI8LnH}=cO z4enoZ^XHNq+%M#&{@J8pQW3*7wf~!O6UZe_PRJze?yP?ZxtZxCr?Qr(pLX!QEPNKU z!2KpbkZJ7XD=Zq8fpfH#ipu*ZHv;C)YWMlfgJhyRiUMT9XAPA+kl&w8GPJ@=W2LC! zxb@4(4bUt7B)P$Ak~WkzuOLet+#c)NhL;!iuzH~K8Rusrk2Fc|MI z2LHJjjQ0hDYn2xtquKg|>ipltV8|6VR8Q3#)ENE{44yEF%HAAV??bymho?cn9>4WY z+C`bc)`ewSD$X=!V|M=-JRQ!+R^w$EDw9Nj+!}SFokmn=OlvMVKM&b0^F->+A713} zuLKx?Ug;+>Sg?ZAX!CVSSjPkQE!Clr3i))EQ$Z`eGQ|-*c8x}MAHjHgF&O9xH#q!X zg27@gvPg4-3@2vfhvpDW!(W{ zjn{|tHSgjb)#Z`rR;vP=4-~eHv+74SIVxRNpJtxIX1MGaOd1(>@AGBRF6z4Jz9AFu zK0L19wys4-^MBldK+$V-mT&lmALZQ<25cmjc+)xyQ_5W4zD)Np4t$~vOy8pZhq@T! zGMQUo*9Z5V=l!r@$)6ct{SC=qAI#m)~mfxigZ9+4-sR3;U&wZV8%8Zx{UlX$Qs#+;@bL6%pwu6 zsc&4O$GO)jAw&zq8vXCfweBv)D4hUvG4aYy9J|-wtF*ua#`UKnbRF16wcZBRnL};> zwED#5Un=P~nQ}P^yp$4!ba9B@xpSQW(W(T5TV(SwA$!7xrg2{A)#tHjFjX9>cJKTckf1r^t}q4 z=3S;pCKu<`L?fjE!@0l^PXDM)Ec$eso-C(YyOoPiCXTRF(1qiWb;#W_!^)En6qNvh z)%)>b9snY6Frq-0wZn+sv%o@qBgx}Y=wck)teQB--l7XSI!Bi#p>4B3Q_&!zt_NTK zT|Up@|Ap)lCE*0bj;E<9i&wc8c~IxLMEh~!0@Sw_(elr5&Ldq*Iuq{}rHpAW8;3cM zW!qk;Vlo6*setG?Dd%2El1Q41o!nCGXP1oh(bPO$OOT7~SV%%LDp9Xc!gv1}gLyGt3|aP|KQZx}>3rOS)l?VyrwP?j23sf?VNktW0~i z`+a=9Z{>NYc+mx&$P;KEYnl3_%q67Jz8`LUqLlbL-{s}bnew+RM7;Ao=588r&dJ@_ z?;3@BZX;?7t|&6sD>-=zsW~Gymdp$%P@OPZTMyK5Cq#Vu&Mm%Qozkb!N8L@({TvBb z;t7L|Cgb6BD#p5}M`n6%iVWRAvGsdILwEKm_SK6=j$TQ>!E6o}qn>mV9Xx99o`Qoi zm(^&>jwL*C>GV$TSwsP)bmh~`@2og4tDn8b*7t&*xGk8l<@E}#bd2(oU@my94_

vHSrHL`4Y!))mrU|+My=J*?NCLvdFH?W^2AuF;5 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

hRd> zxIQPp`+1js7cr;A2G9Sz#Cb*_ZChlr4a28WU<}i_e zuBDVaJs))P(_~?NiyW>e0sKw|^8J9ysCp2xuN>~eXfYP}L34T0&Vl@^Y>%3Ak2Gc2 z=2WYe5D?fq2^Q{$PoR`I^N>b4&(CSKn(E<7H*fFteEu3mG(ELNVuKBMD)3?Hzi;RA z0KgT9_q}C4#xxE125~RlKd(fbZcWBo(;Ny0G{(BW#;AQLH}OsrS0;jtccn82Pi`8m zEVJKD9UY>B$9?dWF8`W1|0ti&FbF%@J+DSS*eufMZM#DeoKiQ_{s0Lx8W~@KF;K$S7sZKXveAYNqj>VEwyjh zygOO+_X9ij@pQ*bD0J)s$?U!zguw1HfBgabp*jpTv(UqwVa5Q9f*2)ETS{+U@ z(91|c9d%iy-5|-dTY~SupRGgCbbb;L)@nP3bQs(wvRJUogv;<93B(NK;WogNHfyx?;$PWbpeo}586beLoy^){uPPtQkWyUmd z$P0x9GS9s>RIS~?8t659XIsut5g!zI3esTO)QVSY0pzL?RS^tp7o2;#rMVYqy|@*e zfHvkiYLHiGQIL?JY%H@7m`u!Q--ErdL68Fk2>QifGy2$u5FWF68d%;$_o}+ro(L8;f zRQlq(ABEhnECtxP{tK?lY5d0~_5Gl(SFi}^g3m4pz$y^*@%E83Zv?g%A(46jxTayS zK7AnO*@ME}P-uVA)V0<3)Pf;5(p!REzqxxf=6nUn;5|-`IX@a^sSo17!$JpG^R2QK}`xx z^{s*JMhXeM>udPHaGON`n6^Yyph50n-(ocNRFGySSY%w`;&C;}3tWx2z4UA> zC(;!N_9`ZAJ!xG)-RtIwUsxGxlt~$sI}V{$Z)#vkHHs`M{0r0GG%N%OGIu`?lZj29 zf3`$-Ji{ss+T|~KnNQvVrueu&JuXcTi7nrF^<|ulh6{qI9=-z z=)o<%q>sL2=1SOT|H`s2yq;#oJF5EC=d>Q>Q=IhGv9{GttrrT`0mr7#fXOm136)>5IZ;*)F#M_@$iNDNk95?mDo+;AV^ku7jA=+UawDfSY|N3OeBf2{0HWD@7 zx3fO~OVTQ_JSBv7Y=$#McEod(Sq{+Fi!S84MOeTVJrKF0b4wp)B z{3ef03I_DC!c%0MoVLgdr%0HkhW~pQ97Kw^l7LqT-FCvR&uuOVBfzL zlh;5$s7o}Y={-9pt^R`XHlyxO?YkP- zjT(^9R%uOhZrSs2Y6nWx)5V{vKQ06{5`~= z>bfv67RUT}avLGuIriNh+!5YA@m_CKAL-fD+$SBP0(=YCO&Ato_1$P4JBRbg&$Wt%a9Uo};AOy= zkLW9zov-(9_7(RL5yCr@Y1^;RjYOw@$bupO8!DVS^ z3wjK42M;AL^jkYb9}ck;Yh*Oi{z_MDq;nqDnjZ2A)6ez$i5wlXs!uksGA*j5dvN$Z z%QuBH>z1KT%w#BTYMgZ;(YQnQ!^YmIp+{PQnBU@T(ZXB00sgGDt)l(UoMO?Okh;DE z(IK|4er36?Xv{E5oYjsDpHvxFF`bEkFwbs)n$ncKBi0=Cup0ljBJI z*_ckYK#r_*PAVxXgcu7wJ7?_BI1bZ|E?^Xl2XkH2Y4^E%f{eJp&K;@Jxq<{C3*yn+ zAcL$Y_0h-Gr#<=+yYsMKkIFDC$`#ut41*0mf|^sFi3G)HIhl-QgwV07U5RaS+jCVg zjT`dIvM`tI4>UUi|!zZX`8mL$1=8 z2uRgaaBac_7`k--bu!_0(IFe+vimP9f=QYZTpQbmQW@5VqYzT3>RW08A#2Q}K3sSd zpX}2bSrzfzHd;=1`pA(*O!yr|wFOPJ#J|6Wshfh){bv|ouzXYNxH4oaQkq~NU4heg zF54)FraK(TOA%@}3out?z@hb@itG<^zXRp_HU{Kc+6kGW-*=WAXW&P&@C z&LU_h0gv3VMDAtxVd=llHyF17-}pz_WkVc=;>EEvy%9F@cU>^if?}dV&D?(XI3MdBYt7nYA+ly-rrcsZoXiR zzUk9bD(*mPJ2$r~Q6#B-eIhlAd z&zG+{_)-(o(;5?ExiqpmDL_q};XxfytFoL2+!BU}bGH)g%LGGV_Fj<@stcD6DVCE|)zLgX zlIf~j0z^sQMndT!Vqz03irvU|_a6rruVO^&IuhM2dO72~RU06TNSfji#(294L4aX3 zh({ns1A^BV93lF59`!Qn1&v06ahLmmI&vh`Zt#FDGGbi0bQ@o7cEsrWJnLb!(+Cn4 zPLnluPN^I!h|o>fMer!+cWf;U5R)t)^TNKK6O$mC5rgpP%jWuh>y#F)O**V=js|-; zEluRNBBd#2K>Ae0U!gz=1ZW0Ww*ULWFkevc8MD3&xUhr-xTLjc(16VW@}^N5-Rss` zF)!=LGhk?)Nj!!QYH zhSf2gYdD`mH0rbfRj$ZPc{QZ}KbN?jVcT>nrlAoeregry6r&CD3fvsav2bUA?UbFu z8k+Hc+aWOwv@pcFXK2!)KgQB@A0fqS`W{|L- z#EjbD^c8EGE0@OTGTV87WUyAK)56#Mkn7VM_of#DN|7}nc}S5`F-x(yo_zD(4b(LG zguj9xkzQBoVq*xr*&2LmRgV%&Z3%y6GZtn1>z39*bhc!Li_LSUpr z4MmmU=D}U7G*0SAlv)}yF&C62@mS4{UlIRyurln@uWVWTB2w|N2y{2=vdA`dX{~e) zhPP8mnXf-4#hZH5Y$@=Rr(l|7#VJl3)@-AoRP6OA1y^P8n#BoqR4TJygu6GW$7_dM za0WC@34UP+0As1AA~d8I313LZP6DO_1iZ~I4jdFwoFDVUM~efowTJ`zXBkjz8XSn; zRda$rh4y=H{s_OtDB#gUb8d>{L4!y^zH{NSaTHX{4eq<3<|z_B(umKEsiu-C?KcW# ztp?$oCq81*ROKQ~-4j$jrZwvqzx;^FxV5(t7ez&P8 zcOcsisWJhBr6v1BaN8h8F>AJRXH@`&pMO~T?_(<+z}C+h2_JVl!?9=~;vf)^T9%sa z(EIQj3E6Ln#Y1t06Whk%U4;-)1R|rp$V7?ty33%P z2#;xYUJe$7^-HVA*S^-rPyi>-iX)2LJpeVQ$fxm}c>y>}1A z2&>OmCW&>9r1XZPb4jm}!Qxn6-#;b)B0e2W*y#km3JxQi(2XN^JK;jlI=Ee*IN1kn z=r3JcGxnN#*)2OYIfCk-ixQXZ1+`@C)R6+Pm7E|F?1YxtL*U4^ZlK$fX(noL5F$te zL}qvLQ)-2SXT!7GbLczyuSw0R$*PuWnqZz`sf$hEqOaF*kx%&cRJHikq`3CC-$aCv zW>GaLnI+X?aT!PV9C2_vaqHyrq);(R;42hx#vDUohsTh(d&8E@cA2t`FLr(}=Y-fB zSv+Yoi6YqOb`Y_UtsB}kOcfPm(x1I$WDpGtVgQE5R|xyg0da~~Uiv>GWIm+o9V8`5 zu+5J{3DhRE?o#i~<*DhF`yMIQ9JJX>UND+BfrZA8rfrwj9z0WqN2-aWj%zcyCbHsn zbm?%h)VRKMeNy9r)FZ%QA5Sa&DpPei+e0W{);d|5a3@tP%)CxxfvT4_R?r$TF3;E3 zBmq1BTJD?lhbE*p1QSWKdALjwjy+e(Ta7*1|`31W%-o{T?zKYtTs6(u_2?2o~sAGHpX6Q=EJyZ{{SG${N1M!s+ zJry{Pf0kmBL`t9a6eYm_SHCaBum-S|>bgVd**FmHL)M8dSQ-%5nXJj8r4TQn06!yt zzp=$v+1B6)y$G#y)33aUMItZ(YCI+<9hiW!%Dq;f{tJ9#!}fd#-!vwX_$-{@l!zCP z#m*ee`~^*A-~8(Rg>bg@Z?^s^^nWGW4G(+Ac3ekb%@|ehn-SSBhS-`J6g_Ufxp$H< zhreB}zebXrqizWajoOHS=i7vLKu;%e$d#E~n`u|~ny^jiohr%>F$^$EC(&6w7*|;Gv*sxa131!ZQkQk%_#oPde z3Cba563H-89FEX9uyq(P$%6k}3!AM01+n7IrdNUR6OU376sntGlM#!uGHP^`v|prl=vg>mE;;^U*AmUEud3oY}>kq6wT)#t2vs?BRyFeIsdSw zWo4jS)byoQGU9~wjXK@WDi~5IvoDAe=yx{w!3s0Q{|>KAfR%wVSLi!q(o4#UJlL^` z3uFxzHG^28Z*i0S^kn@8V5^l6{R3MpiPm3HJ9O7;ySXBUD3Ag{DHYt=;T=TgG%AZ) z>lL#R;DfEA_)tJn@b;E~c`J#C@KqM&eEf>AR^tuS0G8T7_BUPVHEesj{ zi^%;1sH!mNF0u+HoewK7b zvHk|cL=n(I^Pj}BieR(gBI3yTZSAO8)7%n)sZUXTc60WOIUY5JkJSnOfjdv8DIPNu zmPfHSl1L%6)VV*Rb#*zR^m#9y>BZsUu*_qsJQYKhU7I?avbEKo_wGgk={2Ko)bjxg z_5QvdB&&SzQ*Tq9S4p3>rJtlx1#<8KrP4tR9i4wyHUYb{5x;aCP_G+QB%Oi6X^OK? zVctD<+ybrsF8GCQ_bwwQnuXIH5I5bC*JZX$z7<1rQRN0+b&}#T%ddr4fA@01yb@s_ zu&)?gmp(T#%n+kN)MRKuT{NX8E7Q4JSBRJElB$IkcZ*sMqWt$X1WlRup<^2^4GZJG z{O5KurEncsW5+qbmoVg#6md6MU)hdjt%$H6E~%o1)n_jUh0hZ-pfV^cxB4ri8lM!u zE-y=uJukTWup{@;P59bT0h7i`>3_$?sT>CVOliCL#1c!uo;SoiQf%ny=zCS|duHK% zi@-U2D*_AUv?ztWE1cM-1%mOlF?FZW;J$AFZZK5_a8V+JD7YbNNk|ve*;-@vMTLwMDLX>Jz8>5dn93HN{xosZg~ge!1aXeZ#N#kAG~ z@qHL>w%vC89dgmr?hS;hW_K4N$d4%dlfC1g#4_`Xm-)|Jh5^x@fv`5~6CkM7mkWZx z*2t3k{=z1S z@AkbdtYSkXGxkix&Pe&-j?mY7;Fo@hk=9TIKb7A4`hbau^gtq%u=(@#!(8NoP=J@B z$Un9Wg@6D{R|TP=mE0pzn>1|P%h~+)opjxscs+3A`i`a7>cjv3_jw5o;H6Tit)r+4 zWt$Xr!-^J$J!kUBXJhQEsgSGw{K2)$^rmd|w~$X@Vp8GD=n4tLwyU0)*+KY;D~BNFuhvK-4y6kJEZ*P{T+CYPP&9(KVmz2(vA5y=C)DRiROS z5)FTE`wiih=J6d`%AH>$!$(FSAlhoP9KGB-iI|2rusI_nvMw1RN4+Izt5Ml)xh zh^mVu6$xin0&qFCUNVj!;1IPH)DL1nBU@Oa8ceroAt7vdV7V{cF4c3r6-GVsIUfnT zXt$O6u=HQ&5cGY3Ln0OEoAeE-2cX-Pl!i>HDq{8Q_ejIZ1_x@mTnW4|r?YuMq*1RF zgo`>R${wyXjZh%SV-g;ggr%o&s${R4uzF5JboXu0@VQ}g**#KxA1f6)!j1XKM?;Uf#`j)U#_h-lOb# zv9RUys2p;U=v}GI)U$1zxbNGY-uNT-aaky}h8~YX7=1UwUV$a^5bU<`U)1!13F_o& zA--C_HY=KP$5CAM?Vei21yh1w!YzVtx$xxb+Ns$2PQyD$*hA7Gr{Q_ROHzXg(FxF~ zznWpgnZF5_cPMd>Bv=L?Z{EBt#7(P=D5*>5ULy&=l~U)oNPYQF-VjU4mxO21&Iy&( zr_}2t26hHYtWWdFuu!utVCr2S&lJ!x^uKcQK|5ot>pSRR>em>AHlz5z0aci4^zf!)CgW%)|-r6RMrXI$wKMdJtO# zc5r<37sU-FS^ajh>PHm9_2m@=%4Xp4ZZ@wW``pt{4+l+%K$%+lB;S0Fv%#qTX6v6q z{})?{8!&nE!pc|ejFfp8hJ~uIiig)W;lzz%O_vMf-+MD(J+(erAe+^vRXF`U@NTg zNj;EP;ieu<@>>?vC@DPrBXLmWICkil>>{&*Jt#Tn^vn#T4X)Hi08kO3n8x$(g7Uev$@WpaWF1C@+2r6cIe&Ze1p>9{F& z@0^+_iyH$ND}1mrpgFP$a$Yjj+jG2+s|n?4>h}4R>syuP!}sAwIf*-1+ZoRTZ9fJ` zk^No3*bOl5-D;3Og9PgRP`gJw-(X|?bt>jPdmaZ&AJxHiE$JBGnto(rB z;vt8<6iRa+UeR2%)#EL}PKL99Y^v6gs>3AGTk z3M_(g*(qX=%}-;X2R#*61d#5i6s=!2H3w!Q{^aCy^CXGNEA+_{`e~4bpr4&_3$jFR z`86@(pi(=yeHsb=v;_QEOJ04J);|@z4e-2y^iP!g9FSTl(;%Y90mK?csz3EwD=Gv8 z@>Yuc0hoBD-{m%$ck3a#Z=NB~ zt4u1VAyrC$o%p6;rkIqdzaEO(?#PVCLIX>1^KEPvOgIHD#`7%w&DKAK{x7z2T#Hg` z0=&T5@Dr!PjfMt(N#q%&I^4WVarTSOuGGS}4@p(-?;FjPJxLKkmp?H8OB}0Xac71l zgo8_GNUVEp5N|YtwL&d`lalr_BsGAv-M#maR!{Mg_cb;Y4N{2%*z#3W2(coWz=T;O zZRKQKk7(BKp_)Zv{rw-df-^#?X8RVpsBBewm!ZN#VTGwS|z{R-lHxv6IE3x z^STA5-e5<)HK-UrcfV{=Mxo_YqG8dcrLZY1jYU^Gm4c7|)~&^{VtnAuLO(^R2Al11z%ox)ZKi$Wue*C}sxr&HrJIy7-*QwVCznW;Bsf;2%m*Kk;>8ymYtnXyYA>Al zWt8u$2nQI3V&uLqaNPIZN-A!41no2_!00f&#O*sMV=*yK-hjmRN(9y~(z3D^d!d9& zcsz_{QC6`qS)z4j?7^ewDi1r28)OCgV5I&fc-dGPeTMG%@BO+(w(qlM4Ce4RH98Ts z1yHkv!)n%Fn1A)a`{!sC)wlTL{b7re8BB0&m=%F7;UoyPDY44GA<`0gFwHO|yfBj3 zft2Nc-vaxksxW7!mH3cE>aG3Qtfp1)V{@6NRg4>Fc_M2rS=a-xH5KzQXSE&@*%-}n zpq&FSwvtg5#qs4Z=+z*0U z@*7>WOGPk1p9htdH8~1QTn;kii}05_2*-&%+Z}=4*ujqvvcLWk-}KV#%jYGub+dOs zfkI|Btv1bME?_Brb66%ZJgN53iLox0#_;@`t$zyrUu=~>h6#u%AE*>7Gh-LV>ka;J zCvIP4)Tb4*rplWba|_x>C7a19@5o`;wExnv>K=*QLh@tXJ|#@&<{-6zUJZBbQR?i6D!V-Kx{kr6N zaePdVDo*Bf*$%fB>9`fpB2%LV0_J|$M@(6*jk_vZt@vKA9~E&tWvkAysgXTi6WGg5 z82D2;ND@Vb!0iz^DX8hiIDo&~&e=B`jY%u}YN)r7(LoNgT{Od_>F04t0zFUz7Qt0k0g+C-+xQmW3Rc4S3 z=U0jZJ8o;`ju5X4@jfVUvA@~+r_le!Rw4|XMA(HuQF~sBz6elg6VG;aD>&?YqO(_w zus9goOsi9_qe1KiH&4a>FK+EQ9N=vCm|s?SkD~O15@4pJpfsmS+>Yy@;H~`p6m`BM58krS5?ic=c!`l;McZc=_i$f06dO@uCd$IDd zf)F3Rz?7%41F<0!iKqCi`Jj)Sm^B*}SN(Gm20BiYbYMJ@sCe7W@p^>|wm*8Xm_wkCNrcrp2{n zp#F0l{9aE{i4N(xR-WX{c@iz0`gcbfT`gYH7N0Nl;QJlpr&b$mj^0B+p^zd+GCF@> zk;WSUI=sehCKu_@&z}v`Oe!y3mxcI zx}dk3B=d1J9vI64QTsQ*n{p6m9fS|SP`lp{<~h`sgCm1=U^*m6O#DnqVRh&UrpN1K zBB)77{;>4l#})*Dtwj3qPF}WJgfpjDs9{*zpUX$Q_7~2!E^LT4xL+H{oENxgYCmvG8A8j1IXpB9OaINAL+Hta3)3`WF}j(yVLmhbFc+6YCD_^3%k(>vGVlNZggr zD?y-HJXC#y~amX z)89=mNmy$TLr-X^bR@!`vNAAD(uz&(u->EN>XUto!+m!7_#Z--BnS!FDY>>LvX0$> zposj$;eDj79=>PZsb_4~pj$HP=q+F^@YW;DVdI(NJB{NZ`djj#=^aF^NY<|*x7o-h zM9M|K+>DzqI>l%ot7S?ie&^G~ZNbNg%F~D0vMQK(N97d+6^>`_PcD_K22syPU!2s>ftWeNLrt zFcLO1WHz+%A~Rk9TN1(_FS`NV8KBJ8NyH}yNz)z_nTINx1mmTox}pCze(3KUw0NJ+~HaM^fRU7B08jJK(u36tmdL^Q!3BZqees4u($89G3n z|NpGvpF;l^TS<;3?JZ9`vg(e8y`~o529dt0q{M$&hRLo`2@9T$JPowFht?6<@!MdI zs-kMgnFj7k{CU&ft3y&{uTP{rWLziA1f<5efvF>l*X5-hRZl%lc6h%6CNDYU6lf8~ zNQ{h0sKn-$HGlK*t%$i!OggQZQ6(n+fHXaN#u(%H>4@Q29vQqHDIq;r`%rjPui*q%OF^p zeTq#pQ|M7XZw(ZA{i6ED5aXkFrZEJ+Ap)2;&hm9K8@szz3C~AQ3mvWr^}tYh|6T%Buz{yQ#gHb*Jm`=_itcF z0Cg_yW-^!khWp#7&>wZcEb&FYhNVa!ml?3}is+)m%3cd*xdBoxuYDMVemj-j(pR)#NTERo;8z7WkrsNEts(e=Qy5(K(ynxm-N#buM zHIt10zsJ8DH&7@{dZZ=PPoti4$TptTzMy%!o;+>8kBz6&{_? zObwvMfSAUZsRg2BCurA7?qSq^mUIi>;`aE#d6rmjpeJK3(+8PhY}q8kS<6FUSF-qc z&_Yo|D6ny~@V`$h|0(o;86@FM0wkc1aKjw~>NQfcCrfY=c5q47pM|JCV&+&yi7bBb z!X_)s`0|t*o9C7Z!w4wE>=S}&XTtk8HS5Cm+7ivMx2dvYU8kzR42x2JJhI{|GTB^7 z9hf`a&-z7LM=^g4l7eddYGl46uWCD2iz|=N6kZ?#5~vyqr$jM{;H-Dj{L&iu%Ux{S z*7i}NsASNSJ1`IP2-QGx>0KI;T}tDs0VKBPCqzxSU6wdZ-EW(9HVpY;48n&1VGGOS zurn3mb2L(oG|nit?lf&|7EMQkVOw-05_?7G%w`&VXhJUf-WnyNT@HGI z<5i5i_>YWlY5|BM(JjBpW`M)+LDT4tY}4yCR>(2ISyx3Xw|94gw%*#d+t>*iDntAp z*S@AGNdWhzv3k<)h!>R6zuM84=8%~H$#>~ENq!nycIpeYB-$Eo+)sF(Ff7+0`1Bxt z{?rqKNecu{~%#PLh3cNZOZm-vHb^PX%P*mvfq(sQ((ZZCkms?NH%yoxfWdvB{uxzm+UC814`DC-qo$Gp zhPY(q%ei6!QzFgW@;5W^M(0cJ28e4al?wmr_df=~LKNn>^Gu}g&muu)Sh}dNsn!u{ ze-^?vMMR*07V&Ye&w&^QiQyvSSKs2f7n&;QwuP zX^MoxEwwcxiOamOLt$O%v*h-Lo7|pgusI4LjnL(7O$Z410u}ay7>d-M1tLh_-K$Or z@gVlBTdSQw-+W#4v~aQoI1=z@Bt9(t*Zsmr^9DF1VjGryXS-x|c7k60ad84)-vKF8 zX>$+=v9VAdB5I@)2HoT*+6bS612k#M#I80FRwUb6vR}%I6#NSYQDje^f|Fwm&^peK z1xHt3aMhp6kym)vYO;1uGkY1>!#LjRXT@~XqLSQ|AavcokLk(kdU{n<=^ zml(Z-)A;3~rjhRU5by2CII^8`r$#H`8$GV5U=OmAfY4kQuJx*$P_ODn78{nwE& zVkzi&c~1G>r6RZPuOf6>U)6gxJ#x>B{c%YB!Ds3U2EK}Kf(d8mHKbHOU~n}ge?>|< z%f+@<;9z3t%J!$g0Kxl%EOSar;fzb}v0NZ&|W!Is)mj2QSKk zcO;J@Ec7sp=-Rg+F$D0SyDqAOL-8V$G*D7lXTb08fmR9=$Po@^F)-L}xLA(Qxv=1* z{6Fg6I;yH~{~A7YclV*C8v*GM>4t+K(%mW2-JpPgbax5TNJ@8yfPkcQNXK)ySA6;U z-Z6OZ^NeSV_jiVW0M0Y_+G~E+Tx)+md#_Cc!(E9C~bP15UHNkura# zYr8M)8}g?yRfSkvv@GR#OM}?zv7Eyjd|0sT?e(mLXnnOYZOkZw{>NO6IAd_d2dwY0 zp&3?drcGf#Z08LmSzD*#HmLwBAnb;X(yvl$;h(^>Olv@l&*Z(SzQrRN))#J}@>WDG zfHrL8LcrD2NOx!+*jRC)GF5YDUYS5i!`ln}?rS%W-uvWJl)=ES9W@&qMYf%B1LA~i z1aaR8w^Q!fQDW>9;Cc+!PyDX6xz%=%?x(yHKk*>D{&>-7^JU(6NaaLssqF*6{uD$< zpqQ1SyAM$VrfLk1K6f@tsU|P}7~ae;6$^n^vL$LHq2%9X>AT9PsZ3lkM$6Z~e)}rL zGJv*`_G2XFG%mJciFcwQkn_$%GUK@goZhk}qaSwIh`X-cgoWU=?KpN{XZ$j85cU0t zMnL?pM+(c)OP?={Q}&wE&@I>a67%h|Vm?QrKU*x+TlfhNvmt+XFV@2h+=<&^c5_6! zy#2R?+EF~}mBwU9NyBrMR%v*~8_%>Syg ztzI3I=b_KR?>?-tSa<;apXVXUU=Mi*ktoI`NG=*6CWI72?z{IvNl>a_7w2OPYh$aM z+ZDSq&miTC{QCC=ArcF*bc3Sd02zK3rNDEmet5Cn%KzH zJA2hSBO^26`8hF7G^jkwvmXZEsWW|j-~c{@>x$DMS(Vy6RBa-dFw2J^D-63Tk^ zNq<3Ph9GJ&;r@2Wb2KD_*TfZFihP{4QWye}GMP9`RC?4#7`XhRnFHvOVxOe=G4_TA ztkRfkRLl>hTzGitZL%O7faI5vBYAyPfVY;A0;!xxb~y*SMr~5wM&NhBMnt=L<4F^@ z4Ley^EML^WH#7HSLr%_>wc2t zRJwwGSh5m+HEL(e&Bm${g$DimU9er$K349Y%yb<^xEQ+fq0f~lrX71^bbl$K{;>E& zTt^T64+q&-wrZgSJW*Yi3z(%uw|XXvz#`ok+*(@aMaW7((Bvs?YCb`{@Tifd0>O5Nfc4_+^j)bBru!MKjmv@ae10 zI(DAmyze5z;1kltkPS(4rhwz#iL`#d*=6GRPRS^h0?H7gOgilwO3%eXQ$I*;x`CAj zu^_M7jk_$J0e!o9cpDAfj678Mt9kR$`kA1x+Mhh*V_kpmAl_PRiCFya0^vjkgqLIX z-nFMdzC?w)3@p&;Wjv?ifapKf(>>_J6#r77O%{ap?HO+PUVIA_h0VSxU|j1>Y<_;< z%U<|%#T!HkiS!J?$)R(;jSK{hNLJF}-0uyZ6ocSAW8U$e3jXZodljdb8^)yam7S?_ zO_T_x@~It0JUyuMK_R2$14xqH$6ChmRl?A3=8A*BZ-;XSQIbE+d~I5I;8t{#ndNzy z{OWn%>GP4%schA4@hrS|cwQ*w+R0zhUbbN$(EhS=M*%|nY}WDLoM`Ny%O8hssb;}C zy`xkt-|x`hNOCu`i8Y z3;~3qc#s0u^hlBh>?B=~v1Qr~|6st3e{=MuHXe?+ zCeV(1j#PI0n%!rXkfXf1c}Bk9#P94O^ncvg!a4(wtzfyG%vlYqB*bJ-iM0H+(1<)J zCgdiW&uLC6h|a^&e93g=09ZGRp(Ktee}6IE9z259-Kwmc4#}^Cx8dnXSOcxzVsnxR zk#;YXvDo{RmirFT%ef| z(NoS#bfR528P9V6l}&BO{Gblq*>zlRB2o~AMG^LrYWO0Y`gh`MnO|PS))c3JE)yb~ zJUEx`u3~*xxA_iU9me6_0FfKvnXZkT^nX2T5pk`}F`{?SSS|4};rKO@+CuKBoP#E6 z@&G4+5U)M5h65eC>W`hXeQ`ubXlc3`_&8-7Sy-`)oNTq%*Cp%7SWycs#}xT*k&d~7 zLyx0ES@k2T0T9Y|3B+aqlw_uC%VG`~p)v+~kD z3Is)Y8#!zU`b|t3V-~Bksa7&(Bdnh3%$VhaUr-K0q+cvB`lyOmU9g^2tMz?@JG_dwtJ8VP+QBKa=AR)rHQ2KOzhfpxXDRdzT=C<$Dc@4vj$O$6qj4@P= z@hLwpTo6v=)L=&Eax+7p3Co}Ns&~JPRWPe^P{E3O(n*s&wOUhy&diyN=W#bXzj#d) z=jFqnr8o7YSTFZRYzhviqd{7TF1G#0`|p?=kOcVu+RI+{GiArI@qUvtj2is`^vhu9 z2u*j=oCb=hD@2QixqLM6do%tqKZ~nobnp_h`q^SDO=rs|Opz0@Xvu5L^(JJQCrIgW zBY{2SSsB~|4@uBBGpd;`zmljGP$Cq>8|D)#*YyFVP`rsbV?Q6Q}PqgU>3h*FlFBtfzmufgf2Glyw)XKVMoPxcu&z5>O zM1qXgEeWL=eDY00%Cjn93an>xMnYgZGpTboawkNj>R8j%+iW}RZp{_4gj) z9qC1TEy#zAE80YrdRju<8??Y?ZI^)#KIJ=J{EKFF`{;_Hr;W z5#vt9ddNN*W86UzQJQ;A)UJOVHmCA4C?8`L7F{m*6R&KA(60mJfeS|acPo4!sCn3= zUK5x=>L<^a;S4@eC;~OotzR<6m(xyf!J{wr&Ia_oeXcvNYxW}ch5Res*pUOh;MR|6 zku;|6jZ z&V_tVMAr#gkbB$6lvegFFNO8>%f$wJ_FwWd(Pvq!IY{YR-0xNARpVW@w!}3*b&)4g ztlpmBXX@T2*U+#|D$TY^8&ML=g^nAHErkiV~dx(VA5jC_Uk|2inf+^f4d{W$I6ed&S+ieN+iTqNLXc zK7{xGRQK7h*JYbm&SoPC+J{u?!EExOTvD^8?wv_8Mkkz+XV@S`-^*iEv#1Fp2>P;O z_d(a_%df8zNcIkc(WUK8mM455-H3s)p~F|wIo;eC^Dp5XpxbzQlPb1-?eWM?Fg=!4 zw>0$L_?`B&zF6&tf0rIg0RoUqno3wS?piinRS_v%>9CU}MPj$XhngPgFO>v^=*GU^ zbkAqt#MO|)RWVZ^NVxM5ce7z-A?w{Q1FxXNWnD7QNBBH-pJ{NNpF%p}tLmM}{eCWD zT$j$sG_M^|?(<;r`KeIjQO$PVxcX$r!Y~TeaXbskKOH3cD|ZMUFWC=Cv*ltM#E)8J z)hpVc6q--dRJ!Orc8p}e4uTp7`@j~kVRqp@Vz(?)s;4gHoMsK;iO?R^Q_FkQUFmT3 zR+m297wvzZgQS8TBo8iAHy9i0#dJ4G8e|={>rzNKH(bp~RM#orO7FBPKpyMH>TC4N-X*Xq4}xxm_0=g>%ub5I`zPe$$9YIuJ3ULv?KnhQlkIad$2cuH^mi3I zaN%HqB)qP@h|Y5=3{QnW;P~!=B(CZxpxD6!w9f(0sdTu}<2OBK?-JQ3>khThWt>Pp z^-OMP79*p)tMR8UMLaL34gU@wN8!Kp{3ou}CLLSzsvqD`C)bsI>McXVa6_qOtp0WP zDkMT^;r@P1>uE-8QBVo^dGlq@>!&#QDLxd)zTP>iheR+cJuQA+w(d=g*{O~OgIAZ& z!w>-Ss&O*GlcGGoFllT&r8d%X(N?ucC-Bdj*@X_47=~Un{$xZPL4fz~tVS@1&&;uN zSk_YRiUwJz8!;%r>DVFHqAJ=PFf8E%%yT~XrKz1KMr!~H{KMe4NWC;a4tv-(4n`JG z_z6sX5_v)wDa#nv=Fy^iMpUQbw~#jGd)4iOmv9@b1g=#)Z9hu!r`;3u3FZxp!kGja z(Kz=F0Uh{C5mnz$lRvMltozxDDG+u34G@<@gZ_8UTz>){d0|Pl{38P#4uo(X}D8R*f zywv~2lSVJob*}Soi{(DFpKS6TKvgx>dlM>^Sbr5oDR?#JMz;<{=Qn2uLKxZLz7j`= z{QLkKVS|dWD8U_2>tp(1Ks|4lWl$GDvrAbl{_4#>u(ekhJg{2;|93xxIwV+j;Nyf5 z^-W+S397#aM)T9I_w41B<^E`@f~mAzPC`~Tt8y{kqI*rY=N1*+Ra^JozJd+)10k}L z9!BgW>R7&aFSSN*#+o8Zb1DW%Pyc4IZVIh*1NzH&4N2gZCxIUf-W9{{!T%MK? zB`52g61UTSF${|DP$JfT>eD4u<@muWMAK|KZO2cHyJoZ9a_xK>LJq-Ap`}V zR8|6_1lZ7egjuxUas$l_YcEEWTv_n|vPuam&0$^($^!dN4}lSrALlUKb$X05s&HT?otFC^SS!s`)_5&H~s2(FVNHlnq}nB zyxmwEki59?ULzv^y^WCli2Hm9+6$SYP>{3Xt3l>L3OfRVa9RJtCJQGZb&RkW;Ikq0 zgFzsyl4C@G@BiAs&3=EPQ0fQ;EBNA7tFZN_Sx;r0hMTt!p#R0({AL`)W8g{5N94a5 zE9P!nl&JA!|Ai<#?oBms%g0+XCUD4CJPZCDBuVkix>nI~=@j@3@+ua(F00WAp^SfU z0sE~X#I3NTR6?t~!XyOMUSMoD!VlW^ZZi^ezRz8CC7^?GHC|GdS4@jn$^-K zv=GdllysX^aFEBRRCot1?uM2)_&CLHBfd?OYLG}cD3_wbg1BE#VkntM4(vM9RCOOA zQC5~9YMk8ua_xqB(#df#%t?-f8lO491>91>_cq6x# zOGy`IU1l4uoEm#jh^etrG=x8V&~Ir`q>>#!srKkokEj7*Kd4}D=?^+g}nHg1*im7Z%Y`$&N6xs+{B(nZv%GqQC+4?}9U%OqpS zK&=FSV*dNjX??bhJkz?E-%cY~aO9(kqrJFR-*Yx^FuyVVbdw<$-*RYGlyKNDDUV8A z*IX>O#RM&641FTSST!$E>4WQ7cHtl~cj$g-H}DDXtNf}tF_D}P@2!Mm+K7MIxgO-O4eMajpHbgZf;l3+2JeF z9S)ztwpEF3B%izw6r$L_GKsi0t&_|(e0i{tg z4zcO|n$TaE<@070m@BwmT8$$!ao;@_28M;;;VoaCbiO;-|86t7p8O+{NQ1IW%#QhM zUX=mTK?`{I(SPnlmPH6$IR=hoyd)`&QaQOs`|#$R*H4q2&!dtHHJy;ILJSZj1NFcc z$`p`|CN%~)ZrjSB8DmhE%42mQZj=t@GOvq=ak6**XD{#Zz!s9J29f+Ki~WY3=xVg5 z=S$T^Cq-Q9rUT7+t4&N4Tt1hV{e~6$F(4y0S4{_|)=zT-u zBe^$G>)$V60&)-`}t`Xf2siug0rT7KH9xia4Sg8O9D&dCw}5_b7vtr_3c>G3FAh}X;-fAcw@>jNG zh&^MXSaLoYr|s02gDgspm{w7trHh#iq+OCiS@owi?&EBQhc+#|qVqiLVuz6vP5D3t z%k?rG%3^>my?YE?UEf3Z6* zArdTkBkH^xHkdcqzTW!~h^jgE!LP=CH;B^k07=QFOM;El-V-u`=aV%L#h1^<-&oNG z!xMYMRdhx8ZBAV~NM%5=MT^V_rKQ+?)XEnfVE?ungPe5x*0Brc=+$j;1hN zXe=Y%Gr8F6%oZ*3JaGJ%RV|u}>Pkg7Z4U4Bg@vI=fG`I0%hn+q$@IJ~RqwZ^$xCa` zxiQ3jt6qV0OV6)@qoC8h1O)47(699Nwxw186yoR;CBZ2t!u0KC zTd^%tVM~h>cR#ZO2pG%F&Q&mnZc`T39TS_L@oNO?-~}7b9YSveGFu&sd2e4Ee%y*^ zxU&$itD1I*pPP?l4c?xP-+aCYo^=G2hjh2EbuXZf-R)bX%^xeWZCT{uvRt| zaDb~u&ThF6e*%kv`HF8@BbWzISpMeIcT|(c!FFP9(II|p-?UqWIs&!NaTsr|EAaDN zElJsm(-Dt!-!B6y$C6ZPdr;f8X+CBgnucIeBT=#cv5?1l{@y~0bSuJUmARzW%~OCx zo7OfR3~u&n_{(fa;t-Kvx_(sG@@Hbt$*yjiSWaD@QSRyDG}@$cT#p1K3f(GHyQJLM z!Bcob{W#D~DATF#juy4EA}PZ%=|uC9l(oR}`e=rDXCb9V0ooS{v`G~wN)ods%1&b& zLA*J$s{k`WbUD5%qtw&x`9WWI%!;(oFXJD=?2y{ zxYg5gPhp+235eo}rMdRk*p(SLcD*$vE}G#fffWT^bg)D(2>Q_a4?-g8mOa|F*4{(g zuq`2X&sbbQL6~>6iZMI^o*fKRo;}wQyztn(x zk2_na%GFt{_87yHb53OM!fmmjniWKBTHfB6B?bkyIf z59C9zEngpJWC>*hpH6;UXO;R|yG$;J%zmTL(eDHK?5bOwueby?z~{<9jlGs`HK|c+ z1^SEgPrEduLX(gVZNk0jFz?0?;`G-B(qq8wos;yoJ-%v2w^+1ja$doP{(7U-@yauM zeNl1nJ9oodsfC1Z_&*=&7i2SEIfT`4SL6W%I8o6czRRn$KXa9}KpdTE?m6{KsFJ3b zSnGwA6nVXhkmbz;zvc$sE9%xMrf+#twKcOfoYYy$S#W#WG}pChMOE<&pOUr4md3%I zg;bO&C?>O}Z}dJFw_#D^_^AEVQ}?KxM&#qhyBd%)!z=Iuw09N~_U)wu*(83Z2NNEC z{VQM55>J^>(?X#E(2}Mup)WTlI2HMiItBB9Wt41!*C|eSFr7HCa602v65rg^#r_A-|KdVM zx_su%o+%$gT?*~U7oA-}q_u=Yi9@FIYGZjOu?JOO`$OAe;qlFxXcaly^t-UE}pczp08^f0O-+*uz4U&vs70UxJqMJAiM7&21{>ILmk_7L@( z#=LbTQ{ks(wd%!3M+zoIjJkLvG9l@uMg7*HYY{^P5JZgR5IL_$=obc|k&sK2&xNQY zcPLHf{LGG4Uw4gV%SzOU^t+*DBsqk$qs=w}3L*c!ebZw-A0Y|=_<(@U_QpV4dm9^L z)_;Hg{@qW}+Qt#62edcRv-~#@L~I5$aWZmn06LnQIRL@`EX=G8!3aHTLmMk^Z(+a# zk^b-K`Ph^o2kBsDVy)-sWN!ri+;#ms6-a$c8v_gA!~Xz;BmTRu9&4p9hzz|20CAs1 zg247h7ykDz(BMBpcmtrLGW6JdeDwj){?qgd00=bzqOqAJ3mc1py^ZyQ?vN-SY6A{# zYq=Bae;p?f;91$qjts`JPhNkWj99+X+5!;%7mopW7ich%;vEsvzlp5Otc~m+Fg5kN zE^WsVF7Y$jjuhdfnXeLLe?3&$z$%^6HB$TXwhKZA0HOOTbu*9_9RT>lgZMwEk^{gx z6CXa6(oWd+{@r~B;AaLOId43f386_y)00s?cXr=*>rx>vLAzK;7%8!9@M~3^5a<6% zX|qQ-pR0LSmy5kjx(%m9Wp*|W&6yI!wck0b+npwK*^4+m{7-#p^ncs||0#Oz;#}!2 zCXs)~xt_g+(Zl%B>f3~Kg*&QfcM{!;=TySY09!hgmr2{29>$e>43(qH|@E z?N7F5wkD;=knJmpuew`#Jlq3e2q-dYP@UBUO8=A2rhm2WZ%%Kn?Hh+A3^@I(+0QcJ zo+_*=!{1)vpL*ak`TLT5NWu7r&iMbdt^dcsX#ee!kC~S@;a_(3z`1H5{>|~2ZH1;k za3h3dCEvml=)5(36C7+2S|w8nq*B!UW%e(ZS%Zrx45iK- zmRJODH&Txdc$aSC2T0J0wut}$k^qQ*`TH>dQ2x8&kF~`Q8vlu(2%O)8Ho0i*{gU$s zM3VRf*yyV4rva?=)**2LJh3gB7$1CtiXT8}DPZ={Z2Jjek}oCtHep()JzIF5y-%%+ z!3IG)S{yyRd-A)DRk9w-M>M?aaCP{zBK1%qzPZw>y0bKyc5>Hu#6I3rzgzs+;r&F^ zz|jMu1^io?A#!x}Z-(`=>NXUwW(Q?D_A5g|qy?c~6XC68R5rUhJ%FNWuqJX8aBK6R znK`EUGS+R=|Ga7!-$kIU6l1&8kb1aH@NPX*&G(L3KQ(3~MilXo!0$iUzOAonu>UNz ztpAE@9`%5APjyZnvb>Tdy&}FlUJd)Ok?GZy6!_+g?FRqn2{!NM-|Mi)fPNyR-u}KIpn)>ooR-gk@DW>%Zd2F|r?x_D zE3fLy&blI$QzWHYHV{xQrXb($*Ds8VqVLNqhfWjae)vwcX3nS+QU}c5kd|m*5(l8d zH|ro3TK^yrc6>7KGVEtD+sTaRWWf+Cu6GerufREgxvw!kUQDcVqRiV?n{AdkjWHYS z=>gN0@wPL=b**Gz5^j2hUGf%uJiA{KbI7>?&0M{6!BJtyzE^Iu9PvTnPe8zCDKQsf z;;qYD_tk4h5^wk9^kU=YPSN^eQC}3j)r-f_{c_>3y$$GBJU8xrjOZ%w-1vyE%mkJeRzV@n!lg_hZ5Kmo8ef z&QKg#DZ6SV!B;_63Z+^ypotRF%4tngzN!t26R0ndL!SP1V7aW1Veb-t^1el^op3zm6?x-U=}1^%KYp zD2;5v4^WPKRaWxPb?(2|(L5EF0|8~B$67G=<3i9usqEqy>dA%KmG9c$|Db6xp)aif z?z4kQ{6cGp-y|CuqM`-}Y{U`F0Ri=mTlHvs_2U+wk#X%< zd(UB79;K(D$C2aHrB%sD?8!Wqxh=vt%s!xfUy zK|mVv8EuTj3wi6P5%)F)76aQxYN~p3$|IhE-ZFD`dc5CqKV}$z>Ea?@jf5g;wfu^A z)c(y%5DXo`86a@x{~8R>dL0QDl0?pTv8v}m5lOAJV zuvu)&MPCDPmo_6}ko^-~=s-mGIX8P_I5G}5ENXBkV=MN^9~Rngm?~O)7g#`}{^{u^ zVdE#9ztodf{@&mX*dlaPBB#(ShKm;33E4&taTY2&pPxLb`8lNc#lUlOyX9_R!igMt zmlmT%zAI!)_v1-*(-3y4gUmBx`ayA16J}jP3ckj_3g1T>iRv{6Scgi>m;Peh;k~ji zpQAY^EZ%{$mnH&}K=pTA0Fx`BHYv}G-gQrJu$6=udZO$$8^rDYd~50k2?EY**Wy)% zOvn-E`AcQWdYuLSve0tfqGSD5@sl%{c?{*Z+>e?5U%Jd0?PY1i7us#+Ew{PpX^g)N z!csL$ii~L|hLip#QlEA&_hY!fXlL^qZ&VXl)rHNk0o^ z?tVF*aU2?+_!R@o2Jfz~GPy1g6mh_vJZ}=~-Rf6LGF0w&m zD4Sq2+WoO0i1&Q}9bWY{A<*k;)+++o$qQt+p3`p89BR<8#VbeUTwAKxa+? zqUB@*lL)>@=++^#B^Kn=RPq=<7}ZdU-LYvNe9~35UUNnJiwOi=CD+dleu2y(xIa(S z2tYkf3RuEo(!UIc_`zR6SqO`GzbJWZq4U?GM9Vb8Fi+aL&ux&-$9Jfr)UhOBwAnqC zi_fRmMD;`V%7f*DQ(MSK)w&xskAt^ zZatcArW6ATg4#I-*`k~Gi(5#%-pQT=1L24Hs80V(9Y@2XDFBPEF-J%vH38o_8Z(Ek z{nK%nFXU>TvG)P@SVR8`xOlv}6b#DoybkTq96c=&F2`J){Hj>~p$;brvsZ*4J??2A z!~I3O*(5kNha(&)Zv84w`j|$#Yw{@Y67j<1Ej9(51}i_@pE!ODcpt|K#hZ@vDPQE$ z7b7!{pMYN}79kATnvN$a)&t|}0zU%)PeBDf3~@Tu!D)GKqIuXqYh1#qa}*QdbPa)? zI2%8kh6hJa<~P1Rfa<=0$xGDBgXaLWZo;nTiq<>66FsCQ@?0$3ARq}EsUfzt{8K3b z;k9LG2XBYpWLu^F-(491F<(`)mO4e7d#Uvuf zR~MC>AmBxE)cZLCIIs8dX*bV)#80O&byBVAw8ZCyIiFH~V0#88kzP73T3HPZbekzc zeU>GzxhQ&^lJz3XjdZ2#@DeHw7X*~5+zG3?+3`o#wWxq#sB7459PHl7A)+yF9kDdU z9D4fOwjK+%zjP^t$sb~|8x6Z?#FO{(XM({ zhJ4^kO;x-uFS5l7>UJya8&?z7P6GuU2Ga{HA^nm2G2p%2p~(ds-&WcyM$4Whbn{fB zEbLpTJDxz9Nv=(Lw3h-TK){>28;0H>$kOQ}^5edKK(Joiy!q_()wElv;9-qTKR&pV zWjx=HB3LLL6vOSfWAfrZEh+X)p4SBLD^^LYiz1K(2LjR-wv2d9AR`w*xq>V8=F&kyzU$)pjdqqBFb@Ac(EXS7tb`xkNWzo?1jOm+ ziw@K=x;`iips8<`dbL4859FrEW0eqVwQNmtV*-=ROKuYv5m#8f(4emCEHV?>C;uW6 ztc=i-THwdCkY#2A0y-7FM5LYNP_*`~NB@Qnf2z76h>Ghj;oN91sQX?_i|)7Fj~T{a zy7-E%@BNy5{pO5fAyvXWJ)D4SNfOdVLtl)GI2={d$mm|~$8dkqS^z(Au8~4=%;!m+ zRK){XuGPXo;LD=M3@0JN1E)!}KXN|?yqDY9w`f;~8S@~mejrFhm05hsAd4vDId$3$ zw0J~R*}NtQNP3INnKqp-Sl?N4LPB*_=xIJyz#M>#yc&9kv@PQb~6;{{mVs(vah}#>z`hNfJ*C_?~*-(TJ(Ka z<~1xJ!e?=|)Xxp}L6;o?s&Cq8)D$Mv# zY8}9UfM@N3#>>`?J$qf8>4)crW+Oej+dCnxQGdNQioSM5gyx57*H-ykdm$D~We({nN-G zU^Hcz5yJY{|%gQ{H21gfJn8Vl5e4>lPAEO{Apzh=@PzAd8Qscx{V|ystN@2vr z$IV-6GoHm#jU+WO;qZZgJbfA@(ju>ya%on$y~iivKhU8uQn#@pqTKo^elfSB2a`DK zWOO*4nl~O$EAm7|GGYCskM@nIJ-x28g_SW@P6J;YemYoaCw6cYZ&w46%4=P{Ot^l^ z!b*YiJTa)>USY(A2TT(48W%!s$Q{VG-to%(sewYGn0`dhOo_gla~Lr_N~jiU*DQpBi{II68QXl4@5Z*$u(d^D_xYVEc`T*kCj#U9VEMq&;33Q z2wp`~|3yikVhFJ^R;kFpBsqvYafczyY&PRT=doqXuAU;?lAGd1)B6F* zzNsjg7}CYI{3iv+%m-4>j)Z81+wuuOKrsZK=J(p(>EmWMeWk_tI$Hyc(ix7{ zs3fkXqE&ppcO)>+@&uaoTK8hRWF&P~N4~{@M!ZxOphQaEgg7TD!7+gE$lWiLUv@Qa z3E{r0JJ1)v>OLl=$z zNofdb33xui8CzR_6QUv~&Tzs#PZTlt!I>uXEk!3%5{X##hX2 zjg0IaSvfg5iNMuM_`3=w>Hn)5_>a=y@67*WWx@0ZrGvZj-@iHTep35)&i1h);6r=u zzP>d-!V7Ga5P91Z8FKxruKF~6+qT@1AvFj|kf1q-0BR%hcW(7Ds4s}X8}P48{@uC$ z>w^yc`v?Dj6-D2$LFMk8_I@FOJ#C(*2au;t>3>x#U+uMuWZv zCh9z5fP4#Yj|INr{Wt$x$?%Q;^-U*8da0jLxbb+OB-Btc059Kc9KTsO#5cRAO%Ga$ za$yFI8g-j6{wNEb#*!~<4)~M-b!aNzp)nrqAA!>lRrIrwc->uwc%a3S1>fktNby*S z^dHrg|Kk3M{{jjM?aa5q6P-mJ5S+3AC9=Op+pjX;-Y=bP)GQ#T!-)o0SA@Z36{dg3 z#bd;N0DzycxA*-xdW`&T9R0U){%?Hc_c&r_Xa6&f?(18R6>I;YfL-gsQ;Q+2uvvH) z%fJXD#)dH}y|s|p9Pi+85)P-O4-Xa+{{{51PWsRFt;Y`VAL?5s7{A1C;*X(5Z}%}t z+hv7WT*q|ky}#N(uNytr8abMM(EKEm(`ZBBE?rtms~$8~Ha3~4oZkT%G?evZOBdql z{KJ>F5^y9b4$7eQE}mUlCGEA1xCk;KC?WdvoqmX-B6fIp@u$A^*x~<5C4Dxrrme&A z8`@el&-<0Q9adZKBTE{PSBKlE0`>CJz~Vt`FFzTF3Dk8ACK`0_HF}=WJDQ8qm7gQg zyTYu?E4wzne*kR`n0ada!6Q+(Kmq|f7WS$Yfb|07Frm2QXNOGr8sOo*9|efV3R$H> z@_xz*zE*B3%+9FtUKjkY4`LGa_eK@m z)J^EAGqyHLQOk9X_57apG2CCYTFvDt*d0i^(RDc=kEO)}leF)C7)*QZvA+WGb4yGku3|;Q zl|13@V)G}OPHvLmuaK`xI#RBe$xw-GLKEpCu{jML&Ae!O+nl|I-5iPk^3>= zz1(;OzZ74?=HeTw@+ssaRNpw&AH?`os#-E$ETCr|vFU-INnC94K!&4Uq|G>pB#sGm zyDokHx+|O1e))_R|10eNol>t$pN$nqNWhjiMSXV_D`kBnc$B}_xAdI`!Ar%qDpv&r zR6HFS_^!2cCVJ)&s(B`oj(mz)z_HPf&x1=>U-F@l4otF2w!7PON*2rwv6v=oyz)~n zncF+!OwZ7^jG9z`o)28`n8((V86X#cr#t2(aD`eVTi-GF_p!vTncyP${N70o8%&~l zEX79MC3=klVerer7ac7Lpts6MGzN1!_11@~XP*oNM3GW-uJA@QbQ82dQHZx6F~bz=ihQInt7=!8b+y zUhc4Xu%v}I^LplG*{TT3G?z|s1x*3=v zdfM}mztU?<6+DQPVk*z4sIjscLZ%%p$U#6+HS5XJwE1_Rg^8(>hn8UtCR9q~Q)mR* z=)|ZJT}C*;o%|$}@QR0eK0ZHRng~RLgBdhavg?FmEEP}sxd*2bhXMqAE=&jA{U+#g zKrAp^2bC0u;DcNZol$iEzH>Yg^wIWRC31-1mM?j*S@LI7Fr5-24+JGq0AcYPh1h1& z8`PyXLvUdg*Ym1F!@gS{J7~M5DDnG%0Q%*pFSe(;JT-^8h0-c_Rg)ZptlgTFBZkd}7pza)%BIA5#;6)x~teuza?zL)zk++Vav zd~^}CbP>}NFx4gp86~hp+u9|}#FP5->LRE&)&_2WCRM|S?4rAYboXL#$+&pNjSqpd@1%v4_0Haqh>x2@iKPLf8JRx(99vtS{)7+1Q%9! za3nlAP4`@g+R!HBh+it)=yu9@TZDzP|O? zLg%kV$>Q7GR0&+n)Oh!k9)|Ax4#QJTk@IuR&4~;}nJF3Ti1Rd3LJrF(d-nnNSVR8`xSzM9)8cwX zg;?f${9bdJIJ<4l4vjF%R8MrgHcn&{!tQAw!~I2@AoZdo815`zXO)pT=HdsNbmd!Z zlOVrYf0Nm5`0K{!f8zKt;C&pMTZS#^%|Y0tL%8}=MX7J1*!3Iw$ubas0dRKK6Tb#m z)HlTnF@JSA$6UL1{rriCn7UJzZ0`jQ$jzdKC#It&S00eK%@l35j|`M+A(HuCu3|$je8e&B6bO9RIs|9o@Xc%>VrC= ziECg)$!a17z;b84!yLImUxd^f=<7hv43ar#M$I^NVTfa>g#%?&blnxn3PxdDqQv$R zV=BEr!lhsgagOB#yr&@f6mKA90c6!9eBTmS?_E6 z3D4bd$VJ{8-kFx`unl>p|7DyhvLohixgRr(zjVQt+3TrGR6hM8IHSLIvQ`2EKWmB+ z-A3>^J;KDn4?6i??#FO{(NgXUq$Md?$O&WRZBSoWHF#*zSlS&8pbNm0Y`&cQl<-IH z$AI^8a~v*pt@3k(0W3y0V0eYPG3FzQ7hm8Sx@n{*CT`8rf`HbCXXo1_tleLxOHYz? z)7^R!s$w0=IIl7Qyy+vE-zmVI%na;}`R(2PGO-yfsW4)Klb@%bzxUUc z-!|i@knvI%sx%krk;MEgPXhv$X<$sl39`p97mo$asx7?4J{$INIVRFPtI0py$P-b(k7hCYL-<pR8-jA!1`|Wb-@--otdnYcqFa<<&fXlsR4elET@4ra!1#kICT=L1{S2gxU zyPI#+6Gf_ZiAmgzDLGA33$$djGV(xno}h2j-%jUru4OqSH=in%D`wEb*+Vhd)?8 zV8AQxI62*tH!#n?ESp2HW{;>Ma&jU)YB_fD@?A&K?`a>y{Y7i4uu`0TL{k2(uvNb*#imKw7Lcf25LU#Y zh1HSEsUP_#;vNIuM;rzJ@d$R`Sp+~YTq)}-))l*5LQJnU!Uu&QWWHYm@4(AoD~2B& z+tWuLspyn5m?p{Q5=WK}7^8OB@;j$kBMaAe!`e;3>3FTlih=EPWlUV7aW)Rl7-Ra~ zN@HY^CNXxo4>S1I$++{hv6mdw$fGwCn<PyQXsFOgGn5u>hwb=GTwe~waF5In*Ywr{|guOB05B@z~Pp>YZL63=(Q#Eeh!R~ zNo7(TA|j`Ua^DY9hu&j{)typsD7VN9{x+D$!s0Jo4n9C?CG$nsdMCNBGnTAr$s9u$ zgt>B*bfh{Iuze#sx|jPg++VaNZ-`O6!;_l$HZ+u(k!0vEN`B~D2tQ>e{zSGl1?>W}TlK3kgqL{%9+`Yr+J%K3I+W%i+l z@vfXn+__>Ho)jla67#>lsw$L`Dyq|*(a#Kuh&47I-4nBI)wqAwJd+*a#V(j3q{Mvb`9Sx+6jlqFKt*V1sOVtB5>oyR1Ex5Mh{?2G)>2|!;J;sj?eZ{RTd=RGat1;`&iu`~Ah z3Pe&mCdj`cB5)8}K+*Wav;`EjKTKOdvHioe1r#TTwG~uR5y7nB8IC#h&VHe(Wa5Zo zo>;dIoJ1>`V8dhq^3i|}9fwORx0TwQmt*D%TUGb^3n-j^n6`kT?uTg$C^&y;wxEL% zXx@SjMxdxTY{QD+z(MBlaVH(SlwZ8@_m^~JR^ae2vnKI%Kl%b=@ns^SS;29abSBf0 zJN$PbXlJqu z(B)aoQ3ykx$&tU%gf@@i_i4OTy4I=BU<6xFGtN~^7(?ev|jyT$#J;8MH~<2!Tp=sx*7M_3c`<}Rj`5!Jr!A4gKN_pVG- zaWw1}-0NJRknmyJ0*V|TrY)cV^I_To3f98f%DLi9N3Ff=K+ntgFe7C6$^7HdBZjo< zC>SiAGx5v|m9#2D6BsWU2cK&hat9SzlJ4~vP)zqQZ2^UT57QPz_H-6bb?GBX`<<`!|bvO5voG0QP37BT(&l35%?<_F}-VDyw z-0NJRXy;+t0t$*ArY)e@>7m*Jh(p5K^6zq~%Ifr_InrEQI>=cq=7G!DiivhofsEP_ z{*%(F>Bb^-X8dHddvCg#qZ>)k(f0ZaC|r4%wt%9VhiMBa_<3lypo0-8egqwik2KE8 z=J+o@q}4aZR1v*bm#$G6Ytg>BeDbIHX)&G4`RoK6cn0=sS$byJ5C z%Igy?z2$Ob0g;t&OB-8Nj&@teA3IE2Xo$!+28b6kHgQv_PnM5&;ZCYc%-Q$vh$nq~ zb{t>f^kLdMH=+FI65_MW4ddi8b)*?FQ?BuOexL58tGVr+?Ih-ShiMCK>PnVLL(&aT zeDo_J+KR5lTDnDoIPx1gsp+$C>xB7MSdoEisD94vL6O8$EgS0^ zyc=27-}&pG+KTngk3BBbbX(nd*^J15z@(wAK6{9>r-}BA!F)2Y$9MhkpW4d0J3{=- zHV?h7@Jy9al#=%4q-d!jzObm5om)=Sex5jo_6?zf5hy|n9gMT?Si$*$-X;v#F|KWw zwd>{S=8hVdW~!#@;@je@yPbkS5Z_u8hwoRvRNG>iKwFK$$Gj&flB;%zpO$JV9e?R#o zl*g}|=}R{l;xS(ndl;{U3Zrc%n(DriN317*sc8KAe7^?%fCy4yrv9BB%e~I6<~{q) z&x_H7SA6C@G5SLdDS|pbA3lmyy|JUt)o^6wupi-{+L{cMP1oIOpA(TbMku9YL)$|i$Uhul~0TXUN^`WTrPyPn!>*2A;Y*xA)ySZJo_DW2c!ulHs|&abPN?aA{PPIw9nX$pjZ zNZuy4E>*t{k#Lf;#)o~ce`<^F{mFzZi@vC*We;AowN;(Sio`y;i8KA0$Y2>4mBAJF zFm0)u1&AY#vy;)R`6d`i1izofK#UO}-EqD{?Hk4ak{q37>P332;GsfyfOEE|?1W4Y3T*+d0U@PSY zyUfo?JInITks#o9>fJHqt#A-oN2NG%b)FD!Gcs8oMr(tDZDHEDd#L1_ilC<>_O_yx z#Bs9<awc4>MH$u;KK8Z zo8(uC(1h@hnJSDmNWQXMKX*k41=i)6D?-eyD;;fVZ5}^3aVyX= z#qK!LVcH6%2#>zQaYURHUZcjlJ7?sA(1f z_K?PVrPP_OtItqsl3Y<#ipF1!#~nRPTY78yrnY`sx}^U3FV8QNe+YT?Zjo{&0;Tno z#IbIlG!U$<+yEhd;n5dGY4L&=6zR?m+1+q$yqTj&UX4k(6MMDUZi?Swjd5B)o!od#)w58!jNXQghz^mUPE=u=G!DnFP~0$%9Qm zlUrdHjIkB6lunJQOY#=(0)A1BoG+`to4)_4tp`tK@1qDm$Fri5 zr;%Zu8}>oRVN`;eAp-s`YhGaTy&S>yRbJvXTSOYsro8cCql75brD;P)g*i7ikf89! zyS>gmgL&+>5>H~muizw5OB^c&tQr!FI$k}^y^JLoTE5lLpGby@87*UPxk*w^jk zzGpP}NhGc;qE?IZM!FR>Yf<#3ZIGQa|8A1`r?#$A(JCK5%e7)m&~9B@vz1(D$8Ucn zj!+A?_0rK^d^_61d@oVDIvJM!j08gl31$H<6x#Tyu=H_{M>EnD+&gSksNb#p|IvFv z2O|*NLkA;JiCox^aM0=v;*g^Ju%R)aO1g(>3#j1kVcG(!%X^r%fNI>r+DeZkLVQbR zM5A=}f-8>uET`3*ZKq7b=1d%8@8jF#8>S%<4N9G7eZ5>|yW6hQ5cKTz7f>nO!?Xod zG50WS0TtFgG+WTY2t=r%gAu5rEo{TW>3o-tj4d);P#-AlR2dU4!}zFLRK;}deyt__ zRC2vX6RrqznG1baQwXWNHA$$$UgrW8zCBD^K=pAC(-u$}-NUp6RFW3fmN%0dLatdx zlhmBgu)&b=;&q1Hktg?61aGIXnvJm=t_lU@I@yz=TME^@Zv=Q_!M}n9`yeb)!61!q%9r z7Ny5oGsfs_*_IsGbKc;c5}wgs=K_^;Jxp6bRbLO&7Ep26!?XodXcg90qE!97aDUCX za+Rdv=-=J83Kr7y&h`s=;z4Sq%$&vx#U9SnW z0ez+T@M10w%AVXxqF(PnQ0ZRh0##o5>g{Uv$5k5cRkC;S@${@s8Z=++5#$QdYHC=>Yg5^Eub2ru(pCuxt^-pqO=oh z{$PBci8!8;#%9W?y}P_DVLqs;b!e6j&6ol|#UU)Zu)D|Uox9dve*u*mJxp6b6-f`% z7Es~RL$d{QCD@mn)4m?J{POee?iog)8`L~tc3nyIg1A)dZP2GfE*RBr9hqq{adS#&n3?GOC%{^j>TJXiDR3bt^z}D`2v?i zj=6!1zMh{0((S5F<@{M3`j0d8$Cv*?{^s_jfTsZ@Oa!<4YX}bds~H|n-uo+P4>g z{%VF_gVwkNx~T5E)4u8?Z&77qAj8cZDF*${i$}U7=pVMwhV^M4#%U z>TlmjeH`){)I45eU4=u&hSY@A9gXwE^-XG=9IICDk84~48Tdhs%ZK=s;inzNPTxoo z@0e{Dw-UVG|K6p+#_eiH?V29a%TSo+*X)Q}yQB`GGnKC&^=)nx{)9cLjg3v}be?wh z?9j9sY}o{4f3<1J!DsRMeePzAlU=wjQ>A(i76yb7KDIo`(h>IGYFq-D-&0O%;^TEr z)sn!07L7+s0X-ywD59N}m<~A3MBH;_8D$PKFs;3DB4&{}DJ+o(-V@E9k1VytI1Let z+Nmpr3LO(I)Po#GRL;Ce<|=!qb4*?Q`8$=by0c3HlaAy}7=9Q&xkLtU0b6-0;^ap@ zxAl_x5lSsBL{JAUo%#KTdK>!B6;*lNdy^h)LQnDoHFVEO{>zVBqLFNZz+%`N<|fZx zs;e=$t(To$*EC<%6X#FW(u1l6lG~%UJN4Mg$+6C&<~D*3$?C~%GS-~x2rkj6Ctk(5 z_@9jbr@j!7AhbY0Sly1Q9Qr8VlqWv@n|ck`_`67n8DoQd{M5BCX*9#&)Ml;Yoj^X@rY<>%$2MW-0Czy*_IF`ORn`9-RH55D7w8~ujJ;m-J@APlZmrq zW3n{EH|y0+1}VP?kVd&%4+5yO?eU97FcwX*0q>dm)tWV`8A=+?>ynNo7C7}XzLCl8 zqPS0>?go2xusm(A|9Uw8!dOJqRE{sJk6J zv5wzhdFS?Y=S+Cx`PL|$Zlc<4bi!=@yK6^gb#@@tNP$v6As_iod!wq}s*&xmzoKb` z!Km&jW$!D9ZYbwT_n<%C=Xqf(jvG?P>k&1M!vZlND%67_PT7V4kw-KNbc%w+B;;M zOz+7Z3Lci-c23<1_>?dRDgO_Y3<~+k6}@LCaV*bLw&)JjT>Ge0t2p~8b&4_`8*-WY zJL;BqJAUPsm=}$vBfy8JTx|3|*^SRhK&V3 z$i??#rUDiY0V%wi%+aYIY#rEH14xb?Yet6=UB5qiTm)5-Bvo{Uq+;+?NkXL3WQWc1 z(tFM>lp#X>7oi;vsG)m0T>M;D3_QhzXAQh&h8QDG(d}2DR~p(foVOp!DR=}U!9&#o z$?Z`)sM_FPpO~*d`uYPNzMaWH5fAbzy;FDL>T-XP>~X@)Z~YiZ5ZaH6>0F5Uj0bGv zc6_+q#V@MY&c0zAOr`I=*KjG%q^9~h~v{^{+ITj&UY9pLIxI=v~bNWRK7T7v~?D?kS8?z9Ee+4lHF zmg5eyolwQ?q4q8tN$}up@10TK^Ld{26PsJnsW_NcA*K^2M;wD*L!>mXZ1 zJVQ`N(I=4>OcilFO}G+o^r6!?cLNDR-QBg3^IkzYKChZpuqjK0{;}CriH{wIC7Nlr zg&!j8^AsT{1(0?7*;C8X#*>IF?IZ7*6W0uB=(Rd>D+riOE7CP*33`a_kE_!FrPD%` z@HK%DFh|2FFi^^`(lnw3Dln);<6W*BpgN`SHvNO4kU9-eo-9O383|h+FWmmEF#g| z4}vjOZ)cy(@h{Hbqky^_NN$f>QYNW2B4Hw;^zAYv*TI{idJG6YNo%ts&5Es5B6!0m zzquPo5bAC?K~$|RbA2;mf`|RJd|gtXh-Go{$QS~&IRkYV92{jJ>j{8tQOHNWdbyUN zThVWYZp{>5`a|pls{a%sQSjmh;&k6;Y8$>F1RDU-Kp{%veXeoLtDHV3(=dOdSxu<0 zChRKGxfqlIReGY2=vs7;JKTWsJ0VJxN&>+#@flr57Vn(SVSQW&ny77Sc3fz9ckdp8T$5YxTq&!ZUeR`ERqnxf@6j>TX5o_iXZz9;p_&RUCuryGo0ZKN7d}Pd=vOrJMI%jFjQ0bB(S~~DGcDVa7 zt&~Rzy_q$yFhP_6WsyRZR4Szisn@R_Nj;aPk8$KO9LBw?H?6XBD|7F3^Qlho%$KC{3MDruKo9{a@6%;BiU<@^*;C`P z5XU`4m+mCS(w$?vCU>{)n={g|&-sP)*3k)tqgx`4!q5i+I$Sxzf(pmnY)(3y`+6&_ zgc-Eih$N22ii4=Xv1Ff~91EX2>Ji%(!%tU0^KvrYHG z-zej7Hm~mGEL*i*-4LKBm&(GYXJD&T^-9Um+ci*i)rp=Wd_XHu%@C8>-i_fcv z6>Xz)e3jz$%PB+df{=?YfD&jSANgus7x#>&Ded;if}G=Sn`In`* z6JT{3pv+i^5+mXiq<48ex=dQ55*K0yAmtUJga*X# z&Sg2iio139NxcwNNX8lQCOvV(`5+;o?4XwCkfjq)E-UozDWH(^=ews4dfNIY9@)Kn ziktgOcKw$-+@MuBolnl}+<1hGtiRQlC+~MhnM3(izc$GSM-opb)5O@vQORTLwiy4SvYD5 zOS?B*h7dp8H@L{U$Fv6~xhAUwpBTGndF zW+|>WF3z}%zjWSJof@Yc1P9$ygClUp5&8NT1jzq{a|R*!f7z4L2O+rkuqc8cbzITD zZYCO7S-@ViC7IP)Nz*cO(Sbt0x`R~c)Vufb_q_%- z`d39}SIsZO9vpkoWIHkwLN+4EYT1@b7=3MwG`@OCc@jUPTlca`sa-9cJP1x#J8H_4 z5eEd?ZJZHAr_yJgm|u1385meto8N@#A3%30LbT#HJ!Se(-D>$wD`?Lvf4*4}MkUbg zilDErtPi>Z@Ww#*2ZDa^#s>fRjlVSy?jNgyQve~|qL}Yg%0QdYe`@n>7Zc#US$l4? z(3jw_`aF@Mz<5&v9(#j^ha{A=>s102Ms`<{-m=+&*|n`&yBy6O=mcX?pFB8TdV4@=JCAO!L|~k8udL zx}RR9kl*0z(eS5z`0-a`JBh{g(E?eO@R8+PbQmi$RfIfj8625OASEIC38)t z2gnT7;^E(*` zOT=?m=QByt&)F&}WnaE-?Yq+T>8Qk4PzDlnJ@5P#ga7AGmv3|G+R3j7YcwP%1- z+5ZJVJx&)_ebC{oh}#=6K}|$vs!jU@z~rst&9zROsIvyBjspBP@hZ+H5 z!36;hFc=DeeY*e%^n@D-!A%s~8Nz(k89`r{ZvPGd9wJumhU#rP)K3}Qq2MQXpL!_n zV3``B#Rq$bjFacykD^P{zsa0u{=uS$X5>TLT%B+ z*C(@-1WoN@Ad_3t9Q&g`LWWzRnei+v0=JUFH z9EY<=FQ+VD@ga?@?Lbd@Qj_X6H z%QnPRr?eSvznWZRqMp1|G819per99A_&AhVZLs??j|aaAt-kAV+q@&gU52T|$#~-A zM*o{E7shOh#9zj&5cbWiZZkjY!BSzWG-9pr!Er>Td1s-P;5-^Z8>3-3QxT^7FYPsa zUVQ!CYq)C4{xd6AgfGl$GSfUU!MIE>c)!f*^bMp9?-knA2O{c~baZrfUtG-;% zNr9pomGP_dt=jkY&#V@$Cwf#xH#9IF_7EB#2N5c2QJh{Z)I1f>C@TNz6yna0{QR%% zH2_U5-!UuH$n&l#=?IDA`d0`EL>bQ;OQL6BCB5Y;-Ha=HqU{2E-QfX5%fwLODM&{e zsdEEaTBPKH(Rmq8lbH(XuWx>gdrkAC31+Wh1q6cbH2^*Jd9UH1xbhQ^K=&Ge(C6D; z19X!IXrzDl;SaELa{y7G?HZU5zY0QtQd*!v@RO9*l&j~`kOtUenI+HsNnofs*Mqtw z8QavGrwy(mgc<~pf)SvU)?>HSEYK5fAVfD&YG>H?{I65mLu9|ujs!H7eAkhH4!OcG z&jW~}Ea6~dc&?BM-)lvebW^`nNqenIC+o&L$|FY0OH@=7Xg@ukQ+}?^LHE+$| zFAFWEfp3=%Z+hDqS8>XlNa4}DnsS7yrF%`XF|W^!#%G-W5j*)kH@N-_EVSqTN41Jr z7Y@07vd~BL0=DwqeCY*JZXhH#QDJ9K7$g5BGIWT%2^t3q)4zJTpeJifvQX$e*=gce{K8pU?owW0 zYT+aD47vRFzbp=f^cR8uTm(k?g21EiU6iZ}0-ikGF9N^KVwxeMQ$e6-QEg5|$EA5r ze1|ey=Bax^?%AO3l&QaQpk1syoE^5lg+;jc2s%0!_ldyIjGhyW5EHJw%{+bH_xKcP z2Y-Y#R;P7s$nzu5j|d?&{09*jsKfnk2#f?pU?5-oBm$e9Q&(iy?O+{|e>hGQ>CIha zo}>{8kkv+~P&&o*6gTSxh2V2%72tp8lFM*afUBi=sAEaLPo*Y^w7!?@2r zZMqFxAVw3qJ(+1a1{NepmfBiHF^AWTq2Jol{kvQ~%ygJRrHNE-o5nlP6JL9eb?iML zLNANyiKlN@O~m<-Sq@PP6ANkaxumE%PBK`Rddw%_g6iOWyqmt)FT-_|zqtPA z;yTI~Tpwn92&yb$p=R1IuG>$$>s8!Vqn>^$ZS2k7I*ZaqcGt2z3_Oqv0o|4op6_^bDGRA`X zm$Cf0j0N=zV;QJCsT=OT-)XaQ?PH7=EAyFK z4{$OSDCg@(e*Ra|M}a!r@5WeAp^OE{7eC2Zbdk!uEt!W;Z0qDacd9vKqmTFrSB!rL z{X!jSJHPau3^3|m#sc(&8ywotGnU7pRb7$F!}quEcwxl1%hojA)))v-WPbiZQgYQ! z6Kw13vU@xTNN{hp2_O)Lf%g3ZYl8Zwm>>No`^*^{N~Ce~;;dWX9Tyv2EwX4a{)bfU za=#2%(f$JKp9`#LUx2mGAR#B@mbYZkeu0(u3KBkhpdrVk2Z@i?r)M_%7cyFEf(S-XqBkpGo&V0_;duP)aQ!8myWVTOWJ)fXjFUF`?@XVDMUtjRS)17vc zfMI$8d~=nx1d*=R>;J(}08oef-GCJh3amiB_(@>py}tcC)om_@1^giN#wEEMnC!Kw z(`gTfh`Q2!s4w1}0;BB(R-h-`;Lv{_STn88QLij8PO0FAzuCBKacuN5ww<>VJG?+g z!QF?}A;QjSyTA%0xVP%W0f6-YqWi^QFK^Zs32H3#(AIMs2(QxIv9_%jteKbu7iOh2 z>?jp$e;Ed&|Ha@x7lYBiU~msy7DijaTPDN(VsK4Kc^m1NNxk0Y$IVX-CubwNXb4D$ zh?|TCH*Rw2Ej9hc;KN+pj`vkc`ypUP>1{7Fc=+*!9c$FNes@owIaQh{V4Kbi)wxSxLuY6mF}=>gZ{&` z;AyZ(vP>5keJ=(BJ>dq2@$(pLLG4J6*C`cnnhawrz~2LFnkcaMlCJkm+$3>;&L>H> zZ}%hu3GOX?aR6ZbcSQFK*Pu0hypCMCpioZ*w#kI6-bxzlIL`T{!QL3brc~Mz<-ZKB zG5*5!p9|L*U*Nhk`uUaF@l*%R{lfLr2@{-zjJ)~_3Hga*)E#(USHk>XHw{SRKgOVI z$o5V83)hDU*HeOpGZvi^ZVCH@Ypf-?!MVY;%1;5KWc4>ez9$_5${T!+W)2QA>v9=Q z+x!RN8mPnlZg7nOg=-*R{3KjA-W69*_A1*LvdOFuH;8ScKKn^2HlDBR-j0>Lc~DFm z7-KJ713lpehxzkxZDMoT@K1}ZtCoZp}!2-G5;d_ zpNs66Uy!|BsIuxk{JS~#{USRi(={984~>fFHt7+2rAf|t9k=GFyql22YA!J%<8!>~ zFR~vdvcHeY46u5Rj_a^bWWQETBR-H(UGIvdXDNx25175A6J?l5ygpfxY)Bd1vSJU1SFm+;s@Ycf9$u9f2K^sMZGw;!Ey8a)3K6< zjvtY|Lrpb;+cNzeMr7($TaPdCGno}YopYmAI&*(~S1P)<=YioB{b;~|tgmxb(QTlo`5J=2b zo$CK}@6rFj5M_><7c<%tBK-KnOYV@swu%PGj%-{FpPAP@L<%ne9z?=iQ3hXMSrYdru3IY3*1Xb6;A01b}cjapzssYTF* zH>nnn*M_`w7BF;a8oZmO!zr7J-=10#OTSVtueMd7 zDHs(i;QE~B2<~?9JIwu4i%%Gqs>?c6*{6KCQ({~OTaR@GI+|Whvmxm9$$V_g8~h_b z|0~fApjY}yYQYd~5+>iQa%9YCOD!STXu>%V3_3HCgV=?J+_OBu;|a!rQVZx!i9k=d z!Qn!3)?qJ71Ul^wvji8bbD$7a+eyQ*@`(@}h!R&z#(8ONV+MXG&i=~gnEf=2=#h1k zGhXWDop=cakK~k*=oCz+l4;vkoJTZP+HM?eI{S@<0O|b8LcUz;4D)FZz(RmTcMsbG zIcx7>`~8v|pyBzuksBN+xl#6u~~=ELm)xyO34VK(6kGVI8LnH}=cO z4enoZ^XHNq+%M#&{@J8pQW3*7wf~!O6UZe_PRJze?yP?ZxtZxCr?Qr(pLX!QEPNKU z!2KpbkZJ7XD=Zq8fpfH#ipu*ZHv;C)YWMlfgJhyRiUMT9XAPA+kl&w8GPJ@=W2LC! zxb@4(4bUt7B)P$Ak~WkzuOLet+#c)NhL;!iuzH~K8Rusrk2Fc|MI z2LHJjjQ0hDYn2xtquKg|>ipltV8|6VR8Q3#)ENE{44yEF%HAAV??bymho?cn9>4WY z+C`bc)`ewSD$X=!V|M=-JRQ!+R^w$EDw9Nj+!}SFokmn=OlvMVKM&b0^F->+A713} zuLKx?Ug;+>Sg?ZAX!CVSSjPkQE!Clr3i))EQ$Z`eGQ|-*c8x}MAHjHgF&O9xH#q!X zg27@gvPg4-3@2vfhvpDW!(W{ zjn{|tHSgjb)#Z`rR;vP=4-~eHv+74SIVxRNpJtxIX1MGaOd1(>@AGBRF6z4Jz9AFu zK0L19wys4-^MBldK+$V-mT&lmALZQ<25cmjc+)xyQ_5W4zD)Np4t$~vOy8pZhq@T! zGMQUo*9Z5V=l!r@$)6ct{SC=qAI#m)~mfxigZ9+4-sR3;U&wZV8%8Zx{UlX$Qs#+;@bL6%pwu6 zsc&4O$GO)jAw&zq8vXCfweBv)D4hUvG4aYy9J|-wtF*ua#`UKnbRF16wcZBRnL};> zwED#5Un=P~nQ}P^yp$4!ba9B@xpSQW(W(T5TV(SwA$!7xrg2{A)#tHjFjX9>cJKTckf1r^t}q4 z=3S;pCKu<`L?fjE!@0l^PXDM)Ec$eso-C(YyOoPiCXTRF(1qiWb;#W_!^)En6qNvh z)%)>b9snY6Frq-0wZn+sv%o@qBgx}Y=wck)teQB--l7XSI!Bi#p>4B3Q_&!zt_NTK zT|Up@|Ap)lCE*0bj;E<9i&wc8c~IxLMEh~!0@Sw_(elr5&Ldq*Iuq{}rHpAW8;3cM zW!qk;Vlo6*setG?Dd%2El1Q41o!nCGXP1oh(bPO$OOT7~SV%%LDp9Xc!gv1}gLyGt3|aP|KQZx}>3rOS)l?VyrwP?j23sf?VNktW0~i z`+a=9Z{>NYc+mx&$P;KEYnl3_%q67Jz8`LUqLlbL-{s}bnew+RM7;Ao=588r&dJ@_ z?;3@BZX;?7t|&6sD>-=zsW~Gymdp$%P@OPZTMyK5Cq#Vu&Mm%Qozkb!N8L@({TvBb z;t7L|Cgb6BD#p5}M`n6%iVWRAvGsdILwEKm_SK6=j$TQ>!E6o}qn>mV9Xx99o`Qoi zm(^&>jwL*C>GV$TSwsP)bmh~`@2og4tDn8b*7t&*xGk8l<@E}#bd2(oU@my94_

vHSrHL`4Y!))mrU|+My=J*?NCLvdFH?W^2AuF;5 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

ecf&5+ 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 Date: Wed, 21 Jul 2021 10:21:40 +0100 Subject: [PATCH 136/291] Improve error handing in tests When error is not expected in tests, assert that there is indeed no error. This commit was moved from ipld/go-car@ffcc4b77dd181ed7b2179e7e4013b70461473b22 --- ipld/car/v2/index/index_test.go | 1 + ipld/car/v2/index_gen_test.go | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index 03efbc94d6..acafffe13e 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -39,6 +39,7 @@ func TestNew(t *testing.T) { if tt.wantErr { require.Error(t, err) } else { + require.NoError(t, err) require.Equal(t, tt.want, got) } }) diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 97eb244b45..058d07e6e0 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -98,9 +98,11 @@ func TestReadOrGenerateIndex(t *testing.T) { got, err := ReadOrGenerateIndex(carFile, tt.readOpts...) if tt.wantErr { require.Error(t, err) + } else { + require.NoError(t, err) + want := tt.wantIndexer(t) + require.Equal(t, want, got) } - want := tt.wantIndexer(t) - require.Equal(t, want, got) }) } } @@ -143,9 +145,11 @@ func TestGenerateIndexFromFile(t *testing.T) { got, err := GenerateIndexFromFile(tt.carPath) if tt.wantErr { require.Error(t, err) + } else { + require.NoError(t, err) + want := tt.wantIndexer(t) + require.Equal(t, want, got) } - want := tt.wantIndexer(t) - require.Equal(t, want, got) }) } } From 985af925de0d5f2085b3cd6d75bdb8d5efdd2bad Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 23 Jul 2021 12:02:00 +0100 Subject: [PATCH 137/291] Add zero-length sections as EOF option to internal CARv1 reader The CARv2 implementation uses an internal fork of CARv1 due to upstream dependency issues captured in #104. Propagate the options set in CARv2 APIs for treatment of zero-lenth sections onto internal packages so that APIs using the internal CARv1 reader behave consistently. Use a `bool` for setting the option, since it is the only option needed in CARv1. Update tests to reflect changes. Add additional tests to internal CARv1 package and ReadOnly blockstore that assert option is propagated. Fixes #190 This commit was moved from ipld/go-car@703b88c25262f019bd1a4be7aa213fb04785e53a --- ipld/car/v2/blockstore/readonly.go | 4 +-- ipld/car/v2/blockstore/readonly_test.go | 40 ++++++++++++++++++------- ipld/car/v2/index/index_test.go | 2 +- ipld/car/v2/internal/carv1/car.go | 22 ++++++++++---- ipld/car/v2/internal/carv1/car_test.go | 34 +++++++++++++++++++++ ipld/car/v2/internal/carv1/util/util.go | 8 +++-- 6 files changed, 88 insertions(+), 22 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 6c2397f991..fcd973456e 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -162,7 +162,7 @@ func OpenReadOnly(path string, opts ...carv2.ReadOption) (*ReadOnly, error) { } func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { - bcid, data, err := util.ReadNode(internalio.NewOffsetReadSeeker(b.backing, idx)) + bcid, data, err := util.ReadNode(internalio.NewOffsetReadSeeker(b.backing, idx), b.ropts.ZeroLengthSectionAsEOF) return bcid, data, err } @@ -252,7 +252,7 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { b.mu.RLock() defer b.mu.RUnlock() - var fnSize int = -1 + fnSize := -1 var fnErr error err := b.idx.GetAll(key, func(offset uint64) bool { rdr := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 4d443c3cfb..470ace2cc6 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -34,25 +34,38 @@ func TestReadOnly(t *testing.T) { tests := []struct { name string v1OrV2path string + opts []carv2.ReadOption v1r *carv1.CarReader }{ { "OpenedWithCarV1", "../testdata/sample-v1.car", - newReaderFromV1File(t, "../testdata/sample-v1.car"), + []carv2.ReadOption{UseWholeCIDs(true)}, + newV1ReaderFromV1File(t, "../testdata/sample-v1.car", false), }, { "OpenedWithCarV2", "../testdata/sample-wrapped-v2.car", - newReaderFromV2File(t, "../testdata/sample-wrapped-v2.car"), + []carv2.ReadOption{UseWholeCIDs(true)}, + newV1ReaderFromV2File(t, "../testdata/sample-wrapped-v2.car", false), + }, + { + "OpenedWithCarV1ZeroLenSection", + "../testdata/sample-v1-with-zero-len-section.car", + []carv2.ReadOption{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + newV1ReaderFromV1File(t, "../testdata/sample-v1-with-zero-len-section.car", true), + }, + { + "OpenedWithAnotherCarV1ZeroLenSection", + "../testdata/sample-v1-with-zero-len-section2.car", + []carv2.ReadOption{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + newV1ReaderFromV1File(t, "../testdata/sample-v1-with-zero-len-section2.car", true), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - subject, err := OpenReadOnly(tt.v1OrV2path, - UseWholeCIDs(true), - ) - t.Cleanup(func() { subject.Close() }) + subject, err := OpenReadOnly(tt.v1OrV2path, tt.opts...) + t.Cleanup(func() { require.NoError(t, subject.Close()) }) require.NoError(t, err) // Assert roots match v1 payload. @@ -118,22 +131,29 @@ func TestNewReadOnlyFailsOnUnknownVersion(t *testing.T) { require.Nil(t, subject) } -func newReaderFromV1File(t *testing.T, carv1Path string) *carv1.CarReader { +func newV1ReaderFromV1File(t *testing.T, carv1Path string, zeroLenSectionAsEOF bool) *carv1.CarReader { f, err := os.Open(carv1Path) require.NoError(t, err) t.Cleanup(func() { f.Close() }) - v1r, err := carv1.NewCarReader(f) + v1r, err := newV1Reader(f, zeroLenSectionAsEOF) require.NoError(t, err) return v1r } -func newReaderFromV2File(t *testing.T, carv2Path string) *carv1.CarReader { +func newV1ReaderFromV2File(t *testing.T, carv2Path string, zeroLenSectionAsEOF bool) *carv1.CarReader { f, err := os.Open(carv2Path) require.NoError(t, err) t.Cleanup(func() { f.Close() }) v2r, err := carv2.NewReader(f) require.NoError(t, err) - v1r, err := carv1.NewCarReader(v2r.DataReader()) + v1r, err := newV1Reader(v2r.DataReader(), zeroLenSectionAsEOF) require.NoError(t, err) return v1r } + +func newV1Reader(r io.Reader, zeroLenSectionAsEOF bool) (*carv1.CarReader, error) { + if zeroLenSectionAsEOF { + return carv1.NewCarReaderWithZeroLengthSectionAsEOF(r) + } + return carv1.NewCarReader(r) +} diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index acafffe13e..bb369cfe06 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -77,7 +77,7 @@ func TestReadFrom(t *testing.T) { require.NoError(t, err) // Read the fame at offset and assert the frame corresponds to the expected block. - gotCid, gotData, err := util.ReadNode(crf) + gotCid, gotData, err := util.ReadNode(crf, false) require.NoError(t, err) gotBlock, err := blocks.NewBlockWithCid(gotData, gotCid) require.NoError(t, err) diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index 48b4f3eebd..6a25e667f5 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -57,7 +57,7 @@ func WriteCar(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.W } func ReadHeader(r io.Reader) (*CarHeader, error) { - hb, err := util.LdRead(r) + hb, err := util.LdRead(r, false) if err != nil { return nil, err } @@ -106,11 +106,20 @@ func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { } type CarReader struct { - r io.Reader - Header *CarHeader + r io.Reader + Header *CarHeader + zeroLenAsEOF bool +} + +func NewCarReaderWithZeroLengthSectionAsEOF(r io.Reader) (*CarReader, error) { + return newCarReader(r, true) } func NewCarReader(r io.Reader) (*CarReader, error) { + return newCarReader(r, false) +} + +func newCarReader(r io.Reader, zeroLenAsEOF bool) (*CarReader, error) { ch, err := ReadHeader(r) if err != nil { return nil, err @@ -125,13 +134,14 @@ func NewCarReader(r io.Reader) (*CarReader, error) { } return &CarReader{ - r: r, - Header: ch, + r: r, + Header: ch, + zeroLenAsEOF: zeroLenAsEOF, }, nil } func (cr *CarReader) Next() (blocks.Block, error) { - c, data, err := util.ReadNode(cr.r) + c, data, err := util.ReadNode(cr.r, cr.zeroLenAsEOF) if err != nil { return nil, err } diff --git a/ipld/car/v2/internal/carv1/car_test.go b/ipld/car/v2/internal/carv1/car_test.go index 70ce7d8591..71e06ee933 100644 --- a/ipld/car/v2/internal/carv1/car_test.go +++ b/ipld/car/v2/internal/carv1/car_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/hex" "io" + "os" "strings" "testing" @@ -297,3 +298,36 @@ func TestCarHeaderMatchess(t *testing.T) { }) } } + +func TestReadingZeroLengthSectionWithoutOptionSetIsError(t *testing.T) { + f, err := os.Open("../../testdata/sample-v1-with-zero-len-section.car") + require.NoError(t, err) + subject, err := NewCarReader(f) + require.NoError(t, err) + + for { + _, err := subject.Next() + if err == io.EOF { + break + } else if err != nil { + require.EqualError(t, err, "varints malformed, could not reach the end") + return + } + } + require.Fail(t, "expected error when reading file with zero section without option set") +} + +func TestReadingZeroLengthSectionWithOptionSetIsSuccess(t *testing.T) { + f, err := os.Open("../../testdata/sample-v1-with-zero-len-section.car") + require.NoError(t, err) + subject, err := NewCarReaderWithZeroLengthSectionAsEOF(f) + require.NoError(t, err) + + for { + _, err := subject.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + } +} diff --git a/ipld/car/v2/internal/carv1/util/util.go b/ipld/car/v2/internal/carv1/util/util.go index dce53003ba..6b94956137 100644 --- a/ipld/car/v2/internal/carv1/util/util.go +++ b/ipld/car/v2/internal/carv1/util/util.go @@ -15,8 +15,8 @@ type BytesReader interface { io.ByteReader } -func ReadNode(r io.Reader) (cid.Cid, []byte, error) { - data, err := LdRead(r) +func ReadNode(r io.Reader, zeroLenAsEOF bool) (cid.Cid, []byte, error) { + data, err := LdRead(r, zeroLenAsEOF) if err != nil { return cid.Cid{}, nil, err } @@ -61,7 +61,7 @@ func LdSize(d ...[]byte) uint64 { return sum + uint64(s) } -func LdRead(r io.Reader) ([]byte, error) { +func LdRead(r io.Reader, zeroLenAsEOF bool) ([]byte, error) { l, err := varint.ReadUvarint(internalio.ToByteReader(r)) if err != nil { // If the length of bytes read is non-zero when the error is EOF then signal an unclean EOF. @@ -69,6 +69,8 @@ func LdRead(r io.Reader) ([]byte, error) { return nil, io.ErrUnexpectedEOF } return nil, err + } else if l == 0 && zeroLenAsEOF { + return nil, io.EOF } buf := make([]byte, l) From 9fccb1714375cf6a6982dc4903a9937b6e4b945d Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 23 Jul 2021 10:24:42 +0100 Subject: [PATCH 138/291] Propagate async `blockstore.AllKeysChan` errors via context Implement a mechanism that allows the user to hook an error handling function to the context passed to `AllKeysChan`. The function is then notified when an error occurs during asynchronous traversal of data payload. Add tests that assert the success and failure cases when error handler is set. Fixes #177 This commit was moved from ipld/go-car@de2711c09ab3267aba84f7b8bd8ecaaeb4885f21 --- ipld/car/v2/blockstore/readonly.go | 88 +++++++++++++++++-------- ipld/car/v2/blockstore/readonly_test.go | 78 ++++++++++++++++++++++ 2 files changed, 139 insertions(+), 27 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index fcd973456e..dbf7bf61b8 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -24,26 +24,34 @@ import ( var _ blockstore.Blockstore = (*ReadOnly)(nil) -// ReadOnly provides a read-only CAR Block Store. -type ReadOnly struct { - // mu allows ReadWrite to be safe for concurrent use. - // It's in ReadOnly so that read operations also grab read locks, - // given that ReadWrite embeds ReadOnly for methods like Get and Has. - // - // The main fields guarded by the mutex are the index and the underlying writers. - // For simplicity, the entirety of the blockstore methods grab the mutex. - mu sync.RWMutex - - // The backing containing the data payload in CARv1 format. - backing io.ReaderAt - // The CARv1 content index. - idx index.Index - - // If we called carv2.NewReaderMmap, remember to close it too. - carv2Closer io.Closer - - ropts carv2.ReadOptions -} +var errZeroLengthSection = fmt.Errorf("zero-length section not allowed by default; see WithZeroLengthSectionAsEOF option") + +type ( + // ReadOnly provides a read-only CAR Block Store. + ReadOnly struct { + // mu allows ReadWrite to be safe for concurrent use. + // It's in ReadOnly so that read operations also grab read locks, + // given that ReadWrite embeds ReadOnly for methods like Get and Has. + // + // The main fields guarded by the mutex are the index and the underlying writers. + // For simplicity, the entirety of the blockstore methods grab the mutex. + mu sync.RWMutex + + // The backing containing the data payload in CARv1 format. + backing io.ReaderAt + // The CARv1 content index. + idx index.Index + + // If we called carv2.NewReaderMmap, remember to close it too. + carv2Closer io.Closer + + ropts carv2.ReadOptions + } + + contextKey string +) + +const asyncErrHandlerKey contextKey = "asyncErrorHandlerKey" // UseWholeCIDs is a read option which makes a CAR blockstore identify blocks by // whole CIDs, and not just their multihashes. The default is to use @@ -303,7 +311,19 @@ func (b *ReadOnly) PutMany([]blocks.Block) error { panic("called write method on a read-only blockstore") } -// AllKeysChan returns the list of keys in the CAR. +// WithAsyncErrorHandler returns a context with async error handling set to the given errHandler. +// Any errors that occur during asynchronous operations of AllKeysChan will be passed to the given +// handler. +func WithAsyncErrorHandler(ctx context.Context, errHandler func(error)) context.Context { + return context.WithValue(ctx, asyncErrHandlerKey, errHandler) +} + +// AllKeysChan returns the list of keys in the CAR data payload. +// If the ctx is constructed using WithAsyncErrorHandler any errors that occur during asynchronous +// retrieval of CIDs will be passed to the error handler function set in context. +// Otherwise, errors will terminate the asynchronous operation silently. +// +// See WithAsyncErrorHandler func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { // We release the lock when the channel-sending goroutine stops. b.mu.RLock() @@ -334,7 +354,10 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { for { length, err := varint.ReadUvarint(rdr) if err != nil { - return // TODO: log this error + if err != io.EOF { + maybeReportError(ctx, err) + } + return } // Null padding; by default it's an error. @@ -342,18 +365,20 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { if b.ropts.ZeroLengthSectionAsEOF { break } else { - return // TODO: log this error - // return fmt.Errorf("carv1 null padding not allowed by default; see WithZeroLegthSectionAsEOF") + maybeReportError(ctx, errZeroLengthSection) + return } } thisItemForNxt := rdr.Offset() _, c, err := cid.CidFromReader(rdr) if err != nil { - return // TODO: log this error + maybeReportError(ctx, err) + return } if _, err := rdr.Seek(thisItemForNxt+int64(length), io.SeekStart); err != nil { - return // TODO: log this error + maybeReportError(ctx, err) + return } // If we're just using multihashes, flatten to the "raw" codec. @@ -364,7 +389,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { select { case ch <- c: case <-ctx.Done(): - // TODO: log ctx error + maybeReportError(ctx, ctx.Err()) return } } @@ -372,6 +397,15 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { return ch, nil } +// maybeReportError checks if an error handler is present in context associated to the key +// asyncErrHandlerKey, and if preset it will pass the error to it. +func maybeReportError(ctx context.Context, err error) { + value := ctx.Value(asyncErrHandlerKey) + if eh, _ := value.(func(error)); eh != nil { + eh(err) + } +} + // HashOnRead is currently unimplemented; hashing on reads never happens. func (b *ReadOnly) HashOnRead(bool) { // TODO: implement before the final release? diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 470ace2cc6..ecc30e1d10 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -131,6 +131,84 @@ func TestNewReadOnlyFailsOnUnknownVersion(t *testing.T) { require.Nil(t, subject) } +func TestReadOnlyAllKeysChanErrHandlerCalledOnTimeout(t *testing.T) { + expiredCtx, cancel := context.WithTimeout(context.Background(), -time.Millisecond) + t.Cleanup(cancel) + + subject, err := OpenReadOnly("../testdata/sample-v1.car") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, subject.Close()) }) + + // Make a channel to be able to select and block on until error handler is called. + errHandlerCalled := make(chan interface{}) + expiredErrHandlingCtx := WithAsyncErrorHandler(expiredCtx, func(err error) { + defer close(errHandlerCalled) + require.EqualError(t, err, "context deadline exceeded") + }) + _, err = subject.AllKeysChan(expiredErrHandlingCtx) + require.NoError(t, err) + + // Assert error handler was called with required condition, waiting at most 3 seconds. + select { + case <-errHandlerCalled: + break + case <-time.After(time.Second * 3): + require.Fail(t, "error handler was not called within expected time window") + } +} + +func TestReadOnlyAllKeysChanErrHandlerNeverCalled(t *testing.T) { + tests := []struct { + name string + path string + errHandler func(err error) + wantCIDs []cid.Cid + }{ + { + "ReadingValidCarV1ReturnsNoErrors", + "../testdata/sample-v1.car", + func(err error) { + require.Fail(t, "unexpected call", "error handler called unexpectedly with err: %v", err) + }, + listCids(t, newV1ReaderFromV1File(t, "../testdata/sample-v1.car", false)), + }, + { + "ReadingValidCarV2ReturnsNoErrors", + "../testdata/sample-wrapped-v2.car", + func(err error) { + require.Fail(t, "unexpected call", "error handler called unexpectedly with err: %v", err) + }, + listCids(t, newV1ReaderFromV2File(t, "../testdata/sample-wrapped-v2.car", false)), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + subject, err := OpenReadOnly(tt.path, UseWholeCIDs(true)) + require.NoError(t, err) + ctx := WithAsyncErrorHandler(context.Background(), tt.errHandler) + keysChan, err := subject.AllKeysChan(ctx) + require.NoError(t, err) + var gotCids []cid.Cid + for k := range keysChan { + gotCids = append(gotCids, k) + } + require.Equal(t, tt.wantCIDs, gotCids) + }) + } +} + +func listCids(t *testing.T, v1r *carv1.CarReader) (cids []cid.Cid) { + for { + block, err := v1r.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + cids = append(cids, block.Cid()) + } + return +} + func newV1ReaderFromV1File(t *testing.T, carv1Path string, zeroLenSectionAsEOF bool) *carv1.CarReader { f, err := os.Open(carv1Path) require.NoError(t, err) From 7d6ac7be50e69cfcc7fdcc492413e8e0da913a51 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 23 Jul 2021 18:30:38 +0100 Subject: [PATCH 139/291] Implement reader block iterator over CARv1 or CARv2 Implement the mechanism to iterate over blocks in CARv1 or CARv2 via the `carv2.Reader` API. Implement tests both for the iterator and the rest of the reader API. Add test files with corrupt sections etc. Fixes #189 This commit was moved from ipld/go-car@d393a8377dcd52ed356c3f8da53499414e475622 --- ipld/car/v2/reader.go | 96 ++++++-- ipld/car/v2/reader_test.go | 231 ++++++++++++++++++ .../car/v2/testdata/sample-corrupt-pragma.car | 1 + .../sample-v1-tailing-corrupt-section.car | Bin 0 -> 479894 bytes .../sample-v2-corrupt-data-and-index.car | Bin 0 -> 521859 bytes 5 files changed, 301 insertions(+), 27 deletions(-) create mode 100644 ipld/car/v2/reader_test.go create mode 100644 ipld/car/v2/testdata/sample-corrupt-pragma.car create mode 100644 ipld/car/v2/testdata/sample-v1-tailing-corrupt-section.car create mode 100644 ipld/car/v2/testdata/sample-v2-corrupt-data-and-index.car diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 98d3715d37..b4b4d0d45b 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -4,6 +4,8 @@ import ( "fmt" "io" + blocks "github.com/ipfs/go-block-format" + internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipfs/go-cid" @@ -13,10 +15,16 @@ import ( // Reader represents a reader of CARv2. type Reader struct { - Header Header - r io.ReaderAt - roots []cid.Cid - closer io.Closer + Header Header + Version uint64 + r io.ReaderAt + roots []cid.Cid + ropts ReadOptions + // carV1Reader is lazily created, is not reusable, and exclusively used by Reader.Next. + // Note, this reader is forward-only and cannot be rewound. Once it reaches the end of the data + // payload, it will always return io.EOF. + carV1Reader *carv1.CarReader + closer io.Closer } // OpenReader is a wrapper for NewReader which opens the file at path. @@ -35,32 +43,39 @@ func OpenReader(path string, opts ...ReadOption) (*Reader, error) { return r, nil } -// NewReader constructs a new reader that reads CARv2 from the given r. -// Upon instantiation, the reader inspects the payload by reading the pragma and will return -// an error if the pragma does not represent a CARv2. +// NewReader constructs a new reader that reads either CARv1 or CARv2 from the given r. +// Upon instantiation, the reader inspects the payload and provides appropriate read operations +// for both CARv1 and CARv2. +// +// Note that any other version other than 1 or 2 will result in an error. The caller may use +// Reader.Version to get the actual version r represents. In the case where r represents a CARv1 +// Reader.Header will not be populated and is left as zero-valued. func NewReader(r io.ReaderAt, opts ...ReadOption) (*Reader, error) { cr := &Reader{ r: r, } - if err := cr.requireVersion2(); err != nil { - return nil, err + for _, o := range opts { + o(&cr.ropts) } - if err := cr.readHeader(); err != nil { + + or := internalio.NewOffsetReadSeeker(r, 0) + var err error + cr.Version, err = ReadVersion(or) + if err != nil { return nil, err } - return cr, nil -} -func (r *Reader) requireVersion2() (err error) { - or := internalio.NewOffsetReadSeeker(r.r, 0) - version, err := ReadVersion(or) - if err != nil { - return + if cr.Version != 1 && cr.Version != 2 { + return nil, fmt.Errorf("invalid car version: %d", cr.Version) } - if version != 2 { - return fmt.Errorf("invalid car version: %d", version) + + if cr.Version == 2 { + if err := cr.readV2Header(); err != nil { + return nil, err + } } - return + + return cr, nil } // Roots returns the root CIDs. @@ -77,7 +92,7 @@ func (r *Reader) Roots() ([]cid.Cid, error) { return r.roots, nil } -func (r *Reader) readHeader() (err error) { +func (r *Reader) readV2Header() (err error) { headerSection := io.NewSectionReader(r.r, PragmaSize, HeaderSize) _, err = r.Header.ReadFrom(headerSection) return @@ -94,12 +109,20 @@ type SectionReader interface { // DataReader provides a reader containing the data payload in CARv1 format. func (r *Reader) DataReader() SectionReader { - return io.NewSectionReader(r.r, int64(r.Header.DataOffset), int64(r.Header.DataSize)) + if r.Version == 2 { + return io.NewSectionReader(r.r, int64(r.Header.DataOffset), int64(r.Header.DataSize)) + } + return internalio.NewOffsetReadSeeker(r.r, 0) } -// IndexReader provides an io.Reader containing the index for the data payload. +// IndexReader provides an io.Reader containing the index for the data payload if the index is +// present. Otherwise, returns nil. +// Note, this function will always return nil if the backing payload represents a CARv1. func (r *Reader) IndexReader() io.Reader { - return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) + if r.Version == 2 { + return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) + } + return nil } // Close closes the underlying reader if it was opened by OpenReader. @@ -110,13 +133,32 @@ func (r *Reader) Close() error { return nil } +// Next reads the next block in the data payload with an io.EOF error indicating the end is reached. +// Note, this function is forward-only; once the end has been reached it will always return io.EOF. +func (r *Reader) Next() (blocks.Block, error) { + if r.carV1Reader == nil { + var err error + if r.carV1Reader, err = r.newCarV1Reader(); err != nil { + return nil, err + } + } + return r.carV1Reader.Next() +} + +func (r *Reader) newCarV1Reader() (*carv1.CarReader, error) { + dr := r.DataReader() + if r.ropts.ZeroLengthSectionAsEOF { + return carv1.NewCarReaderWithZeroLengthSectionAsEOF(dr) + } + return carv1.NewCarReader(dr) +} + // ReadVersion reads the version from the pragma. // This function accepts both CARv1 and CARv2 payloads. -func ReadVersion(r io.Reader) (version uint64, err error) { - // TODO if the user provides a reader that sufficiently satisfies what carv1.ReadHeader is asking then use that instead of wrapping every time. +func ReadVersion(r io.Reader) (uint64, error) { header, err := carv1.ReadHeader(r) if err != nil { - return + return 0, err } return header.Version, nil } diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go new file mode 100644 index 0000000000..8b5ec228f6 --- /dev/null +++ b/ipld/car/v2/reader_test.go @@ -0,0 +1,231 @@ +package car_test + +import ( + "io" + "os" + "testing" + + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/stretchr/testify/require" +) + +func TestReadVersion(t *testing.T) { + tests := []struct { + name string + path string + want uint64 + wantErr bool + }{ + { + name: "CarV1VersionIsOne", + path: "testdata/sample-v1.car", + want: 1, + }, + { + name: "CarV2VersionIsTwo", + path: "testdata/sample-rw-bs-v2.car", + want: 2, + }, + { + name: "CarV1VersionWithZeroLenSectionIsOne", + path: "testdata/sample-v1-with-zero-len-section.car", + want: 1, + }, + { + name: "AnotherCarV1VersionWithZeroLenSectionIsOne", + path: "testdata/sample-v1-with-zero-len-section2.car", + want: 1, + }, + { + name: "WrappedCarV1InCarV2VersionIsTwo", + path: "testdata/sample-wrapped-v2.car", + want: 2, + }, + { + name: "FutureVersionWithCorrectPragmaIsAsExpected", + path: "testdata/sample-rootless-v42.car", + want: 42, + }, + { + name: "CarV1WithValidHeaderButCorruptSectionIsOne", + path: "testdata/sample-v1-tailing-corrupt-section.car", + want: 1, + }, + { + name: "CarV2WithValidHeaderButCorruptSectionAndIndexIsTwo", + path: "testdata/sample-v2-corrupt-data-and-index.car", + want: 2, + }, + { + name: "CarFileWithCorruptPragmaIsError", + path: "testdata/sample-corrupt-pragma.car", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + + got, err := carv2.ReadVersion(f) + if tt.wantErr { + require.Error(t, err, "ReadVersion() error = %v, wantErr %v", err, tt.wantErr) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got, "ReadVersion() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReaderFailsOnUnknownVersion(t *testing.T) { + _, err := carv2.OpenReader("testdata/sample-rootless-v42.car") + require.EqualError(t, err, "invalid car version: 42") +} + +func TestReaderFailsOnCorruptPragma(t *testing.T) { + _, err := carv2.OpenReader("testdata/sample-corrupt-pragma.car") + require.EqualError(t, err, "unexpected EOF") +} + +func TestReader_WithCarV1Consistency(t *testing.T) { + tests := []struct { + name string + path string + zerLenAsEOF bool + }{ + { + name: "CarV1WithoutZeroLengthSection", + path: "testdata/sample-v1.car", + }, + { + name: "CarV1WithZeroLenSection", + path: "testdata/sample-v1-with-zero-len-section.car", + zerLenAsEOF: true, + }, + { + name: "AnotherCarV1WithZeroLenSection", + path: "testdata/sample-v1-with-zero-len-section2.car", + zerLenAsEOF: true, + }, + { + name: "CarV1WithZeroLenSectionWithoutOption", + path: "testdata/sample-v1-with-zero-len-section.car", + }, + { + name: "AnotherCarV1WithZeroLenSectionWithoutOption", + path: "testdata/sample-v1-with-zero-len-section2.car", + }, + { + name: "CorruptCarV1", + path: "testdata/sample-v1-tailing-corrupt-section.car", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + subject, err := carv2.OpenReader(tt.path, carv2.ZeroLengthSectionAsEOF(tt.zerLenAsEOF)) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, subject.Close()) }) + wantReader := requireNewCarV1ReaderFromV1File(t, tt.path, tt.zerLenAsEOF) + + require.Equal(t, uint64(1), subject.Version) + gotRoots, err := subject.Roots() + require.NoError(t, err) + require.Equal(t, wantReader.Header.Roots, gotRoots) + require.Nil(t, subject.IndexReader()) + + for { + gotBlock, gotErr := subject.Next() + wantBlock, wantErr := wantReader.Next() + require.Equal(t, wantBlock, gotBlock) + require.Equal(t, wantErr, gotErr) + if gotErr == io.EOF { + break + } + } + }) + } +} + +func TestReader_WithCarV2Consistency(t *testing.T) { + tests := []struct { + name string + path string + zerLenAsEOF bool + }{ + { + name: "CarV2WrappingV1", + path: "testdata/sample-wrapped-v2.car", + }, + { + name: "CarV2ProducedByBlockstore", + path: "testdata/sample-rw-bs-v2.car", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + subject, err := carv2.OpenReader(tt.path, carv2.ZeroLengthSectionAsEOF(tt.zerLenAsEOF)) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, subject.Close()) }) + wantReader := requireNewCarV1ReaderFromV2File(t, tt.path, tt.zerLenAsEOF) + + require.Equal(t, uint64(2), subject.Version) + gotRoots, err := subject.Roots() + require.NoError(t, err) + require.Equal(t, wantReader.Header.Roots, gotRoots) + + gotIndexReader := subject.IndexReader() + require.NotNil(t, gotIndexReader) + gotIndex, err := index.ReadFrom(gotIndexReader) + require.NoError(t, err) + wantIndex, err := carv2.GenerateIndex(subject.DataReader()) + require.NoError(t, err) + require.Equal(t, wantIndex, gotIndex) + + for { + gotBlock, gotErr := subject.Next() + wantBlock, wantErr := wantReader.Next() + require.Equal(t, wantBlock, gotBlock) + require.Equal(t, wantErr, gotErr) + if gotErr == io.EOF { + break + } + } + }) + } +} + +func requireNewCarV1ReaderFromV2File(t *testing.T, carV12Path string, zerLenAsEOF bool) *carv1.CarReader { + f, err := os.Open(carV12Path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + + _, err = f.Seek(carv2.PragmaSize, io.SeekStart) + require.NoError(t, err) + header := carv2.Header{} + _, err = header.ReadFrom(f) + require.NoError(t, err) + return requireNewCarV1Reader(t, io.NewSectionReader(f, int64(header.DataOffset), int64(header.DataSize)), zerLenAsEOF) +} + +func requireNewCarV1ReaderFromV1File(t *testing.T, carV1Path string, zerLenAsEOF bool) *carv1.CarReader { + f, err := os.Open(carV1Path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + return requireNewCarV1Reader(t, f, zerLenAsEOF) +} + +func requireNewCarV1Reader(t *testing.T, r io.Reader, zerLenAsEOF bool) *carv1.CarReader { + var cr *carv1.CarReader + var err error + if zerLenAsEOF { + cr, err = carv1.NewCarReaderWithZeroLengthSectionAsEOF(r) + } else { + cr, err = carv1.NewCarReader(r) + } + require.NoError(t, err) + return cr +} diff --git a/ipld/car/v2/testdata/sample-corrupt-pragma.car b/ipld/car/v2/testdata/sample-corrupt-pragma.car new file mode 100644 index 0000000000..1ac73be1ed --- /dev/null +++ b/ipld/car/v2/testdata/sample-corrupt-pragma.car @@ -0,0 +1 @@ +erootsgversio \ No newline at end of file diff --git a/ipld/car/v2/testdata/sample-v1-tailing-corrupt-section.car b/ipld/car/v2/testdata/sample-v1-tailing-corrupt-section.car new file mode 100644 index 0000000000000000000000000000000000000000..23f8308a67bb87c1f53184349a16ca07ce45f48a GIT binary patch literal 479894 zcmdSBRZtz;(yooW>%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� 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 Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ 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

hRd> zxIQPp`+1js7cr;A2G9Sz#Cb*_ZChlr4a28WU<}i_e zuBDVaJs))P(_~?NiyW>e0sKw|^8J9ysCp2xuN>~eXfYP}L34T0&Vl@^Y>%3Ak2Gc2 z=2WYe5D?fq2^Q{$PoR`I^N>b4&(CSKn(E<7H*fFteEu3mG(ELNVuKBMD)3?Hzi;RA z0KgT9_q}C4#xxE125~RlKd(fbZcWBo(;Ny0G{(BW#;AQLH}OsrS0;jtccn82Pi`8m zEVJKD9UY>B$9?dWF8`W1|0ti&FbF%@J+DSS*eufMZM#DeoKiQ_{s0Lx8W~@KF;K$S7sZKXveAYNqj>VEwyjh zygOO+_X9ij@pQ*bD0J)s$?U!zguw1HfBgabp*jpTv(UqwVa5Q9f*2)ETS{+U@ z(91|c9d%iy-5|-dTY~SupRGgCbbb;L)@nP3bQs(wvRJUogv;<93B(NK;WogNHfyx?;$PWbpeo}586beLoy^){uPPtQkWyUmd z$P0x9GS9s>RIS~?8t659XIsut5g!zI3esTO)QVSY0pzL?RS^tp7o2;#rMVYqy|@*e zfHvkiYLHiGQIL?JY%H@7m`u!Q--ErdL68Fk2>QifGy2$u5FWF68d%;$_o}+ro(L8;f zRQlq(ABEhnECtxP{tK?lY5d0~_5Gl(SFi}^g3m4pz$y^*@%E83Zv?g%A(46jxTayS zK7AnO*@ME}P-uVA)V0<3)Pf;5(p!REzqxxf=6nUn;5|-`IX@a^sSo17!$JpG^R2QK}`xx z^{s*JMhXeM>udPHaGON`n6^Yyph50n-(ocNRFGySSY%w`;&C;}3tWx2z4UA> zC(;!N_9`ZAJ!xG)-RtIwUsxGxlt~$sI}V{$Z)#vkHHs`M{0r0GG%N%OGIu`?lZj29 zf3`$-Ji{ss+T|~KnNQvVrueu&JuXcTi7nrF^<|ulh6{qI9=-z z=)o<%q>sL2=1SOT|H`s2yq;#oJF5EC=d>Q>Q=IhGv9{GttrrT`0mr7#fXOm136)>5IZ;*)F#M_@$iNDNk95?mDo+;AV^ku7jA=+UawDfSY|N3OeBf2{0HWD@7 zx3fO~OVTQ_JSBv7Y=$#McEod(Sq{+Fi!S84MOeTVJrKF0b4wp)B z{3ef03I_DC!c%0MoVLgdr%0HkhW~pQ97Kw^l7LqT-FCvR&uuOVBfzL zlh;5$s7o}Y={-9pt^R`XHlyxO?YkP- zjT(^9R%uOhZrSs2Y6nWx)5V{vKQ06{5`~= z>bfv67RUT}avLGuIriNh+!5YA@m_CKAL-fD+$SBP0(=YCO&Ato_1$P4JBRbg&$Wt%a9Uo};AOy= zkLW9zov-(9_7(RL5yCr@Y1^;RjYOw@$bupO8!DVS^ z3wjK42M;AL^jkYb9}ck;Yh*Oi{z_MDq;nqDnjZ2A)6ez$i5wlXs!uksGA*j5dvN$Z z%QuBH>z1KT%w#BTYMgZ;(YQnQ!^YmIp+{PQnBU@T(ZXB00sgGDt)l(UoMO?Okh;DE z(IK|4er36?Xv{E5oYjsDpHvxFF`bEkFwbs)n$ncKBi0=Cup0ljBJI z*_ckYK#r_*PAVxXgcu7wJ7?_BI1bZ|E?^Xl2XkH2Y4^E%f{eJp&K;@Jxq<{C3*yn+ zAcL$Y_0h-Gr#<=+yYsMKkIFDC$`#ut41*0mf|^sFi3G)HIhl-QgwV07U5RaS+jCVg zjT`dIvM`tI4>UUi|!zZX`8mL$1=8 z2uRgaaBac_7`k--bu!_0(IFe+vimP9f=QYZTpQbmQW@5VqYzT3>RW08A#2Q}K3sSd zpX}2bSrzfzHd;=1`pA(*O!yr|wFOPJ#J|6Wshfh){bv|ouzXYNxH4oaQkq~NU4heg zF54)FraK(TOA%@}3out?z@hb@itG<^zXRp_HU{Kc+6kGW-*=WAXW&P&@C z&LU_h0gv3VMDAtxVd=llHyF17-}pz_WkVc=;>EEvy%9F@cU>^if?}dV&D?(XI3MdBYt7nYA+ly-rrcsZoXiR zzUk9bD(*mPJ2$r~Q6#B-eIhlAd z&zG+{_)-(o(;5?ExiqpmDL_q};XxfytFoL2+!BU}bGH)g%LGGV_Fj<@stcD6DVCE|)zLgX zlIf~j0z^sQMndT!Vqz03irvU|_a6rruVO^&IuhM2dO72~RU06TNSfji#(294L4aX3 zh({ns1A^BV93lF59`!Qn1&v06ahLmmI&vh`Zt#FDGGbi0bQ@o7cEsrWJnLb!(+Cn4 zPLnluPN^I!h|o>fMer!+cWf;U5R)t)^TNKK6O$mC5rgpP%jWuh>y#F)O**V=js|-; zEluRNBBd#2K>Ae0U!gz=1ZW0Ww*ULWFkevc8MD3&xUhr-xTLjc(16VW@}^N5-Rss` zF)!=LGhk?)Nj!!QYH zhSf2gYdD`mH0rbfRj$ZPc{QZ}KbN?jVcT>nrlAoeregry6r&CD3fvsav2bUA?UbFu z8k+Hc+aWOwv@pcFXK2!)KgQB@A0fqS`W{|L- z#EjbD^c8EGE0@OTGTV87WUyAK)56#Mkn7VM_of#DN|7}nc}S5`F-x(yo_zD(4b(LG zguj9xkzQBoVq*xr*&2LmRgV%&Z3%y6GZtn1>z39*bhc!Li_LSUpr z4MmmU=D}U7G*0SAlv)}yF&C62@mS4{UlIRyurln@uWVWTB2w|N2y{2=vdA`dX{~e) zhPP8mnXf-4#hZH5Y$@=Rr(l|7#VJl3)@-AoRP6OA1y^P8n#BoqR4TJygu6GW$7_dM za0WC@34UP+0As1AA~d8I313LZP6DO_1iZ~I4jdFwoFDVUM~efowTJ`zXBkjz8XSn; zRda$rh4y=H{s_OtDB#gUb8d>{L4!y^zH{NSaTHX{4eq<3<|z_B(umKEsiu-C?KcW# ztp?$oCq81*ROKQ~-4j$jrZwvqzx;^FxV5(t7ez&P8 zcOcsisWJhBr6v1BaN8h8F>AJRXH@`&pMO~T?_(<+z}C+h2_JVl!?9=~;vf)^T9%sa z(EIQj3E6Ln#Y1t06Whk%U4;-)1R|rp$V7?ty33%P z2#;xYUJe$7^-HVA*S^-rPyi>-iX)2LJpeVQ$fxm}c>y>}1A z2&>OmCW&>9r1XZPb4jm}!Qxn6-#;b)B0e2W*y#km3JxQi(2XN^JK;jlI=Ee*IN1kn z=r3JcGxnN#*)2OYIfCk-ixQXZ1+`@C)R6+Pm7E|F?1YxtL*U4^ZlK$fX(noL5F$te zL}qvLQ)-2SXT!7GbLczyuSw0R$*PuWnqZz`sf$hEqOaF*kx%&cRJHikq`3CC-$aCv zW>GaLnI+X?aT!PV9C2_vaqHyrq);(R;42hx#vDUohsTh(d&8E@cA2t`FLr(}=Y-fB zSv+Yoi6YqOb`Y_UtsB}kOcfPm(x1I$WDpGtVgQE5R|xyg0da~~Uiv>GWIm+o9V8`5 zu+5J{3DhRE?o#i~<*DhF`yMIQ9JJX>UND+BfrZA8rfrwj9z0WqN2-aWj%zcyCbHsn zbm?%h)VRKMeNy9r)FZ%QA5Sa&DpPei+e0W{);d|5a3@tP%)CxxfvT4_R?r$TF3;E3 zBmq1BTJD?lhbE*p1QSWKdALjwjy+e(Ta7*1|`31W%-o{T?zKYtTs6(u_2?2o~sAGHpX6Q=EJyZ{{SG${N1M!s+ zJry{Pf0kmBL`t9a6eYm_SHCaBum-S|>bgVd**FmHL)M8dSQ-%5nXJj8r4TQn06!yt zzp=$v+1B6)y$G#y)33aUMItZ(YCI+<9hiW!%Dq;f{tJ9#!}fd#-!vwX_$-{@l!zCP z#m*ee`~^*A-~8(Rg>bg@Z?^s^^nWGW4G(+Ac3ekb%@|ehn-SSBhS-`J6g_Ufxp$H< zhreB}zebXrqizWajoOHS=i7vLKu;%e$d#E~n`u|~ny^jiohr%>F$^$EC(&6w7*|;Gv*sxa131!ZQkQk%_#oPde z3Cba563H-89FEX9uyq(P$%6k}3!AM01+n7IrdNUR6OU376sntGlM#!uGHP^`v|prl=vg>mE;;^U*AmUEud3oY}>kq6wT)#t2vs?BRyFeIsdSw zWo4jS)byoQGU9~wjXK@WDi~5IvoDAe=yx{w!3s0Q{|>KAfR%wVSLi!q(o4#UJlL^` z3uFxzHG^28Z*i0S^kn@8V5^l6{R3MpiPm3HJ9O7;ySXBUD3Ag{DHYt=;T=TgG%AZ) z>lL#R;DfEA_)tJn@b;E~c`J#C@KqM&eEf>AR^tuS0G8T7_BUPVHEesj{ zi^%;1sH!mNF0u+HoewK7b zvHk|cL=n(I^Pj}BieR(gBI3yTZSAO8)7%n)sZUXTc60WOIUY5JkJSnOfjdv8DIPNu zmPfHSl1L%6)VV*Rb#*zR^m#9y>BZsUu*_qsJQYKhU7I?avbEKo_wGgk={2Ko)bjxg z_5QvdB&&SzQ*Tq9S4p3>rJtlx1#<8KrP4tR9i4wyHUYb{5x;aCP_G+QB%Oi6X^OK? zVctD<+ybrsF8GCQ_bwwQnuXIH5I5bC*JZX$z7<1rQRN0+b&}#T%ddr4fA@01yb@s_ zu&)?gmp(T#%n+kN)MRKuT{NX8E7Q4JSBRJElB$IkcZ*sMqWt$X1WlRup<^2^4GZJG z{O5KurEncsW5+qbmoVg#6md6MU)hdjt%$H6E~%o1)n_jUh0hZ-pfV^cxB4ri8lM!u zE-y=uJukTWup{@;P59bT0h7i`>3_$?sT>CVOliCL#1c!uo;SoiQf%ny=zCS|duHK% zi@-U2D*_AUv?ztWE1cM-1%mOlF?FZW;J$AFZZK5_a8V+JD7YbNNk|ve*;-@vMTLwMDLX>Jz8>5dn93HN{xosZg~ge!1aXeZ#N#kAG~ z@qHL>w%vC89dgmr?hS;hW_K4N$d4%dlfC1g#4_`Xm-)|Jh5^x@fv`5~6CkM7mkWZx z*2t3k{=z1S z@AkbdtYSkXGxkix&Pe&-j?mY7;Fo@hk=9TIKb7A4`hbau^gtq%u=(@#!(8NoP=J@B z$Un9Wg@6D{R|TP=mE0pzn>1|P%h~+)opjxscs+3A`i`a7>cjv3_jw5o;H6Tit)r+4 zWt$Xr!-^J$J!kUBXJhQEsgSGw{K2)$^rmd|w~$X@Vp8GD=n4tLwyU0)*+KY;D~BNFuhvK-4y6kJEZ*P{T+CYPP&9(KVmz2(vA5y=C)DRiROS z5)FTE`wiih=J6d`%AH>$!$(FSAlhoP9KGB-iI|2rusI_nvMw1RN4+Izt5Ml)xh zh^mVu6$xin0&qFCUNVj!;1IPH)DL1nBU@Oa8ceroAt7vdV7V{cF4c3r6-GVsIUfnT zXt$O6u=HQ&5cGY3Ln0OEoAeE-2cX-Pl!i>HDq{8Q_ejIZ1_x@mTnW4|r?YuMq*1RF zgo`>R${wyXjZh%SV-g;ggr%o&s${R4uzF5JboXu0@VQ}g**#KxA1f6)!j1XKM?;Uf#`j)U#_h-lOb# zv9RUys2p;U=v}GI)U$1zxbNGY-uNT-aaky}h8~YX7=1UwUV$a^5bU<`U)1!13F_o& zA--C_HY=KP$5CAM?Vei21yh1w!YzVtx$xxb+Ns$2PQyD$*hA7Gr{Q_ROHzXg(FxF~ zznWpgnZF5_cPMd>Bv=L?Z{EBt#7(P=D5*>5ULy&=l~U)oNPYQF-VjU4mxO21&Iy&( zr_}2t26hHYtWWdFuu!utVCr2S&lJ!x^uKcQK|5ot>pSRR>em>AHlz5z0aci4^zf!)CgW%)|-r6RMrXI$wKMdJtO# zc5r<37sU-FS^ajh>PHm9_2m@=%4Xp4ZZ@wW``pt{4+l+%K$%+lB;S0Fv%#qTX6v6q z{})?{8!&nE!pc|ejFfp8hJ~uIiig)W;lzz%O_vMf-+MD(J+(erAe+^vRXF`U@NTg zNj;EP;ieu<@>>?vC@DPrBXLmWICkil>>{&*Jt#Tn^vn#T4X)Hi08kO3n8x$(g7Uev$@WpaWF1C@+2r6cIe&Ze1p>9{F& z@0^+_iyH$ND}1mrpgFP$a$Yjj+jG2+s|n?4>h}4R>syuP!}sAwIf*-1+ZoRTZ9fJ` zk^No3*bOl5-D;3Og9PgRP`gJw-(X|?bt>jPdmaZ&AJxHiE$JBGnto(rB z;vt8<6iRa+UeR2%)#EL}PKL99Y^v6gs>3AGTk z3M_(g*(qX=%}-;X2R#*61d#5i6s=!2H3w!Q{^aCy^CXGNEA+_{`e~4bpr4&_3$jFR z`86@(pi(=yeHsb=v;_QEOJ04J);|@z4e-2y^iP!g9FSTl(;%Y90mK?csz3EwD=Gv8 z@>Yuc0hoBD-{m%$ck3a#Z=NB~ zt4u1VAyrC$o%p6;rkIqdzaEO(?#PVCLIX>1^KEPvOgIHD#`7%w&DKAK{x7z2T#Hg` z0=&T5@Dr!PjfMt(N#q%&I^4WVarTSOuGGS}4@p(-?;FjPJxLKkmp?H8OB}0Xac71l zgo8_GNUVEp5N|YtwL&d`lalr_BsGAv-M#maR!{Mg_cb;Y4N{2%*z#3W2(coWz=T;O zZRKQKk7(BKp_)Zv{rw-df-^#?X8RVpsBBewm!ZN#VTGwS|z{R-lHxv6IE3x z^STA5-e5<)HK-UrcfV{=Mxo_YqG8dcrLZY1jYU^Gm4c7|)~&^{VtnAuLO(^R2Al11z%ox)ZKi$Wue*C}sxr&HrJIy7-*QwVCznW;Bsf;2%m*Kk;>8ymYtnXyYA>Al zWt8u$2nQI3V&uLqaNPIZN-A!41no2_!00f&#O*sMV=*yK-hjmRN(9y~(z3D^d!d9& zcsz_{QC6`qS)z4j?7^ewDi1r28)OCgV5I&fc-dGPeTMG%@BO+(w(qlM4Ce4RH98Ts z1yHkv!)n%Fn1A)a`{!sC)wlTL{b7re8BB0&m=%F7;UoyPDY44GA<`0gFwHO|yfBj3 zft2Nc-vaxksxW7!mH3cE>aG3Qtfp1)V{@6NRg4>Fc_M2rS=a-xH5KzQXSE&@*%-}n zpq&FSwvtg5#qs4Z=+z*0U z@*7>WOGPk1p9htdH8~1QTn;kii}05_2*-&%+Z}=4*ujqvvcLWk-}KV#%jYGub+dOs zfkI|Btv1bME?_Brb66%ZJgN53iLox0#_;@`t$zyrUu=~>h6#u%AE*>7Gh-LV>ka;J zCvIP4)Tb4*rplWba|_x>C7a19@5o`;wExnv>K=*QLh@tXJ|#@&<{-6zUJZBbQR?i6D!V-Kx{kr6N zaePdVDo*Bf*$%fB>9`fpB2%LV0_J|$M@(6*jk_vZt@vKA9~E&tWvkAysgXTi6WGg5 z82D2;ND@Vb!0iz^DX8hiIDo&~&e=B`jY%u}YN)r7(LoNgT{Od_>F04t0zFUz7Qt0k0g+C-+xQmW3Rc4S3 z=U0jZJ8o;`ju5X4@jfVUvA@~+r_le!Rw4|XMA(HuQF~sBz6elg6VG;aD>&?YqO(_w zus9goOsi9_qe1KiH&4a>FK+EQ9N=vCm|s?SkD~O15@4pJpfsmS+>Yy@;H~`p6m`BM58krS5?ic=c!`l;McZc=_i$f06dO@uCd$IDd zf)F3Rz?7%41F<0!iKqCi`Jj)Sm^B*}SN(Gm20BiYbYMJ@sCe7W@p^>|wm*8Xm_wkCNrcrp2{n zp#F0l{9aE{i4N(xR-WX{c@iz0`gcbfT`gYH7N0Nl;QJlpr&b$mj^0B+p^zd+GCF@> zk;WSUI=sehCKu_@&z}v`Oe!y3mxcI zx}dk3B=d1J9vI64QTsQ*n{p6m9fS|SP`lp{<~h`sgCm1=U^*m6O#DnqVRh&UrpN1K zBB)77{;>4l#})*Dtwj3qPF}WJgfpjDs9{*zpUX$Q_7~2!E^LT4xL+H{oENxgYCmvG8A8j1IXpB9OaINAL+Hta3)3`WF}j(yVLmhbFc+6YCD_^3%k(>vGVlNZggr zD?y-HJXC#y~amX z)89=mNmy$TLr-X^bR@!`vNAAD(uz&(u->EN>XUto!+m!7_#Z--BnS!FDY>>LvX0$> zposj$;eDj79=>PZsb_4~pj$HP=q+F^@YW;DVdI(NJB{NZ`djj#=^aF^NY<|*x7o-h zM9M|K+>DzqI>l%ot7S?ie&^G~ZNbNg%F~D0vMQK(N97d+6^>`_PcD_K22syPU!2s>ftWeNLrt zFcLO1WHz+%A~Rk9TN1(_FS`NV8KBJ8NyH}yNz)z_nTINx1mmTox}pCze(3KUw0NJ+~HaM^fRU7B08jJK(u36tmdL^Q!3BZqees4u($89G3n z|NpGvpF;l^TS<;3?JZ9`vg(e8y`~o529dt0q{M$&hRLo`2@9T$JPowFht?6<@!MdI zs-kMgnFj7k{CU&ft3y&{uTP{rWLziA1f<5efvF>l*X5-hRZl%lc6h%6CNDYU6lf8~ zNQ{h0sKn-$HGlK*t%$i!OggQZQ6(n+fHXaN#u(%H>4@Q29vQqHDIq;r`%rjPui*q%OF^p zeTq#pQ|M7XZw(ZA{i6ED5aXkFrZEJ+Ap)2;&hm9K8@szz3C~AQ3mvWr^}tYh|6T%Buz{yQ#gHb*Jm`=_itcF z0Cg_yW-^!khWp#7&>wZcEb&FYhNVa!ml?3}is+)m%3cd*xdBoxuYDMVemj-j(pR)#NTERo;8z7WkrsNEts(e=Qy5(K(ynxm-N#buM zHIt10zsJ8DH&7@{dZZ=PPoti4$TptTzMy%!o;+>8kBz6&{_? zObwvMfSAUZsRg2BCurA7?qSq^mUIi>;`aE#d6rmjpeJK3(+8PhY}q8kS<6FUSF-qc z&_Yo|D6ny~@V`$h|0(o;86@FM0wkc1aKjw~>NQfcCrfY=c5q47pM|JCV&+&yi7bBb z!X_)s`0|t*o9C7Z!w4wE>=S}&XTtk8HS5Cm+7ivMx2dvYU8kzR42x2JJhI{|GTB^7 z9hf`a&-z7LM=^g4l7eddYGl46uWCD2iz|=N6kZ?#5~vyqr$jM{;H-Dj{L&iu%Ux{S z*7i}NsASNSJ1`IP2-QGx>0KI;T}tDs0VKBPCqzxSU6wdZ-EW(9HVpY;48n&1VGGOS zurn3mb2L(oG|nit?lf&|7EMQkVOw-05_?7G%w`&VXhJUf-WnyNT@HGI z<5i5i_>YWlY5|BM(JjBpW`M)+LDT4tY}4yCR>(2ISyx3Xw|94gw%*#d+t>*iDntAp z*S@AGNdWhzv3k<)h!>R6zuM84=8%~H$#>~ENq!nycIpeYB-$Eo+)sF(Ff7+0`1Bxt z{?rqKNecu{~%#PLh3cNZOZm-vHb^PX%P*mvfq(sQ((ZZCkms?NH%yoxfWdvB{uxzm+UC814`DC-qo$Gp zhPY(q%ei6!QzFgW@;5W^M(0cJ28e4al?wmr_df=~LKNn>^Gu}g&muu)Sh}dNsn!u{ ze-^?vMMR*07V&Ye&w&^QiQyvSSKs2f7n&;QwuP zX^MoxEwwcxiOamOLt$O%v*h-Lo7|pgusI4LjnL(7O$Z410u}ay7>d-M1tLh_-K$Or z@gVlBTdSQw-+W#4v~aQoI1=z@Bt9(t*Zsmr^9DF1VjGryXS-x|c7k60ad84)-vKF8 zX>$+=v9VAdB5I@)2HoT*+6bS612k#M#I80FRwUb6vR}%I6#NSYQDje^f|Fwm&^peK z1xHt3aMhp6kym)vYO;1uGkY1>!#LjRXT@~XqLSQ|AavcokLk(kdU{n<=^ zml(Z-)A;3~rjhRU5by2CII^8`r$#H`8$GV5U=OmAfY4kQuJx*$P_ODn78{nwE& zVkzi&c~1G>r6RZPuOf6>U)6gxJ#x>B{c%YB!Ds3U2EK}Kf(d8mHKbHOU~n}ge?>|< z%f+@<;9z3t%J!$g0Kxl%EOSar;fzb}v0NZ&|W!Is)mj2QSKk zcO;J@Ec7sp=-Rg+F$D0SyDqAOL-8V$G*D7lXTb08fmR9=$Po@^F)-L}xLA(Qxv=1* z{6Fg6I;yH~{~A7YclV*C8v*GM>4t+K(%mW2-JpPgbax5TNJ@8yfPkcQNXK)ySA6;U z-Z6OZ^NeSV_jiVW0M0Y_+G~E+Tx)+md#_Cc!(E9C~bP15UHNkura# zYr8M)8}g?yRfSkvv@GR#OM}?zv7Eyjd|0sT?e(mLXnnOYZOkZw{>NO6IAd_d2dwY0 zp&3?drcGf#Z08LmSzD*#HmLwBAnb;X(yvl$;h(^>Olv@l&*Z(SzQrRN))#J}@>WDG zfHrL8LcrD2NOx!+*jRC)GF5YDUYS5i!`ln}?rS%W-uvWJl)=ES9W@&qMYf%B1LA~i z1aaR8w^Q!fQDW>9;Cc+!PyDX6xz%=%?x(yHKk*>D{&>-7^JU(6NaaLssqF*6{uD$< zpqQ1SyAM$VrfLk1K6f@tsU|P}7~ae;6$^n^vL$LHq2%9X>AT9PsZ3lkM$6Z~e)}rL zGJv*`_G2XFG%mJciFcwQkn_$%GUK@goZhk}qaSwIh`X-cgoWU=?KpN{XZ$j85cU0t zMnL?pM+(c)OP?={Q}&wE&@I>a67%h|Vm?QrKU*x+TlfhNvmt+XFV@2h+=<&^c5_6! zy#2R?+EF~}mBwU9NyBrMR%v*~8_%>Syg ztzI3I=b_KR?>?-tSa<;apXVXUU=Mi*ktoI`NG=*6CWI72?z{IvNl>a_7w2OPYh$aM z+ZDSq&miTC{QCC=ArcF*bc3Sd02zK3rNDEmet5Cn%KzH zJA2hSBO^26`8hF7G^jkwvmXZEsWW|j-~c{@>x$DMS(Vy6RBa-dFw2J^D-63Tk^ zNq<3Ph9GJ&;r@2Wb2KD_*TfZFihP{4QWye}GMP9`RC?4#7`XhRnFHvOVxOe=G4_TA ztkRfkRLl>hTzGitZL%O7faI5vBYAyPfVY;A0;!xxb~y*SMr~5wM&NhBMnt=L<4F^@ z4Ley^EML^WH#7HSLr%_>wc2t zRJwwGSh5m+HEL(e&Bm${g$DimU9er$K349Y%yb<^xEQ+fq0f~lrX71^bbl$K{;>E& zTt^T64+q&-wrZgSJW*Yi3z(%uw|XXvz#`ok+*(@aMaW7((Bvs?YCb`{@Tifd0>O5Nfc4_+^j)bBru!MKjmv@ae10 zI(DAmyze5z;1kltkPS(4rhwz#iL`#d*=6GRPRS^h0?H7gOgilwO3%eXQ$I*;x`CAj zu^_M7jk_$J0e!o9cpDAfj678Mt9kR$`kA1x+Mhh*V_kpmAl_PRiCFya0^vjkgqLIX z-nFMdzC?w)3@p&;Wjv?ifapKf(>>_J6#r77O%{ap?HO+PUVIA_h0VSxU|j1>Y<_;< z%U<|%#T!HkiS!J?$)R(;jSK{hNLJF}-0uyZ6ocSAW8U$e3jXZodljdb8^)yam7S?_ zO_T_x@~It0JUyuMK_R2$14xqH$6ChmRl?A3=8A*BZ-;XSQIbE+d~I5I;8t{#ndNzy z{OWn%>GP4%schA4@hrS|cwQ*w+R0zhUbbN$(EhS=M*%|nY}WDLoM`Ny%O8hssb;}C zy`xkt-|x`hNOCu`i8Y z3;~3qc#s0u^hlBh>?B=~v1Qr~|6st3e{=MuHXe?+ zCeV(1j#PI0n%!rXkfXf1c}Bk9#P94O^ncvg!a4(wtzfyG%vlYqB*bJ-iM0H+(1<)J zCgdiW&uLC6h|a^&e93g=09ZGRp(Ktee}6IE9z259-Kwmc4#}^Cx8dnXSOcxzVsnxR zk#;YXvDo{RmirFT%ef| z(NoS#bfR528P9V6l}&BO{Gblq*>zlRB2o~AMG^LrYWO0Y`gh`MnO|PS))c3JE)yb~ zJUEx`u3~*xxA_iU9me6_0FfKvnXZkT^nX2T5pk`}F`{?SSS|4};rKO@+CuKBoP#E6 z@&G4+5U)M5h65eC>W`hXeQ`ubXlc3`_&8-7Sy-`)oNTq%*Cp%7SWycs#}xT*k&d~7 zLyx0ES@k2T0T9Y|3B+aqlw_uC%VG`~p)v+~kD z3Is)Y8#!zU`b|t3V-~Bksa7&(Bdnh3%$VhaUr-K0q+cvB`lyOmU9g^2tMz?@JG_dwtJ8VP+QBKa=AR)rHQ2KOzhfpxXDRdzT=C<$Dc@4vj$O$6qj4@P= z@hLwpTo6v=)L=&Eax+7p3Co}Ns&~JPRWPe^P{E3O(n*s&wOUhy&diyN=W#bXzj#d) z=jFqnr8o7YSTFZRYzhviqd{7TF1G#0`|p?=kOcVu+RI+{GiArI@qUvtj2is`^vhu9 z2u*j=oCb=hD@2QixqLM6do%tqKZ~nobnp_h`q^SDO=rs|Opz0@Xvu5L^(JJQCrIgW zBY{2SSsB~|4@uBBGpd;`zmljGP$Cq>8|D)#*YyFVP`rsbV?Q6Q}PqgU>3h*FlFBtfzmufgf2Glyw)XKVMoPxcu&z5>O zM1qXgEeWL=eDY00%Cjn93an>xMnYgZGpTboawkNj>R8j%+iW}RZp{_4gj) z9qC1TEy#zAE80YrdRju<8??Y?ZI^)#KIJ=J{EKFF`{;_Hr;W z5#vt9ddNN*W86UzQJQ;A)UJOVHmCA4C?8`L7F{m*6R&KA(60mJfeS|acPo4!sCn3= zUK5x=>L<^a;S4@eC;~OotzR<6m(xyf!J{wr&Ia_oeXcvNYxW}ch5Res*pUOh;MR|6 zku;|6jZ z&V_tVMAr#gkbB$6lvegFFNO8>%f$wJ_FwWd(Pvq!IY{YR-0xNARpVW@w!}3*b&)4g ztlpmBXX@T2*U+#|D$TY^8&ML=g^nAHErkiV~dx(VA5jC_Uk|2inf+^f4d{W$I6ed&S+ieN+iTqNLXc zK7{xGRQK7h*JYbm&SoPC+J{u?!EExOTvD^8?wv_8Mkkz+XV@S`-^*iEv#1Fp2>P;O z_d(a_%df8zNcIkc(WUK8mM455-H3s)p~F|wIo;eC^Dp5XpxbzQlPb1-?eWM?Fg=!4 zw>0$L_?`B&zF6&tf0rIg0RoUqno3wS?piinRS_v%>9CU}MPj$XhngPgFO>v^=*GU^ zbkAqt#MO|)RWVZ^NVxM5ce7z-A?w{Q1FxXNWnD7QNBBH-pJ{NNpF%p}tLmM}{eCWD zT$j$sG_M^|?(<;r`KeIjQO$PVxcX$r!Y~TeaXbskKOH3cD|ZMUFWC=Cv*ltM#E)8J z)hpVc6q--dRJ!Orc8p}e4uTp7`@j~kVRqp@Vz(?)s;4gHoMsK;iO?R^Q_FkQUFmT3 zR+m297wvzZgQS8TBo8iAHy9i0#dJ4G8e|={>rzNKH(bp~RM#orO7FBPKpyMH>TC4N-X*Xq4}xxm_0=g>%ub5I`zPe$$9YIuJ3ULv?KnhQlkIad$2cuH^mi3I zaN%HqB)qP@h|Y5=3{QnW;P~!=B(CZxpxD6!w9f(0sdTu}<2OBK?-JQ3>khThWt>Pp z^-OMP79*p)tMR8UMLaL34gU@wN8!Kp{3ou}CLLSzsvqD`C)bsI>McXVa6_qOtp0WP zDkMT^;r@P1>uE-8QBVo^dGlq@>!&#QDLxd)zTP>iheR+cJuQA+w(d=g*{O~OgIAZ& z!w>-Ss&O*GlcGGoFllT&r8d%X(N?ucC-Bdj*@X_47=~Un{$xZPL4fz~tVS@1&&;uN zSk_YRiUwJz8!;%r>DVFHqAJ=PFf8E%%yT~XrKz1KMr!~H{KMe4NWC;a4tv-(4n`JG z_z6sX5_v)wDa#nv=Fy^iMpUQbw~#jGd)4iOmv9@b1g=#)Z9hu!r`;3u3FZxp!kGja z(Kz=F0Uh{C5mnz$lRvMltozxDDG+u34G@<@gZ_8UTz>){d0|Pl{38P#4uo(X}D8R*f zywv~2lSVJob*}Soi{(DFpKS6TKvgx>dlM>^Sbr5oDR?#JMz;<{=Qn2uLKxZLz7j`= z{QLkKVS|dWD8U_2>tp(1Ks|4lWl$GDvrAbl{_4#>u(ekhJg{2;|93xxIwV+j;Nyf5 z^-W+S397#aM)T9I_w41B<^E`@f~mAzPC`~Tt8y{kqI*rY=N1*+Ra^JozJd+)10k}L z9!BgW>R7&aFSSN*#+o8Zb1DW%Pyc4IZVIh*1NzH&4N2gZCxIUf-W9{{!T%MK? zB`52g61UTSF${|DP$JfT>eD4u<@muWMAK|KZO2cHyJoZ9a_xK>LJq-Ap`}V zR8|6_1lZ7egjuxUas$l_YcEEWTv_n|vPuam&0$^($^!dN4}lSrALlUKb$X05s&HT?otFC^SS!s`)_5&H~s2(FVNHlnq}nB zyxmwEki59?ULzv^y^WCli2Hm9+6$SYP>{3Xt3l>L3OfRVa9RJtCJQGZb&RkW;Ikq0 zgFzsyl4C@G@BiAs&3=EPQ0fQ;EBNA7tFZN_Sx;r0hMTt!p#R0({AL`)W8g{5N94a5 zE9P!nl&JA!|Ai<#?oBms%g0+XCUD4CJPZCDBuVkix>nI~=@j@3@+ua(F00WAp^SfU z0sE~X#I3NTR6?t~!XyOMUSMoD!VlW^ZZi^ezRz8CC7^?GHC|GdS4@jn$^-K zv=GdllysX^aFEBRRCot1?uM2)_&CLHBfd?OYLG}cD3_wbg1BE#VkntM4(vM9RCOOA zQC5~9YMk8ua_xqB(#df#%t?-f8lO491>91>_cq6x# zOGy`IU1l4uoEm#jh^etrG=x8V&~Ir`q>>#!srKkokEj7*Kd4}D=?^+g}nHg1*im7Z%Y`$&N6xs+{B(nZv%GqQC+4?}9U%OqpS zK&=FSV*dNjX??bhJkz?E-%cY~aO9(kqrJFR-*Yx^FuyVVbdw<$-*RYGlyKNDDUV8A z*IX>O#RM&641FTSST!$E>4WQ7cHtl~cj$g-H}DDXtNf}tF_D}P@2!Mm+K7MIxgO-O4eMajpHbgZf;l3+2JeF z9S)ztwpEF3B%izw6r$L_GKsi0t&_|(e0i{tg z4zcO|n$TaE<@070m@BwmT8$$!ao;@_28M;;;VoaCbiO;-|86t7p8O+{NQ1IW%#QhM zUX=mTK?`{I(SPnlmPH6$IR=hoyd)`&QaQOs`|#$R*H4q2&!dtHHJy;ILJSZj1NFcc z$`p`|CN%~)ZrjSB8DmhE%42mQZj=t@GOvq=ak6**XD{#Zz!s9J29f+Ki~WY3=xVg5 z=S$T^Cq-Q9rUT7+t4&N4Tt1hV{e~6$F(4y0S4{_|)=zT-u zBe^$G>)$V60&)-`}t`Xf2siug0rT7KH9xia4Sg8O9D&dCw}5_b7vtr_3c>G3FAh}X;-fAcw@>jNG zh&^MXSaLoYr|s02gDgspm{w7trHh#iq+OCiS@owi?&EBQhc+#|qVqiLVuz6vP5D3t z%k?rG%3^>my?YE?UEf3Z6* zArdTkBkH^xHkdcqzTW!~h^jgE!LP=CH;B^k07=QFOM;El-V-u`=aV%L#h1^<-&oNG z!xMYMRdhx8ZBAV~NM%5=MT^V_rKQ+?)XEnfVE?ungPe5x*0Brc=+$j;1hN zXe=Y%Gr8F6%oZ*3JaGJ%RV|u}>Pkg7Z4U4Bg@vI=fG`I0%hn+q$@IJ~RqwZ^$xCa` zxiQ3jt6qV0OV6)@qoC8h1O)47(699Nwxw186yoR;CBZ2t!u0KC zTd^%tVM~h>cR#ZO2pG%F&Q&mnZc`T39TS_L@oNO?-~}7b9YSveGFu&sd2e4Ee%y*^ zxU&$itD1I*pPP?l4c?xP-+aCYo^=G2hjh2EbuXZf-R)bX%^xeWZCT{uvRt| zaDb~u&ThF6e*%kv`HF8@BbWzISpMeIcT|(c!FFP9(II|p-?UqWIs&!NaTsr|EAaDN zElJsm(-Dt!-!B6y$C6ZPdr;f8X+CBgnucIeBT=#cv5?1l{@y~0bSuJUmARzW%~OCx zo7OfR3~u&n_{(fa;t-Kvx_(sG@@Hbt$*yjiSWaD@QSRyDG}@$cT#p1K3f(GHyQJLM z!Bcob{W#D~DATF#juy4EA}PZ%=|uC9l(oR}`e=rDXCb9V0ooS{v`G~wN)ods%1&b& zLA*J$s{k`WbUD5%qtw&x`9WWI%!;(oFXJD=?2y{ zxYg5gPhp+235eo}rMdRk*p(SLcD*$vE}G#fffWT^bg)D(2>Q_a4?-g8mOa|F*4{(g zuq`2X&sbbQL6~>6iZMI^o*fKRo;}wQyztn(x zk2_na%GFt{_87yHb53OM!fmmjniWKBTHfB6B?bkyIf z59C9zEngpJWC>*hpH6;UXO;R|yG$;J%zmTL(eDHK?5bOwueby?z~{<9jlGs`HK|c+ z1^SEgPrEduLX(gVZNk0jFz?0?;`G-B(qq8wos;yoJ-%v2w^+1ja$doP{(7U-@yauM zeNl1nJ9oodsfC1Z_&*=&7i2SEIfT`4SL6W%I8o6czRRn$KXa9}KpdTE?m6{KsFJ3b zSnGwA6nVXhkmbz;zvc$sE9%xMrf+#twKcOfoYYy$S#W#WG}pChMOE<&pOUr4md3%I zg;bO&C?>O}Z}dJFw_#D^_^AEVQ}?KxM&#qhyBd%)!z=Iuw09N~_U)wu*(83Z2NNEC z{VQM55>J^>(?X#E(2}Mup)WTlI2HMiItBB9Wt41!*C|eSFr7HCa602v65rg^#r_A-|KdVM zx_su%o+%$gT?*~U7oA-}q_u=Yi9@FIYGZjOu?JOO`$OAe;qlFxXcaly^t-UE}pczp08^f0O-+*uz4U&vs70UxJqMJAiM7&21{>ILmk_7L@( z#=LbTQ{ks(wd%!3M+zoIjJkLvG9l@uMg7*HYY{^P5JZgR5IL_$=obc|k&sK2&xNQY zcPLHf{LGG4Uw4gV%SzOU^t+*DBsqk$qs=w}3L*c!ebZw-A0Y|=_<(@U_QpV4dm9^L z)_;Hg{@qW}+Qt#62edcRv-~#@L~I5$aWZmn06LnQIRL@`EX=G8!3aHTLmMk^Z(+a# zk^b-K`Ph^o2kBsDVy)-sWN!ri+;#ms6-a$c8v_gA!~Xz;BmTRu9&4p9hzz|20CAs1 zg247h7ykDz(BMBpcmtrLGW6JdeDwj){?qgd00=bzqOqAJ3mc1py^ZyQ?vN-SY6A{# zYq=Bae;p?f;91$qjts`JPhNkWj99+X+5!;%7mopW7ich%;vEsvzlp5Otc~m+Fg5kN zE^WsVF7Y$jjuhdfnXeLLe?3&$z$%^6HB$TXwhKZA0HOOTbu*9_9RT>lgZMwEk^{gx z6CXa6(oWd+{@r~B;AaLOId43f386_y)00s?cXr=*>rx>vLAzK;7%8!9@M~3^5a<6% zX|qQ-pR0LSmy5kjx(%m9Wp*|W&6yI!wck0b+npwK*^4+m{7-#p^ncs||0#Oz;#}!2 zCXs)~xt_g+(Zl%B>f3~Kg*&QfcM{!;=TySY09!hgmr2{29>$e>43(qH|@E z?N7F5wkD;=knJmpuew`#Jlq3e2q-dYP@UBUO8=A2rhm2WZ%%Kn?Hh+A3^@I(+0QcJ zo+_*=!{1)vpL*ak`TLT5NWu7r&iMbdt^dcsX#ee!kC~S@;a_(3z`1H5{>|~2ZH1;k za3h3dCEvml=)5(36C7+2S|w8nq*B!UW%e(ZS%Zrx45iK- zmRJODH&Txdc$aSC2T0J0wut}$k^qQ*`TH>dQ2x8&kF~`Q8vlu(2%O)8Ho0i*{gU$s zM3VRf*yyV4rva?=)**2LJh3gB7$1CtiXT8}DPZ={Z2Jjek}oCtHep()JzIF5y-%%+ z!3IG)S{yyRd-A)DRk9w-M>M?aaCP{zBK1%qzPZw>y0bKyc5>Hu#6I3rzgzs+;r&F^ zz|jMu1^io?A#!x}Z-(`=>NXUwW(Q?D_A5g|qy?c~6XC68R5rUhJ%FNWuqJX8aBK6R znK`EUGS+R=|Ga7!-$kIU6l1&8kb1aH@NPX*&G(L3KQ(3~MilXo!0$iUzOAonu>UNz ztpAE@9`%5APjyZnvb>Tdy&}FlUJd)Ok?GZy6!_+g?FRqn2{!NM-|Mi)fPNyR-u}KIpn)>ooR-gk@DW>%Zd2F|r?x_D zE3fLy&blI$QzWHYHV{xQrXb($*Ds8VqVLNqhfWjae)vwcX3nS+QU}c5kd|m*5(l8d zH|ro3TK^yrc6>7KGVEtD+sTaRWWf+Cu6GerufREgxvw!kUQDcVqRiV?n{AdkjWHYS z=>gN0@wPL=b**Gz5^j2hUGf%uJiA{KbI7>?&0M{6!BJtyzE^Iu9PvTnPe8zCDKQsf z;;qYD_tk4h5^wk9^kU=YPSN^eQC}3j)r-f_{c_>3y$$GBJU8xrjOZ%w-1vyE%mkJeRzV@n!lg_hZ5Kmo8ef z&QKg#DZ6SV!B;_63Z+^ypotRF%4tngzN!t26R0ndL!SP1V7aW1Veb-t^1el^op3zm6?x-U=}1^%KYp zD2;5v4^WPKRaWxPb?(2|(L5EF0|8~B$67G=<3i9usqEqy>dA%KmG9c$|Db6xp)aif z?z4kQ{6cGp-y|CuqM`-}Y{U`F0Ri=mTlHvs_2U+wk#X%< zd(UB79;K(D$C2aHrB%sD?8!Wqxh=vt%s!xfUy zK|mVv8EuTj3wi6P5%)F)76aQxYN~p3$|IhE-ZFD`dc5CqKV}$z>Ea?@jf5g;wfu^A z)c(y%5DXo`86a@x{~8R>dL0QDl0?pTv8v}m5lOAJV zuvu)&MPCDPmo_6}ko^-~=s-mGIX8P_I5G}5ENXBkV=MN^9~Rngm?~O)7g#`}{^{u^ zVdE#9ztodf{@&mX*dlaPBB#(ShKm;33E4&taTY2&pPxLb`8lNc#lUlOyX9_R!igMt zmlmT%zAI!)_v1-*(-3y4gUmBx`ayA16J}jP3ckj_3g1T>iRv{6Scgi>m;Peh;k~ji zpQAY^EZ%{$mnH&}K=pTA0Fx`BHYv}G-gQrJu$6=udZO$$8^rDYd~50k2?EY**Wy)% zOvn-E`AcQWdYuLSve0tfqGSD5@sl%{c?{*Z+>e?5U%Jd0?PY1i7us#+Ew{PpX^g)N z!csL$ii~L|hLip#QlEA&_hY!fXlL^qZ&VXl)rHNk0o^ z?tVF*aU2?+_!R@o2Jfz~GPy1g6mh_vJZ}=~-Rf6LGF0w&m zD4Sq2+WoO0i1&Q}9bWY{A<*k;)+++o$qQt+p3`p89BR<8#VbeUTwAKxa+? zqUB@*lL)>@=++^#B^Kn=RPq=<7}ZdU-LYvNe9~35UUNnJiwOi=CD+dleu2y(xIa(S z2tYkf3RuEo(!UIc_`zR6SqO`GzbJWZq4U?GM9Vb8Fi+aL&ux&-$9Jfr)UhOBwAnqC zi_fRmMD;`V%7f*DQ(MSK)w&xskAt^ zZatcArW6ATg4#I-*`k~Gi(5#%-pQT=1L24Hs80V(9Y@2XDFBPEF-J%vH38o_8Z(Ek z{nK%nFXU>TvG)P@SVR8`xOlv}6b#DoybkTq96c=&F2`J){Hj>~p$;brvsZ*4J??2A z!~I3O*(5kNha(&)Zv84w`j|$#Yw{@Y67j<1Ej9(51}i_@pE!ODcpt|K#hZ@vDPQE$ z7b7!{pMYN}79kATnvN$a)&t|}0zU%)PeBDf3~@Tu!D)GKqIuXqYh1#qa}*QdbPa)? zI2%8kh6hJa<~P1Rfa<=0$xGDBgXaLWZo;nTiq<>66FsCQ@?0$3ARq}EsUfzt{8K3b z;k9LG2XBYpWLu^F-(491F<(`)mO4e7d#Uvuf zR~MC>AmBxE)cZLCIIs8dX*bV)#80O&byBVAw8ZCyIiFH~V0#88kzP73T3HPZbekzc zeU>GzxhQ&^lJz3XjdZ2#@DeHw7X*~5+zG3?+3`o#wWxq#sB7459PHl7A)+yF9kDdU z9D4fOwjK+%zjP^t$sb~|8x6Z?#FO{(XM({ zhJ4^kO;x-uFS5l7>UJya8&?z7P6GuU2Ga{HA^nm2G2p%2p~(ds-&WcyM$4Whbn{fB zEbLpTJDxz9Nv=(Lw3h-TK){>28;0H>$kOQ}^5edKK(Joiy!q_()wElv;9-qTKR&pV zWjx=HB3LLL6vOSfWAfrZEh+X)p4SBLD^^LYiz1K(2LjR-wv2d9AR`w*xq>V8=F&kyzU$)pjdqqBFb@Ac(EXS7tb`xkNWzo?1jOm+ ziw@K=x;`iips8<`dbL4859FrEW0eqVwQNmtV*-=ROKuYv5m#8f(4emCEHV?>C;uW6 ztc=i-THwdCkY#2A0y-7FM5LYNP_*`~NB@Qnf2z76h>Ghj;oN91sQX?_i|)7Fj~T{a zy7-E%@BNy5{pO5fAyvXWJ)D4SNfOdVLtl)GI2={d$mm|~$8dkqS^z(Au8~4=%;!m+ zRK){XuGPXo;LD=M3@0JN1E)!}KXN|?yqDY9w`f;~8S@~mejrFhm05hsAd4vDId$3$ zw0J~R*}NtQNP3INnKqp-Sl?N4LPB*_=xIJyz#M>#yc&9kv@PQb~6;{{mVs(vah}#>z`hNfJ*C_?~*-(TJ(Ka z<~1xJ!e?=|)Xxp}L6;o?s&Cq8)D$Mv# zY8}9UfM@N3#>>`?J$qf8>4)crW+Oej+dCnxQGdNQioSM5gyx57*H-ykdm$D~We({nN-G zU^Hcz5yJY{|%gQ{H21gfJn8Vl5e4>lPAEO{Apzh=@PzAd8Qscx{V|ystN@2vr z$IV-6GoHm#jU+WO;qZZgJbfA@(ju>ya%on$y~iivKhU8uQn#@pqTKo^elfSB2a`DK zWOO*4nl~O$EAm7|GGYCskM@nIJ-x28g_SW@P6J;YemYoaCw6cYZ&w46%4=P{Ot^l^ z!b*YiJTa)>USY(A2TT(48W%!s$Q{VG-to%(sewYGn0`dhOo_gla~Lr_N~jiU*DQpBi{II68QXl4@5Z*$u(d^D_xYVEc`T*kCj#U9VEMq&;33Q z2wp`~|3yikVhFJ^R;kFpBsqvYafczyY&PRT=doqXuAU;?lAGd1)B6F* zzNsjg7}CYI{3iv+%m-4>j)Z81+wuuOKrsZK=J(p(>EmWMeWk_tI$Hyc(ix7{ zs3fkXqE&ppcO)>+@&uaoTK8hRWF&P~N4~{@M!ZxOphQaEgg7TD!7+gE$lWiLUv@Qa z3E{r0JJ1)v>OLl=$z zNofdb33xui8CzR_6QUv~&Tzs#PZTlt!I>uXEk!3%5{X##hX2 zjg0IaSvfg5iNMuM_`3=w>Hn)5_>a=y@67*WWx@0ZrGvZj-@iHTep35)&i1h);6r=u zzP>d-!V7Ga5P91Z8FKxruKF~6+qT@1AvFj|kf1q-0BR%hcW(7Ds4s}X8}P48{@uC$ z>w^yc`v?Dj6-D2$LFMk8_I@FOJ#C(*2au;t>3>x#U+uMuWZv zCh9z5fP4#Yj|INr{Wt$x$?%Q;^-U*8da0jLxbb+OB-Btc059Kc9KTsO#5cRAO%Ga$ za$yFI8g-j6{wNEb#*!~<4)~M-b!aNzp)nrqAA!>lRrIrwc->uwc%a3S1>fktNby*S z^dHrg|Kk3M{{jjM?aa5q6P-mJ5S+3AC9=Op+pjX;-Y=bP)GQ#T!-)o0SA@Z36{dg3 z#bd;N0DzycxA*-xdW`&T9R0U){%?Hc_c&r_Xa6&f?(18R6>I;YfL-gsQ;Q+2uvvH) z%fJXD#)dH}y|s|p9Pi+85)P-O4-Xa+{{{51PWsRFt;Y`VAL?5s7{A1C;*X(5Z}%}t z+hv7WT*q|ky}#N(uNytr8abMM(EKEm(`ZBBE?rtms~$8~Ha3~4oZkT%G?evZOBdql z{KJ>F5^y9b4$7eQE}mUlCGEA1xCk;KC?WdvoqmX-B6fIp@u$A^*x~<5C4Dxrrme&A z8`@el&-<0Q9adZKBTE{PSBKlE0`>CJz~Vt`FFzTF3Dk8ACK`0_HF}=WJDQ8qm7gQg zyTYu?E4wzne*kR`n0ada!6Q+(Kmq|f7WS$Yfb|07Frm2QXNOGr8sOo*9|efV3R$H> z@_xz*zE*B3%+9FtUKjkY4`LGa_eK@m z)J^EAGqyHLQOk9X_57apG2CCYTFvDt*d0i^(RDc=kEO)}leF)C7)*QZvA+WGb4yGku3|;Q zl|13@V)G}OPHvLmuaK`xI#RBe$xw-GLKEpCu{jML&Ae!O+nl|I-5iPk^3>= zz1(;OzZ74?=HeTw@+ssaRNpw&AH?`os#-E$ETCr|vFU-INnC94K!&4Uq|G>pB#sGm zyDokHx+|O1e))_R|10eNol>t$pN$nqNWhjiMSXV_D`kBnc$B}_xAdI`!Ar%qDpv&r zR6HFS_^!2cCVJ)&s(B`oj(mz)z_HPf&x1=>U-F@l4otF2w!7PON*2rwv6v=oyz)~n zncF+!OwZ7^jG9z`o)28`n8((V86X#cr#t2(aD`eVTi-GF_p!vTncyP${N70o8%&~l zEX79MC3=klVerer7ac7Lpts6MGzN1!_11@~XP*oNM3GW-uJA@QbQ82dQHZx6F~bz=ihQInt7=!8b+y zUhc4Xu%v}I^LplG*{TT3G?z|s1x*3=v zdfM}mztU?<6+DQPVk*z4sIjscLZ%%p$U#6+HS5XJwE1_Rg^8(>hn8UtCR9q~Q)mR* z=)|ZJT}C*;o%|$}@QR0eK0ZHRng~RLgBdhavg?FmEEP}sxd*2bhXMqAE=&jA{U+#g zKrAp^2bC0u;DcNZol$iEzH>Yg^wIWRC31-1mM?j*S@LI7Fr5-24+JGq0AcYPh1h1& z8`PyXLvUdg*Ym1F!@gS{J7~M5DDnG%0Q%*pFSe(;JT-^8h0-c_Rg)ZptlgTFBZkd}7pza)%BIA5#;6)x~teuza?zL)zk++Vav zd~^}CbP>}NFx4gp86~hp+u9|}#FP5->LRE&)&_2WCRM|S?4rAYboXL#$+&pNjSqpd@1%v4_0Haqh>x2@iKPLf8JRx(99vtS{)7+1Q%9! za3nlAP4`@g+R!HBh+it)=yu9@TZDzP|O? zLg%kV$>Q7GR0&+n)Oh!k9)|Ax4#QJTk@IuR&4~;}nJF3Ti1Rd3LJrF(d-nnNSVR8`xSzM9)8cwX zg;?f${9bdJIJ<4l4vjF%R8MrgHcn&{!tQAw!~I2@AoZdo815`zXO)pT=HdsNbmd!Z zlOVrYf0Nm5`0K{!f8zKt;C&pMTZS#^%|Y0tL%8}=MX7J1*!3Iw$ubas0dRKK6Tb#m z)HlTnF@JSA$6UL1{rriCn7UJzZ0`jQ$jzdKC#It&S00eK%@l35j|`M+A(HuCu3|$je8e&B6bO9RIs|9o@Xc%>VrC= ziECg)$!a17z;b84!yLImUxd^f=<7hv43ar#M$I^NVTfa>g#%?&blnxn3PxdDqQv$R zV=BEr!lhsgagOB#yr&@f6mKA90c6!9eBTmS?_E6 z3D4bd$VJ{8-kFx`unl>p|7DyhvLohixgRr(zjVQt+3TrGR6hM8IHSLIvQ`2EKWmB+ z-A3>^J;KDn4?6i??#FO{(NgXUq$Md?$O&WRZBSoWHF#*zSlS&8pbNm0Y`&cQl<-IH z$AI^8a~v*pt@3k(0W3y0V0eYPG3FzQ7hm8Sx@n{*CT`8rf`HbCXXo1_tleLxOHYz? z)7^R!s$w0=IIl7Qyy+vE-zmVI%na;}`R(2PGO-yfsW4)Klb@%bzxUUc z-!|i@knvI%sx%krk;MEgPXhv$X<$sl39`p97mo$asx7?4J{$INIVRFPtI0py$P-b(k7hCYL-<pR8-jA!1`|Wb-@--otdnYcqFa<<&fXlsR4elET@4ra!1#kICT=L1{S2gxU zyPI#+6Gf_ZiAmgzDLGA33$$djGV(xno}h2j-%jUru4OqSH=in%D`wEb*+Vhd)?8 zV8AQxI62*tH!#n?ESp2HW{;>Ma&jU)YB_fD@?A&K?`a>y{Y7i4uu`0TL{k2(uvNb*#imKw7Lcf25LU#Y zh1HSEsUP_#;vNIuM;rzJ@d$R`Sp+~YTq)}-))l*5LQJnU!Uu&QWWHYm@4(AoD~2B& z+tWuLspyn5m?p{Q5=WK}7^8OB@;j$kBMaAe!`e;3>3FTlih=EPWlUV7aW)Rl7-Ra~ zN@HY^CNXxo4>S1I$++{hv6mdw$fGwCn<PyQXsFOgGn5u>hwb=GTwe~waF5In*Ywr{|guOB05B@z~Pp>YZL63=(Q#Eeh!R~ zNo7(TA|j`Ua^DY9hu&j{)typsD7VN9{x+D$!s0Jo4n9C?CG$nsdMCNBGnTAr$s9u$ zgt>B*bfh{Iuze#sx|jPg++VaNZ-`O6!;_l$HZ+u(k!0vEN`B~D2tQ>e{zSGl1?>W}TlK3kgqL{%9+`Yr+J%K3I+W%i+l z@vfXn+__>Ho)jla67#>lsw$L`Dyq|*(a#Kuh&47I-4nBI)wqAwJd+*a#V(j3q{Mvb`9Sx+6jlqFKt*V1sOVtB5>oyR1Ex5Mh{?2G)>2|!;J;sj?eZ{RTd=RGat1;`&iu`~Ah z3Pe&mCdj`cB5)8}K+*Wav;`EjKTKOdvHioe1r#TTwG~uR5y7nB8IC#h&VHe(Wa5Zo zo>;dIoJ1>`V8dhq^3i|}9fwORx0TwQmt*D%TUGb^3n-j^n6`kT?uTg$C^&y;wxEL% zXx@SjMxdxTY{QD+z(MBlaVH(SlwZ8@_m^~JR^ae2vnKI%Kl%b=@ns^SS;29abSBf0 zJN$PbXlJqu z(B)aoQ3ykx$&tU%gf@@i_i4OTy4I=BU<6xFGtN~^7(?ev|jyT$#J;8MH~<2!Tp=sx*7M_3c`<}Rj`5!Jr!A4gKN_pVG- zaWw1}-0NJRknmyJ0*V|TrY)cV^I_To3f98f%DLi9N3Ff=K+ntgFe7C6$^7HdBZjo< zC>SiAGx5v|m9#2D6BsWU2cK&hat9SzlJ4~vP)zqQZ2^UT57QPz_H-6bb?GBX`<<`!|bvO5voG0QP37BT(&l35%?<_F}-VDyw z-0NJRXy;+t0t$*ArY)e@>7m*Jh(p5K^6zq~%Ifr_InrEQI>=cq=7G!DiivhofsEP_ z{*%(F>Bb^-X8dHddvCg#qZ>)k(f0ZaC|r4%wt%9VhiMBa_<3lypo0-8egqwik2KE8 z=J+o@q}4aZR1v*bm#$G6Ytg>BeDbIHX)&G4`RoK6cn0=sS$byJ5C z%Igy?z2$Ob0g;t&OB-8Nj&@teA3IE2Xo$!+28b6kHgQv_PnM5&;ZCYc%-Q$vh$nq~ zb{t>f^kLdMH=+FI65_MW4ddi8b)*?FQ?BuOexL58tGVr+?Ih-ShiMCK>PnVLL(&aT zeDo_J+KR5lTDnDoIPx1gsp+$C>xB7MSdoEisD94vL6O8$EgS0^ zyc=27-}&pG+KTngk3BBbbX(nd*^J15z@(wAK6{9>r-}BA!F)2Y$9MhkpW4d0J3{=- zHV?h7@Jy9al#=%4q-d!jzObm5om)=Sex5jo_6?zf5hy|n9gMT?Si$*$-X;v#F|KWw zwd>{S=8hVdW~!#@;@je@yPbkS5Z_u8hwoRvRNG>iKwFK$$Gj&flB;%zpO$JV9e?R#o zl*g}|=}R{l;xS(ndl;{U3Zrc%n(DriN317*sc8KAe7^?%fCy4yrv9BB%e~I6<~{q) z&x_H7SA6C@G5SLdDS|pbA3lmyy|JUt)o^6wupi-{+L{cMP1oIOpA(TbMku9YL)$|i$Uhul~0TXUN^`WTrPyPn!>*2A;Y*xA)ySZJo_DW2c!ulHs|&abPN?aA{PPIw9nX$pjZ zNZuy4E>*t{k#Lf;#)o~ce`<^F{mFzZi@vC*We;AowN;(Sio`y;i8KA0$Y2>4mBAJF zFm0)u1&AY#vy;)R`6d`i1izofK#UO}-EqD{?Hk4ak{q37>P332;GsfyfOEE|?1W4Y3T*+d0U@PSY zyUfo?JInITks#o9>fJHqt#A-oN2NG%b)FD!Gcs8oMr(tDZDHEDd#L1_ilC<>_O_yx z#Bs9<awc4>MH$u;KK8Z zo8(uC(1h@hnJSDmNWQXMKX*k41=i)6D?-eyD;;fVZ5}^3aVyX= z#qK!LVcH6%2#>zQaYURHUZcjlJ7?sA(1f z_K?PVrPP_OtItqsl3Y<#ipF1!#~nRPTY78yrnY`sx}^U3FV8QNe+YT?Zjo{&0;Tno z#IbIlG!U$<+yEhd;n5dGY4L&=6zR?m+1+q$yqTj&UX4k(6MMDUZi?Swjd5B)o!od#)w58!jNXQghz^mUPE=u=G!DnFP~0$%9Qm zlUrdHjIkB6lunJQOY#=(0)A1BoG+`to4)_4tp`tK@1qDm$Fri5 zr;%Zu8}>oRVN`;eAp-s`YhGaTy&S>yRbJvXTSOYsro8cCql75brD;P)g*i7ikf89! zyS>gmgL&+>5>H~muizw5OB^c&tQr!FI$k}^y^JLoTE5lLpGby@87*UPxk*w^jk zzGpP}NhGc;qE?IZM!FR>Yf<#3ZIGQa|8A1`r?#$A(JCK5%e7)m&~9B@vz1(D$8Ucn zj!+A?_0rK^d^_61d@oVDIvJM!j08gl31$H<6x#Tyu=H_{M>EnD+&gSksNb#p|IvFv z2O|*NLkA;JiCox^aM0=v;*g^Ju%R)aO1g(>3#j1kVcG(!%X^r%fNI>r+DeZkLVQbR zM5A=}f-8>uET`3*ZKq7b=1d%8@8jF#8>S%<4N9G7eZ5>|yW6hQ5cKTz7f>nO!?Xod zG50WS0TtFgG+WTY2t=r%gAu5rEo{TW>3o-tj4d);P#-AlR2dU4!}zFLRK;}deyt__ zRC2vX6RrqznG1baQwXWNHA$$$UgrW8zCBD^K=pAC(-u$}-NUp6RFW3fmN%0dLatdx zlhmBgu)&b=;&q1Hktg?61aGIXnvJm=t_lU@I@yz=TME^@Zv=Q_!M}n9`yeb)!61!q%9r z7Ny5oGsfs_*_IsGbKc;c5}wgs=K_^;Jxp6bRbLO&7Ep26!?XodXcg90qE!97aDUCX za+Rdv=-=J83Kr7y&h`s=;z4Sq%$&vx#U9SnW z0ez+T@M10w%AVXxqF(PnQ0ZRh0##o5>g{Uv$5k5cRkC;S@${@s8Z=++5#$QdYHC=>Yg5^Eub2ru(pCuxt^-pqO=oh z{$PBci8!8;#%9W?y}P_DVLqs;b!e6j&6ol|#UU)Zu)D|Uox9dve*u*mJxp6b6-f`% z7Es~RL$d{QCD@mn)4m?J{POee?iog)8`L~tc3nyIg1A)dZP2GfE*RBr9hqq{adS#&n3?GOC%{^j>TJXiDR3bt^z}D`2v?i zj=6!1zMh{0((S5F<@{M3`j0d8$Cv*?{^s_jfTsZ@Oa!<4YX}bds~H|n-uo+P4>g z{%VF_gVwkNx~T5E)4u8?Z&77qAj8cZDF*${i$}U7=pVMwhV^M4#%U z>TlmjeH`){)I45eU4=u&hSY@A9gXwE^-XG=9IICDk84~48Tdhs%ZK=s;inzNPTxoo z@0e{Dw-UVG|K6p+#_eiH?V29a%TSo+*X)Q}yQB`GGnKC&^=)nx{)9cLjg3v}be?wh z?9j9sY}o{4f3<1J!DsRMeePzAlU=wjQ>A(i76yb7KDIo`(h>IGYFq-D-&0O%;^TEr z)sn!07L7+s0X-ywD59N}m<~A3MBH;_8D$PKFs;3DB4&{}DJ+o(-V@E9k1VytI1Let z+Nmpr3LO(I)Po#GRL;Ce<|=!qb4*?Q`8$=by0c3HlaAy}7=9Q&xkLtU0b6-0;^ap@ zxAl_x5lSsBL{JAUo%#KTdK>!B6;*lNdy^h)LQnDoHFVEO{>zVBqLFNZz+%`N<|fZx zs;e=$t(To$*EC<%6X#FW(u1l6lG~%UJN4Mg$+6C&<~D*3$?C~%GS-~x2rkj6Ctk(5 z_@9jbr@j!7AhbY0Sly1Q9Qr8VlqWv@n|ck`_`67n8DoQd{M5BCX*9#&)Ml;Yoj^X@rY<>%$2MW-0Czy*_IF`ORn`9-RH55D7w8~ujJ;m-J@APlZmrq zW3n{EH|y0+1}VP?kVd&%4+5yO?eU97FcwX*0q>dm)tWV`8A=+?>ynNo7C7}XzLCl8 zqPS0>?go2xusm(A|9Uw8!dOJqRE{sJk6J zv5wzhdFS?Y=S+Cx`PL|$Zlc<4bi!=@yK6^gb#@@tNP$v6As_iod!wq}s*&xmzoKb` z!Km&jW$!D9ZYbwT_n<%C=Xqf(jvG?P>k&1M!vZlND%67_PT7V4kw-KNbc%w+B;;M zOz+7Z3Lci-c23<1_>?dRDgO_Y3<~+k6}@LCaV*bLw&)JjT>Ge0t2p~8b&4_`8*-WY zJL;BqJAUPsm=}$vBfy8JTx|3|*^SRhK&V3 z$i??#rUDiY0V%wi%+aYIY#rEH14xb?Yet6=UB5qiTm)5-Bvo{Uq+;+?NkXL3WQWc1 z(tFM>lp#X>7oi;vsG)m0T>M;D3_QhzXAQh&h8QDG(d}2DR~p(foVOp!DR=}U!9&#o z$?Z`)sM_FPpO~*d`uYPNzMaWH5fAbzy;FDL>T-XP>~X@)Z~YiZ5ZaH6>0F5Uj0bGv zc6_+q#V@MY&c0zAOr`I=*KjG%q^9~h~v{^{+ITj&UY9pLIxI=v~bNWRK7T7v~?D?kS8?z9Ee+4lHF zmg5eyolwQ?q4q8tN$}up@10TK^Ld{26PsJnsW_NcA*K^2M;wD*L!>mXZ1 zJVQ`N(I=4>OcilFO}G+o^r6!?cLNDR-QBg3^IkzYKChZpuqjK0{;}CriH{wIC7Nlr zg&!j8^AsT{1(0?7*;C8X#*>IF?IZ7*6W0uB=(Rd>D+riOE7CP*33`a_kE_!FrPD%` z@HK%DFh|2FFi^^`(lnw3Dln);<6W*BpgN`SHvNO4kU9-eo-9O383|h+FWmmEF#g| z4}vjOZ)cy(@h{Hbqky^_NN$f>QYNW2B4Hw;^zAYv*TI{idJG6YNo%ts&5Es5B6!0m zzquPo5bAC?K~$|RbA2;mf`|RJd|gtXh-Go{$QS~&IRkYV92{jJ>j{8tQOHNWdbyUN zThVWYZp{>5`a|pls{a%sQSjmh;&k6;Y8$>F1RDU-Kp{%veXeoLtDHV3(=dOdSxu<0 zChRKGxfqlIReGY2=vs7;JKTWsJ0VJxN&>+#@flr57Vn(SVSQW&ny77Sc3fz9ckdp8T$5YxTq&!ZUeR`ERqnxf@6j>TX5o_iXZz9;p_&RUCuryGo0ZKN7d}Pd=vOrJMI%jFjQ0bB(S~~DGcDVa7 zt&~Rzy_q$yFhP_6WsyRZR4Szisn@R_Nj;aPk8$KO9LBw?H?6XBD|7F3^Qlho%$KC{3MDruKo9{a@6%;BiU<@^*;C`P z5XU`4m+mCS(w$?vCU>{)n={g|&-sP)*3k)tqgx`4!q5i+I$Sxzf(pmnY)(3y`+6&_ zgc-Eih$N22ii4=Xv1Ff~91EX2>Ji%(!%tU0^KvrYHG z-zej7Hm~mGEL*i*-4LKBm&(GYXJD&T^-9Um+ci*i)rp=Wd_XHu%@C8>-i_fcv z6>Xz)e3jz$%PB+df{=?YfD&jSANgus7x#>&Ded;if}G=Sn`In`* z6JT{3pv+i^5+mXiq<48ex=dQ55*K0yAmtUJga*X# z&Sg2iio139NxcwNNX8lQCOvV(`5+;o?4XwCkfjq)E-UozDWH(^=ews4dfNIY9@)Kn ziktgOcKw$-+@MuBolnl}+<1hGtiRQlC+~MhnM3(izc$GSM-opb)5O@vQORTLwiy4SvYD5 zOS?B*h7dp8H@L{U$Fv6~xhAUwpBTGndF zW+|>WF3z}%zjWSJof@Yc1P9$ygClUp5&8NT1jzq{a|R*!f7z4L2O+rkuqc8cbzITD zZYCO7S-@ViC7IP)Nz*cO(Sbt0x`R~c)Vufb_q_%- z`d39}SIsZO9vpkoWIHkwLN+4EYT1@b7=3MwG`@OCc@jUPTlca`sa-9cJP1x#J8H_4 z5eEd?ZJZHAr_yJgm|u1385meto8N@#A3%30LbT#HJ!Se(-D>$wD`?Lvf4*4}MkUbg zilDErtPi>Z@Ww#*2ZDa^#s>fRjlVSy?jNgyQve~|qL}Yg%0QdYe`@n>7Zc#US$l4? z(3jw_`aF@Mz<5&v9(#j^ha{A=>s102Ms`<{-m=+&*|n`&yBy6O=mcX?pFB8TdV4@=JCAO!L|~k8udL zx}RR9kl*0z(eS5z`0-a`JBh{g(E?eO@R8+PbQmi$RfIfj8625OASEIC38)t z2gnT7;^E(*` zOT=?m=QByt&)F&}WnaE-?Yq+T>8Qk4PzDlnJ@5P#ga7AGmv3|G+R3j7YcwP%1- z+5ZJVJx&)_ebC{oh}#=6K}|$vs!jU@z~rst&9zROsIvyBjspBP@hZ+H5 z!36;hFc=DeeY*e%^n@D-!A%s~8Nz(k89`r{ZvPGd9wJumhU#rP)K3}Qq2MQXpL!_n zV3``B#Rq$bjFacykD^P{zsa0u{=uS$X5>TLT%B+ z*C(@-1WoN@Ad_3t9Q&g`LWWzRnei+v0=JUFH z9EY<=FQ+VD@ga?@?Lbd@Qj_X6H z%QnPRr?eSvznWZRqMp1|G819per99A_&AhVZLs??j|aaAt-kAV+q@&gU52T|$#~-A zM*o{E7shOh#9zj&5cbWiZZkjY!BSzWG-9pr!Er>Td1s-P;5-^Z8>3-3QxT^7FYPsa zUVQ!CYq)C4{xd6AgfGl$GSfUU!MIE>c)!f*^bMp9?-knA2O{c~baZrfUtG-;% zNr9pomGP_dt=jkY&#V@$Cwf#xH#9IF_7EB#2N5c2QJh{Z)I1f>C@TNz6yna0{QR%% zH2_U5-!UuH$n&l#=?IDA`d0`EL>bQ;OQL6BCB5Y;-Ha=HqU{2E-QfX5%fwLODM&{e zsdEEaTBPKH(Rmq8lbH(XuWx>gdrkAC31+Wh1q6cbH2^*Jd9UH1xbhQ^K=&Ge(C6D; z19X!IXrzDl;SaELa{y7G?HZU5zY0QtQd*!v@RO9*l&j~`kOtUenI+HsNnofs*Mqtw z8QavGrwy(mgc<~pf)SvU)?>HSEYK5fAVfD&YG>H?{I65mLu9|ujs!H7eAkhH4!OcG z&jW~}Ea6~dc&?BM-)lvebW^`nNqenIC+o&L$|FY0OH@=7Xg@ukQ+}?^LHE+$| zFAFWEfp3=%Z+hDqS8>XlNa4}DnsS7yrF%`XF|W^!#%G-W5j*)kH@N-_EVSqTN41Jr z7Y@07vd~BL0=DwqeCY*JZXhH#QDJ9K7$g5BGIWT%2^t3q)4zJTpeJifvQX$e*=gce{K8pU?owW0 zYT+aD47vRFzbp=f^cR8uTm(k?g21EiU6iZ}0-ikGF9N^KVwxeMQ$e6-QEg5|$EA5r ze1|ey=Bax^?%AO3l&QaQpk1syoE^5lg+;jc2s%0!_ldyIjGhyW5EHJw%{+bH_xKcP z2Y-Y#R;P7s$nzu5j|d?&{09*jsKfnk2#f?pU?5-oBm$e9Q&(iy?O+{|e>hGQ>CIha zo}>{8kkv+~P&&o*6gTSxh2V2%72tp8lFM*afUBi=sAEaLPo*Y^w7!?@2r zZMqFxAVw3qJ(+1a1{NepmfBiHF^AWTq2Jol{kvQ~%ygJRrHNE-o5nlP6JL9eb?iML zLNANyiKlN@O~m<-Sq@PP6ANkaxumE%PBK`Rddw%_g6iOWyqmt)FT-_|zqtPA z;yTI~Tpwn92&yb$p=R1IuG>$$>s8!Vqn>^$ZS2k7I*ZaqcGt2z3_Oqv0o|4op6_^bDGRA`X zm$Cf0j0N=zV;QJCsT=OT-)XaQ?PH7=EAyFK z4{$OSDCg@(e*Ra|M}a!r@5WeAp^OE{7eC2Zbdk!uEt!W;Z0qDacd9vKqmTFrSB!rL z{X!jSJHPau3^3|m#sc(&8ywotGnU7pRb7$F!}quEcwxl1%hojA)))v-WPbiZQgYQ! z6Kw13vU@xTNN{hp2_O)Lf%g3ZYl8Zwm>>No`^*^{N~Ce~;;dWX9Tyv2EwX4a{)bfU za=#2%(f$JKp9`#LUx2mGAR#B@mbYZkeu0(u3KBkhpdrVk2Z@i?r)M_%7cyFEf(S-XqBkpGo&V0_;duP)aQ!8myWVTOWJ)fXjFUF`?@XVDMUtjRS)17vc zfMI$8d~=nx1d*=R>;J(}08oef-GCJh3amiB_(@>py}tcC)om_@1^giN#wEEMnC!Kw z(`gTfh`Q2!s4w1}0;BB(R-h-`;Lv{_STn88QLij8PO0FAzuCBKacuN5ww<>VJG?+g z!QF?}A;QjSyTA%0xVP%W0f6-YqWi^QFK^Zs32H3#(AIMs2(QxIv9_%jteKbu7iOh2 z>?jp$e;Ed&|Ha@x7lYBiU~msy7DijaTPDN(VsK4Kc^m1NNxk0Y$IVX-CubwNXb4D$ zh?|TCH*Rw2Ej9hc;KN+pj`vkc`ypUP>1{7Fc=+*!9c$FNes@owIaQh{V4Kbi)wxSxLuY6mF}=>gZ{&` z;AyZ(vP>5keJ=(BJ>dq2@$(pLLG4J6*C`cnnhawrz~2LFnkcaMlCJkm+$3>;&L>H> zZ}%hu3GOX?aR6ZbcSQFK*Pu0hypCMCpioZ*w#kI6-bxzlIL`T{!QL3brc~Mz<-ZKB zG5*5!p9|L*U*Nhk`uUaF@l*%R{lfLr2@{-zjJ)~_3Hga*)E#(USHk>XHw{SRKgOVI z$o5V83)hDU*HeOpGZvi^ZVCH@Ypf-?!MVY;%1;5KWc4>ez9$_5${T!+W)2QA>v9=Q z+x!RN8mPnlZg7nOg=-*R{3KjA-W69*_A1*LvdOFuH;8ScKKn^2HlDBR-j0>Lc~DFm z7-KJ713lpehxzkxZDMoT@K1}ZtCoZp}!2-G5;d_ zpNs66Uy!|BsIuxk{JS~#{USRi(={984~>fFHt7+2rAf|t9k=GFyql22YA!J%<8!>~ zFR~vdvcHeY46u5Rj_a^bWWQETBR-H(UGIvdXDNx25175A6J?l5ygpfxY)Bd1vSJU1SFm+;s@Ycf9$u9f2K^sMZGw;!Ey8a)3K6< zjvtY|Lrpb;+cNzeMr7($TaPdCGno}YopYmAI&*(~S1P)<=YioB{b;~|tgmxb(QTlo`5J=2b zo$CK}@6rFj5M_><7c<%tBK-KnOYV@swu%PGj%-{FpPAP@L<%ne9z?=iQ3hXMSrYdru3IY3*1Xb6;A01b}cjapzssYTF* zH>nnn*M_`w7BF;a8oZmO!zr7J-=10#OTSVtueMd7 zDHs(i;QE~B2<~?9JIwu4i%%Gqs>?c6*{6KCQ({~OTaR@GI+|Whvmxm9$$V_g8~h_b z|0~fApjY}yYQYd~5+>iQa%9YCOD!STXu>%V3_3HCgV=?J+_OBu;|a!rQVZx!i9k=d z!Qn!3)?qJ71Ul^wvji8bbD$7a+eyQ*@`(@}h!R&z#(8ONV+MXG&i=~gnEf=2=#h1k zGhXWDop=cakK~k*=oCz+l4;vkoJTZP+HM?eI{S@<0O|b8LcUz;4D)FZz(RmTcMsbG zIcx7>`~8v|pyBzuksBN+xl#6u~~=ELm)xyO34VK(6kGVI8LnH}=cO z4enoZ^XHNq+%M#&{@J8pQW3*7wf~!O6UZe_PRJze?yP?ZxtZxCr?Qr(pLX!QEPNKU z!2KpbkZJ7XD=Zq8fpfH#ipu*ZHv;C)YWMlfgJhyRiUMT9XAPA+kl&w8GPJ@=W2LC! zxb@4(4bUt7B)P$Ak~WkzuOLet+#c)NhL;!iuzH~K8Rusrk2Fc|MI z2LHJjjQ0hDYn2xtquKg|>ipltV8|6VR8Q3#)ENE{44yEF%HAAV??bymho?cn9>4WY z+C`bc)`ewSD$X=!V|M=-JRQ!+R^w$EDw9Nj+!}SFokmn=OlvMVKM&b0^F->+A713} zuLKx?Ug;+>Sg?ZAX!CVSSjPkQE!Clr3i))EQ$Z`eGQ|-*c8x}MAHjHgF&O9xH#q!X zg27@gvPg4-3@2vfhvpDW!(W{ zjn{|tHSgjb)#Z`rR;vP=4-~eHv+74SIVxRNpJtxIX1MGaOd1(>@AGBRF6z4Jz9AFu zK0L19wys4-^MBldK+$V-mT&lmALZQ<25cmjc+)xyQ_5W4zD)Np4t$~vOy8pZhq@T! zGMQUo*9Z5V=l!r@$)6ct{SC=qAI#m)~mfxigZ9+4-sR3;U&wZV8%8Zx{UlX$Qs#+;@bL6%pwu6 zsc&4O$GO)jAw&zq8vXCfweBv)D4hUvG4aYy9J|-wtF*ua#`UKnbRF16wcZBRnL};> zwED#5Un=P~nQ}P^yp$4!ba9B@xpSQW(W(T5TV(SwA$!7xrg2{A)#tHjFjX9>cJKTckf1r^t}q4 z=3S;pCKu<`L?fjE!@0l^PXDM)Ec$eso-C(YyOoPiCXTRF(1qiWb;#W_!^)En6qNvh z)%)>b9snY6Frq-0wZn+sv%o@qBgx}Y=wck)teQB--l7XSI!Bi#p>4B3Q_&!zt_NTK zT|Up@|Ap)lCE*0bj;E<9i&wc8c~IxLMEh~!0@Sw_(elr5&Ldq*Iuq{}rHpAW8;3cM zW!qk;Vlo6*setG?Dd%2El1Q41o!nCGXP1oh(bPO$OOT7~SV%%LDp9Xc!gv1}gLyGt3|aP|KQZx}>3rOS)l?VyrwP?j23sf?VNktW0~i z`+a=9Z{>NYc+mx&$P;KEYnl3_%q67Jz8`LUqLlbL-{s}bnew+RM7;Ao=588r&dJ@_ z?;3@BZX;?7t|&6sD>-=zsW~Gymdp$%P@OPZTMyK5Cq#Vu&Mm%Qozkb!N8L@({TvBb z;t7L|Cgb6BD#p5}M`n6%iVWRAvGsdILwEKm_SK6=j$TQ>!E6o}qn>mV9Xx99o`Qoi zm(^&>jwL*C>GV$TSwsP)bmh~`@2og4tDn8b*7t&*xGk8l<@E}#bd2(oU@my94_

vHSrHL`4Y!))mrU|+My=J*?NCLvdFH?W^2AuF;5 z;H^HAd%^lZMQrJb*-5nbPf&?pd+=T^*X121D!XTq%{*6JEFWU=;F+tYu?UAjkMY#2 z_9sGRCvFW^MH99shL0Yf>5~Qh{TcKETfSaucsJA?dq_JPhI zPm<(TUpm*yla4 z=0y*|Q`IdP?pb3TiYFo-X$Ac#{AC2$7ghqnsemwzu9~v{Z2dAwHhG9EZDazpei;a& zRq?Y{w69vPnqR#MgGeSEyxr25sUJM29N6)7FT8xxZ*ysx=x+GD!*!Ywij>XW)rLOk z3c#+lB^6oOWtQR)m5q_BOj_65h3>*V2%R`fu8woBdnX7z6(kRW)76fe@?^vTfp(W_ z2LJetzcugkruskoc!XaT)8%ZK<*%I);z{X1R_P>}O9eP*pICS4EmsZa0;^eaac&EZ;X>`UEO_7o5)N%Leic*5Ih^iR;MoSu9bOhPL1GBY<@NAIXn6z|P1aigxdofdul_ zZ&!i}=xR#ngaR}re>b5V;JC^GL=SYb1s_Uid4kPP;FswPJcBC{Sw*}XyW<)jFPM&Ws&D)LwIgU z>jz0*!?enm=Obo=PoKs45z+ZeXn%IOwxA!`C>KiB=DlA+yV{JA9bmiSMtyQ+FeiIj zI~B8q&x)(PQtvj& zr`$^BNaRt%|6$!OnOJ0T_*|R%$7I2Pglrt1fkhI#J3@h9u}N7ZQTvsX_hHn zAt$eM@E(UD$3RDNw06Vd02=@QkNP@;K(4zJdI@-K_ZQ^QFO*@_FI}+r*3}IWvNzHO ziKHC)7=N?n=oxN#oJSpw($rJ}zl>oJ{$&_{F2f-H!Z3=j(RklcOFjR1zYL@OV}DZi(?o;uE=|1lzh|n4!QQ_eb98cx4k-yACGY%P@eRaDyZIB@Dwi%j#u=1OXNj zwcjm{<=FLk%&`WmVs62k#8q>7w_d#aR_+T(@V6`XbpVEOfEW=F8M%k{m5pSfOT8&o zRVl-@tXJcPLx>1<_7k$ezJKy!u~1Yx$ansi@dcuN^93|K5(jqA)0DULsm)rV!rl8D zGF7nB)`cHE-0Iy7pcwcOiX6@s8>CtSN8js&^EY?bY=5`dKwo=N`?aH_30AUF2qWZq z(v7W3oKs6h43$I=ZqL?>mYz#DB_a7j7@ngr&od;(B!v7W3@)y}U%jt`5(c#yDrFGI zH149}eZE*K+-R=UEzIdaA2Cq7R#tjUndN_wFaXUh-w}obY@8iH^gw%T5YGH{1OoZu zCkaEKfg7JtN*?WpAc7AL>f~da50D9oc1~2|y=6ZtlX_+nOthCU06pObNBm0&gL8E- zGNJ3TB~g}8$#q{A+mnt$;kqSpx)|t7!(5lnWD*I7Gq#qcuQ@ zMe*sZj?4w2zE2z`MAp~o=&?j*19|iA+8s$KO7`dZ+9iR+T*V>53~1dlNH7CRNg@cE zBUF4^5KotTu3J!25(JY`waX;f+rF;5;TRYw+z^0{r^2m0;#glpcE`#5&oBwSewJ=&Cr}c0+KT$Yk~?QMaA>@u+_eyv2zrvwZX58B31?h;6%>2+KS)V{=9b@$k`O~F z36L*-l9C|QkrOb_Ny=l4Eaomx;$)aonX65gI6uaqt`_d4q`3+vhEkHOU2X*Qgc}?Q z$uHp~nGd43{B@CJ#TmAXD@w4|A79NH6zj-}m`{73zj+1m>bLY6Ai>`*edd3ilN^BQ z0P&Kl-SZ1Mu>lvW)WSG>BV9QSvRrSi4Y^8&uHAoR!bgbHWr^hpJsXs!?n&Cm9XD|| z`#Mc!VE%iofWr0cSQ~LaZ5AxeV_^w(^tF=aehi-~@7Y`3WG_MnEp}ely|8T27p=~} z7#O)tv_r!~c2a`AgjDe)TUzkAuey=LtI#0EH;Nb^>sFr}|9pNSKtm|XFO)XvIu@b3 z3?<@y@(Z^D3i(yUH4A##6ncq5sFe|-*z#6c&py`%4|P~LCDOM&U|MzXoULgWcX;Mc zKd!HhM}>HyU)1iUVLFkC2FVNO6Sr6DI_!2(ZyOUYu94u}o6n!Gf#1aAKm6JX_$_(ZG8SbC#*-=HWNj8Z#e#BpSH3 zy`(CckV#pq_3jQYIemxYw%uK8eMrrqFvn_d>Pz@4L&5xt@@?lk&Ws#mO{J~~_KhQ( z&)CQ+rBvjXH-TeM-iMwHqq!f!iKnP;KBqT5lABcj(i2mmeK@rtK4ngIJ_CL=BW}sy)NRn z#<4C}Gkk^FN_~06ughWLmi02;x&F%*GbAMYUd_-TH2#zjdhv&M-X2bX6;f73f9jgGZ1dmM%mG(39!jo%v&qqW<4p%?v6{ zsBE|}dA!{1KEGQv0^Gq82ew% zP`{7x`$Q0Pom2Q0yihx>Q~pDf0GvAAiAd39(8D5w4?hY}9O@y4!d)&7G`IY2s~Hj! zuicgdIpZgnGsc8ZR@?Ke76lQ?EX8IB1WmWNd|YLwhiUxo5myOs?0`u~pzE3b-If47 z0$Ivspp13!T1fl|gvNal8zA^lHAucLo5 zO=QeQUDuF%7NNU&Gi0Cy&COU*YLKr#{|ICm(hLtdM*SqjTo`=CAx`8h4-eJ^Tukr< z2AVBg;TybSa}LW^!kuPn$0t}c(`kImL*$ETOqkG~T*0Lm${=`j@`5)u+bxV)_`pdq zJwL*e2(LV<k^D@NUqE(Nl(YS5XL#BdLO!uNzQT~IF z3iS?@&4^j@ZRb~7G%Z~Q;m-%$sGJSk5^eA2B(bZDymTn>w&xs59Ve0DX_rgV{LuB3 zhxrw@!!$To|9*F^jdt{?;0KXe3D3(QWR!G_=1V=?(}NhXG>y)JowL}u%(0YDZKy9y zu0I#tZcp@;2-`*uFXEg$FO_edyRDA#y9K6SD(4OM9pj8+rKR$47Od{AQ{|=QlXNuj@XddzYL;tb$MBvKy7eW?jj$N<6{gd$6RVTcDhZ{XV{u4y~y>NCG+7M zW5={ELyl3Wjgc(boG|qINbEH8Vz3JV3Efh3r{_u9` zUWNn+iMg*xgVxtDyarSTjHb@DTQ27_WCY}m8k3Z&Vpx&lAbehi5I*qWCcDH_AE_8h z#_uJ4&ENCwN%D0QOUuoNX-hlIvZOQ!aIj^c|e{KpX3FVg+OFEVMT9@R^)qUrX3bZ_KUY4<}detZr zCf~s}>AF;Ca%>=g_4;zdJP!T3#gCZm?|VvRIB#&K#ol#v>o;AotRD5=)?j;Y9I@Lz zpd8Y;o8H;?NpmRe2D9%@CRA29Y51}d(h}&EZ?Io^aKu_~|0|XX!t{GWi2gdsl+RSF z5pd*aCs!d^+S+S7)k`>DV7B@9M z-S8Y&^xZEPzplAWC#x;-0)^KDojjHcBkr|Nrn|c(l#p&jx*G&(Bm_hWX(5}@P z_rWhZe4fqrKF5!q8~$UAx%RmBUh7)#*PLt4xuzo@_x6!75?0&!2x@01?_}WTJZWIr z{;Q+}nA!IwB`0|o-~OGJl#GI(DJeO1oq)ZG-@E`I_(LF=g62Bmo(5m{-DvI@ip&j| zx)eJkysq}Q)z&`hWtw|h2m$pBaxc37*P#sF1ZCen>uV@bziG zf|w!Fz#(G&dbu<@HNLsE;(CowSvts!!B=Zp^ii3rr7-oxjtcm4;TLG$LzQo}lh%&$y+&4Qp2z&y9DQGBzcZxE= zCgUej2I5uJHJ-?xCj1x9sgN|*-rFAKH}hdcU5z*78;|Hv@F4+s-=hq$Atxw<|GS|K zv^C%Mn1-KcljSb;teuz-?2-a{Qk!XsW2SNTEKUE+*RHd`3{SFTJkQ?30|Lr`kRbxB zcI8yw(Vse9r3i8A>(#0<)gzFtn3`>*;s_z_dN)aKjmr`7`q#-j{)N2%i{+j8i@ZP8 zuxJn6lr0`Uue?9sSX!Zd-|N>NV~XuEvc^%`RZ9Rd!876OaJ> z@8uoXkP~?)_}%1vQ2>HL(+bc9=>hQT$*v*nkPYnh#|)7aBxu+Agl^&CdD;*RnBmD6 z^E`WM{Ljcc2&tOqB>@1Tixq?_uIekBM!&+wQ5n1%bS=J&%Tn*8?{~z@ps@Tp5+JxB zfqyXxkbEJ5^j$|@fr11(z4MYl865t|R(Rmjv$2|+O*yl-LNFXIWyJC8#uwnHh_iVvo4Rx(JY zLjnlCCjnqXPDp_8cOwBR@+T7x6D;qwZ3x1>8oboK#YcP}M})fs2vg8)V0RRJ?ZXAk z@cTYo|K}v|4?u%b0u1R3#+3&lmy=KyT3$Be`tR^8amw5&igUSy+oN&)*Fgi}1vLDN zp@H-ZG^9p(WOk^j^zxk-8qkDp%`$q)bWgUutNSEmnsD<)YxTxk8n!p&Ef1T_{h}_Q z;qMF|9OWCy=n;4xoF-xbOLX`kz7IhNgR1O*BN<1Lp#O1JszW=c&;FIwR?V$&hFS4H z5E_6jxZe&M2v4B_*kt@9G>{VwjwD(#=?t^=Jn!)W)4b18&{YJ}D+NiD2H0OtON0av zeh&@6hMYhH(eDNgG&#X<@SZy;$&Bx{SY)H!Ag;|9#I728G_8$ae7WK_)z{Wqzzn}{ zt@S5C!{5jkr}d*u@f8M3JMz(+hk?b*9+9dNNJy}c^GdsF5t0B*sm^Mr_h0$T@`-Dj zfR%Sq;dSscIZh|VCg<9t~@4slrnLmmFmFa>k=>3j|k(!gP z$*>Toy$b0A%EGh3Tic7868TV^@-kHfl&zih2c4z0s?Im`bGSr4CvImbBQv2KaG*|v zDR@P}Yf8*kybE1jcA}!qD(Q(nIc{#uiqJCg^sX4jhmjMs;irj(q6{vzXcG@#Z8Y(P zdaAO+zXS#Cfi%??tCsB2Jy;jv==c6mA5Rvdi$Sm~Y4J6d>6+-f#?YVxr1j4CDe#Iu z;enfh)Ncf?2|3(QV7M{B9ot)pdV zx~R%g>Qe3z==9q~ZdgxiRI_&c;#!`9g(!!;L5Q>%<&DHG&vpX&CE_l?{vw{%uD_%J3Q=_#cEi?dLj6$Ni z!k$!&Vh>~TUul2xPTXSNVNh%S1HFwNcZ$JZBbO?@igl1ZH zhNo+*apvV@C-OD($6BeidR0b(c%2Y@EBFz31WJs%hB|-$ryibS}$*zWiQGSMPA+-Fu(iJ{5|g{wA|FmMULKPPUJ z=^#u-wfAwW^snwh45-pO(*&SDp%V0K^28go!|)}*h5ELB^zL!WFq57oPnYTPY;2}4 zP0PKqEL$a%1C~2fl=o4H0!6@})s+E6XY!VSq4gQlKQ0>^r|%>i+t0q*dHw+v;F!>d z?|M!ji5h>R^9Q((TGNr+;RSNxVMEH#)oyl2v~OMKpX21mEkwJCuNO-JfQQH=r9!2+ zc@VE!t=mnkgxw=&$fQ!E{ZOuQX1A-FECN2)!@UD+7wKy@_L2GmpAf};-UIX=zxB2l z@R#)ur>9ss(G`jOhBuYHul_nGh3MQ)%29)}g+4j^158^+E|DzR2V*34CiASXyAx+_ zJBEXU(f^2C|DO8MfsnmT=br0P!GDE*E*kWmw?QBI7bhjLE6lslJD#uVyiN*~GkGfp zp+x))WZ4xdy>Z0}t@VSjE3|&t-teoJstB-uG}RZ8&7X{HVuGV;jI8+TNLHUSvWY_} zcO05KnGlRfpl=P`sLV9$?8&&T0xO&U7ESRoYV9BBqyXDnzHw4O=(-1jDM$_a07R#0 zw7_1KZ=M{4j4BXJK})_OI(4srEu)`wulx-cQ=vrZFQb;<6II~!ZVd3%X7$BR zhI)?jq8AcC^u2opY{-dwMf|(DSMdUEYA!Pw1A1zou2vzal^NiG_pDo=T%=W3g7=sa z$M0(+LSTmfMI%BGlCMB;g#OP>iOAae;xzIuA+)UP5b&Nrmh?YrA-3?`XS@2MX&A-<8%NW`Wiv1aj`bnV@Lq;_hbWX$O+ky{BC5!f|%LDkjNsoU@d^V zi7UbpMK2B;pRtgqhW~mXZmu==Ydb?=hW|x7!+$B+084y36F>tNWk02s)ygO)o3L^k zt6`0dS8rk`o=Y^R&d<>5M%>`-W}LYD@Yhib$+@XTO5}z~hjB;Zm`IbbRC1HeqFN#> z|MeLCO+Wh({wOB%e~wyCT1jEBLxW8gTu=*$)I#}%S{@^(8E-2sul%PkwISH(=X=@} zK3_+V_@C?OZ<$*e+nrg)z}({>P4z`2=~qRPNEEa5-c8 zoN2@MJS-zd56f~GEi544UV79`F;Q7>J6Q+PN;?SB$e#!1N}3d$Uvy;f1S&0iZ+Rg#HM_?syWqm zcQy+}#9dSE!W990#D0~W@K@~@Yars!PVB#fqBF_1kYRhA~ zuYd#WGcN$A2TnNv>C8_H0^k4(GyT;$4%lRU-#PBQ+!T~jjP3`{9=y=C-A+FYBO2!R zaQN;-21MZco!5vVv)aFon@G>kO*^Ca=%TtlLfj$TaBJimd|~rq5)uuvAX%!7GvaX^ z^N+;z?}_%DG_-kgwSsRz{eqi7w+!URg*T0Zdyti@@?fExEdv=F!Rojm<1o{03gfK;Rz~b`LMT zrsL;?1dx8uO~8hna1+@d%uPZ3BC68c&ma})AKzUff|#4Xl|?jNxO&uAGBAtfbX)ps z>m*=?-?vTzh5~11<-GE)xU=Q|TKsy-#*mkC)0h{O1!k?sSb4A$ZEym5=vaKmuakGO zbIW@}kXupuY%PW}hWyN4bdwJ<}TxJqS-k`#Tn)8p(4UYicM(H#(gRkv`fEj+@ zP6z~f2f_5bBrrbT4Q=@rA!OP&IrLQ`cCxl3E3HW@!j0*@b|X3Q3e9U;_;?~dI}ggI8WZ<`s0%G`V#V^*rXMFR;S|DFVZ z4LKnJia(eHKB>**W)FXwZq1dlO{2iew?(_(>&jyxo)h8{P!1sH{n~j5nBhrk%Qu~e zK#=!;#uOMdoEG_!Zz!S&C89zXL@wgXY1zZ?jm18L)kevwEUOh_AcR;vy(IFPZ0!=; zpgm?^j!u8I6q=~-@J+Lkc^YYUI(8MRta3*Cc;ycgeiJpc4=B@KfDIAic3Rk&ILyfF zIA+F=@yB$RG7h&B$%$y?^&& ziHdQe&I~CCBeiNmQ$8=NFx}d3Zsp)^sj9KyrPdBa!%AUqxDQ+7>ow0A z@h~gZ9o(8q%(db1q13LFqj3`J8NMj^PUx|lZNg1aFBszjw8IS=uDF? zV2yoZ_R#Bt{_9Wz#&b9P2(E4I!iGF_ZS*hXF)uSCFLETI7uXGQ?yFg+sB`u`@x5YBgjXpm zFPE!Z!kZ`Hy7KKN=8wH*xdj5h(*kF5AbBXM0>C4OUhf2awh6ZFlpuIea30t|-fKxM~RPbj0%h z0NW?S;}h$oql~a|Mv_mqM}3HwT3sx4l3K7LYFbEYno#8f-@?xPS;4nC`*#K$lDu?D zBo?SY)V;imktm|2j#ojMSL<7jAS)N(@WRL8pmUq>ouW&eXWol&cs}#&5Wn}=6{yf5 z<(R+2txrmPFx_Q(xcJt&!!%q<}DO zH4`+DMWz%`3avmdBXB#Sn`4z)f{vZbYe#P9I#^27$WzdJKhYeZN|V^@p)v^n(oOAI zwI2puA&&KtnqEvwSln%FPfQi=$*gVXZU|8?yK4URqnFp*YpV4ZGv?rgP$FS)_VKNf z?h-#!634+plh+Q+uSvwHbFi0^qgNZm)*ShKNhvT5vH&~51}Ku+dA3FJ9zPOeS5{g>3?`S zgkN5+2NF-)jQV7Z7t!dU-DU6dwzw5HUmDr6>#p#O{-l%TVOC~ozh1o`%T9+#{o+znowi3g*4PfX79-{d5(Gr*;=R!lRHUvx*9oo?)PAj? zeSC)}s)BTNnd6c_?Z`!%Ri9_mpN^C*_rP{y`F@>SPjPOyUi)fQ4>xD2Vb!c*6bL?BUM@+fm?>IeKbq>_H}CP}e8`#G^g)g7 zU(4q=yMYxG(oWG_&CZaV^{?jx*5*gkfERAsYWioGZ~iyy7?KT<))py%K3owxkaJpUtIf`RQVznxo8 zaautO>{a>Z$w4TK1;G@w3R;TOfCR8*^pgPzDD4Kw&c={eg4@dwTauSfB{M`Z)AS8DdUwTQP=t7fkrcs~t^yr3lV^D5wn*3$_ zNdb{7t=E@JzBWJvW_a=&c%Ge=eF2VRnFEk^1825wKpvLM6@6xv+RYTF{ zI%Z<~-LW>O?-Q^s>6`Zngxi`xFa_;mCyLWrmS-&zc~}SQ`CX8B)TGlT9+qp9!#k{Z zIF{di^aB%2NB?z9M|r_?|36DQY>4n`jp?eQl=mjgX|!y+uRH(`i=LCiS3?c%KZ-!i z0h0y;*V9h?;7g4P2KJXA8=jPjT>o4qa_K*1BKn4g)}VpdUy^J9_POHZzxyZv!xyIW z*PD%S#EE=#^M8}+rkz{C4g=LJPj6QE)0s{W!D9H5>APT|FtYA91+A7-r8^EqnnQEX z6^}%iD>xU=&vd4pVa}v-D>oqo|9zV=E4X6PlP>*;$YLi*2;z(4b; z!1`&N`C7orb5BtO*qij*p$OF}itypl4?YZEG-BK}YEsLz*INbmtAZ9*zWLVr?kCqk zxsSgNMW`-NzzOBa>X&pW2*EtURx^)Rk3LK4(rEyfb*lsd$GikMs&-BwjT7f`}gLn2K|!^Frcx8 zxY614r{dUwseM&99)Jhv~m*mtrkv-qR5$EG#97e`L zDjD>oP1D5Y3->+!b<{$AK`sAcYGL|9E#C2a-hnX`6z9%$1s3W5k(&L!hhiW2%TeYK zT1L?SigEqv)MDF~urQvQ93$)pQGo!f5+l!L6erx<(pgBB$8zsQasK(Kg`BZ$AS+N3 z+z|Wtp%TYyfF)Oy++;A*c55^)U(L2D@{i{EZ#@({4WIz~N6C_#DStxZEln9@FdadgZTNb6n1F2|#JBQM5z zuv4j~WycNeJ5Ck7)(K0j!dpp4k4MF*51Hb>^9+LZ;PR+EGXQP*JpxIbiEuL68vj_Z z=t9TVrIL@g_n7*dwMSxJESkRh*z`Fsw`^JBZ38#aPPGy*-st=GmFff--26xwMAurX z8O6}NJE_7_OTuZwviFbga|T&!!ljz-CGw<*q?@}yZ=lFNh9mE+BGEX)^-|W(yrMuh z$q|pQSw8Y<0Xp(s1X`KZ8U^IgttDY3{&%j=xhfpyjz%j!ZZ*s6v>#mMoPunEm?h}B z@i1njfA0m2DK{77Zkk)fYk3yys%!jihHE^_b?>8gs96*mtS>Q?(~exDcI{F$AcZYO zfAVnOPx<9#zP#&A#nie-;HqxTZZ}*_C}VTedg;UsU^Zu+fn1^AOj zhBb8i*u3?I@-Dcd^)4ZoZ95dg+;Wgmtqsw;g|ayGI^68_f&!@ZgZJ0&1&_?fbMf7m ztZHGDFo#SkI&5uhAL7{zi(+2?gAGlvSJVBZ($?{GJW|`+t&XS#TsWS2 z6+WS^oDZlVi`RUmUu@IUw79Gis;<)FJeT1c-IRV)LY@&~g1-k)QENcqm(D1WyLw$s zFYjt`9&)rLAtNg$wSq@RgXT(=I4P@9TgrCXB~nxfO>O=fISH;EQG&gMeWH=Fd*H(n zF=T>6BuqL=#w=BbuZ-7g!hF;%m1|_L%vav2V$rnIwOSTRQ@JHb-B!8zKI3y<;hsWq=XWw`vx79&#Q zrR1bo68TOA;N8Y6{YQwnNZ+Ooj<-*9W*a6v)ZxiQZ|xT`k8+lz&mHlp^3?$oJQg!! z5&?syi3iZ0**Af){Tb6gE-x3i`6Ms5h=ZZ-^|Oh(_`O(EEmv5q<_JYcM6Qe{LpRAX zM@e60lrYo^PZ3Yvqa0eOHI#44!Lh@3H{!=slCY+E>xYOOjr3IhX7+6_wG=y5F{6t2 zyPRxz*wb@#?3mR3avN4JhQFo`28|NF!O^>e^0WPD0l>AbOBo!l4Ih?c#~rape2V?HS~&Iju7oW^*G9Zb z@YwDZn?9n@cM!tJe&lJtLPR3*@_OI-y(EOC``mQc$WhrUX;g`F_eP{r53`9}JqC1l zRTas?E+hVEp8r-a=@bWnEx2!R5QMBc5KKW!z@t6&?|@ClPx^Q89qWCbZOPZ3x|2pF zz2bXlE6o|p(<_)Y&&=Y=^!5P?B!KpN{|?xY6aS9(5BBd`IR-HWn+Ne2^mT_C_$ofy zGhe2~dYPpgFV~Z6HGI|b419$7(^ecS2#@i$*Pt{HeW1L9xGaEMYJEjZRZh8^Gt=a& ze+TR{|78CTSe@aS-wT-227>85@c$GZ0h@yVqW@BBN_XmH)9$9(bmZgSJ~BqaYC9i6 z?d;^84E&rY4J_NIdkV14?VCLXgv>$^OhHRRq&B8NtUPqKf>88ZPMYXB%q6ab2|^k$Z2%a!xv&eM~sj7<6?TKBp49 zCu=|U&Jn+;#01L$VX`R#Dg|EYA4rG57Th;GIS9xHf+=WpNOwwyz$W7-=}Em-^R8+swo*H!?@75+!ky^5Wt0E;h4JX%t(0xybz=oXAA>IE59TGyOXvYjTE?!P> zg14J4A8>}gp21>toE=;JT5u)eK;dhXDPV^GMUyFDc>rgK3s^tvQ)p0-7}ZVJLl@fc zakjNm8X?_kq$oxkhK<`zuAWNGf@k@4&_H(q4gX?jVEY0Mcd&?U4BBFwAkGU7B8ziJ zmsjtN)$j*@j9GCp!%!3`L#ciRzx>cFt?J>v(gif=vwm(@W%OUymOw~@VIstE>}XHQ z60n^U8X%0}qc0bQ3t4YLxOwlj%yePkbXthkBsy0{33$Pc3jKl50BphicF;h73Jt&} z<0qk^DHnsRoN;+y1sV|GT-BsDG(3kqCswqlH?pyV*6UUT384QT8h{Nsfd=~j4QTij z?Ei$)0pSR1t!cZq!uvQyeY$t7cz+A0_kkqMVsYR9>qiB|@W1Fs1p+jH5YYj~7xi%e ziRhRceZ0g1wSX09v0Sz%pfG8hA;s-#TItn$zgN;`jbr+>A5|9WC64xWc06rsF6caR z$aimqV+EY}MTh9QkjqLLbmCaX--3se6Q@&mJbWZ*@J1&XbR`Ji7%d zFR`ga6CPDgm?{4Cu#24~Bg*SHIZqp@hh{YM%Ev7}pL<79Ux89b&XQ)Sc0{>P!7HX8 zu4WJ8Z^2I%y|oX>2)q?ivN%<6UzB5dupG)MAT>%a4YGWef!GQqsFX=XjF9MobOf~} z(vta2xEJzyto`O1_c$V+>xmd17a{4;2_AH*ciDP46{&Bu8@7q1&%E?r)k)ILn{9YA ztl9B?sEiL-G~k*)u64fVQzI7qASV4B z8SOp~ITYLm5+KV1xsvUrgtpvbp2n;2UZvg}Y_$nO6>D99I%yEoyt~M0<$fz!6rw&%P^M$%-uTLR zV-11#IU#QNNlHJ2E2-b9I7+>uhW}M=Ms?A+&(%BOlt_%+oGW?9ODb!@dOCrQ;s`rvnv#Dq)o?Wy;IDoQ?~C>Eq=nBp+a$OHV$n&B*e9fJmD zr!h)~r#4WiOOCyJ(#bW(c9oB+gyjXu z2JNqJU_XdmakC|vAjsbT3*ijFOiw=N|KELie|~W_#}^mnwj!Gax#ykL((}3~-E$Tp z!)#R2)A#YEj*dGV!Bk@;gR-?m=EAPE!;NEb{b;H$;+j7j*F^e6?{es;1wC+17llJX zis#vlH}Zhqa%cfKwVUha0hU*fE6EKCj|!fNOeFq+E()-{#E`IM6&8lFX@-2K=3pCfaHo3Io$6_HAq1k` z*?jD9PI!8mmX;+i4;}(aySUNf9&K+tiX)r5DWSv+DjNb{&+CDG=AV3C4}#0~H=5r6 zt2hAI6#N%mi~bq66@=daSSjCW1l@?4xy%SPQNWw_rZMN;{u@mRwg|^5=7-@;RQ|W) zdw$_H4Gb4}`Y(>4bAG{7`xU+g(dX=kGyj`-`VlN0d!Dt3JK`_=``t>{K*iIL8~7i+ zXxB2A$E3VqxdYb_Anun=cjPl(H!)O&?PW*SKV8fWDO+3MhYjmx@?A zst{-0t(TNFvCjQRZph!7lzECuz`oK?qEdoI!5VEaOYG)=4GRj)wR@dQyU6`k z&K(%Gf%%XC#_v%H*pL%cV*KAgrBFLZ+t$KM^t9}<^o%mlRyRb3J8RwsTcI`?4n2f{ z^Zwc<2$NedEx8kO2sSDQL-pjHlGXzd#d3#_8bJCqxY2K^J6& z#mtI01qL4h)}24l9*i9K>w;>G7u51ErWUR*)M6VfdQ+lbZan_H)S|SjO+mjy%LASB zIJKA&KoJngF&Vptw7YeSrk@)&xdgWyonn!9(HjCMDBal@Wi#aB=T61?+6omMBW^Em^$YrRqlaGtRxe7 z!9ss03t^HV?)3$)U3wbROmw!p?k;Wg$ zLckW>Z^uF`rz`|)GJcYUQsKP1oH^LN9i)}bwm9f9qS!1m#*%u4_jNKmu4! zS!npL)DK`oPFRTLBzkeSk@KI_7ZO;`&>3XuoOTRWwSx3B((V{+E2ucc(tkjQRffNN zpQ{dimv>v>jHwN+9gOj%eSDb-^v1$V`DnLt*<@_g++BZD z;{u!hPHe)y5ABd+olEWUZw?0!?gt;}aCyic@y9(O?k zSii?6U_(x@iS+`TzQd-3=*GTyrLmAh4?m=|Zpr30C-vb_dD@B{QPC||4M-QyQ^mF; zo`9$?a%=EIb#E%Wo3z?01lXs)F<}~*MpK78b-C-;DK_hcV*iU3oA--in?YOH6QzfW z&z@JY594ar=Wt*YQF~Z@l)YU7c%>dZOtvX+M4;}g!Y&`$y-@7GQ?UV4`)d@@$5s93 zRP4az0|l-%!-tRLV;)Y=2WAlg;5^2gOtTevUqQZ_o*4N96&u)s`|T8)?NqUWO~y|u zwmTv_UY}41@msCcIh@U0e!sw#sd;wZ^4FdF4e>@#aUlV0-zzq-At#E>cA?ncDRzOK z0`cZ5^#^6N*zrAMYx*f;6j%Sd%n*D%We*}9qXfPx_U@{4))%lhsFW7VJMzv4JhP-%hdFPZb;3Wc;LJZ;(S4huB`ZlE`DX$bIjF zpMAyV5`5h(WltVq?mlm_G$erid&LGePJB6b%-OtK=~Yg$&Qg9`yK91#II9q z_6x=S7b`aZ7sWOd@^I@Q*b8MpuVRZy5(X8c6kWORMYPL;P|uwv`^uvBQCY%TlXUvR zQZMy|V*j0rO_(`9=Rgu=A8<~^Hu<=bL7nnu9^YoB@$H+5op)3)S%o_^Vq5}qXwl}y zxk$qL2T(5~*;Wp(~-f0`<0HjoeedMql&0J?{>%WsRx^gh z4=Yj`G_T~O<4fE7Dh&EnBt2fX?HA#Z+`N+eK0SkaStII2q>1Pun!=}hVBBy=A%gVF zA~MhD78dgRP*hrVa2@Qgp=>kpzYaRe?r27yN?^Ma*=y^5f0=gi61X4IXzf_k$7!Q} zsMRJ8P7^}Iz~(AM`DiX`O-#OS>y44v8gqbG0PoU)a~zTuw)X&>KXLE_)l|tT8-xOG zio>_5hRx-9FU`m&^4_l-N!Lc_Zp2|xxTnrQV2`MV8ct_6W$@|a&4vs(1Xt5%yAPN3 zXSwszWd}OF6=)&2k9Lq(-nZdI)QaBx0?`kq6eGU96-9JT{>IUWz6LuCI;W^dn^=iy zy6=^8)A_)$L%*dC$j(@^ns_<~#i-JjUW!+Zcmp>cOIT6fbz-rKY&U8yp6UzLe%b+> z(wMD0pfWEpTF@pdCQ6mR7aQ;fHepP-!xVGk<1U|lN}g5M>r!)6Df8obqKYo2_(Uc{ zj#pj9GHQAOD!D6prOIBfF0-u78NlRNl(fx@8%%C67W(9m;bO|Xi_*RAY@B6`h)Aqy z?D}p9BL5D*om%1b;u%PHBa=<}l-;e%podnbHPxJLS}zj(L&E%b;isew5cEZ-`)9 zkQ1-W7|?QG7XgEpJpgc?$qE4mt7lCAxGs$N6DM65Gbqt{n9D~P=*zSU1*-8}xYqg+ zlRiehj@A(9f~Mc%7NQjuC42c0+gU_TED2UH3{FiU<*{KaE^05kZwWjutWai7>CuqF z^ILk@ay#VD`?9HIi#o&jJ0kU}>RhaQ9KlXE5iR$C`E5!4&X616T#s`z@5_%$kE+?( z& zTZ5y>6na zSPB;?y!yO}l=7mve!H#_BX>JkiXGyo@d@zgeT@-Hv zNt8(^9d6Ry0xQF=Cy8bvtwaeP$8ay71X}+6V-2U|{p|xWgbB8U)`oav`_-79KCT{r zC745$60Jw}rtMh`_QoSdw67B`;|p@W)#{shii$DK_E0|*4$7-ZV$KfPwC`TqKC3rP zT%)#$u+Qw(2TlSbtSlo}1D-ddTA-{w(LGLoFtQk9;8w$*o~wx9oi^>6Ia~LHGvm5$ z;`=fHyJdf{2~Ap0#8cEpF_&wR%DG9Ns^}v5v%SSbynvXJV?8Ij4>2G7S!CZ0T}9Nd z9v-}bDdS(2<3=dN%t)yEVzZOo%?kfY6;6iT%d1cIj~~c!zZxBMgK3F^k1y|eIclPu zbdUM!vlYn_>x4x*(&b>?>-Gd`w}k@&RZD6YF_Or&QtsE3m=_Yxv^Fru$@wcS-S(h4 z=9VJLJFuJ@zwYNBQZ!)k=E;&zBF{4z<*I=?kt<0N`UoeDTZTU!g&2^dK%#Uea& zpB@U$Rco^G6qyliR$NTT&#hcnmGb2MpN=I%ag6AcGK!mfrje{mX)_&7H4tjK&e-X5NfU*8=QGwvwW9V#xlLEThf8+*ToJv z&K*18y=TEP_>`dk5Iw)G*{uE%T+-dd=v|xCp%(u>;%7-sKXNGlo>s0WH&62x)2U6K?EG;^%o}5!~ejoJ!HsmDg!TG-t^$21#?8BOlywWCahSAgP zL;u>h{r;Zj4E^CND}h@B(L!hN&3!)R(|#+nT{hNXQ@t(myxj^b^rk(VZh0({D0Xl} z+SgWQzzqN7R%XB|7|#4&`nv?qXb}37fnW++D>Kg1q)){L`SqqSilVZyl;=;lr)Ino=bmxI96}Tl_rF82M}_`% zP|0}#mH*-cDZwvLsa}y512AHlJ@g!dfbJ}jk9&ZrE&Gdw?3PU%%bJ!{NYqX@MPxw`k^$+RWo_L`SP znW}96Z-vTJzybD^eiCq+bkUJ0RPqV7-am&Qf|aG;7O}~uD1oHhe<~bW z+iI!^3E=u3aDWXt0UWLi!1)evwB_z;WIA*?xT{8Xk)cG~j4`$6vkf$hYJMbN$6AU1 z|9Xc3Gd%fa|JdGPAOH@qKJ8~%2w0o3Qx*dDD*tvY#B~~_H{cUbVC}<=sl3y8OebpJ zu1O&1D^@mPFnsCOC=W+U&`Fej6#Sl=UJ5qPwG~yZiF8Z{e?;j82=bMYFbTsqB}I;$ zWjTXklUSnOG8$g`?_YgVG4w|eEduy6`M)GU@~Om#GcIAXH!s{ZDxXat=&0Zb7Roj( zc6rA#cR4+7@xA)x#KR{j{ntN7=?ih99Y1B~y)g+UGPGRsB^hdHd_n-V#;UARtwlw; zArMK^>b@~P;^Nx@tzZ<38~s$#v(lDy)l~pZZdG}zN@H{_o&{&s-W%*TMtTl)Dhh8% zEvvc9xGTz%wELug(p7M$z8Bi!=prAlNKbZ`9>MX(ay^12MBqUYRSKPDKR%qhFO*_M zuMz?Mp$v*W^h!#3&y$>^j2ae9#1tM_-0jQSCKC35rtoV@EHZ^$aC*J(Ahz~W_7JDC zyJ$R$!5p2+ZNqB3H+QI0RbpfI7AGv4@wZbR8^DiA=h%|HO3^fUJKC;8M%;wCS&b57 ztY%bHy6Nk|O0;q%71lCNCcfVqew=R&v1Vx8xuv!^cBW2z1#5_^WJaE`uU5 zICZ&EB*w`BsvQ!Q&8Rvevi^Y*ke@K^w6$Et8|JbfH8GYV?a>Zah#-&Aa@JaO#>~K7 zdTOI|mBfz|Kq$u=S_a!j&7Ddsu}qy-uVf`62aBm`(|7YK^BuN5A{SPCGBv%JC-WVq zifMDM=ASSeN-=LcnrUlJ)Vmq8V892EsqWtDUrXk~C}VZ%8ISf$pG5tDViV(qO>~X_ zPC1O%ZG?%%wfk}eZ52#7b&*d5iscEx(|mG$ogOb(?paJ)uOLu;{2Zl+6B4{C)eya( zE>TL>eCwu)lT&Ay>2b`yL-25+lms;^F4UI`*9J2ZirnBhnikiif_f!6$qwcn83iH> zUL%#}!r?)Z8{krWd(;;Zq!@zkv;!6yOOH1Plg75;8&B9*151uChZLh*8P;)f)K{nv z;6C$m19Jn;nEr7`eFS_bM}27Act9KCUEWuKU zV>)^rWBQn5N!HL|1CiYw%N`;dKRD*t(ocKfghDB3u|kaRfD}G)bJi8_ZTRO+BwS2_ zaTfO3ZRvbx{_FN9J5j{eA7cy`<~m-Wpm0CrCqk~?SnM-tCJmJe#BzV6 zN>dm+nLS|K4XvCW##Nd;Y+DN-mov9&c4*hWPGV$DjE|_ixH~10&4;VHkwz0r6jgPj zezQXa);`|7FVIHFT0P;z*i8D;n@pD4z?5N#2HmvQNtdA|;>7XsmP$Tu#BA^( z9oq<(UGW^Q=pkQtcOa_zsU~x7?i#Uw4LuzF{jcM~icMur|pb$JjTQIr0Y+S3*A$_^8Jyq+&6PH&#^iO0w zgXqEsZJs$UY#;|w`!dOyJMCmxOj}qUMh5VNY>c$_ZY_5Xzqs09KxcRFr5~}zu4zt= zJariPBKf!=8GtoHd3_U&+|B$xvzlH`OJGm*Iw~cX$&TKW1bALwfpMb;ok*`WI-g7V zizBo}>*UZCKQ(J^@i6DBPZS-%*=;gX68vN&{P6WA5s|JcuOdRaZi$Y2!V+s49rVb8 zxspoXo=t~fW6I5XM5tVt&zpz>UEA);GfK}Hf z4*F0LIN_ALomkCP2kh3tvpq;YwHZG3xt6WRi4X1UfW`dnG2g`oa(c=Kc5=-3WdjLw zoN)~Z9&tf~i-U%moy+u4TF0|3;$UsNtWzDssipF%&L5E$F6#xMYS(ZN=|^M0gHBtj zmUU^@EfFO)_cZ=we5Y0bBe=_L5BaCeT%Hd}UNJ?= z-K>D`XZ19LV2;g*;rij3z~6Js_Yo|ue0tl<>ET~4^~EvYd5`(7eTm3ikErJ)F#;b5 z{_^`7o&YY8t11I;nxs;7Ssd`a*Oq4#mmg^x&cxqAd_eH7t|WD$GK%9zp8D?_k=dg1 z^APE&^}q9*Mr6n=dixXT1F6jadOnEnX2-_b$=L1;oT&#{WYu%$s1}M9wNUksU?tt@ zNx6N+2Fi-SkKEKYp7s0@8CE%NDGpWBem#o(Y;Z%SWtVrU3fm5F zzcnIr>Q4gOTfXrpK{&eu!4$M(KJL>e2llFb^W-3$-GN{V+A$yZX%r0DGWy9V7}~g| z!7wuNLrwMe$!qTF6I2B6(6=~xp=6uEWoo_QDf-$%iK4LON|abHBizKep5M~+)W z;$LCzUd;_?Ac3LLiBr^uQ@k8*CoLtx8bC7owNn}}!#}xG8VFIaf5!B@hljv++HbZA z5KaU^Fa_=Kko)xTaIolsX!Sk~Y}m93ob8nMyWQNC0Rx2y%S{=>;YAOd*7u_XysZzl^-zy7331DzLrfo1Gj4{sh4kG*rEO3VgsO<0padsUBviBt-0v5EWxu z+jVyFh46;RT5?ou5*})9Q*(i6NWkS&;Ct{_&wF4)PJr+736`8~d;c$OjSE6v*QSVc zC76Nm-tc7I4Ho3l@C~diW7iwVYbmnouMS?KLZ9&vyCZIgZme9r7KKm2GpOQ-FfJcU z6->OuPQ*DMQ*C)n_mvHSedYxl_P{9zAf5SXLBPn?lBz^uePoP;)pkCD z+S$oF8TdI*8d$bZ2@CGo^IinL_A7{(`{MFSD_jR5?t&?_4E099b~_bur8mQ zu$1m4-R)kh5MFZ{n@K|X_*`H+cHojMn-28`j*FtR>(zgbuud}Bk__MKd0*uGTZ2eg zqF)G0wxdVxNf1S=^#x)58wg7vHos1eb=a`5_4akl-}rH{tq^y0IYv zJl_)*upuXe#dASe--XnG(a;$T2;vt}mEL{^sYw6$?h+Bi-2AO9qUplbqrQ@XSuCg9 z(qCH(WIlkjeB*W3F3u=DzODxc%hpHfKyvJ8_<3_lkf>#R`Ds;fxSN($${H5`17XG) zON}-JA^ACjNwxsfGIe~le3Gc*cfq@``LrS)BrO#>FkX`7P$`4xajf$Yj_UoohA#sO zX3MP%VoZA!hV-iUat*AVg4v@tmm48^xZ!Tm-@eV)@;Oz&)Bdf&3e?hoCKbHPOI9Q# ziCdng&urzQda%$+HCRswtHFxOWQ+(k&PU3g`s9>hB0E+QtGT%oJDMB2zwCPekjLitHQPcib2GdV@t8!Z)Gbypv-b)hLyK|jmBY2|IBY5PG-t}F;_R&^aep0y z;}kivj-dw05ZWL^$6rjfb*Z_#jR`-X;Qm~3;~`Ia{U+9Bjb?{C>6iGg^&)OwZ|LqP zs7ukc1)G7adv9XgkAn1uHXJ@Lf6`X}buqV9^n9RJ)h9K&8CPFQRX>3Zg&>SanJ()^ zvpX*^vLretGu2lH;b1=a!pz)C6wp*C_l@2p&qo#~t)9JZ(<;oOvqoZrbl`wn=)y~{ zQlYe%%UapyzgD+qc){-$o4%W)x0xCTHbfsBic z`?Yr@l60L9zM5WSgiz@niULuF;x$>lNB8}a-Ba1&lbLPhWObpZawX&_u{xATXP?jAWmTHHi&5!IJipsj5Ia_}1a)KPR0L+9j zXAIH&=TrfPhpxw%MBdZ)bDI zc0ZD1O_sm1WS@j382JkOi%=;i4Kx^^GR%|W;cu#f`EEM&znogbZ?#i5hYzxi)pjR`9|eAf~cvKud6}iCZszrfT|8*I+AF>T9|HTg2m$#<&+)QJTtQ^rG$YCDm!V z#t)thIK#R=S5TQV`gI-x&$&H>dgc$;LmzKt(PxeyNp{^rw|y1qWk7U&X^`Rwywk0Z z=%4ctPR@JL6#InD@GoMpAbJSmUpxd!#JQWzZu(FW=k*Z!k>%|$qZTpLQOjA`UBw^j z$`s42@CD;=Nmbu;&k2J1(Ntfg3$QZl8ybS>!$C-cVN$fWNd~gtf* zUT0<(ga9@Orod{9PGj8xPb%tR1;}sN`S;HGxxWno%X#^!O^&eFv~70%p?X5&uOnIB zbCax?YXo!k9Uato{yjIh5LEkc4Qx2VCMksC{zoF!$G*rv!kfP*n_$P!so&~Sg$v@nRta?*m@>^R^&(g zK_%H_TJArPWP$B1-|!#^DFPsvf|gCdcglmnCgUf0aQb5p>-AT&f?STzmy!1607>!v#pf^ zK%Rn&{PiMZTgSl7zGJF04-k5tPCk8ESK`K%_s1+2*p9a36v%?h;oKz;TuC!3vmTyO ziyK><3Emx;0Fo4~X3?6GNbjPogV(GAb(0%6 z7dmM}K9}+!=lAoumD;bo0LOn__n)Jdy# zWznsV!>1&qOKt>_MnI5|k`R#Y?(XiA?nY@)1nExcMi7)#LP9_(2}P8U7GCrSKV&`c z8sI$oRS*1q?ODUUXJ&t{ooxGR0ji8sszpFJO07b}6p3YZVWU2hMM%X@rNhUVe3h@+nHKUuNKysMb7B_qKw+=d?nt6OGc1uuaZ`MF3}-yuK}oRhdj`wZ30KQI2?r)qmjU z0cTspSe-s9S%3dbm$UWA$6b#s^bI&%(@?mRwczK7&w%qc0LP)AW(K=Z)PO`JV?wYx zrZF}cZ5_^|x+ki9E>EpBaq15Q4$!ydw*wCQK5&34<8Vt>+t?wu13Sv~VPqDbxDk4ahauY6JaHD#)A%w>UFaTR?u?vaIo z1hF3l4p5Oj;IN+o=k&mlZ85t-Lj8EwpLJ2Jxjl_B-OP#`g%-Yi)ZK5IaH(PXV4q*0 zz+bb^@6Q3wPq2`cKD~He!s_k*S9(jPU6Dq-!Hjm-a!V=e*I9`D*cN)6 zv5tZ6Z&K^@>U_YH66a~noLFr*i<(a_U2B9!mmV(sJPYmQYj)IZmPBlwb@rUFvq$)w zg>HHy7AVOt3>KeR=x?x4#KP*{!LO0FcMHd%eItxgtQRM=-Vx&6E?N+TSXBG);t#YC z(6{Bcvk=F=g@7vKlooO{5s6qok7O-XC|C`J!oXbsmsr?#u`R-q9MPQJ|6(a5h~ub* zfQsx{h~vycr?*fuF?EEA0%2<<44SF#GhRyTGAhHG3lSPd$@uTNsnk#ixqVV*`wo7>=rinAqn+5vYRtCvg~iHTeQfz zb=%d(;NFr_NSzSTADi<^1=v zZ)XW^gVF2M>H55(U%{GGA|G`y3t8z@YZvP^EDx{FYOz|VKud0iLz=cjr@v!F#Y|Ll zi7xYXPQ$tRbYuQ6bczm6F-a|J9HUJ|DoAdLZG7D2Y70XTA3uC3s3TLNx~i{WZQ<}_ zTK`>aQ5mJlO-9RUG}mDhVIzpl?Sep4zTp0>Pw>pjsY;8`#XSU$lMGfLkX$tJdD%5X zgkOQz-UTP0E}CSsT_aaT;3QDc@Vdk@_gYa-^Q8dJN;mEm=Vg>)U#^;qqU4DZdfMl4 zi!BsGhCVi}U%v}p2Zzxb$_5=Oz0k$JID1*|zH9DeW%yQtY%fRE+j7$hXl}b9GkM_} z4mvc*(3`k=NzOiyNY}nDbopczH(BFi(Yk@|(Se5TZY(Twi5dMF=w^sk9IHL_78}&p zWiGi7K=~2n74Ejz)_4nEIbWwob5Z7Qd_mp-m3R)lXImc|^y4xYQC#xQQEbJft|GUE z58ST#Yl9L-BV~jruW%VZyc!!_=K+H!u>;*alo|u&3x`7gxMePvQuda)_=wCdJsFBD zT(qHUZU~-)rXX(y3#d{J>+iP$Vp$|!}{^*!&(xCrGhacd(THoE~;<2MHOWbCLb(wnZ;Oj=gplOm!Xva zEjhjM5b!8Ksln3}T)19UWeHX<_3JV(j$>zDD~qJn)FSmf!Svk_P_U^2uZz!p9KKYY z3jv2PUo~mu_R}&i3~&?$7F{c2__VXk>x7w?=(o)4Mgx97oPo!j`f)R_Pv~*l6xHsd z?w3{35^|zvXI7L{g35a*B8b%xiR!jq{Zmz+Eps{TG8eLIjb-#XL>?aBA8(*tF`XOS zpN}f~$6G8CZ}J5_;~-MEaDV#f!qgypmSmaJqRAhac>#S}e!I-;=KeAlpjYLGJ3qnN zl@o-XY?;f={hSf#F*;?=n27sPOTRdVi0TT&OHZ`V(;q6{y+X7X)ov(pMe)3LNd^*h z^JvZpRAet_ym^*0o<3)cXi9D0!R9XVufi;U`v}_Jf~aCWa+boYiJd>Y4YlOl!7>-1 zz`aUvKfG1d36iU`(Bm!~K@QSU-5S61tVWO|jbl=xZ)4TgT;yi5gavaXWR>29cfZbt zH_vSN7oTA#_RWUOY81wV?Vb#p$F*S-%r@#=mB2zNYLfq(ja@1QjN4FT7qyCDv3$O9J+B-J z5$>vPP41UCP0lMbxi~`N51Z#^kCAS=7%De zKS_=wFO9TBPvBZ@x`1idV~P9Z!1ioP8{;Y7)FG;PZE-~wl~!%0_%l-k-@a$&8rE@mOxbJJ_TTprFx-UcVcHpnG4#H-eD^w{$hOjap%%6UU^BvNw+h zf~+cJc)48k#`q?yR6Z2xI9bqZ(HNx{UU4pd{jyh{BwHs%G!2wzOw4&sW3gvK)gjT= z3E}71#o-&hn-I7n7(;?cgyvYCk^P8_-W#3C^}06893|N9j3>0=ITtF!^dONimRmQ} zue^EU(yVYxl8=cz^lhW`t98%B6+3YkL*hn9rD~*i5uQUrney=C@oGNfVim+|;EMvn z7Yqux(YD>X**>LbJ+g;ZH+lXFYYeF~&%_wn^x-om^;$~f-CV(U&RB?AujPDaj16eV zdOYDx6_Pu7$ezYWOvc%~xTm5?-YawG;hbhFnWY7T!sUw!B=yWD>un_2B%D|r7X6jN zuFOGi;3THt6*RmT0~)smAO=*w;CcjMwKu%c`+^aVGl+f#56Mlzm#sD+Go2vY`PSAP ztT5$#GtKllPJ3a z^y%_%49#ZLcJ~G+yc$7=4}%9vn+~;Cf8OB4m#>2pD8y@yfhhU5)TARbgGF^+X}9LE zr_l7sskV3)%SSRl*L}`J!WvM*=)UtlHRK&UifxtlQ-Q9r_#PX~_d23Rb>YbYIkPqK zN_XCKlgYrDjbF=v;`3dMRv9?gp5c+Je=s-!dpC4suH*h7)YZk9s)9PDIKD>xsoIr|&t6YE^kS3J$Pg%*o zvU33jq?K5TF(C?M;dh~*O_4wD6nTkn2`K%2_82S^W?tdrCZMa3`5Kd2csI!NNVnD+YiA{IBfz-xm*x*dF>h1{t6JF!HKkV#$7(z2}$B0 zb`cdW=q)ItT0u!qBACdt}}nLjxsIgHYF-|hz8eAiyT|w58KA(u$IJb2*USmVQjR<5 zV981$o8(lNuD>HYqnX=hF*$nZM3_TxaI{iw7MfznPDgnV-5h zM&L5fam|M%8-LgKwmWH*clAfRX){#0%sJYcdt)XlSIa<-7bp16%=d2&I#A1|Eo*mF zNQWJB&_O!_Q3)T5r2?!jkToOciw{f9U1ss~l3m5xq{cF8%-8=w^8r0@znz@8_vHjs z8K;yJGweL)YL~)Dr+l-64s#`&d``gw-?@+7zE4{VX=QZW$dnei@FK!?H~bCJXDyD8 z)|m|c&p+wBULg0o>~+X)u^1c@4w?x zzmvA_$*H!KQn4T3d_WKEpfP{JG5Xnvly=E?WnQ!fv3s=?@(*m^fxaz2wC^YAs5wFC z$@+9|?YHkhm2t}UT@LPgtabz0V6rkzx}=M8)PRIyH6bJu!!_@b9e>jC8<3z|N85Lx zB75!ot<$&f)*Ch^OrUXGv<5NRWjCQ9+(4u!gN&T zv)^I@RmLe>Oku`LnPWvuFJU>Wp?Jn`zBA`B+pWc%sHU@dORWCLV;T~~bF{?-DzevN z@|?cKd>SQ~g|=MECro4CDkQOJ_!e2TSWdJnOzjC@f}?u?&wh&u6nCV>1ll$5VDeF* z3a=m(`AQP_{kDKWv>tIGmF0l&K?K{En?w^YC5c|{9{?CS*c|a`CQ!UTPir)Bh0C_O zD!4@|&uuzIw%;O!mmH6o9vo*YRo#t)1zoIXS33zM2pqmhg8pL;qzcL14DVz{TW`z2 z=dmtXtz-ydY*3hLo4b}G5!h-@lpRGz8&;RUPCiQ8QeBK)S|wvLuzt%-^9eqKxSqf= z4*JXF`g$iyy3HihxuPMH#!{n8*f-7eyqb|q-Rm0`&0ypNLIWq>v%p&1HGnEK?ig5l zPo*O8yn2299^?eJ?kBYfe2eWq->sk*u%lS30tg8b4vHX;b+XIw4Vl+h7RunL%}H-j zKqL!O>OyeJwvM33EWxx|$ih59&n%YDG3kzbF4jl&jEHr*KL~QVBQqOesp1LxbK7`P zTp3|jp?CeZR19x$__6Za-ulAksZ41iepa)cti>Ti=Tzr}UPKV;x$~K2t~J5ZAI9}@ zAWi8*F)opsR}r9)eupBvwmcp+V`dLAayd7>t~to{_%8itLf5)3WhlIr6g8cmnz}VY z`GL!TCry!O#ro;F7BZ7rS9`<)w*+I;8YTh#JbR93aMeQH+B#sg={T}u8KW-Z>~R?q zya8x1;$T;zJ0$E;E(tH+?!+f+)Asf;TP7PN;xsHBtbJeD)_HknZO<@u~q z(I(xXf<_9)`7b{6bQQ0u9zK6()l^Nk`3bIbw>7+$yp#P)t?Uq$tp}FCIT60>$xcQ> z7ONO+)N|F*?5ev2-fu1bbI6_d1CkJpj<&C3@S)1BSrH3F1A}4FiP5L!p1%eo z+7Dy|cIhIvi7iu!XXEIJB0m*rNR(Z~Y^v%!A7Vu?CS(`1fKsuXzhRBazngu(r?@VQ zyJmUU*7i9y)S@H}I_pZmZ&+v2)oU;64<;WWK)6e6Y z?O#NBO5JnD?h~?G_8Ys&3QePOL7U(?PZFQyw zSK<$-Sd=g>m4%#O>HjRK+rMO(SkRaZp>;@{b$8O#u>@GBE$9u&ryslTq z+kVSSb%<3O?Rwr^#}?5S#4$(7@WaQ9O@QUSHgc6R_)_Lu=bBs?XO-#{yOb)M!NM(a z^-eGo%E1&KplhB@;W;ve2WVc-p?3=?bU*h2s;ncqkB`J%dDbKkPNCb=h+#0D0@FxP zHhA>`<2~<$+1`M4k{#?k3KaN1vh!#FL|}jP@_kIxkaH8!?LOwT4&4K71GyI2{m~4T zG)!0bziL3A*B`!W3ZMx%!KCz$5oXXy@UC@n8X)+!?elN6@Z(Bus`$GmA9@T<5DL45 zf{ft1a`9%!b9i1a1+_*(qbBx8$j`JpK?}T}5c;^l!1%$yIl=b+K7)aH_q&BZu4)mu z2RmUvUg6!WS}CqQZ!Re&Lz**(FoDEv8kkl2>wL<4Y@cSj3RMeXuju6RI2Og_VTekO zO)@55%bm|=UgMjZl867N#_Zo2NJZGZa^nT}hI!4IPfzGmxovLUEPM4Lw%__-i6zP8=7-74A+8+%Ccjc->6$2l^D~+w#Mg`~;(;CkQ>+ zKq}w9Pk}1qls+BEl5M(Ir|BP1pVn4s*4>JlN0=BpK!#Eim?4zzauFF4#CO!EKt=X^ z%6EF7uD*MwB0tgC7e|U5Y0Us~Mb*s>T;WYGU?PYYx93q6-KP;y+z}dCKE6sw6Eht} zFx$S35v}^nFlZ=Z9^V@cOBBwBWy(SHU?WnX!2gkrNP$i{KBO3+`Ct1Mak4F%M0=t9 zT*cZ?P#c~0tq4p&CDo;Z%nfgRp}DHXvR`KrzGGWNS)zzZO|gD?^4)+{LOCNV8LABS z275ruRD8-9^CH7f9HK^6!He(m>kYnnW|0$GME;va26K$USyi!fYrfyrHa zOC%F0-^oo@#I}1~lpLj#aqew?h*>jU`zJ^c|51wo71^^0|LHB_HfezYT51)s#1DS2 z$YMOgCXjmWDRElPyoEqQKeTx9a9kIv`@la$(6t)-pr5F9dD&Dew^-nVbG^zTw zsE9ax(|QLEy5N8Uf6d+}K$Er)QQ!niy-pB%0*jpBs>fx^49P@F5=-o}TLtXfa$uVT zJz^UYkJxRngPV8yoHa)5ejQu*kIfco;YaSkYuvPIz=Tkrz|O^Q14?8wj9@;9)Bi?Pq1S3Uv1p?RD7 zp=~ZRwUt8Y8dfRTpcNG9=BrYcKaedz-U3aJva1i4NUB*LS%&m2^?h$P?0^h2%MfRC6|=OdC+&uWfUu6?`$pUtoP>$ zpahDpE_e9Q)UbZRK3K^O6nNwox-Yi0ES7P>K0=r?fFD@pMG#z$G#d_KbfDOZGb6vP zatH#w5q6==A31JC;}Ek9$=RMC6Te3qa(SK}k6|{p#$=b`fH*+cJR=T5%m6{np`Z2y z!~qK3@0rmID#Me{B1?0a(jLH`m%Q(7o9!At%5vfH;9?kML7C^TBaXnaiQ@$)tIN~Z z8UuaTj)QMj;5=Pe7^qDJLOSTYfv@ z2<{UHs4`AToK_lwan>^5?1}c(r5!cbdt=j=AG&@llW@B;iA1D5vIYqfJW3p(B74LU zJmL$W8Apd2!BWpoB$7oY$kojlqs?u0LOm=j%OMDLYG(K$ZSqH-3(1Bp~@JlWt^90?p zKS>jd+5@Vl+df;xf#IP1CvXU1j|t)bz}6M$+w$ABu0s2*D^O*evUSB1i>Zz!4^u|U z+6irTdUw6+nZ`=;OClK2`zc|mTU7TTK|)7cSD+$$t*g+HrtJi+YyWL1!4Fr|X}KQa zrH7SpHdJIUt;I+sF6Y0^WA)bNAUkMXfdY@*!U zuUl*|Gk0u0R^>attPIc1#(s||@M#Wqn94Ae-yKFHWl-bZl5w#DdWT@mmB6pd_@e0K zp5M#pGKaPHh;!bQZb596&wpIxZ;NgkjfudATtx1;(x1Uk%0E)G?G!guT2{7q5@V!a zLK>R6c5rNw?GhESwvyFG?3H&BY1H;E)7X6jRA?Q97b<6$5fN_Jk5`@B5i6i%^@cO_ zZAx6&++k5+dRfly2)byh_QX?#FxRf9rzt#dvMY=-w3?0obL(=_5L{%`xaqtTuXcvV zoLB-Fk!-3eQT6=u?Qw@(pA@>@qF69^ z{JwHoJx)I}C^*}Vn~d{X7`9xmODdQg<#di)uB|(l(>qNk#dLOJ>Y-v^ZjB_D5qzp% z0Z*Z&hqhQN1zqg2J9nds$vwFPq+Tg#WQcNW5h%g_)H5GJ;fnyeoc ztf2GZN4sKs;lxGoO4KdPD1KeWhopUZRTt{{t%hZ0T-HYgNilHEW9E32uB9ywTiYf# zQNAtWyOmm}#&Lm}r+R%M!H(5;D7-?kI@#!&iO5KHF6rtGiM@@+f7n6XyR)L!4Ki4i z0)kE+na)mp8EmIJA1<<1Wc#Ap9MViZ4;Z4@Cy?+Ve*&@cQ0O1Gi0^X!-XgyI*n-!i z7eKR}Wl-=LqV9atNjvDH*boCqW#+0i_bicNk!y9fEUxw#5us!HqmzN3&x*!I#U!Zb zkh{Ac|r=$+h#|8SQyIC7V+l~U) zn+J_MoFQ+g&OgJHxtJ^_0jqF>vlE*1pQ`%z-9h|!@BX4Ge<1Z*g#l;k`{NC?gLuuV zXLSGFoB16Axdp|p!m+JIp?BU(nHODHS4+LFH;eiQ0v+hv^4kGjcz>Y|(5v#pou6P^ z@DqfdYzJ}S{nP^JF*;>xp+2q-_9SJbr-PF%G*9B?^>j1qvRa%3u)2juYB$wULf%xwEzk{ zatn9vkGKS5fdw;Z&yHKrfK}Ao@aTTpZcO{&!@GQsuIjQw8p315owbU_g7k~4l};5r zAIuOHrzC7SIRK%qI+8>sVsS7w33Sb0Gd2k{h z3bxJr2dWe3f&1<3CbDlgpvpL<-HJ$srd%WX-E{o8g*HkdyFIn-Chj~(b3|lty;-f5 zQwj+ZIchhcB71fdIRd8>*bN=oGl5Jc!-Q)+N7dzd_=cKfFe!>jlabWN*7IT48DAaP z4Jh!)EdV*|P?O!;9!7bw&=$v1q|S9Rpn5Ba9v0qpy(agrkA$-D`Sl8SPl*2JaJ~GWvjJ#2`k@~IXhQz}w60f9)XCK_Q^D#XWMIj$)&iSi!3??|%;`9ASU;%I zaH0GU}!^kW=$<2Ud;>y{SFuXhsLwVLt z&X*t14mBhk>*YMg7vo+rYy>pR@j9yC>g=NJ+z9 zbWIRk^Q9c}!;}j8%+h_6)q3M`p^r;9SO(L<9!fxgzh)1m6S(CB!Z^WIkBgC@I5rk+ z?qb>mNu&#RBJb;u-CB&LuzW|wtx_J=9vPAJ>o6jEhLOJ*BkJEUf;`gsGTx#hB<;8u zNt~?nQzX|sRghy+f6k@<5eU!c{C z4OlB43c)&GdnG0UgTGkHemk7dpv~^`o9Y9M00kbog%e=pq20YJsgYTmLvK1TpftG_ zACc2x(qicB4k_h7Q2lZ@_+Zx}puk_VYtaeV@N+P7f*UxlTk6@vT^u;MWHXhtUJt=H zM5MIwt=Fh~Af>PhVP?k-PW?K!h@H9RFLsN@H@6@<4mkRG&DrxE*DbE~1u$8ZPnzgK z7VK2_Q^Z+9&0(APpTy(ipe&RpB?g?i<==G6jhnVloE}=?rXJHRQkO05A?TXeS#`tA zCO)p;&A6`AwkG0q*@v{&mTSApa<@^bBp-CTYxI#lx|78gGA5QlXL->Y@4Qf zWdltC4V6hc>O%wfVoWLy@??=%;V&W9X2>YZPN`)ij zeB#FEn01Unk&LP+(VP5q;1;03Be!q@x8zf#MH$aJGH@cZdL2*%UXq0;C z+G^0dl^sm60Sf%>rr7+SyX7Yk$J|-2T?s!VHh0e9gZJC><7M#K8I_NyF$&_l(lwPD zMt&V}#LtNH7ZXSG8*$)4kE=KN#FC61GplX;c}1wJDaWubddX3QeY3~Dn3@g0V# zFOg_#Nuy`P`8SEfmSlogm@qGL{+Ps3{t#rAd*4JPA!~8k0fmt;5ISz7Guzalz(B%U zoj_Oh49et*YlQ3h$a1ZQPWH>6ri#psT#cZVDmTeXjS__|8+rW82bD4q}}jm9{pYo z!y9uJOUZ@B8ihPB*vBwXI7jEAF!6Hu9_qD;Ml$r8=6i@(N15R|JezL722Uu}Ld@l< zCux0FYTolqA+z=izD4`nGHZJlLeMK1%;(fx(zQdJXxMa4+5Isz_u{1Ove;GWbi1E< zjWdPnImFu<4i>Uq`(WO1m)PF1fQwXL?PdmjnS5li$%FDf72zwy{ZdtNoGA6i4exZL z1TBKbQ9{NP;~ou?#u}q0HrX^TaFYeR&3GE&ZxG)?dC%r448<^Rfa9k}&8L`qd_2%j}t#PaSP&w<1Ri$uKWmOOc;!s1N)20^cCS z;t3T=npr5fK5AP8>mrlwFwlcY!$((K{qt&L#LVcV**D@sv{&jVg4NaBh;`$(B&bq%jL z9O`DGuHcNB#9c};Kf*$E= zb|mh?mb+OyX%IT=%q2*iHH1*RT;uEOb(%@#m@*0OO>S?xAlx8w63hi1{oK+VpaV1( zxAiw2*Hg47@tdn-Cbq+GXjiClH?GkunhKUwg}%|7S&}_iQu=6jvyj=z5aI@niTdzG zM2tR+aBNXIg4#?xa*W5$mMN_^auYCz;Xy4ZLS z9jx5)dgA7KFnoPkSh}hMqJg($NaSOme>~RU_gzxD@y?c=W%=2r!uOQrcRLsICZv`F zR1xZo!7)>+{FhhJDfnmw@<`tgFI;jTv9`f#mRDptM=B!{er-hX?eSA8`@(B6QR9A< z<%IG2AFy8Yr<)CW^AS6kqOo(K1xXB}{!{AwQ_h71dfIixmym6J zaHfU4tsCrX-_RH?m3#F0eViMNvuwH#>g&{k1W6uEsep>?rBsrqPpR%1@sOtH1({IE z?#i~I6uijPW)^>Kt8LSV(p+E=T-|$+QUL`XNvVL2Iy__wp!DZZXjlMw<*o$xn;V_A z$j!rZBu?-jZeo|2v#ZF9$@Vg2=(+E&2nKoset7Usa30?YLQi&dpXC1Reg1;QWT7fWpI4VLczd>fGB*!Z*;kHA;jfX&y8FE)U=!tg7%uGwzm1n_6>m$2E*Xe zRd^mfdl&5fF$n?DBUQ;UEh@E4Up(C|mDMGVh#C2+rY1hcl()XLAcOD^Bm~d{_rsU` z1RFM-AoOH=J4o#l0;n=hNr=9?DFQTh$cm;mG3mACBXRhsEQ~jkxbrB5@+nj2L=_-G zQb!2^RAi43Ql}>b9Cu-Fr@`mza+(Z<63M7H3DEoPK|z!bMgv30R4rvQ2ZR6${3V3A z1evTFJJj@^Io=-5YN}$;;ha=Djm~cNy_#0ej}hAv?)x2r3yu+DU1l%mH%h`FEv1#{ zZt_6eDmP~&2C>BJ4BG~V?so_#-_v8y9+-L{FTRe8wdE{HdP!)8fFh3|PAnWV8@!x(ay0J=L&D=7Eu<~XRHI#kpft5e@V%6s{=)5vxL9+CDx|HX zrL~h2uAU)z!+~5IFWi+Deq;JAK}-F|wN9ckErjb0i5E@LmmCIe@UJg?mMv%C`JzB$ z2Ya8~oJeiHNpyT_; zUcah(fs5TA&~rB0s72zaV%OMHxpHkDlXte7G)xwV*P*G# z8M&5V4)|U4ke%^y=HzdDue$QQA)FXyhNg{SGMi#Ec!sY*ObxnlQ4M|Rc7!*i5KYk`W%TYB=Ubw7=(ZxyxL!EF@QrD@y*0|C6V)MRLum=_3=0X#Ak4x{)1n^a zRhdZIn9tSUE&`jK#ssEoMJq1V69fap-f(G|=yd~v(2Skt1@f;Qf|FAGjEzZtP<>sZ zX~~Tme!*~;?qzT}MUY2;U>?g7kzpf%f9(*2RIrI^5RksX+3agjh4sSiEqxKhjdbw5 z8VHUYR$2^IY}g+=1P!g1+g>XKJThkbBU z-XWNh+{e)_HHsZI2P4&V;mgpKIYO{!Fd;WX?{M_ifiunWY7~mVo*$LHKHcYh!}R^N z(O3)-Vd8%AWcGs&L2U44&8ud{=&s4qcJQc*fim3T;dp4(kQB0yNHl9EHGZAurOq_} z7i(VctLAZbub~>0MoMq39#`{|H?oHNIz9)QN-d^D;i=B{m{@^9LLg`Jz9)RdB7`LM zXK8+0#5m$T`;vp)cP;;J9fG%>ijxY^Rk+3;Q}Y5To1PwTHewmTqM-+OMKSy)U@UbW zvP<{#j6O9yBW;uVmObujHXQ`NQi4#aUF_Hng^xpZDbf~1dX z9;nEk=A}=s`G+O(u5C1RpWVqgG7N;!?b+>_TXP3rAt_ae zlg5zK>NN(s$%l%oTRd|mJLMY}5GXfL1|KOxuDI{-Ck7Pw+wCXTp4^)<@nn6)U@G$d z%YH(tOEp$LoT}N0Yw0*SNsTnC`#qgNul5gb)CsyTPY`;to=)lgp3bY6&8%^*O0VQX zHL77;-KiGFWt&$HOBSqT$*=Env=RGtHj+NG(O+yM{ckqf?!-ZFA?`+U`rowCPQK>s zt2|rX?!UxFg@JUVo+acvkJr*C85FTqRpqs?nwh<+Az}<&W(nUA9N$Jv>0>H5RZVc< z=bGZ260f1@Y)~gGMGm5N`ows<=o8ERbQ|pp1L$8mr7-GU(=p0=iCd&!tMSnIw`g4L zqEHaB^bc&OdEXX!v7HkVBy&_4Kt=Y1A#-|R%*&xVeo4T=8y&g==U?AD&Kp^5*y|O9 z%sd1OiFTEv{y-Q&fxko;{P(*kuL@SyZPhi@NNs_c!jm~rBH%su$Fug&U1CJuKG=E; zDDb!2dJJeO?BQOX)IcnyIZn>q5-NBR4c?9L*GQ00pYBj;)Ng(}r*a=7K=0CThY^{5 zjOZ+LmRzk)$9-Fl!XRaD;r7(N3v9$!Zs?_*6nGOyjK{CTh|C#A{$h+6e8b45F>!r) za$=ME|0G5*z)=*~=B}j`e)vl;QdT5Trj?+H|8S`hJPtDg@s92JbZ>KqVPYar{zR;a zrsHD-g_<+7?V*AKYFe)EPMCglsc!(-E3KXf&#~-<6m5%$ei=r9{-sl5L>|kKZsx%Z zo-ct2n9a-A2#+5OJE1QJDVmX(a@O?WLqmdOk75L<$R0*yPmhtZUGA$6WOs-L9zRg3 z_Rv~&w!xz}NE{GPk8AGs9_3m+zz9&_FTu#5$RJW#iuUXqs&xtcoQ+YV7uhNsQ%~C@ z&{134_}lGLb^?q5t>`#~TYz4>-_9+v!_fa(wnFKF)70{%mR1G>{+kGfb=Km7-wdN0{5USIjdN-e@=Duda2~hIU0fbh^{=z^UE6CQm(n zAd5-MA5Br5G&!f4%)9NiU&8ty;zCWnq|T6hc3oiA z6MH4XuPR`Gqsm^F>OIR1UbRLIw&VdJZk0>Pv1Xj~9 zyjZ{7n#Jm|H+rjg;qmK8YY`R8=7p`gG#4n4xP zo`F_~Oev9HV0miK5j)5fFKil^h(^$YZ$V@*kV$y*fFTP@g5(aB0HW-n&_8eVHh*vQ zw%9u|Dtvx0zhY6z_<8XInIuDF3(3I>VlvYGjm4$gvF?- zK2g;J|IizJg3x+yI0#(q@%GEb;S^nbLQ(KcBpeq_*tOQTHdA`=6Fa!T!1%$yIl=b+ zK7)be_WM?WD&v&t%*%?5IBwiteQ<;27GGS?!10`AcV7)#p}~vlN#+sq?T{e3qv;G# zk-c>QS z4t#!wq)(z*XszGUju62-{0^bi_*8P4MguAIl#l4p5Oj;>e$#I5)}8Nh{sO&59As@L1~{ zO~i(^%z_sRE>Pv2;!z#b^*kUBP~a~i4u*mE`v^*$7om1JX>Y+smZic3)1Pz?LwUuQ zsYwZ=P#kOm1Qht&Z36UvPMj0$$q00o>Ar@5UgqCUL-PCmz?aC}mR;-QXVxBktO>f$ z_Q0rGC}t)C5+5O0x4%uX{pwyna1G>MKX7Jdn$ont@7R_*f1S-^MZd8wa3B22&GYoe zH!&N0yE3R^#X3TIyb{TXg1h4BW6+Se1p}hTBI(rC#i%cDw?#qI_g@ZZ zj2mHs9xwY3kAUob5<{TK=jk73x_nml!8C>8;dj!yvA4HT{Q$EjVY`mq4FvYj)(dl} ziq}h~ZZNM5UP|YGA?LeXfcT-C9ojHI^j#(VsG0bssWDfZ>Vb-xxaz0y_u(i;FFZ=> zy3>IL{z4v+pNcANR=fDs9GE!i)O zL@QxS<{y6T2Yz;|>oRNdJ-6+hLNKL>UOAU{@*(9bJl5r+SZ4Zq_)^%g5I^(-Uzq8H z#p5!ib5WXjhQN*ysz2jzS924>&#A9$3dcoZe6JrkD;}h9D60kn@}bZ_t{<2X_G>?| zG#3QumeF&J;ewqCPB^h(QhjaAH077i2V^#(+?LHcJG~3X^-3FLUxJ)2JNRs$V~g6{ z(A^!M+zny9DV-F7xtT+jb8S~`FhY3RdCa$zn_x`FqHy42?-bXn{s4GB*#1&LV@_Jf z;obqASt}fYx{LyD_Dlo>=1W*vi+s@FTP4JA_Xk;WDeAr2&1)B2eUv-=&

ecf&5+ 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

  • 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

    g`WHLp*^ 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ώtL~geALh4$?$m5~zXBOJGVB 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

    o_luVG zHJ%0A;We5Ku|LgUTmLrsJuBfhVx;2ulgk_1 ztR;B$i&P#bThZxut^}FGtrnf zzhqc*3W}H|>8M3?j!~w$-sZ8bc_dBw)RR0j2r~ivW|~)D(L^$*-Y(lPe})$GojzNb zwGY>%UF&w4LbnaCx&yuEWZE{XY|WE!>nOZE^( z7*_$fWR-u2`!+(4|FD7#nq3G8HA{Z=ep7%P;kl90S9slw$Orw8&vr_sE=VYSUfCSS zfHV-fs-t1WWWo6PvGSuTL+33zf>ITzyV(tyUZc!icRo~t#ip|8&H~QdBNfZ5$UoZj zw)Q28?eak9VsZ#B4NdBk$rz}?7yQuf4$fOnR*{^)8uc`Iq4+Toevbp~qvy!IF70c^N8fMTRPBEukq$=&-b?=1Et}w=b4ocgu4o!h&XZC>TuW@; zXo9XZZM_Ym+}VQy2g@>VhAK2}0Dd_OgA+d6`A(vo66jcdu!*jIh?cP6^wBWy^V2RV z!;d56K>p?Ib_Hvy=uwf*hbnip7O7it%pP|oNj-X+PKFarKjbef1Gwa(R9r%pAlK>c zGx8drPN;a-;RKmGzTp2x=cO6fKd2|S1o+Jjjv$KszM-W|32E?QBCN4r**BS*UKN}Y z{K}rnepSHy0@9a;x`I!bba=*At4yR)V>}2EUDXIy*;CV`irbAWGq?*nr=D9PPnaqn zHVt=2-cpD#c!^c*z*y=`_2d*~!O`xQsRju;m!C%nZ$2;&n`~I#YtTLC?K#w3oi?ys zcN|x)O+q177NHh115Z&hh&iSzZvo7px0S){Xtk2t#beNInm{b$6xacuV+06atX%0bU29+nAEdR@=s=tE=s@u#7~nAt zp7)3nXDLa^N7~MV&W#mj1fAZ?ZvBk`J13;wpy_429S=Kxjj{RT(QB-2+y{eM2EbH26i#(xGIoIq=>J>cp*c!XRl~^lqNd_~;(4?Xla%P$@ii&*OhmHL+VGO& zDZp+K%H^W_MD@4!^kulEaxK5PRt#Q|l9M-@tTdC=$_Q*ReOyS#JjJHzBXRAC~pGM zoNNiGJ{Q>}r!5O1s*q%95N(Yj!IYkc2(rVg^>^FPo`>2XX5IM$d0LFT^rA3-BU-_D zCh;te%wqMUl1|@5n>gx0Z|LJ)RO;U!fVW}+#dlevy`PTgQfiW;@Xkk=AqC{q7Luei z_hZ$p++kkD0B^;?5Ei5U-gfCf3p0A=u`z7}RbFK_-`5%k8zLkuGFmi(0Gx|sX#&AU zt+uUJX%H)u6s#e+zRoXF8e8bBTCDOrTCm;x0dOfU+tK{`^*iJJ#SeZg*}tBUT8tGQ zZ0zYg3UXJ5rBJ5HKEU&`cmTIR5z&+}olQ&39Kjj&VXS~OzR)-0o{#X&sX3Wm4m3Zk zL?0tY20Zmagp#3PT$xSkB(5)^n+yeugoldD1E z<-Zs5bDpR*4b&9hCJYWkxn3! zdfD=!DaT)NIXjol`j8fEG%dE0X7eBg4i(JJ2QPil1ALToW@#-A)KV9nlu>{)bT>Wy z%1YAICcdUu-YGIf=Krwb1MHUfx;rGq^*~$bUyKg9Zss}@w~=i3d7>FwoZF?+Aqw7s z=4q9$D-qpC6B(HJPJjjcNz6UT&e3tIAjY*rKzyyudBz5er^i2V-cQIVF>c{qmmC92-& zJ0CMNs1K!5xK_}7%&ye!d#T7^N0W(aA)!f^qh810r|9cnv~|=eC%}5EEbWV%LTwwV z*0F;fTL1o2?;Om4#v#akFPgq5mDt%R0`z^WincJ({K$<%7};rk%cq;etU(e|gz-tg z(f^mDKAU&b259bnmD8=%Yi)W)!lc^B$bnmpIFbIQ->6|iMdAsBaH;g7#|VI5)dD>_ ztt5+dCFSYg3qqeYdk5@6bxkfP%J1@VOlvaxK6XI7Rm)ntcdB!bD@3lPhuKiU=EZbg zt5GC1bu!{i+-p)BB=;b>myobfpVIK!()QQkqn~EyQm06+M@r<~ww~mM9?TG2pte<4C7SDj$O3!D@*4+ik7w);kW`;Y2a6*YD1>6bO#?yt{oq25g3eDlr0364oZ zUVT8k)v?*H(fZIbVia%fy&cGpnZIk+MdxeME-Ox(UQD2V3vmMLw>lm*v8i#^ADPWY z=BmuF!gz@WO@5L%JUIU1T~2GN@J#~9)9Ug{4s>Z-7Ew`1n?Z;f4F}u!SNDz$Ps)tQ?`v1S0 zHfIcy_e0Wf-$!(v*;@9>+wABJ_NfqcLc&2Nbq+~%Q2%o+rR<(p{G#{IU{TqpMu-_d zBd49HDKs}E4lkj0!}3N3(0v`Xo~0so#9I0IXUu$|+WYRqmba=S1tv0>bVG_~ejkgK5O0AqdpT+Nb;!)oz;G0r`!;2rIs6Tg1wY9oP&<27imU!9eNTiuQINizRj;*}C6Ny{+&keyMZ zBN-~o-GbZK0~c|?zq)WeSBv^3&woN)pCm`5*D$+SxyI~_7hasw~<|tzK`b`Zuj5dyv!DQfANqo<}u=Mzp8!dEFq%7==H$7XtDtE zpdMOgvXSEb7k4@L(9VovQh!QJj_NJ{bV`V4@^-wh)F7xIxL)OkX@X++Q1^GO@lZrE zI{)X9^-&_2Kgd}E`-U`J%7sKgy{|X!JWr~Fon9J$UT;wQa;C-o**mTPS!SP*3sEK& zZmkv%z@`3=bS38Nb=52c9@Uq<1}}@NowWxRclPs{ix4p3LNvX0Al~{NM*4CRraT_M zRrp+!qW5m(O8KfPF5J;7Qng*&>@b4wKpYK(KeoluKbpU5w!%ieT>pGrtdvFx`+9#f z9?0Cm_lB!Y2$Ey}C4tTAe*e`fPU6+x5aTnI&qXlwD@039@fj-r9={_4P+uB&y;^MZ z0!d>8Jk>q*ZCZ>^sLVgfPQa}8{u5^rmqj0D2G(zbi4|2VrvKFGmRa##+F5u@o3A`k zU~wx}hYG}x7bEj2Xpmf*NFD(cd#hd{wx_d9)LyRwA_*f2SyW5JyouMcLl7G19-@Zw zg?ftCe|c;vXJhyUB;Y5gJqIl7q( z=xg+wK&H6jg1CQ9HhX>*AT_o#uadG4uysAl1$PgHC=#Ls@M|n+=WKNww==J;(G`$? z!0`)qW4()?t$LHPL(_y_>okl7@}TkN?2=}6A*8SOXLg_3am*1?Bdt0{|JcR(Tp&iz zh_s_Fz;6@U=DE5u4*Vwg&t@81HREB)aTpw*iM#)1Tfn6wporfdf#f)IsHMD4+BQov zr?OVAE6qdBIw)o*e5#Id0wt(K`*Oj6IGSpun+&rINV2c{KBZ&EBSksF7X?_m{3R#z zlgh!~>Gw$n;%I8ppsq>{WA=Q(#q1%iWSZ_^dGF29^RS@*C_v28HSr7bn*%5+R z`-l8W>==hm@*uFKd_8KcR(si=hw@q#?PxDW%Kto25@P%oT=OXkUbtyLeSWx`YT9} ztBB>^bsN(;vo4M@SYBQn2$Gu&ZAgauG_h6Db`x#b!;|wCd7ew89J_ph$R8_E%CI3l@I`^;scs$A57L4Xkfcw>fq3mjr*9to z8f8UA!gLXlwDN~cAQiy|dN&v}AFee8GRqBOV(&>tEpsmLbSBH!D!c&|SzJG#A^g0Q zN!cCL|J+UjJ|~qrzO#_u%N>gKHGOZe2eSVKST7fi{w;7QYTfVU1}l;+Df)I3I_DZlX*H1ue&l~P;BhGTw| zZ^^rxT6sgaK)fAVls@W0Dfm(AHHk7ztx4H?%YJ*mi>V-mb;T&81xnUk0e(9MRbC9< z|9V=2l`N&>=%*2_s=E~%Y|F)p{w)`hk>yAR&DZOg`<^F8tgAy+h~Upb-_xZ^%fF&D z>THzbe|7e6b@}uI)c@RZ@0YX~EmSowT~S@{C6_W%8xwV^Y+Z}>s9V5pe1v+)4f5~l zGh5U9wPN=GIg&AS2TFkSHf9M5M3`iPE>hZw(0-g25N{`utT?BB+h_ee89F35)hATq zEq5R34{+?!#gQu`;ZKI3KCMo_Fgql!{De1H@lGgmm+$o#&!!e%<5@nvfERO6ee)^Yr*@vb-snT}!@-9XrVb%rODE}q#}f9+g*W})j1-k*46@rduJ#VQE^@whgg_w_ znn&Jxlpjk}ssCG@Z$fzC98C%|r?+$QkzNNz`NI0MY5b3kmegn_yr1gITA|4;Fi>-+ z5a{gCfW9v81ZK7MR*#;1ouyl>@9jK+$HZu9N1kFUv!zcD*nqO2U7$+?YK>FIpcNkX2O2ecfepXt>V{G)D&QUoEn(C_# zPCh@4HU>ZfICqV^Zl5LLZSynO%#>qho9*Kk3|y-mp;s;G|5ELA8!iXUOY6EzK4IxR zqK0ocRIpT65^*ZHKBATPSXeb*TM8$TcINd2`FGcIBt6wcV3s>~_(D!~tbRr@Rd29E=S@)RRDn~r}AULCUQ*Vz|%pzpA~Ers&d9?Ctt4G?T_R=?eI zU;M$6@SH*iqN1438Ndx?`T>4>N1os6N~?EcLK8&cNp_^WU0IBXUE9x?`_fl(Tsuua zQG(?5>Rf6`vSqSYySyi@<3h%aX&@;GZC^@_Rfw+KurT&^fjIi&tTpRL*Qvt<)X|T4 z#UwA~ZqZr7{$^0lEeRK~lpYH+0Qu5atLw*7vx%-@W3VjBc|Gf}Y-`SW(#9ic`HK8o z=zhH|3#4z8i^YX%H#1kQ`aS_&=USE2|>b8odx8hRx$;9oz?E9!BZ^hG0T zZph}-2ZIKNDMEi}CQa zS3J?-?`}raiZkugZ`pLQ-zR^6Dn=uEJn}yQ@_7(%%)*xUJoWR1%xKzER(<&@6xbk)X4`$!Gq?W{=r(@+t=TfR=LD4EQ(b zW79QQp6C@NzwtM*>c(5hOA$8r&skfNbcvH?FH1fNB}kv&$Lx>+^H1zf#!Xt4_tuK) zsL(zzm>{qQ1d$}KEu;_-zk^{*;LIk50l(U32c5}ebi})0xdIXJ^H>q{XTvfc)6PKs zNP`_cGr0TaBL*+eFCU{E=V^;17`PkQ(={o$pSps9yH)ad%pupvQ`pn3tZ^tzc)%iu$ zD?thLyc{}po>?JKPHt=A9Y!C-jTP>?;q$yruSjFVfi-sjv@N6y=o@At(KwzU>3w86 z_2~QYIx@(EcAP0o{E}*8MUuSakP+Dl&Lku>>InKs^}tQ;GefR^o&sBDh%Oj{x(brctV*e`~nDsvz-~aoy?;H0O9YQKl5_ zrcqLk*?C0v+tu&=&tL1+zD~yK4oGIX`GkstgZc#j^wIqibSTfj#LkYpEUbq99?7iq?wf_lcaIb*%r63p3p&5_SV|j4*%M=4%Qn+ZnsRe%JL% z-8vx7_PT8P<{h<*OlGw9&;mMVI1&aYkj3vF1nxGEcLgPn@rPhlydse(jlM#t4{bNW zdml6hawMic1!;SC8w~FZR^vxR#l$fp@l%q8@XNwMTQ=>!3=%4^K1M2-Oi*`F5Ls2Z z+XwS)9}i-&Ap-oF&va?+uu1wN?JuK2a@XJ8L-qBO1l)~vz=X@bJQp}jQp&T*p`_1y zN)Z$Bxq|vnN5QI-RlnRxh_ew^3q*-k^PnGm@uc9R+fjL#>SCKyK{PV9pX)?ErsLam_p0Bb%zKo7)ge2ej_Bpa(ZSJFzo-ygU7LKUw z8d0)QC?!4@hLA7<>v!~m%hY?Nj(gjT)x8S)V!6*73DWJ{4qtGNQXh9d+KT!MP}jx? z%m$PNNiG6&8%Mqj@a~uPfB(h+Q^C4acc9e9XXwsd1n@h?zJehd~OBZ z3=qdy-dz69z~imyUP9W!A}Ir5hh3nsPFc=F5q^1YAM?F6=s7di{((-Lg<4EZH3wU2Luvy410eKzlq?zMbkX%bV(=0CHM z-+EjUeG$jVG*9uLDSSzb7Nal9Lj{pPpf z$L;#|-6T=v0I)kw-D>!6eJ5od6YBmmn<0-rN9x58!(71zx?%`E!%LtzGe}M|2-Wu+ z!P0m!4%AU=dC}gH(wkzxhI9W{ny~Gec5M`Q!0rTLVL#T76Q4ONv37oYuVMakMOtow z8|nH5m07VtIEH;SU>!|;K~ekrvu#z)np9_XQf}~HeQ2nxAcP)1_Caoax2xkN==|l> zUxT$3=~^t1~HH5S;q!7b3WHHTjF4yf#Q*#3_CXrZ!ouyyvDH zE|;h7f#a@WC)Y+Uf5Wbl>y)FfC>?{-Ymf585N| zy-*DWu->MxAcjA$dSc@2C{Uo!7qZ@B(Y*9EVaK`uS#jqw zp?yiWqH)#U1nuX{JkwrD6lKldnT7LiQh_PPsv*$Zo8}fQM4UiJi)W^n@&W$MKC`%f zC9oAKVBd-AEehn-ILm9+;)$`MMP6jJ|C~mA1e*6c$3n-0U_&8iVS}VxBjJdhcZDg8cajT53<{7h zb7qvdMRqchklXkTQe#ufEXUVv6n$8+&gUZ{o!=@g3_*2#F5W5T=2B2B37?#2Ma>a@ zOEHx)lK6~`h~p@w!{US10o1=c*EKTYt23r;)%DGgvid1m&VcWGI3yXjDJnO{WRa;* zUoDW&b7Qe|cr}jrw(?zGyP@qb;cT!7UXL9W(ShHztX!_WbZ= z-#?}t!PHY^BFePI4`an9V7JgYgWiF9F!x-{V}eD2-7E7MQ!jA_<;VLgwnAouC2Jv! zd508Ma4;DlH_uEq!TNg_ zHBH?s3gagJkFbOJ03Qn(aTlbe{A-(*U(v)ODx=*zelCIKo^d%TLN9i=ot1Rp0d^N! ziql02=1e}B-V6m1_=Rnk-}nUSJIXHO>E&9-tBs{4f7k5Ww#eSU|QkEOMR8`^vpa?)olc9)XSx>uNcc(A|2* zm(KkNU0@w8AuHNpB%PvbqO`EExk*Lq$=AtW@7Y=^VL zs@**1JU>PtbpO{puj7Jb8lV)J&>ILbWDEGWRI1va+GP+D+Fdx>YBe*0T1lFdvP6Z_ z5mqc)I3O}*_yPzdn`VsCkosw&vQvN>}jPy zHkaAKaOD)8n0lL30peZ8Sc57YIr*~_T`sdeFJ#qrrXj#DQ+3UWp%~Q43j6yjDo}Tq z<(HA0@{Nu*86Llpls~PTO_ymu?G2B9L;L+558Z)QC>5}~LZPiO9VtmVSfF2zqw3j# zq#Y_kmpVNHMXNamqd&&+4EjH_QX4GM@j?v~yJrk*yQ?$vSaG>*_$}VzR^Djs)XU}o z*Ac*ZWjG=|R|wI$HA;1(Z1h2o)+~q+>fW$fM7<8K9G}dP^c%qMD(S?Jr^;$X=WjPV zAwlO5{pC9F#(1?AZFTmic4Cb6qVGVwtCCsk{b&}MozB2Fa?coEr%WvKuQjf5|03wQuuf zZQ1Ccc-PKhz^Z~u#gUKjJ1CS2gJsC><#;bKV#{9AkZYH%)g7vOmd z6++gOdPD;4|z;9h`GFe65WW=8oKZspanG zZ<`D~=F2^(k7wNue2Aa_2a=H?+FeczPRpb>C9zym4feIi!WWjT9#3@y!0!4K_#KoR zs$gX+)TxOuXAb;kz;gD2FbvtLnBT<-a%bEpU>$8Bt$x(Vcach2Y{f5n7FGS@XJdK} zEyXo`Ft)(PMXBSR1@POj>fb7@+;c=6b}xwNG5!Y5O~qK>j&I|h5)|ArH?9db4cOh7 zV3p`T)8t2kVS^vBZ2*39c-qMBc3B{FnQt55Re2|6c(z1|xrma27Sc2ldx| z`oYH0li#U)R0OAxO8&k2cmOV&sEr>l{+rfN*_i%0XOnag{1~N@uXnIk;6HN7Utjg- zN`N}FX>FNdz?AwK`z=BZ_OV5g34chMG7icExNIHGL0L?~N9_gW#0+O)<>;8qX~06?+e}mLeazKUDouKQ%$ybk|H1RpojaH%ZQ*!Am!t&E zXWap-+%WS}HhpUf>hj8~Nw+I-Pq{8oE#?uGr;m!@LU{)V>d=m=pS_LRlv5_6I$^Ui zKKq9@E|(g+*WNXpe#@|1xvS+IkbgT4kR~=injYAyEk8N_ol)yVoF51=!nzNtOD7bL zJ9=xu2l&`2{vcK9@F|B4sPiWbg7NV*o_jow%SByOn?7t<=b4Gx0r=gip+3VRQ-vg_ zRhuYBKqYC#_9Cp6E(|EDSBPIKJ1(LGeOKFQ;T_7_-dQ4p%-+cGPwU$Mf!p*02F^0A zWx>sRH+-;N8sy($jWfSDdj!=sTfqDVgSzP$cL;5qLy%3mRQUOTK%=cC$nGhg=mZb+ z($PTFk`d8B&Fs>6=+_=6-RY6pC)cwIBWY|Pj$Pi;zw_gFVV25Q`H4h2N3#}oU=ZM+ z%Op-o>v&|5m6rkm{C4%*AnW_9G{|FYqXeIhbN4J5?yvmK$7P5yLqaE@^BF;NFLyWg zdOSCxEbB>S{k4v63=bB*{*prQuqLJ{m$V_l=+wmo;@G`i@k$Zoq2$vJUrUQK{hrFU zM{RTaYdN3w``M}UKeW>c%TTha}aG=7P#>V+v zuh|a?P(RY%MH}qE6P;AJCWb4-l5;D(jh+~|Du+s{4z>;|6z5?(s6Ou_oy@zU@TwJr z3)hYyKXT|je1kgs(@iD5$9PpbWXSn+4B)(vrgGrss+4o3c+>Axezb>r2ZfllH0O>H zW^Xx$E}=!w3apQP*H7sL?Wim#Wn}&L@ge%3hIF(STgu|Vl~Ij-QcV#0y?{9Oz3M{~ zIkK|8z15W4pa=hPFB_Xz71qwy2)+)X71&1j@&MA8KROW;S0ewDr{^#KaMUwNrDy}8 zX~wiifWS;Pc2A6c2uNSO<NJX3aWycGP!H zdpCE)KCPPezmG?JAxp>=fZTz-tYPsuZ;6UD)^xn%N8BdaouS~ReF;a%)W4t89I;TK z{@sH#!rlG|!F#y!Hmv#5msIz6ehKMq=2yZT6L=JIyr!ySAP){Mp%%0;6p0UuHG{Y( zDWy%XYPbjG)ao}&{|ypZTBRDz0DK(cRawit%~dmeb&67`6bkNzOKw`!(b6x*w-zHi zTfGcX0G@A$6g&C3JSl&~ss&!vz@ER%Dm;Cw6L7$d7<`}i&|h=%#RBOw&#Hadeq4pO zCqvW3U@Caslk!!eaP_4sLy%V?Ath`{L@$}&ZyW4=={;)zcJJJ z@FLY;XPJ`J^cnQ^7vh3jx7Rm38D_9c?{ipF0RYa2&$cXT9dT=un(5au%Q*b7A`5*; z9w-o>~VtrbC+Wf>vx}P{F2g8c`V@H@t5)STEx8n=7E>r6hNQN0)t)F|J$tO$v=7@~!I3>0zp5vRFm(NK_#Ix&-IeFolD} zt#p!PZz*Mm7jJ@I;Ga$0Fi2t4nfYN@?*M*}2cHEQD|_jon_YW?ODNBb5BqAl5@(az zEpMX{?>*YO#DM%e-uM)+wRjkpYEa5FVf~oF`Ac}(O$d3Lm4Wiz4x zyy)w;?|@P!r`Gj8x|dH0ZwI~SoC<-{YgysULU`eX%$Ic%PJInu2*rq!5hj?>envC* zO40}TIF%hs@10oYk14ZvJt09hN8QQ!Jl_{oDAX=e@R_vv)XEIt_jIQ$3bH?WGm*&l zU9$4apX=rAF};MS$P~V0dtT*OIlfIm?hGpb4O*|>l&>XQ7S@AlRY4wx7?HICGD z)QNbrJs;F(bcW;0WTu|ORrcQv(8gb%|K-LWBbtm0=Z8_CQob>Kb$8 z^M5T}c{G&m8@7im*;DjcBU{!mg-G&|HCvW8l!z>m8M2eC*$df{$-WGk#@KiHkQv0# z*!K)(XvQplzjNOC|324!-S>6f=Y8IDo+Z(@y;AAn@hblOo`8-( z?upAYV>gHFE#4%E>@w_%vx?Lk3?1-?^~D4A;*Ot4Mx}%aGRxmSK@ec=oG)!G?Jieu zaV_&0`6g9<*JlOl*cT{pS7?h`i2BEkzoL6#TAXW5p zsr!gr75@ylyx+WY#o)-tr54lv&zoyk9%k8rP|8b@=~;<@?+>26@E5@LM@rzGUeUg} z$?WrPTm41n4dc?o6S%>`CN}+mz6Ze@zZ?un_qnov33(_n2wAy`=^wzIb;{)o%_nqN zX^WXa{|+FW>JEEB#h*W%et46O)^8|{yx;fGS}`N3Ps@A)e)X9r9WRH;r4W<}k17b? zP^w*bF(Z3g7UOEWj61?cG& z=?3m-M{gwteJAX4bgj#xYsG8Gpc3;xHvrx6R}kqw*J#byH38P`pA-K`Lb5*59F3%0 zb-E_05_&A#VgMe&*c`#|#E3M%+-0ZA^E(qSROtz^<2fj^xIai4ILCS%4&V_EF1M-N z?KDo&_{qe=UT*pu#)`v;x79FlaP1;iyd$%FfViVC^K&525sQU#mDQTt=mxIIuh2V8 z)b%tqLsUjb_-+6dsP~w;?$OGVOQ0)tj>Gje`YAsL1=%>HEmth?f{ppF8FP2&-=kwi zM#9Dfw*4i0NmQxx);QWK%Y|KXeJi>wc;M@mGk1&W=RS}9LaP`cY|~@k{ajOH99ouy z(mMS#!P?a-2YgcHLO%}$0e;6}hvl8AC6E0= zyLv34`QcV`c-DNE%4gR(^IvyjfIk0qzAmN9r0}RXj{o5nX!=Q^$#2}24H-^o5t-wO z_>lL5{vO)jI6LdDUF8+)0nUERRJ#1_CGXM5shaZ;_#|9RuIkO(MPPpaR*pi!k{)B; zy#cNoo_oSYmE@l>==Rwg{u_Ry#ogKC<3L`bVj6Yf&x^hkJQP$}D2rEmt%Kz*f~$u) z{EoxdT+D(DGvy;`|&Gs9zq7~Rg;dMN_zRj?v0&cduY`lL;)Q>NZC=XXhXFT`qZ_^<;d33z}(CkGpTTbjO&1qTm9E+DRB?KWg zcsMbY?=CYMR`~H6fJeHTd}TdnF|Y4!{A)jSU7V>rGSX{9i-cKhh?Z-72|bqt%rD7^ z?~!D(O0M84UYab-{=oi)W7y`g>t~$@0;|Hxh+REknZrP`{qxF0VYXS%uKUMWcMJEm z?lZPsnHP6?&WLtQp~ONWxq$Tx$*%7;Sd&H_54@WSE>Jy5b)O^TYD>aKxCmNZuSXDD z%7Fh!`DS;YzrQ7CzOQoAdPNIc? z{Jb>iTqV%Ilhk*mvadL4LI-`vo!{e5%kHmEVVlTDB2#bwmEUQaF2U2|kcoLIC&g4q z1k=&S!6AN*zr%ywpuKkM_!XrMov1e60D9g(t1MnnU0I|%rr@!ik7}I14x=;dh`A*g zs@oEg{SimspLl}ny84Yze{}!)`=+!&akQxnrF<+`E`*2g3v7IOh(}8f(2X1}ALeen z@#`75>D8xU$0u2Q zn*29N6}MEpu1en|n+P}VuLJ#~5C?{$6}||}UrzrQtDfAg-lOW1*Yeq6r|z}iUYx41 zJN^4X<#qv!xYhHS?T7fNu|24}7v=F)fd9_Y3ywt13)s+}b^3oY^#w$H2y<&8w)uj3 zbknmOna8gT1*&qQf^e54Ob2YI-(vwis0E#e8zO8r=g?~L_Hp1OklEYx5`TCvr`&X# zFdRets{!z%4#%6vLVFcuO(xG>b1GcUv4PF7z*g#=ZVk)I;CTlPj{!YsoX#x@*DgTT zAZAVxk{!GP>jOo#YU&Djc?iay`lEmt2n;4oOci-=^bA)c?0Sim=%SRPK~wK)k^=vM z*k{isw`=)o2>?Hu%xv~(j%ci&_{4OIYn=Vt&OzwA$I583vb27}7t{=ee!nB_{`Pf4 znHUg-ADwtO`hjtvJAHLUO^e^=QsO+pK2HDN5ISBbJGXFBu4|@)?5(HEAKw4)&6KRY z<5gFD5R}dO9Fri21L~#qg!(*|)_5xzpnT1^(&8;vqU-6V+AykUEhIRTkwNi(7=U-m z60}pXI+kH~Jc2@|nUztJ6uH`ZJ-hGrEOwmQI=z!R1pIQ!*`)eUUfMZGJm8m8TX9m&*pbv7kWINna^1?X=&q=<9d|#NL9H#Y53}^0 Ie$V^=0Iu6JN&o-= literal 0 HcmV?d00001 From e544633fbf2adf93d91f3b184f5ffee89c3c5f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 27 Jul 2021 13:36:43 +0100 Subject: [PATCH 140/291] add the first read-only benchmarks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BenchmarkReadBlocks uses carv2.OpenReader with its Roots and Next method. From six runs on a laptop with a i5-8350U and benchstat: name time/op ReadBlocks-8 633µs ± 2% name speed ReadBlocks-8 824MB/s ± 2% name alloc/op ReadBlocks-8 1.32MB ± 0% name allocs/op ReadBlocks-8 13.5k ± 0% OpenReadOnlyV1 uses blockstore.OpenReadOnly with its Get method. The method is used on all blocks in a shuffled order, to ensure that the index is working as intended. The input file lacks an index, so we also generate that. name time/op OpenReadOnlyV1-8 899µs ± 1% name speed OpenReadOnlyV1-8 534MB/s ± 1% name alloc/op OpenReadOnlyV1-8 1.52MB ± 0% name allocs/op OpenReadOnlyV1-8 27.2k ± 0% Both benchmarks use the "sample" v1/v2 CAR files, which weigh about half a megabyte. It seems like a good size; a significantly larger CAR file would make each benchmark iteration take tens or hundreds of milliseconds, making it much slower to obtain many benchmark results, since we want at least thousands of iterations to avoid noise. This commit was moved from ipld/go-car@311809b079f4e839bfa815fb6986f713841644e0 --- ipld/car/v2/bench_test.go | 48 +++++++++++++++++++ ipld/car/v2/blockstore/bench_test.go | 71 ++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 ipld/car/v2/bench_test.go create mode 100644 ipld/car/v2/blockstore/bench_test.go diff --git a/ipld/car/v2/bench_test.go b/ipld/car/v2/bench_test.go new file mode 100644 index 0000000000..c1fe5b6242 --- /dev/null +++ b/ipld/car/v2/bench_test.go @@ -0,0 +1,48 @@ +package car_test + +import ( + "io" + "os" + "testing" + + carv2 "github.com/ipld/go-car/v2" +) + +// Open a reader, get the roots, and iterate over all blocks. +// Essentially looking at the contents of any CARv1 or CARv2 file. +// Note that this also uses ReadVersion underneath. + +func BenchmarkReadBlocks(b *testing.B) { + path := "testdata/sample-wrapped-v2.car" + + info, err := os.Stat(path) + if err != nil { + b.Fatal(err) + } + b.SetBytes(info.Size()) + b.ReportAllocs() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + cr, err := carv2.OpenReader(path) + if err != nil { + b.Fatal(err) + } + _, err = cr.Roots() + if err != nil { + b.Fatal(err) + } + for { + _, err := cr.Next() + if err == io.EOF { + break + } + if err != nil { + b.Fatal(err) + } + } + + cr.Close() + } + }) +} diff --git a/ipld/car/v2/blockstore/bench_test.go b/ipld/car/v2/blockstore/bench_test.go new file mode 100644 index 0000000000..c97dc35267 --- /dev/null +++ b/ipld/car/v2/blockstore/bench_test.go @@ -0,0 +1,71 @@ +package blockstore_test + +import ( + "io" + mathrand "math/rand" + "os" + "testing" + + "github.com/ipfs/go-cid" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" +) + +// Open a read-only blockstore, +// and retrieve all blocks in a shuffled order. +// Note that this benchmark includes generating an index, +// since the input file is a CARv1. + +func BenchmarkOpenReadOnlyV1(b *testing.B) { + path := "../testdata/sample-v1.car" + + info, err := os.Stat(path) + if err != nil { + b.Fatal(err) + } + b.SetBytes(info.Size()) + b.ReportAllocs() + + var shuffledCIDs []cid.Cid + cr, err := carv2.OpenReader(path) + if err != nil { + b.Fatal(err) + } + for { + block, err := cr.Next() + if err == io.EOF { + break + } + if err != nil { + b.Fatal(err) + } + shuffledCIDs = append(shuffledCIDs, block.Cid()) + } + cr.Close() + + // The shuffling needs to be deterministic, + // for the sake of stable benchmark results. + // Any source number works as long as it's fixed. + rnd := mathrand.New(mathrand.NewSource(123456)) + rnd.Shuffle(len(shuffledCIDs), func(i, j int) { + shuffledCIDs[i], shuffledCIDs[j] = shuffledCIDs[j], shuffledCIDs[i] + }) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + bs, err := blockstore.OpenReadOnly(path) + if err != nil { + b.Fatal(err) + } + + for _, c := range shuffledCIDs { + _, err := bs.Get(c) + if err != nil { + b.Fatal(err) + } + } + + bs.Close() + } + }) +} From b1c325bd7233d50504a6300b92e27103f63ef3aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 2 Aug 2021 12:19:18 +0100 Subject: [PATCH 141/291] blockstore: use errors when API contracts are broken It's technically correct to use panics in these situations, as the downstream user is clearly breaking the contract documented in the API's godoc. However, all these methods return errors already, so panicking is not our only option here. There's another reason that makes a panic unfortunate. ReadWrite.Finalize enables the panic behavior on other methods, which makes it much easier to run into in production even when the code and its tests work normally. Finally, there's some precedent for IO interfaces using errors rather than panics when they are used after closing. For example, os.File.Read returns an error if the file is closed. Note that this change doesn't make panics entirely impossible. The blockstore could still run into exceptional and unexpected panics, such as those caused by memory corruption or internal bugs. This commit was moved from ipld/go-car@a6dc547f3808b4acf5ae2345c1f08c21d38cbc86 --- ipld/car/v2/blockstore/readonly.go | 11 +++++--- ipld/car/v2/blockstore/readonly_test.go | 6 ++-- ipld/car/v2/blockstore/readwrite.go | 36 +++++++++++++----------- ipld/car/v2/blockstore/readwrite_test.go | 26 ++++++++++------- 4 files changed, 46 insertions(+), 33 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index dbf7bf61b8..2df9a7946b 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -24,7 +24,10 @@ import ( var _ blockstore.Blockstore = (*ReadOnly)(nil) -var errZeroLengthSection = fmt.Errorf("zero-length section not allowed by default; see WithZeroLengthSectionAsEOF option") +var ( + errZeroLengthSection = fmt.Errorf("zero-length carv2 section not allowed by default; see WithZeroLengthSectionAsEOF option") + errReadOnly = fmt.Errorf("called write method on a read-only carv2 blockstore") +) type ( // ReadOnly provides a read-only CAR Block Store. @@ -176,7 +179,7 @@ func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { // DeleteBlock is unsupported and always panics. func (b *ReadOnly) DeleteBlock(_ cid.Cid) error { - panic("called write method on a read-only blockstore") + return errReadOnly } // Has indicates if the store contains a block that corresponds to the given key. @@ -303,12 +306,12 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { // Put is not supported and always returns an error. func (b *ReadOnly) Put(blocks.Block) error { - panic("called write method on a read-only blockstore") + return errReadOnly } // PutMany is not supported and always returns an error. func (b *ReadOnly) PutMany([]blocks.Block) error { - panic("called write method on a read-only blockstore") + return errReadOnly } // WithAsyncErrorHandler returns a context with async error handling set to the given errHandler. diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index ecc30e1d10..3fd16c8c64 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -102,9 +102,9 @@ func TestReadOnly(t *testing.T) { require.Equal(t, wantBlock, gotBlock) // Assert write operations panic - require.Panics(t, func() { subject.Put(wantBlock) }) - require.Panics(t, func() { subject.PutMany([]blocks.Block{wantBlock}) }) - require.Panics(t, func() { subject.DeleteBlock(key) }) + require.Error(t, subject.Put(wantBlock)) + require.Error(t, subject.PutMany([]blocks.Block{wantBlock})) + require.Error(t, subject.DeleteBlock(key)) } ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index ba87ee601c..1192e749ca 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -23,6 +23,8 @@ import ( var _ blockstore.Blockstore = (*ReadWrite)(nil) +var errFinalized = fmt.Errorf("cannot use a read-write carv2 blockstore after finalizing") + // ReadWrite implements a blockstore that stores blocks in CARv2 format. // Blocks put into the blockstore can be read back once they are successfully written. // This implementation is preferable for a write-heavy workload. @@ -284,22 +286,22 @@ func (b *ReadWrite) unfinalize() error { return err } -func (b *ReadWrite) panicIfFinalized() { - if b.header.DataSize != 0 { - panic("must not use a read-write blockstore after finalizing") - } +func (b *ReadWrite) finalized() bool { + return b.header.DataSize != 0 } // Put puts a given block to the underlying datastore func (b *ReadWrite) Put(blk blocks.Block) error { - // PutMany already calls panicIfFinalized. + // PutMany already checks b.finalized. return b.PutMany([]blocks.Block{blk}) } // PutMany puts a slice of blocks at the same time using batching // capabilities of the underlying datastore whenever possible. func (b *ReadWrite) PutMany(blks []blocks.Block) error { - b.panicIfFinalized() + if b.finalized() { + return errFinalized + } b.mu.Lock() defer b.mu.Unlock() @@ -362,31 +364,33 @@ func (b *ReadWrite) Finalize() error { } func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - b.panicIfFinalized() + if b.finalized() { + return nil, errFinalized + } return b.ReadOnly.AllKeysChan(ctx) } func (b *ReadWrite) Has(key cid.Cid) (bool, error) { - b.panicIfFinalized() + if b.finalized() { + return false, errFinalized + } return b.ReadOnly.Has(key) } func (b *ReadWrite) Get(key cid.Cid) (blocks.Block, error) { - b.panicIfFinalized() + if b.finalized() { + return nil, errFinalized + } return b.ReadOnly.Get(key) } func (b *ReadWrite) GetSize(key cid.Cid) (int, error) { - b.panicIfFinalized() + if b.finalized() { + return 0, errFinalized + } return b.ReadOnly.GetSize(key) } - -func (b *ReadWrite) HashOnRead(h bool) { - b.panicIfFinalized() - - b.ReadOnly.HashOnRead(h) -} diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index f3016e25d0..77442de0dc 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -494,18 +494,24 @@ func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { require.True(t, has) subject.HashOnRead(true) - // Delete should always panic regardless of finalize - require.Panics(t, func() { subject.DeleteBlock(oneTestBlockCid) }) + // Delete should always error regardless of finalize + require.Error(t, subject.DeleteBlock(oneTestBlockCid)) require.NoError(t, subject.Finalize()) - require.Panics(t, func() { subject.Get(oneTestBlockCid) }) - require.Panics(t, func() { subject.GetSize(anotherTestBlockCid) }) - require.Panics(t, func() { subject.Has(anotherTestBlockCid) }) - require.Panics(t, func() { subject.HashOnRead(true) }) - require.Panics(t, func() { subject.Put(oneTestBlockWithCidV1) }) - require.Panics(t, func() { subject.PutMany([]blocks.Block{anotherTestBlockWithCidV0}) }) - require.Panics(t, func() { subject.AllKeysChan(context.Background()) }) - require.Panics(t, func() { subject.DeleteBlock(oneTestBlockCid) }) + require.Error(t, subject.Finalize()) + + _, err = subject.Get(oneTestBlockCid) + require.Error(t, err) + _, err = subject.GetSize(anotherTestBlockCid) + require.Error(t, err) + _, err = subject.Has(anotherTestBlockCid) + require.Error(t, err) + + require.Error(t, subject.Put(oneTestBlockWithCidV1)) + require.Error(t, subject.PutMany([]blocks.Block{anotherTestBlockWithCidV0})) + _, err = subject.AllKeysChan(context.Background()) + require.Error(t, err) + require.Error(t, subject.DeleteBlock(oneTestBlockCid)) } func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { From dc7d0f816f72c3f07ad4e76f19f63dcdb6d214fc Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 3 Aug 2021 12:48:08 +0100 Subject: [PATCH 142/291] Implement version agnostic streaming CAR block iterator Implement an iterator over CAR blocks that accepts both v1 and v2. The reader only requires io.Reader interface to operate and exposes the version and roots of the underlaying CAR file as fields. Remove `Next` from CARv2 reader now that the iteration functionality is provided using `BlockReader`. Update the benchmarks to reflect the changes and add example usage for `NewBlockReader`. Add tests that assert `BlockReader` behaviour in erroneous and successful cases for both underlying CARv1 and CARv2 payload. This commit was moved from ipld/go-car@d0e44a62f0a3b31f0c6280653381fcdff19f65e5 --- ipld/car/v2/bench_test.go | 17 ++-- ipld/car/v2/block_reader.go | 142 +++++++++++++++++++++++++++ ipld/car/v2/block_reader_test.go | 112 +++++++++++++++++++++ ipld/car/v2/blockstore/bench_test.go | 24 +++-- ipld/car/v2/example_test.go | 59 +++++++++++ ipld/car/v2/reader.go | 28 +----- ipld/car/v2/reader_test.go | 20 ---- 7 files changed, 339 insertions(+), 63 deletions(-) create mode 100644 ipld/car/v2/block_reader.go create mode 100644 ipld/car/v2/block_reader_test.go diff --git a/ipld/car/v2/bench_test.go b/ipld/car/v2/bench_test.go index c1fe5b6242..83adc346a9 100644 --- a/ipld/car/v2/bench_test.go +++ b/ipld/car/v2/bench_test.go @@ -8,10 +8,9 @@ import ( carv2 "github.com/ipld/go-car/v2" ) -// Open a reader, get the roots, and iterate over all blocks. -// Essentially looking at the contents of any CARv1 or CARv2 file. -// Note that this also uses ReadVersion underneath. - +// BenchmarkReadBlocks instantiates a BlockReader, and iterates over all blocks. +// It essentially looks at the contents of any CARv1 or CARv2 file. +// Note that this also uses internal carv1.ReadHeader underneath. func BenchmarkReadBlocks(b *testing.B) { path := "testdata/sample-wrapped-v2.car" @@ -24,16 +23,16 @@ func BenchmarkReadBlocks(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - cr, err := carv2.OpenReader(path) + r, err := os.Open("testdata/sample-wrapped-v2.car") if err != nil { b.Fatal(err) } - _, err = cr.Roots() + br, err := carv2.NewBlockReader(r) if err != nil { b.Fatal(err) } for { - _, err := cr.Next() + _, err := br.Next() if err == io.EOF { break } @@ -42,7 +41,9 @@ func BenchmarkReadBlocks(b *testing.B) { } } - cr.Close() + if err := r.Close(); err != nil { + b.Fatal(err) + } } }) } diff --git a/ipld/car/v2/block_reader.go b/ipld/car/v2/block_reader.go new file mode 100644 index 0000000000..5c33502945 --- /dev/null +++ b/ipld/car/v2/block_reader.go @@ -0,0 +1,142 @@ +package car + +import ( + "fmt" + "io" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/ipld/go-car/v2/internal/carv1/util" + internalio "github.com/ipld/go-car/v2/internal/io" +) + +// BlockReader facilitates iteration over CAR blocks for both CARv1 and CARv2. +// See NewBlockReader +type BlockReader struct { + // The detected version of the CAR payload. + Version uint64 + // The roots of the CAR payload. May be empty. + Roots []cid.Cid + + // Used internally only, by BlockReader.Next during iteration over blocks. + r io.Reader + ropts ReadOptions +} + +// NewBlockReader instantiates a new BlockReader facilitating iteration over blocks in CARv1 or +// CARv2 payload. Upon instantiation, the version is automatically detected and exposed via +// BlockReader.Version. The root CIDs of the CAR payload are exposed via BlockReader.Roots +// +// See BlockReader.Next +func NewBlockReader(r io.Reader, opts ...ReadOption) (*BlockReader, error) { + // Read CARv1 header or CARv2 pragma. + // Both are a valid CARv1 header, therefore are read as such. + pragmaOrV1Header, err := carv1.ReadHeader(r) + if err != nil { + return nil, err + } + + // Populate the block reader version. + br := &BlockReader{ + Version: pragmaOrV1Header.Version, + } + + // Populate read options + for _, o := range opts { + o(&br.ropts) + } + + // Expect either version 1 or 2. + switch br.Version { + case 1: + // If version is 1, r represents a CARv1. + // Simply populate br.Roots and br.r without modifying r. + br.Roots = pragmaOrV1Header.Roots + br.r = r + case 2: + // If the version is 2: + // 1. Read CARv2 specific header to locate the inner CARv1 data payload offset and size. + // 2. Skip to the beginning of the inner CARv1 data payload. + // 3. Re-initialize r as a LimitReader, limited to the size of the inner CARv1 payload. + // 4. Read the header of inner CARv1 data payload via r to populate br.Roots. + + // Read CARv2-specific header. + v2h := Header{} + if _, err := v2h.ReadFrom(r); err != nil { + return nil, err + } + // Assert the data payload offset validity. + // It must be at least 51 ( + ). + dataOffset := int64(v2h.DataOffset) + if dataOffset < PragmaSize+HeaderSize { + return nil, fmt.Errorf("invalid data payload offset: %v", dataOffset) + } + // Assert the data size validity. + // It must be larger than zero. + // Technically, it should be at least 11 bytes (i.e. a valid CARv1 header with no roots) but + // we let further parsing of the header to signal invalid data payload header. + dataSize := int64(v2h.DataSize) + if dataSize <= 0 { + return nil, fmt.Errorf("invalid data payload size: %v", dataSize) + } + + // Skip to the beginning of inner CARv1 data payload. + // Note, at this point the pragma and CARv1 header have been read. + // An io.ReadSeeker is opportunistically constructed from the given io.Reader r. + // The constructor does not take an initial offset, so we use Seek in io.SeekCurrent to + // fast forward to the beginning of data payload by subtracting pragma and header size from + // dataOffset. + rs := internalio.ToByteReadSeeker(r) + if _, err := rs.Seek(dataOffset-PragmaSize-HeaderSize, io.SeekCurrent); err != nil { + return nil, err + } + + // Set br.r to a LimitReader reading from r limited to dataSize. + br.r = io.LimitReader(r, dataSize) + + // Populate br.Roots by reading the inner CARv1 data payload header. + header, err := carv1.ReadHeader(br.r) + if err != nil { + return nil, err + } + // Assert that the data payload header is exactly 1, i.e. the header represents a CARv1. + if header.Version != 1 { + return nil, fmt.Errorf("invalid data payload header version; expected 1, got %v", header.Version) + } + br.Roots = header.Roots + default: + // Otherwise, error out with invalid version since only versions 1 or 2 are expected. + return nil, fmt.Errorf("invalid car version: %d", br.Version) + } + return br, nil +} + +// Next iterates over blocks in the underlying CAR payload with an io.EOF error indicating the end +// is reached. Note, this function is forward-only; once the end has been reached it will always +// return io.EOF. +// +// When the payload represents a CARv1 the BlockReader.Next simply iterates over blocks until it +// reaches the end of the underlying io.Reader stream. +// +// As for CARv2 payload, the underlying io.Reader is read only up to the end of the last block. +// Note, in a case where ReadOption.ZeroLengthSectionAsEOF is enabled, io.EOF is returned +// immediately upon encountering a zero-length section without reading any further bytes from the +// underlying io.Reader. +func (br *BlockReader) Next() (blocks.Block, error) { + c, data, err := util.ReadNode(br.r, br.ropts.ZeroLengthSectionAsEOF) + if err != nil { + return nil, err + } + + hashed, err := c.Prefix().Sum(data) + if err != nil { + return nil, err + } + + if !hashed.Equals(c) { + return nil, fmt.Errorf("mismatch in content integrity, name: %s, data: %s", c, hashed) + } + + return blocks.NewBlockWithCid(data, c) +} diff --git a/ipld/car/v2/block_reader_test.go b/ipld/car/v2/block_reader_test.go new file mode 100644 index 0000000000..afffc806cd --- /dev/null +++ b/ipld/car/v2/block_reader_test.go @@ -0,0 +1,112 @@ +package car_test + +import ( + "io" + "os" + "testing" + + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/stretchr/testify/require" +) + +func TestBlockReaderFailsOnUnknownVersion(t *testing.T) { + r := requireReaderFromPath(t, "testdata/sample-rootless-v42.car") + _, err := carv2.NewBlockReader(r) + require.EqualError(t, err, "invalid car version: 42") +} + +func TestBlockReaderFailsOnCorruptPragma(t *testing.T) { + r := requireReaderFromPath(t, "testdata/sample-corrupt-pragma.car") + _, err := carv2.NewBlockReader(r) + require.EqualError(t, err, "unexpected EOF") +} + +func TestBlockReader_WithCarV1Consistency(t *testing.T) { + tests := []struct { + name string + path string + zerLenAsEOF bool + wantVersion uint64 + }{ + { + name: "CarV1WithoutZeroLengthSection", + path: "testdata/sample-v1.car", + wantVersion: 1, + }, + { + name: "CarV1WithZeroLenSection", + path: "testdata/sample-v1-with-zero-len-section.car", + zerLenAsEOF: true, + wantVersion: 1, + }, + { + name: "AnotherCarV1WithZeroLenSection", + path: "testdata/sample-v1-with-zero-len-section2.car", + zerLenAsEOF: true, + wantVersion: 1, + }, + { + name: "CarV1WithZeroLenSectionWithoutOption", + path: "testdata/sample-v1-with-zero-len-section.car", + wantVersion: 1, + }, + { + name: "AnotherCarV1WithZeroLenSectionWithoutOption", + path: "testdata/sample-v1-with-zero-len-section2.car", + wantVersion: 1, + }, + { + name: "CorruptCarV1", + path: "testdata/sample-v1-tailing-corrupt-section.car", + wantVersion: 1, + }, + { + name: "CarV2WrappingV1", + path: "testdata/sample-wrapped-v2.car", + wantVersion: 2, + }, + { + name: "CarV2ProducedByBlockstore", + path: "testdata/sample-rw-bs-v2.car", + wantVersion: 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := requireReaderFromPath(t, tt.path) + subject, err := carv2.NewBlockReader(r, carv2.ZeroLengthSectionAsEOF(tt.zerLenAsEOF)) + require.NoError(t, err) + + require.Equal(t, tt.wantVersion, subject.Version) + + var wantReader *carv1.CarReader + switch tt.wantVersion { + case 1: + wantReader = requireNewCarV1ReaderFromV1File(t, tt.path, tt.zerLenAsEOF) + case 2: + wantReader = requireNewCarV1ReaderFromV2File(t, tt.path, tt.zerLenAsEOF) + default: + require.Failf(t, "invalid test-case", "unknown wantVersion %v", tt.wantVersion) + } + require.Equal(t, wantReader.Header.Roots, subject.Roots) + + for { + gotBlock, gotErr := subject.Next() + wantBlock, wantErr := wantReader.Next() + require.Equal(t, wantBlock, gotBlock) + require.Equal(t, wantErr, gotErr) + if gotErr == io.EOF { + break + } + } + }) + } +} + +func requireReaderFromPath(t *testing.T, path string) io.Reader { + f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + return f +} diff --git a/ipld/car/v2/blockstore/bench_test.go b/ipld/car/v2/blockstore/bench_test.go index c97dc35267..644acbe95c 100644 --- a/ipld/car/v2/blockstore/bench_test.go +++ b/ipld/car/v2/blockstore/bench_test.go @@ -11,14 +11,21 @@ import ( "github.com/ipld/go-car/v2/blockstore" ) -// Open a read-only blockstore, -// and retrieve all blocks in a shuffled order. +// BenchmarkOpenReadOnlyV1 opens a read-only blockstore, +// and retrieves all blocks in a shuffled order. // Note that this benchmark includes generating an index, // since the input file is a CARv1. - func BenchmarkOpenReadOnlyV1(b *testing.B) { path := "../testdata/sample-v1.car" - + f, err := os.Open("../testdata/sample-v1.car") + if err != nil { + b.Fatal(err) + } + defer func() { + if err := f.Close(); err != nil { + b.Fatal(err) + } + }() info, err := os.Stat(path) if err != nil { b.Fatal(err) @@ -27,12 +34,12 @@ func BenchmarkOpenReadOnlyV1(b *testing.B) { b.ReportAllocs() var shuffledCIDs []cid.Cid - cr, err := carv2.OpenReader(path) + br, err := carv2.NewBlockReader(f) if err != nil { b.Fatal(err) } for { - block, err := cr.Next() + block, err := br.Next() if err == io.EOF { break } @@ -41,7 +48,6 @@ func BenchmarkOpenReadOnlyV1(b *testing.B) { } shuffledCIDs = append(shuffledCIDs, block.Cid()) } - cr.Close() // The shuffling needs to be deterministic, // for the sake of stable benchmark results. @@ -65,7 +71,9 @@ func BenchmarkOpenReadOnlyV1(b *testing.B) { } } - bs.Close() + if err := bs.Close(); err != nil { + b.Fatal(err) + } } }) } diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go index 37bcc22b53..24223c634d 100644 --- a/ipld/car/v2/example_test.go +++ b/ipld/car/v2/example_test.go @@ -3,6 +3,7 @@ package car_test import ( "bytes" "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -68,3 +69,61 @@ func ExampleWrapV1File() { // Inner CARv1 is exactly the same: true // [Block bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy] } + +// ExampleNewBlockReader instantiates a new BlockReader for a CARv1 file and its wrapped CARv2 +// version. For each file, it prints the version, the root CIDs and the first five block CIDs. +// Note, the roots and first five block CIDs are identical in both files since both represent the +// same root CIDs and data blocks. +func ExampleNewBlockReader() { + for _, path := range []string{ + "testdata/sample-v1.car", + "testdata/sample-wrapped-v2.car", + } { + fmt.Println("File:", path) + f, err := os.Open(path) + if err != nil { + panic(err) + } + br, err := carv2.NewBlockReader(f) + if err != nil { + panic(err) + } + defer func() { + if err := f.Close(); err != nil { + panic(err) + } + }() + fmt.Println("Version:", br.Version) + fmt.Println("Roots:", br.Roots) + fmt.Println("First 5 block CIDs:") + for i := 0; i < 5; i++ { + bl, err := br.Next() + if err == io.EOF { + break + } + if err != nil { + panic(err) + } + fmt.Printf("\t%v\n", bl.Cid()) + } + } + // Output: + // File: testdata/sample-v1.car + // Version: 1 + // Roots: [bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy] + // First 5 block CIDs: + // bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy + // bafy2bzaceaycv7jhaegckatnncu5yugzkrnzeqsppzegufr35lroxxnsnpspu + // bafy2bzaceb62wdepofqu34afqhbcn4a7jziwblt2ih5hhqqm6zitd3qpzhdp4 + // bafy2bzaceb3utcspm5jqcdqpih3ztbaztv7yunzkiyfq7up7xmokpxemwgu5u + // bafy2bzacedjwekyjresrwjqj4n2r5bnuuu3klncgjo2r3slsp6wgqb37sz4ck + // File: testdata/sample-wrapped-v2.car + // Version: 2 + // Roots: [bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy] + // First 5 block CIDs: + // bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy + // bafy2bzaceaycv7jhaegckatnncu5yugzkrnzeqsppzegufr35lroxxnsnpspu + // bafy2bzaceb62wdepofqu34afqhbcn4a7jziwblt2ih5hhqqm6zitd3qpzhdp4 + // bafy2bzaceb3utcspm5jqcdqpih3ztbaztv7yunzkiyfq7up7xmokpxemwgu5u + // bafy2bzacedjwekyjresrwjqj4n2r5bnuuu3klncgjo2r3slsp6wgqb37sz4ck +} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index b4b4d0d45b..db8f821051 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -4,8 +4,6 @@ import ( "fmt" "io" - blocks "github.com/ipfs/go-block-format" - internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipfs/go-cid" @@ -20,11 +18,7 @@ type Reader struct { r io.ReaderAt roots []cid.Cid ropts ReadOptions - // carV1Reader is lazily created, is not reusable, and exclusively used by Reader.Next. - // Note, this reader is forward-only and cannot be rewound. Once it reaches the end of the data - // payload, it will always return io.EOF. - carV1Reader *carv1.CarReader - closer io.Closer + closer io.Closer } // OpenReader is a wrapper for NewReader which opens the file at path. @@ -133,26 +127,6 @@ func (r *Reader) Close() error { return nil } -// Next reads the next block in the data payload with an io.EOF error indicating the end is reached. -// Note, this function is forward-only; once the end has been reached it will always return io.EOF. -func (r *Reader) Next() (blocks.Block, error) { - if r.carV1Reader == nil { - var err error - if r.carV1Reader, err = r.newCarV1Reader(); err != nil { - return nil, err - } - } - return r.carV1Reader.Next() -} - -func (r *Reader) newCarV1Reader() (*carv1.CarReader, error) { - dr := r.DataReader() - if r.ropts.ZeroLengthSectionAsEOF { - return carv1.NewCarReaderWithZeroLengthSectionAsEOF(dr) - } - return carv1.NewCarReader(dr) -} - // ReadVersion reads the version from the pragma. // This function accepts both CARv1 and CARv2 payloads. func ReadVersion(r io.Reader) (uint64, error) { diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index 8b5ec228f6..79d6f855d2 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -136,16 +136,6 @@ func TestReader_WithCarV1Consistency(t *testing.T) { require.NoError(t, err) require.Equal(t, wantReader.Header.Roots, gotRoots) require.Nil(t, subject.IndexReader()) - - for { - gotBlock, gotErr := subject.Next() - wantBlock, wantErr := wantReader.Next() - require.Equal(t, wantBlock, gotBlock) - require.Equal(t, wantErr, gotErr) - if gotErr == io.EOF { - break - } - } }) } } @@ -184,16 +174,6 @@ func TestReader_WithCarV2Consistency(t *testing.T) { wantIndex, err := carv2.GenerateIndex(subject.DataReader()) require.NoError(t, err) require.Equal(t, wantIndex, gotIndex) - - for { - gotBlock, gotErr := subject.Next() - wantBlock, wantErr := wantReader.Next() - require.Equal(t, wantBlock, gotBlock) - require.Equal(t, wantErr, gotErr) - if gotErr == io.EOF { - break - } - } }) } } From 5045cdd7462ba9999425826407b9367682c3b7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 3 Aug 2021 18:59:24 +0100 Subject: [PATCH 143/291] blockstore: stop embedding ReadOnly in ReadWrite It has unintended side effects, as we leak the inner ReadOnly value as well as its methods to the parent ReadWrite API. One unintended consequence is that it was possible to "close" a ReadWrite, when we only wanted to support finalizing it. The resumption tests do want to close without finalization, for the sake of emulating partially-written CARv2 files. Those can hook into the unexported API via export_test.go. If a downstream really wants to support closing a ReadWrite blockstore without finalizing it, two alternatives are outlined in export_test.go. This commit was moved from ipld/go-car@3752fdb3f2d6be8cf6da7c37591c0ac5822c0822 --- ipld/car/v2/blockstore/example_test.go | 2 - ipld/car/v2/blockstore/export_test.go | 11 +++++ ipld/car/v2/blockstore/readwrite.go | 53 +++++++++++++++--------- ipld/car/v2/blockstore/readwrite_test.go | 14 +++---- 4 files changed, 51 insertions(+), 29 deletions(-) create mode 100644 ipld/car/v2/blockstore/export_test.go diff --git a/ipld/car/v2/blockstore/example_test.go b/ipld/car/v2/blockstore/example_test.go index 3091e471e7..7e826addb0 100644 --- a/ipld/car/v2/blockstore/example_test.go +++ b/ipld/car/v2/blockstore/example_test.go @@ -93,7 +93,6 @@ func ExampleOpenReadWrite() { if err != nil { panic(err) } - defer rwbs.Close() // Put all blocks onto the blockstore. blocks := []blocks.Block{thisBlock, thatBlock} @@ -121,7 +120,6 @@ func ExampleOpenReadWrite() { if err != nil { panic(err) } - defer resumedRwbos.Close() // Put another block, appending it to the set of blocks that are written previously. if err := resumedRwbos.Put(andTheOtherBlock); err != nil { diff --git a/ipld/car/v2/blockstore/export_test.go b/ipld/car/v2/blockstore/export_test.go new file mode 100644 index 0000000000..d4998eec8c --- /dev/null +++ b/ipld/car/v2/blockstore/export_test.go @@ -0,0 +1,11 @@ +package blockstore + +// CloseReadWrite allows our external tests to close a read-write blockstore +// without finalizing it. +// The public API doesn't expose such a method. +// In the future, we might consider adding NewReadWrite taking io interfaces, +// meaning that the caller could be fully in control of opening and closing files. +// Another option would be to expose a "Discard" method alongside "Finalize". +func CloseReadWrite(b *ReadWrite) error { + return b.ronly.Close() +} diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 1192e749ca..8986ff3055 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -35,11 +35,12 @@ var errFinalized = fmt.Errorf("cannot use a read-write carv2 blockstore after fi // Upon calling Finalize header is finalized and index is written out. // Once finalized, all read and write calls to this blockstore will result in panics. type ReadWrite struct { + ronly ReadOnly + f *os.File dataWriter *internalio.OffsetWriteSeeker - ReadOnly - idx *insertionIndex - header carv2.Header + idx *insertionIndex + header carv2.Header wopts carv2.WriteOptions } @@ -120,7 +121,7 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.ReadWriteOption) for _, opt := range opts { switch opt := opt.(type) { case carv2.ReadOption: - opt(&rwbs.ropts) + opt(&rwbs.ronly.ropts) case carv2.WriteOption: opt(&rwbs.wopts) } @@ -134,9 +135,9 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.ReadWriteOption) rwbs.dataWriter = internalio.NewOffsetWriter(rwbs.f, int64(rwbs.header.DataOffset)) v1r := internalio.NewOffsetReadSeeker(rwbs.f, int64(rwbs.header.DataOffset)) - rwbs.ReadOnly.backing = v1r - rwbs.ReadOnly.idx = rwbs.idx - rwbs.ReadOnly.carv2Closer = rwbs.f + rwbs.ronly.backing = v1r + rwbs.ronly.idx = rwbs.idx + rwbs.ronly.carv2Closer = rwbs.f if resume { if err = rwbs.resumeWithRoots(roots); err != nil { @@ -216,7 +217,7 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { } // Use the given CARv1 padding to instantiate the CARv1 reader on file. - v1r := internalio.NewOffsetReadSeeker(b.ReadOnly.backing, 0) + v1r := internalio.NewOffsetReadSeeker(b.ronly.backing, 0) header, err := carv1.ReadHeader(v1r) if err != nil { // Cannot read the CARv1 header; the file is most likely corrupt. @@ -256,7 +257,7 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { // Null padding; by default it's an error. if length == 0 { - if b.ropts.ZeroLengthSectionAsEOF { + if b.ronly.ropts.ZeroLengthSectionAsEOF { break } else { return fmt.Errorf("carv1 null padding not allowed by default; see WithZeroLegthSectionAsEOF") @@ -303,17 +304,17 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { return errFinalized } - b.mu.Lock() - defer b.mu.Unlock() + b.ronly.mu.Lock() + defer b.ronly.mu.Unlock() for _, bl := range blks { c := bl.Cid() if !b.wopts.BlockstoreAllowDuplicatePuts { - if b.ropts.BlockstoreUseWholeCIDs && b.idx.hasExactCID(c) { + if b.ronly.ropts.BlockstoreUseWholeCIDs && b.idx.hasExactCID(c) { continue // deduplicated by CID } - if !b.ropts.BlockstoreUseWholeCIDs { + if !b.ronly.ropts.BlockstoreUseWholeCIDs { _, err := b.idx.Get(c) if err == nil { continue // deduplicated by hash @@ -340,8 +341,8 @@ func (b *ReadWrite) Finalize() error { return fmt.Errorf("called Finalize twice") } - b.mu.Lock() - defer b.mu.Unlock() + b.ronly.mu.Lock() + defer b.ronly.mu.Unlock() // TODO check if add index option is set and don't write the index then set index offset to zero. b.header = b.header.WithDataSize(uint64(b.dataWriter.Position())) @@ -349,7 +350,7 @@ func (b *ReadWrite) Finalize() error { // mutex we're holding here. // TODO: should we check the error here? especially with OpenReadWrite, // we should care about close errors. - defer b.closeWithoutMutex() + defer b.ronly.closeWithoutMutex() // TODO if index not needed don't bother flattening it. fi, err := b.idx.flatten() @@ -368,7 +369,7 @@ func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { return nil, errFinalized } - return b.ReadOnly.AllKeysChan(ctx) + return b.ronly.AllKeysChan(ctx) } func (b *ReadWrite) Has(key cid.Cid) (bool, error) { @@ -376,7 +377,7 @@ func (b *ReadWrite) Has(key cid.Cid) (bool, error) { return false, errFinalized } - return b.ReadOnly.Has(key) + return b.ronly.Has(key) } func (b *ReadWrite) Get(key cid.Cid) (blocks.Block, error) { @@ -384,7 +385,7 @@ func (b *ReadWrite) Get(key cid.Cid) (blocks.Block, error) { return nil, errFinalized } - return b.ReadOnly.Get(key) + return b.ronly.Get(key) } func (b *ReadWrite) GetSize(key cid.Cid) (int, error) { @@ -392,5 +393,17 @@ func (b *ReadWrite) GetSize(key cid.Cid) (int, error) { return 0, errFinalized } - return b.ReadOnly.GetSize(key) + return b.ronly.GetSize(key) +} + +func (b *ReadWrite) DeleteBlock(_ cid.Cid) error { + return fmt.Errorf("ReadWrite blockstore does not support deleting blocks") +} + +func (b *ReadWrite) HashOnRead(enable bool) { + b.ronly.HashOnRead(enable) +} + +func (b *ReadWrite) Roots() ([]cid.Cid, error) { + return b.ronly.Roots() } diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 77442de0dc..5316f5bf7e 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -38,7 +38,7 @@ var ( func TestReadWriteGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) { path := filepath.Join(t.TempDir(), "readwrite-err-not-found.car") subject, err := blockstore.OpenReadWrite(path, []cid.Cid{}) - t.Cleanup(func() { subject.Close() }) + t.Cleanup(func() { subject.Finalize() }) require.NoError(t, err) nonExistingKey := merkledag.NewRawNode([]byte("undadasea")).Block.Cid() @@ -373,7 +373,7 @@ func TestBlockstoreResumption(t *testing.T) { // Close off the open file and re-instantiate a new subject with resumption enabled. // Note, we don't have to close the file for resumption to work. // We do this to avoid resource leak during testing. - require.NoError(t, subject.Close()) + require.NoError(t, blockstore.CloseReadWrite(subject)) } subject, err = blockstore.OpenReadWrite(path, r.Header.Roots, blockstore.UseWholeCIDs(true)) @@ -405,7 +405,7 @@ func TestBlockstoreResumption(t *testing.T) { require.Equal(t, wantBlockCountSoFar, gotBlockCountSoFar) } } - require.NoError(t, subject.Close()) + require.NoError(t, blockstore.CloseReadWrite(subject)) // Finalize the blockstore to complete partially written CARv2 file. subject, err = blockstore.OpenReadWrite(path, r.Header.Roots, @@ -460,8 +460,8 @@ func TestBlockstoreResumptionIsSupportedOnFinalizedFile(t *testing.T) { require.NoError(t, err) require.NoError(t, subject.Finalize()) subject, err = blockstore.OpenReadWrite(path, []cid.Cid{}) - t.Cleanup(func() { subject.Close() }) require.NoError(t, err) + t.Cleanup(func() { subject.Finalize() }) } func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { @@ -472,7 +472,6 @@ func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { subject, err := blockstore.OpenReadWrite(path, wantRoots) require.NoError(t, err) - t.Cleanup(func() { subject.Close() }) require.NoError(t, subject.Put(oneTestBlockWithCidV1)) require.NoError(t, subject.Put(anotherTestBlockWithCidV0)) @@ -500,6 +499,9 @@ func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { require.NoError(t, subject.Finalize()) require.Error(t, subject.Finalize()) + _, ok := (interface{})(subject).(io.Closer) + require.False(t, ok) + _, err = subject.Get(oneTestBlockCid) require.Error(t, err) _, err = subject.GetSize(anotherTestBlockCid) @@ -528,7 +530,6 @@ func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { carv2.UseDataPadding(wantCarV1Padding), carv2.UseIndexPadding(wantIndexPadding)) require.NoError(t, err) - t.Cleanup(func() { subject.Close() }) require.NoError(t, subject.Put(oneTestBlockWithCidV1)) require.NoError(t, subject.Put(anotherTestBlockWithCidV0)) require.NoError(t, subject.Finalize()) @@ -606,7 +607,6 @@ func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing. WantRoots, carv2.UseDataPadding(1413)) require.NoError(t, err) - t.Cleanup(func() { subject.Close() }) require.NoError(t, subject.Put(oneTestBlockWithCidV1)) require.NoError(t, subject.Finalize()) From cffae0eb1a8f20ff0069546cdbaf34e5ed371292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Fri, 30 Jul 2021 15:07:36 +0100 Subject: [PATCH 144/291] update package godocs and root level README for v2 While at it, make v2's license a symlink, so the two stay in sync. The reason we want the v2 module to have its own license is so that its module zip includes the file as well. Besides mentioning v0 and v2, the root README now also links to pkgsite and lists Masih and myself as default maintainers, given that we're the ones that did major work on this library last. Fixes #124. Fixes #179. This commit was moved from ipld/go-car@61cfb4dbcfc4bf48c54425385a1aae279b2b6ca1 --- ipld/car/README.md | 17 ++- ipld/car/v2/LICENSE.md | 230 +--------------------------------- ipld/car/v2/blockstore/doc.go | 2 +- ipld/car/v2/doc.go | 9 +- 4 files changed, 21 insertions(+), 237 deletions(-) mode change 100644 => 120000 ipld/car/v2/LICENSE.md diff --git a/ipld/car/README.md b/ipld/car/README.md index 033318ee12..faec4b69aa 100644 --- a/ipld/car/README.md +++ b/ipld/car/README.md @@ -4,17 +4,24 @@ go-car (go!) [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) [![](https://img.shields.io/badge/project-ipld-orange.svg?style=flat-square)](https://github.com/ipld/ipld) [![](https://img.shields.io/badge/freenode-%23ipld-orange.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipld) +[![Go Reference](https://pkg.go.dev/badge/github.com/ipld/go-car.svg)](https://pkg.go.dev/github.com/ipld/go-car) [![Coverage Status](https://codecov.io/gh/ipld/go-car/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/go-car/branch/master) -> go-car is a simple way of packing a merkledag into a single file +> A library to interact with merkledags stored as a single file +This is an implementation in Go of the [CAR spec](https://ipld.io/specs/transport/car/). -## Table of Contents +Note that there are two major module versions: -- [Install](#install) -- [Contribute](#contribute) -- [License](#license) +* [go-car/v2](v2/) is geared towards reading and writing CARv2 files, and also + supports consuming CARv1 files and using CAR files as an IPFS blockstore. +* go-car v0, in the root directory, just supports reading and writing CARv1 files. +Most users should attempt to use v2, especially for new software. + +## Maintainers + +[Daniel Martí](https://github.com/mvdan) and [Masih Derkani](https://github.com/masih). ## Contribute diff --git a/ipld/car/v2/LICENSE.md b/ipld/car/v2/LICENSE.md deleted file mode 100644 index 15601cba67..0000000000 --- a/ipld/car/v2/LICENSE.md +++ /dev/null @@ -1,229 +0,0 @@ -The contents of this repository are Copyright (c) corresponding authors and -contributors, licensed under the `Permissive License Stack` meaning either of: - -- Apache-2.0 Software License: https://www.apache.org/licenses/LICENSE-2.0 - ([...4tr2kfsq](https://gateway.ipfs.io/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) - -- MIT Software License: https://opensource.org/licenses/MIT - ([...vljevcba](https://gateway.ipfs.io/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) - -You may not use the contents of this repository except in compliance -with one of the listed Licenses. For an extended clarification of the -intent behind the choice of Licensing please refer to -https://protocol.ai/blog/announcing-the-permissive-license-stack/ - -Unless required by applicable law or agreed to in writing, software -distributed under the terms listed in this notice is distributed on -an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, -either express or implied. See each License for the specific language -governing permissions and limitations under that License. - - -`SPDX-License-Identifier: Apache-2.0 OR MIT` - -Verbatim copies of both licenses are included below: - -

    Apache-2.0 Software License - -``` - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS -``` -
    - -
    MIT Software License - -``` -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -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. -``` -
    diff --git a/ipld/car/v2/LICENSE.md b/ipld/car/v2/LICENSE.md new file mode 120000 index 0000000000..7eabdb1c27 --- /dev/null +++ b/ipld/car/v2/LICENSE.md @@ -0,0 +1 @@ +../LICENSE.md \ No newline at end of file diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index 180dc7292f..de95d63bfc 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -1,4 +1,4 @@ -// package blockstore implements IPFS blockstore interface backed by a CAR file. +// package blockstore implements the IPFS blockstore interface backed by a CAR file. // This package provides two flavours of blockstore: ReadOnly and ReadWrite. // // The ReadOnly blockstore provides a read-only random access from a given data payload either in diff --git a/ipld/car/v2/doc.go b/ipld/car/v2/doc.go index 2029d7cf38..b12266649a 100644 --- a/ipld/car/v2/doc.go +++ b/ipld/car/v2/doc.go @@ -1,3 +1,8 @@ -// Package car represents the CARv2 implementation. -// TODO add CARv2 byte structure here. +// Package car allows inspecting and reading CAR files, +// described at https://ipld.io/specs/transport/car/. +// The entire library is geared towards the CARv2 spec, +// but many of the APIs consuming CAR files also accept CARv1. +// +// The blockstore sub-package contains an implementation of the +// go-ipfs-blockstore interface. package car From 1296deb10c924807dfaa924a95cf8b7d6f9a4bed Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 4 Aug 2021 16:36:26 +0100 Subject: [PATCH 145/291] Update the readme with link to examples Add examples link to the README and refine its structure. List features of CARv2. Use stronger language to encourage users to use `v2` over `v0`. This commit was moved from ipld/go-car@92dec83621a0da02046f8e29313d8e6e02b2e54e --- ipld/car/README.md | 48 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/ipld/car/README.md b/ipld/car/README.md index faec4b69aa..7ccf7cca31 100644 --- a/ipld/car/README.md +++ b/ipld/car/README.md @@ -9,25 +9,61 @@ go-car (go!) > A library to interact with merkledags stored as a single file -This is an implementation in Go of the [CAR spec](https://ipld.io/specs/transport/car/). +This is a Go implementation of the [CAR specifications](https://ipld.io/specs/transport/car/), both [CARv1](https://ipld.io/specs/transport/car/carv1/) and [CARv2](https://ipld.io/specs/transport/car/carv2/). Note that there are two major module versions: -* [go-car/v2](v2/) is geared towards reading and writing CARv2 files, and also +* [`go-car/v2`](v2/) is geared towards reading and writing CARv2 files, and also supports consuming CARv1 files and using CAR files as an IPFS blockstore. -* go-car v0, in the root directory, just supports reading and writing CARv1 files. +* `go-car` v0, in the root directory, just supports reading and writing CARv1 files. -Most users should attempt to use v2, especially for new software. +Most users should use v2, especially for new software, since the v2 API transparently supports both CAR formats. + +## Features + +[CARv2](v2) features: +* [Generate index](https://pkg.go.dev/github.com/ipld/go-car/v2#GenerateIndex) from an existing CARv1 file +* [Wrap](https://pkg.go.dev/github.com/ipld/go-car/v2#WrapV1) CARv1 files into a CARv2 with automatic index generation. +* Random-access to blocks in a CAR file given their CID via [Read-Only blockstore](https://pkg.go.dev/github.com/ipld/go-car/v2/blockstore#NewReadOnly) API, with transparent support for both CARv1 and CARv2 +* Write CARv2 files via [Read-Write blockstore](https://pkg.go.dev/github.com/ipld/go-car/v2/blockstore#OpenReadWrite) API, with support for appending blocks to an existing CARv2 file, and resumption from a partially written CARv2 files. +* Individual access to [inner CARv1 data payload]((https://pkg.go.dev/github.com/ipld/go-car/v2#Reader.DataReader)) and [index]((https://pkg.go.dev/github.com/ipld/go-car/v2#Reader.IndexReader)) of a CARv2 file via the `Reader` API. + +## Install + +To install the latest version of `go-car/v2` module, run: +```shell script +go get github.com/ipld/go-car/v2 +``` + +Alternatively, to install the v0 module, run: +```shell script +go get github.com/ipld/go-car +``` + +## API Documentation + +See docs on [pkg.go.dev](https://pkg.go.dev/github.com/ipld/go-car). + +## Examples + +Here is a shortlist of other examples from the documentation + +* [Wrap an existing CARv1 file into an indexed CARv2 file](https://pkg.go.dev/github.com/ipld/go-car/v2#example-WrapV1File) +* [Open read-only blockstore from a CAR file](https://pkg.go.dev/github.com/ipld/go-car/v2/blockstore#example-OpenReadOnly) +* [Open read-write blockstore from a CAR file](https://pkg.go.dev/github.com/ipld/go-car/v2/blockstore#example-OpenReadWrite) +* [Read the index from an existing CARv2 file](https://pkg.go.dev/github.com/ipld/go-car/v2/index#example-ReadFrom) +* [Extract the index from a CARv2 file and store it as a separate file](https://pkg.go.dev/github.com/ipld/go-car/v2/index#example-WriteTo) ## Maintainers -[Daniel Martí](https://github.com/mvdan) and [Masih Derkani](https://github.com/masih). +* [Daniel Martí](https://github.com/mvdan) +* [Masih Derkani](https://github.com/masih) ## Contribute PRs are welcome! -Small note: If editing the Readme, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. +When editing the Readme, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. ## License From 7ab33240b1154a548b3fb25ed7243a7c58a82bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 5 Aug 2021 17:44:38 +0100 Subject: [PATCH 146/291] v2: stop using a symlink for LICENSE.md Go modules has to support many OSs and filesystems, so it completely ignores symlinks when creating module zips. And parts of the Go ecosystem, like pkg.go.dev, consume those zips. So this symlink actually makes the module technically non-OSS, since it completely lacks a LICENSE file when downloaded. pkg.go.dev thus refuses to show its documentation. One option would be to rename the root LICENSE.md file to just LICENSE, since cmd/go treats that file in a special way, including it in subdirectory-module zips. Unfortunately, we do want the ".md" extension, since the file is formatted to outline the double license. As such, simply copy the file. It should be fine as it's mostly static. This commit was moved from ipld/go-car@00a30a95ae6c7a6a8596f5166f93156678b631e2 --- ipld/car/LICENSE.md | 229 ---------------------------------------- ipld/car/v2/LICENSE.md | 230 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 229 insertions(+), 230 deletions(-) delete mode 100644 ipld/car/LICENSE.md mode change 120000 => 100644 ipld/car/v2/LICENSE.md diff --git a/ipld/car/LICENSE.md b/ipld/car/LICENSE.md deleted file mode 100644 index 15601cba67..0000000000 --- a/ipld/car/LICENSE.md +++ /dev/null @@ -1,229 +0,0 @@ -The contents of this repository are Copyright (c) corresponding authors and -contributors, licensed under the `Permissive License Stack` meaning either of: - -- Apache-2.0 Software License: https://www.apache.org/licenses/LICENSE-2.0 - ([...4tr2kfsq](https://gateway.ipfs.io/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) - -- MIT Software License: https://opensource.org/licenses/MIT - ([...vljevcba](https://gateway.ipfs.io/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) - -You may not use the contents of this repository except in compliance -with one of the listed Licenses. For an extended clarification of the -intent behind the choice of Licensing please refer to -https://protocol.ai/blog/announcing-the-permissive-license-stack/ - -Unless required by applicable law or agreed to in writing, software -distributed under the terms listed in this notice is distributed on -an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, -either express or implied. See each License for the specific language -governing permissions and limitations under that License. - - -`SPDX-License-Identifier: Apache-2.0 OR MIT` - -Verbatim copies of both licenses are included below: - -
    Apache-2.0 Software License - -``` - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS -``` -
    - -
    MIT Software License - -``` -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -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. -``` -
    diff --git a/ipld/car/v2/LICENSE.md b/ipld/car/v2/LICENSE.md deleted file mode 120000 index 7eabdb1c27..0000000000 --- a/ipld/car/v2/LICENSE.md +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.md \ No newline at end of file diff --git a/ipld/car/v2/LICENSE.md b/ipld/car/v2/LICENSE.md new file mode 100644 index 0000000000..15601cba67 --- /dev/null +++ b/ipld/car/v2/LICENSE.md @@ -0,0 +1,229 @@ +The contents of this repository are Copyright (c) corresponding authors and +contributors, licensed under the `Permissive License Stack` meaning either of: + +- Apache-2.0 Software License: https://www.apache.org/licenses/LICENSE-2.0 + ([...4tr2kfsq](https://gateway.ipfs.io/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) + +- MIT Software License: https://opensource.org/licenses/MIT + ([...vljevcba](https://gateway.ipfs.io/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) + +You may not use the contents of this repository except in compliance +with one of the listed Licenses. For an extended clarification of the +intent behind the choice of Licensing please refer to +https://protocol.ai/blog/announcing-the-permissive-license-stack/ + +Unless required by applicable law or agreed to in writing, software +distributed under the terms listed in this notice is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +either express or implied. See each License for the specific language +governing permissions and limitations under that License. + + +`SPDX-License-Identifier: Apache-2.0 OR MIT` + +Verbatim copies of both licenses are included below: + +
    Apache-2.0 Software License + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +``` +
    + +
    MIT Software License + +``` +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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. +``` +
    From 6e7b62dd830fa13e8b305e6df23712492e4aad13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 5 Aug 2021 17:58:52 +0100 Subject: [PATCH 147/291] re-add root LICENSE file The last commit did a mv. I intended to do a cp. This commit was moved from ipld/go-car@2c0f1eee363d6ccdaff9849b763035913fcd895d --- ipld/car/LICENSE.md | 229 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 ipld/car/LICENSE.md diff --git a/ipld/car/LICENSE.md b/ipld/car/LICENSE.md new file mode 100644 index 0000000000..15601cba67 --- /dev/null +++ b/ipld/car/LICENSE.md @@ -0,0 +1,229 @@ +The contents of this repository are Copyright (c) corresponding authors and +contributors, licensed under the `Permissive License Stack` meaning either of: + +- Apache-2.0 Software License: https://www.apache.org/licenses/LICENSE-2.0 + ([...4tr2kfsq](https://gateway.ipfs.io/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) + +- MIT Software License: https://opensource.org/licenses/MIT + ([...vljevcba](https://gateway.ipfs.io/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) + +You may not use the contents of this repository except in compliance +with one of the listed Licenses. For an extended clarification of the +intent behind the choice of Licensing please refer to +https://protocol.ai/blog/announcing-the-permissive-license-stack/ + +Unless required by applicable law or agreed to in writing, software +distributed under the terms listed in this notice is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +either express or implied. See each License for the specific language +governing permissions and limitations under that License. + + +`SPDX-License-Identifier: Apache-2.0 OR MIT` + +Verbatim copies of both licenses are included below: + +
    Apache-2.0 Software License + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS +``` +
    + +
    MIT Software License + +``` +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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. +``` +
    From 2585bdf4ab09067b5d98ceb7b537a00202d7efcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Fri, 6 Aug 2021 16:03:58 +0100 Subject: [PATCH 148/291] update LICENSE files to point to the new gateway This commit was moved from ipld/go-car@6c87996fda3e65ebab397c4278cfc64e815285fb --- ipld/car/LICENSE.md | 4 ++-- ipld/car/v2/LICENSE.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ipld/car/LICENSE.md b/ipld/car/LICENSE.md index 15601cba67..2fa16a1537 100644 --- a/ipld/car/LICENSE.md +++ b/ipld/car/LICENSE.md @@ -2,10 +2,10 @@ The contents of this repository are Copyright (c) corresponding authors and contributors, licensed under the `Permissive License Stack` meaning either of: - Apache-2.0 Software License: https://www.apache.org/licenses/LICENSE-2.0 - ([...4tr2kfsq](https://gateway.ipfs.io/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) + ([...4tr2kfsq](https://dweb.link/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) - MIT Software License: https://opensource.org/licenses/MIT - ([...vljevcba](https://gateway.ipfs.io/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) + ([...vljevcba](https://dweb.link/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) You may not use the contents of this repository except in compliance with one of the listed Licenses. For an extended clarification of the diff --git a/ipld/car/v2/LICENSE.md b/ipld/car/v2/LICENSE.md index 15601cba67..2fa16a1537 100644 --- a/ipld/car/v2/LICENSE.md +++ b/ipld/car/v2/LICENSE.md @@ -2,10 +2,10 @@ The contents of this repository are Copyright (c) corresponding authors and contributors, licensed under the `Permissive License Stack` meaning either of: - Apache-2.0 Software License: https://www.apache.org/licenses/LICENSE-2.0 - ([...4tr2kfsq](https://gateway.ipfs.io/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) + ([...4tr2kfsq](https://dweb.link/ipfs/bafkreiankqxazcae4onkp436wag2lj3ccso4nawxqkkfckd6cg4tr2kfsq)) - MIT Software License: https://opensource.org/licenses/MIT - ([...vljevcba](https://gateway.ipfs.io/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) + ([...vljevcba](https://dweb.link/ipfs/bafkreiepofszg4gfe2gzuhojmksgemsub2h4uy2gewdnr35kswvljevcba)) You may not use the contents of this repository except in compliance with one of the listed Licenses. For an extended clarification of the From facff846be34777c82d6401a10b2a7ebed42e9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 10 Aug 2021 10:57:48 +0100 Subject: [PATCH 149/291] v2/blockstore: add ReadWrite.Discard This allows closing a read-write blockstore without doing the extra work to finalize its header and index. Can be useful if an entire piece of work is cancelled, and also simplifies the tests. Also make ReadOnly error in a straightforward way if it is used after being closed. Before, this could lead to panics, as we'd attempt to read the CARv1 file when it's closed. Both mechanisms now use a "closed" boolean, which is consistent and simpler than checking a header field. Finally, add tests that ensure both ReadOnly and ReadWrite behave as intended once they have been closed. The tests also uncovered that AllKeysChan would not release the mutex if it encountered an error early on. Fix that, too. While at it, fix some now-obsolete references to panics on unsupported or after-close method calls. Fixes #205. This commit was moved from ipld/go-car@039ddc7c8d11415c11b3f84a202c076fae25f748 --- ipld/car/v2/blockstore/doc.go | 2 +- ipld/car/v2/blockstore/export_test.go | 11 ---- ipld/car/v2/blockstore/readonly.go | 80 ++++++++++++++++-------- ipld/car/v2/blockstore/readonly_test.go | 37 ++++++++++- ipld/car/v2/blockstore/readwrite.go | 72 ++++++++++----------- ipld/car/v2/blockstore/readwrite_test.go | 52 ++++++++++++++- 6 files changed, 175 insertions(+), 79 deletions(-) delete mode 100644 ipld/car/v2/blockstore/export_test.go diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index de95d63bfc..a82723419c 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -11,7 +11,7 @@ // The ReadWrite blockstore allows writing and reading of the blocks concurrently. The user of this // blockstore is responsible for calling ReadWrite.Finalize when finished writing blocks. // Upon finalization, the instance can no longer be used for reading or writing blocks and will -// panic if used. To continue reading the blocks users are encouraged to use ReadOnly blockstore +// error if used. To continue reading the blocks users are encouraged to use ReadOnly blockstore // instantiated from the same file path using OpenReadOnly. // A user may resume reading/writing from files produced by an instance of ReadWrite blockstore. The // resumption is attempted automatically, if the path passed to OpenReadWrite exists. diff --git a/ipld/car/v2/blockstore/export_test.go b/ipld/car/v2/blockstore/export_test.go deleted file mode 100644 index d4998eec8c..0000000000 --- a/ipld/car/v2/blockstore/export_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package blockstore - -// CloseReadWrite allows our external tests to close a read-write blockstore -// without finalizing it. -// The public API doesn't expose such a method. -// In the future, we might consider adding NewReadWrite taking io interfaces, -// meaning that the caller could be fully in control of opening and closing files. -// Another option would be to expose a "Discard" method alongside "Finalize". -func CloseReadWrite(b *ReadWrite) error { - return b.ronly.Close() -} diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 2df9a7946b..7e165bd245 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -27,32 +27,36 @@ var _ blockstore.Blockstore = (*ReadOnly)(nil) var ( errZeroLengthSection = fmt.Errorf("zero-length carv2 section not allowed by default; see WithZeroLengthSectionAsEOF option") errReadOnly = fmt.Errorf("called write method on a read-only carv2 blockstore") + errClosed = fmt.Errorf("cannot use a carv2 blockstore after closing") ) -type ( - // ReadOnly provides a read-only CAR Block Store. - ReadOnly struct { - // mu allows ReadWrite to be safe for concurrent use. - // It's in ReadOnly so that read operations also grab read locks, - // given that ReadWrite embeds ReadOnly for methods like Get and Has. - // - // The main fields guarded by the mutex are the index and the underlying writers. - // For simplicity, the entirety of the blockstore methods grab the mutex. - mu sync.RWMutex - - // The backing containing the data payload in CARv1 format. - backing io.ReaderAt - // The CARv1 content index. - idx index.Index - - // If we called carv2.NewReaderMmap, remember to close it too. - carv2Closer io.Closer - - ropts carv2.ReadOptions - } +// ReadOnly provides a read-only CAR Block Store. +type ReadOnly struct { + // mu allows ReadWrite to be safe for concurrent use. + // It's in ReadOnly so that read operations also grab read locks, + // given that ReadWrite embeds ReadOnly for methods like Get and Has. + // + // The main fields guarded by the mutex are the index and the underlying writers. + // For simplicity, the entirety of the blockstore methods grab the mutex. + mu sync.RWMutex + + // When true, the blockstore has been closed via Close, Discard, or + // Finalize, and must not be used. Any further blockstore method calls + // will return errClosed to avoid panics or broken behavior. + closed bool + + // The backing containing the data payload in CARv1 format. + backing io.ReaderAt + // The CARv1 content index. + idx index.Index + + // If we called carv2.NewReaderMmap, remember to close it too. + carv2Closer io.Closer + + ropts carv2.ReadOptions +} - contextKey string -) +type contextKey string const asyncErrHandlerKey contextKey = "asyncErrorHandlerKey" @@ -177,7 +181,7 @@ func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { return bcid, data, err } -// DeleteBlock is unsupported and always panics. +// DeleteBlock is unsupported and always errors. func (b *ReadOnly) DeleteBlock(_ cid.Cid) error { return errReadOnly } @@ -187,6 +191,10 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { b.mu.RLock() defer b.mu.RUnlock() + if b.closed { + return false, errClosed + } + var fnFound bool var fnErr error err := b.idx.GetAll(key, func(offset uint64) bool { @@ -223,6 +231,10 @@ func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { b.mu.RLock() defer b.mu.RUnlock() + if b.closed { + return nil, errClosed + } + var fnData []byte var fnErr error err := b.idx.GetAll(key, func(offset uint64) bool { @@ -263,6 +275,10 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { b.mu.RLock() defer b.mu.RUnlock() + if b.closed { + return 0, errClosed + } + fnSize := -1 var fnErr error err := b.idx.GetAll(key, func(offset uint64) bool { @@ -329,16 +345,26 @@ func WithAsyncErrorHandler(ctx context.Context, errHandler func(error)) context. // See WithAsyncErrorHandler func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { // We release the lock when the channel-sending goroutine stops. + // Note that we can't use a deferred unlock here, + // because if we return a nil error, + // we only want to unlock once the async goroutine has stopped. b.mu.RLock() + if b.closed { + b.mu.RUnlock() // don't hold the mutex forever + return nil, errClosed + } + // TODO we may use this walk for populating the index, and we need to be able to iterate keys in this way somewhere for index generation. In general though, when it's asked for all keys from a blockstore with an index, we should iterate through the index when possible rather than linear reads through the full car. rdr := internalio.NewOffsetReadSeeker(b.backing, 0) header, err := carv1.ReadHeader(rdr) if err != nil { + b.mu.RUnlock() // don't hold the mutex forever return nil, fmt.Errorf("error reading car header: %w", err) } headerSize, err := carv1.HeaderSize(header) if err != nil { + b.mu.RUnlock() // don't hold the mutex forever return nil, err } @@ -347,6 +373,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { // Seek to the end of header. if _, err = rdr.Seek(int64(headerSize), io.SeekStart); err != nil { + b.mu.RUnlock() // don't hold the mutex forever return nil, err } @@ -424,10 +451,10 @@ func (b *ReadOnly) Roots() ([]cid.Cid, error) { } // Close closes the underlying reader if it was opened by OpenReadOnly. +// After this call, the blockstore can no longer be used. // // Note that this call may block if any blockstore operations are currently in -// progress, including an AllKeysChan that hasn't been fully consumed or -// cancelled. +// progress, including an AllKeysChan that hasn't been fully consumed or cancelled. func (b *ReadOnly) Close() error { b.mu.Lock() defer b.mu.Unlock() @@ -436,6 +463,7 @@ func (b *ReadOnly) Close() error { } func (b *ReadOnly) closeWithoutMutex() error { + b.closed = true if b.carv2Closer != nil { return b.carv2Closer.Close() } diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 3fd16c8c64..7b87aee2a9 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -101,7 +101,7 @@ func TestReadOnly(t *testing.T) { require.NoError(t, err) require.Equal(t, wantBlock, gotBlock) - // Assert write operations panic + // Assert write operations error require.Error(t, subject.Put(wantBlock)) require.Error(t, subject.PutMany([]blocks.Block{wantBlock})) require.Error(t, subject.DeleteBlock(key)) @@ -235,3 +235,38 @@ func newV1Reader(r io.Reader, zeroLenSectionAsEOF bool) (*carv1.CarReader, error } return carv1.NewCarReader(r) } + +func TestReadOnlyErrorAfterClose(t *testing.T) { + bs, err := OpenReadOnly("../testdata/sample-v1.car") + require.NoError(t, err) + + roots, err := bs.Roots() + require.NoError(t, err) + _, err = bs.Has(roots[0]) + require.NoError(t, err) + _, err = bs.Get(roots[0]) + require.NoError(t, err) + _, err = bs.GetSize(roots[0]) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + _, err = bs.AllKeysChan(ctx) + require.NoError(t, err) + cancel() // to stop the AllKeysChan goroutine + + bs.Close() + + _, err = bs.Roots() + require.Error(t, err) + _, err = bs.Has(roots[0]) + require.Error(t, err) + _, err = bs.Get(roots[0]) + require.Error(t, err) + _, err = bs.GetSize(roots[0]) + require.Error(t, err) + _, err = bs.AllKeysChan(ctx) + require.Error(t, err) + + // TODO: test that closing blocks if an AllKeysChan operation is + // in progress. +} diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 8986ff3055..ae482a7f9a 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -23,8 +23,6 @@ import ( var _ blockstore.Blockstore = (*ReadWrite)(nil) -var errFinalized = fmt.Errorf("cannot use a read-write carv2 blockstore after finalizing") - // ReadWrite implements a blockstore that stores blocks in CARv2 format. // Blocks put into the blockstore can be read back once they are successfully written. // This implementation is preferable for a write-heavy workload. @@ -33,7 +31,7 @@ var errFinalized = fmt.Errorf("cannot use a read-write carv2 blockstore after fi // // The Finalize function must be called once the putting blocks are finished. // Upon calling Finalize header is finalized and index is written out. -// Once finalized, all read and write calls to this blockstore will result in panics. +// Once finalized, all read and write calls to this blockstore will result in errors. type ReadWrite struct { ronly ReadOnly @@ -62,7 +60,7 @@ func AllowDuplicatePuts(allow bool) carv2.WriteOption { // ReadWrite.Finalize must be called once putting and reading blocks are no longer needed. // Upon calling ReadWrite.Finalize the CARv2 header and index are written out onto the file and the // backing file is closed. Once finalized, all read and write calls to this blockstore will result -// in panics. Note, ReadWrite.Finalize must be called on an open instance regardless of whether any +// in errors. Note, ReadWrite.Finalize must be called on an open instance regardless of whether any // blocks were put or not. // // If a file at given path does not exist, the instantiation will write car.Pragma and data payload @@ -287,26 +285,22 @@ func (b *ReadWrite) unfinalize() error { return err } -func (b *ReadWrite) finalized() bool { - return b.header.DataSize != 0 -} - // Put puts a given block to the underlying datastore func (b *ReadWrite) Put(blk blocks.Block) error { - // PutMany already checks b.finalized. + // PutMany already checks b.ronly.closed. return b.PutMany([]blocks.Block{blk}) } // PutMany puts a slice of blocks at the same time using batching // capabilities of the underlying datastore whenever possible. func (b *ReadWrite) PutMany(blks []blocks.Block) error { - if b.finalized() { - return errFinalized - } - b.ronly.mu.Lock() defer b.ronly.mu.Unlock() + if b.ronly.closed { + return errClosed + } + for _, bl := range blks { c := bl.Cid() @@ -331,25 +325,37 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { return nil } +// Discard closes this blockstore without finalizing its header and index. +// After this call, the blockstore can no longer be used. +// +// Note that this call may block if any blockstore operations are currently in +// progress, including an AllKeysChan that hasn't been fully consumed or cancelled. +func (b *ReadWrite) Discard() { + // Same semantics as ReadOnly.Close, including allowing duplicate calls. + // The only difference is that our method is called Discard, + // to further clarify that we're not properly finalizing and writing a + // CARv2 file. + b.ronly.Close() +} + // Finalize finalizes this blockstore by writing the CARv2 header, along with flattened index // for more efficient subsequent read. -// After this call, this blockstore can no longer be used for read or write. +// After this call, the blockstore can no longer be used. func (b *ReadWrite) Finalize() error { - if b.header.DataSize != 0 { + b.ronly.mu.Lock() + defer b.ronly.mu.Unlock() + + if b.ronly.closed { // Allow duplicate Finalize calls, just like Close. // Still error, just like ReadOnly.Close; it should be discarded. - return fmt.Errorf("called Finalize twice") + return fmt.Errorf("called Finalize on a closed blockstore") } - b.ronly.mu.Lock() - defer b.ronly.mu.Unlock() // TODO check if add index option is set and don't write the index then set index offset to zero. b.header = b.header.WithDataSize(uint64(b.dataWriter.Position())) // Note that we can't use b.Close here, as that tries to grab the same // mutex we're holding here. - // TODO: should we check the error here? especially with OpenReadWrite, - // we should care about close errors. defer b.ronly.closeWithoutMutex() // TODO if index not needed don't bother flattening it. @@ -360,39 +366,29 @@ func (b *ReadWrite) Finalize() error { if err := index.WriteTo(fi, internalio.NewOffsetWriter(b.f, int64(b.header.IndexOffset))); err != nil { return err } - _, err = b.header.WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)) - return err -} + if _, err := b.header.WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)); err != nil { + return err + } -func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - if b.finalized() { - return nil, errFinalized + if err := b.ronly.closeWithoutMutex(); err != nil { + return err } + return nil +} +func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { return b.ronly.AllKeysChan(ctx) } func (b *ReadWrite) Has(key cid.Cid) (bool, error) { - if b.finalized() { - return false, errFinalized - } - return b.ronly.Has(key) } func (b *ReadWrite) Get(key cid.Cid) (blocks.Block, error) { - if b.finalized() { - return nil, errFinalized - } - return b.ronly.Get(key) } func (b *ReadWrite) GetSize(key cid.Cid) (int, error) { - if b.finalized() { - return 0, errFinalized - } - return b.ronly.GetSize(key) } diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 5316f5bf7e..a30bbb235d 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -373,7 +373,7 @@ func TestBlockstoreResumption(t *testing.T) { // Close off the open file and re-instantiate a new subject with resumption enabled. // Note, we don't have to close the file for resumption to work. // We do this to avoid resource leak during testing. - require.NoError(t, blockstore.CloseReadWrite(subject)) + subject.Discard() } subject, err = blockstore.OpenReadWrite(path, r.Header.Roots, blockstore.UseWholeCIDs(true)) @@ -405,7 +405,7 @@ func TestBlockstoreResumption(t *testing.T) { require.Equal(t, wantBlockCountSoFar, gotBlockCountSoFar) } } - require.NoError(t, blockstore.CloseReadWrite(subject)) + subject.Discard() // Finalize the blockstore to complete partially written CARv2 file. subject, err = blockstore.OpenReadWrite(path, r.Header.Roots, @@ -619,3 +619,51 @@ func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing. "Expected padding value of 1413 but got 1314") require.Nil(t, resumingSubject) } + +func TestReadWriteErrorAfterClose(t *testing.T) { + root := blocks.NewBlock([]byte("foo")) + for _, closeMethod := range []func(*blockstore.ReadWrite){ + (*blockstore.ReadWrite).Discard, + func(bs *blockstore.ReadWrite) { bs.Finalize() }, + } { + path := filepath.Join(t.TempDir(), "readwrite.car") + bs, err := blockstore.OpenReadWrite(path, []cid.Cid{root.Cid()}) + require.NoError(t, err) + + err = bs.Put(root) + require.NoError(t, err) + + roots, err := bs.Roots() + require.NoError(t, err) + _, err = bs.Has(roots[0]) + require.NoError(t, err) + _, err = bs.Get(roots[0]) + require.NoError(t, err) + _, err = bs.GetSize(roots[0]) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + _, err = bs.AllKeysChan(ctx) + require.NoError(t, err) + cancel() // to stop the AllKeysChan goroutine + + closeMethod(bs) + + _, err = bs.Roots() + require.Error(t, err) + _, err = bs.Has(roots[0]) + require.Error(t, err) + _, err = bs.Get(roots[0]) + require.Error(t, err) + _, err = bs.GetSize(roots[0]) + require.Error(t, err) + _, err = bs.AllKeysChan(ctx) + require.Error(t, err) + + err = bs.Put(root) + require.Error(t, err) + + // TODO: test that closing blocks if an AllKeysChan operation is + // in progress. + } +} From 240282a4a651c6c331225bf582cd38f4abd287c2 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 10 Aug 2021 16:12:12 +0100 Subject: [PATCH 150/291] Implement utility to extract CARv1 from a CARv2 Implement `ExtractV1File` where the function takes path to a CARv2 file and efficiently extracts its inner CARv1 payload. Note, the implementation only supports CARv2 as input and returns a dedicated error if the supplied input is already in CARv1 format. Implement benchmarks comparing extraction using `Reader` vs `ExtractV1File`. Implement tests that assert in-place extraction as well as invalid input and both v1/v2 input Fixes #207 This commit was moved from ipld/go-car@81137942c924463c87a8cd310283ac651da5d7c7 --- ipld/car/v2/bench_test.go | 98 +++++++++++++++++++++++++++++++++ ipld/car/v2/writer.go | 108 +++++++++++++++++++++++++++++++++++++ ipld/car/v2/writer_test.go | 43 +++++++++++++++ 3 files changed, 249 insertions(+) diff --git a/ipld/car/v2/bench_test.go b/ipld/car/v2/bench_test.go index 83adc346a9..16ae933783 100644 --- a/ipld/car/v2/bench_test.go +++ b/ipld/car/v2/bench_test.go @@ -2,12 +2,20 @@ package car_test import ( "io" + "math/rand" "os" + "path/filepath" "testing" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-merkledag" + "github.com/ipld/go-car/v2/blockstore" + carv2 "github.com/ipld/go-car/v2" ) +var rng = rand.New(rand.NewSource(1413)) + // BenchmarkReadBlocks instantiates a BlockReader, and iterates over all blocks. // It essentially looks at the contents of any CARv1 or CARv2 file. // Note that this also uses internal carv1.ReadHeader underneath. @@ -47,3 +55,93 @@ func BenchmarkReadBlocks(b *testing.B) { } }) } + +// BenchmarkExtractV1File extracts inner CARv1 payload from a sample CARv2 file using ExtractV1File. +func BenchmarkExtractV1File(b *testing.B) { + path := filepath.Join(b.TempDir(), "bench-large-v2.car") + generateRandomCarV2File(b, path, 10*1024*1024) // 10 MiB + defer os.Remove(path) + + info, err := os.Stat(path) + if err != nil { + b.Fatal(err) + } + b.SetBytes(info.Size()) + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + dstPath := filepath.Join(b.TempDir(), "destination.car") + for pb.Next() { + err = carv2.ExtractV1File(path, dstPath) + if err != nil { + b.Fatal(err) + } + _ = os.Remove(dstPath) + } + }) +} + +// BenchmarkExtractV1UsingReader extracts inner CARv1 payload from a sample CARv2 file using Reader +// API. This benchmark is implemented to be used as a comparison in conjunction with +// BenchmarkExtractV1File. +func BenchmarkExtractV1UsingReader(b *testing.B) { + path := filepath.Join(b.TempDir(), "bench-large-v2.car") + generateRandomCarV2File(b, path, 10*1024*1024) // 10 MiB + defer os.Remove(path) + + info, err := os.Stat(path) + if err != nil { + b.Fatal(err) + } + b.SetBytes(info.Size()) + b.ReportAllocs() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + dstPath := filepath.Join(b.TempDir(), "destination.car") + for pb.Next() { + dst, err := os.Create(dstPath) + if err != nil { + b.Fatal(err) + } + reader, err := carv2.OpenReader(path) + if err != nil { + b.Fatal(err) + } + _, err = io.Copy(dst, reader.DataReader()) + if err != nil { + b.Fatal(err) + } + if err := dst.Close(); err != nil { + b.Fatal(err) + } + } + }) +} + +func generateRandomCarV2File(b *testing.B, path string, minTotalBlockSize int) { + bs, err := blockstore.OpenReadWrite(path, []cid.Cid{}) + defer func() { + if err := bs.Finalize(); err != nil { + b.Fatal(err) + } + }() + if err != nil { + b.Fatal(err) + } + buf := make([]byte, 1024) + var totalBlockSize int + for totalBlockSize < minTotalBlockSize { + size, err := rng.Read(buf) + if err != nil { + b.Fatal(err) + } + + blk := merkledag.NewRawNode(buf) + if err := bs.Put(blk); err != nil { + b.Fatal(err) + } + totalBlockSize += size + } +} diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 40004648e0..91b5340163 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -1,6 +1,8 @@ package car import ( + "errors" + "fmt" "io" "os" @@ -9,6 +11,9 @@ import ( "github.com/ipld/go-car/v2/index" ) +// ErrAlreadyV1 signals that the given payload is already in CARv1 format. +var ErrAlreadyV1 = errors.New("already a CARv1") + // WrapV1File is a wrapper around WrapV1 that takes filesystem paths. // The source path is assumed to exist, and the destination path is overwritten. // Note that the destination path might still be created even if an error @@ -79,6 +84,109 @@ func WrapV1(src io.ReadSeeker, dst io.Writer) error { return nil } +// ExtractV1File takes a CARv2 file and extracts its CARv1 data payload, unmodified. +// The resulting CARv1 file will not include any data payload padding that may be present in the +// CARv2 srcPath. +// If srcPath represents a CARv1 ErrAlreadyV1 error is returned. +// The srcPath is assumed to exist, and the destination path is created if not exist. +// Note that the destination path might still be created even if an error +// occurred. +// If srcPath and dstPath are the same, then the dstPath is converted, in-place, to CARv1. +func ExtractV1File(srcPath, dstPath string) (err error) { + src, err := os.Open(srcPath) + if err != nil { + return err + } + + // Ignore close error since only reading from src. + defer src.Close() + + // Detect CAR version. + version, err := ReadVersion(src) + if err != nil { + return err + } + if version == 1 { + return ErrAlreadyV1 + } + if version != 2 { + return fmt.Errorf("invalid source version: %v", version) + } + + // Read CARv2 header to locate data payload. + var v2h Header + if _, err := v2h.ReadFrom(src); err != nil { + return err + } + + // TODO consider extracting this into Header.Validate since it is also implemented in BlockReader. + // Validate header + dataOffset := int64(v2h.DataOffset) + if dataOffset < PragmaSize+HeaderSize { + return fmt.Errorf("invalid data payload offset: %v", dataOffset) + } + dataSize := int64(v2h.DataSize) + if dataSize <= 0 { + return fmt.Errorf("invalid data payload size: %v", dataSize) + } + + // Seek to the point where the data payload starts + if _, err := src.Seek(dataOffset, io.SeekStart); err != nil { + return err + } + + // Open destination as late as possible to minimise unintended file creation in case an error + // occurs earlier. + // Note, we explicitly do not use os.O_TRUNC here so that we can support in-place extraction. + // Otherwise, truncation of an existing file will wipe the data we would be reading from if + // source and destination paths are the same. + // Later, we do truncate the file to the right size to assert there are no tailing extra bytes. + dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_WRONLY, 0o666) + if err != nil { + return err + } + + defer func() { + // Close destination and override return error type if it is nil. + cerr := dst.Close() + if err == nil { + err = cerr + } + }() + + // Copy data payload over, expecting to write exactly the right number of bytes. + // Note that we explicitly use io.CopyN using file descriptors to leverage the SDK's efficient + // byte copy which should stay out of userland. + // There are two benchmarks to measure this: BenchmarkExtractV1File vs. BenchmarkExtractV1UsingReader + written, err := io.CopyN(dst, src, dataSize) + if err != nil { + return err + } + if written != dataSize { + return fmt.Errorf("expected to write exactly %v but wrote %v", dataSize, written) + } + + // Check that the size destination file matches expected size. + // If bigger truncate. + // Note, we need to truncate: + // - if file is changed in-place, i.e. src and dst paths are the same then index or padding + // could be present after the data payload. + // - if an existing file is passed as destination which is different from source and is larger + // than the data payload size. + // In general, we want to guarantee that this function produces correct CARv2 payload in + // destination. + stat, err := dst.Stat() + if err != nil { + return err + } + if stat.Size() > dataSize { + // Truncate to the expected size to assure the resulting file is a correctly sized CARv1. + err = dst.Truncate(written) + } + + return err +} + // AttachIndex attaches a given index to an existing CARv2 file at given path and offset. func AttachIndex(path string, idx index.Index, offset uint64) error { // TODO: instead of offset, maybe take padding? diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 3cf119cee0..c35beb4a42 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -59,6 +59,49 @@ func TestWrapV1(t *testing.T) { require.Equal(t, wantIdx, gotIdx) } +func TestExtractV1(t *testing.T) { + // Produce a CARv1 file to test. + dagSvc := dstest.Mock() + v1Src := filepath.Join(t.TempDir(), "original-test-v1.car") + v1f, err := os.Create(v1Src) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, v1f.Close()) }) + require.NoError(t, carv1.WriteCar(context.Background(), dagSvc, generateRootCid(t, dagSvc), v1f)) + _, err = v1f.Seek(0, io.SeekStart) + require.NoError(t, err) + wantV1, err := ioutil.ReadAll(v1f) + require.NoError(t, err) + + // Wrap the produced CARv1 into a CARv2 to use for testing. + v2path := filepath.Join(t.TempDir(), "wrapped-for-extract-test-v2.car") + require.NoError(t, WrapV1File(v1Src, v2path)) + + // Assert extract from CARv2 file is as expected. + dstPath := filepath.Join(t.TempDir(), "extract-file-test-v1.car") + require.NoError(t, ExtractV1File(v2path, dstPath)) + gotFromFile, err := ioutil.ReadFile(dstPath) + require.NoError(t, err) + require.Equal(t, wantV1, gotFromFile) + + // Assert extract from CARv2 file in-place is as expected + require.NoError(t, ExtractV1File(v2path, v2path)) + gotFromInPlaceFile, err := ioutil.ReadFile(v2path) + require.NoError(t, err) + require.Equal(t, wantV1, gotFromInPlaceFile) +} + +func TestExtractV1WithUnknownVersionIsError(t *testing.T) { + dstPath := filepath.Join(t.TempDir(), "extract-dst-file-test-v42.car") + err := ExtractV1File("testdata/sample-rootless-v42.car", dstPath) + require.EqualError(t, err, "invalid source version: 42") +} + +func TestExtractV1FromACarV1IsError(t *testing.T) { + dstPath := filepath.Join(t.TempDir(), "extract-dst-file-test-v1.car") + err := ExtractV1File("testdata/sample-v1.car", dstPath) + require.Equal(t, ErrAlreadyV1, err) +} + func generateRootCid(t *testing.T, adder format.NodeAdder) []cid.Cid { // TODO convert this into a utility testing lib that takes an rng and generates a random DAG with some threshold for depth/breadth. this := merkledag.NewRawNode([]byte("fish")) From 7fd2bd25c643303dd20d33308ef5e1f4390eb81d Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 11 Aug 2021 12:36:42 +0100 Subject: [PATCH 151/291] Document performance caveats of `ExtractV1File` and address comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since `CopyFileRange` performance is OS-dependant, we cannot guarantee that `ExtractV1File` will keep copies out of user-space. For example, on Linux with sufficiently old Kernel the current behaviour will fall back to user-space copy. Document this on the function so that it is made clear. Improve benchmarking determinism and error messages. The benchmark numbers that Daniel obtained on his laptop, running Linux 5.13 on an i5-8350U with /tmp being tmpfs, are as follows. The "old" results are BenchmarkExtractV1UsingReader, and the "new" are BenchmarkExtractV1File. name old time/op new time/op delta ExtractV1-8 1.33ms ± 1% 1.11ms ± 2% -16.48% (p=0.000 n=8+8) name old speed new speed delta ExtractV1-8 7.88GB/s ± 1% 9.43GB/s ± 2% +19.74% (p=0.000 n=8+8) name old alloc/op new alloc/op delta ExtractV1-8 34.0kB ± 0% 1.0kB ± 0% -97.09% (p=0.000 n=8+8) name old allocs/op new allocs/op delta ExtractV1-8 26.0 ± 0% 23.0 ± 0% -11.54% (p=0.000 n=8+8) So, at least in the case where the filesystem is very fast, we can see that the benefit is around 10-20%, as well as fewer allocs thanks to not needing a user-space buffer. The performance benefit will likely be smaller on slower disks. For the Linux syscall logic, see: - https://cs.opensource.google/go/go/+/refs/tags/go1.16.7:src/internal/poll/copy_file_range_linux.go;drc=refs%2Ftags%2Fgo1.16.7;l=54 This commit was moved from ipld/go-car@c514a30114d7725035293f7718de4b4e1cff9fdf --- ipld/car/v2/bench_test.go | 10 +++++----- ipld/car/v2/writer.go | 14 ++++++++++---- ipld/car/v2/writer_test.go | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/ipld/car/v2/bench_test.go b/ipld/car/v2/bench_test.go index 16ae933783..15ae0c24fa 100644 --- a/ipld/car/v2/bench_test.go +++ b/ipld/car/v2/bench_test.go @@ -14,8 +14,6 @@ import ( carv2 "github.com/ipld/go-car/v2" ) -var rng = rand.New(rand.NewSource(1413)) - // BenchmarkReadBlocks instantiates a BlockReader, and iterates over all blocks. // It essentially looks at the contents of any CARv1 or CARv2 file. // Note that this also uses internal carv1.ReadHeader underneath. @@ -59,7 +57,7 @@ func BenchmarkReadBlocks(b *testing.B) { // BenchmarkExtractV1File extracts inner CARv1 payload from a sample CARv2 file using ExtractV1File. func BenchmarkExtractV1File(b *testing.B) { path := filepath.Join(b.TempDir(), "bench-large-v2.car") - generateRandomCarV2File(b, path, 10*1024*1024) // 10 MiB + generateRandomCarV2File(b, path, 10<<20) // 10 MiB defer os.Remove(path) info, err := os.Stat(path) @@ -87,7 +85,7 @@ func BenchmarkExtractV1File(b *testing.B) { // BenchmarkExtractV1File. func BenchmarkExtractV1UsingReader(b *testing.B) { path := filepath.Join(b.TempDir(), "bench-large-v2.car") - generateRandomCarV2File(b, path, 10*1024*1024) // 10 MiB + generateRandomCarV2File(b, path, 10<<20) // 10 MiB defer os.Remove(path) info, err := os.Stat(path) @@ -121,6 +119,8 @@ func BenchmarkExtractV1UsingReader(b *testing.B) { } func generateRandomCarV2File(b *testing.B, path string, minTotalBlockSize int) { + // Use fixed RNG for determinism across benchmarks. + rng := rand.New(rand.NewSource(1413)) bs, err := blockstore.OpenReadWrite(path, []cid.Cid{}) defer func() { if err := bs.Finalize(); err != nil { @@ -130,7 +130,7 @@ func generateRandomCarV2File(b *testing.B, path string, minTotalBlockSize int) { if err != nil { b.Fatal(err) } - buf := make([]byte, 1024) + buf := make([]byte, 32<<10) // 32 KiB var totalBlockSize int for totalBlockSize < minTotalBlockSize { size, err := rng.Read(buf) diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 91b5340163..c344828159 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -92,6 +92,12 @@ func WrapV1(src io.ReadSeeker, dst io.Writer) error { // Note that the destination path might still be created even if an error // occurred. // If srcPath and dstPath are the same, then the dstPath is converted, in-place, to CARv1. +// +// This function aims to extract the CARv1 payload as efficiently as possible. +// The method is best-effort and depends on your operating system; +// for example, it should use copy_file_range on recent Linux versions. +// This API should be preferred over copying directly via Reader.DataReader, +// as it should allow for better performance while always being at least as efficient. func ExtractV1File(srcPath, dstPath string) (err error) { src, err := os.Open(srcPath) if err != nil { @@ -110,7 +116,7 @@ func ExtractV1File(srcPath, dstPath string) (err error) { return ErrAlreadyV1 } if version != 2 { - return fmt.Errorf("invalid source version: %v", version) + return fmt.Errorf("source version must be 2; got: %d", version) } // Read CARv2 header to locate data payload. @@ -123,11 +129,11 @@ func ExtractV1File(srcPath, dstPath string) (err error) { // Validate header dataOffset := int64(v2h.DataOffset) if dataOffset < PragmaSize+HeaderSize { - return fmt.Errorf("invalid data payload offset: %v", dataOffset) + return fmt.Errorf("invalid data payload offset: %d", dataOffset) } dataSize := int64(v2h.DataSize) if dataSize <= 0 { - return fmt.Errorf("invalid data payload size: %v", dataSize) + return fmt.Errorf("invalid data payload size: %d", dataSize) } // Seek to the point where the data payload starts @@ -163,7 +169,7 @@ func ExtractV1File(srcPath, dstPath string) (err error) { return err } if written != dataSize { - return fmt.Errorf("expected to write exactly %v but wrote %v", dataSize, written) + return fmt.Errorf("expected to write exactly %d but wrote %d", dataSize, written) } // Check that the size destination file matches expected size. diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index c35beb4a42..c29b433973 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -93,7 +93,7 @@ func TestExtractV1(t *testing.T) { func TestExtractV1WithUnknownVersionIsError(t *testing.T) { dstPath := filepath.Join(t.TempDir(), "extract-dst-file-test-v42.car") err := ExtractV1File("testdata/sample-rootless-v42.car", dstPath) - require.EqualError(t, err, "invalid source version: 42") + require.EqualError(t, err, "source version must be 2; got: 42") } func TestExtractV1FromACarV1IsError(t *testing.T) { From 5f33f8c06a2d9866fa83362934a6156f0b87e6bd Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 19 Aug 2021 14:21:50 +0100 Subject: [PATCH 152/291] Assert `OpenReader` from file does not panic after closure Write a test that asserts reader instantiated from `OpenReader` do not panic and instead error gracefully if underlying IO is closed. Relates to #211 This commit was moved from ipld/go-car@ed281f92b484d92584a32fdf9b0b5ccd1e4588cc --- ipld/car/v2/reader_test.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index 79d6f855d2..02906f1a89 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -178,6 +178,40 @@ func TestReader_WithCarV2Consistency(t *testing.T) { } } +func TestOpenReader_DoesNotPanicForReadersCreatedBeforeClosure(t *testing.T) { + subject, err := carv2.OpenReader("testdata/sample-wrapped-v2.car") + require.NoError(t, err) + dReaderBeforeClosure := subject.DataReader() + iReaderBeforeClosure := subject.IndexReader() + require.NoError(t, subject.Close()) + + buf := make([]byte, 1) + panicTest := func(r io.Reader) { + _, err := r.Read(buf) + require.EqualError(t, err, "mmap: closed") + } + + require.NotPanics(t, func() { panicTest(dReaderBeforeClosure) }) + require.NotPanics(t, func() { panicTest(iReaderBeforeClosure) }) +} + +func TestOpenReader_DoesNotPanicForReadersCreatedAfterClosure(t *testing.T) { + subject, err := carv2.OpenReader("testdata/sample-wrapped-v2.car") + require.NoError(t, err) + require.NoError(t, subject.Close()) + dReaderAfterClosure := subject.DataReader() + iReaderAfterClosure := subject.IndexReader() + + buf := make([]byte, 1) + panicTest := func(r io.Reader) { + _, err := r.Read(buf) + require.EqualError(t, err, "mmap: closed") + } + + require.NotPanics(t, func() { panicTest(dReaderAfterClosure) }) + require.NotPanics(t, func() { panicTest(iReaderAfterClosure) }) +} + func requireNewCarV1ReaderFromV2File(t *testing.T, carV12Path string, zerLenAsEOF bool) *carv1.CarReader { f, err := os.Open(carV12Path) require.NoError(t, err) From 13be8273ebe30744bf6c8a5fc908afba7d7c3dbe Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 27 Aug 2021 15:04:24 +0100 Subject: [PATCH 153/291] Return `nil` as Index reader when reading indexless CARv2 Fix an issue where if a `v2.Reader` is given an indexless CARv2, an invalid section reader is returned. Add tests to assert fix using CARv1 and indexless CARv2 sample files. This commit was moved from ipld/go-car@1bac13d05359f7998e694dd55741e6ed3cf8be74 --- ipld/car/v2/reader.go | 6 ++--- ipld/car/v2/reader_test.go | 24 +++++++++++++++++++ ipld/car/v2/testdata/sample-v2-indexless.car | Bin 0 -> 479958 bytes 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 ipld/car/v2/testdata/sample-v2-indexless.car diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index db8f821051..2f7cbb18e4 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -113,10 +113,10 @@ func (r *Reader) DataReader() SectionReader { // present. Otherwise, returns nil. // Note, this function will always return nil if the backing payload represents a CARv1. func (r *Reader) IndexReader() io.Reader { - if r.Version == 2 { - return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) + if r.Version == 1 || !r.Header.HasIndex() { + return nil } - return nil + return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) } // Close closes the underlying reader if it was opened by OpenReader. diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index 02906f1a89..a0c6e3cdaf 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -212,6 +212,30 @@ func TestOpenReader_DoesNotPanicForReadersCreatedAfterClosure(t *testing.T) { require.NotPanics(t, func() { panicTest(iReaderAfterClosure) }) } +func TestReader_ReturnsNilWhenThereIsNoIndex(t *testing.T) { + tests := []struct { + name string + path string + }{ + { + name: "IndexlessCarV2", + path: "testdata/sample-v2-indexless.car", + }, + { + name: "CarV1", + path: "testdata/sample-v1.car", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + subject, err := carv2.OpenReader(tt.path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, subject.Close()) }) + require.Nil(t, subject.IndexReader()) + }) + } +} + func requireNewCarV1ReaderFromV2File(t *testing.T, carV12Path string, zerLenAsEOF bool) *carv1.CarReader { f, err := os.Open(carV12Path) require.NoError(t, err) diff --git a/ipld/car/v2/testdata/sample-v2-indexless.car b/ipld/car/v2/testdata/sample-v2-indexless.car new file mode 100644 index 0000000000000000000000000000000000000000..3b160b6fa75fad21af1c21778aa13c8ae145d2e5 GIT binary patch literal 479958 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
    {1kxno)d5r;&4t;VAqjNzP=e` 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

    hRd> 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

    vHSrHL`4Y!))mrU|+My=J*?NCLvdFH?W^2AuF;5 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

    ecf&5+ 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 z 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í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

    CJyD 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 J2lbx!{{TLP56A!j literal 0 HcmV?d00001 diff --git a/ipld/car/cmd/car/testdata/script/get-block.txt b/ipld/car/cmd/car/testdata/script/get-block.txt new file mode 100644 index 0000000000..1e5fd7c27b --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/get-block.txt @@ -0,0 +1,19 @@ +env SAMPLE_CID='bafy2bzacebohz654namrgmwjjx4qmtwgxixsd7pn4tlanyrc3g3hwj75hlxrw' +env MISSING_CID='bafy2bzacebohz654namrgmwjjx4qmtwgxixsd7pn4tlanyrc3g3hwj75xxxxx' + +# "get-block" on a CARv1 with an output file. +car get-block ${INPUTS}/sample-v1.car ${SAMPLE_CID} out.block +cmp out.block ${INPUTS}/${SAMPLE_CID}.block +rm out.block + +# "get-block" on a CARv1 with stdout. +car get-block ${INPUTS}/sample-v1.car ${SAMPLE_CID} +cmp stdout ${INPUTS}/${SAMPLE_CID}.block + +# Short "gb" alias. +car get-block ${INPUTS}/sample-v1.car ${SAMPLE_CID} +cmp stdout ${INPUTS}/${SAMPLE_CID}.block + +# "get-block" on a missing CID. +! car get-block ${INPUTS}/sample-v1.car ${MISSING_CID} +stderr 'block not found' diff --git a/ipld/car/cmd/car/testdata/script/list.txt b/ipld/car/cmd/car/testdata/script/list.txt new file mode 100644 index 0000000000..29bb9021b7 --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/list.txt @@ -0,0 +1,16 @@ +env SAMPLE_CID='bafy2bzacebohz654namrgmwjjx4qmtwgxixsd7pn4tlanyrc3g3hwj75hlxrw' +# "list" on a CARv1. +car list ${INPUTS}/sample-v1.car +stdout -count=1043 '^bafy' +stdout -count=6 '^bafk' +stdout -count=1 'bafy2bzacebohz654namrgmwjjx4qmtwgxixsd7pn4tlanyrc3g3hwj75hlxrw' + +# "list" on a CARv2. +car list ${INPUTS}/sample-wrapped-v2.car +stdout -count=1043 '^bafy' +stdout -count=6 '^bafk' +stdout -count=1 'bafy2bzacebohz654namrgmwjjx4qmtwgxixsd7pn4tlanyrc3g3hwj75hlxrw' + +# Short "l" alias. +car l ${INPUTS}/sample-v1.car +stdout -count=1043 '^bafy' From 03248629a3f8abfe8ba0e9c2058004b843656e55 Mon Sep 17 00:00:00 2001 From: Will Date: Wed, 15 Sep 2021 11:00:34 -0700 Subject: [PATCH 171/291] Add test script for car verify (#236) * Add test script for car verify * Add test script for filter This commit was moved from ipld/go-car@c8572be41c5bf2f90b78961cc4edcbccc25015a4 --- ipld/car/.gitattributes | 2 ++ ipld/car/cmd/car/car.go | 4 +++ ipld/car/cmd/car/filter.go | 28 ++++++++++++++++++- ipld/car/cmd/car/testdata/script/filter.txt | 30 +++++++++++++++++++++ ipld/car/cmd/car/testdata/script/verify.txt | 3 +++ ipld/car/cmd/car/verify.go | 15 +++++++++-- 6 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 ipld/car/.gitattributes create mode 100644 ipld/car/cmd/car/testdata/script/filter.txt create mode 100644 ipld/car/cmd/car/testdata/script/verify.txt diff --git a/ipld/car/.gitattributes b/ipld/car/.gitattributes new file mode 100644 index 0000000000..6f95229927 --- /dev/null +++ b/ipld/car/.gitattributes @@ -0,0 +1,2 @@ +# To prevent CRLF breakages on Windows for fragile files, like testdata. +* -text diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 12a1f1f1b5..dca4c0f25c 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -31,6 +31,10 @@ func main1() int { Usage: "A file to read CIDs from", TakesFile: true, }, + &cli.BoolFlag{ + Name: "append", + Usage: "Append cids to an existing output file", + }, }, }, { diff --git a/ipld/car/cmd/car/filter.go b/ipld/car/cmd/car/filter.go index a941a8b369..0a0f289377 100644 --- a/ipld/car/cmd/car/filter.go +++ b/ipld/car/cmd/car/filter.go @@ -50,10 +50,36 @@ func FilterCar(c *cli.Context) error { outRoots = append(outRoots, r) } } + + outPath := c.Args().Get(1) + if !c.Bool("append") { + if _, err := os.Stat(outPath); err == nil || !os.IsNotExist(err) { + // output to an existing file. + if err := os.Truncate(outPath, 0); err != nil { + return err + } + } + } else { + // roots will need to be whatever is in the output already. + cv2r, err := carv2.OpenReader(outPath) + if err != nil { + return err + } + if cv2r.Version != 2 { + return fmt.Errorf("can only append to version 2 car files") + } + outRoots, err = cv2r.Roots() + if err != nil { + return err + } + _ = cv2r.Close() + } + if len(outRoots) == 0 { fmt.Fprintf(os.Stderr, "warning: no roots defined after filtering\n") } - bs, err := blockstore.OpenReadWrite(c.Args().Get(1), outRoots) + + bs, err := blockstore.OpenReadWrite(outPath, outRoots) if err != nil { return err } diff --git a/ipld/car/cmd/car/testdata/script/filter.txt b/ipld/car/cmd/car/testdata/script/filter.txt new file mode 100644 index 0000000000..0c0b12de79 --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/filter.txt @@ -0,0 +1,30 @@ +# basic filter +stdin filteredcids.txt +car filter ${INPUTS}/sample-wrapped-v2.car out.car +stderr 'warning: no roots defined after filtering' +car list out.car +! stderr . +cmp stdout filteredcids.txt + +# filter with root CID +stdin filteredroot.txt +car filter ${INPUTS}/sample-wrapped-v2.car out.car +! stderr . +car list out.car +! stderr . +cmp stdout filteredroot.txt + +# append other cids +stdin filteredcids.txt +car filter -append ${INPUTS}/sample-wrapped-v2.car out.car +! stderr . +car list out.car +stdout -count=4 '^bafy' + + +-- filteredcids.txt -- +bafy2bzacebohz654namrgmwjjx4qmtwgxixsd7pn4tlanyrc3g3hwj75hlxrw +bafy2bzaceaqtiesyfqd2jibmofz22oolguzf5wscwh73rmeypglfu2xhkptri +bafy2bzacebct3dm7izgyauijzkaf3yd7ylni725k66rq7dfp3jr5ywhpprj3k +-- filteredroot.txt -- +bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy diff --git a/ipld/car/cmd/car/testdata/script/verify.txt b/ipld/car/cmd/car/testdata/script/verify.txt new file mode 100644 index 0000000000..3ef3c49e4c --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/verify.txt @@ -0,0 +1,3 @@ +# "verify" should exit with code 0 on reasonable cars. +car verify ${INPUTS}/sample-v1.car +car verify ${INPUTS}/sample-wrapped-v2.car diff --git a/ipld/car/cmd/car/verify.go b/ipld/car/cmd/car/verify.go index c9c753d783..3a43de938e 100644 --- a/ipld/car/cmd/car/verify.go +++ b/ipld/car/cmd/car/verify.go @@ -8,6 +8,7 @@ import ( "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" + "github.com/multiformats/go-multihash" "github.com/urfave/cli/v2" ) @@ -44,12 +45,15 @@ func VerifyCar(c *cli.Context) error { if err != nil { return err } - lengthToIndex := carv2.PragmaSize + carv2.HeaderSize + rx.Header.DataOffset + rx.Header.DataSize + lengthToIndex := carv2.PragmaSize + carv2.HeaderSize + rx.Header.DataSize if uint64(flen.Size()) > lengthToIndex && rx.Header.IndexOffset == 0 { return fmt.Errorf("header claims no index, but extra bytes in file beyond data size") } + if rx.Header.DataOffset < carv2.PragmaSize+carv2.HeaderSize { + return fmt.Errorf("data offset places data within carv2 header") + } if rx.Header.IndexOffset < lengthToIndex { - return fmt.Errorf("index offset overlaps with data") + return fmt.Errorf("index offset overlaps with data. data ends at %d. index offset of %d", lengthToIndex, rx.Header.IndexOffset) } } @@ -87,6 +91,13 @@ func VerifyCar(c *cli.Context) error { return err } for _, c := range cidList { + cidHash, err := multihash.Decode(c.Hash()) + if err != nil { + return err + } + if cidHash.Code == multihash.IDENTITY { + continue + } if err := idx.GetAll(c, func(_ uint64) bool { return true }); err != nil { From 24b126977b926e0241fce8643e60086398cacaf5 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 15 Sep 2021 21:08:52 +0100 Subject: [PATCH 172/291] Combine API options for simplicity and logical coherence Introduce a simple `Option` type that includes both read and write options, as well as read-write options. The rationale for this change is to avoid re-declaring options that apply to both read and write, and reduce code verbosity when converting from one type to another as part of API call chains. Deprecate `ReadOption`, `WriteOption` and `ReadWriteOption` in favour of the simpler `Option`, and update APIs to use the new type. Remove now redundant internal utility functions for working with option types. Reformat code using `gofumt` utility for consistency. This commit was moved from ipld/go-car@23ca7db579f3d625be7957db11282ef5dbe64437 --- ipld/car/v2/block_reader.go | 12 +-- ipld/car/v2/blockstore/readonly.go | 30 ++++---- ipld/car/v2/blockstore/readonly_test.go | 17 ++--- ipld/car/v2/blockstore/readwrite.go | 50 +++++-------- ipld/car/v2/index/mhindexsorted.go | 6 +- ipld/car/v2/index_gen.go | 10 +-- ipld/car/v2/index_gen_test.go | 22 +++--- ipld/car/v2/internal/options/options.go | 19 ----- ipld/car/v2/options.go | 98 ++++++++++--------------- ipld/car/v2/reader.go | 11 ++- ipld/car/v2/writer.go | 18 +---- 11 files changed, 110 insertions(+), 183 deletions(-) delete mode 100644 ipld/car/v2/internal/options/options.go diff --git a/ipld/car/v2/block_reader.go b/ipld/car/v2/block_reader.go index 8cd23e72c2..456d8d3177 100644 --- a/ipld/car/v2/block_reader.go +++ b/ipld/car/v2/block_reader.go @@ -20,8 +20,8 @@ type BlockReader struct { Roots []cid.Cid // Used internally only, by BlockReader.Next during iteration over blocks. - r io.Reader - ropts ReadOptions + r io.Reader + opts Options } // NewBlockReader instantiates a new BlockReader facilitating iteration over blocks in CARv1 or @@ -29,7 +29,7 @@ type BlockReader struct { // BlockReader.Version. The root CIDs of the CAR payload are exposed via BlockReader.Roots // // See BlockReader.Next -func NewBlockReader(r io.Reader, opts ...ReadOption) (*BlockReader, error) { +func NewBlockReader(r io.Reader, opts ...Option) (*BlockReader, error) { // Read CARv1 header or CARv2 pragma. // Both are a valid CARv1 header, therefore are read as such. pragmaOrV1Header, err := carv1.ReadHeader(r) @@ -40,7 +40,7 @@ func NewBlockReader(r io.Reader, opts ...ReadOption) (*BlockReader, error) { // Populate the block reader version and options. br := &BlockReader{ Version: pragmaOrV1Header.Version, - ropts: ApplyReadOptions(opts...), + opts: ApplyOptions(opts...), } // Expect either version 1 or 2. @@ -116,11 +116,11 @@ func NewBlockReader(r io.Reader, opts ...ReadOption) (*BlockReader, error) { // reaches the end of the underlying io.Reader stream. // // As for CARv2 payload, the underlying io.Reader is read only up to the end of the last block. -// Note, in a case where ReadOption.ZeroLengthSectionAsEOF is enabled, io.EOF is returned +// Note, in a case where ZeroLengthSectionAsEOF Option is enabled, io.EOF is returned // immediately upon encountering a zero-length section without reading any further bytes from the // underlying io.Reader. func (br *BlockReader) Next() (blocks.Block, error) { - c, data, err := util.ReadNode(br.r, br.ropts.ZeroLengthSectionAsEOF) + c, data, err := util.ReadNode(br.r, br.opts.ZeroLengthSectionAsEOF) if err != nil { return nil, err } diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 4e7e82027f..690e233b7d 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -8,8 +8,6 @@ import ( "io" "sync" - "golang.org/x/exp/mmap" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" @@ -20,6 +18,7 @@ import ( internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multihash" "github.com/multiformats/go-varint" + "golang.org/x/exp/mmap" ) var _ blockstore.Blockstore = (*ReadOnly)(nil) @@ -53,7 +52,7 @@ type ReadOnly struct { // If we called carv2.NewReaderMmap, remember to close it too. carv2Closer io.Closer - ropts carv2.ReadOptions + opts carv2.Options } type contextKey string @@ -78,8 +77,8 @@ const asyncErrHandlerKey contextKey = "asyncErrorHandlerKey" // // Note that this option only affects the blockstore, and is ignored by the root // go-car/v2 package. -func UseWholeCIDs(enable bool) carv2.ReadOption { - return func(o *carv2.ReadOptions) { +func UseWholeCIDs(enable bool) carv2.Option { + return func(o *carv2.Options) { o.BlockstoreUseWholeCIDs = enable } } @@ -93,10 +92,9 @@ func UseWholeCIDs(enable bool) carv2.ReadOption { // * For a CARv2 backing an index is only generated if Header.HasIndex returns false. // // There is no need to call ReadOnly.Close on instances returned by this function. -func NewReadOnly(backing io.ReaderAt, idx index.Index, opts ...carv2.ReadOption) (*ReadOnly, error) { - ropts := carv2.ApplyReadOptions(opts...) +func NewReadOnly(backing io.ReaderAt, idx index.Index, opts ...carv2.Option) (*ReadOnly, error) { b := &ReadOnly{ - ropts: ropts, + opts: carv2.ApplyOptions(opts...), } version, err := readVersion(backing) @@ -147,7 +145,7 @@ func readVersion(at io.ReaderAt) (uint64, error) { return carv2.ReadVersion(rr) } -func generateIndex(at io.ReaderAt, opts ...carv2.ReadOption) (index.Index, error) { +func generateIndex(at io.ReaderAt, opts ...carv2.Option) (index.Index, error) { var rs io.ReadSeeker switch r := at.(type) { case io.ReadSeeker: @@ -163,7 +161,7 @@ func generateIndex(at io.ReaderAt, opts ...carv2.ReadOption) (index.Index, error // OpenReadOnly opens a read-only blockstore from a CAR file (either v1 or v2), generating an index if it does not exist. // Note, the generated index if the index does not exist is ephemeral and only stored in memory. // See car.GenerateIndex and Index.Attach for persisting index onto a CAR file. -func OpenReadOnly(path string, opts ...carv2.ReadOption) (*ReadOnly, error) { +func OpenReadOnly(path string, opts ...carv2.Option) (*ReadOnly, error) { f, err := mmap.Open(path) if err != nil { return nil, err @@ -179,7 +177,7 @@ func OpenReadOnly(path string, opts ...carv2.ReadOption) (*ReadOnly, error) { } func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { - bcid, data, err := util.ReadNode(internalio.NewOffsetReadSeeker(b.backing, idx), b.ropts.ZeroLengthSectionAsEOF) + bcid, data, err := util.ReadNode(internalio.NewOffsetReadSeeker(b.backing, idx), b.opts.ZeroLengthSectionAsEOF) return bcid, data, err } @@ -221,7 +219,7 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { fnErr = err return false } - if b.ropts.BlockstoreUseWholeCIDs { + if b.opts.BlockstoreUseWholeCIDs { fnFound = readCid.Equals(key) return !fnFound // continue looking if we haven't found it } else { @@ -263,7 +261,7 @@ func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { fnErr = err return false } - if b.ropts.BlockstoreUseWholeCIDs { + if b.opts.BlockstoreUseWholeCIDs { if readCid.Equals(key) { fnData = data return false @@ -321,7 +319,7 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { fnErr = err return false } - if b.ropts.BlockstoreUseWholeCIDs { + if b.opts.BlockstoreUseWholeCIDs { if readCid.Equals(key) { fnSize = int(sectionLen) - cidLen return false @@ -430,7 +428,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { // Null padding; by default it's an error. if length == 0 { - if b.ropts.ZeroLengthSectionAsEOF { + if b.opts.ZeroLengthSectionAsEOF { break } else { maybeReportError(ctx, errZeroLengthSection) @@ -450,7 +448,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { } // If we're just using multihashes, flatten to the "raw" codec. - if !b.ropts.BlockstoreUseWholeCIDs { + if !b.opts.BlockstoreUseWholeCIDs { c = cid.NewCidV1(cid.Raw, c.Hash()) } diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 7b87aee2a9..129e6b0da7 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -7,14 +7,11 @@ import ( "testing" "time" - blockstore "github.com/ipfs/go-ipfs-blockstore" - "github.com/ipfs/go-merkledag" - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" + blockstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipfs/go-merkledag" carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/internal/carv1" "github.com/stretchr/testify/require" ) @@ -34,31 +31,31 @@ func TestReadOnly(t *testing.T) { tests := []struct { name string v1OrV2path string - opts []carv2.ReadOption + opts []carv2.Option v1r *carv1.CarReader }{ { "OpenedWithCarV1", "../testdata/sample-v1.car", - []carv2.ReadOption{UseWholeCIDs(true)}, + []carv2.Option{UseWholeCIDs(true)}, newV1ReaderFromV1File(t, "../testdata/sample-v1.car", false), }, { "OpenedWithCarV2", "../testdata/sample-wrapped-v2.car", - []carv2.ReadOption{UseWholeCIDs(true)}, + []carv2.Option{UseWholeCIDs(true)}, newV1ReaderFromV2File(t, "../testdata/sample-wrapped-v2.car", false), }, { "OpenedWithCarV1ZeroLenSection", "../testdata/sample-v1-with-zero-len-section.car", - []carv2.ReadOption{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + []carv2.Option{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, newV1ReaderFromV1File(t, "../testdata/sample-v1-with-zero-len-section.car", true), }, { "OpenedWithAnotherCarV1ZeroLenSection", "../testdata/sample-v1-with-zero-len-section2.car", - []carv2.ReadOption{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + []carv2.Option{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, newV1ReaderFromV1File(t, "../testdata/sample-v1-with-zero-len-section2.car", true), }, } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 7b14b55f97..4b31190872 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -7,18 +7,15 @@ import ( "io" "os" - "github.com/ipld/go-car/v2/internal/carv1" - "github.com/multiformats/go-varint" - + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" carv2 "github.com/ipld/go-car/v2" - internalio "github.com/ipld/go-car/v2/internal/io" - "github.com/ipld/go-car/v2/index" - - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-varint" ) var _ blockstore.Blockstore = (*ReadWrite)(nil) @@ -40,7 +37,7 @@ type ReadWrite struct { idx *insertionIndex header carv2.Header - wopts carv2.WriteOptions + opts carv2.Options } // AllowDuplicatePuts is a write option which makes a CAR blockstore not @@ -49,8 +46,8 @@ type ReadWrite struct { // // Note that this option only affects the blockstore, and is ignored by the root // go-car/v2 package. -func AllowDuplicatePuts(allow bool) carv2.WriteOption { - return func(o *carv2.WriteOptions) { +func AllowDuplicatePuts(allow bool) carv2.Option { + return func(o *carv2.Options) { o.BlockstoreAllowDuplicatePuts = allow } } @@ -89,7 +86,7 @@ func AllowDuplicatePuts(allow bool) carv2.WriteOption { // // Resuming from finalized files is allowed. However, resumption will regenerate the index // regardless by scanning every existing block in file. -func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.ReadWriteOption) (*ReadWrite, error) { +func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.Option) (*ReadWrite, error) { f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o666) // TODO: Should the user be able to configure FileMode permissions? if err != nil { return nil, fmt.Errorf("could not open read/write file: %w", err) @@ -114,25 +111,14 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.ReadWriteOption) f: f, idx: newInsertionIndex(), header: carv2.NewHeader(0), + opts: carv2.ApplyOptions(opts...), } + rwbs.ronly.opts = rwbs.opts - var ro []carv2.ReadOption - var wo []carv2.WriteOption - for _, opt := range opts { - switch opt := opt.(type) { - case carv2.ReadOption: - ro = append(ro, opt) - case carv2.WriteOption: - wo = append(wo, opt) - } - } - rwbs.ronly.ropts = carv2.ApplyReadOptions(ro...) - rwbs.wopts = carv2.ApplyWriteOptions(wo...) - - if p := rwbs.wopts.DataPadding; p > 0 { + if p := rwbs.opts.DataPadding; p > 0 { rwbs.header = rwbs.header.WithDataPadding(p) } - if p := rwbs.wopts.IndexPadding; p > 0 { + if p := rwbs.opts.IndexPadding; p > 0 { rwbs.header = rwbs.header.WithIndexPadding(p) } @@ -260,7 +246,7 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { // Null padding; by default it's an error. if length == 0 { - if b.ronly.ropts.ZeroLengthSectionAsEOF { + if b.ronly.opts.ZeroLengthSectionAsEOF { break } else { return fmt.Errorf("carv1 null padding not allowed by default; see WithZeroLegthSectionAsEOF") @@ -316,11 +302,11 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { continue } - if !b.wopts.BlockstoreAllowDuplicatePuts { - if b.ronly.ropts.BlockstoreUseWholeCIDs && b.idx.hasExactCID(c) { + if !b.opts.BlockstoreAllowDuplicatePuts { + if b.ronly.opts.BlockstoreUseWholeCIDs && b.idx.hasExactCID(c) { continue // deduplicated by CID } - if !b.ronly.ropts.BlockstoreUseWholeCIDs { + if !b.ronly.opts.BlockstoreUseWholeCIDs { _, err := b.idx.Get(c) if err == nil { continue // deduplicated by hash @@ -371,7 +357,7 @@ func (b *ReadWrite) Finalize() error { defer b.ronly.closeWithoutMutex() // TODO if index not needed don't bother flattening it. - fi, err := b.idx.flatten(b.wopts.IndexCodec) + fi, err := b.idx.flatten(b.opts.IndexCodec) if err != nil { return err } diff --git a/ipld/car/v2/index/mhindexsorted.go b/ipld/car/v2/index/mhindexsorted.go index 15a731c80f..6ae2c5687d 100644 --- a/ipld/car/v2/index/mhindexsorted.go +++ b/ipld/car/v2/index/mhindexsorted.go @@ -10,8 +10,10 @@ import ( "github.com/multiformats/go-multihash" ) -var _ Index = (*MultihashIndexSorted)(nil) -var _ IterableIndex = (*MultihashIndexSorted)(nil) +var ( + _ Index = (*MultihashIndexSorted)(nil) + _ IterableIndex = (*MultihashIndexSorted)(nil) +) type ( // MultihashIndexSorted maps multihash code (i.e. hashing algorithm) to multiWidthCodedIndex. diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 91ebb58911..4602add561 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -16,7 +16,7 @@ import ( // The generated index will be in multicodec.CarMultihashIndexSorted, the default index codec. // The index can be stored in serialized format using index.WriteTo. // See LoadIndex. -func GenerateIndex(v1r io.Reader, opts ...ReadOption) (index.Index, error) { +func GenerateIndex(v1r io.Reader, opts ...Option) (index.Index, error) { idx := index.NewMultihashSorted() if err := LoadIndex(idx, v1r, opts...); err != nil { return nil, err @@ -26,7 +26,7 @@ func GenerateIndex(v1r io.Reader, opts ...ReadOption) (index.Index, error) { // LoadIndex populates idx with index records generated from v1r. // The v1r must be data payload in CARv1 format. -func LoadIndex(idx index.Index, v1r io.Reader, opts ...ReadOption) error { +func LoadIndex(idx index.Index, v1r io.Reader, opts ...Option) error { reader := internalio.ToByteReadSeeker(v1r) header, err := carv1.ReadHeader(reader) if err != nil { @@ -38,7 +38,7 @@ func LoadIndex(idx index.Index, v1r io.Reader, opts ...ReadOption) error { } // Parse Options. - ropts := ApplyReadOptions(opts...) + o := ApplyOptions(opts...) // Record the start of each section, with first section starring from current position in the // reader, i.e. right after the header, since we have only read the header so far. @@ -64,7 +64,7 @@ func LoadIndex(idx index.Index, v1r io.Reader, opts ...ReadOption) error { // Null padding; by default it's an error. if sectionLen == 0 { - if ropts.ZeroLengthSectionAsEOF { + if o.ZeroLengthSectionAsEOF { break } else { return fmt.Errorf("carv1 null padding not allowed by default; see ZeroLengthSectionAsEOF") @@ -112,7 +112,7 @@ func GenerateIndexFromFile(path string) (index.Index, error) { // // Note, the returned index lives entirely in memory and will not depend on the // given reader to fulfill index lookup. -func ReadOrGenerateIndex(rs io.ReadSeeker, opts ...ReadOption) (index.Index, error) { +func ReadOrGenerateIndex(rs io.ReadSeeker, opts ...Option) (index.Index, error) { // Read version. version, err := ReadVersion(rs) if err != nil { diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 601fb8cb51..529fb91379 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -5,16 +5,14 @@ import ( "os" "testing" - "github.com/multiformats/go-multihash" - "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-multihash" "github.com/multiformats/go-varint" - - "github.com/ipld/go-car/v2/index" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,14 +21,14 @@ func TestReadOrGenerateIndex(t *testing.T) { tests := []struct { name string carPath string - readOpts []carv2.ReadOption + opts []carv2.Option wantIndexer func(t *testing.T) index.Index wantErr bool }{ { "CarV1IsIndexedAsExpected", "testdata/sample-v1.car", - []carv2.ReadOption{}, + []carv2.Option{}, func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1.car") require.NoError(t, err) @@ -44,7 +42,7 @@ func TestReadOrGenerateIndex(t *testing.T) { { "CarV2WithIndexIsReturnedAsExpected", "testdata/sample-wrapped-v2.car", - []carv2.ReadOption{}, + []carv2.Option{}, func(t *testing.T) index.Index { v2, err := os.Open("testdata/sample-wrapped-v2.car") require.NoError(t, err) @@ -60,7 +58,7 @@ func TestReadOrGenerateIndex(t *testing.T) { { "CarV1WithZeroLenSectionIsGeneratedAsExpected", "testdata/sample-v1-with-zero-len-section.car", - []carv2.ReadOption{carv2.ZeroLengthSectionAsEOF(true)}, + []carv2.Option{carv2.ZeroLengthSectionAsEOF(true)}, func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1-with-zero-len-section.car") require.NoError(t, err) @@ -74,7 +72,7 @@ func TestReadOrGenerateIndex(t *testing.T) { { "AnotherCarV1WithZeroLenSectionIsGeneratedAsExpected", "testdata/sample-v1-with-zero-len-section2.car", - []carv2.ReadOption{carv2.ZeroLengthSectionAsEOF(true)}, + []carv2.Option{carv2.ZeroLengthSectionAsEOF(true)}, func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1-with-zero-len-section2.car") require.NoError(t, err) @@ -88,14 +86,14 @@ func TestReadOrGenerateIndex(t *testing.T) { { "CarV1WithZeroLenSectionWithoutOptionIsError", "testdata/sample-v1-with-zero-len-section.car", - []carv2.ReadOption{}, + []carv2.Option{}, func(t *testing.T) index.Index { return nil }, true, }, { "CarOtherThanV1OrV2IsError", "testdata/sample-rootless-v42.car", - []carv2.ReadOption{}, + []carv2.Option{}, func(t *testing.T) index.Index { return nil }, true, }, @@ -105,7 +103,7 @@ func TestReadOrGenerateIndex(t *testing.T) { carFile, err := os.Open(tt.carPath) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, carFile.Close()) }) - got, err := carv2.ReadOrGenerateIndex(carFile, tt.readOpts...) + got, err := carv2.ReadOrGenerateIndex(carFile, tt.opts...) if tt.wantErr { require.Error(t, err) } else { diff --git a/ipld/car/v2/internal/options/options.go b/ipld/car/v2/internal/options/options.go deleted file mode 100644 index 3add2b5a43..0000000000 --- a/ipld/car/v2/internal/options/options.go +++ /dev/null @@ -1,19 +0,0 @@ -// Package options provides utilities to work with ReadOption and WriteOption used internally. -package options - -import carv2 "github.com/ipld/go-car/v2" - -// SplitReadWriteOptions splits the rwopts by type into ReadOption and WriteOption slices. -func SplitReadWriteOptions(rwopts ...carv2.ReadWriteOption) ([]carv2.ReadOption, []carv2.WriteOption) { - var ropts []carv2.ReadOption - var wopts []carv2.WriteOption - for _, opt := range rwopts { - switch opt := opt.(type) { - case carv2.ReadOption: - ropts = append(ropts, opt) - case carv2.WriteOption: - wopts = append(wopts, opt) - } - } - return ropts, wopts -} diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index ed6502cc91..ad554a79cd 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -2,51 +2,41 @@ package car import "github.com/multiformats/go-multicodec" -// ReadOptions holds the configured options after applying a number of -// ReadOption funcs. -// -// This type should not be used directly by end users; it's only exposed as a -// side effect of ReadOption. -type ReadOptions struct { - ZeroLengthSectionAsEOF bool - - BlockstoreUseWholeCIDs bool -} - -// ApplyReadOptions applies ropts and returns the resulting ReadOptions. -func ApplyReadOptions(ropts ...ReadOption) ReadOptions { - var opts ReadOptions - for _, opt := range ropts { - opt(&opts) - } - return opts -} +// Option describes an option which affects behavior when interacting with CAR files. +type Option func(*Options) -// ReadOption describes an option which affects behavior when parsing CAR files. -type ReadOption func(*ReadOptions) +// ReadOption hints that an API wants options related only to reading CAR files. +type ReadOption = Option -func (ReadOption) readWriteOption() {} +// WriteOption hints that an API wants options related only to reading CAR files. +type WriteOption = Option -var _ ReadWriteOption = ReadOption(nil) +// ReadWriteOption is either a ReadOption or a WriteOption. +// Deprecated: use Option instead. +type ReadWriteOption = Option -// WriteOptions holds the configured options after applying a number of -// WriteOption funcs. +// Options holds the configured options after applying a number of +// Option funcs. // // This type should not be used directly by end users; it's only exposed as a -// side effect of WriteOption. -type WriteOptions struct { - DataPadding uint64 - IndexPadding uint64 - IndexCodec multicodec.Code +// side effect of Option. +type Options struct { + DataPadding uint64 + IndexPadding uint64 + IndexCodec multicodec.Code + ZeroLengthSectionAsEOF bool BlockstoreAllowDuplicatePuts bool + BlockstoreUseWholeCIDs bool } -// ApplyWriteOptions applies the given ropts and returns the resulting WriteOptions. -func ApplyWriteOptions(ropts ...WriteOption) WriteOptions { - var opts WriteOptions - for _, opt := range ropts { - opt(&opts) +// ApplyOptions applies given opts and returns the resulting Options. +// This function should not be used directly by end users; it's only exposed as a +// side effect of Option. +func ApplyOptions(opt ...Option) Options { + var opts Options + for _, o := range opt { + o(&opts) } // Set defaults for zero valued fields. if opts.IndexCodec == 0 { @@ -55,47 +45,33 @@ func ApplyWriteOptions(ropts ...WriteOption) WriteOptions { return opts } -// WriteOption describes an option which affects behavior when encoding CAR files. -type WriteOption func(*WriteOptions) - -func (WriteOption) readWriteOption() {} - -var _ ReadWriteOption = WriteOption(nil) - -// ReadWriteOption is either a ReadOption or a WriteOption. -type ReadWriteOption interface { - readWriteOption() -} - -// ZeroLengthSectionAsEOF is a read option which allows a CARv1 decoder to treat +// ZeroLengthSectionAsEOF sets whether to allow the CARv1 decoder to treat // a zero-length section as the end of the input CAR file. For example, this can // be useful to allow "null padding" after a CARv1 without knowing where the // padding begins. -func ZeroLengthSectionAsEOF(enable bool) ReadOption { - return func(o *ReadOptions) { +func ZeroLengthSectionAsEOF(enable bool) Option { + return func(o *Options) { o.ZeroLengthSectionAsEOF = enable } } -// UseDataPadding is a write option which sets the padding to be added between -// CARv2 header and its data payload on Finalize. -func UseDataPadding(p uint64) WriteOption { - return func(o *WriteOptions) { +// UseDataPadding sets the padding to be added between CARv2 header and its data payload on Finalize. +func UseDataPadding(p uint64) Option { + return func(o *Options) { o.DataPadding = p } } -// UseIndexPadding is a write option which sets the padding between data payload -// and its index on Finalize. -func UseIndexPadding(p uint64) WriteOption { - return func(o *WriteOptions) { +// UseIndexPadding sets the padding between data payload and its index on Finalize. +func UseIndexPadding(p uint64) Option { + return func(o *Options) { o.IndexPadding = p } } -// UseIndexCodec is a write option which sets the codec used for index generation. -func UseIndexCodec(c multicodec.Code) WriteOption { - return func(o *WriteOptions) { +// UseIndexCodec sets the codec used for index generation. +func UseIndexCodec(c multicodec.Code) Option { + return func(o *Options) { o.IndexCodec = c } } diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index b9742ec72c..9394a736bf 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -4,10 +4,9 @@ import ( "fmt" "io" - internalio "github.com/ipld/go-car/v2/internal/io" - "github.com/ipfs/go-cid" "github.com/ipld/go-car/v2/internal/carv1" + internalio "github.com/ipld/go-car/v2/internal/io" "golang.org/x/exp/mmap" ) @@ -17,12 +16,12 @@ type Reader struct { Version uint64 r io.ReaderAt roots []cid.Cid - ropts ReadOptions + opts Options closer io.Closer } // OpenReader is a wrapper for NewReader which opens the file at path. -func OpenReader(path string, opts ...ReadOption) (*Reader, error) { +func OpenReader(path string, opts ...Option) (*Reader, error) { f, err := mmap.Open(path) if err != nil { return nil, err @@ -44,11 +43,11 @@ func OpenReader(path string, opts ...ReadOption) (*Reader, error) { // Note that any other version other than 1 or 2 will result in an error. The caller may use // Reader.Version to get the actual version r represents. In the case where r represents a CARv1 // Reader.Header will not be populated and is left as zero-valued. -func NewReader(r io.ReaderAt, opts ...ReadOption) (*Reader, error) { +func NewReader(r io.ReaderAt, opts ...Option) (*Reader, error) { cr := &Reader{ r: r, } - cr.ropts = ApplyReadOptions(opts...) + cr.opts = ApplyOptions(opts...) or := internalio.NewOffsetReadSeeker(r, 0) var err error diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 2a22414db6..8ed5756fe5 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -46,27 +46,17 @@ func WrapV1File(srcPath, dstPath string) error { // WrapV1 takes a CARv1 file and wraps it as a CARv2 file with an index. // The resulting CARv2 file's inner CARv1 payload is left unmodified, // and does not use any padding before the innner CARv1 or index. -func WrapV1(src io.ReadSeeker, dst io.Writer, opts ...ReadWriteOption) error { +func WrapV1(src io.ReadSeeker, dst io.Writer, opts ...Option) error { // TODO: verify src is indeed a CARv1 to prevent misuse. // GenerateIndex should probably be in charge of that. - var ro []ReadOption - var wo []WriteOption - for _, opt := range opts { - switch opt := opt.(type) { - case ReadOption: - ro = append(ro, opt) - case WriteOption: - wo = append(wo, opt) - } - } - wopts := ApplyWriteOptions(wo...) - idx, err := index.New(wopts.IndexCodec) + o := ApplyOptions(opts...) + idx, err := index.New(o.IndexCodec) if err != nil { return err } - if err := LoadIndex(idx, src, ro...); err != nil { + if err := LoadIndex(idx, src, opts...); err != nil { return err } From fb4eba3870533c15c6aaab79218c45a0ee848620 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 17 Sep 2021 21:56:56 +0100 Subject: [PATCH 173/291] Implement options to handle `IDENTITY` CIDs gracefully Implement two additional options that allow a CARv2 file to 1) include IDENTNTIY CIDs, and 2) specify a maximum allowed CID length with default of 2KiB as a sufficiently large default. Configure ReadWrite blockstore to persist given blocks with IDENTITY CIDs. Introduce a new Characteristics filed that signalls whether an index in a CAR file contains a full catalog of CIDs for backward compatibility purposes. Note, this is a new addition and will need to be added to the spec in a separate PR. Relates to #215 This commit was moved from ipld/go-car@f35d88ce16ca85297c9d2ca6cf23022ed8b7aec6 --- ipld/car/v2/blockstore/doc.go | 2 +- ipld/car/v2/blockstore/readonly_test.go | 20 +-- ipld/car/v2/blockstore/readwrite.go | 23 ++- ipld/car/v2/blockstore/readwrite_test.go | 181 +++++++++++++++++++++-- ipld/car/v2/car.go | 34 +++++ ipld/car/v2/car_test.go | 43 ++++++ ipld/car/v2/errors.go | 18 +++ ipld/car/v2/errors_test.go | 12 ++ ipld/car/v2/index/index.go | 25 +++- ipld/car/v2/index/indexsorted.go | 6 - ipld/car/v2/index/indexsorted_test.go | 42 ------ ipld/car/v2/index/mhindexsorted.go | 5 - ipld/car/v2/index/mhindexsorted_test.go | 25 ---- ipld/car/v2/index_gen.go | 18 ++- ipld/car/v2/options.go | 25 ++++ ipld/car/v2/options_test.go | 41 +++++ 16 files changed, 408 insertions(+), 112 deletions(-) create mode 100644 ipld/car/v2/errors.go create mode 100644 ipld/car/v2/errors_test.go create mode 100644 ipld/car/v2/options_test.go diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index 6b96b7a6ef..479442e129 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -22,7 +22,7 @@ // * blockstore.Has will always return true. // * blockstore.Get will always succeed, returning the multihash digest of the given CID. // * blockstore.GetSize will always succeed, returning the multihash digest length of the given CID. -// * blockstore.Put and blockstore.PutMany will always succeed without performing any operation. +// * blockstore.Put and blockstore.PutMany will always succeed without performing any operation unless car.StoreIdentityCIDs is enabled. // // See: https://pkg.go.dev/github.com/ipfs/go-ipfs-blockstore#NewIdStore package blockstore diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 129e6b0da7..a242abd8c0 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -32,48 +32,50 @@ func TestReadOnly(t *testing.T) { name string v1OrV2path string opts []carv2.Option - v1r *carv1.CarReader }{ { "OpenedWithCarV1", "../testdata/sample-v1.car", - []carv2.Option{UseWholeCIDs(true)}, - newV1ReaderFromV1File(t, "../testdata/sample-v1.car", false), + []carv2.Option{UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, }, { "OpenedWithCarV2", "../testdata/sample-wrapped-v2.car", - []carv2.Option{UseWholeCIDs(true)}, - newV1ReaderFromV2File(t, "../testdata/sample-wrapped-v2.car", false), + []carv2.Option{UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, }, { "OpenedWithCarV1ZeroLenSection", "../testdata/sample-v1-with-zero-len-section.car", []carv2.Option{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, - newV1ReaderFromV1File(t, "../testdata/sample-v1-with-zero-len-section.car", true), }, { "OpenedWithAnotherCarV1ZeroLenSection", "../testdata/sample-v1-with-zero-len-section2.car", []carv2.Option{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, - newV1ReaderFromV1File(t, "../testdata/sample-v1-with-zero-len-section2.car", true), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { subject, err := OpenReadOnly(tt.v1OrV2path, tt.opts...) + require.NoError(t, err) t.Cleanup(func() { require.NoError(t, subject.Close()) }) + + f, err := os.Open(tt.v1OrV2path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + + reader, err := carv2.NewBlockReader(f, tt.opts...) require.NoError(t, err) // Assert roots match v1 payload. - wantRoots := tt.v1r.Header.Roots + wantRoots := reader.Roots gotRoots, err := subject.Roots() require.NoError(t, err) require.Equal(t, wantRoots, gotRoots) var wantCids []cid.Cid for { - wantBlock, err := tt.v1r.Next() + wantBlock, err := reader.Next() if err == io.EOF { break } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 4b31190872..b98c58d4be 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -295,11 +295,23 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { for _, bl := range blks { c := bl.Cid() - // Check for IDENTITY CID. If IDENTITY, ignore and move to the next block. - if _, ok, err := isIdentity(c); err != nil { - return err - } else if ok { - continue + // If StoreIdentityCIDs option is disabled then treat IDENTITY CIDs like IdStore. + if !b.opts.StoreIdentityCIDs { + // Check for IDENTITY CID. If IDENTITY, ignore and move to the next block. + if _, ok, err := isIdentity(c); err != nil { + return err + } else if ok { + continue + } + } + + // Check if its size is too big. + // If larger than maximum allowed size, return error. + // Note, we need to check this regardless of whether we have IDENTITY CID or not. + // Since multhihash codes other than IDENTITY can result in large digests. + cSize := uint64(len(c.Bytes())) + if cSize > b.opts.MaxIndexCidSize { + return &carv2.ErrCidTooLarge{MaxSize: b.opts.MaxIndexCidSize, CurrentSize: cSize} } if !b.opts.BlockstoreAllowDuplicatePuts { @@ -351,6 +363,7 @@ func (b *ReadWrite) Finalize() error { // TODO check if add index option is set and don't write the index then set index offset to zero. b.header = b.header.WithDataSize(uint64(b.dataWriter.Position())) + b.header.Characteristics.SetFullyIndexed(b.opts.StoreIdentityCIDs) // Note that we can't use b.Close here, as that tries to grab the same // mutex we're holding here. diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index cfdad46422..b7da6ec837 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -2,6 +2,7 @@ package blockstore_test import ( "context" + "crypto/sha512" "fmt" "io" "io/ioutil" @@ -12,21 +13,19 @@ import ( "testing" "time" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipfsblockstore "github.com/ipfs/go-ipfs-blockstore" + cbor "github.com/ipfs/go-ipld-cbor" "github.com/ipfs/go-merkledag" - carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" "github.com/ipld/go-car/v2/index" - "github.com/stretchr/testify/assert" - + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - ipfsblockstore "github.com/ipfs/go-ipfs-blockstore" - "github.com/ipld/go-car/v2/blockstore" - - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" - "github.com/ipld/go-car/v2/internal/carv1" ) var ( @@ -688,3 +687,165 @@ func TestReadWriteErrorAfterClose(t *testing.T) { // in progress. } } + +func TestOpenReadWrite_WritesIdentityCIDsWhenOptionIsEnabled(t *testing.T) { + path := filepath.Join(t.TempDir(), "readwrite-with-id-enabled.car") + subject, err := blockstore.OpenReadWrite(path, []cid.Cid{}, carv2.StoreIdentityCIDs(true)) + require.NoError(t, err) + + data := []byte("fish") + idmh, err := multihash.Sum(data, multihash.IDENTITY, -1) + require.NoError(t, err) + idCid := cid.NewCidV1(uint64(multicodec.Raw), idmh) + + idBlock, err := blocks.NewBlockWithCid(data, idCid) + require.NoError(t, err) + err = subject.Put(idBlock) + require.NoError(t, err) + + has, err := subject.Has(idCid) + require.NoError(t, err) + require.True(t, has) + + gotBlock, err := subject.Get(idCid) + require.NoError(t, err) + require.Equal(t, idBlock, gotBlock) + + keysChan, err := subject.AllKeysChan(context.Background()) + require.NoError(t, err) + var i int + for c := range keysChan { + i++ + require.Equal(t, idCid, c) + } + require.Equal(t, 1, i) + + err = subject.Finalize() + require.NoError(t, err) + + // Assert resulting CAR file indeed has the IDENTITY block. + f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + + reader, err := carv2.NewBlockReader(f) + require.NoError(t, err) + + gotBlock, err = reader.Next() + require.NoError(t, err) + require.Equal(t, idBlock, gotBlock) + + next, err := reader.Next() + require.Equal(t, io.EOF, err) + require.Nil(t, next) + + // Assert the id is indexed. + r, err := carv2.OpenReader(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, r.Close()) }) + require.True(t, r.Header.HasIndex()) + + ir := r.IndexReader() + require.NotNil(t, ir) + + gotIdx, err := index.ReadFrom(ir) + require.NoError(t, err) + + // Determine expected offset as the length of header plus one + header, err := carv1.ReadHeader(r.DataReader()) + require.NoError(t, err) + object, err := cbor.DumpObject(header) + require.NoError(t, err) + expectedOffset := len(object) + 1 + + // Assert index is iterable and has exactly one record with expected multihash and offset. + switch idx := gotIdx.(type) { + case index.IterableIndex: + var i int + err := idx.ForEach(func(mh multihash.Multihash, offset uint64) error { + i++ + require.Equal(t, idmh, mh) + require.Equal(t, uint64(expectedOffset), offset) + return nil + }) + require.NoError(t, err) + require.Equal(t, 1, i) + default: + require.Failf(t, "unexpected index type", "wanted %v but got %v", multicodec.CarMultihashIndexSorted, idx.Codec()) + } +} + +func TestOpenReadWrite_ErrorsWhenWritingTooLargeOfACid(t *testing.T) { + maxAllowedCidSize := uint64(2) + path := filepath.Join(t.TempDir(), "readwrite-with-id-enabled-too-large.car") + subject, err := blockstore.OpenReadWrite(path, []cid.Cid{}, carv2.MaxIndexCidSize(maxAllowedCidSize)) + t.Cleanup(subject.Discard) + require.NoError(t, err) + + data := []byte("monsterlobster") + mh, err := multihash.Sum(data, multihash.SHA2_256, -1) + require.NoError(t, err) + bigCid := cid.NewCidV1(uint64(multicodec.Raw), mh) + bigCidLen := uint64(bigCid.ByteLen()) + require.True(t, bigCidLen > maxAllowedCidSize) + + bigBlock, err := blocks.NewBlockWithCid(data, bigCid) + require.NoError(t, err) + err = subject.Put(bigBlock) + require.Equal(t, &carv2.ErrCidTooLarge{MaxSize: maxAllowedCidSize, CurrentSize: bigCidLen}, err) +} + +func TestReadWrite_ReWritingCARv1WithIdentityCidIsIdenticalToOriginalWithOptionsEnabled(t *testing.T) { + originalCARv1Path := "../testdata/sample-v1.car" + originalCarV1, err := os.Open(originalCARv1Path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, originalCarV1.Close()) }) + + r, err := carv2.NewBlockReader(originalCarV1) + require.NoError(t, err) + + path := filepath.Join(t.TempDir(), "readwrite-from-carv1-with-id-enabled.car") + subject, err := blockstore.OpenReadWrite(path, r.Roots, carv2.StoreIdentityCIDs(true)) + require.NoError(t, err) + var idCidCount int + for { + next, err := r.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + if next.Cid().Prefix().MhType == multihash.IDENTITY { + idCidCount++ + } + err = subject.Put(next) + require.NoError(t, err) + } + require.NotZero(t, idCidCount) + err = subject.Finalize() + require.NoError(t, err) + + v2r, err := carv2.OpenReader(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, v2r.Close()) }) + + // Assert characteristics bit is set. + require.True(t, v2r.Header.Characteristics.IsFullyIndexed()) + + // Assert original CARv1 and generated innter CARv1 payload have the same SHA512 hash + // Note, we hash instead of comparing bytes to avoid excessive memory usage when sample CARv1 is large. + + hasher := sha512.New() + gotWritten, err := io.Copy(hasher, v2r.DataReader()) + require.NoError(t, err) + gotSum := hasher.Sum(nil) + + hasher.Reset() + _, err = originalCarV1.Seek(0, io.SeekStart) + require.NoError(t, err) + wantWritten, err := io.Copy(hasher, originalCarV1) + require.NoError(t, err) + wantSum := hasher.Sum(nil) + + require.Equal(t, wantWritten, gotWritten) + require.Equal(t, wantSum, gotSum) +} diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index d12683313b..f2885d9d6e 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -43,6 +43,9 @@ type ( } ) +// fullyIndexedCharPos is the position of Characteristics.Hi bit that specifies whether the index is a catalog af all CIDs or not. +const fullyIndexedCharPos = 7 // left-most bit + // WriteTo writes this characteristics to the given w. func (c Characteristics) WriteTo(w io.Writer) (n int64, err error) { buf := make([]byte, 16) @@ -64,6 +67,37 @@ func (c *Characteristics) ReadFrom(r io.Reader) (int64, error) { return n, nil } +// IsFullyIndexed specifies whether the index of CARv2 represents a catalog of all CID segments. +// See StoreIdentityCIDs +func (c *Characteristics) IsFullyIndexed() bool { + return isBitSet(c.Hi, fullyIndexedCharPos) +} + +// SetFullyIndexed sets whether of CARv2 represents a catalog of all CID segments. +func (c *Characteristics) SetFullyIndexed(b bool) { + if b { + c.Hi = setBit(c.Hi, fullyIndexedCharPos) + } else { + c.Hi = unsetBit(c.Hi, fullyIndexedCharPos) + } +} + +func setBit(n uint64, pos uint) uint64 { + n |= 1 << pos + return n +} + +func unsetBit(n uint64, pos uint) uint64 { + mask := uint64(^(1 << pos)) + n &= mask + return n +} + +func isBitSet(n uint64, pos uint) bool { + bit := n & (1 << pos) + return bit > 0 +} + // NewHeader instantiates a new CARv2 header, given the data size. func NewHeader(dataSize uint64) Header { header := Header{ diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index 4223a5600e..64519b2976 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -4,6 +4,8 @@ import ( "bytes" "testing" + "github.com/stretchr/testify/require" + carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/internal/carv1" "github.com/stretchr/testify/assert" @@ -199,3 +201,44 @@ func TestNewHeaderHasExpectedValues(t *testing.T) { got := carv2.NewHeader(wantCarV1Len) assert.Equal(t, want, got, "NewHeader got = %v, want = %v", got, want) } + +func TestCharacteristics_StoreIdentityCIDs(t *testing.T) { + subject := carv2.Characteristics{} + require.False(t, subject.IsFullyIndexed()) + + subject.SetFullyIndexed(true) + require.True(t, subject.IsFullyIndexed()) + + var buf bytes.Buffer + written, err := subject.WriteTo(&buf) + require.NoError(t, err) + require.Equal(t, int64(16), written) + require.Equal(t, []byte{ + 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }, buf.Bytes()) + + var decodedSubject carv2.Characteristics + read, err := decodedSubject.ReadFrom(&buf) + require.NoError(t, err) + require.Equal(t, int64(16), read) + require.True(t, decodedSubject.IsFullyIndexed()) + + buf.Reset() + subject.SetFullyIndexed(false) + require.False(t, subject.IsFullyIndexed()) + + written, err = subject.WriteTo(&buf) + require.NoError(t, err) + require.Equal(t, int64(16), written) + require.Equal(t, []byte{ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }, buf.Bytes()) + + var decodedSubjectAgain carv2.Characteristics + read, err = decodedSubjectAgain.ReadFrom(&buf) + require.NoError(t, err) + require.Equal(t, int64(16), read) + require.False(t, decodedSubjectAgain.IsFullyIndexed()) +} diff --git a/ipld/car/v2/errors.go b/ipld/car/v2/errors.go new file mode 100644 index 0000000000..ee89e0b25a --- /dev/null +++ b/ipld/car/v2/errors.go @@ -0,0 +1,18 @@ +package car + +import ( + "fmt" +) + +var _ (error) = (*ErrCidTooLarge)(nil) + +// ErrCidTooLarge signals that a CID is too large to include in CARv2 index. +// See: MaxIndexCidSize. +type ErrCidTooLarge struct { + MaxSize uint64 + CurrentSize uint64 +} + +func (e *ErrCidTooLarge) Error() string { + return fmt.Sprintf("cid size is larger than max allowed (%d > %d)", e.CurrentSize, e.MaxSize) +} diff --git a/ipld/car/v2/errors_test.go b/ipld/car/v2/errors_test.go new file mode 100644 index 0000000000..56e2c7a095 --- /dev/null +++ b/ipld/car/v2/errors_test.go @@ -0,0 +1,12 @@ +package car + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewErrCidTooLarge_ErrorContainsSizes(t *testing.T) { + subject := &ErrCidTooLarge{MaxSize: 1413, CurrentSize: 1414} + require.EqualError(t, subject, "cid size is larger than max allowed (1414 > 1413)") +} diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 8abd1d3a30..8e447d0fe5 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -31,9 +31,7 @@ type ( // implementations might index the entire CID, the entire multihash, or // just part of a multihash's digest. // - // In accordance with the CARv2 specification, Index will never contain information about CIDs - // with multihash.IDENTITY code. - // See: https://ipld.io/specs/transport/car/carv2/#index-format + // See: multicodec.CarIndexSorted, multicodec.CarMultihashIndexSorted Index interface { // Codec provides the multicodec code that the index implements. // @@ -47,12 +45,19 @@ type ( Unmarshal(r io.Reader) error // Load inserts a number of records into the index. + // Note that Index will load all given records. Any filtering of the records such as + // exclusion of CIDs with multihash.IDENTITY code must occur prior to calling this function. + // + // Further, the actual information extracted and indexed from the given records entirely + // depends on the concrete index implementation. + // For example, some index implementations may only store partial multihashes. Load([]Record) error // GetAll looks up all blocks matching a given CID, // calling a function for each one of their offsets. // - // If the function returns false, GetAll stops. + // GetAll stops if the given function returns false, + // or there are no more offsets; whichever happens first. // // If no error occurred and the CID isn't indexed, // meaning that no callbacks happen, @@ -61,8 +66,13 @@ type ( } // IterableIndex extends Index in cases where the Index is able to - // provide an iterator for getting the list of all entries in the + // provide an iterator for getting the list of all multihashes in the // index. + // + // Note that it is possible for an index to contain multiple offsets for + // a given multihash. + // + // See: IterableIndex.ForEach, Index.GetAll. IterableIndex interface { Index @@ -73,6 +83,11 @@ type ( // // If the callback returns a non-nil error, the iteration is aborted, // and the ForEach function returns the error to the user. + // + // An index may contain multiple offsets corresponding to the same multihash, e.g. via duplicate blocks. + // In such cases, the given function may be called multiple times with the same multhihash but different offset. + // + // The order of calls to the given function is entirely index-specific. ForEach(func(multihash.Multihash, uint64) error) error } ) diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index c6165ffa76..6b6c5a6805 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -207,12 +207,6 @@ func (m *multiWidthIndex) Load(items []Record) error { return err } - // Ignore records with IDENTITY as required by CARv2 spec. - // See: https://ipld.io/specs/transport/car/carv2/#index-format - if decHash.Code == multihash.IDENTITY { - continue - } - digest := decHash.Digest idx, ok := idxs[len(digest)] if !ok { diff --git a/ipld/car/v2/index/indexsorted_test.go b/ipld/car/v2/index/indexsorted_test.go index 46322e5435..5c1ee44956 100644 --- a/ipld/car/v2/index/indexsorted_test.go +++ b/ipld/car/v2/index/indexsorted_test.go @@ -4,9 +4,6 @@ import ( "encoding/binary" "testing" - "github.com/ipfs/go-cid" - "github.com/multiformats/go-multihash" - "github.com/ipfs/go-merkledag" "github.com/multiformats/go-multicodec" "github.com/stretchr/testify/require" @@ -65,42 +62,3 @@ func TestSingleWidthIndex_GetAll(t *testing.T) { require.NoError(t, err) require.Equal(t, 3, foundCount) } - -func TestIndexSorted_IgnoresIdentityCids(t *testing.T) { - data := []byte("🐟 in da 🌊d") - // Generate a record with IDENTITY multihash - idMh, err := multihash.Sum(data, multihash.IDENTITY, -1) - require.NoError(t, err) - idRec := Record{ - Cid: cid.NewCidV1(cid.Raw, idMh), - Offset: 1, - } - // Generate a record with non-IDENTITY multihash - nonIdMh, err := multihash.Sum(data, multihash.SHA2_256, -1) - require.NoError(t, err) - noIdRec := Record{ - Cid: cid.NewCidV1(cid.Raw, nonIdMh), - Offset: 2, - } - - subject := newSorted() - err = subject.Load([]Record{idRec, noIdRec}) - require.NoError(t, err) - - // Assert record with IDENTITY CID is not present. - err = subject.GetAll(idRec.Cid, func(u uint64) bool { - require.Fail(t, "no IDENTITY record shoul be found") - return false - }) - require.Equal(t, ErrNotFound, err) - - // Assert record with non-IDENTITY CID is indeed present. - var found bool - err = subject.GetAll(noIdRec.Cid, func(gotOffset uint64) bool { - found = true - require.Equal(t, noIdRec.Offset, gotOffset) - return false - }) - require.NoError(t, err) - require.True(t, found) -} diff --git a/ipld/car/v2/index/mhindexsorted.go b/ipld/car/v2/index/mhindexsorted.go index 6ae2c5687d..f81e3a942b 100644 --- a/ipld/car/v2/index/mhindexsorted.go +++ b/ipld/car/v2/index/mhindexsorted.go @@ -17,7 +17,6 @@ var ( type ( // MultihashIndexSorted maps multihash code (i.e. hashing algorithm) to multiWidthCodedIndex. - // This index ignores any Record with multihash.IDENTITY. MultihashIndexSorted map[uint64]*multiWidthCodedIndex // multiWidthCodedIndex stores multihash code for each multiWidthIndex. multiWidthCodedIndex struct { @@ -123,10 +122,6 @@ func (m *MultihashIndexSorted) Load(records []Record) error { return err } code := dmh.Code - // Ignore IDENTITY multihash in the index. - if code == multihash.IDENTITY { - continue - } recsByCode, ok := byCode[code] if !ok { recsByCode = make([]Record, 0) diff --git a/ipld/car/v2/index/mhindexsorted_test.go b/ipld/car/v2/index/mhindexsorted_test.go index ced8a921a1..b5ef7b89b7 100644 --- a/ipld/car/v2/index/mhindexsorted_test.go +++ b/ipld/car/v2/index/mhindexsorted_test.go @@ -20,31 +20,6 @@ func TestMutilhashSortedIndex_Codec(t *testing.T) { require.Equal(t, multicodec.CarMultihashIndexSorted, subject.Codec()) } -func TestMultiWidthCodedIndex_LoadDoesNotLoadIdentityMultihash(t *testing.T) { - rng := rand.New(rand.NewSource(1413)) - identityRecords := generateIndexRecords(t, multihash.IDENTITY, rng) - nonIdentityRecords := generateIndexRecords(t, multihash.SHA2_256, rng) - records := append(identityRecords, nonIdentityRecords...) - - subject, err := index.New(multicodec.CarMultihashIndexSorted) - require.NoError(t, err) - err = subject.Load(records) - require.NoError(t, err) - - // Assert index does not contain any records with IDENTITY multihash code. - for _, r := range identityRecords { - wantCid := r.Cid - err = subject.GetAll(wantCid, func(o uint64) bool { - require.Fail(t, "subject should not contain any records with IDENTITY multihash code") - return false - }) - require.Equal(t, index.ErrNotFound, err) - } - - // Assert however, index does contain the non IDENTITY records. - requireContainsAll(t, subject, nonIdentityRecords) -} - func TestMultiWidthCodedIndex_MarshalUnmarshal(t *testing.T) { rng := rand.New(rand.NewSource(1413)) records := generateIndexRecords(t, multihash.SHA2_256, rng) diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 4602add561..81d23919db 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -9,15 +9,19 @@ import ( "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-multihash" "github.com/multiformats/go-varint" ) // GenerateIndex generates index for a given car in v1 format. -// The generated index will be in multicodec.CarMultihashIndexSorted, the default index codec. // The index can be stored in serialized format using index.WriteTo. // See LoadIndex. func GenerateIndex(v1r io.Reader, opts ...Option) (index.Index, error) { - idx := index.NewMultihashSorted() + wopts := ApplyOptions(opts...) + idx, err := index.New(wopts.IndexCodec) + if err != nil { + return nil, err + } if err := LoadIndex(idx, v1r, opts...); err != nil { return nil, err } @@ -76,7 +80,13 @@ func LoadIndex(idx index.Index, v1r io.Reader, opts ...Option) error { if err != nil { return err } - records = append(records, index.Record{Cid: c, Offset: uint64(sectionOffset)}) + + if o.StoreIdentityCIDs || c.Prefix().MhType != multihash.IDENTITY { + if uint64(cidLen) > o.MaxIndexCidSize { + return &ErrCidTooLarge{MaxSize: o.MaxIndexCidSize, CurrentSize: uint64(cidLen)} + } + records = append(records, index.Record{Cid: c, Offset: uint64(sectionOffset)}) + } // Seek to the next section by skipping the block. // The section length includes the CID, so subtract it. @@ -94,7 +104,7 @@ func LoadIndex(idx index.Index, v1r io.Reader, opts ...Option) error { } // GenerateIndexFromFile walks a car v1 file at the give path and generates an index of cid->byte offset. -// The index can be stored using index.Save into a file or serialized using index.WriteTo. +// The index can be stored using index.WriteTo. // See GenerateIndex. func GenerateIndexFromFile(path string) (index.Index, error) { f, err := os.Open(path) diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index ad554a79cd..cc6018c400 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -2,6 +2,9 @@ package car import "github.com/multiformats/go-multicodec" +// DefaultMaxIndexCidSize specifies the maximum size in byptes accepted as a section CID by CARv2 index. +const DefaultMaxIndexCidSize = 2 << 10 // 2 KiB + // Option describes an option which affects behavior when interacting with CAR files. type Option func(*Options) @@ -25,6 +28,8 @@ type Options struct { IndexPadding uint64 IndexCodec multicodec.Code ZeroLengthSectionAsEOF bool + MaxIndexCidSize uint64 + StoreIdentityCIDs bool BlockstoreAllowDuplicatePuts bool BlockstoreUseWholeCIDs bool @@ -42,6 +47,9 @@ func ApplyOptions(opt ...Option) Options { if opts.IndexCodec == 0 { opts.IndexCodec = multicodec.CarMultihashIndexSorted } + if opts.MaxIndexCidSize == 0 { + opts.MaxIndexCidSize = DefaultMaxIndexCidSize + } return opts } @@ -75,3 +83,20 @@ func UseIndexCodec(c multicodec.Code) Option { o.IndexCodec = c } } + +// StoreIdentityCIDs sets whether to persist sections that are referenced by +// CIDs with multihash.IDENTITY digest. +// This option is disabled by default. +func StoreIdentityCIDs(b bool) Option { + return func(o *Options) { + o.StoreIdentityCIDs = b + } +} + +// MaxIndexCidSize specifies the maximum allowed size for indexed CIDs in bytes. +// Indexing a CID with larger than the allowed size results in ErrCidTooLarge error. +func MaxIndexCidSize(s uint64) Option { + return func(o *Options) { + o.MaxIndexCidSize = s + } +} diff --git a/ipld/car/v2/options_test.go b/ipld/car/v2/options_test.go new file mode 100644 index 0000000000..307568a4ad --- /dev/null +++ b/ipld/car/v2/options_test.go @@ -0,0 +1,41 @@ +package car_test + +import ( + "testing" + + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" + "github.com/multiformats/go-multicodec" + "github.com/stretchr/testify/require" +) + +func TestApplyOptions_SetsExpectedDefaults(t *testing.T) { + require.Equal(t, carv2.Options{ + IndexCodec: multicodec.CarMultihashIndexSorted, + MaxIndexCidSize: carv2.DefaultMaxIndexCidSize, + }, carv2.ApplyOptions()) +} + +func TestApplyOptions_AppliesOptions(t *testing.T) { + require.Equal(t, + carv2.Options{ + DataPadding: 123, + IndexPadding: 456, + IndexCodec: multicodec.CarIndexSorted, + ZeroLengthSectionAsEOF: true, + MaxIndexCidSize: 789, + StoreIdentityCIDs: true, + BlockstoreAllowDuplicatePuts: true, + BlockstoreUseWholeCIDs: true, + }, + carv2.ApplyOptions( + carv2.UseDataPadding(123), + carv2.UseIndexPadding(456), + carv2.UseIndexCodec(multicodec.CarIndexSorted), + carv2.ZeroLengthSectionAsEOF(true), + carv2.MaxIndexCidSize(789), + carv2.StoreIdentityCIDs(true), + blockstore.AllowDuplicatePuts(true), + blockstore.UseWholeCIDs(true), + )) +} From d666c463e521df9175c72978df8ae558d42dd984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 27 Sep 2021 17:45:01 +0100 Subject: [PATCH 174/291] clarify the relation between StoreIdentityCIDs and SetFullyIndexed I had to read the code to remind myself if this was the case. Document it, for the sake of other godoc readers. This commit was moved from ipld/go-car@999f74fc3e3f0b06ea155b5efb16e43f6d422bcd --- ipld/car/v2/options.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index cc6018c400..5a3b1930a2 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -86,6 +86,9 @@ func UseIndexCodec(c multicodec.Code) Option { // StoreIdentityCIDs sets whether to persist sections that are referenced by // CIDs with multihash.IDENTITY digest. +// When writing CAR files with this option, +// Characteristics.IsFullyIndexed will be set. +// // This option is disabled by default. func StoreIdentityCIDs(b bool) Option { return func(o *Options) { From 32b4e02fba2d8df64765f710840b25632d8cfab5 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 30 Sep 2021 17:48:19 +1000 Subject: [PATCH 175/291] Add MaxTraversalLinks option to NewSelectiveCar to limit link loading Using the new traversal budget feature of go-ipld-prime This commit was moved from ipld/go-car@2aa0e3852942a0c7b3640db6bff07daef71d0694 --- ipld/car/car_test.go | 39 ++++++++++++++++++++++++++++++++ ipld/car/selectivecar.go | 49 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 137edd6299..7a15033c49 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -15,6 +15,7 @@ import ( basicnode "github.com/ipld/go-ipld-prime/node/basic" "github.com/ipld/go-ipld-prime/traversal/selector" "github.com/ipld/go-ipld-prime/traversal/selector/builder" + selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" "github.com/stretchr/testify/require" ) @@ -173,6 +174,44 @@ func TestRoundtripSelective(t *testing.T) { } } +func TestLinkLimitSelective(t *testing.T) { + sourceBserv := dstest.Bserv() + sourceBs := sourceBserv.Blockstore() + dserv := merkledag.NewDAGService(sourceBserv) + a := merkledag.NewRawNode([]byte("aaaa")) + b := merkledag.NewRawNode([]byte("bbbb")) + c := merkledag.NewRawNode([]byte("cccc")) + + nd1 := &merkledag.ProtoNode{} + nd1.AddNodeLink("cat", a) + + nd2 := &merkledag.ProtoNode{} + nd2.AddNodeLink("first", nd1) + nd2.AddNodeLink("dog", b) + nd2.AddNodeLink("repeat", nd1) + + nd3 := &merkledag.ProtoNode{} + nd3.AddNodeLink("second", nd2) + nd3.AddNodeLink("bear", c) + + assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) + + sc := NewSelectiveCar(context.Background(), + sourceBs, + []Dag{{Root: nd3.Cid(), Selector: selectorparse.CommonSelector_ExploreAllRecursively}}, + MaxTraversalLinks(2)) + + buf := new(bytes.Buffer) + blockCount := 0 + err := sc.Write(buf, func(block Block) error { + blockCount++ + return nil + }) + require.Equal(t, blockCount, 3) // root + 2 + require.Error(t, err) + require.Regexp(t, "^traversal budget exceeded: budget for links reached zero while on path .*", err) +} + func TestEOFHandling(t *testing.T) { // fixture is a clean single-block, single-root CAR fixture, err := hex.DecodeString("3aa265726f6f747381d82a58250001711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80b6776657273696f6e012c01711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80ba165646f646779f5") diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 50a955206a..657a4ed227 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "math" cid "github.com/ipfs/go-cid" util "github.com/ipld/go-car/util" @@ -40,6 +41,7 @@ type SelectiveCar struct { ctx context.Context dags []Dag store ReadStore + opts selectiveCarOptions } // OnCarHeaderFunc is called during traversal when the header is created @@ -61,16 +63,16 @@ type SelectiveCarPrepared struct { // NewSelectiveCar creates a new SelectiveCar for the given car file based // a block store and set of root+selector pairs -func NewSelectiveCar(ctx context.Context, store ReadStore, dags []Dag) SelectiveCar { +func NewSelectiveCar(ctx context.Context, store ReadStore, dags []Dag, opts ...SelectiveCarOption) SelectiveCar { return SelectiveCar{ ctx: ctx, store: store, dags: dags, + opts: applyOptions(opts...), } } func (sc SelectiveCar) traverse(onCarHeader OnCarHeaderFunc, onNewCarBlock OnNewCarBlockFunc) (uint64, error) { - traverser := &selectiveCarTraverser{onCarHeader, onNewCarBlock, 0, cid.NewSet(), sc, cidlink.DefaultLinkSystem()} traverser.lsys.StorageReadOpener = traverser.loader return traverser.traverse() @@ -264,16 +266,55 @@ func (sct *selectiveCarTraverser) traverseBlocks() error { if err != nil { return err } - err = traversal.Progress{ + prog := traversal.Progress{ Cfg: &traversal.Config{ Ctx: sct.sc.ctx, LinkSystem: sct.lsys, LinkTargetNodePrototypeChooser: nsc, }, - }.WalkAdv(nd, parsed, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) + } + if sct.sc.opts.maxTraversalLinks < math.MaxInt64 { + prog.Budget = &traversal.Budget{ + NodeBudget: math.MaxInt64, + LinkBudget: sct.sc.opts.maxTraversalLinks, + } + } + err = prog.WalkAdv(nd, parsed, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) if err != nil { return err } } return nil } + +// selectiveCarOptions holds the configured options after applying a number of +// SelectiveCarOption funcs. +// +// This type should not be used directly by end users; it's only exposed as a +// side effect of SelectiveCarOption. +type selectiveCarOptions struct { + maxTraversalLinks int64 +} + +// SelectiveCarOption describes an option which affects behavior when +// interacting with the SelectiveCar interface. +type SelectiveCarOption func(*selectiveCarOptions) + +// MaxTraversalLinks changes the allowed number of links a selector traversal +// can execute before failing +func MaxTraversalLinks(maxTraversalLinks uint64) SelectiveCarOption { + return func(sco *selectiveCarOptions) { + sco.maxTraversalLinks = int64(maxTraversalLinks) + } +} + +// applyOptions applies given opts and returns the resulting selectiveCarOptions +func applyOptions(opt ...SelectiveCarOption) selectiveCarOptions { + opts := selectiveCarOptions{ + maxTraversalLinks: math.MaxInt64, // default: traverse all + } + for _, o := range opt { + o(&opts) + } + return opts +} From 4bbb58ee890b9416f94d9dc9fbcdd3ddbfcfd2b0 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 30 Sep 2021 20:59:45 +1000 Subject: [PATCH 176/291] Defer uint64 cast of link limit until it's passed to go-ipld-prime This commit was moved from ipld/go-car@4fe92f5bc09bec6066495286019eed2ead4f0517 --- ipld/car/selectivecar.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 657a4ed227..c15eb205d6 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -276,7 +276,7 @@ func (sct *selectiveCarTraverser) traverseBlocks() error { if sct.sc.opts.maxTraversalLinks < math.MaxInt64 { prog.Budget = &traversal.Budget{ NodeBudget: math.MaxInt64, - LinkBudget: sct.sc.opts.maxTraversalLinks, + LinkBudget: int64(sct.sc.opts.maxTraversalLinks), } } err = prog.WalkAdv(nd, parsed, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) @@ -293,7 +293,7 @@ func (sct *selectiveCarTraverser) traverseBlocks() error { // This type should not be used directly by end users; it's only exposed as a // side effect of SelectiveCarOption. type selectiveCarOptions struct { - maxTraversalLinks int64 + maxTraversalLinks uint64 } // SelectiveCarOption describes an option which affects behavior when @@ -304,7 +304,7 @@ type SelectiveCarOption func(*selectiveCarOptions) // can execute before failing func MaxTraversalLinks(maxTraversalLinks uint64) SelectiveCarOption { return func(sco *selectiveCarOptions) { - sco.maxTraversalLinks = int64(maxTraversalLinks) + sco.maxTraversalLinks = maxTraversalLinks } } From 6fb151275454c61ee65de324d5fbf74ef35dfed0 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 1 Oct 2021 15:56:49 +1000 Subject: [PATCH 177/291] Move to generic `Options` like v2 Options This commit was moved from ipld/go-car@db3d3b2580563226955f2a22ef55cf57a6d287d4 --- ipld/car/selectivecar.go | 42 +++++----------------------------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index c15eb205d6..04d4b59404 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -41,7 +41,7 @@ type SelectiveCar struct { ctx context.Context dags []Dag store ReadStore - opts selectiveCarOptions + opts Options } // OnCarHeaderFunc is called during traversal when the header is created @@ -63,12 +63,12 @@ type SelectiveCarPrepared struct { // NewSelectiveCar creates a new SelectiveCar for the given car file based // a block store and set of root+selector pairs -func NewSelectiveCar(ctx context.Context, store ReadStore, dags []Dag, opts ...SelectiveCarOption) SelectiveCar { +func NewSelectiveCar(ctx context.Context, store ReadStore, dags []Dag, opts ...Option) SelectiveCar { return SelectiveCar{ ctx: ctx, store: store, dags: dags, - opts: applyOptions(opts...), + opts: ApplyOptions(opts...), } } @@ -273,10 +273,10 @@ func (sct *selectiveCarTraverser) traverseBlocks() error { LinkTargetNodePrototypeChooser: nsc, }, } - if sct.sc.opts.maxTraversalLinks < math.MaxInt64 { + if sct.sc.opts.MaxTraversalLinks < math.MaxInt64 { prog.Budget = &traversal.Budget{ NodeBudget: math.MaxInt64, - LinkBudget: int64(sct.sc.opts.maxTraversalLinks), + LinkBudget: int64(sct.sc.opts.MaxTraversalLinks), } } err = prog.WalkAdv(nd, parsed, func(traversal.Progress, ipld.Node, traversal.VisitReason) error { return nil }) @@ -286,35 +286,3 @@ func (sct *selectiveCarTraverser) traverseBlocks() error { } return nil } - -// selectiveCarOptions holds the configured options after applying a number of -// SelectiveCarOption funcs. -// -// This type should not be used directly by end users; it's only exposed as a -// side effect of SelectiveCarOption. -type selectiveCarOptions struct { - maxTraversalLinks uint64 -} - -// SelectiveCarOption describes an option which affects behavior when -// interacting with the SelectiveCar interface. -type SelectiveCarOption func(*selectiveCarOptions) - -// MaxTraversalLinks changes the allowed number of links a selector traversal -// can execute before failing -func MaxTraversalLinks(maxTraversalLinks uint64) SelectiveCarOption { - return func(sco *selectiveCarOptions) { - sco.maxTraversalLinks = maxTraversalLinks - } -} - -// applyOptions applies given opts and returns the resulting selectiveCarOptions -func applyOptions(opt ...SelectiveCarOption) selectiveCarOptions { - opts := selectiveCarOptions{ - maxTraversalLinks: math.MaxInt64, // default: traverse all - } - for _, o := range opt { - o(&opts) - } - return opts -} From 9599a55e392f16901a8d0dcb5a73308f474412eb Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 1 Oct 2021 16:28:53 +1000 Subject: [PATCH 178/291] Expose TraverseLinksOnlyOnce -> traversal.LinkVisitOnlyOnce option This commit was moved from ipld/go-car@5b6a589c70b24016e6a135b69d4820bf1c255b52 --- ipld/car/car_test.go | 152 ++--------------------- ipld/car/options.go | 56 +++++++++ ipld/car/options_test.go | 28 +++++ ipld/car/selectivecar.go | 1 + ipld/car/selectivecar_test.go | 227 ++++++++++++++++++++++++++++++++++ 5 files changed, 319 insertions(+), 145 deletions(-) create mode 100644 ipld/car/options.go create mode 100644 ipld/car/options_test.go create mode 100644 ipld/car/selectivecar_test.go diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 7a15033c49..0a9c80fedf 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -1,4 +1,4 @@ -package car +package car_test import ( "bytes" @@ -12,11 +12,7 @@ import ( format "github.com/ipfs/go-ipld-format" "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" - basicnode "github.com/ipld/go-ipld-prime/node/basic" - "github.com/ipld/go-ipld-prime/traversal/selector" - "github.com/ipld/go-ipld-prime/traversal/selector/builder" - selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" - "github.com/stretchr/testify/require" + car "github.com/ipld/go-car" ) func assertAddNodes(t *testing.T, ds format.DAGService, nds ...format.Node) { @@ -47,12 +43,12 @@ func TestRoundtrip(t *testing.T) { assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) buf := new(bytes.Buffer) - if err := WriteCar(context.Background(), dserv, []cid.Cid{nd3.Cid()}, buf); err != nil { + if err := car.WriteCar(context.Background(), dserv, []cid.Cid{nd3.Cid()}, buf); err != nil { t.Fatal(err) } bserv := dstest.Bserv() - ch, err := LoadCar(bserv.Blockstore(), buf) + ch, err := car.LoadCar(bserv.Blockstore(), buf) if err != nil { t.Fatal(err) } @@ -78,140 +74,6 @@ func TestRoundtrip(t *testing.T) { } } -func TestRoundtripSelective(t *testing.T) { - sourceBserv := dstest.Bserv() - sourceBs := sourceBserv.Blockstore() - dserv := merkledag.NewDAGService(sourceBserv) - a := merkledag.NewRawNode([]byte("aaaa")) - b := merkledag.NewRawNode([]byte("bbbb")) - c := merkledag.NewRawNode([]byte("cccc")) - - nd1 := &merkledag.ProtoNode{} - nd1.AddNodeLink("cat", a) - - nd2 := &merkledag.ProtoNode{} - nd2.AddNodeLink("first", nd1) - nd2.AddNodeLink("dog", b) - nd2.AddNodeLink("repeat", nd1) - - nd3 := &merkledag.ProtoNode{} - nd3.AddNodeLink("second", nd2) - nd3.AddNodeLink("bear", c) - - assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) - - ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) - - // the graph assembled above looks as follows, in order: - // nd3 -> [c, nd2 -> [nd1 -> a, b, nd1 -> a]] - // this selector starts at n3, and traverses a link at index 1 (nd2, the second link, zero indexed) - // it then recursively traverses all of its children - // the only node skipped is 'c' -- link at index 0 immediately below nd3 - // the purpose is simply to show we are not writing the entire merkledag underneath - // nd3 - selector := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { - efsb.Insert("Links", - ssb.ExploreIndex(1, ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())))) - }).Node() - - sc := NewSelectiveCar(context.Background(), sourceBs, []Dag{{Root: nd3.Cid(), Selector: selector}}) - - // write car in one step - buf := new(bytes.Buffer) - blockCount := 0 - var oneStepBlocks []Block - err := sc.Write(buf, func(block Block) error { - oneStepBlocks = append(oneStepBlocks, block) - blockCount++ - return nil - }) - require.Equal(t, blockCount, 5) - require.NoError(t, err) - - // create a new builder for two-step write - sc2 := NewSelectiveCar(context.Background(), sourceBs, []Dag{{Root: nd3.Cid(), Selector: selector}}) - - // write car in two steps - var twoStepBlocks []Block - scp, err := sc2.Prepare(func(block Block) error { - twoStepBlocks = append(twoStepBlocks, block) - return nil - }) - require.NoError(t, err) - buf2 := new(bytes.Buffer) - err = scp.Dump(buf2) - require.NoError(t, err) - - // verify preparation step correctly assesed length and blocks - require.Equal(t, scp.Size(), uint64(buf.Len())) - require.Equal(t, len(scp.Cids()), blockCount) - - // verify equal data written by both methods - require.Equal(t, buf.Bytes(), buf2.Bytes()) - - // verify equal blocks were passed to user block hook funcs - require.Equal(t, oneStepBlocks, twoStepBlocks) - - // readout car and verify contents - bserv := dstest.Bserv() - ch, err := LoadCar(bserv.Blockstore(), buf) - require.NoError(t, err) - require.Equal(t, len(ch.Roots), 1) - - require.True(t, ch.Roots[0].Equals(nd3.Cid())) - - bs := bserv.Blockstore() - for _, nd := range []format.Node{a, b, nd1, nd2, nd3} { - has, err := bs.Has(nd.Cid()) - require.NoError(t, err) - require.True(t, has) - } - - for _, nd := range []format.Node{c} { - has, err := bs.Has(nd.Cid()) - require.NoError(t, err) - require.False(t, has) - } -} - -func TestLinkLimitSelective(t *testing.T) { - sourceBserv := dstest.Bserv() - sourceBs := sourceBserv.Blockstore() - dserv := merkledag.NewDAGService(sourceBserv) - a := merkledag.NewRawNode([]byte("aaaa")) - b := merkledag.NewRawNode([]byte("bbbb")) - c := merkledag.NewRawNode([]byte("cccc")) - - nd1 := &merkledag.ProtoNode{} - nd1.AddNodeLink("cat", a) - - nd2 := &merkledag.ProtoNode{} - nd2.AddNodeLink("first", nd1) - nd2.AddNodeLink("dog", b) - nd2.AddNodeLink("repeat", nd1) - - nd3 := &merkledag.ProtoNode{} - nd3.AddNodeLink("second", nd2) - nd3.AddNodeLink("bear", c) - - assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) - - sc := NewSelectiveCar(context.Background(), - sourceBs, - []Dag{{Root: nd3.Cid(), Selector: selectorparse.CommonSelector_ExploreAllRecursively}}, - MaxTraversalLinks(2)) - - buf := new(bytes.Buffer) - blockCount := 0 - err := sc.Write(buf, func(block Block) error { - blockCount++ - return nil - }) - require.Equal(t, blockCount, 3) // root + 2 - require.Error(t, err) - require.Regexp(t, "^traversal budget exceeded: budget for links reached zero while on path .*", err) -} - func TestEOFHandling(t *testing.T) { // fixture is a clean single-block, single-root CAR fixture, err := hex.DecodeString("3aa265726f6f747381d82a58250001711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80b6776657273696f6e012c01711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80ba165646f646779f5") @@ -219,8 +81,8 @@ func TestEOFHandling(t *testing.T) { t.Fatal(err) } - load := func(t *testing.T, byts []byte) *CarReader { - cr, err := NewCarReader(bytes.NewReader(byts)) + load := func(t *testing.T, byts []byte) *car.CarReader { + cr, err := car.NewCarReader(bytes.NewReader(byts)) if err != nil { t.Fatal(err) } @@ -333,7 +195,7 @@ func TestBadHeaders(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = NewCarReader(bytes.NewReader(fixture)) + _, err = car.NewCarReader(bytes.NewReader(fixture)) return err } diff --git a/ipld/car/options.go b/ipld/car/options.go new file mode 100644 index 0000000000..d82bd44e00 --- /dev/null +++ b/ipld/car/options.go @@ -0,0 +1,56 @@ +package car + +import "math" + +// Options holds the configured options after applying a number of +// Option funcs. +// +// This type should not be used directly by end users; it's only exposed as a +// side effect of Option. +type Options struct { + TraverseLinksOnlyOnce bool + MaxTraversalLinks uint64 +} + +// Option describes an option which affects behavior when +// interacting with the interface. +type Option func(*Options) + +// TraverseLinksOnlyOnce prevents the traversal engine from repeatedly visiting +// the same links more than once. +// +// This can be an efficient strategy for an exhaustive selector where it's known +// that repeat visits won't impact the completeness of execution. However it +// should be used with caution with most other selectors as repeat visits of +// links for different reasons during selector execution can be valid and +// necessary to perform full traversal. +func TraverseLinksOnlyOnce() Option { + return func(sco *Options) { + sco.TraverseLinksOnlyOnce = true + } +} + +// MaxTraversalLinks changes the allowed number of links a selector traversal +// can execute before failing. +// +// Note that setting this option may cause an error to be returned from selector +// execution when building a SelectiveCar. +func MaxTraversalLinks(MaxTraversalLinks uint64) Option { + return func(sco *Options) { + sco.MaxTraversalLinks = MaxTraversalLinks + } +} + +// ApplyOptions applies given opts and returns the resulting Options. +// This function should not be used directly by end users; it's only exposed as a +// side effect of Option. +func ApplyOptions(opt ...Option) Options { + opts := Options{ + TraverseLinksOnlyOnce: false, // default: recurse until exhausted + MaxTraversalLinks: math.MaxInt64, // default: traverse all + } + for _, o := range opt { + o(&opts) + } + return opts +} diff --git a/ipld/car/options_test.go b/ipld/car/options_test.go new file mode 100644 index 0000000000..34f623f524 --- /dev/null +++ b/ipld/car/options_test.go @@ -0,0 +1,28 @@ +package car_test + +import ( + "math" + "testing" + + car "github.com/ipld/go-car" + "github.com/stretchr/testify/require" +) + +func TestApplyOptions_SetsExpectedDefaults(t *testing.T) { + require.Equal(t, car.Options{ + MaxTraversalLinks: math.MaxInt64, + TraverseLinksOnlyOnce: false, + }, car.ApplyOptions()) +} + +func TestApplyOptions_AppliesOptions(t *testing.T) { + require.Equal(t, + car.Options{ + MaxTraversalLinks: 123, + TraverseLinksOnlyOnce: true, + }, + car.ApplyOptions( + car.MaxTraversalLinks(123), + car.TraverseLinksOnlyOnce(), + )) +} diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 04d4b59404..6e5c5438fa 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -271,6 +271,7 @@ func (sct *selectiveCarTraverser) traverseBlocks() error { Ctx: sct.sc.ctx, LinkSystem: sct.lsys, LinkTargetNodePrototypeChooser: nsc, + LinkVisitOnlyOnce: sct.sc.opts.TraverseLinksOnlyOnce, }, } if sct.sc.opts.MaxTraversalLinks < math.MaxInt64 { diff --git a/ipld/car/selectivecar_test.go b/ipld/car/selectivecar_test.go new file mode 100644 index 0000000000..387203ff83 --- /dev/null +++ b/ipld/car/selectivecar_test.go @@ -0,0 +1,227 @@ +package car_test + +import ( + "bytes" + "context" + "testing" + + blocks "github.com/ipfs/go-block-format" + cid "github.com/ipfs/go-cid" + format "github.com/ipfs/go-ipld-format" + "github.com/ipfs/go-merkledag" + dstest "github.com/ipfs/go-merkledag/test" + car "github.com/ipld/go-car" + basicnode "github.com/ipld/go-ipld-prime/node/basic" + "github.com/ipld/go-ipld-prime/traversal/selector" + "github.com/ipld/go-ipld-prime/traversal/selector/builder" + selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" + "github.com/stretchr/testify/require" +) + +func TestRoundtripSelective(t *testing.T) { + sourceBserv := dstest.Bserv() + sourceBs := sourceBserv.Blockstore() + dserv := merkledag.NewDAGService(sourceBserv) + a := merkledag.NewRawNode([]byte("aaaa")) + b := merkledag.NewRawNode([]byte("bbbb")) + c := merkledag.NewRawNode([]byte("cccc")) + + nd1 := &merkledag.ProtoNode{} + nd1.AddNodeLink("cat", a) + + nd2 := &merkledag.ProtoNode{} + nd2.AddNodeLink("first", nd1) + nd2.AddNodeLink("dog", b) + nd2.AddNodeLink("repeat", nd1) + + nd3 := &merkledag.ProtoNode{} + nd3.AddNodeLink("second", nd2) + nd3.AddNodeLink("bear", c) + + assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) + + ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype.Any) + + // the graph assembled above looks as follows, in order: + // nd3 -> [c, nd2 -> [nd1 -> a, b, nd1 -> a]] + // this selector starts at n3, and traverses a link at index 1 (nd2, the second link, zero indexed) + // it then recursively traverses all of its children + // the only node skipped is 'c' -- link at index 0 immediately below nd3 + // the purpose is simply to show we are not writing the entire merkledag underneath + // nd3 + selector := ssb.ExploreFields(func(efsb builder.ExploreFieldsSpecBuilder) { + efsb.Insert("Links", + ssb.ExploreIndex(1, ssb.ExploreRecursive(selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge())))) + }).Node() + + sc := car.NewSelectiveCar(context.Background(), sourceBs, []car.Dag{{Root: nd3.Cid(), Selector: selector}}) + + // write car in one step + buf := new(bytes.Buffer) + blockCount := 0 + var oneStepBlocks []car.Block + err := sc.Write(buf, func(block car.Block) error { + oneStepBlocks = append(oneStepBlocks, block) + blockCount++ + return nil + }) + require.Equal(t, blockCount, 5) + require.NoError(t, err) + + // create a new builder for two-step write + sc2 := car.NewSelectiveCar(context.Background(), sourceBs, []car.Dag{{Root: nd3.Cid(), Selector: selector}}) + + // write car in two steps + var twoStepBlocks []car.Block + scp, err := sc2.Prepare(func(block car.Block) error { + twoStepBlocks = append(twoStepBlocks, block) + return nil + }) + require.NoError(t, err) + buf2 := new(bytes.Buffer) + err = scp.Dump(buf2) + require.NoError(t, err) + + // verify preparation step correctly assesed length and blocks + require.Equal(t, scp.Size(), uint64(buf.Len())) + require.Equal(t, len(scp.Cids()), blockCount) + + // verify equal data written by both methods + require.Equal(t, buf.Bytes(), buf2.Bytes()) + + // verify equal blocks were passed to user block hook funcs + require.Equal(t, oneStepBlocks, twoStepBlocks) + + // readout car and verify contents + bserv := dstest.Bserv() + ch, err := car.LoadCar(bserv.Blockstore(), buf) + require.NoError(t, err) + require.Equal(t, len(ch.Roots), 1) + + require.True(t, ch.Roots[0].Equals(nd3.Cid())) + + bs := bserv.Blockstore() + for _, nd := range []format.Node{a, b, nd1, nd2, nd3} { + has, err := bs.Has(nd.Cid()) + require.NoError(t, err) + require.True(t, has) + } + + for _, nd := range []format.Node{c} { + has, err := bs.Has(nd.Cid()) + require.NoError(t, err) + require.False(t, has) + } +} + +func TestNoLinkRepeatSelective(t *testing.T) { + sourceBserv := dstest.Bserv() + sourceBs := countingReadStore{bs: sourceBserv.Blockstore()} + dserv := merkledag.NewDAGService(sourceBserv) + a := merkledag.NewRawNode([]byte("aaaa")) + b := merkledag.NewRawNode([]byte("bbbb")) + c := merkledag.NewRawNode([]byte("cccc")) + + nd1 := &merkledag.ProtoNode{} + nd1.AddNodeLink("cat", a) + + nd2 := &merkledag.ProtoNode{} + nd2.AddNodeLink("first", nd1) + nd2.AddNodeLink("dog", b) + nd2.AddNodeLink("repeat", nd1) + + nd3 := &merkledag.ProtoNode{} + nd3.AddNodeLink("second", nd2) + nd3.AddNodeLink("bear", c) + nd3.AddNodeLink("bearagain1", c) + nd3.AddNodeLink("bearagain2", c) + nd3.AddNodeLink("bearagain3", c) + + assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) + + t.Run("TraverseLinksOnlyOnce off", func(t *testing.T) { + sourceBs.count = 0 + sc := car.NewSelectiveCar(context.Background(), + &sourceBs, + []car.Dag{{Root: nd3.Cid(), Selector: selectorparse.CommonSelector_ExploreAllRecursively}}, + ) + + buf := new(bytes.Buffer) + blockCount := 0 + err := sc.Write(buf, func(block car.Block) error { + blockCount++ + return nil + }) + require.Equal(t, blockCount, 6) + require.Equal(t, sourceBs.count, 11) // with TraverseLinksOnlyOnce off, we expect repeat block visits because our DAG has repeat links + require.NoError(t, err) + }) + + t.Run("TraverseLinksOnlyOnce on", func(t *testing.T) { + sourceBs.count = 0 + + sc := car.NewSelectiveCar(context.Background(), + &sourceBs, + []car.Dag{{Root: nd3.Cid(), Selector: selectorparse.CommonSelector_ExploreAllRecursively}}, + car.TraverseLinksOnlyOnce(), + ) + + buf := new(bytes.Buffer) + blockCount := 0 + err := sc.Write(buf, func(block car.Block) error { + blockCount++ + return nil + }) + require.Equal(t, blockCount, 6) + require.Equal(t, sourceBs.count, 6) // only 6 blocks to load, no duplicate loading expected + require.NoError(t, err) + }) +} + +func TestLinkLimitSelective(t *testing.T) { + sourceBserv := dstest.Bserv() + sourceBs := sourceBserv.Blockstore() + dserv := merkledag.NewDAGService(sourceBserv) + a := merkledag.NewRawNode([]byte("aaaa")) + b := merkledag.NewRawNode([]byte("bbbb")) + c := merkledag.NewRawNode([]byte("cccc")) + + nd1 := &merkledag.ProtoNode{} + nd1.AddNodeLink("cat", a) + + nd2 := &merkledag.ProtoNode{} + nd2.AddNodeLink("first", nd1) + nd2.AddNodeLink("dog", b) + nd2.AddNodeLink("repeat", nd1) + + nd3 := &merkledag.ProtoNode{} + nd3.AddNodeLink("second", nd2) + nd3.AddNodeLink("bear", c) + + assertAddNodes(t, dserv, a, b, c, nd1, nd2, nd3) + + sc := car.NewSelectiveCar(context.Background(), + sourceBs, + []car.Dag{{Root: nd3.Cid(), Selector: selectorparse.CommonSelector_ExploreAllRecursively}}, + car.MaxTraversalLinks(2)) + + buf := new(bytes.Buffer) + blockCount := 0 + err := sc.Write(buf, func(block car.Block) error { + blockCount++ + return nil + }) + require.Equal(t, blockCount, 3) // root + 2 + require.Error(t, err) + require.Regexp(t, "^traversal budget exceeded: budget for links reached zero while on path .*", err) +} + +type countingReadStore struct { + bs car.ReadStore + count int +} + +func (rs *countingReadStore) Get(c cid.Cid) (blocks.Block, error) { + rs.count++ + return rs.bs.Get(c) +} From 9c0ff822e5d04da9b3164f88c2ff42f0da135f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 30 Sep 2021 11:07:37 +0100 Subject: [PATCH 179/291] blockstore: OpenReadWrite should not modify if it refuses to resume If OpenReadWrite sees a good reason to refuse to resume, such as the provided roots not matching what's on disk, it should not truncate or un-finalize the file on disk. Add a test that exercises that edge case, and also verifies that the file isn't modified. To fix the problem, carefully move truncation and un-finalization past the point where we've checked the existing header. Note that we may still leave a broken file if we encounter an error later on, such as when writing to the file or building the index. If we want those to not break the input file we'll need bigger changes, such as writing to a temporary file and atomically replacing the final file at the very end. For now, at least we can avoid a common breakage case. Before the fix, the test would fail: Error Trace: readwrite_test.go:621 Error: Not equal: expected: 521708 actual : 479958 Test: TestReadWriteResumptionMismatchingRootsIsError Fixes #247. This commit was moved from ipld/go-car@4e0a1fa04c8f7e8fc69176007bc07c25ef7ce9c9 --- ipld/car/v2/blockstore/readwrite.go | 32 +++++++++++++----------- ipld/car/v2/blockstore/readwrite_test.go | 26 +++++++++++++++++-- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index b98c58d4be..0dda5a0bd9 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -182,27 +182,13 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { "`WithDataPadding` option must match the padding on file. "+ "Expected padding value of %v but got %v", wantPadding, gotPadding, ) - } else if headerInFile.DataSize != 0 { - // If header in file contains the size of car v1, then the index is most likely present. - // Since we will need to re-generate the index, as the one in file is flattened, truncate - // the file so that the Readonly.backing has the right set of bytes to deal with. - // This effectively means resuming from a finalized file will wipe its index even if there - // are no blocks put unless the user calls finalize. - if err := b.f.Truncate(int64(headerInFile.DataOffset + headerInFile.DataSize)); err != nil { - return err - } - } else { + } else if headerInFile.DataSize == 0 { // If CARv1 size is zero, since CARv1 offset wasn't, then the CARv2 header was // most-likely partially written. Since we write the header last in Finalize then the // file most-likely contains the index and we cannot know where it starts, therefore // can't resume. return errors.New("corrupt CARv2 header; cannot resume from file") } - // Now that CARv2 header is present on file, clear it to avoid incorrect size and offset in - // header in case blocksotre is closed without finalization and is resumed from. - if err := b.unfinalize(); err != nil { - return err - } } // Use the given CARv1 padding to instantiate the CARv1 reader on file. @@ -217,6 +203,22 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { return errors.New("cannot resume on file with mismatching data header") } + if headerInFile.DataOffset != 0 { + // If header in file contains the size of car v1, then the index is most likely present. + // Since we will need to re-generate the index, as the one in file is flattened, truncate + // the file so that the Readonly.backing has the right set of bytes to deal with. + // This effectively means resuming from a finalized file will wipe its index even if there + // are no blocks put unless the user calls finalize. + if err := b.f.Truncate(int64(headerInFile.DataOffset + headerInFile.DataSize)); err != nil { + return err + } + } + // Now that CARv2 header is present on file, clear it to avoid incorrect size and offset in + // header in case blocksotre is closed without finalization and is resumed from. + if err := b.unfinalize(); err != nil { + return fmt.Errorf("could not un-finalize: %w", err) + } + // TODO See how we can reduce duplicate code here. // The code here comes from car.GenerateIndex. // Copied because we need to populate an insertindex, not a sorted index. diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index b7da6ec837..d58b6de3c8 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -600,17 +600,39 @@ func TestReadWriteResumptionFromNonV2FileIsError(t *testing.T) { require.Nil(t, subject) } +func TestReadWriteResumptionMismatchingRootsIsError(t *testing.T) { + tmpPath := requireTmpCopy(t, "../testdata/sample-wrapped-v2.car") + + origContent, err := ioutil.ReadFile(tmpPath) + require.NoError(t, err) + + badRoot, err := cid.NewPrefixV1(cid.Raw, multihash.SHA2_256).Sum([]byte("bad root")) + require.NoError(t, err) + + subject, err := blockstore.OpenReadWrite(tmpPath, []cid.Cid{badRoot}) + require.EqualError(t, err, "cannot resume on file with mismatching data header") + require.Nil(t, subject) + + newContent, err := ioutil.ReadFile(tmpPath) + require.NoError(t, err) + + // Expect the bad file to be left untouched; check the size first. + // If the sizes mismatch, printing a huge diff would not help us. + require.Equal(t, len(origContent), len(newContent)) + require.Equal(t, origContent, newContent) +} + func requireTmpCopy(t *testing.T, src string) string { srcF, err := os.Open(src) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, srcF.Close()) }) + defer func() { require.NoError(t, srcF.Close()) }() stats, err := srcF.Stat() require.NoError(t, err) dst := filepath.Join(t.TempDir(), stats.Name()) dstF, err := os.Create(dst) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, dstF.Close()) }) + defer func() { require.NoError(t, dstF.Close()) }() _, err = io.Copy(dstF, srcF) require.NoError(t, err) From 501b8412572ef72018e43cc8fd1baf8fa340f7f8 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 30 Sep 2021 16:43:37 +0100 Subject: [PATCH 180/291] Implement API to allow replacing root CIDs in a CARv1 or CARv2 Implement an API that allows a caller to replace root CIDs in an existing CAR file, may it be v1 or v2, as long as the resulting serialized header is of identical size to the existing header. Assert that the new API works in a variety of CARv1 and CARv2 files along with failure scenarios. Fixes #245 This commit was moved from ipld/go-car@f4378127a67f1bf4d616ef83a1e5a4d841a9478b --- ipld/car/v2/writer.go | 100 +++++++++++++++++++++++++ ipld/car/v2/writer_test.go | 146 +++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 8ed5756fe5..72675cb27e 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -1,12 +1,15 @@ package car import ( + "bytes" "errors" "fmt" "io" "os" + "github.com/ipfs/go-cid" "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1" internalio "github.com/ipld/go-car/v2/internal/io" ) @@ -210,3 +213,100 @@ func AttachIndex(path string, idx index.Index, offset uint64) error { indexWriter := internalio.NewOffsetWriter(out, int64(offset)) return index.WriteTo(idx, indexWriter) } + +// ReplaceRootsInFile replaces the root CIDs in CAR file at given path with the given roots. +// This function accepts both CARv1 and CARv2 files. +// +// Note that the roots are only replaced if their total serialized size exactly matches the total +// serialized size of existing roots in CAR file. +func ReplaceRootsInFile(path string, roots []cid.Cid) (err error) { + f, err := os.OpenFile(path, os.O_RDWR, 0o666) + if err != nil { + return err + } + defer func() { + // Close file and override return error type if it is nil. + if cerr := f.Close(); err == nil { + err = cerr + } + }() + + // Read header or pragma; note that both are a valid CARv1 header. + header, err := carv1.ReadHeader(f) + if err != nil { + return err + } + + var currentSize int64 + var newHeaderOffset int64 + switch header.Version { + case 1: + // When the given file is a CARv1 : + // 1. The offset at which the new header should be written is zero (newHeaderOffset = 0) + // 2. The current header size is equal to the number of bytes read, and + // + // Note that we explicitly avoid using carv1.HeaderSize to determine the current header size. + // This is based on the fact that carv1.ReadHeader does not read any extra bytes. + // Therefore, we can avoid extra allocations of carv1.HeaderSize to determine size by simply + // counting the bytes read so far. + currentSize, err = f.Seek(0, io.SeekCurrent) + if err != nil { + return err + } + case 2: + // When the given file is a CARv2 : + // 1. The offset at which the new header should be written is carv2.Header.DataOffset + // 2. The inner CARv1 header size is equal to the number of bytes read minus carv2.Header.DataOffset + var v2h Header + if _, err = v2h.ReadFrom(f); err != nil { + return err + } + newHeaderOffset = int64(v2h.DataOffset) + if _, err = f.Seek(newHeaderOffset, io.SeekStart); err != nil { + return err + } + var innerV1Header *carv1.CarHeader + innerV1Header, err = carv1.ReadHeader(f) + if err != nil { + return err + } + if innerV1Header.Version != 1 { + err = fmt.Errorf("invalid data payload header: expected version 1, got %d", innerV1Header.Version) + } + var readSoFar int64 + readSoFar, err = f.Seek(0, io.SeekCurrent) + if err != nil { + return err + } + currentSize = readSoFar - newHeaderOffset + default: + err = fmt.Errorf("invalid car version: %d", header.Version) + return err + } + + newHeader := &carv1.CarHeader{ + Roots: roots, + Version: 1, + } + // Serialize the new header straight up instead of using carv1.HeaderSize. + // Because, carv1.HeaderSize serialises it to calculate size anyway. + // By serializing straight up we get the replacement bytes and size. + // Otherwise, we end up serializing the new header twice: + // once through carv1.HeaderSize, and + // once to write it out. + var buf bytes.Buffer + if err = carv1.WriteHeader(newHeader, &buf); err != nil { + return err + } + // Assert the header sizes are consistent. + newSize := int64(buf.Len()) + if currentSize != newSize { + return fmt.Errorf("current header size (%d) must match replacement header size (%d)", currentSize, newSize) + } + // Seek to the offset at which the new header should be written. + if _, err = f.Seek(newHeaderOffset, io.SeekStart); err != nil { + return err + } + _, err = f.Write(buf.Bytes()) + return err +} diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index c29b433973..11b5ff1296 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -131,3 +131,149 @@ func assertAddNodes(t *testing.T, adder format.NodeAdder, nds ...format.Node) { assert.NoError(t, adder.Add(context.Background(), nd)) } } + +func TestReplaceRootsInFile(t *testing.T) { + tests := []struct { + name string + path string + roots []cid.Cid + wantErrMsg string + }{ + { + name: "CorruptPragmaIsRejected", + path: "testdata/sample-corrupt-pragma.car", + wantErrMsg: "unexpected EOF", + }, + { + name: "CARv42IsRejected", + path: "testdata/sample-rootless-v42.car", + wantErrMsg: "invalid car version: 42", + }, + { + name: "CARv1RootsOfDifferentSizeAreNotReplaced", + path: "testdata/sample-v1.car", + wantErrMsg: "current header size (61) must match replacement header size (18)", + }, + { + name: "CARv2RootsOfDifferentSizeAreNotReplaced", + path: "testdata/sample-wrapped-v2.car", + wantErrMsg: "current header size (61) must match replacement header size (18)", + }, + { + name: "CARv1NonEmptyRootsOfDifferentSizeAreNotReplaced", + path: "testdata/sample-v1.car", + roots: []cid.Cid{requireDecodedCid(t, "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n")}, + wantErrMsg: "current header size (61) must match replacement header size (57)", + }, + { + name: "CARv1ZeroLenNonEmptyRootsOfDifferentSizeAreNotReplaced", + path: "testdata/sample-v1-with-zero-len-section.car", + roots: []cid.Cid{merkledag.NewRawNode([]byte("fish")).Cid()}, + wantErrMsg: "current header size (61) must match replacement header size (59)", + }, + { + name: "CARv2NonEmptyRootsOfDifferentSizeAreNotReplaced", + path: "testdata/sample-wrapped-v2.car", + roots: []cid.Cid{merkledag.NewRawNode([]byte("fish")).Cid()}, + wantErrMsg: "current header size (61) must match replacement header size (59)", + }, + { + name: "CARv2IndexlessNonEmptyRootsOfDifferentSizeAreNotReplaced", + path: "testdata/sample-v2-indexless.car", + roots: []cid.Cid{merkledag.NewRawNode([]byte("fish")).Cid()}, + wantErrMsg: "current header size (61) must match replacement header size (59)", + }, + { + name: "CARv1SameSizeRootsAreReplaced", + path: "testdata/sample-v1.car", + roots: []cid.Cid{requireDecodedCid(t, "bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5od")}, + }, + { + name: "CARv2SameSizeRootsAreReplaced", + path: "testdata/sample-wrapped-v2.car", + roots: []cid.Cid{requireDecodedCid(t, "bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oi")}, + }, + { + name: "CARv2IndexlessSameSizeRootsAreReplaced", + path: "testdata/sample-v2-indexless.car", + roots: []cid.Cid{requireDecodedCid(t, "bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oi")}, + }, + { + name: "CARv1ZeroLenSameSizeRootsAreReplaced", + path: "testdata/sample-v1-with-zero-len-section.car", + roots: []cid.Cid{requireDecodedCid(t, "bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5o5")}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Make a copy of input files to preserve original for comparison. + // This also avoids modification files in testdata. + tmpCopy := requireTmpCopy(t, tt.path) + err := ReplaceRootsInFile(tmpCopy, tt.roots) + if tt.wantErrMsg != "" { + require.EqualError(t, err, tt.wantErrMsg) + return + } + require.NoError(t, err) + + original, err := os.Open(tt.path) + require.NoError(t, err) + defer func() { require.NoError(t, original.Close()) }() + + target, err := os.Open(tmpCopy) + require.NoError(t, err) + defer func() { require.NoError(t, target.Close()) }() + + // Assert file size has not changed. + wantStat, err := original.Stat() + require.NoError(t, err) + gotStat, err := target.Stat() + require.NoError(t, err) + require.Equal(t, wantStat.Size(), gotStat.Size()) + + wantReader, err := NewBlockReader(original, ZeroLengthSectionAsEOF(true)) + require.NoError(t, err) + gotReader, err := NewBlockReader(target, ZeroLengthSectionAsEOF(true)) + require.NoError(t, err) + + // Assert roots are replaced. + require.Equal(t, tt.roots, gotReader.Roots) + + // Assert data blocks are identical. + for { + wantNext, wantErr := wantReader.Next() + gotNext, gotErr := gotReader.Next() + if wantErr == io.EOF { + require.Equal(t, io.EOF, gotErr) + break + } + require.NoError(t, wantErr) + require.NoError(t, gotErr) + require.Equal(t, wantNext, gotNext) + } + }) + } +} + +func requireDecodedCid(t *testing.T, s string) cid.Cid { + decoded, err := cid.Decode(s) + require.NoError(t, err) + return decoded +} + +func requireTmpCopy(t *testing.T, src string) string { + srcF, err := os.Open(src) + require.NoError(t, err) + defer func() { require.NoError(t, srcF.Close()) }() + stats, err := srcF.Stat() + require.NoError(t, err) + + dst := filepath.Join(t.TempDir(), stats.Name()) + dstF, err := os.Create(dst) + require.NoError(t, err) + defer func() { require.NoError(t, dstF.Close()) }() + + _, err = io.Copy(dstF, srcF) + require.NoError(t, err) + return dst +} From 901cdc0ae4a3722ad8b0ef3d89b738a376bc3005 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 1 Oct 2021 19:49:00 +1000 Subject: [PATCH 181/291] Make Options private This commit was moved from ipld/go-car@3ff3c49cdb50241006e9124a88aa87acdf59a072 --- ipld/car/options.go | 17 ++++++----------- ipld/car/options_test.go | 15 +++++++-------- ipld/car/selectivecar.go | 4 ++-- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/ipld/car/options.go b/ipld/car/options.go index d82bd44e00..4eef80696c 100644 --- a/ipld/car/options.go +++ b/ipld/car/options.go @@ -4,17 +4,14 @@ import "math" // Options holds the configured options after applying a number of // Option funcs. -// -// This type should not be used directly by end users; it's only exposed as a -// side effect of Option. -type Options struct { +type options struct { TraverseLinksOnlyOnce bool MaxTraversalLinks uint64 } // Option describes an option which affects behavior when // interacting with the interface. -type Option func(*Options) +type Option func(*options) // TraverseLinksOnlyOnce prevents the traversal engine from repeatedly visiting // the same links more than once. @@ -25,7 +22,7 @@ type Option func(*Options) // links for different reasons during selector execution can be valid and // necessary to perform full traversal. func TraverseLinksOnlyOnce() Option { - return func(sco *Options) { + return func(sco *options) { sco.TraverseLinksOnlyOnce = true } } @@ -36,16 +33,14 @@ func TraverseLinksOnlyOnce() Option { // Note that setting this option may cause an error to be returned from selector // execution when building a SelectiveCar. func MaxTraversalLinks(MaxTraversalLinks uint64) Option { - return func(sco *Options) { + return func(sco *options) { sco.MaxTraversalLinks = MaxTraversalLinks } } // ApplyOptions applies given opts and returns the resulting Options. -// This function should not be used directly by end users; it's only exposed as a -// side effect of Option. -func ApplyOptions(opt ...Option) Options { - opts := Options{ +func applyOptions(opt ...Option) options { + opts := options{ TraverseLinksOnlyOnce: false, // default: recurse until exhausted MaxTraversalLinks: math.MaxInt64, // default: traverse all } diff --git a/ipld/car/options_test.go b/ipld/car/options_test.go index 34f623f524..250c672037 100644 --- a/ipld/car/options_test.go +++ b/ipld/car/options_test.go @@ -1,28 +1,27 @@ -package car_test +package car import ( "math" "testing" - car "github.com/ipld/go-car" "github.com/stretchr/testify/require" ) func TestApplyOptions_SetsExpectedDefaults(t *testing.T) { - require.Equal(t, car.Options{ + require.Equal(t, options{ MaxTraversalLinks: math.MaxInt64, TraverseLinksOnlyOnce: false, - }, car.ApplyOptions()) + }, applyOptions()) } func TestApplyOptions_AppliesOptions(t *testing.T) { require.Equal(t, - car.Options{ + options{ MaxTraversalLinks: 123, TraverseLinksOnlyOnce: true, }, - car.ApplyOptions( - car.MaxTraversalLinks(123), - car.TraverseLinksOnlyOnce(), + applyOptions( + MaxTraversalLinks(123), + TraverseLinksOnlyOnce(), )) } diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 6e5c5438fa..9b5bd8cef4 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -41,7 +41,7 @@ type SelectiveCar struct { ctx context.Context dags []Dag store ReadStore - opts Options + opts options } // OnCarHeaderFunc is called during traversal when the header is created @@ -68,7 +68,7 @@ func NewSelectiveCar(ctx context.Context, store ReadStore, dags []Dag, opts ...O ctx: ctx, store: store, dags: dags, - opts: ApplyOptions(opts...), + opts: applyOptions(opts...), } } From c27f485f406a94af12d475dafc0b954ec4bc31f9 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 1 Oct 2021 20:05:30 +1000 Subject: [PATCH 182/291] fix: doc typos This commit was moved from ipld/go-car@11922368e3a4cc435b5a1bdee65b5e3c293b9d76 --- ipld/car/options.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipld/car/options.go b/ipld/car/options.go index 4eef80696c..e317f9cc9e 100644 --- a/ipld/car/options.go +++ b/ipld/car/options.go @@ -2,7 +2,7 @@ package car import "math" -// Options holds the configured options after applying a number of +// options holds the configured options after applying a number of // Option funcs. type options struct { TraverseLinksOnlyOnce bool @@ -38,7 +38,7 @@ func MaxTraversalLinks(MaxTraversalLinks uint64) Option { } } -// ApplyOptions applies given opts and returns the resulting Options. +// applyOptions applies given opts and returns the resulting options. func applyOptions(opt ...Option) options { opts := options{ TraverseLinksOnlyOnce: false, // default: recurse until exhausted From 31cd0742970e47a1148caa1b44e015d29556dbdc Mon Sep 17 00:00:00 2001 From: Will Date: Sat, 16 Oct 2021 16:26:42 -0700 Subject: [PATCH 183/291] forEach iterates over index in stable order (#258) * forEach iterates over index in stable order This commit was moved from ipld/go-car@f9c3b063844cc50478c41bea1e407c2d40a03696 --- ipld/car/v2/index/index.go | 2 +- ipld/car/v2/index/indexsorted.go | 8 ++++++- ipld/car/v2/index/mhindexsorted.go | 8 ++++++- ipld/car/v2/index/mhindexsorted_test.go | 29 +++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 8e447d0fe5..998a17a0bb 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -87,7 +87,7 @@ type ( // An index may contain multiple offsets corresponding to the same multihash, e.g. via duplicate blocks. // In such cases, the given function may be called multiple times with the same multhihash but different offset. // - // The order of calls to the given function is entirely index-specific. + // The order of calls to the given function is deterministic, but entirely index-specific. ForEach(func(multihash.Multihash, uint64) error) error } ) diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 6b6c5a6805..86994dd8b8 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -235,7 +235,13 @@ func (m *multiWidthIndex) Load(items []Record) error { } func (m *multiWidthIndex) forEachDigest(f func(digest []byte, offset uint64) error) error { - for _, swi := range *m { + sizes := make([]uint32, 0, len(*m)) + for k := range *m { + sizes = append(sizes, k) + } + sort.Slice(sizes, func(i, j int) bool { return sizes[i] < sizes[j] }) + for _, s := range sizes { + swi := (*m)[s] if err := swi.forEachDigest(f); err != nil { return err } diff --git a/ipld/car/v2/index/mhindexsorted.go b/ipld/car/v2/index/mhindexsorted.go index f81e3a942b..e3cae3d073 100644 --- a/ipld/car/v2/index/mhindexsorted.go +++ b/ipld/car/v2/index/mhindexsorted.go @@ -157,7 +157,13 @@ func (m *MultihashIndexSorted) GetAll(cid cid.Cid, f func(uint64) bool) error { // ForEach calls f for every multihash and its associated offset stored by this index. func (m *MultihashIndexSorted) ForEach(f func(mh multihash.Multihash, offset uint64) error) error { - for _, mwci := range *m { + sizes := make([]uint64, 0, len(*m)) + for k := range *m { + sizes = append(sizes, k) + } + sort.Slice(sizes, func(i, j int) bool { return sizes[i] < sizes[j] }) + for _, s := range sizes { + mwci := (*m)[s] if err := mwci.forEach(f); err != nil { return err } diff --git a/ipld/car/v2/index/mhindexsorted_test.go b/ipld/car/v2/index/mhindexsorted_test.go index b5ef7b89b7..e02ba0599b 100644 --- a/ipld/car/v2/index/mhindexsorted_test.go +++ b/ipld/car/v2/index/mhindexsorted_test.go @@ -46,6 +46,35 @@ func TestMultiWidthCodedIndex_MarshalUnmarshal(t *testing.T) { requireContainsAll(t, umSubject, records) } +func TestMultiWidthCodedIndex_StableIterate(t *testing.T) { + rng := rand.New(rand.NewSource(1414)) + records := generateIndexRecords(t, multihash.SHA2_256, rng) + records = append(records, generateIndexRecords(t, multihash.SHA2_512, rng)...) + records = append(records, generateIndexRecords(t, multihash.IDENTITY, rng)...) + + // Create a new mh sorted index and load randomly generated records into it. + subject, err := index.New(multicodec.CarMultihashIndexSorted) + require.NoError(t, err) + err = subject.Load(records) + require.NoError(t, err) + + iterable := subject.(index.IterableIndex) + mh := make([]multihash.Multihash, 0, len(records)) + require.NoError(t, iterable.ForEach(func(m multihash.Multihash, _ uint64) error { + mh = append(mh, m) + return nil + })) + + for i := 0; i < 10; i++ { + candidate := make([]multihash.Multihash, 0, len(records)) + require.NoError(t, iterable.ForEach(func(m multihash.Multihash, _ uint64) error { + candidate = append(candidate, m) + return nil + })) + require.Equal(t, mh, candidate) + } +} + func generateIndexRecords(t *testing.T, hasherCode uint64, rng *rand.Rand) []index.Record { var records []index.Record recordCount := rng.Intn(99) + 1 // Up to 100 records From 27eb714bc83bb25aed64eea72ea47d78fd9b4e3a Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 21 Oct 2021 11:14:25 -0700 Subject: [PATCH 184/291] creation of car from file / directory (#246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * creation of car from file / directory Co-authored-by: Daniel Martí Co-authored-by: Rod Vagg This commit was moved from ipld/go-car@9bd74165e21fd22be458fa05d8ce7dda5a507bbc --- ipld/car/cmd/car/car.go | 29 +++ ipld/car/cmd/car/create.go | 120 ++++++++++++ ipld/car/cmd/car/get.go | 9 +- ipld/car/cmd/car/index.go | 17 ++ ipld/car/cmd/car/list.go | 193 ++++++++++++++++++-- ipld/car/cmd/car/testdata/script/create.txt | 13 ++ 6 files changed, 365 insertions(+), 16 deletions(-) create mode 100644 ipld/car/cmd/car/create.go create mode 100644 ipld/car/cmd/car/testdata/script/create.txt diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index dca4c0f25c..aea9f4c25c 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -15,6 +15,20 @@ func main1() int { Name: "car", Usage: "Utility for working with car files", Commands: []*cli.Command{ + { + Name: "create", + Usage: "Create a car file", + Aliases: []string{"c"}, + Action: CreateCar, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "file", + Aliases: []string{"f", "output", "o"}, + Usage: "The car file to write to", + TakesFile: true, + }, + }, + }, { Name: "detach-index", Usage: "Detach an index to a detached file", @@ -72,6 +86,10 @@ func main1() int { Usage: "The type of index to write", Value: multicodec.CarMultihashIndexSorted.String(), }, + &cli.BoolFlag{ + Name: "v1", + Usage: "Write out only the carV1 file. Implies codec of 'none'", + }, }, }, { @@ -79,6 +97,17 @@ func main1() int { Aliases: []string{"l"}, Usage: "List the CIDs in a car", Action: ListCar, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Usage: "Include verbose information about contained blocks", + }, + &cli.BoolFlag{ + Name: "unixfs", + Usage: "List unixfs filesystem from the root of the car", + }, + }, }, { Name: "verify", diff --git a/ipld/car/cmd/car/create.go b/ipld/car/cmd/car/create.go new file mode 100644 index 0000000000..4979053818 --- /dev/null +++ b/ipld/car/cmd/car/create.go @@ -0,0 +1,120 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "io" + "path" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-unixfsnode/data/builder" + "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" + dagpb "github.com/ipld/go-codec-dagpb" + "github.com/ipld/go-ipld-prime" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-multihash" + "github.com/urfave/cli/v2" +) + +// CreateCar creates a car +func CreateCar(c *cli.Context) error { + var err error + if c.Args().Len() == 0 { + return fmt.Errorf("a source location to build the car from must be specified") + } + + if !c.IsSet("file") { + return fmt.Errorf("a file destination must be specified") + } + + // make a cid with the right length that we eventually will patch with the root. + hasher, err := multihash.GetHasher(multihash.SHA2_256) + if err != nil { + return err + } + digest := hasher.Sum([]byte{}) + hash, err := multihash.Encode(digest, multihash.SHA2_256) + if err != nil { + return err + } + proxyRoot := cid.NewCidV1(uint64(multicodec.DagPb), hash) + + cdest, err := blockstore.OpenReadWrite(c.String("file"), []cid.Cid{proxyRoot}) + if err != nil { + return err + } + + // Write the unixfs blocks into the store. + root, err := writeFiles(c.Context, cdest, c.Args().Slice()...) + if err != nil { + return err + } + + if err := cdest.Finalize(); err != nil { + return err + } + // re-open/finalize with the final root. + return car.ReplaceRootsInFile(c.String("file"), []cid.Cid{root}) +} + +func writeFiles(ctx context.Context, bs *blockstore.ReadWrite, paths ...string) (cid.Cid, error) { + ls := cidlink.DefaultLinkSystem() + ls.TrustedStorage = true + ls.StorageReadOpener = func(_ ipld.LinkContext, l ipld.Link) (io.Reader, error) { + cl, ok := l.(cidlink.Link) + if !ok { + return nil, fmt.Errorf("not a cidlink") + } + blk, err := bs.Get(cl.Cid) + if err != nil { + return nil, err + } + return bytes.NewBuffer(blk.RawData()), nil + } + ls.StorageWriteOpener = func(_ ipld.LinkContext) (io.Writer, ipld.BlockWriteCommitter, error) { + buf := bytes.NewBuffer(nil) + return buf, func(l ipld.Link) error { + cl, ok := l.(cidlink.Link) + if !ok { + return fmt.Errorf("not a cidlink") + } + blk, err := blocks.NewBlockWithCid(buf.Bytes(), cl.Cid) + if err != nil { + return err + } + bs.Put(blk) + return nil + }, nil + } + + topLevel := make([]dagpb.PBLink, 0, len(paths)) + for _, p := range paths { + l, size, err := builder.BuildUnixFSRecursive(p, &ls) + if err != nil { + return cid.Undef, err + } + name := path.Base(p) + entry, err := builder.BuildUnixFSDirectoryEntry(name, int64(size), l) + if err != nil { + return cid.Undef, err + } + topLevel = append(topLevel, entry) + } + + // make a directory for the file(s). + + root, err := builder.BuildUnixFSDirectory(topLevel, &ls) + if err != nil { + return cid.Undef, nil + } + rcl, ok := root.(cidlink.Link) + if !ok { + return cid.Undef, fmt.Errorf("could not interpret %s", root) + } + + return rcl.Cid, nil +} diff --git a/ipld/car/cmd/car/get.go b/ipld/car/cmd/car/get.go index 9cdc7a4cca..f137331368 100644 --- a/ipld/car/cmd/car/get.go +++ b/ipld/car/cmd/car/get.go @@ -85,8 +85,9 @@ func GetCarDag(c *cli.Context) error { } ls := cidlink.DefaultLinkSystem() + ls.TrustedStorage = true ls.StorageReadOpener = func(_ linking.LinkContext, l datamodel.Link) (io.Reader, error) { - if cl, ok := l.(*cidlink.Link); ok { + if cl, ok := l.(cidlink.Link); ok { blk, err := bs.Get(cl.Cid) if err != nil { if err == ipfsbs.ErrNotFound { @@ -104,7 +105,7 @@ func GetCarDag(c *cli.Context) error { ls.StorageWriteOpener = func(_ linking.LinkContext) (io.Writer, linking.BlockWriteCommitter, error) { buf := bytes.NewBuffer(nil) return buf, func(l datamodel.Link) error { - if cl, ok := l.(*cidlink.Link); ok { + if cl, ok := l.(cidlink.Link); ok { blk, err := blocks.NewBlockWithCid(buf.Bytes(), cl.Cid) if err != nil { return err @@ -124,7 +125,7 @@ func GetCarDag(c *cli.Context) error { } // selector traversal - s := selectorParser.CommonSelector_MatchAllRecursively + s, _ := selector.CompileSelector(selectorParser.CommonSelector_MatchAllRecursively) if c.IsSet("selector") { sn, err := selectorParser.ParseJSONSelector(c.String("selector")) if err != nil { @@ -141,7 +142,7 @@ func GetCarDag(c *cli.Context) error { } err = traversal.WalkMatching(node, s, func(p traversal.Progress, n datamodel.Node) error { if p.LastBlock.Link != nil { - if cl, ok := p.LastBlock.Link.(*cidlink.Link); ok { + if cl, ok := p.LastBlock.Link.(cidlink.Link); ok { lnkProto = cidlink.LinkPrototype{ Prefix: cl.Prefix(), } diff --git a/ipld/car/cmd/car/index.go b/ipld/car/cmd/car/index.go index a1fa6d8644..ac3688861f 100644 --- a/ipld/car/cmd/car/index.go +++ b/ipld/car/cmd/car/index.go @@ -23,6 +23,23 @@ func IndexCar(c *cli.Context) error { } defer r.Close() + if c.Bool("v1") { + if c.IsSet("codec") && c.String("codec") != "none" { + return fmt.Errorf("only supported codec for a v1 car is 'none'") + } + outStream := os.Stdout + if c.Args().Len() >= 2 { + outStream, err = os.Create(c.Args().Get(1)) + if err != nil { + return err + } + } + defer outStream.Close() + + _, err := io.Copy(outStream, r.DataReader()) + return err + } + var idx index.Index if c.String("codec") != "none" { var mc multicodec.Code diff --git a/ipld/car/cmd/car/list.go b/ipld/car/cmd/car/list.go index e9cf1f7e1c..2912b1d331 100644 --- a/ipld/car/cmd/car/list.go +++ b/ipld/car/cmd/car/list.go @@ -1,39 +1,51 @@ package main import ( + "bytes" "fmt" "io" "os" + "path" + "github.com/dustin/go-humanize" + "github.com/ipfs/go-cid" + data "github.com/ipfs/go-unixfsnode/data" + "github.com/ipfs/go-unixfsnode/hamt" carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" + dagpb "github.com/ipld/go-codec-dagpb" + "github.com/ipld/go-ipld-prime" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/multiformats/go-multicodec" "github.com/urfave/cli/v2" ) // ListCar is a command to output the cids in a car. func ListCar(c *cli.Context) error { - inStream := os.Stdin var err error - if c.Args().Len() >= 1 { - inStream, err = os.Open(c.Args().First()) + outStream := os.Stdout + if c.Args().Len() >= 2 { + outStream, err = os.Create(c.Args().Get(1)) if err != nil { return err } - defer inStream.Close() } - rd, err := carv2.NewBlockReader(inStream) - if err != nil { - return err + defer outStream.Close() + + if c.Bool("unixfs") { + return listUnixfs(c, outStream) } - outStream := os.Stdout - if c.Args().Len() >= 2 { - outStream, err = os.Create(c.Args().Get(1)) + inStream := os.Stdin + if c.Args().Len() >= 1 { + inStream, err = os.Open(c.Args().First()) if err != nil { return err } + defer inStream.Close() } - defer outStream.Close() + rd, err := carv2.NewBlockReader(inStream) if err != nil { return err } @@ -46,8 +58,165 @@ func ListCar(c *cli.Context) error { } return err } - fmt.Fprintf(outStream, "%s\n", blk.Cid()) + if c.Bool("verbose") { + fmt.Fprintf(outStream, "%s: %s\n", + multicodec.Code(blk.Cid().Prefix().Codec).String(), + blk.Cid()) + if blk.Cid().Prefix().Codec == uint64(multicodec.DagPb) { + // parse as dag-pb + builder := dagpb.Type.PBNode.NewBuilder() + if err := dagpb.DecodeBytes(builder, blk.RawData()); err != nil { + fmt.Fprintf(outStream, "\tnot interpretable as dag-pb: %s\n", err) + continue + } + n := builder.Build() + pbn, ok := n.(dagpb.PBNode) + if !ok { + continue + } + dl := 0 + if pbn.Data.Exists() { + dl = len(pbn.Data.Must().Bytes()) + } + fmt.Fprintf(outStream, "\t%d links. %d bytes\n", pbn.Links.Length(), dl) + // example link: + li := pbn.Links.ListIterator() + max := 3 + for !li.Done() { + _, l, _ := li.Next() + max-- + pbl, ok := l.(dagpb.PBLink) + if ok && max >= 0 { + hsh := "" + lnk, ok := pbl.Hash.Link().(cidlink.Link) + if ok { + hsh = lnk.Cid.String() + } + name := "" + if pbl.Name.Exists() { + name = pbl.Name.Must().String() + } + size := 0 + if pbl.Tsize.Exists() { + size = int(pbl.Tsize.Must().Int()) + } + fmt.Fprintf(outStream, "\t\t%s[%s] %s\n", name, humanize.Bytes(uint64(size)), hsh) + } + } + if max < 0 { + fmt.Fprintf(outStream, "\t\t(%d total)\n", 3-max) + } + // see if it's unixfs. + ufd, err := data.DecodeUnixFSData(pbn.Data.Must().Bytes()) + if err != nil { + fmt.Fprintf(outStream, "\tnot interpretable as unixfs: %s\n", err) + continue + } + fmt.Fprintf(outStream, "\tUnixfs %s\n", data.DataTypeNames[ufd.FieldDataType().Int()]) + } + } else { + fmt.Fprintf(outStream, "%s\n", blk.Cid()) + } } return err } + +func listUnixfs(c *cli.Context, outStream io.Writer) error { + if c.Args().Len() == 0 { + return fmt.Errorf("must provide file to read from. unixfs reading requires random access") + } + + bs, err := blockstore.OpenReadOnly(c.Args().First()) + if err != nil { + return err + } + ls := cidlink.DefaultLinkSystem() + ls.TrustedStorage = true + ls.StorageReadOpener = func(_ ipld.LinkContext, l ipld.Link) (io.Reader, error) { + cl, ok := l.(cidlink.Link) + if !ok { + return nil, fmt.Errorf("not a cidlink") + } + blk, err := bs.Get(cl.Cid) + if err != nil { + return nil, err + } + return bytes.NewBuffer(blk.RawData()), nil + } + + roots, err := bs.Roots() + if err != nil { + return err + } + for _, r := range roots { + if err := printUnixFSNode(c, "", r, &ls, outStream); err != nil { + return err + } + } + return nil +} + +func printUnixFSNode(c *cli.Context, prefix string, node cid.Cid, ls *ipld.LinkSystem, outStream io.Writer) error { + // it might be a raw file (bytes) node. if so, not actually an error. + if node.Prefix().Codec == cid.Raw { + return nil + } + + pbn, err := ls.Load(ipld.LinkContext{}, cidlink.Link{Cid: node}, dagpb.Type.PBNode) + if err != nil { + return err + } + + pbnode := pbn.(dagpb.PBNode) + + ufd, err := data.DecodeUnixFSData(pbnode.Data.Must().Bytes()) + if err != nil { + return err + } + + if ufd.FieldDataType().Int() == data.Data_Directory { + i := pbnode.Links.Iterator() + for !i.Done() { + _, l := i.Next() + name := path.Join(prefix, l.Name.Must().String()) + fmt.Fprintf(outStream, "%s\n", name) + // recurse into the file/directory + cl, err := l.Hash.AsLink() + if err != nil { + return err + } + if cidl, ok := cl.(cidlink.Link); ok { + if err := printUnixFSNode(c, name, cidl.Cid, ls, outStream); err != nil { + return err + } + } + + } + } else if ufd.FieldDataType().Int() == data.Data_HAMTShard { + hn, err := hamt.AttemptHAMTShardFromNode(c.Context, pbn, ls) + if err != nil { + return err + } + i := hn.Iterator() + for !i.Done() { + n, l := i.Next() + fmt.Fprintf(outStream, "%s\n", path.Join(prefix, n.String())) + // recurse into the file/directory + cl, err := l.AsLink() + if err != nil { + return err + } + if cidl, ok := cl.(cidlink.Link); ok { + if err := printUnixFSNode(c, path.Join(prefix, n.String()), cidl.Cid, ls, outStream); err != nil { + return err + } + } + } + } else { + // file, file chunk, symlink, other un-named entities. + return nil + } + + return nil +} diff --git a/ipld/car/cmd/car/testdata/script/create.txt b/ipld/car/cmd/car/testdata/script/create.txt new file mode 100644 index 0000000000..13849e31aa --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/create.txt @@ -0,0 +1,13 @@ +car create --file=out.car foo.txt bar.txt + +car verify out.car +car list --unixfs out.car +stdout -count=2 'txt$' +car list out.car +stdout -count=3 '^baf' +stdout -count=2 '^bafk' + +-- foo.txt -- +foo content +-- bar.txt -- +bar content From 66b393c6a8b9104446debfe61e79b4beec427b98 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 21 Oct 2021 21:36:04 +1100 Subject: [PATCH 185/291] feat: fix get-dag and add version=1 option This commit was moved from ipld/go-car@4a8d623fa17148133077b1b85c49dce14eb1de98 --- ipld/car/cmd/car/car.go | 5 ++ ipld/car/cmd/car/get.go | 121 +++++++++++++++++++++++----------------- 2 files changed, 76 insertions(+), 50 deletions(-) diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index aea9f4c25c..76a1ed4980 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -72,6 +72,11 @@ func main1() int { Name: "strict", Usage: "Fail if the selector finds links to blocks not in the original car", }, + &cli.IntFlag{ + Name: "version", + Value: 2, + Usage: "Write output as a v1 or v2 format car", + }, }, }, { diff --git a/ipld/car/cmd/car/get.go b/ipld/car/cmd/car/get.go index f137331368..324f050add 100644 --- a/ipld/car/cmd/car/get.go +++ b/ipld/car/cmd/car/get.go @@ -2,20 +2,22 @@ package main import ( "bytes" + "context" "fmt" "io" "os" - _ "github.com/ipld/go-codec-dagpb" + dagpb "github.com/ipld/go-codec-dagpb" + "github.com/ipld/go-ipld-prime" _ "github.com/ipld/go-ipld-prime/codec/cbor" _ "github.com/ipld/go-ipld-prime/codec/dagcbor" _ "github.com/ipld/go-ipld-prime/codec/dagjson" _ "github.com/ipld/go-ipld-prime/codec/json" _ "github.com/ipld/go-ipld-prime/codec/raw" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" ipfsbs "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipld/go-car" "github.com/ipld/go-car/v2/blockstore" "github.com/ipld/go-ipld-prime/datamodel" "github.com/ipld/go-ipld-prime/linking" @@ -68,18 +70,45 @@ func GetCarDag(c *cli.Context) error { return fmt.Errorf("usage: car get-dag [-s selector] ") } - bs, err := blockstore.OpenReadOnly(c.Args().Get(0)) + // string to CID for the root of the DAG to extract + rootCid, err := cid.Parse(c.Args().Get(1)) if err != nil { return err } - // string to CID - blkCid, err := cid.Parse(c.Args().Get(1)) + bs, err := blockstore.OpenReadOnly(c.Args().Get(0)) if err != nil { return err } - outStore, err := blockstore.OpenReadWrite(c.Args().Get(2), []cid.Cid{blkCid}) + output := c.Args().Get(2) + strict := c.Bool("strict") + + // selector traversal, default to ExploreAllRecursively which only explores the DAG blocks + // because we only care about the blocks loaded during the walk, not the nodes matched + sel := selectorParser.CommonSelector_ExploreAllRecursively + if c.IsSet("selector") { + sel, err = selectorParser.ParseJSONSelector(c.String("selector")) + if err != nil { + return err + } + } + linkVisitOnlyOnce := !c.IsSet("selector") // if using a custom selector, this isn't as safe + + switch c.Int("version") { + case 2: + return writeCarV2(rootCid, output, bs, strict, sel, linkVisitOnlyOnce) + case 1: + return writeCarV1(rootCid, output, bs, strict, sel, linkVisitOnlyOnce) + default: + return fmt.Errorf("invalid CAR version %d", c.Int("version")) + } +} + +func writeCarV2(rootCid cid.Cid, output string, bs *blockstore.ReadOnly, strict bool, sel datamodel.Node, linkVisitOnlyOnce bool) error { + _ = os.Remove(output) + + outStore, err := blockstore.OpenReadWrite(output, []cid.Cid{rootCid}, blockstore.AllowDuplicatePuts(false)) if err != nil { return err } @@ -91,7 +120,7 @@ func GetCarDag(c *cli.Context) error { blk, err := bs.Get(cl.Cid) if err != nil { if err == ipfsbs.ErrNotFound { - if c.Bool("strict") { + if strict { return nil, err } return nil, traversal.SkipMe{} @@ -102,61 +131,53 @@ func GetCarDag(c *cli.Context) error { } return nil, fmt.Errorf("unknown link type: %T", l) } - ls.StorageWriteOpener = func(_ linking.LinkContext) (io.Writer, linking.BlockWriteCommitter, error) { - buf := bytes.NewBuffer(nil) - return buf, func(l datamodel.Link) error { - if cl, ok := l.(cidlink.Link); ok { - blk, err := blocks.NewBlockWithCid(buf.Bytes(), cl.Cid) - if err != nil { - return err - } - return outStore.Put(blk) - } - return fmt.Errorf("unknown link type: %T", l) - }, nil - } - rootlnk := cidlink.Link{ - Cid: blkCid, + nsc := func(lnk datamodel.Link, lctx ipld.LinkContext) (datamodel.NodePrototype, error) { + if lnk, ok := lnk.(cidlink.Link); ok && lnk.Cid.Prefix().Codec == 0x70 { + return dagpb.Type.PBNode, nil + } + return basicnode.Prototype.Any, nil } - node, err := ls.Load(linking.LinkContext{}, rootlnk, basicnode.Prototype.Any) + + rootLink := cidlink.Link{Cid: rootCid} + ns, _ := nsc(rootLink, ipld.LinkContext{}) + rootNode, err := ls.Load(ipld.LinkContext{}, rootLink, ns) if err != nil { return err } - // selector traversal - s, _ := selector.CompileSelector(selectorParser.CommonSelector_MatchAllRecursively) - if c.IsSet("selector") { - sn, err := selectorParser.ParseJSONSelector(c.String("selector")) - if err != nil { - return err - } - s, err = selector.CompileSelector(sn) - if err != nil { - return err - } + traversalProgress := traversal.Progress{ + Cfg: &traversal.Config{ + LinkSystem: ls, + LinkTargetNodePrototypeChooser: nsc, + LinkVisitOnlyOnce: linkVisitOnlyOnce, + }, } - lnkProto := cidlink.LinkPrototype{ - Prefix: blkCid.Prefix(), + s, err := selector.CompileSelector(sel) + if err != nil { + return err } - err = traversal.WalkMatching(node, s, func(p traversal.Progress, n datamodel.Node) error { - if p.LastBlock.Link != nil { - if cl, ok := p.LastBlock.Link.(cidlink.Link); ok { - lnkProto = cidlink.LinkPrototype{ - Prefix: cl.Prefix(), - } - } - } - _, err = ls.Store(linking.LinkContext{}, lnkProto, n) - if err != nil { - return err - } - return nil - }) + + err = traversalProgress.WalkMatching(rootNode, s, func(p traversal.Progress, n datamodel.Node) error { return nil }) if err != nil { return err } return outStore.Finalize() } + +func writeCarV1(rootCid cid.Cid, output string, bs *blockstore.ReadOnly, strict bool, sel datamodel.Node, linkVisitOnlyOnce bool) error { + opts := make([]car.Option, 0) + if linkVisitOnlyOnce { + opts = append(opts, car.TraverseLinksOnlyOnce()) + } + sc := car.NewSelectiveCar(context.Background(), bs, []car.Dag{{Root: rootCid, Selector: sel}}, opts...) + f, err := os.Create(output) + if err != nil { + return err + } + defer f.Close() + + return sc.Write(f) +} From 6ea6de457ac8a180f0b97e195fcb3df6e1c2deb9 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 22 Oct 2021 12:49:43 +1100 Subject: [PATCH 186/291] fix!: use -version=n instead of -v1 for index command This commit was moved from ipld/go-car@5a583de9641de1f2f6d20dc46b5a0313b67d7f81 --- ipld/car/cmd/car/car.go | 7 ++++--- ipld/car/cmd/car/index.go | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 76a1ed4980..850673c6f9 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -91,9 +91,10 @@ func main1() int { Usage: "The type of index to write", Value: multicodec.CarMultihashIndexSorted.String(), }, - &cli.BoolFlag{ - Name: "v1", - Usage: "Write out only the carV1 file. Implies codec of 'none'", + &cli.IntFlag{ + Name: "version", + Value: 2, + Usage: "Write output as a v1 or v2 format car", }, }, }, diff --git a/ipld/car/cmd/car/index.go b/ipld/car/cmd/car/index.go index ac3688861f..153c0b7c94 100644 --- a/ipld/car/cmd/car/index.go +++ b/ipld/car/cmd/car/index.go @@ -23,9 +23,9 @@ func IndexCar(c *cli.Context) error { } defer r.Close() - if c.Bool("v1") { + if c.Int("version") == 1 { if c.IsSet("codec") && c.String("codec") != "none" { - return fmt.Errorf("only supported codec for a v1 car is 'none'") + return fmt.Errorf("'none' is the only supported codec for a v1 car") } outStream := os.Stdout if c.Args().Len() >= 2 { @@ -40,6 +40,10 @@ func IndexCar(c *cli.Context) error { return err } + if c.Int("version") != 2 { + return fmt.Errorf("invalid CAR version %d", c.Int("version")) + } + var idx index.Index if c.String("codec") != "none" { var mc multicodec.Code From 815dd33f8cefacb6203c459b2b673aea9eb1af0e Mon Sep 17 00:00:00 2001 From: Will Date: Mon, 1 Nov 2021 02:22:43 -0700 Subject: [PATCH 187/291] Add a barebones readme to the car CLI (#262) This commit was moved from ipld/go-car@85f0751677fa4115a44455d16ae1d060bc078104 --- ipld/car/README.md | 2 +- ipld/car/cmd/car/README.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 ipld/car/cmd/car/README.md diff --git a/ipld/car/README.md b/ipld/car/README.md index 7ccf7cca31..0f2157991f 100644 --- a/ipld/car/README.md +++ b/ipld/car/README.md @@ -3,7 +3,7 @@ go-car (go!) [![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) [![](https://img.shields.io/badge/project-ipld-orange.svg?style=flat-square)](https://github.com/ipld/ipld) -[![](https://img.shields.io/badge/freenode-%23ipld-orange.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipld) +[![](https://img.shields.io/badge/matrix-%23ipld-blue.svg?style=flat-square)](https://matrix.to/#/#ipld:ipfs.io) [![Go Reference](https://pkg.go.dev/badge/github.com/ipld/go-car.svg)](https://pkg.go.dev/github.com/ipld/go-car) [![Coverage Status](https://codecov.io/gh/ipld/go-car/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/go-car/branch/master) diff --git a/ipld/car/cmd/car/README.md b/ipld/car/cmd/car/README.md new file mode 100644 index 0000000000..f3825f0d2c --- /dev/null +++ b/ipld/car/cmd/car/README.md @@ -0,0 +1,33 @@ +car - The CLI tool +================== + +[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai) +[![](https://img.shields.io/badge/project-ipld-orange.svg?style=flat-square)](https://github.com/ipld/ipld) +[![](https://img.shields.io/badge/matrix-%23ipld-blue.svg?style=flat-square)](https://matrix.to/#/#ipld:ipfs.io) + +> A CLI to interact with car files + +## Usage + +``` +USAGE: + car [global options] command [command options] [arguments...] + +COMMANDS: + create, c Create a car file + detach-index Detach an index to a detached file + filter, f Filter the CIDs in a car + get-block, gb Get a block out of a car + get-dag, gd Get a dag out of a car + index, i write out the car with an index + list, l List the CIDs in a car + verify, v Verify a CAR is wellformed + help, h Shows a list of commands or help for one command +``` + +## Install + +To install the latest version of `car` module, run: +```shell script +go install github.com/ipld/go-car/cmd/car +``` From a8d200c1b313f82188dbb335baf926d1bc704d0d Mon Sep 17 00:00:00 2001 From: Will Date: Tue, 9 Nov 2021 02:29:01 -0800 Subject: [PATCH 188/291] support extraction of unixfs content stored in car files (#263) * support extraction of unixfs content stored in car files This commit was moved from ipld/go-car@6d94b7b90cf3d267d566223cd386a2dd36e6749f --- ipld/car/cmd/car/car.go | 20 ++ ipld/car/cmd/car/extract.go | 228 ++++++++++++++++++ .../car/testdata/script/create-extract.txt | 12 + 3 files changed, 260 insertions(+) create mode 100644 ipld/car/cmd/car/extract.go create mode 100644 ipld/car/cmd/car/testdata/script/create-extract.txt diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 850673c6f9..43b7307d72 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -34,6 +34,26 @@ func main1() int { Usage: "Detach an index to a detached file", Action: DetachCar, }, + { + Name: "extract", + Aliases: []string{"x"}, + Usage: "Extract the contents of a car when the car encodes UnixFS data", + Action: ExtractCar, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "file", + Aliases: []string{"f"}, + Usage: "The car file to extract from", + Required: true, + TakesFile: true, + }, + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Usage: "Include verbose information about extracted contents", + }, + }, + }, { Name: "filter", Aliases: []string{"f"}, diff --git a/ipld/car/cmd/car/extract.go b/ipld/car/cmd/car/extract.go new file mode 100644 index 0000000000..4a56e1775a --- /dev/null +++ b/ipld/car/cmd/car/extract.go @@ -0,0 +1,228 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "os" + "path" + "path/filepath" + + "github.com/ipfs/go-cid" + "github.com/ipfs/go-unixfsnode" + "github.com/ipfs/go-unixfsnode/data" + "github.com/ipfs/go-unixfsnode/file" + "github.com/ipld/go-car/v2/blockstore" + dagpb "github.com/ipld/go-codec-dagpb" + "github.com/ipld/go-ipld-prime" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + basicnode "github.com/ipld/go-ipld-prime/node/basic" + "github.com/urfave/cli/v2" +) + +// ExtractCar pulls files and directories out of a car +func ExtractCar(c *cli.Context) error { + outputDir, err := os.Getwd() + if err != nil { + return err + } + if c.Args().Present() { + outputDir = c.Args().First() + } + + bs, err := blockstore.OpenReadOnly(c.String("file")) + if err != nil { + return err + } + + ls := cidlink.DefaultLinkSystem() + ls.TrustedStorage = true + ls.StorageReadOpener = func(_ ipld.LinkContext, l ipld.Link) (io.Reader, error) { + cl, ok := l.(cidlink.Link) + if !ok { + return nil, fmt.Errorf("not a cidlink") + } + blk, err := bs.Get(cl.Cid) + if err != nil { + return nil, err + } + return bytes.NewBuffer(blk.RawData()), nil + } + + roots, err := bs.Roots() + if err != nil { + return err + } + + for _, root := range roots { + if err := extractRoot(c, &ls, root, outputDir); err != nil { + return err + } + } + + return nil +} + +func extractRoot(c *cli.Context, ls *ipld.LinkSystem, root cid.Cid, outputDir string) error { + if root.Prefix().Codec == cid.Raw { + if c.IsSet("verbose") { + fmt.Fprintf(c.App.ErrWriter, "skipping raw root %s\n", root) + } + return nil + } + + pbn, err := ls.Load(ipld.LinkContext{}, cidlink.Link{Cid: root}, dagpb.Type.PBNode) + if err != nil { + return err + } + pbnode := pbn.(dagpb.PBNode) + + ufn, err := unixfsnode.Reify(ipld.LinkContext{}, pbnode, ls) + if err != nil { + return err + } + + outputResolvedDir, err := filepath.EvalSymlinks(outputDir) + if err != nil { + return err + } + if _, err := os.Stat(outputResolvedDir); os.IsNotExist(err) { + if err := os.Mkdir(outputResolvedDir, 0755); err != nil { + return err + } + } + if err := extractDir(c, ls, ufn, outputResolvedDir, "/"); err != nil { + return fmt.Errorf("%s: %w", root, err) + } + + return nil +} + +func resolvePath(root, pth string) (string, error) { + rp, err := filepath.Rel("/", pth) + if err != nil { + return "", fmt.Errorf("couldn't check relative-ness of %s: %w", pth, err) + } + joined := path.Join(root, rp) + + basename := path.Dir(joined) + final, err := filepath.EvalSymlinks(basename) + if err != nil { + return "", fmt.Errorf("couldn't eval symlinks in %s: %w", basename, err) + } + if final != path.Clean(basename) { + return "", fmt.Errorf("path attempts to redirect through symlinks") + } + return joined, nil +} + +func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, outputPath string) error { + dirPath, err := resolvePath(outputRoot, outputPath) + if err != nil { + return err + } + // make the directory. + if err := os.MkdirAll(dirPath, 0755); err != nil { + return err + } + + if n.Kind() == ipld.Kind_Map { + mi := n.MapIterator() + for !mi.Done() { + key, val, err := mi.Next() + if err != nil { + return err + } + ks, err := key.AsString() + if err != nil { + return err + } + nextRes, err := resolvePath(outputRoot, path.Join(outputPath, ks)) + if err != nil { + return err + } + if c.IsSet("verbose") { + fmt.Fprintf(c.App.Writer, "%s\n", nextRes) + } + + if val.Kind() != ipld.Kind_Link { + return fmt.Errorf("unexpected map value for %s at %s", ks, outputPath) + } + // a directory may be represented as a map of name: if unixADL is applied + vl, err := val.AsLink() + if err != nil { + return err + } + dest, err := ls.Load(ipld.LinkContext{}, vl, basicnode.Prototype.Any) + if err != nil { + return err + } + // degenerate files are handled here. + if dest.Kind() == ipld.Kind_Bytes { + if err := extractFile(c, ls, dest, nextRes); err != nil { + return err + } + continue + } else { + // dir / pbnode + pbb := dagpb.Type.PBNode.NewBuilder() + if err := pbb.AssignNode(dest); err != nil { + return err + } + dest = pbb.Build() + } + pbnode := dest.(dagpb.PBNode) + + // interpret dagpb 'data' as unixfs data and look at type. + ufsData, err := pbnode.LookupByString("Data") + if err != nil { + return err + } + ufsBytes, err := ufsData.AsBytes() + if err != nil { + return err + } + ufsNode, err := data.DecodeUnixFSData(ufsBytes) + if err != nil { + return err + } + if ufsNode.DataType.Int() == data.Data_Directory || ufsNode.DataType.Int() == data.Data_HAMTShard { + ufn, err := unixfsnode.Reify(ipld.LinkContext{}, pbnode, ls) + if err != nil { + return err + } + + if err := extractDir(c, ls, ufn, outputRoot, path.Join(outputPath, ks)); err != nil { + return err + } + } else if ufsNode.DataType.Int() == data.Data_File || ufsNode.DataType.Int() == data.Data_Raw { + if err := extractFile(c, ls, pbnode, nextRes); err != nil { + return err + } + } else if ufsNode.DataType.Int() == data.Data_Symlink { + data := ufsNode.Data.Must().Bytes() + if err := os.Symlink(string(data), nextRes); err != nil { + return err + } + } + } + return nil + } + return fmt.Errorf("not a directory") +} + +func extractFile(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputName string) error { + node, err := file.NewUnixFSFile(c.Context, n, ls) + if err != nil { + return err + } + + f, err := os.Create(outputName) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, node) + + return err +} diff --git a/ipld/car/cmd/car/testdata/script/create-extract.txt b/ipld/car/cmd/car/testdata/script/create-extract.txt new file mode 100644 index 0000000000..648bafc493 --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/create-extract.txt @@ -0,0 +1,12 @@ +car create --file=out.car foo.txt bar.txt +mkdir out +car extract -v -f out.car out +! stderr . +stdout -count=2 'txt$' +car create --file=out2.car out/foo.txt out/bar.txt +cmp out.car out2.car + +-- foo.txt -- +foo content +-- bar.txt -- +bar content From dcf224435eb1d34a5784349a608b4e9665291613 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 16 Nov 2021 21:01:54 +0000 Subject: [PATCH 189/291] Seek to start before index generation in `ReadOnly` blockstore The blockstore reads the version of the given CAR payload to determine whether to generate an index for the given payload or not. When the payload represents a CARv1 and no index is specified, the backing reader is passed on for index generation. But the version is already read from the stream. Seek to the beginning of the backing reader before generating the index so that the index generation mechanism receives the complete CARv1. Fixes #265 This commit was moved from ipld/go-car@fa995b9d72f961b725eb8abd4dbe3de6765f8b27 --- ipld/car/v2/blockstore/readonly.go | 4 ++++ ipld/car/v2/blockstore/readonly_test.go | 28 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 690e233b7d..e25f512519 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -150,6 +150,10 @@ func generateIndex(at io.ReaderAt, opts ...carv2.Option) (index.Index, error) { switch r := at.(type) { case io.ReadSeeker: rs = r + // The version may have been read from the given io.ReaderAt; therefore move back to the begining. + if _, err := rs.Seek(0, io.SeekStart); err != nil { + return nil, err + } default: rs = internalio.NewOffsetReadSeeker(r, 0) } diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index a242abd8c0..4922acd1b6 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -1,8 +1,10 @@ package blockstore import ( + "bytes" "context" "io" + "io/ioutil" "os" "testing" "time" @@ -269,3 +271,29 @@ func TestReadOnlyErrorAfterClose(t *testing.T) { // TODO: test that closing blocks if an AllKeysChan operation is // in progress. } + +func TestNewReadOnly_CarV1WithoutIndexWorksAsExpected(t *testing.T) { + carV1Bytes, err := ioutil.ReadFile("../testdata/sample-v1.car") + require.NoError(t, err) + + reader := bytes.NewReader(carV1Bytes) + v1r, err := carv1.NewCarReader(reader) + require.NoError(t, err) + require.Equal(t, uint64(1), v1r.Header.Version) + + // Pick the first block in CARv1 as candidate to check `Get` works. + wantBlock, err := v1r.Next() + require.NoError(t, err) + + // Seek back to the begining of the CARv1 payload. + _, err = reader.Seek(0, io.SeekStart) + require.NoError(t, err) + + subject, err := NewReadOnly(reader, nil, UseWholeCIDs(true)) + require.NoError(t, err) + + // Require that the block is found via ReadOnly API and contetns are as expected. + gotBlock, err := subject.Get(wantBlock.Cid()) + require.NoError(t, err) + require.Equal(t, wantBlock, gotBlock) +} From ab62a53afcdf15e0324f1a7b3e4a824d0973cb87 Mon Sep 17 00:00:00 2001 From: Will Date: Tue, 30 Nov 2021 10:21:59 -0800 Subject: [PATCH 190/291] Traversal-based car creation (#269) Add a writing/creation API in the car v2 package. Allows creation of file and streaming car files in a selector order. This commit was moved from ipld/go-car@c35591a559b5fc60bdd107f164b5b25d56d06378 --- ipld/car/v2/blockstore/insertionindex.go | 9 +- ipld/car/v2/blockstore/readwrite.go | 2 +- ipld/car/v2/index/example_test.go | 2 +- ipld/car/v2/index/index.go | 16 +- ipld/car/v2/index/index_test.go | 6 +- ipld/car/v2/index/indexsorted.go | 28 +- ipld/car/v2/index/mhindexsorted.go | 20 +- ipld/car/v2/index/mhindexsorted_test.go | 2 +- .../car/v2/internal/loader/counting_loader.go | 61 ++++ ipld/car/v2/internal/loader/writing_loader.go | 114 ++++++++ ipld/car/v2/options.go | 16 +- ipld/car/v2/options_test.go | 7 +- ipld/car/v2/selective.go | 269 ++++++++++++++++++ ipld/car/v2/selective_test.go | 83 ++++++ ipld/car/v2/testdata/sample-unixfs-v2.car | Bin 0 -> 485 bytes ipld/car/v2/writer.go | 5 +- 16 files changed, 603 insertions(+), 37 deletions(-) create mode 100644 ipld/car/v2/internal/loader/counting_loader.go create mode 100644 ipld/car/v2/internal/loader/writing_loader.go create mode 100644 ipld/car/v2/selective.go create mode 100644 ipld/car/v2/selective_test.go create mode 100644 ipld/car/v2/testdata/sample-unixfs-v2.car diff --git a/ipld/car/v2/blockstore/insertionindex.go b/ipld/car/v2/blockstore/insertionindex.go index 7cca61b311..e8575ee1de 100644 --- a/ipld/car/v2/blockstore/insertionindex.go +++ b/ipld/car/v2/blockstore/insertionindex.go @@ -103,10 +103,13 @@ func (ii *insertionIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { return nil } -func (ii *insertionIndex) Marshal(w io.Writer) error { +func (ii *insertionIndex) Marshal(w io.Writer) (uint64, error) { + l := uint64(0) if err := binary.Write(w, binary.LittleEndian, int64(ii.items.Len())); err != nil { - return err + return l, err } + l += 8 + var err error iter := func(i llrb.Item) bool { if err = cbor.Encode(w, i.(recordDigest).Record); err != nil { @@ -115,7 +118,7 @@ func (ii *insertionIndex) Marshal(w io.Writer) error { return true } ii.items.AscendGreaterOrEqual(ii.items.Min(), iter) - return err + return l, err } func (ii *insertionIndex) Unmarshal(r io.Reader) error { diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 0dda5a0bd9..4f288fa0ea 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -376,7 +376,7 @@ func (b *ReadWrite) Finalize() error { if err != nil { return err } - if err := index.WriteTo(fi, internalio.NewOffsetWriter(b.f, int64(b.header.IndexOffset))); err != nil { + if _, err := index.WriteTo(fi, internalio.NewOffsetWriter(b.f, int64(b.header.IndexOffset))); err != nil { return err } if _, err := b.header.WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)); err != nil { diff --git a/ipld/car/v2/index/example_test.go b/ipld/car/v2/index/example_test.go index ca0ac73ab9..3e484afb93 100644 --- a/ipld/car/v2/index/example_test.go +++ b/ipld/car/v2/index/example_test.go @@ -77,7 +77,7 @@ func ExampleWriteTo() { panic(err) } }() - err = index.WriteTo(idx, f) + _, err = index.WriteTo(idx, f) if err != nil { panic(err) } diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 998a17a0bb..10195b43a3 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -15,6 +15,9 @@ import ( "github.com/ipfs/go-cid" ) +// CarIndexNone is a sentinal value used as a multicodec code for the index indicating no index. +const CarIndexNone = 0x300000 + type ( // Record is a pre-processed record of a car item and location. Record struct { @@ -40,7 +43,7 @@ type ( Codec() multicodec.Code // Marshal encodes the index in serial form. - Marshal(w io.Writer) error + Marshal(w io.Writer) (uint64, error) // Unmarshal decodes the index from its serial form. Unmarshal(r io.Reader) error @@ -118,13 +121,16 @@ func New(codec multicodec.Code) (Index, error) { // WriteTo writes the given idx into w. // The written bytes include the index encoding. // This can then be read back using index.ReadFrom -func WriteTo(idx Index, w io.Writer) error { +func WriteTo(idx Index, w io.Writer) (uint64, error) { buf := make([]byte, binary.MaxVarintLen64) b := varint.PutUvarint(buf, uint64(idx.Codec())) - if _, err := w.Write(buf[:b]); err != nil { - return err + n, err := w.Write(buf[:b]) + if err != nil { + return uint64(n), err } - return idx.Marshal(w) + + l, err := idx.Marshal(w) + return uint64(n) + l, err } // ReadFrom reads index from r. diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index bb369cfe06..267beb0344 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -100,7 +100,8 @@ func TestWriteTo(t *testing.T) { destF, err := os.Create(dest) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, destF.Close()) }) - require.NoError(t, WriteTo(wantIdx, destF)) + _, err = WriteTo(wantIdx, destF) + require.NoError(t, err) // Seek to the beginning of the written out file. _, err = destF.Seek(0, io.SeekStart) @@ -126,6 +127,7 @@ func TestMarshalledIndexStartsWithCodec(t *testing.T) { // Assert the first two bytes are the corresponding multicodec code. buf := new(bytes.Buffer) - require.NoError(t, WriteTo(wantIdx, buf)) + _, err = WriteTo(wantIdx, buf) + require.NoError(t, err) require.Equal(t, varint.ToUvarint(uint64(multicodec.CarIndexSorted)), buf.Bytes()[:2]) } diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 86994dd8b8..2c05a9227d 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -46,16 +46,18 @@ func (r recordSet) Swap(i, j int) { r[i], r[j] = r[j], r[i] } -func (s *singleWidthIndex) Marshal(w io.Writer) error { +func (s *singleWidthIndex) Marshal(w io.Writer) (uint64, error) { + l := uint64(0) if err := binary.Write(w, binary.LittleEndian, s.width); err != nil { - return err + return 0, err } + l += 4 if err := binary.Write(w, binary.LittleEndian, int64(len(s.index))); err != nil { - return err + return l, err } - // TODO: we could just w.Write(s.index) here and avoid overhead - _, err := io.Copy(w, bytes.NewBuffer(s.index)) - return err + l += 8 + n, err := w.Write(s.index) + return l + uint64(n), err } func (s *singleWidthIndex) Unmarshal(r io.Reader) error { @@ -158,10 +160,12 @@ func (m *multiWidthIndex) Codec() multicodec.Code { return multicodec.CarIndexSorted } -func (m *multiWidthIndex) Marshal(w io.Writer) error { +func (m *multiWidthIndex) Marshal(w io.Writer) (uint64, error) { + l := uint64(0) if err := binary.Write(w, binary.LittleEndian, int32(len(*m))); err != nil { - return err + return l, err } + l += 4 // The widths are unique, but ranging over a map isn't deterministic. // As per the CARv2 spec, we must order buckets by digest length. @@ -176,11 +180,13 @@ func (m *multiWidthIndex) Marshal(w io.Writer) error { for _, width := range widths { bucket := (*m)[width] - if err := bucket.Marshal(w); err != nil { - return err + n, err := bucket.Marshal(w) + l += n + if err != nil { + return l, err } } - return nil + return l, nil } func (m *multiWidthIndex) Unmarshal(r io.Reader) error { diff --git a/ipld/car/v2/index/mhindexsorted.go b/ipld/car/v2/index/mhindexsorted.go index e3cae3d073..55975b8e5f 100644 --- a/ipld/car/v2/index/mhindexsorted.go +++ b/ipld/car/v2/index/mhindexsorted.go @@ -31,11 +31,12 @@ func newMultiWidthCodedIndex() *multiWidthCodedIndex { } } -func (m *multiWidthCodedIndex) Marshal(w io.Writer) error { +func (m *multiWidthCodedIndex) Marshal(w io.Writer) (uint64, error) { if err := binary.Write(w, binary.LittleEndian, m.code); err != nil { - return err + return 8, err } - return m.multiWidthIndex.Marshal(w) + n, err := m.multiWidthIndex.Marshal(w) + return 8 + n, err } func (m *multiWidthCodedIndex) Unmarshal(r io.Reader) error { @@ -59,22 +60,25 @@ func (m *MultihashIndexSorted) Codec() multicodec.Code { return multicodec.CarMultihashIndexSorted } -func (m *MultihashIndexSorted) Marshal(w io.Writer) error { +func (m *MultihashIndexSorted) Marshal(w io.Writer) (uint64, error) { if err := binary.Write(w, binary.LittleEndian, int32(len(*m))); err != nil { - return err + return 4, err } // The codes are unique, but ranging over a map isn't deterministic. // As per the CARv2 spec, we must order buckets by digest length. // TODO update CARv2 spec to reflect this for the new index type. codes := m.sortedMultihashCodes() + l := uint64(4) for _, code := range codes { mwci := (*m)[code] - if err := mwci.Marshal(w); err != nil { - return err + n, err := mwci.Marshal(w) + l += n + if err != nil { + return l, err } } - return nil + return l, nil } func (m *MultihashIndexSorted) sortedMultihashCodes() []uint64 { diff --git a/ipld/car/v2/index/mhindexsorted_test.go b/ipld/car/v2/index/mhindexsorted_test.go index e02ba0599b..79fc9c5f01 100644 --- a/ipld/car/v2/index/mhindexsorted_test.go +++ b/ipld/car/v2/index/mhindexsorted_test.go @@ -32,7 +32,7 @@ func TestMultiWidthCodedIndex_MarshalUnmarshal(t *testing.T) { // Marshal the index. buf := new(bytes.Buffer) - err = subject.Marshal(buf) + _, err = subject.Marshal(buf) require.NoError(t, err) // Unmarshal it back to another instance of mh sorted index. diff --git a/ipld/car/v2/internal/loader/counting_loader.go b/ipld/car/v2/internal/loader/counting_loader.go new file mode 100644 index 0000000000..e428993ea4 --- /dev/null +++ b/ipld/car/v2/internal/loader/counting_loader.go @@ -0,0 +1,61 @@ +package loader + +import ( + "bytes" + "io" + + "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/linking" + "github.com/multiformats/go-varint" +) + +// counter tracks how much data has been read. +type counter struct { + totalRead uint64 +} + +func (c *counter) Size() uint64 { + return c.totalRead +} + +// ReadCounter provides an externally consumable interface to the +// additional data tracked about the linksystem. +type ReadCounter interface { + Size() uint64 +} + +type countingReader struct { + r io.Reader + c *counter +} + +func (c *countingReader) Read(p []byte) (int, error) { + n, err := c.r.Read(p) + c.c.totalRead += uint64(n) + return n, err +} + +// CountingLinkSystem wraps an ipld linksystem with to track the size of +// data loaded in a `counter` object. Each time nodes are loaded from the +// link system which trigger block reads, the size of the block as it would +// appear in a CAR file is added to the counter (included the size of the +// CID and the varint length for the block data). +func CountingLinkSystem(ls ipld.LinkSystem) (ipld.LinkSystem, ReadCounter) { + c := counter{} + clc := ls + clc.StorageReadOpener = func(lc linking.LinkContext, l ipld.Link) (io.Reader, error) { + r, err := ls.StorageReadOpener(lc, l) + if err != nil { + return nil, err + } + buf := bytes.NewBuffer(nil) + n, err := buf.ReadFrom(r) + if err != nil { + return nil, err + } + size := varint.ToUvarint(uint64(n) + uint64(len(l.Binary()))) + c.totalRead += uint64(len(size)) + uint64(len(l.Binary())) + return &countingReader{buf, &c}, nil + } + return clc, &c +} diff --git a/ipld/car/v2/internal/loader/writing_loader.go b/ipld/car/v2/internal/loader/writing_loader.go new file mode 100644 index 0000000000..13236f1c60 --- /dev/null +++ b/ipld/car/v2/internal/loader/writing_loader.go @@ -0,0 +1,114 @@ +package loader + +import ( + "bytes" + "io" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/linking" + "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-varint" +) + +type writerOutput struct { + w io.Writer + size uint64 + code multicodec.Code + rcrds []index.Record +} + +func (w *writerOutput) Size() uint64 { + return w.size +} + +func (w *writerOutput) Index() (index.Index, error) { + idx, err := index.New(w.code) + if err != nil { + return nil, err + } + if err := idx.Load(w.rcrds); err != nil { + return nil, err + } + + return idx, nil +} + +// An IndexTracker tracks the records loaded/written, calculate an +// index based on them. +type IndexTracker interface { + ReadCounter + Index() (index.Index, error) +} + +type writingReader struct { + r io.Reader + len int64 + cid string + wo *writerOutput +} + +func (w *writingReader) Read(p []byte) (int, error) { + if w.wo != nil { + // write the cid + size := varint.ToUvarint(uint64(w.len) + uint64(len(w.cid))) + if _, err := w.wo.w.Write(size); err != nil { + return 0, err + } + if _, err := w.wo.w.Write([]byte(w.cid)); err != nil { + return 0, err + } + cpy := bytes.NewBuffer(w.r.(*bytes.Buffer).Bytes()) + if _, err := cpy.WriteTo(w.wo.w); err != nil { + return 0, err + } + + // maybe write the index. + if w.wo.code != index.CarIndexNone { + _, c, err := cid.CidFromBytes([]byte(w.cid)) + if err != nil { + return 0, err + } + w.wo.rcrds = append(w.wo.rcrds, index.Record{ + Cid: c, + Offset: w.wo.size, + }) + } + w.wo.size += uint64(w.len) + uint64(len(size)+len(w.cid)) + + w.wo = nil + } + + return w.r.Read(p) +} + +// TeeingLinkSystem wraps an IPLD.LinkSystem so that each time a block is loaded from it, +// that block is also written as a CAR block to the provided io.Writer. Metadata +// (the size of data written) is provided in the second return value. +// The `initialOffset` is used to calculate the offsets recorded for the index, and will be +// included in the `.Size()` of the IndexTracker. +// An indexCodec of `index.CarIndexNoIndex` can be used to not track these offsets. +func TeeingLinkSystem(ls ipld.LinkSystem, w io.Writer, initialOffset uint64, indexCodec multicodec.Code) (ipld.LinkSystem, IndexTracker) { + wo := writerOutput{ + w: w, + size: initialOffset, + code: indexCodec, + rcrds: make([]index.Record, 0), + } + + tls := ls + tls.StorageReadOpener = func(lc linking.LinkContext, l ipld.Link) (io.Reader, error) { + r, err := ls.StorageReadOpener(lc, l) + if err != nil { + return nil, err + } + buf := bytes.NewBuffer(nil) + n, err := buf.ReadFrom(r) + if err != nil { + return nil, err + } + return &writingReader{buf, n, l.Binary(), &wo}, nil + } + return tls, &wo +} diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index 5a3b1930a2..2fb1d1fc47 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -1,6 +1,11 @@ package car -import "github.com/multiformats/go-multicodec" +import ( + "math" + + "github.com/ipld/go-car/v2/index" + "github.com/multiformats/go-multicodec" +) // DefaultMaxIndexCidSize specifies the maximum size in byptes accepted as a section CID by CARv2 index. const DefaultMaxIndexCidSize = 2 << 10 // 2 KiB @@ -33,6 +38,7 @@ type Options struct { BlockstoreAllowDuplicatePuts bool BlockstoreUseWholeCIDs bool + MaxTraversalLinks uint64 } // ApplyOptions applies given opts and returns the resulting Options. @@ -40,6 +46,7 @@ type Options struct { // side effect of Option. func ApplyOptions(opt ...Option) Options { var opts Options + opts.MaxTraversalLinks = math.MaxInt64 //default: traverse all for _, o := range opt { o(&opts) } @@ -84,6 +91,13 @@ func UseIndexCodec(c multicodec.Code) Option { } } +// WithoutIndex flags that no index should be included in generation. +func WithoutIndex() Option { + return func(o *Options) { + o.IndexCodec = index.CarIndexNone + } +} + // StoreIdentityCIDs sets whether to persist sections that are referenced by // CIDs with multihash.IDENTITY digest. // When writing CAR files with this option, diff --git a/ipld/car/v2/options_test.go b/ipld/car/v2/options_test.go index 307568a4ad..7e060acf09 100644 --- a/ipld/car/v2/options_test.go +++ b/ipld/car/v2/options_test.go @@ -1,6 +1,7 @@ package car_test import ( + "math" "testing" carv2 "github.com/ipld/go-car/v2" @@ -11,8 +12,9 @@ import ( func TestApplyOptions_SetsExpectedDefaults(t *testing.T) { require.Equal(t, carv2.Options{ - IndexCodec: multicodec.CarMultihashIndexSorted, - MaxIndexCidSize: carv2.DefaultMaxIndexCidSize, + IndexCodec: multicodec.CarMultihashIndexSorted, + MaxIndexCidSize: carv2.DefaultMaxIndexCidSize, + MaxTraversalLinks: math.MaxInt64, }, carv2.ApplyOptions()) } @@ -27,6 +29,7 @@ func TestApplyOptions_AppliesOptions(t *testing.T) { StoreIdentityCIDs: true, BlockstoreAllowDuplicatePuts: true, BlockstoreUseWholeCIDs: true, + MaxTraversalLinks: math.MaxInt64, }, carv2.ApplyOptions( carv2.UseDataPadding(123), diff --git a/ipld/car/v2/selective.go b/ipld/car/v2/selective.go new file mode 100644 index 0000000000..22351e715c --- /dev/null +++ b/ipld/car/v2/selective.go @@ -0,0 +1,269 @@ +package car + +import ( + "context" + "fmt" + "io" + "math" + "os" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/ipld/go-car/v2/internal/loader" + ipld "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/linking" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/node/basicnode" + "github.com/ipld/go-ipld-prime/traversal" + "github.com/ipld/go-ipld-prime/traversal/selector" +) + +// ErrSizeMismatch is returned when a written traversal realizes the written header size does not +// match the actual number of car bytes written. +var ErrSizeMismatch = fmt.Errorf("car-error-sizemismatch") + +// ErrOffsetImpossible is returned when specified paddings or offsets of either a wrapped carv1 +// or index cannot be satisfied based on the data being written. +var ErrOffsetImpossible = fmt.Errorf("car-error-offsetimpossible") + +// MaxTraversalLinks changes the allowed number of links a selector traversal +// can execute before failing. +// +// Note that setting this option may cause an error to be returned from selector +// execution when building a SelectiveCar. +func MaxTraversalLinks(MaxTraversalLinks uint64) Option { + return func(sco *Options) { + sco.MaxTraversalLinks = MaxTraversalLinks + } +} + +// NewSelectiveWriter walks through the proposed dag traversal to learn its total size in order to be able to +// stream out a car to a writer in the expected traversal order in one go. +func NewSelectiveWriter(ctx context.Context, ls *ipld.LinkSystem, root cid.Cid, selector ipld.Node, opts ...Option) (Writer, error) { + cls, cntr := loader.CountingLinkSystem(*ls) + + c1h := carv1.CarHeader{Roots: []cid.Cid{root}, Version: 1} + headSize, err := carv1.HeaderSize(&c1h) + if err != nil { + return nil, err + } + if err := traverse(ctx, &cls, root, selector, ApplyOptions(opts...)); err != nil { + return nil, err + } + tc := traversalCar{ + size: headSize + cntr.Size(), + ctx: ctx, + root: root, + selector: selector, + ls: ls, + opts: ApplyOptions(opts...), + } + return &tc, nil +} + +// TraverseToFile writes a car file matching a given root and selector to the +// path at `destination` using one read of each block. +func TraverseToFile(ctx context.Context, ls *ipld.LinkSystem, root cid.Cid, selector ipld.Node, destination string, opts ...Option) error { + tc := traversalCar{ + size: 0, + ctx: ctx, + root: root, + selector: selector, + ls: ls, + opts: ApplyOptions(opts...), + } + + fp, err := os.Create(destination) + if err != nil { + return err + } + defer fp.Close() + + _, err = tc.WriteTo(fp) + if err != nil { + return err + } + + // fix header size. + if _, err = fp.Seek(0, 0); err != nil { + return err + } + + tc.size = uint64(tc.size) + if _, err = tc.WriteV2Header(fp); err != nil { + return err + } + + return nil +} + +// TraverseV1 walks through the proposed dag traversal and writes a carv1 to the provided io.Writer +func TraverseV1(ctx context.Context, ls *ipld.LinkSystem, root cid.Cid, selector ipld.Node, writer io.Writer, opts ...Option) (uint64, error) { + opts = append(opts, WithoutIndex()) + tc := traversalCar{ + size: 0, + ctx: ctx, + root: root, + selector: selector, + ls: ls, + opts: ApplyOptions(opts...), + } + + len, _, err := tc.WriteV1(writer) + return len, err +} + +// Writer is an interface allowing writing a car prepared by PrepareTraversal +type Writer interface { + io.WriterTo +} + +var _ Writer = (*traversalCar)(nil) + +type traversalCar struct { + size uint64 + ctx context.Context + root cid.Cid + selector ipld.Node + ls *ipld.LinkSystem + opts Options +} + +func (tc *traversalCar) WriteTo(w io.Writer) (int64, error) { + n, err := tc.WriteV2Header(w) + if err != nil { + return n, err + } + v1s, idx, err := tc.WriteV1(w) + n += int64(v1s) + + if err != nil { + return n, err + } + + // index padding, then index + if tc.opts.IndexCodec != index.CarIndexNone { + if tc.opts.IndexPadding > 0 { + buf := make([]byte, tc.opts.IndexPadding) + pn, err := w.Write(buf) + n += int64(pn) + if err != nil { + return n, err + } + } + in, err := index.WriteTo(idx, w) + n += int64(in) + if err != nil { + return n, err + } + } + + return n, err +} + +func (tc *traversalCar) WriteV2Header(w io.Writer) (int64, error) { + n, err := w.Write(Pragma) + if err != nil { + return int64(n), err + } + + h := NewHeader(tc.size) + if p := tc.opts.DataPadding; p > 0 { + h = h.WithDataPadding(p) + } + if p := tc.opts.IndexPadding; p > 0 { + h = h.WithIndexPadding(p) + } + if tc.opts.IndexCodec == index.CarIndexNone { + h.IndexOffset = 0 + } + hn, err := h.WriteTo(w) + if err != nil { + return int64(n) + hn, err + } + hn += int64(n) + + // We include the initial data padding after the carv2 header + if h.DataOffset > uint64(hn) { + // TODO: buffer writes if this needs to be big. + buf := make([]byte, h.DataOffset-uint64(hn)) + n, err = w.Write(buf) + hn += int64(n) + if err != nil { + return hn, err + } + } else if h.DataOffset < uint64(hn) { + return hn, ErrOffsetImpossible + } + + return hn, nil +} + +func (tc *traversalCar) WriteV1(w io.Writer) (uint64, index.Index, error) { + // write the v1 header + c1h := carv1.CarHeader{Roots: []cid.Cid{tc.root}, Version: 1} + if err := carv1.WriteHeader(&c1h, w); err != nil { + return 0, nil, err + } + v1Size, err := carv1.HeaderSize(&c1h) + if err != nil { + return v1Size, nil, err + } + + // write the block. + wls, writer := loader.TeeingLinkSystem(*tc.ls, w, v1Size, tc.opts.IndexCodec) + err = traverse(tc.ctx, &wls, tc.root, tc.selector, tc.opts) + v1Size = writer.Size() + if err != nil { + return v1Size, nil, err + } + if tc.size != 0 && tc.size != v1Size { + return v1Size, nil, ErrSizeMismatch + } + tc.size = v1Size + + if tc.opts.IndexCodec == index.CarIndexNone { + return v1Size, nil, nil + } + idx, err := writer.Index() + return v1Size, idx, err +} + +func traverse(ctx context.Context, ls *ipld.LinkSystem, root cid.Cid, s ipld.Node, opts Options) error { + sel, err := selector.CompileSelector(s) + if err != nil { + return err + } + + progress := traversal.Progress{ + Cfg: &traversal.Config{ + Ctx: ctx, + LinkSystem: *ls, + LinkTargetNodePrototypeChooser: func(_ ipld.Link, _ linking.LinkContext) (ipld.NodePrototype, error) { + return basicnode.Prototype.Any, nil + }, + LinkVisitOnlyOnce: !opts.BlockstoreAllowDuplicatePuts, + }, + } + if opts.MaxTraversalLinks < math.MaxInt64 { + progress.Budget = &traversal.Budget{ + NodeBudget: math.MaxInt64, + LinkBudget: int64(opts.MaxTraversalLinks), + } + } + + lnk := cidlink.Link{Cid: root} + ls.TrustedStorage = true + rootNode, err := ls.Load(ipld.LinkContext{}, lnk, basicnode.Prototype.Any) + if err != nil { + return fmt.Errorf("root blk load failed: %s", err) + } + err = progress.WalkMatching(rootNode, sel, func(_ traversal.Progress, _ ipld.Node) error { + return nil + }) + if err != nil { + return fmt.Errorf("walk failed: %s", err) + } + return nil +} diff --git a/ipld/car/v2/selective_test.go b/ipld/car/v2/selective_test.go new file mode 100644 index 0000000000..f2bba6f8ac --- /dev/null +++ b/ipld/car/v2/selective_test.go @@ -0,0 +1,83 @@ +package car_test + +import ( + "bytes" + "context" + "os" + "path" + "testing" + + "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/storage/bsadapter" + selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" + "github.com/stretchr/testify/require" + + _ "github.com/ipld/go-codec-dagpb" + _ "github.com/ipld/go-ipld-prime/codec/dagcbor" + _ "github.com/ipld/go-ipld-prime/codec/raw" +) + +func TestPrepareTraversal(t *testing.T) { + from, err := blockstore.OpenReadOnly("testdata/sample-unixfs-v2.car") + require.NoError(t, err) + ls := cidlink.DefaultLinkSystem() + bsa := bsadapter.Adapter{Wrapped: from} + ls.SetReadStorage(&bsa) + + rts, _ := from.Roots() + writer, err := car.NewSelectiveWriter(context.Background(), &ls, rts[0], selectorparse.CommonSelector_ExploreAllRecursively) + require.NoError(t, err) + + buf := bytes.Buffer{} + n, err := writer.WriteTo(&buf) + require.NoError(t, err) + require.Equal(t, int64(len(buf.Bytes())), n) + + fi, _ := os.Stat("testdata/sample-unixfs-v2.car") + require.Equal(t, fi.Size(), n) + + // Headers should be equal + h1, _ := car.OpenReader("testdata/sample-unixfs-v2.car") + h1h := bytes.Buffer{} + h1h.Write(car.Pragma) + h1.Header.WriteTo(&h1h) + require.Equal(t, buf.Bytes()[:h1h.Len()], h1h.Bytes()) +} + +func TestFileTraversal(t *testing.T) { + from, err := blockstore.OpenReadOnly("testdata/sample-unixfs-v2.car") + require.NoError(t, err) + ls := cidlink.DefaultLinkSystem() + bsa := bsadapter.Adapter{Wrapped: from} + ls.SetReadStorage(&bsa) + + rts, _ := from.Roots() + outDir := t.TempDir() + err = car.TraverseToFile(context.Background(), &ls, rts[0], selectorparse.CommonSelector_ExploreAllRecursively, path.Join(outDir, "out.car")) + require.NoError(t, err) + + require.FileExists(t, path.Join(outDir, "out.car")) + + fa, _ := os.Stat("testdata/sample-unixfs-v2.car") + fb, _ := os.Stat(path.Join(outDir, "out.car")) + require.Equal(t, fa.Size(), fb.Size()) +} + +func TestV1Traversal(t *testing.T) { + from, err := blockstore.OpenReadOnly("testdata/sample-v1.car") + require.NoError(t, err) + ls := cidlink.DefaultLinkSystem() + bsa := bsadapter.Adapter{Wrapped: from} + ls.SetReadStorage(&bsa) + + rts, _ := from.Roots() + w := bytes.NewBuffer(nil) + n, err := car.TraverseV1(context.Background(), &ls, rts[0], selectorparse.CommonSelector_ExploreAllRecursively, w) + require.NoError(t, err) + require.Equal(t, int64(len(w.Bytes())), int64(n)) + + fa, _ := os.Stat("testdata/sample-v1.car") + require.Equal(t, fa.Size(), int64(n)) +} diff --git a/ipld/car/v2/testdata/sample-unixfs-v2.car b/ipld/car/v2/testdata/sample-unixfs-v2.car new file mode 100644 index 0000000000000000000000000000000000000000..0d8d8fd6451f778cc3ed8d936714ebe286e659eb GIT binary patch literal 485 zcmd;Dm|m7zRGgWg$HagJjG=rPMhL?nN?R>TEy~X?DQ>)>6`{(&SRkapW#UnI@7eJX zh5E>xKU3FfURkh6u0VNGRBWQELhhASTVPf&8Zd?mDXjFE;IGf`KD)nhdZxF5gg39= z#e-+H7jKM;zNqQv}B@#SbOdO1%#5i0?8)z~yE?`WQU;vrg$N>r@ gA!x{f*cw1w0p$}4r(;lcghI#~s*X^= Date: Thu, 9 Dec 2021 17:20:40 -0800 Subject: [PATCH 191/291] update context datastore This commit was moved from ipld/go-car@b04ac83786cb9ddd9b632a921004fede96c7ff73 --- ipld/car/car.go | 22 +++++++++++----------- ipld/car/car_test.go | 5 +++-- ipld/car/selectivecar.go | 6 +++--- ipld/car/selectivecar_test.go | 13 +++++++------ 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 51826706d5..bf773779a4 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -20,11 +20,11 @@ func init() { } type Store interface { - Put(blocks.Block) error + Put(context.Context, blocks.Block) error } type ReadStore interface { - Get(cid.Cid) (blocks.Block, error) + Get(context.Context, cid.Cid) (blocks.Block, error) } type CarHeader struct { @@ -163,30 +163,30 @@ func (cr *CarReader) Next() (blocks.Block, error) { } type batchStore interface { - PutMany([]blocks.Block) error + PutMany(context.Context, []blocks.Block) error } -func LoadCar(s Store, r io.Reader) (*CarHeader, error) { +func LoadCar(ctx context.Context, s Store, r io.Reader) (*CarHeader, error) { cr, err := NewCarReader(r) if err != nil { return nil, err } if bs, ok := s.(batchStore); ok { - return loadCarFast(bs, cr) + return loadCarFast(ctx, bs, cr) } - return loadCarSlow(s, cr) + return loadCarSlow(ctx, s, cr) } -func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { +func loadCarFast(ctx context.Context, s batchStore, cr *CarReader) (*CarHeader, error) { var buf []blocks.Block for { blk, err := cr.Next() if err != nil { if err == io.EOF { if len(buf) > 0 { - if err := s.PutMany(buf); err != nil { + if err := s.PutMany(ctx, buf); err != nil { return nil, err } } @@ -198,7 +198,7 @@ func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { buf = append(buf, blk) if len(buf) > 1000 { - if err := s.PutMany(buf); err != nil { + if err := s.PutMany(ctx, buf); err != nil { return nil, err } buf = buf[:0] @@ -206,7 +206,7 @@ func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { } } -func loadCarSlow(s Store, cr *CarReader) (*CarHeader, error) { +func loadCarSlow(ctx context.Context, s Store, cr *CarReader) (*CarHeader, error) { for { blk, err := cr.Next() if err != nil { @@ -216,7 +216,7 @@ func loadCarSlow(s Store, cr *CarReader) (*CarHeader, error) { return nil, err } - if err := s.Put(blk); err != nil { + if err := s.Put(ctx, blk); err != nil { return nil, err } } diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 0a9c80fedf..9ae309099a 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -24,6 +24,7 @@ func assertAddNodes(t *testing.T, ds format.DAGService, nds ...format.Node) { } func TestRoundtrip(t *testing.T) { + ctx := context.Background() dserv := dstest.Mock() a := merkledag.NewRawNode([]byte("aaaa")) b := merkledag.NewRawNode([]byte("bbbb")) @@ -48,7 +49,7 @@ func TestRoundtrip(t *testing.T) { } bserv := dstest.Bserv() - ch, err := car.LoadCar(bserv.Blockstore(), buf) + ch, err := car.LoadCar(ctx, bserv.Blockstore(), buf) if err != nil { t.Fatal(err) } @@ -63,7 +64,7 @@ func TestRoundtrip(t *testing.T) { bs := bserv.Blockstore() for _, nd := range []format.Node{a, b, c, nd1, nd2, nd3} { - has, err := bs.Has(nd.Cid()) + has, err := bs.Has(ctx, nd.Cid()) if err != nil { t.Fatal(err) } diff --git a/ipld/car/selectivecar.go b/ipld/car/selectivecar.go index 9b5bd8cef4..14c5b05aba 100644 --- a/ipld/car/selectivecar.go +++ b/ipld/car/selectivecar.go @@ -140,7 +140,7 @@ func (sc SelectiveCarPrepared) Cids() []cid.Cid { // Dump writes the car file as quickly as possible based on information already // collected -func (sc SelectiveCarPrepared) Dump(w io.Writer) error { +func (sc SelectiveCarPrepared) Dump(ctx context.Context, w io.Writer) error { offset, err := HeaderSize(&sc.header) if err != nil { return fmt.Errorf("failed to size car header: %s", err) @@ -149,7 +149,7 @@ func (sc SelectiveCarPrepared) Dump(w io.Writer) error { return fmt.Errorf("failed to write car header: %s", err) } for _, c := range sc.cids { - blk, err := sc.store.Get(c) + blk, err := sc.store.Get(ctx, c) if err != nil { return err } @@ -223,7 +223,7 @@ func (sct *selectiveCarTraverser) loader(ctx ipld.LinkContext, lnk ipld.Link) (i return nil, errors.New("incorrect link type") } c := cl.Cid - blk, err := sct.sc.store.Get(c) + blk, err := sct.sc.store.Get(ctx.Ctx, c) if err != nil { return nil, err } diff --git a/ipld/car/selectivecar_test.go b/ipld/car/selectivecar_test.go index 387203ff83..59a4c8e1ff 100644 --- a/ipld/car/selectivecar_test.go +++ b/ipld/car/selectivecar_test.go @@ -19,6 +19,7 @@ import ( ) func TestRoundtripSelective(t *testing.T) { + ctx := context.Background() sourceBserv := dstest.Bserv() sourceBs := sourceBserv.Blockstore() dserv := merkledag.NewDAGService(sourceBserv) @@ -79,7 +80,7 @@ func TestRoundtripSelective(t *testing.T) { }) require.NoError(t, err) buf2 := new(bytes.Buffer) - err = scp.Dump(buf2) + err = scp.Dump(ctx, buf2) require.NoError(t, err) // verify preparation step correctly assesed length and blocks @@ -94,7 +95,7 @@ func TestRoundtripSelective(t *testing.T) { // readout car and verify contents bserv := dstest.Bserv() - ch, err := car.LoadCar(bserv.Blockstore(), buf) + ch, err := car.LoadCar(ctx, bserv.Blockstore(), buf) require.NoError(t, err) require.Equal(t, len(ch.Roots), 1) @@ -102,13 +103,13 @@ func TestRoundtripSelective(t *testing.T) { bs := bserv.Blockstore() for _, nd := range []format.Node{a, b, nd1, nd2, nd3} { - has, err := bs.Has(nd.Cid()) + has, err := bs.Has(ctx, nd.Cid()) require.NoError(t, err) require.True(t, has) } for _, nd := range []format.Node{c} { - has, err := bs.Has(nd.Cid()) + has, err := bs.Has(ctx, nd.Cid()) require.NoError(t, err) require.False(t, has) } @@ -221,7 +222,7 @@ type countingReadStore struct { count int } -func (rs *countingReadStore) Get(c cid.Cid) (blocks.Block, error) { +func (rs *countingReadStore) Get(ctx context.Context, c cid.Cid) (blocks.Block, error) { rs.count++ - return rs.bs.Get(c) + return rs.bs.Get(ctx, c) } From 6226033d34f29554e4a427e41d0a353faae66473 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Fri, 10 Dec 2021 19:09:42 -0500 Subject: [PATCH 192/291] Update v2 to context datastores (#275) This commit was moved from ipld/go-car@be2525f6bf2d9d9eda818b41cd275521b3ddca9b --- ipld/car/v2/bench_test.go | 3 +- ipld/car/v2/blockstore/bench_test.go | 3 +- ipld/car/v2/blockstore/example_test.go | 16 +-- ipld/car/v2/blockstore/readonly.go | 12 +-- ipld/car/v2/blockstore/readonly_test.go | 30 +++--- ipld/car/v2/blockstore/readwrite.go | 20 ++-- ipld/car/v2/blockstore/readwrite_test.go | 120 +++++++++++++---------- ipld/car/v2/example_test.go | 3 +- ipld/car/v2/internal/carv1/car.go | 21 ++-- ipld/car/v2/internal/carv1/car_test.go | 2 +- 10 files changed, 130 insertions(+), 100 deletions(-) diff --git a/ipld/car/v2/bench_test.go b/ipld/car/v2/bench_test.go index 15ae0c24fa..6e76693592 100644 --- a/ipld/car/v2/bench_test.go +++ b/ipld/car/v2/bench_test.go @@ -1,6 +1,7 @@ package car_test import ( + "context" "io" "math/rand" "os" @@ -139,7 +140,7 @@ func generateRandomCarV2File(b *testing.B, path string, minTotalBlockSize int) { } blk := merkledag.NewRawNode(buf) - if err := bs.Put(blk); err != nil { + if err := bs.Put(context.TODO(), blk); err != nil { b.Fatal(err) } totalBlockSize += size diff --git a/ipld/car/v2/blockstore/bench_test.go b/ipld/car/v2/blockstore/bench_test.go index 644acbe95c..57544b967b 100644 --- a/ipld/car/v2/blockstore/bench_test.go +++ b/ipld/car/v2/blockstore/bench_test.go @@ -1,6 +1,7 @@ package blockstore_test import ( + "context" "io" mathrand "math/rand" "os" @@ -65,7 +66,7 @@ func BenchmarkOpenReadOnlyV1(b *testing.B) { } for _, c := range shuffledCIDs { - _, err := bs.Get(c) + _, err := bs.Get(context.TODO(), c) if err != nil { b.Fatal(err) } diff --git a/ipld/car/v2/blockstore/example_test.go b/ipld/car/v2/blockstore/example_test.go index 7e826addb0..198443c9d8 100644 --- a/ipld/car/v2/blockstore/example_test.go +++ b/ipld/car/v2/blockstore/example_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "time" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" @@ -57,7 +58,7 @@ func ExampleOpenReadOnly() { cancel() break } - size, err := robs.GetSize(k) + size, err := robs.GetSize(context.TODO(), k) if err != nil { panic(err) } @@ -78,6 +79,9 @@ func ExampleOpenReadOnly() { // ExampleOpenReadWrite creates a read-write blockstore and puts func ExampleOpenReadWrite() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + thisBlock := merkledag.NewRawNode([]byte("fish")).Block thatBlock := merkledag.NewRawNode([]byte("lobster")).Block andTheOtherBlock := merkledag.NewRawNode([]byte("barreleye")).Block @@ -96,13 +100,13 @@ func ExampleOpenReadWrite() { // Put all blocks onto the blockstore. blocks := []blocks.Block{thisBlock, thatBlock} - if err := rwbs.PutMany(blocks); err != nil { + if err := rwbs.PutMany(ctx, blocks); err != nil { panic(err) } fmt.Printf("Successfully wrote %v blocks into the blockstore.\n", len(blocks)) // Any blocks put can be read back using the same blockstore instance. - block, err := rwbs.Get(thatBlock.Cid()) + block, err := rwbs.Get(ctx, thatBlock.Cid()) if err != nil { panic(err) } @@ -122,13 +126,13 @@ func ExampleOpenReadWrite() { } // Put another block, appending it to the set of blocks that are written previously. - if err := resumedRwbos.Put(andTheOtherBlock); err != nil { + if err := resumedRwbos.Put(ctx, andTheOtherBlock); err != nil { panic(err) } // Read back the the block put before resumption. // Blocks previously put are present. - block, err = resumedRwbos.Get(thatBlock.Cid()) + block, err = resumedRwbos.Get(ctx, thatBlock.Cid()) if err != nil { panic(err) } @@ -136,7 +140,7 @@ func ExampleOpenReadWrite() { // Put an additional block to the CAR. // Blocks put after resumption are also present. - block, err = resumedRwbos.Get(andTheOtherBlock.Cid()) + block, err = resumedRwbos.Get(ctx, andTheOtherBlock.Cid()) if err != nil { panic(err) } diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index e25f512519..31636d4e4c 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -186,13 +186,13 @@ func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { } // DeleteBlock is unsupported and always errors. -func (b *ReadOnly) DeleteBlock(_ cid.Cid) error { +func (b *ReadOnly) DeleteBlock(_ context.Context, _ cid.Cid) error { return errReadOnly } // Has indicates if the store contains a block that corresponds to the given key. // This function always returns true for any given key with multihash.IDENTITY code. -func (b *ReadOnly) Has(key cid.Cid) (bool, error) { +func (b *ReadOnly) Has(ctx context.Context, key cid.Cid) (bool, error) { // Check if the given CID has multihash.IDENTITY code // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. if _, ok, err := isIdentity(key); err != nil { @@ -241,7 +241,7 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { // Get gets a block corresponding to the given key. // This API will always return true if the given key has multihash.IDENTITY code. -func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { +func (b *ReadOnly) Get(ctx context.Context, key cid.Cid) (blocks.Block, error) { // Check if the given CID has multihash.IDENTITY code // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. if digest, ok, err := isIdentity(key); err != nil { @@ -293,7 +293,7 @@ func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { } // GetSize gets the size of an item corresponding to the given key. -func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { +func (b *ReadOnly) GetSize(ctx context.Context, key cid.Cid) (int, error) { // Check if the given CID has multihash.IDENTITY code // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. if digest, ok, err := isIdentity(key); err != nil { @@ -361,12 +361,12 @@ func isIdentity(key cid.Cid) (digest []byte, ok bool, err error) { } // Put is not supported and always returns an error. -func (b *ReadOnly) Put(blocks.Block) error { +func (b *ReadOnly) Put(context.Context, blocks.Block) error { return errReadOnly } // PutMany is not supported and always returns an error. -func (b *ReadOnly) PutMany([]blocks.Block) error { +func (b *ReadOnly) PutMany(context.Context, []blocks.Block) error { return errReadOnly } diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 4922acd1b6..0ac2b7d169 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -24,7 +24,7 @@ func TestReadOnlyGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) { nonExistingKey := merkledag.NewRawNode([]byte("lobstermuncher")).Block.Cid() // Assert blockstore API returns blockstore.ErrNotFound - gotBlock, err := subject.Get(nonExistingKey) + gotBlock, err := subject.Get(context.TODO(), nonExistingKey) require.Equal(t, blockstore.ErrNotFound, err) require.Nil(t, gotBlock) } @@ -58,6 +58,7 @@ func TestReadOnly(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() subject, err := OpenReadOnly(tt.v1OrV2path, tt.opts...) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, subject.Close()) }) @@ -87,25 +88,25 @@ func TestReadOnly(t *testing.T) { wantCids = append(wantCids, key) // Assert blockstore contains key. - has, err := subject.Has(key) + has, err := subject.Has(ctx, key) require.NoError(t, err) require.True(t, has) // Assert size matches block raw data length. - gotSize, err := subject.GetSize(key) + gotSize, err := subject.GetSize(ctx, key) wantSize := len(wantBlock.RawData()) require.NoError(t, err) require.Equal(t, wantSize, gotSize) // Assert block itself matches v1 payload block. - gotBlock, err := subject.Get(key) + gotBlock, err := subject.Get(ctx, key) require.NoError(t, err) require.Equal(t, wantBlock, gotBlock) // Assert write operations error - require.Error(t, subject.Put(wantBlock)) - require.Error(t, subject.PutMany([]blocks.Block{wantBlock})) - require.Error(t, subject.DeleteBlock(key)) + require.Error(t, subject.Put(ctx, wantBlock)) + require.Error(t, subject.PutMany(ctx, []blocks.Block{wantBlock})) + require.Error(t, subject.DeleteBlock(ctx, key)) } ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) @@ -239,15 +240,16 @@ func newV1Reader(r io.Reader, zeroLenSectionAsEOF bool) (*carv1.CarReader, error func TestReadOnlyErrorAfterClose(t *testing.T) { bs, err := OpenReadOnly("../testdata/sample-v1.car") + ctx := context.TODO() require.NoError(t, err) roots, err := bs.Roots() require.NoError(t, err) - _, err = bs.Has(roots[0]) + _, err = bs.Has(ctx, roots[0]) require.NoError(t, err) - _, err = bs.Get(roots[0]) + _, err = bs.Get(ctx, roots[0]) require.NoError(t, err) - _, err = bs.GetSize(roots[0]) + _, err = bs.GetSize(ctx, roots[0]) require.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) @@ -259,11 +261,11 @@ func TestReadOnlyErrorAfterClose(t *testing.T) { _, err = bs.Roots() require.Error(t, err) - _, err = bs.Has(roots[0]) + _, err = bs.Has(ctx, roots[0]) require.Error(t, err) - _, err = bs.Get(roots[0]) + _, err = bs.Get(ctx, roots[0]) require.Error(t, err) - _, err = bs.GetSize(roots[0]) + _, err = bs.GetSize(ctx, roots[0]) require.Error(t, err) _, err = bs.AllKeysChan(ctx) require.Error(t, err) @@ -293,7 +295,7 @@ func TestNewReadOnly_CarV1WithoutIndexWorksAsExpected(t *testing.T) { require.NoError(t, err) // Require that the block is found via ReadOnly API and contetns are as expected. - gotBlock, err := subject.Get(wantBlock.Cid()) + gotBlock, err := subject.Get(context.TODO(), wantBlock.Cid()) require.NoError(t, err) require.Equal(t, wantBlock, gotBlock) } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 4f288fa0ea..8b2ca90d5f 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -279,14 +279,14 @@ func (b *ReadWrite) unfinalize() error { } // Put puts a given block to the underlying datastore -func (b *ReadWrite) Put(blk blocks.Block) error { +func (b *ReadWrite) Put(ctx context.Context, blk blocks.Block) error { // PutMany already checks b.ronly.closed. - return b.PutMany([]blocks.Block{blk}) + return b.PutMany(ctx, []blocks.Block{blk}) } // PutMany puts a slice of blocks at the same time using batching // capabilities of the underlying datastore whenever possible. -func (b *ReadWrite) PutMany(blks []blocks.Block) error { +func (b *ReadWrite) PutMany(ctx context.Context, blks []blocks.Block) error { b.ronly.mu.Lock() defer b.ronly.mu.Unlock() @@ -393,19 +393,19 @@ func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { return b.ronly.AllKeysChan(ctx) } -func (b *ReadWrite) Has(key cid.Cid) (bool, error) { - return b.ronly.Has(key) +func (b *ReadWrite) Has(ctx context.Context, key cid.Cid) (bool, error) { + return b.ronly.Has(ctx, key) } -func (b *ReadWrite) Get(key cid.Cid) (blocks.Block, error) { - return b.ronly.Get(key) +func (b *ReadWrite) Get(ctx context.Context, key cid.Cid) (blocks.Block, error) { + return b.ronly.Get(ctx, key) } -func (b *ReadWrite) GetSize(key cid.Cid) (int, error) { - return b.ronly.GetSize(key) +func (b *ReadWrite) GetSize(ctx context.Context, key cid.Cid) (int, error) { + return b.ronly.GetSize(ctx, key) } -func (b *ReadWrite) DeleteBlock(_ cid.Cid) error { +func (b *ReadWrite) DeleteBlock(_ context.Context, _ cid.Cid) error { return fmt.Errorf("ReadWrite blockstore does not support deleting blocks") } diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index d58b6de3c8..90f5bb536a 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -42,7 +42,7 @@ func TestReadWriteGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) nonExistingKey := merkledag.NewRawNode([]byte("undadasea")).Block.Cid() // Assert blockstore API returns blockstore.ErrNotFound - gotBlock, err := subject.Get(nonExistingKey) + gotBlock, err := subject.Get(context.TODO(), nonExistingKey) require.Equal(t, ipfsblockstore.ErrNotFound, err) require.Nil(t, gotBlock) } @@ -71,13 +71,13 @@ func TestBlockstore(t *testing.T) { } require.NoError(t, err) - err = ingester.Put(b) + err = ingester.Put(ctx, b) require.NoError(t, err) cids = append(cids, b.Cid()) // try reading a random one: candidate := cids[rng.Intn(len(cids))] - if has, err := ingester.Has(candidate); !has || err != nil { + if has, err := ingester.Has(ctx, candidate); !has || err != nil { t.Fatalf("expected to find %s but didn't: %s", candidate, err) } @@ -89,7 +89,7 @@ func TestBlockstore(t *testing.T) { } for _, c := range cids { - b, err := ingester.Get(c) + b, err := ingester.Get(ctx, c) require.NoError(t, err) if !b.Cid().Equals(c) { t.Fatal("wrong item returned") @@ -106,7 +106,7 @@ func TestBlockstore(t *testing.T) { require.NoError(t, err) numKeysCh := 0 for c := range allKeysCh { - b, err := robs.Get(c) + b, err := robs.Get(ctx, c) require.NoError(t, err) if !b.Cid().Equals(c) { t.Fatal("wrong item returned") @@ -117,7 +117,7 @@ func TestBlockstore(t *testing.T) { require.Equal(t, expectedCidCount, numKeysCh, "AllKeysChan returned an unexpected amount of keys; expected %v but got %v", expectedCidCount, numKeysCh) for _, c := range cids { - b, err := robs.Get(c) + b, err := robs.Get(ctx, c) require.NoError(t, err) if !b.Cid().Equals(c) { t.Fatal("wrong item returned") @@ -127,6 +127,8 @@ func TestBlockstore(t *testing.T) { func TestBlockstorePutSameHashes(t *testing.T) { tdir := t.TempDir() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() // This blockstore allows duplicate puts, // and identifies by multihash as per the default. @@ -209,26 +211,26 @@ func TestBlockstorePutSameHashes(t *testing.T) { // Has should never error here. // The first block should be missing. // Others might not, given the duplicate hashes. - has, err := bs.Has(block.Cid()) + has, err := bs.Has(ctx, block.Cid()) require.NoError(t, err) if i == 0 { require.False(t, has) } - err = bs.Put(block) + err = bs.Put(ctx, block) require.NoError(t, err) // Has, Get, and GetSize need to work right after a Put. - has, err = bs.Has(block.Cid()) + has, err = bs.Has(ctx, block.Cid()) require.NoError(t, err) require.True(t, has) - got, err := bs.Get(block.Cid()) + got, err := bs.Get(ctx, block.Cid()) require.NoError(t, err) require.Equal(t, block.Cid(), got.Cid()) require.Equal(t, block.RawData(), got.RawData()) - size, err := bs.GetSize(block.Cid()) + size, err := bs.GetSize(ctx, block.Cid()) require.NoError(t, err) require.Equal(t, len(block.RawData()), size) } @@ -263,6 +265,9 @@ func TestBlockstorePutSameHashes(t *testing.T) { func TestBlockstoreConcurrentUse(t *testing.T) { wbs, err := blockstore.OpenReadWrite(filepath.Join(t.TempDir(), "readwrite.car"), nil) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + require.NoError(t, err) t.Cleanup(func() { wbs.Finalize() }) @@ -285,14 +290,14 @@ func TestBlockstoreConcurrentUse(t *testing.T) { block, err := blocks.NewBlockWithCid(data, c) require.NoError(t, err) - has, err := wbs.Has(block.Cid()) + has, err := wbs.Has(ctx, block.Cid()) require.NoError(t, err) require.False(t, has) - err = wbs.Put(block) + err = wbs.Put(ctx, block) require.NoError(t, err) - got, err := wbs.Get(block.Cid()) + got, err := wbs.Get(ctx, block.Cid()) require.NoError(t, err) require.Equal(t, data, got.RawData()) }() @@ -310,6 +315,9 @@ func (b bufferReaderAt) ReadAt(p []byte, off int64) (int, error) { } func TestBlockstoreNullPadding(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + paddedV1, err := ioutil.ReadFile("../testdata/sample-v1-with-zero-len-section.car") require.NoError(t, err) @@ -320,17 +328,14 @@ func TestBlockstoreNullPadding(t *testing.T) { roots, err := rbs.Roots() require.NoError(t, err) - has, err := rbs.Has(roots[0]) + has, err := rbs.Has(ctx, roots[0]) require.NoError(t, err) require.True(t, has) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - allKeysCh, err := rbs.AllKeysChan(ctx) require.NoError(t, err) for c := range allKeysCh { - b, err := rbs.Get(c) + b, err := rbs.Get(ctx, c) require.NoError(t, err) if !b.Cid().Equals(c) { t.Fatal("wrong item returned") @@ -339,6 +344,9 @@ func TestBlockstoreNullPadding(t *testing.T) { } func TestBlockstoreResumption(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + v1f, err := os.Open("../testdata/sample-v1.car") require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, v1f.Close()) }) @@ -390,7 +398,7 @@ func TestBlockstoreResumption(t *testing.T) { blockstore.UseWholeCIDs(true)) require.NoError(t, err) } - require.NoError(t, subject.Put(b)) + require.NoError(t, subject.Put(ctx, b)) // With 10% chance test read operations on an resumed read-write blockstore. // We don't test on every put to reduce test runtime. @@ -404,10 +412,10 @@ func TestBlockstoreResumption(t *testing.T) { keysChan, err := subject.AllKeysChan(ctx) require.NoError(t, err) for k := range keysChan { - has, err := subject.Has(k) + has, err := subject.Has(ctx, k) require.NoError(t, err) require.True(t, has) - gotBlock, err := subject.Get(k) + gotBlock, err := subject.Get(ctx, k) require.NoError(t, err) require.Equal(t, wantBlocks[k], gotBlock) gotBlockCountSoFar++ @@ -485,6 +493,9 @@ func TestBlockstoreResumptionIsSupportedOnFinalizedFile(t *testing.T) { } func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + oneTestBlockCid := oneTestBlockWithCidV1.Cid() anotherTestBlockCid := anotherTestBlockWithCidV0.Cid() wantRoots := []cid.Cid{oneTestBlockCid, anotherTestBlockCid} @@ -493,14 +504,14 @@ func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { subject, err := blockstore.OpenReadWrite(path, wantRoots) require.NoError(t, err) - require.NoError(t, subject.Put(oneTestBlockWithCidV1)) - require.NoError(t, subject.Put(anotherTestBlockWithCidV0)) + require.NoError(t, subject.Put(ctx, oneTestBlockWithCidV1)) + require.NoError(t, subject.Put(ctx, anotherTestBlockWithCidV0)) - gotBlock, err := subject.Get(oneTestBlockCid) + gotBlock, err := subject.Get(ctx, oneTestBlockCid) require.NoError(t, err) require.Equal(t, oneTestBlockWithCidV1, gotBlock) - gotSize, err := subject.GetSize(oneTestBlockCid) + gotSize, err := subject.GetSize(ctx, oneTestBlockCid) require.NoError(t, err) require.Equal(t, len(oneTestBlockWithCidV1.RawData()), gotSize) @@ -508,13 +519,13 @@ func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { require.NoError(t, err) require.Equal(t, wantRoots, gotRoots) - has, err := subject.Has(oneTestBlockCid) + has, err := subject.Has(ctx, oneTestBlockCid) require.NoError(t, err) require.True(t, has) subject.HashOnRead(true) // Delete should always error regardless of finalize - require.Error(t, subject.DeleteBlock(oneTestBlockCid)) + require.Error(t, subject.DeleteBlock(ctx, oneTestBlockCid)) require.NoError(t, subject.Finalize()) require.Error(t, subject.Finalize()) @@ -522,21 +533,24 @@ func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { _, ok := (interface{})(subject).(io.Closer) require.False(t, ok) - _, err = subject.Get(oneTestBlockCid) + _, err = subject.Get(ctx, oneTestBlockCid) require.Error(t, err) - _, err = subject.GetSize(anotherTestBlockCid) + _, err = subject.GetSize(ctx, anotherTestBlockCid) require.Error(t, err) - _, err = subject.Has(anotherTestBlockCid) + _, err = subject.Has(ctx, anotherTestBlockCid) require.Error(t, err) - require.Error(t, subject.Put(oneTestBlockWithCidV1)) - require.Error(t, subject.PutMany([]blocks.Block{anotherTestBlockWithCidV0})) + require.Error(t, subject.Put(ctx, oneTestBlockWithCidV1)) + require.Error(t, subject.PutMany(ctx, []blocks.Block{anotherTestBlockWithCidV0})) _, err = subject.AllKeysChan(context.Background()) require.Error(t, err) - require.Error(t, subject.DeleteBlock(oneTestBlockCid)) + require.Error(t, subject.DeleteBlock(ctx, oneTestBlockCid)) } func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + oneTestBlockCid := oneTestBlockWithCidV1.Cid() anotherTestBlockCid := anotherTestBlockWithCidV0.Cid() WantRoots := []cid.Cid{oneTestBlockCid, anotherTestBlockCid} @@ -550,8 +564,8 @@ func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { carv2.UseDataPadding(wantCarV1Padding), carv2.UseIndexPadding(wantIndexPadding)) require.NoError(t, err) - require.NoError(t, subject.Put(oneTestBlockWithCidV1)) - require.NoError(t, subject.Put(anotherTestBlockWithCidV0)) + require.NoError(t, subject.Put(ctx, oneTestBlockWithCidV1)) + require.NoError(t, subject.Put(ctx, anotherTestBlockWithCidV0)) require.NoError(t, subject.Finalize()) // Assert CARv2 header contains right offsets. @@ -649,7 +663,7 @@ func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing. WantRoots, carv2.UseDataPadding(1413)) require.NoError(t, err) - require.NoError(t, subject.Put(oneTestBlockWithCidV1)) + require.NoError(t, subject.Put(context.TODO(), oneTestBlockWithCidV1)) require.NoError(t, subject.Finalize()) resumingSubject, err := blockstore.OpenReadWrite( @@ -663,6 +677,9 @@ func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing. } func TestReadWriteErrorAfterClose(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + root := blocks.NewBlock([]byte("foo")) for _, closeMethod := range []func(*blockstore.ReadWrite){ (*blockstore.ReadWrite).Discard, @@ -672,16 +689,16 @@ func TestReadWriteErrorAfterClose(t *testing.T) { bs, err := blockstore.OpenReadWrite(path, []cid.Cid{root.Cid()}) require.NoError(t, err) - err = bs.Put(root) + err = bs.Put(ctx, root) require.NoError(t, err) roots, err := bs.Roots() require.NoError(t, err) - _, err = bs.Has(roots[0]) + _, err = bs.Has(ctx, roots[0]) require.NoError(t, err) - _, err = bs.Get(roots[0]) + _, err = bs.Get(ctx, roots[0]) require.NoError(t, err) - _, err = bs.GetSize(roots[0]) + _, err = bs.GetSize(ctx, roots[0]) require.NoError(t, err) ctx, cancel := context.WithCancel(context.Background()) @@ -693,16 +710,16 @@ func TestReadWriteErrorAfterClose(t *testing.T) { _, err = bs.Roots() require.Error(t, err) - _, err = bs.Has(roots[0]) + _, err = bs.Has(ctx, roots[0]) require.Error(t, err) - _, err = bs.Get(roots[0]) + _, err = bs.Get(ctx, roots[0]) require.Error(t, err) - _, err = bs.GetSize(roots[0]) + _, err = bs.GetSize(ctx, roots[0]) require.Error(t, err) _, err = bs.AllKeysChan(ctx) require.Error(t, err) - err = bs.Put(root) + err = bs.Put(ctx, root) require.Error(t, err) // TODO: test that closing blocks if an AllKeysChan operation is @@ -711,6 +728,9 @@ func TestReadWriteErrorAfterClose(t *testing.T) { } func TestOpenReadWrite_WritesIdentityCIDsWhenOptionIsEnabled(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + path := filepath.Join(t.TempDir(), "readwrite-with-id-enabled.car") subject, err := blockstore.OpenReadWrite(path, []cid.Cid{}, carv2.StoreIdentityCIDs(true)) require.NoError(t, err) @@ -722,14 +742,14 @@ func TestOpenReadWrite_WritesIdentityCIDsWhenOptionIsEnabled(t *testing.T) { idBlock, err := blocks.NewBlockWithCid(data, idCid) require.NoError(t, err) - err = subject.Put(idBlock) + err = subject.Put(ctx, idBlock) require.NoError(t, err) - has, err := subject.Has(idCid) + has, err := subject.Has(ctx, idCid) require.NoError(t, err) require.True(t, has) - gotBlock, err := subject.Get(idCid) + gotBlock, err := subject.Get(ctx, idCid) require.NoError(t, err) require.Equal(t, idBlock, gotBlock) @@ -813,7 +833,7 @@ func TestOpenReadWrite_ErrorsWhenWritingTooLargeOfACid(t *testing.T) { bigBlock, err := blocks.NewBlockWithCid(data, bigCid) require.NoError(t, err) - err = subject.Put(bigBlock) + err = subject.Put(context.TODO(), bigBlock) require.Equal(t, &carv2.ErrCidTooLarge{MaxSize: maxAllowedCidSize, CurrentSize: bigCidLen}, err) } @@ -839,7 +859,7 @@ func TestReadWrite_ReWritingCARv1WithIdentityCidIsIdenticalToOriginalWithOptions if next.Cid().Prefix().MhType == multihash.IDENTITY { idCidCount++ } - err = subject.Put(next) + err = subject.Put(context.TODO(), next) require.NoError(t, err) } require.NotZero(t, idCidCount) diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go index 24223c634d..53dfa34918 100644 --- a/ipld/car/v2/example_test.go +++ b/ipld/car/v2/example_test.go @@ -2,6 +2,7 @@ package car_test import ( "bytes" + "context" "fmt" "io" "io/ioutil" @@ -61,7 +62,7 @@ func ExampleWrapV1File() { if err != nil { panic(err) } - fmt.Println(bs.Get(roots[0])) + fmt.Println(bs.Get(context.TODO(), roots[0])) // Output: // Roots: [bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy] diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index 6a25e667f5..48b7c86be9 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -19,11 +19,11 @@ func init() { } type Store interface { - Put(blocks.Block) error + Put(context.Context, blocks.Block) error } type ReadStore interface { - Get(cid.Cid) (blocks.Block, error) + Get(context.Context, cid.Cid) (blocks.Block, error) } type CarHeader struct { @@ -159,30 +159,31 @@ func (cr *CarReader) Next() (blocks.Block, error) { } type batchStore interface { - PutMany([]blocks.Block) error + PutMany(context.Context, []blocks.Block) error } func LoadCar(s Store, r io.Reader) (*CarHeader, error) { + ctx := context.TODO() cr, err := NewCarReader(r) if err != nil { return nil, err } if bs, ok := s.(batchStore); ok { - return loadCarFast(bs, cr) + return loadCarFast(ctx, bs, cr) } - return loadCarSlow(s, cr) + return loadCarSlow(ctx, s, cr) } -func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { +func loadCarFast(ctx context.Context, s batchStore, cr *CarReader) (*CarHeader, error) { var buf []blocks.Block for { blk, err := cr.Next() if err != nil { if err == io.EOF { if len(buf) > 0 { - if err := s.PutMany(buf); err != nil { + if err := s.PutMany(ctx, buf); err != nil { return nil, err } } @@ -194,7 +195,7 @@ func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { buf = append(buf, blk) if len(buf) > 1000 { - if err := s.PutMany(buf); err != nil { + if err := s.PutMany(ctx, buf); err != nil { return nil, err } buf = buf[:0] @@ -202,7 +203,7 @@ func loadCarFast(s batchStore, cr *CarReader) (*CarHeader, error) { } } -func loadCarSlow(s Store, cr *CarReader) (*CarHeader, error) { +func loadCarSlow(ctx context.Context, s Store, cr *CarReader) (*CarHeader, error) { for { blk, err := cr.Next() if err != nil { @@ -212,7 +213,7 @@ func loadCarSlow(s Store, cr *CarReader) (*CarHeader, error) { return nil, err } - if err := s.Put(blk); err != nil { + if err := s.Put(ctx, blk); err != nil { return nil, err } } diff --git a/ipld/car/v2/internal/carv1/car_test.go b/ipld/car/v2/internal/carv1/car_test.go index 71e06ee933..bdd57573f9 100644 --- a/ipld/car/v2/internal/carv1/car_test.go +++ b/ipld/car/v2/internal/carv1/car_test.go @@ -65,7 +65,7 @@ func TestRoundtrip(t *testing.T) { bs := bserv.Blockstore() for _, nd := range []format.Node{a, b, c, nd1, nd2, nd3} { - has, err := bs.Has(nd.Cid()) + has, err := bs.Has(context.TODO(), nd.Cid()) if err != nil { t.Fatal(err) } From a4712fa48461bd23295c8f74481606817b54862d Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 6 Jan 2022 03:06:21 -0800 Subject: [PATCH 193/291] make specification of root cid in get-dag command optional (#281) * make specification of root cid in get-dag cmd optional add a test script This commit was moved from ipld/go-car@c9eb0b764cac29b4c70c8290ac87033f3a06a7c5 --- ipld/car/cmd/car/get.go | 35 +++++++++++++++----- ipld/car/cmd/car/testdata/script/get-dag.txt | 6 ++++ 2 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 ipld/car/cmd/car/testdata/script/get-dag.txt diff --git a/ipld/car/cmd/car/get.go b/ipld/car/cmd/car/get.go index 324f050add..de220c5272 100644 --- a/ipld/car/cmd/car/get.go +++ b/ipld/car/cmd/car/get.go @@ -66,27 +66,41 @@ func GetCarBlock(c *cli.Context) error { // GetCarDag is a command to get a dag out of a car func GetCarDag(c *cli.Context) error { - if c.Args().Len() < 3 { - return fmt.Errorf("usage: car get-dag [-s selector] ") + if c.Args().Len() < 2 { + return fmt.Errorf("usage: car get-dag [-s selector] [root cid] ") } - // string to CID for the root of the DAG to extract - rootCid, err := cid.Parse(c.Args().Get(1)) - if err != nil { - return err - } + // if root cid is emitted we'll read it from the root of file.car. + output := c.Args().Get(1) + var rootCid cid.Cid bs, err := blockstore.OpenReadOnly(c.Args().Get(0)) if err != nil { return err } - output := c.Args().Get(2) + if c.Args().Len() == 2 { + roots, err := bs.Roots() + if err != nil { + return err + } + if len(roots) != 1 { + return fmt.Errorf("car file has does not have exactly one root, dag root must be specified explicitly") + } + rootCid = roots[0] + } else { + rootCid, err = cid.Parse(output) + if err != nil { + return err + } + output = c.Args().Get(2) + } + strict := c.Bool("strict") // selector traversal, default to ExploreAllRecursively which only explores the DAG blocks // because we only care about the blocks loaded during the walk, not the nodes matched - sel := selectorParser.CommonSelector_ExploreAllRecursively + sel := selectorParser.CommonSelector_MatchAllRecursively if c.IsSet("selector") { sel, err = selectorParser.ParseJSONSelector(c.String("selector")) if err != nil { @@ -127,6 +141,9 @@ func writeCarV2(rootCid cid.Cid, output string, bs *blockstore.ReadOnly, strict } return nil, err } + if err := outStore.Put(blk); err != nil { + return nil, err + } return bytes.NewBuffer(blk.RawData()), nil } return nil, fmt.Errorf("unknown link type: %T", l) diff --git a/ipld/car/cmd/car/testdata/script/get-dag.txt b/ipld/car/cmd/car/testdata/script/get-dag.txt new file mode 100644 index 0000000000..85751a08f3 --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/get-dag.txt @@ -0,0 +1,6 @@ +env SAMPLE_CID='bafy2bzaceaycv7jhaegckatnncu5yugzkrnzeqsppzegufr35lroxxnsnpspu' +car get-dag ${INPUTS}/sample-v1.car ${SAMPLE_CID} out.car +! stderr . +car list out.car +! stderr . +stdout -count=1 '^bafy2bzaceaycv7jhaegckatnncu5yugzkrnzeqsppzegufr35lroxxnsnpspu' \ No newline at end of file From 18d51e4c219f49e1bd1b73266e08b19a3b0b56cb Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 13 Jan 2022 05:08:49 -0800 Subject: [PATCH 194/291] add `car root` command (#283) * add root subcommand This commit was moved from ipld/go-car@3c99491a50b5a4adfbb187b4dba07bb6b37fbb7d --- ipld/car/cmd/car/car.go | 5 ++++ ipld/car/cmd/car/root.go | 30 +++++++++++++++++++++++ ipld/car/cmd/car/testdata/script/root.txt | 15 ++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 ipld/car/cmd/car/root.go create mode 100644 ipld/car/cmd/car/testdata/script/root.txt diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 43b7307d72..1acb8e14e9 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -135,6 +135,11 @@ func main1() int { }, }, }, + { + Name: "root", + Usage: "Get the root CID of a car", + Action: CarRoot, + }, { Name: "verify", Aliases: []string{"v"}, diff --git a/ipld/car/cmd/car/root.go b/ipld/car/cmd/car/root.go new file mode 100644 index 0000000000..7e8d5b2c4c --- /dev/null +++ b/ipld/car/cmd/car/root.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "os" + + carv2 "github.com/ipld/go-car/v2" + "github.com/urfave/cli/v2" +) + +// CarRoot prints the root CID in a car +func CarRoot(c *cli.Context) (err error) { + inStream := os.Stdin + if c.Args().Len() >= 1 { + inStream, err = os.Open(c.Args().First()) + if err != nil { + return err + } + } + + rd, err := carv2.NewBlockReader(inStream) + if err != nil { + return err + } + for _, r := range rd.Roots { + fmt.Printf("%s\n", r.String()) + } + + return nil +} diff --git a/ipld/car/cmd/car/testdata/script/root.txt b/ipld/car/cmd/car/testdata/script/root.txt new file mode 100644 index 0000000000..834f3a69d4 --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/root.txt @@ -0,0 +1,15 @@ +car root ${INPUTS}/sample-v1.car +cmp stdout v1root.txt + +car root ${INPUTS}/sample-wrapped-v2.car +cmp stdout v2root.txt + +stop stdin_test_needs_car_fix +stdin ${INPUTS}/sample-wrapped-v2.car +car root +cmp stdout v2root.txt + +-- v1root.txt -- +bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy +-- v2root.txt -- +bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy \ No newline at end of file From 5c3ecfa907f66837f1ed3bf496c0d310d7c10593 Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 20 Jan 2022 14:35:36 -0800 Subject: [PATCH 195/291] add `car detach-index list` to list detached index contents (#287) * add `car detach-index list` to list detached index contents Co-authored-by: Masih H. Derkani This commit was moved from ipld/go-car@b2c65c2f4de28dcb1068a0dbf25ceea5e8624a3b --- ipld/car/cmd/car/car.go | 5 +++++ ipld/car/cmd/car/detach.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 1acb8e14e9..094708ba4b 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -33,6 +33,11 @@ func main1() int { Name: "detach-index", Usage: "Detach an index to a detached file", Action: DetachCar, + Subcommands: []*cli.Command{{ + Name: "list", + Usage: "List a detached index", + Action: DetachCarList, + }}, }, { Name: "extract", diff --git a/ipld/car/cmd/car/detach.go b/ipld/car/cmd/car/detach.go index 276d73b47e..da0c236b53 100644 --- a/ipld/car/cmd/car/detach.go +++ b/ipld/car/cmd/car/detach.go @@ -6,6 +6,8 @@ import ( "os" carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" + "github.com/multiformats/go-multihash" "github.com/urfave/cli/v2" ) @@ -33,3 +35,35 @@ func DetachCar(c *cli.Context) error { _, err = io.Copy(outStream, r.IndexReader()) return err } + +// DetachCarList prints a list of what's found in a detached index. +func DetachCarList(c *cli.Context) error { + var err error + + inStream := os.Stdin + if c.Args().Len() >= 1 { + inStream, err = os.Open(c.Args().First()) + if err != nil { + return err + } + defer inStream.Close() + } + + idx, err := index.ReadFrom(inStream) + if err != nil { + return err + } + + if iidx, ok := idx.(index.IterableIndex); ok { + err := iidx.ForEach(func(mh multihash.Multihash, offset uint64) error { + fmt.Printf("%s %d\n", mh, offset) + return nil + }) + if err != nil { + return err + } + return nil + } + + return fmt.Errorf("index of codec %s is not iterable", idx.Codec()) +} From b262c2bdddf6de02ad5b0ef63a20720530f97aa5 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 21 Jan 2022 20:14:36 +1100 Subject: [PATCH 196/291] feat: add option to create blockstore that writes a plain CARv1 (#288) Use-case: I only want a CARv1 output but I want to use the blockstore interface. I don't want to have to extract the CARv1 output from a CARv2 afterward. This commit was moved from ipld/go-car@7e10f104f4525a51cd72ba6bed89a60136e0d97d --- ipld/car/v2/blockstore/readwrite.go | 110 +++++++---- ipld/car/v2/blockstore/readwrite_test.go | 171 +++++++++++------- ipld/car/v2/options.go | 1 + ipld/car/v2/testdata/sample-v1-noidentity.car | Bin 0 -> 479743 bytes 4 files changed, 179 insertions(+), 103 deletions(-) create mode 100644 ipld/car/v2/testdata/sample-v1-noidentity.car diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 8b2ca90d5f..090633c05a 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -40,6 +40,18 @@ type ReadWrite struct { opts carv2.Options } +// WriteAsCarV1 is a write option which makes a CAR blockstore write the output +// as a CARv1 only, with no CARv2 header or index. Indexing is used internally +// during write but is discarded upon finalization. +// +// Note that this option only affects the blockstore, and is ignored by the root +// go-car/v2 package. +func WriteAsCarV1(asCarV1 bool) carv2.Option { + return func(o *carv2.Options) { + o.WriteAsCarV1 = asCarV1 + } +} + // AllowDuplicatePuts is a write option which makes a CAR blockstore not // deduplicate blocks in Put and PutMany. The default is to deduplicate, // which matches the current semantics of go-ipfs-blockstore v1. @@ -122,18 +134,22 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.Option) (*ReadWri rwbs.header = rwbs.header.WithIndexPadding(p) } - rwbs.dataWriter = internalio.NewOffsetWriter(rwbs.f, int64(rwbs.header.DataOffset)) - v1r := internalio.NewOffsetReadSeeker(rwbs.f, int64(rwbs.header.DataOffset)) + offset := int64(rwbs.header.DataOffset) + if rwbs.opts.WriteAsCarV1 { + offset = 0 + } + rwbs.dataWriter = internalio.NewOffsetWriter(rwbs.f, offset) + v1r := internalio.NewOffsetReadSeeker(rwbs.f, offset) rwbs.ronly.backing = v1r rwbs.ronly.idx = rwbs.idx rwbs.ronly.carv2Closer = rwbs.f if resume { - if err = rwbs.resumeWithRoots(roots); err != nil { + if err = rwbs.resumeWithRoots(!rwbs.opts.WriteAsCarV1, roots); err != nil { return nil, err } } else { - if err = rwbs.initWithRoots(roots); err != nil { + if err = rwbs.initWithRoots(!rwbs.opts.WriteAsCarV1, roots); err != nil { return nil, err } } @@ -141,14 +157,16 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.Option) (*ReadWri return rwbs, nil } -func (b *ReadWrite) initWithRoots(roots []cid.Cid) error { - if _, err := b.f.WriteAt(carv2.Pragma, 0); err != nil { - return err +func (b *ReadWrite) initWithRoots(v2 bool, roots []cid.Cid) error { + if v2 { + if _, err := b.f.WriteAt(carv2.Pragma, 0); err != nil { + return err + } } return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, b.dataWriter) } -func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { +func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid) error { // On resumption it is expected that the CARv2 Pragma, and the CARv1 header is successfully written. // Otherwise we cannot resume from the file. // Read pragma to assert if b.f is indeed a CARv2. @@ -158,36 +176,42 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { // Or the write must have failed before pragma was written. return err } - if version != 2 { - // The file is not a CARv2 and we cannot resume from it. + switch { + case version == 1 && !v2: + case version == 2 && v2: + default: + // The file is not the expected version and we cannot resume from it. return fmt.Errorf("cannot resume on CAR file with version %v", version) } - // Check if file was finalized by trying to read the CARv2 header. - // We check because if finalized the CARv1 reader behaviour needs to be adjusted since - // EOF will not signify end of CARv1 payload. i.e. index is most likely present. var headerInFile carv2.Header - _, err = headerInFile.ReadFrom(internalio.NewOffsetReadSeeker(b.f, carv2.PragmaSize)) - - // If reading CARv2 header succeeded, and CARv1 offset in header is not zero then the file is - // most-likely finalized. Check padding and truncate the file to remove index. - // Otherwise, carry on reading the v1 payload at offset determined from b.header. - if err == nil && headerInFile.DataOffset != 0 { - if headerInFile.DataOffset != b.header.DataOffset { - // Assert that the padding on file matches the given WithDataPadding option. - wantPadding := headerInFile.DataOffset - carv2.PragmaSize - carv2.HeaderSize - gotPadding := b.header.DataOffset - carv2.PragmaSize - carv2.HeaderSize - return fmt.Errorf( - "cannot resume from file with mismatched CARv1 offset; "+ - "`WithDataPadding` option must match the padding on file. "+ - "Expected padding value of %v but got %v", wantPadding, gotPadding, - ) - } else if headerInFile.DataSize == 0 { - // If CARv1 size is zero, since CARv1 offset wasn't, then the CARv2 header was - // most-likely partially written. Since we write the header last in Finalize then the - // file most-likely contains the index and we cannot know where it starts, therefore - // can't resume. - return errors.New("corrupt CARv2 header; cannot resume from file") + + if v2 { + // Check if file was finalized by trying to read the CARv2 header. + // We check because if finalized the CARv1 reader behaviour needs to be adjusted since + // EOF will not signify end of CARv1 payload. i.e. index is most likely present. + _, err = headerInFile.ReadFrom(internalio.NewOffsetReadSeeker(b.f, carv2.PragmaSize)) + + // If reading CARv2 header succeeded, and CARv1 offset in header is not zero then the file is + // most-likely finalized. Check padding and truncate the file to remove index. + // Otherwise, carry on reading the v1 payload at offset determined from b.header. + if err == nil && headerInFile.DataOffset != 0 { + if headerInFile.DataOffset != b.header.DataOffset { + // Assert that the padding on file matches the given WithDataPadding option. + wantPadding := headerInFile.DataOffset - carv2.PragmaSize - carv2.HeaderSize + gotPadding := b.header.DataOffset - carv2.PragmaSize - carv2.HeaderSize + return fmt.Errorf( + "cannot resume from file with mismatched CARv1 offset; "+ + "`WithDataPadding` option must match the padding on file. "+ + "Expected padding value of %v but got %v", wantPadding, gotPadding, + ) + } else if headerInFile.DataSize == 0 { + // If CARv1 size is zero, since CARv1 offset wasn't, then the CARv2 header was + // most-likely partially written. Since we write the header last in Finalize then the + // file most-likely contains the index and we cannot know where it starts, therefore + // can't resume. + return errors.New("corrupt CARv2 header; cannot resume from file") + } } } @@ -213,10 +237,13 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { return err } } - // Now that CARv2 header is present on file, clear it to avoid incorrect size and offset in - // header in case blocksotre is closed without finalization and is resumed from. - if err := b.unfinalize(); err != nil { - return fmt.Errorf("could not un-finalize: %w", err) + + if v2 { + // Now that CARv2 header is present on file, clear it to avoid incorrect size and offset in + // header in case blocksotre is closed without finalization and is resumed from. + if err := b.unfinalize(); err != nil { + return fmt.Errorf("could not un-finalize: %w", err) + } } // TODO See how we can reduce duplicate code here. @@ -354,6 +381,13 @@ func (b *ReadWrite) Discard() { // for more efficient subsequent read. // After this call, the blockstore can no longer be used. func (b *ReadWrite) Finalize() error { + if b.opts.WriteAsCarV1 { + // all blocks are already properly written to the CARv1 inner container and there's + // no additional finalization required at the end of the file for a complete v1 + b.ronly.Close() + return nil + } + b.ronly.mu.Lock() defer b.ronly.mu.Unlock() diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 90f5bb536a..63f040d145 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -47,81 +47,122 @@ func TestReadWriteGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) require.Nil(t, gotBlock) } -func TestBlockstore(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() +func TestBlockstoreX(t *testing.T) { + originalCARv1Path := "../testdata/sample-v1.car" + originalCARv1ComparePath := "../testdata/sample-v1-noidentity.car" + originalCARv1ComparePathStat, err := os.Stat(originalCARv1ComparePath) + require.NoError(t, err) + + variants := []struct { + name string + options []carv2.Option + expectedV1StartOffset int64 + }{ + // no options, expect a standard CARv2 with the noidentity inner CARv1 + {"noopt_carv2", []carv2.Option{}, int64(carv2.PragmaSize + carv2.HeaderSize)}, + // option to only write as a CARv1, expect the noidentity inner CARv1 + {"carv1", []carv2.Option{blockstore.WriteAsCarV1(true)}, int64(0)}, + } - f, err := os.Open("../testdata/sample-v1.car") - require.NoError(t, err) - t.Cleanup(func() { assert.NoError(t, f.Close()) }) - r, err := carv1.NewCarReader(f) - require.NoError(t, err) + for _, variant := range variants { + t.Run(variant.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() - path := filepath.Join(t.TempDir(), "readwrite.car") - ingester, err := blockstore.OpenReadWrite(path, r.Header.Roots) - require.NoError(t, err) - t.Cleanup(func() { ingester.Finalize() }) + f, err := os.Open(originalCARv1Path) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, f.Close()) }) + r, err := carv1.NewCarReader(f) + require.NoError(t, err) - cids := make([]cid.Cid, 0) - var idCidCount int - for { - b, err := r.Next() - if err == io.EOF { - break - } - require.NoError(t, err) + path := filepath.Join(t.TempDir(), fmt.Sprintf("readwrite_%s.car", variant.name)) + ingester, err := blockstore.OpenReadWrite(path, r.Header.Roots, variant.options...) + require.NoError(t, err) + t.Cleanup(func() { ingester.Finalize() }) + + cids := make([]cid.Cid, 0) + var idCidCount int + for { + b, err := r.Next() + if err == io.EOF { + break + } + require.NoError(t, err) - err = ingester.Put(ctx, b) - require.NoError(t, err) - cids = append(cids, b.Cid()) + err = ingester.Put(ctx, b) + require.NoError(t, err) + cids = append(cids, b.Cid()) - // try reading a random one: - candidate := cids[rng.Intn(len(cids))] - if has, err := ingester.Has(ctx, candidate); !has || err != nil { - t.Fatalf("expected to find %s but didn't: %s", candidate, err) - } + // try reading a random one: + candidate := cids[rng.Intn(len(cids))] + if has, err := ingester.Has(ctx, candidate); !has || err != nil { + t.Fatalf("expected to find %s but didn't: %s", candidate, err) + } - dmh, err := multihash.Decode(b.Cid().Hash()) - require.NoError(t, err) - if dmh.Code == multihash.IDENTITY { - idCidCount++ - } - } + dmh, err := multihash.Decode(b.Cid().Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + idCidCount++ + } + } - for _, c := range cids { - b, err := ingester.Get(ctx, c) - require.NoError(t, err) - if !b.Cid().Equals(c) { - t.Fatal("wrong item returned") - } - } + for _, c := range cids { + b, err := ingester.Get(ctx, c) + require.NoError(t, err) + if !b.Cid().Equals(c) { + t.Fatal("wrong item returned") + } + } - err = ingester.Finalize() - require.NoError(t, err) - robs, err := blockstore.OpenReadOnly(path) - require.NoError(t, err) - t.Cleanup(func() { assert.NoError(t, robs.Close()) }) + err = ingester.Finalize() + require.NoError(t, err) + robs, err := blockstore.OpenReadOnly(path) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, robs.Close()) }) - allKeysCh, err := robs.AllKeysChan(ctx) - require.NoError(t, err) - numKeysCh := 0 - for c := range allKeysCh { - b, err := robs.Get(ctx, c) - require.NoError(t, err) - if !b.Cid().Equals(c) { - t.Fatal("wrong item returned") - } - numKeysCh++ - } - expectedCidCount := len(cids) - idCidCount - require.Equal(t, expectedCidCount, numKeysCh, "AllKeysChan returned an unexpected amount of keys; expected %v but got %v", expectedCidCount, numKeysCh) + allKeysCh, err := robs.AllKeysChan(ctx) + require.NoError(t, err) + numKeysCh := 0 + for c := range allKeysCh { + b, err := robs.Get(ctx, c) + require.NoError(t, err) + if !b.Cid().Equals(c) { + t.Fatal("wrong item returned") + } + numKeysCh++ + } + expectedCidCount := len(cids) - idCidCount + require.Equal(t, expectedCidCount, numKeysCh, "AllKeysChan returned an unexpected amount of keys; expected %v but got %v", expectedCidCount, numKeysCh) - for _, c := range cids { - b, err := robs.Get(ctx, c) - require.NoError(t, err) - if !b.Cid().Equals(c) { - t.Fatal("wrong item returned") - } + for _, c := range cids { + b, err := robs.Get(ctx, c) + require.NoError(t, err) + if !b.Cid().Equals(c) { + t.Fatal("wrong item returned") + } + } + + wrote, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, wrote.Close()) }) + _, err = wrote.Seek(variant.expectedV1StartOffset, io.SeekStart) + require.NoError(t, err) + hasher := sha512.New() + gotWritten, err := io.Copy(hasher, io.LimitReader(wrote, originalCARv1ComparePathStat.Size())) + require.NoError(t, err) + gotSum := hasher.Sum(nil) + + hasher.Reset() + originalCarV1, err := os.Open(originalCARv1ComparePath) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, originalCarV1.Close()) }) + wantWritten, err := io.Copy(hasher, originalCarV1) + require.NoError(t, err) + wantSum := hasher.Sum(nil) + + require.Equal(t, wantWritten, gotWritten) + require.Equal(t, wantSum, gotSum) + }) } } diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index 2fb1d1fc47..228b359bee 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -39,6 +39,7 @@ type Options struct { BlockstoreAllowDuplicatePuts bool BlockstoreUseWholeCIDs bool MaxTraversalLinks uint64 + WriteAsCarV1 bool } // ApplyOptions applies given opts and returns the resulting Options. diff --git a/ipld/car/v2/testdata/sample-v1-noidentity.car b/ipld/car/v2/testdata/sample-v1-noidentity.car new file mode 100644 index 0000000000000000000000000000000000000000..c83ee22146c36880abe6966b3a3c17bec6d2835d GIT binary patch literal 479743 zcmdSBRZtz;(yooW>%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� 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 Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ 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

    hRd> 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 z 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ώtL~geALh4$?$m5~zXB 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 z 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 o%$na}VY_pmFJJ9Ocq9-~2#kaV Date: Fri, 21 Jan 2022 10:48:04 +1100 Subject: [PATCH 197/291] feat: --version selector for `car create` & update deps This commit was moved from ipld/go-car@497b9e19151bf058a0148a0b499817c998051b68 --- ipld/car/cmd/car/car.go | 5 +++++ ipld/car/cmd/car/create.go | 16 +++++++++++++--- ipld/car/cmd/car/extract.go | 2 +- ipld/car/cmd/car/filter.go | 2 +- ipld/car/cmd/car/get.go | 10 +++++----- ipld/car/cmd/car/index.go | 3 ++- ipld/car/cmd/car/list.go | 2 +- 7 files changed, 28 insertions(+), 12 deletions(-) diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 094708ba4b..d34ce41629 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -27,6 +27,11 @@ func main1() int { Usage: "The car file to write to", TakesFile: true, }, + &cli.IntFlag{ + Name: "version", + Value: 2, + Usage: "Write output as a v1 or v2 format car", + }, }, }, { diff --git a/ipld/car/cmd/car/create.go b/ipld/car/cmd/car/create.go index 4979053818..de9f6abd2a 100644 --- a/ipld/car/cmd/car/create.go +++ b/ipld/car/cmd/car/create.go @@ -43,7 +43,17 @@ func CreateCar(c *cli.Context) error { } proxyRoot := cid.NewCidV1(uint64(multicodec.DagPb), hash) - cdest, err := blockstore.OpenReadWrite(c.String("file"), []cid.Cid{proxyRoot}) + options := []car.Option{} + switch c.Int("version") { + case 1: + options = []car.Option{blockstore.WriteAsCarV1(true)} + case 2: + // already the default + default: + return fmt.Errorf("invalid CAR version %d", c.Int("version")) + } + + cdest, err := blockstore.OpenReadWrite(c.String("file"), []cid.Cid{proxyRoot}, options...) if err != nil { return err } @@ -69,7 +79,7 @@ func writeFiles(ctx context.Context, bs *blockstore.ReadWrite, paths ...string) if !ok { return nil, fmt.Errorf("not a cidlink") } - blk, err := bs.Get(cl.Cid) + blk, err := bs.Get(ctx, cl.Cid) if err != nil { return nil, err } @@ -86,7 +96,7 @@ func writeFiles(ctx context.Context, bs *blockstore.ReadWrite, paths ...string) if err != nil { return err } - bs.Put(blk) + bs.Put(ctx, blk) return nil }, nil } diff --git a/ipld/car/cmd/car/extract.go b/ipld/car/cmd/car/extract.go index 4a56e1775a..76c1173d62 100644 --- a/ipld/car/cmd/car/extract.go +++ b/ipld/car/cmd/car/extract.go @@ -42,7 +42,7 @@ func ExtractCar(c *cli.Context) error { if !ok { return nil, fmt.Errorf("not a cidlink") } - blk, err := bs.Get(cl.Cid) + blk, err := bs.Get(c.Context, cl.Cid) if err != nil { return nil, err } diff --git a/ipld/car/cmd/car/filter.go b/ipld/car/cmd/car/filter.go index 0a0f289377..a76b6bd05a 100644 --- a/ipld/car/cmd/car/filter.go +++ b/ipld/car/cmd/car/filter.go @@ -93,7 +93,7 @@ func FilterCar(c *cli.Context) error { return err } if _, ok := cidMap[blk.Cid()]; ok { - if err := bs.Put(blk); err != nil { + if err := bs.Put(c.Context, blk); err != nil { return err } } diff --git a/ipld/car/cmd/car/get.go b/ipld/car/cmd/car/get.go index de220c5272..090f0fad30 100644 --- a/ipld/car/cmd/car/get.go +++ b/ipld/car/cmd/car/get.go @@ -46,7 +46,7 @@ func GetCarBlock(c *cli.Context) error { return err } - blk, err := bs.Get(blkCid) + blk, err := bs.Get(c.Context, blkCid) if err != nil { return err } @@ -111,7 +111,7 @@ func GetCarDag(c *cli.Context) error { switch c.Int("version") { case 2: - return writeCarV2(rootCid, output, bs, strict, sel, linkVisitOnlyOnce) + return writeCarV2(c.Context, rootCid, output, bs, strict, sel, linkVisitOnlyOnce) case 1: return writeCarV1(rootCid, output, bs, strict, sel, linkVisitOnlyOnce) default: @@ -119,7 +119,7 @@ func GetCarDag(c *cli.Context) error { } } -func writeCarV2(rootCid cid.Cid, output string, bs *blockstore.ReadOnly, strict bool, sel datamodel.Node, linkVisitOnlyOnce bool) error { +func writeCarV2(ctx context.Context, rootCid cid.Cid, output string, bs *blockstore.ReadOnly, strict bool, sel datamodel.Node, linkVisitOnlyOnce bool) error { _ = os.Remove(output) outStore, err := blockstore.OpenReadWrite(output, []cid.Cid{rootCid}, blockstore.AllowDuplicatePuts(false)) @@ -131,7 +131,7 @@ func writeCarV2(rootCid cid.Cid, output string, bs *blockstore.ReadOnly, strict ls.TrustedStorage = true ls.StorageReadOpener = func(_ linking.LinkContext, l datamodel.Link) (io.Reader, error) { if cl, ok := l.(cidlink.Link); ok { - blk, err := bs.Get(cl.Cid) + blk, err := bs.Get(ctx, cl.Cid) if err != nil { if err == ipfsbs.ErrNotFound { if strict { @@ -141,7 +141,7 @@ func writeCarV2(rootCid cid.Cid, output string, bs *blockstore.ReadOnly, strict } return nil, err } - if err := outStore.Put(blk); err != nil { + if err := outStore.Put(ctx, blk); err != nil { return nil, err } return bytes.NewBuffer(blk.RawData()), nil diff --git a/ipld/car/cmd/car/index.go b/ipld/car/cmd/car/index.go index 153c0b7c94..8fd2f7e87d 100644 --- a/ipld/car/cmd/car/index.go +++ b/ipld/car/cmd/car/index.go @@ -156,5 +156,6 @@ func IndexCar(c *cli.Context) error { return err } - return index.WriteTo(idx, outStream) + _, err = index.WriteTo(idx, outStream) + return err } diff --git a/ipld/car/cmd/car/list.go b/ipld/car/cmd/car/list.go index 2912b1d331..62cdb265dd 100644 --- a/ipld/car/cmd/car/list.go +++ b/ipld/car/cmd/car/list.go @@ -138,7 +138,7 @@ func listUnixfs(c *cli.Context, outStream io.Writer) error { if !ok { return nil, fmt.Errorf("not a cidlink") } - blk, err := bs.Get(cl.Cid) + blk, err := bs.Get(c.Context, cl.Cid) if err != nil { return nil, err } From bb04114b7fef3c8f004392100c1ce2f60e6d1a1b Mon Sep 17 00:00:00 2001 From: Jorropo Date: Wed, 9 Feb 2022 23:33:40 +0100 Subject: [PATCH 198/291] cmd/car: use a better install command in the README This commit was moved from ipld/go-car@d1093d99c93081f51295e1068e3f3fb92f1d804d --- ipld/car/cmd/car/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/cmd/car/README.md b/ipld/car/cmd/car/README.md index f3825f0d2c..b9a4ca9a83 100644 --- a/ipld/car/cmd/car/README.md +++ b/ipld/car/cmd/car/README.md @@ -29,5 +29,5 @@ COMMANDS: To install the latest version of `car` module, run: ```shell script -go install github.com/ipld/go-car/cmd/car +go install github.com/ipld/go-car/cmd/car@latest ``` From 2877898ef1b96eff6b0341ef2ba5e9594f8a65c3 Mon Sep 17 00:00:00 2001 From: Will Date: Tue, 15 Feb 2022 02:05:10 -0800 Subject: [PATCH 199/291] Allow extracton of a raw unixfs file (#284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow extracton of a raw unixfs file Co-authored-by: Daniel Martí This commit was moved from ipld/go-car@4cb3d4f649555f072e34d2d175c7173f52cc859c --- ipld/car/cmd/car/extract.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/ipld/car/cmd/car/extract.go b/ipld/car/cmd/car/extract.go index 76c1173d62..5ebc2b7715 100644 --- a/ipld/car/cmd/car/extract.go +++ b/ipld/car/cmd/car/extract.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "errors" "fmt" "io" "os" @@ -20,6 +21,8 @@ import ( "github.com/urfave/cli/v2" ) +var ErrNotDir = fmt.Errorf("not a directory") + // ExtractCar pulls files and directories out of a car func ExtractCar(c *cli.Context) error { outputDir, err := os.Getwd() @@ -92,7 +95,27 @@ func extractRoot(c *cli.Context, ls *ipld.LinkSystem, root cid.Cid, outputDir st } } if err := extractDir(c, ls, ufn, outputResolvedDir, "/"); err != nil { - return fmt.Errorf("%s: %w", root, err) + if !errors.Is(err, ErrNotDir) { + return fmt.Errorf("%s: %w", root, err) + } + ufsData, err := pbnode.LookupByString("Data") + if err != nil { + return err + } + ufsBytes, err := ufsData.AsBytes() + if err != nil { + return err + } + ufsNode, err := data.DecodeUnixFSData(ufsBytes) + if err != nil { + return err + } + if ufsNode.DataType.Int() == data.Data_File || ufsNode.DataType.Int() == data.Data_Raw { + if err := extractFile(c, ls, pbnode, filepath.Join(outputResolvedDir, "unknown")); err != nil { + return err + } + } + return nil } return nil @@ -208,7 +231,7 @@ func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, ou } return nil } - return fmt.Errorf("not a directory") + return ErrNotDir } func extractFile(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputName string) error { From c0633c10d611c85a351d307fb47f743de9bfeb24 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 3 Mar 2022 16:42:51 +1100 Subject: [PATCH 200/291] fix(test): rootless fixture should have no roots, not null roots This commit was moved from ipld/go-car@d2cce98147ead6cd2987bfeb58376d64f4dfb7fe --- ipld/car/v2/testdata/sample-rootless-v42.car | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/v2/testdata/sample-rootless-v42.car b/ipld/car/v2/testdata/sample-rootless-v42.car index 846b71a5a2..3fda90cf95 100644 --- a/ipld/car/v2/testdata/sample-rootless-v42.car +++ b/ipld/car/v2/testdata/sample-rootless-v42.car @@ -1 +1 @@ -erootsgversion* \ No newline at end of file + gversion* \ No newline at end of file From 98f012c64ebf9fa12d0ff8e5a96604656a90e1bc Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 24 Mar 2022 15:34:02 +0000 Subject: [PATCH 201/291] Car command supports for `largebytes` nodes (#296) * Car command supports for `largebytes` nodes and partial unixfs file traversals * check copy error This commit was moved from ipld/go-car@867ce4a3ec215a515c419ee9a7e54de086cea978 --- ipld/car/cmd/car/car.go | 2 +- ipld/car/cmd/car/create.go | 2 +- ipld/car/cmd/car/extract.go | 6 +++++- ipld/car/cmd/car/get.go | 16 +++++++++++++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index d34ce41629..90cf9425d8 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -130,7 +130,7 @@ func main1() int { }, { Name: "list", - Aliases: []string{"l"}, + Aliases: []string{"l", "ls"}, Usage: "List the CIDs in a car", Action: ListCar, Flags: []cli.Flag{ diff --git a/ipld/car/cmd/car/create.go b/ipld/car/cmd/car/create.go index de9f6abd2a..3a76c91316 100644 --- a/ipld/car/cmd/car/create.go +++ b/ipld/car/cmd/car/create.go @@ -117,7 +117,7 @@ func writeFiles(ctx context.Context, bs *blockstore.ReadWrite, paths ...string) // make a directory for the file(s). - root, err := builder.BuildUnixFSDirectory(topLevel, &ls) + root, _, err := builder.BuildUnixFSDirectory(topLevel, &ls) if err != nil { return cid.Undef, nil } diff --git a/ipld/car/cmd/car/extract.go b/ipld/car/cmd/car/extract.go index 5ebc2b7715..ae2da87513 100644 --- a/ipld/car/cmd/car/extract.go +++ b/ipld/car/cmd/car/extract.go @@ -239,13 +239,17 @@ func extractFile(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputName st if err != nil { return err } + nlr, err := node.AsLargeBytes() + if err != nil { + return err + } f, err := os.Create(outputName) if err != nil { return err } defer f.Close() - _, err = io.Copy(f, node) + _, err = io.Copy(f, nlr) return err } diff --git a/ipld/car/cmd/car/get.go b/ipld/car/cmd/car/get.go index 090f0fad30..84531b7a66 100644 --- a/ipld/car/cmd/car/get.go +++ b/ipld/car/cmd/car/get.go @@ -17,6 +17,7 @@ import ( "github.com/ipfs/go-cid" ipfsbs "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipfs/go-unixfsnode" "github.com/ipld/go-car" "github.com/ipld/go-car/v2/blockstore" "github.com/ipld/go-ipld-prime/datamodel" @@ -128,6 +129,7 @@ func writeCarV2(ctx context.Context, rootCid cid.Cid, output string, bs *blockst } ls := cidlink.DefaultLinkSystem() + ls.KnownReifiers = map[string]linking.NodeReifier{"unixfs": unixfsnode.Reify} ls.TrustedStorage = true ls.StorageReadOpener = func(_ linking.LinkContext, l datamodel.Link) (io.Reader, error) { if cl, ok := l.(cidlink.Link); ok { @@ -176,7 +178,19 @@ func writeCarV2(ctx context.Context, rootCid cid.Cid, output string, bs *blockst return err } - err = traversalProgress.WalkMatching(rootNode, s, func(p traversal.Progress, n datamodel.Node) error { return nil }) + err = traversalProgress.WalkMatching(rootNode, s, func(p traversal.Progress, n datamodel.Node) error { + lb, ok := n.(datamodel.LargeBytesNode) + if ok { + rs, err := lb.AsLargeBytes() + if err == nil { + _, err := io.Copy(io.Discard, rs) + if err != nil { + return err + } + } + } + return nil + }) if err != nil { return err } From 373d18038c2ab5270433cd4f920962f1e76a98b9 Mon Sep 17 00:00:00 2001 From: Will Date: Mon, 25 Apr 2022 14:14:11 +0000 Subject: [PATCH 202/291] bump to newer blockstore err not found (#301) This commit was moved from ipld/go-car@c4b9dc88fc62d07a453efc8ae76a341084096ae7 --- ipld/car/v2/blockstore/readonly.go | 11 ++++++----- ipld/car/v2/blockstore/readonly_test.go | 4 ++-- ipld/car/v2/blockstore/readwrite_test.go | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 31636d4e4c..f0a15e782a 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -11,6 +11,7 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" + format "github.com/ipfs/go-ipld-format" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" @@ -280,14 +281,14 @@ func (b *ReadOnly) Get(ctx context.Context, key cid.Cid) (blocks.Block, error) { } }) if errors.Is(err, index.ErrNotFound) { - return nil, blockstore.ErrNotFound + return nil, format.ErrNotFound{Cid: key} } else if err != nil { - return nil, err + return nil, format.ErrNotFound{Cid: key} } else if fnErr != nil { return nil, fnErr } if fnData == nil { - return nil, blockstore.ErrNotFound + return nil, format.ErrNotFound{Cid: key} } return blocks.NewBlockWithCid(fnData, key) } @@ -338,14 +339,14 @@ func (b *ReadOnly) GetSize(ctx context.Context, key cid.Cid) (int, error) { } }) if errors.Is(err, index.ErrNotFound) { - return -1, blockstore.ErrNotFound + return -1, format.ErrNotFound{Cid: key} } else if err != nil { return -1, err } else if fnErr != nil { return -1, fnErr } if fnSize == -1 { - return -1, blockstore.ErrNotFound + return -1, format.ErrNotFound{Cid: key} } return fnSize, nil } diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 0ac2b7d169..f55a1e50cd 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -11,7 +11,7 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" + format "github.com/ipfs/go-ipld-format" "github.com/ipfs/go-merkledag" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/internal/carv1" @@ -25,7 +25,7 @@ func TestReadOnlyGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) { // Assert blockstore API returns blockstore.ErrNotFound gotBlock, err := subject.Get(context.TODO(), nonExistingKey) - require.Equal(t, blockstore.ErrNotFound, err) + require.IsType(t, format.ErrNotFound{}, err) require.Nil(t, gotBlock) } diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 63f040d145..86ab06ea12 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -15,8 +15,8 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - ipfsblockstore "github.com/ipfs/go-ipfs-blockstore" cbor "github.com/ipfs/go-ipld-cbor" + format "github.com/ipfs/go-ipld-format" "github.com/ipfs/go-merkledag" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" @@ -43,7 +43,7 @@ func TestReadWriteGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) // Assert blockstore API returns blockstore.ErrNotFound gotBlock, err := subject.Get(context.TODO(), nonExistingKey) - require.Equal(t, ipfsblockstore.ErrNotFound, err) + require.IsType(t, format.ErrNotFound{}, err) require.Nil(t, gotBlock) } From 5414376436cdf299a73a0e49ebd6b1fb1b8dec30 Mon Sep 17 00:00:00 2001 From: Will Date: Tue, 31 May 2022 07:28:28 +0000 Subject: [PATCH 203/291] PrototypeChooser support (#305) * complete reads of lazy nodes in walks This commit was moved from ipld/go-car@c65f0bfea9e791e877a0a9ccadca54e1d0c1715f --- ipld/car/v2/internal/loader/writing_loader.go | 38 ++++++++----- ipld/car/v2/options.go | 10 ++++ ipld/car/v2/selective.go | 37 +++++++++--- ipld/car/v2/selective_test.go | 56 ++++++++++++++++++- 4 files changed, 118 insertions(+), 23 deletions(-) diff --git a/ipld/car/v2/internal/loader/writing_loader.go b/ipld/car/v2/internal/loader/writing_loader.go index 13236f1c60..b4d0e6efe1 100644 --- a/ipld/car/v2/internal/loader/writing_loader.go +++ b/ipld/car/v2/internal/loader/writing_loader.go @@ -16,7 +16,7 @@ type writerOutput struct { w io.Writer size uint64 code multicodec.Code - rcrds []index.Record + rcrds map[cid.Cid]index.Record } func (w *writerOutput) Size() uint64 { @@ -28,7 +28,11 @@ func (w *writerOutput) Index() (index.Index, error) { if err != nil { return nil, err } - if err := idx.Load(w.rcrds); err != nil { + rcrds := make([]index.Record, 0, len(w.rcrds)) + for _, r := range w.rcrds { + rcrds = append(rcrds, r) + } + if err := idx.Load(rcrds); err != nil { return nil, err } @@ -63,17 +67,13 @@ func (w *writingReader) Read(p []byte) (int, error) { if _, err := cpy.WriteTo(w.wo.w); err != nil { return 0, err } - - // maybe write the index. - if w.wo.code != index.CarIndexNone { - _, c, err := cid.CidFromBytes([]byte(w.cid)) - if err != nil { - return 0, err - } - w.wo.rcrds = append(w.wo.rcrds, index.Record{ - Cid: c, - Offset: w.wo.size, - }) + _, c, err := cid.CidFromBytes([]byte(w.cid)) + if err != nil { + return 0, err + } + w.wo.rcrds[c] = index.Record{ + Cid: c, + Offset: w.wo.size, } w.wo.size += uint64(w.len) + uint64(len(size)+len(w.cid)) @@ -94,11 +94,21 @@ func TeeingLinkSystem(ls ipld.LinkSystem, w io.Writer, initialOffset uint64, ind w: w, size: initialOffset, code: indexCodec, - rcrds: make([]index.Record, 0), + rcrds: make(map[cid.Cid]index.Record), } tls := ls tls.StorageReadOpener = func(lc linking.LinkContext, l ipld.Link) (io.Reader, error) { + _, c, err := cid.CidFromBytes([]byte(l.Binary())) + if err != nil { + return nil, err + } + + // if we've already read this cid in this session, don't re-write it. + if _, ok := wo.rcrds[c]; ok { + return ls.StorageReadOpener(lc, l) + } + r, err := ls.StorageReadOpener(lc, l) if err != nil { return nil, err diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index 228b359bee..d2923b2144 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -4,6 +4,7 @@ import ( "math" "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-ipld-prime/traversal" "github.com/multiformats/go-multicodec" ) @@ -40,6 +41,7 @@ type Options struct { BlockstoreUseWholeCIDs bool MaxTraversalLinks uint64 WriteAsCarV1 bool + TraversalPrototypeChooser traversal.LinkTargetNodePrototypeChooser } // ApplyOptions applies given opts and returns the resulting Options. @@ -118,3 +120,11 @@ func MaxIndexCidSize(s uint64) Option { o.MaxIndexCidSize = s } } + +// WithTraversalPrototypeChooser specifies the prototype chooser that should be used +// when performing traversals in writes from a linksystem. +func WithTraversalPrototypeChooser(t traversal.LinkTargetNodePrototypeChooser) Option { + return func(o *Options) { + o.TraversalPrototypeChooser = t + } +} diff --git a/ipld/car/v2/selective.go b/ipld/car/v2/selective.go index 22351e715c..39bb5f9112 100644 --- a/ipld/car/v2/selective.go +++ b/ipld/car/v2/selective.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "io/ioutil" "math" "os" @@ -12,6 +13,7 @@ import ( "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/loader" ipld "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/datamodel" "github.com/ipld/go-ipld-prime/linking" cidlink "github.com/ipld/go-ipld-prime/linking/cid" "github.com/ipld/go-ipld-prime/node/basicnode" @@ -236,14 +238,19 @@ func traverse(ctx context.Context, ls *ipld.LinkSystem, root cid.Cid, s ipld.Nod return err } + chooser := func(_ ipld.Link, _ linking.LinkContext) (ipld.NodePrototype, error) { + return basicnode.Prototype.Any, nil + } + if opts.TraversalPrototypeChooser != nil { + chooser = opts.TraversalPrototypeChooser + } + progress := traversal.Progress{ Cfg: &traversal.Config{ - Ctx: ctx, - LinkSystem: *ls, - LinkTargetNodePrototypeChooser: func(_ ipld.Link, _ linking.LinkContext) (ipld.NodePrototype, error) { - return basicnode.Prototype.Any, nil - }, - LinkVisitOnlyOnce: !opts.BlockstoreAllowDuplicatePuts, + Ctx: ctx, + LinkSystem: *ls, + LinkTargetNodePrototypeChooser: chooser, + LinkVisitOnlyOnce: !opts.BlockstoreAllowDuplicatePuts, }, } if opts.MaxTraversalLinks < math.MaxInt64 { @@ -255,11 +262,25 @@ func traverse(ctx context.Context, ls *ipld.LinkSystem, root cid.Cid, s ipld.Nod lnk := cidlink.Link{Cid: root} ls.TrustedStorage = true - rootNode, err := ls.Load(ipld.LinkContext{}, lnk, basicnode.Prototype.Any) + rp, err := chooser(lnk, ipld.LinkContext{}) + if err != nil { + return err + } + rootNode, err := ls.Load(ipld.LinkContext{}, lnk, rp) if err != nil { return fmt.Errorf("root blk load failed: %s", err) } - err = progress.WalkMatching(rootNode, sel, func(_ traversal.Progress, _ ipld.Node) error { + err = progress.WalkMatching(rootNode, sel, func(_ traversal.Progress, node ipld.Node) error { + if lbn, ok := node.(datamodel.LargeBytesNode); ok { + s, err := lbn.AsLargeBytes() + if err != nil { + return err + } + _, err = io.Copy(ioutil.Discard, s) + if err != nil { + return err + } + } return nil }) if err != nil { diff --git a/ipld/car/v2/selective_test.go b/ipld/car/v2/selective_test.go index f2bba6f8ac..924351d46a 100644 --- a/ipld/car/v2/selective_test.go +++ b/ipld/car/v2/selective_test.go @@ -3,18 +3,27 @@ package car_test import ( "bytes" "context" + "io" "os" "path" "testing" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-unixfsnode" + "github.com/ipfs/go-unixfsnode/data/builder" "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" + dagpb "github.com/ipld/go-codec-dagpb" + "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/linking" cidlink "github.com/ipld/go-ipld-prime/linking/cid" + basicnode "github.com/ipld/go-ipld-prime/node/basic" "github.com/ipld/go-ipld-prime/storage/bsadapter" + sb "github.com/ipld/go-ipld-prime/traversal/selector/builder" selectorparse "github.com/ipld/go-ipld-prime/traversal/selector/parse" "github.com/stretchr/testify/require" - _ "github.com/ipld/go-codec-dagpb" _ "github.com/ipld/go-ipld-prime/codec/dagcbor" _ "github.com/ipld/go-ipld-prime/codec/raw" ) @@ -81,3 +90,48 @@ func TestV1Traversal(t *testing.T) { fa, _ := os.Stat("testdata/sample-v1.car") require.Equal(t, fa.Size(), int64(n)) } + +func TestPartialTraversal(t *testing.T) { + store := cidlink.Memory{Bag: make(map[string][]byte)} + ls := cidlink.DefaultLinkSystem() + ls.StorageReadOpener = store.OpenRead + ls.StorageWriteOpener = store.OpenWrite + unixfsnode.AddUnixFSReificationToLinkSystem(&ls) + + // write a unixfs file. + initBuf := bytes.Buffer{} + _, _ = initBuf.Write(make([]byte, 1000000)) + rt, _, err := builder.BuildUnixFSFile(&initBuf, "", &ls) + require.NoError(t, err) + + // read a subset of the file. + _, rts, err := cid.CidFromBytes([]byte(rt.Binary())) + require.NoError(t, err) + ssb := sb.NewSelectorSpecBuilder(basicnode.Prototype.Any) + sel := ssb.ExploreInterpretAs("unixfs", ssb.MatcherSubset(0, 256*1000)) + buf := bytes.Buffer{} + chooser := dagpb.AddSupportToChooser(func(l datamodel.Link, lc linking.LinkContext) (datamodel.NodePrototype, error) { + return basicnode.Prototype.Any, nil + }) + _, err = car.TraverseV1(context.Background(), &ls, rts, sel.Node(), &buf, car.WithTraversalPrototypeChooser(chooser)) + require.NoError(t, err) + + fb := len(buf.Bytes()) + require.Less(t, fb, 1000000) + + loaded, err := car.NewBlockReader(&buf) + require.NoError(t, err) + fnd := make(map[cid.Cid]struct{}) + var b blocks.Block + for err == nil { + b, err = loaded.Next() + if err == io.EOF { + break + } + if _, ok := fnd[b.Cid()]; ok { + require.Fail(t, "duplicate block present", b.Cid()) + } + fnd[b.Cid()] = struct{}{} + } + require.Equal(t, 2, len(fnd)) +} From 33899cd0d2da468aed7dbd7a9f45bedc844f6572 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 29 Jun 2022 16:26:39 +0100 Subject: [PATCH 204/291] Add API to regenerate index from CARv1 or CARv2 The index generation APIs either allowed reading an existing index from a CARv2 or explicitly required a CARv1 to generate index. Introduce APIs to make it easier for users that want to regenerate the index regardless of whether it exists in a CAR file or not. The index generation APIs are changed to accept either of the formats and re-generate the index from the data payload unless `ReadOrGenerate` is called. Adjust the tests to run for all flavours of index generation with both CARv1 and CARv2 payload. This commit was moved from ipld/go-car@7ba9372ba205c2baf620c0b104dc08ed80b1dffb --- ipld/car/v2/index_gen.go | 98 ++++++++++++++++++++----- ipld/car/v2/index_gen_test.go | 134 ++++++++++++++++------------------ 2 files changed, 144 insertions(+), 88 deletions(-) diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 81d23919db..10c86dc341 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -13,9 +13,12 @@ import ( "github.com/multiformats/go-varint" ) -// GenerateIndex generates index for a given car in v1 format. +// GenerateIndex generates index for the given car payload reader. // The index can be stored in serialized format using index.WriteTo. -// See LoadIndex. +// +// Note, the index is re-generated every time even if the payload is in CARv2 format and already has +// an index. To read existing index when available see ReadOrGenerateIndex. +// See: LoadIndex. func GenerateIndex(v1r io.Reader, opts ...Option) (index.Index, error) { wopts := ApplyOptions(opts...) idx, err := index.New(wopts.IndexCodec) @@ -28,21 +31,63 @@ func GenerateIndex(v1r io.Reader, opts ...Option) (index.Index, error) { return idx, nil } -// LoadIndex populates idx with index records generated from v1r. -// The v1r must be data payload in CARv1 format. -func LoadIndex(idx index.Index, v1r io.Reader, opts ...Option) error { - reader := internalio.ToByteReadSeeker(v1r) - header, err := carv1.ReadHeader(reader) +// LoadIndex populates idx with index records generated from r. +// The r may be in CARv1 or CARv2 format. +// +// Note, the index is re-generated every time even if r is in CARv2 format and already has an index. +// To read existing index when available see ReadOrGenerateIndex. +func LoadIndex(idx index.Index, r io.Reader, opts ...Option) error { + reader := internalio.ToByteReadSeeker(r) + pragma, err := carv1.ReadHeader(r) if err != nil { return fmt.Errorf("error reading car header: %w", err) } - if header.Version != 1 { - return fmt.Errorf("expected version to be 1, got %v", header.Version) - } + var dataSize, dataOffset int64 + switch pragma.Version { + case 1: + break + case 2: + // Read V2 header which should appear immediately after pragma according to CARv2 spec. + var v2h Header + _, err := v2h.ReadFrom(r) + if err != nil { + return err + } - // Parse Options. - o := ApplyOptions(opts...) + // Sanity-check the CARv2 header + if v2h.DataOffset < HeaderSize { + return fmt.Errorf("malformed CARv2; data offset too small: %d", v2h.DataOffset) + } + if v2h.DataSize < 1 { + return fmt.Errorf("malformed CARv2; data payload size too small: %d", v2h.DataSize) + } + + // Seek to the beginning of the inner CARv1 payload + _, err = reader.Seek(int64(v2h.DataOffset), io.SeekStart) + if err != nil { + return err + } + + // Set dataSize and dataOffset which are then used during index loading logic to decide + // where to stop and adjust section offset respectively. + // Note that we could use a LimitReader here and re-define reader with it. However, it means + // the internalio.ToByteReadSeeker will be less efficient since LimitReader does not + // implement ByteReader nor ReadSeeker. + dataSize = int64(v2h.DataSize) + dataOffset = int64(v2h.DataOffset) + + // Read the inner CARv1 header to skip it and sanity check it. + v1h, err := carv1.ReadHeader(reader) + if err != nil { + return err + } + if v1h.Version != 1 { + return fmt.Errorf("expected data payload header version of 1; got %d", v1h.Version) + } + default: + return fmt.Errorf("expected either version 1 or 2; got %d", pragma.Version) + } // Record the start of each section, with first section starring from current position in the // reader, i.e. right after the header, since we have only read the header so far. @@ -55,6 +100,13 @@ func LoadIndex(idx index.Index, v1r io.Reader, opts ...Option) error { return err } + // Subtract the data offset; if CARv1 this would be zero otherwise the value will come from the + // CARv2 header. + sectionOffset -= dataOffset + + // Parse Options. + o := ApplyOptions(opts...) + records := make([]index.Record, 0) for { // Read the section's length. @@ -94,6 +146,14 @@ func LoadIndex(idx index.Index, v1r io.Reader, opts ...Option) error { if sectionOffset, err = reader.Seek(remainingSectionLen, io.SeekCurrent); err != nil { return err } + // Subtract the data offset which will be non-zero when reader represents a CARv2. + sectionOffset -= dataOffset + + // Check if we have reached the end of data payload and if so treat it as an EOF. + // Note, dataSize will be non-zero only if we are reading from a CARv2. + if dataSize != 0 && sectionOffset >= dataSize { + break + } } if err := idx.Load(records); err != nil { @@ -103,16 +163,20 @@ func LoadIndex(idx index.Index, v1r io.Reader, opts ...Option) error { return nil } -// GenerateIndexFromFile walks a car v1 file at the give path and generates an index of cid->byte offset. -// The index can be stored using index.WriteTo. -// See GenerateIndex. -func GenerateIndexFromFile(path string) (index.Index, error) { +// GenerateIndexFromFile walks a CAR file at the give path and generates an index of cid->byte offset. +// The index can be stored using index.WriteTo. Both CARv1 and CARv2 formats are accepted. +// +// Note, the index is re-generated every time even if the given CAR file is in CARv2 format and +// already has an index. To read existing index when available see ReadOrGenerateIndex. +// +// See: GenerateIndex. +func GenerateIndexFromFile(path string, opts ...Option) (index.Index, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() - return GenerateIndex(f) + return GenerateIndex(f, opts...) } // ReadOrGenerateIndex accepts both CARv1 and CARv2 formats, and reads or generates an index for it. diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 529fb91379..11f0530fb9 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -17,7 +17,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestReadOrGenerateIndex(t *testing.T) { +func TestGenerateIndex(t *testing.T) { tests := []struct { name string carPath string @@ -26,10 +26,9 @@ func TestReadOrGenerateIndex(t *testing.T) { wantErr bool }{ { - "CarV1IsIndexedAsExpected", - "testdata/sample-v1.car", - []carv2.Option{}, - func(t *testing.T) index.Index { + name: "CarV1IsIndexedAsExpected", + carPath: "testdata/sample-v1.car", + wantIndexer: func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1.car") require.NoError(t, err) defer v1.Close() @@ -37,13 +36,11 @@ func TestReadOrGenerateIndex(t *testing.T) { require.NoError(t, err) return want }, - false, }, { - "CarV2WithIndexIsReturnedAsExpected", - "testdata/sample-wrapped-v2.car", - []carv2.Option{}, - func(t *testing.T) index.Index { + name: "CarV2WithIndexIsReturnedAsExpected", + carPath: "testdata/sample-wrapped-v2.car", + wantIndexer: func(t *testing.T) index.Index { v2, err := os.Open("testdata/sample-wrapped-v2.car") require.NoError(t, err) defer v2.Close() @@ -53,13 +50,12 @@ func TestReadOrGenerateIndex(t *testing.T) { require.NoError(t, err) return want }, - false, }, { - "CarV1WithZeroLenSectionIsGeneratedAsExpected", - "testdata/sample-v1-with-zero-len-section.car", - []carv2.Option{carv2.ZeroLengthSectionAsEOF(true)}, - func(t *testing.T) index.Index { + name: "CarV1WithZeroLenSectionIsGeneratedAsExpected", + carPath: "testdata/sample-v1-with-zero-len-section.car", + opts: []carv2.Option{carv2.ZeroLengthSectionAsEOF(true)}, + wantIndexer: func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1-with-zero-len-section.car") require.NoError(t, err) defer v1.Close() @@ -67,13 +63,12 @@ func TestReadOrGenerateIndex(t *testing.T) { require.NoError(t, err) return want }, - false, }, { - "AnotherCarV1WithZeroLenSectionIsGeneratedAsExpected", - "testdata/sample-v1-with-zero-len-section2.car", - []carv2.Option{carv2.ZeroLengthSectionAsEOF(true)}, - func(t *testing.T) index.Index { + name: "AnotherCarV1WithZeroLenSectionIsGeneratedAsExpected", + carPath: "testdata/sample-v1-with-zero-len-section2.car", + opts: []carv2.Option{carv2.ZeroLengthSectionAsEOF(true)}, + wantIndexer: func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1-with-zero-len-section2.car") require.NoError(t, err) defer v1.Close() @@ -81,25 +76,21 @@ func TestReadOrGenerateIndex(t *testing.T) { require.NoError(t, err) return want }, - false, }, { - "CarV1WithZeroLenSectionWithoutOptionIsError", - "testdata/sample-v1-with-zero-len-section.car", - []carv2.Option{}, - func(t *testing.T) index.Index { return nil }, - true, + name: "CarV1WithZeroLenSectionWithoutOptionIsError", + carPath: "testdata/sample-v1-with-zero-len-section.car", + wantErr: true, }, { - "CarOtherThanV1OrV2IsError", - "testdata/sample-rootless-v42.car", - []carv2.Option{}, - func(t *testing.T) index.Index { return nil }, - true, + name: "CarOtherThanV1OrV2IsError", + carPath: "testdata/sample-rootless-v42.car", + wantIndexer: func(t *testing.T) index.Index { return nil }, + wantErr: true, }, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run("ReadOrGenerateIndex_"+tt.name, func(t *testing.T) { carFile, err := os.Open(tt.carPath) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, carFile.Close()) }) @@ -108,54 +99,55 @@ func TestReadOrGenerateIndex(t *testing.T) { require.Error(t, err) } else { require.NoError(t, err) - want := tt.wantIndexer(t) + var want index.Index + if tt.wantIndexer != nil { + want = tt.wantIndexer(t) + } require.Equal(t, want, got) } }) - } -} - -func TestGenerateIndexFromFile(t *testing.T) { - tests := []struct { - name string - carPath string - wantIndexer func(t *testing.T) index.Index - wantErr bool - }{ - { - "CarV1IsIndexedAsExpected", - "testdata/sample-v1.car", - func(t *testing.T) index.Index { - v1, err := os.Open("testdata/sample-v1.car") + t.Run("GenerateIndexFromFile_"+tt.name, func(t *testing.T) { + got, err := carv2.GenerateIndexFromFile(tt.carPath, tt.opts...) + if tt.wantErr { + require.Error(t, err) + } else { require.NoError(t, err) - defer v1.Close() - want, err := carv2.GenerateIndex(v1) + var want index.Index + if tt.wantIndexer != nil { + want = tt.wantIndexer(t) + } + require.Equal(t, want, got) + } + }) + t.Run("LoadIndex_"+tt.name, func(t *testing.T) { + carFile, err := os.Open(tt.carPath) + require.NoError(t, err) + got, err := index.New(multicodec.CarMultihashIndexSorted) + require.NoError(t, err) + err = carv2.LoadIndex(got, carFile, tt.opts...) + if tt.wantErr { + require.Error(t, err) + } else { require.NoError(t, err) - return want - }, - false, - }, - { - "CarV2IsErrorSinceOnlyV1PayloadIsExpected", - "testdata/sample-wrapped-v2.car", - func(t *testing.T) index.Index { return nil }, - true, - }, - { - "CarOtherThanV1OrV2IsError", - "testdata/sample-rootless-v42.car", - func(t *testing.T) index.Index { return nil }, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := carv2.GenerateIndexFromFile(tt.carPath) + var want index.Index + if tt.wantIndexer != nil { + want = tt.wantIndexer(t) + } + require.Equal(t, want, got) + } + }) + t.Run("GenerateIndex_"+tt.name, func(t *testing.T) { + carFile, err := os.Open(tt.carPath) + require.NoError(t, err) + got, err := carv2.GenerateIndex(carFile, tt.opts...) if tt.wantErr { require.Error(t, err) } else { require.NoError(t, err) - want := tt.wantIndexer(t) + var want index.Index + if tt.wantIndexer != nil { + want = tt.wantIndexer(t) + } require.Equal(t, want, got) } }) From 3c6b45ebe0543be40eead8c4cde9540aa3a67f56 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Thu, 16 Jun 2022 23:41:32 +0200 Subject: [PATCH 205/291] fix: don't OOM if the header size is too big This commit was moved from ipld/go-car@a35f04821d5835e46997da3317c09a336a7de86e --- ipld/car/util/util.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ipld/car/util/util.go b/ipld/car/util/util.go index 08048f333e..320ecf89ff 100644 --- a/ipld/car/util/util.go +++ b/ipld/car/util/util.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "encoding/binary" + "errors" "fmt" "io" @@ -11,6 +12,13 @@ import ( mh "github.com/multiformats/go-multihash" ) +// MaxAllowedHeaderSize hint about how big the header red are allowed to be. +// This value is a hint to avoid OOMs, a parser that cannot OOM because it is +// streaming for example, isn't forced to follow that value. +// Deprecated: You should use v2#NewReader instead since it allows for options +// to be passed in. +var MaxAllowedHeaderSize uint = 1024 + var cidv0Pref = []byte{0x12, 0x20} type BytesReader interface { @@ -112,6 +120,10 @@ func LdRead(r *bufio.Reader) ([]byte, error) { return nil, err } + if l > uint64(MaxAllowedHeaderSize) { // Don't OOM + return nil, errors.New("malformed car; header is bigger than util.MaxAllowedHeaderSize") + } + buf := make([]byte, l) if _, err := io.ReadFull(r, buf); err != nil { return nil, err From 6ed9f165316e8c3b42595e70d34617e7de8ef748 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Thu, 16 Jun 2022 23:42:38 +0200 Subject: [PATCH 206/291] fix: do bound check while checking for CIDv0 This commit was moved from ipld/go-car@24feaada4ddc7936153c652538d7743d51afae6b --- ipld/car/util/util.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ipld/car/util/util.go b/ipld/car/util/util.go index 320ecf89ff..fbd6a86bcf 100644 --- a/ipld/car/util/util.go +++ b/ipld/car/util/util.go @@ -28,9 +28,13 @@ type BytesReader interface { // TODO: this belongs in the go-cid package func ReadCid(buf []byte) (cid.Cid, int, error) { - if bytes.Equal(buf[:2], cidv0Pref) { - c, err := cid.Cast(buf[:34]) - return c, 34, err + if len(buf) >= 2 && bytes.Equal(buf[:2], cidv0Pref) { + i := 34 + if len(buf) < i { + i = len(buf) + } + c, err := cid.Cast(buf[:i]) + return c, i, err } br := bytes.NewReader(buf) From eed3b0b1664729e395304e8f0d4acfc2d4fb02f6 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Thu, 16 Jun 2022 23:43:10 +0200 Subject: [PATCH 207/291] test: add fuzzing of NewCarReader This commit was moved from ipld/go-car@d648fc1dbf3310dc8648455b4755b25f13946d9d --- ipld/car/car_test.go | 6 ++-- ipld/car/fuzz_test.go | 35 +++++++++++++++++++ ...133bae39a14164874ed8abdee1f6a6795311a0e546 | 2 ++ ...30cc13b5570849c89b3dbf5bc0152abc66c9642f3e | 2 ++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 ipld/car/fuzz_test.go create mode 100644 ipld/car/testdata/fuzz/FuzzCarReader/21a90a70853c333c6b9ddc133bae39a14164874ed8abdee1f6a6795311a0e546 create mode 100644 ipld/car/testdata/fuzz/FuzzCarReader/5857e57e4072c6b0d8684030cc13b5570849c89b3dbf5bc0152abc66c9642f3e diff --git a/ipld/car/car_test.go b/ipld/car/car_test.go index 9ae309099a..3c6340be3e 100644 --- a/ipld/car/car_test.go +++ b/ipld/car/car_test.go @@ -75,9 +75,11 @@ func TestRoundtrip(t *testing.T) { } } +// fixture is a clean single-block, single-root CAR +const fixtureStr = "3aa265726f6f747381d82a58250001711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80b6776657273696f6e012c01711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80ba165646f646779f5" + func TestEOFHandling(t *testing.T) { - // fixture is a clean single-block, single-root CAR - fixture, err := hex.DecodeString("3aa265726f6f747381d82a58250001711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80b6776657273696f6e012c01711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80ba165646f646779f5") + fixture, err := hex.DecodeString(fixtureStr) if err != nil { t.Fatal(err) } diff --git a/ipld/car/fuzz_test.go b/ipld/car/fuzz_test.go new file mode 100644 index 0000000000..8ac04bbd07 --- /dev/null +++ b/ipld/car/fuzz_test.go @@ -0,0 +1,35 @@ +//go:build go1.18 +// +build go1.18 + +package car_test + +import ( + "bytes" + "encoding/hex" + "io" + "testing" + + car "github.com/ipld/go-car" +) + +func FuzzCarReader(f *testing.F) { + fixture, err := hex.DecodeString(fixtureStr) + if err != nil { + f.Fatal(err) + } + f.Add(fixture) + + f.Fuzz(func(t *testing.T, data []byte) { + r, err := car.NewCarReader(bytes.NewReader(data)) + if err != nil { + return + } + + for { + _, err = r.Next() + if err == io.EOF { + return + } + } + }) +} diff --git a/ipld/car/testdata/fuzz/FuzzCarReader/21a90a70853c333c6b9ddc133bae39a14164874ed8abdee1f6a6795311a0e546 b/ipld/car/testdata/fuzz/FuzzCarReader/21a90a70853c333c6b9ddc133bae39a14164874ed8abdee1f6a6795311a0e546 new file mode 100644 index 0000000000..a7ab1d519c --- /dev/null +++ b/ipld/car/testdata/fuzz/FuzzCarReader/21a90a70853c333c6b9ddc133bae39a14164874ed8abdee1f6a6795311a0e546 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xe0\xe0\xe0\xe0\xa7\x06\folLʔ<#oK\x19g#H\x96\b\xed\xb4*\x8b\x8f\xa8\vgversion\x19") diff --git a/ipld/car/testdata/fuzz/FuzzCarReader/5857e57e4072c6b0d8684030cc13b5570849c89b3dbf5bc0152abc66c9642f3e b/ipld/car/testdata/fuzz/FuzzCarReader/5857e57e4072c6b0d8684030cc13b5570849c89b3dbf5bc0152abc66c9642f3e new file mode 100644 index 0000000000..3e680cf60e --- /dev/null +++ b/ipld/car/testdata/fuzz/FuzzCarReader/5857e57e4072c6b0d8684030cc13b5570849c89b3dbf5bc0152abc66c9642f3e @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte(":\xa2eroots\x81\xd80X%\x00\x0100 00000000000000000000000000000000gversion\x01\x010") From 18a60b08fc58cc3cbd8f7d7000b8899fb93a9b89 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 17 Jun 2022 01:04:46 +0200 Subject: [PATCH 208/291] fix: v2 don't OOM if the header size is too big This commit was moved from ipld/go-car@bf8f97ae8afb64741de0e3f5cab927fd943523a9 --- ipld/car/v2/internal/carv1/util/util.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ipld/car/v2/internal/carv1/util/util.go b/ipld/car/v2/internal/carv1/util/util.go index 6b94956137..dd543ac50a 100644 --- a/ipld/car/v2/internal/carv1/util/util.go +++ b/ipld/car/v2/internal/carv1/util/util.go @@ -1,6 +1,7 @@ package util import ( + "errors" "io" internalio "github.com/ipld/go-car/v2/internal/io" @@ -73,6 +74,11 @@ func LdRead(r io.Reader, zeroLenAsEOF bool) ([]byte, error) { return nil, io.EOF } + const maxAllowedHeaderSize = 1024 * 1024 + if l > maxAllowedHeaderSize { // Don't OOM + return nil, errors.New("invalid input, too big header") + } + buf := make([]byte, l) if _, err := io.ReadFull(r, buf); err != nil { return nil, err From 879fc55928e5739f4b7114c42694ddb8c3c1e9b9 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 17 Jun 2022 01:05:04 +0200 Subject: [PATCH 209/291] test: v2 add fuzzing to BlockReader This commit was moved from ipld/go-car@78a5c3c56412afed13be28fd83f21876849732a2 --- ipld/car/v2/fuzz_test.go | 58 +++++++++++++++++++ ...1ba9f5138a38c882a7fa06456595998e740a9f5a14 | 2 + 2 files changed, 60 insertions(+) create mode 100644 ipld/car/v2/fuzz_test.go create mode 100644 ipld/car/v2/testdata/fuzz/FuzzBlockReader/c3c7eedeb4968a5b3131371ba9f5138a38c882a7fa06456595998e740a9f5a14 diff --git a/ipld/car/v2/fuzz_test.go b/ipld/car/v2/fuzz_test.go new file mode 100644 index 0000000000..82155ae9e9 --- /dev/null +++ b/ipld/car/v2/fuzz_test.go @@ -0,0 +1,58 @@ +//go:build go1.18 +// +build go1.18 + +package car_test + +import ( + "bytes" + "encoding/hex" + "io" + "os" + "path/filepath" + "testing" + + car "github.com/ipld/go-car/v2" +) + +// v1FixtureStr is a clean carv1 single-block, single-root CAR +const v1FixtureStr = "3aa265726f6f747381d82a58250001711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80b6776657273696f6e012c01711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80ba165646f646779f5" + +func FuzzBlockReader(f *testing.F) { + fixture, err := hex.DecodeString(v1FixtureStr) + if err != nil { + f.Fatal(err) + } + f.Add(fixture) + files, err := filepath.Glob("testdata/*.car") + if err != nil { + f.Fatal(err) + } + for _, fname := range files { + func() { + file, err := os.Open(fname) + if err != nil { + f.Fatal(err) + } + defer file.Close() + data, err := io.ReadAll(file) + if err != nil { + f.Fatal(err) + } + f.Add(data) + }() + } + + f.Fuzz(func(t *testing.T, data []byte) { + r, err := car.NewBlockReader(bytes.NewReader(data)) + if err != nil { + return + } + + for { + _, err = r.Next() + if err == io.EOF { + return + } + } + }) +} diff --git a/ipld/car/v2/testdata/fuzz/FuzzBlockReader/c3c7eedeb4968a5b3131371ba9f5138a38c882a7fa06456595998e740a9f5a14 b/ipld/car/v2/testdata/fuzz/FuzzBlockReader/c3c7eedeb4968a5b3131371ba9f5138a38c882a7fa06456595998e740a9f5a14 new file mode 100644 index 0000000000..ae9262d491 --- /dev/null +++ b/ipld/car/v2/testdata/fuzz/FuzzBlockReader/c3c7eedeb4968a5b3131371ba9f5138a38c882a7fa06456595998e740a9f5a14 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\xff\x80\xaa\x95\xa6sion\x01,\x01q\x12 \x15\x1f\xe9\xe7 Date: Fri, 17 Jun 2022 01:50:16 +0200 Subject: [PATCH 210/291] fix: v2 don't accept overflowing offsets while reading v2 headers This commit was moved from ipld/go-car@8414960d76acbe27f340ddc9668f2cb69a9581d0 --- ipld/car/v2/car.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index f2885d9d6e..194731363f 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -2,6 +2,7 @@ package car import ( "encoding/binary" + "errors" "io" ) @@ -166,8 +167,16 @@ func (h *Header) ReadFrom(r io.Reader) (int64, error) { if err != nil { return n, err } - h.DataOffset = binary.LittleEndian.Uint64(buf[:8]) - h.DataSize = binary.LittleEndian.Uint64(buf[8:16]) - h.IndexOffset = binary.LittleEndian.Uint64(buf[16:]) + dataOffset := binary.LittleEndian.Uint64(buf[:8]) + dataSize := binary.LittleEndian.Uint64(buf[8:16]) + indexOffset := binary.LittleEndian.Uint64(buf[16:]) + if int64(dataOffset) < 0 || + int64(dataSize) < 0 || + int64(indexOffset) < 0 { + return n, errors.New("malformed car, overflowing offsets") + } + h.DataOffset = dataOffset + h.DataSize = dataSize + h.IndexOffset = indexOffset return n, nil } From a1f8ca251b06853c2bc64a8cb601fe6b951e77bc Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 17 Jun 2022 01:23:10 +0200 Subject: [PATCH 211/291] test: v2 add fuzzing to Reader This commit was moved from ipld/go-car@b80929dd7ee763597b6d1a2a4b01bf5af176973b --- ipld/car/v2/fuzz_test.go | 25 ++++++++++++++++++- ...9ac8fb6717ecba6e7e591a1ab111514f30e4b3594e | 2 ++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 ipld/car/v2/testdata/fuzz/FuzzReader/e1d7f87ee37f48386642fa9ac8fb6717ecba6e7e591a1ab111514f30e4b3594e diff --git a/ipld/car/v2/fuzz_test.go b/ipld/car/v2/fuzz_test.go index 82155ae9e9..a11dafceba 100644 --- a/ipld/car/v2/fuzz_test.go +++ b/ipld/car/v2/fuzz_test.go @@ -12,12 +12,13 @@ import ( "testing" car "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" ) // v1FixtureStr is a clean carv1 single-block, single-root CAR const v1FixtureStr = "3aa265726f6f747381d82a58250001711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80b6776657273696f6e012c01711220151fe9e73c6267a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80ba165646f646779f5" -func FuzzBlockReader(f *testing.F) { +func seedWithCarFiles(f *testing.F) { fixture, err := hex.DecodeString(v1FixtureStr) if err != nil { f.Fatal(err) @@ -41,6 +42,10 @@ func FuzzBlockReader(f *testing.F) { f.Add(data) }() } +} + +func FuzzBlockReader(f *testing.F) { + seedWithCarFiles(f) f.Fuzz(func(t *testing.T, data []byte) { r, err := car.NewBlockReader(bytes.NewReader(data)) @@ -56,3 +61,21 @@ func FuzzBlockReader(f *testing.F) { } }) } + +func FuzzReader(f *testing.F) { + seedWithCarFiles(f) + + f.Fuzz(func(t *testing.T, data []byte) { + subject, err := car.NewReader(bytes.NewReader(data)) + if err != nil { + return + } + + subject.Roots() + ir := subject.IndexReader() + if ir != nil { + index.ReadFrom(ir) + } + car.GenerateIndex(subject.DataReader()) + }) +} diff --git a/ipld/car/v2/testdata/fuzz/FuzzReader/e1d7f87ee37f48386642fa9ac8fb6717ecba6e7e591a1ab111514f30e4b3594e b/ipld/car/v2/testdata/fuzz/FuzzReader/e1d7f87ee37f48386642fa9ac8fb6717ecba6e7e591a1ab111514f30e4b3594e new file mode 100644 index 0000000000..36168e1c62 --- /dev/null +++ b/ipld/car/v2/testdata/fuzz/FuzzReader/e1d7f87ee37f48386642fa9ac8fb6717ecba6e7e591a1ab111514f30e4b3594e @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0\xa1gversion\x0200000000000000000000000\xb70000000\x8100000000") From 5cc8a7442863a905979855873bf0cc89326d363f Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 17 Jun 2022 01:56:12 +0200 Subject: [PATCH 212/291] fix: v2 don't allocate indexes too big This commit was moved from ipld/go-car@0b68762f86708992ed3267075e6eeca4f6757a70 --- ipld/car/v2/index/indexsorted.go | 5 +++++ ...1356ad2fe2217292374cd93b65a482fec3a43e6021fe87f5607bd8857 | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 ipld/car/v2/testdata/fuzz/FuzzReader/fe93cec1356ad2fe2217292374cd93b65a482fec3a43e6021fe87f5607bd8857 diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 2c05a9227d..8eb0651ed2 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -3,6 +3,7 @@ package index import ( "bytes" "encoding/binary" + "errors" "fmt" "io" "sort" @@ -67,6 +68,10 @@ func (s *singleWidthIndex) Unmarshal(r io.Reader) error { if err := binary.Read(r, binary.LittleEndian, &s.len); err != nil { return err } + const maxSingleWidthIndexSize = 1024 * 1024 + if s.len > maxSingleWidthIndexSize { + return errors.New("single width index is too big") + } s.index = make([]byte, s.len) s.len /= uint64(s.width) _, err := io.ReadFull(r, s.index) diff --git a/ipld/car/v2/testdata/fuzz/FuzzReader/fe93cec1356ad2fe2217292374cd93b65a482fec3a43e6021fe87f5607bd8857 b/ipld/car/v2/testdata/fuzz/FuzzReader/fe93cec1356ad2fe2217292374cd93b65a482fec3a43e6021fe87f5607bd8857 new file mode 100644 index 0000000000..1552def121 --- /dev/null +++ b/ipld/car/v2/testdata/fuzz/FuzzReader/fe93cec1356ad2fe2217292374cd93b65a482fec3a43e6021fe87f5607bd8857 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("\n\xa1gversion\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003\x00\x00\x00\x00\x00\x00\x00\x1c\x01\x00\x00\x00\x00\x00\x00O\x01\x00\x00\x00\x00\x00\x00:\xa2eroots\x81\xd8*X%\x00\x01p\x12 \n4Hq\xde\xe6\xc7T \x7fYl\xfc\x95\xae)Ԡ\xa2\x1ep#\x92Z]a% mԪ\xb4gversion\x010\x01U\x12 \xa9H\x90O/\x0fG\x9b\x8f\x81\x97iK0\x18K\r.\xd1\xc1\xcd*\x1e\xc0\xfb\x85ҙ\xa1\x92\xa4Ghello \x80\xffrld\nY\x01p\x12 \xcb\xce\x128i\xf8U\xaf\x03\xccIu,\x1b\x05\x83 \xb7\xb8\xac\x1c\xeb|\x8eJ\xcbg\x1d\xf6y\x81\x12/\n$\x01U\x12 \xa9H\x90O/\x0fG\x9b\x8f\x81\x97iK0\x18K\r.\xd1\xc1\xcd*\x1e\xc0\xfb\x85ҙ\xa1\x92\xa4G\x12\x05b.txt\x18\f\n\x02\b\x01U\x01p\x12 \n4Hq\xde\xe6\xc7T \x7fYl\xfc\x95\xae)Ԡ\xa2\x1ep#\x92Z]a% mԪ\xb4\x12+\n$\x01p\x12 \xcb\xce\x128i\xf8U\xaf\x03\xccIu,\x1b\x05\x83 \xb7\xb8\xac\x1c\xeb|\x8eJ\xcbg\x1d\xf6y\x81\x12\x01a\x18\x00\n\x02\b\x01\x81\b\x12\x00\x00\n4Hq\xde\xe6\xc7T \x7fYl\xfc\x95\xae)Ԡ\xa2\x1ep#\x92Z]a% \x86\x86\x86\x86\x86\x86\x86\x00\x00\x00\x00\x00\xa9H\x90O/\x0fG\x9b\x8f\x81\x97iK0\x18K\r.\xd1\xc1\xcd*\x1e\xc0\xfb\x85ҙ\xa1\x92\xa4G;\x00\x00\x00\x00\x00\x00\x00\xcb\xce\x128i\xf8U\xaf\x03\xccIu,\x1b\x05\x83 \xb7\xb8\xac\x1c\xeb|\x8eJ\xcbg\x1d\xf6y\x81l\x00\x00\x00\x00\x00\x00\x00") From f4e13fd728624eeb00f5a6f588bc27347a021f2e Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 17 Jun 2022 02:01:37 +0200 Subject: [PATCH 213/291] fix: v2 don't divide by zero in width indexes This commit was moved from ipld/go-car@c43fcbca574d4d73454670e76b8f9a6abb1a4624 --- ipld/car/v2/index/indexsorted.go | 3 +++ ...d07c02728da32cdaa17e13646f587ea278d888a655f683d6e42f19c0316 | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 ipld/car/v2/testdata/fuzz/FuzzReader/c640dd07c02728da32cdaa17e13646f587ea278d888a655f683d6e42f19c0316 diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 8eb0651ed2..9e58a7cbbd 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -65,6 +65,9 @@ func (s *singleWidthIndex) Unmarshal(r io.Reader) error { if err := binary.Read(r, binary.LittleEndian, &s.width); err != nil { return err } + if s.width == 0 { + return errors.New("malformed car width index cannot be 0") + } if err := binary.Read(r, binary.LittleEndian, &s.len); err != nil { return err } diff --git a/ipld/car/v2/testdata/fuzz/FuzzReader/c640dd07c02728da32cdaa17e13646f587ea278d888a655f683d6e42f19c0316 b/ipld/car/v2/testdata/fuzz/FuzzReader/c640dd07c02728da32cdaa17e13646f587ea278d888a655f683d6e42f19c0316 new file mode 100644 index 0000000000..5af80cf1ed --- /dev/null +++ b/ipld/car/v2/testdata/fuzz/FuzzReader/c640dd07c02728da32cdaa17e13646f587ea278d888a655f683d6e42f19c0316 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0\xa1gversion\x0200000000000000000000000000000000O\x01\x00\x00\x00\x00\x00\x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\x81\b0000000000000000\x00\x00\x00\x0000\x00\x00\x00\x00\x00\x00") From 99417ba82c3a76fa39997510b524a5f7919f9dce Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 17 Jun 2022 03:27:53 +0200 Subject: [PATCH 214/291] test: v2 add fuzzing of the index This commit was moved from ipld/go-car@a504086a5bb8bea3d32c61f089aef77910e37717 --- ipld/car/v2/fuzz_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ipld/car/v2/fuzz_test.go b/ipld/car/v2/fuzz_test.go index a11dafceba..2b74fb6b73 100644 --- a/ipld/car/v2/fuzz_test.go +++ b/ipld/car/v2/fuzz_test.go @@ -79,3 +79,36 @@ func FuzzReader(f *testing.F) { car.GenerateIndex(subject.DataReader()) }) } + +func FuzzIndex(f *testing.F) { + files, err := filepath.Glob("testdata/*.car") + if err != nil { + f.Fatal(err) + } + for _, fname := range files { + func() { + file, err := os.Open(fname) + if err != nil { + f.Fatal(err) + } + defer file.Close() + subject, err := car.NewReader(file) + if err != nil { + return + } + index := subject.IndexReader() + if index == nil { + return + } + data, err := io.ReadAll(index) + if err != nil { + f.Fatal(err) + } + f.Add(data) + }() + } + + f.Fuzz(func(t *testing.T, data []byte) { + index.ReadFrom(bytes.NewReader(data)) + }) +} From 9ec5f980c4182ad4421b3de714d30440dfd5ba4c Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 17 Jun 2022 03:36:50 +0200 Subject: [PATCH 215/291] ci: add fuzzing on CI This commit was moved from ipld/go-car@32c1008b892a5b3b25fdc2001f6a132637008315 --- ipld/car/.github/workflows/go-fuzz.yml | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 ipld/car/.github/workflows/go-fuzz.yml diff --git a/ipld/car/.github/workflows/go-fuzz.yml b/ipld/car/.github/workflows/go-fuzz.yml new file mode 100644 index 0000000000..4b51f69c19 --- /dev/null +++ b/ipld/car/.github/workflows/go-fuzz.yml @@ -0,0 +1,50 @@ +on: [push, pull_request] +name: Go Fuzz + +jobs: + v1: + strategy: + fail-fast: true + matrix: + target: [ "CarReader" ] + runs-on: ubuntu-latest + name: Fuzz V1 ${{ matrix.target }} + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - uses: actions/setup-go@v2 + with: + go-version: 1.18.x + - name: Go information + run: | + go version + go env + - name: Run Fuzzing for 1m + uses: protocol/multiple-go-modules@v1.2 + with: + run: go test -v -fuzz=Fuzz${{ matrix.target }} -fuzztime=1m . + v2: + strategy: + fail-fast: true + matrix: + target: [ "BlockReader", "Reader", "Index" ] + runs-on: ubuntu-latest + name: Fuzz V2 ${{ matrix.target }} + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - uses: actions/setup-go@v2 + with: + go-version: 1.18.x + - name: Go information + run: | + go version + go env + - name: Run Fuzzing for 1m + uses: protocol/multiple-go-modules@v1.2 + with: + run: | + cd v2 + go test -v -fuzz=Fuzz${{ matrix.target }} -fuzztime=1m . From 821a3d4362f7489ae4cc919c42458a46417d56aa Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 17 Jun 2022 19:57:56 +0200 Subject: [PATCH 216/291] feat: Refactor indexes to put storage considerations on consumers There is no way I can make a safe implementation of the parser by slurping thing into memory, indexes people use are just too big. So I made a new API which force consumers to manage that. They can choose to use a bytes.Reader, *os.File, mmaped thing, ... This commit was moved from ipld/go-car@3923d315f1083ce25ee4e790287cc5702a381b13 --- ipld/car/v2/blockstore/insertionindex.go | 24 +++ ipld/car/v2/blockstore/readonly.go | 13 +- ipld/car/v2/blockstore/readwrite.go | 23 ++- ipld/car/v2/blockstore/readwrite_test.go | 23 +-- ipld/car/v2/fuzz_test.go | 10 +- ipld/car/v2/index/index.go | 50 ++++-- ipld/car/v2/index/indexsorted.go | 165 +++++++++++++++--- ipld/car/v2/index/indexsorted_test.go | 3 +- ipld/car/v2/index/mhindexsorted.go | 59 ++++++- ipld/car/v2/index/mhindexsorted_test.go | 2 +- ipld/car/v2/index/testutil/equal_index.go | 65 +++++++ ipld/car/v2/index_gen_test.go | 7 +- ipld/car/v2/internal/errsort/search.go | 54 ++++++ ipld/car/v2/internal/io/fullReaderAt.go | 20 +++ ipld/car/v2/internal/io/offset_read_seeker.go | 124 ++++++++++--- ipld/car/v2/reader.go | 4 +- ipld/car/v2/reader_test.go | 11 +- ipld/car/v2/writer_test.go | 3 +- 18 files changed, 549 insertions(+), 111 deletions(-) create mode 100644 ipld/car/v2/index/testutil/equal_index.go create mode 100644 ipld/car/v2/internal/errsort/search.go create mode 100644 ipld/car/v2/internal/io/fullReaderAt.go diff --git a/ipld/car/v2/blockstore/insertionindex.go b/ipld/car/v2/blockstore/insertionindex.go index e8575ee1de..95971aa211 100644 --- a/ipld/car/v2/blockstore/insertionindex.go +++ b/ipld/car/v2/blockstore/insertionindex.go @@ -9,6 +9,7 @@ import ( "github.com/ipfs/go-cid" "github.com/ipld/go-car/v2/index" + internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" "github.com/petar/GoLLRB/llrb" @@ -121,6 +122,20 @@ func (ii *insertionIndex) Marshal(w io.Writer) (uint64, error) { return l, err } +func (ii *insertionIndex) ForEach(f func(multihash.Multihash, uint64) error) error { + var errr error + ii.items.AscendGreaterOrEqual(ii.items.Min(), func(i llrb.Item) bool { + r := i.(recordDigest).Record + err := f(r.Cid.Hash(), r.Offset) + if err != nil { + errr = err + return false + } + return true + }) + return errr +} + func (ii *insertionIndex) Unmarshal(r io.Reader) error { var length int64 if err := binary.Read(r, binary.LittleEndian, &length); err != nil { @@ -137,6 +152,15 @@ func (ii *insertionIndex) Unmarshal(r io.Reader) error { return nil } +func (ii *insertionIndex) UnmarshalLazyRead(r io.ReaderAt) (int64, error) { + rdr := internalio.NewOffsetReadSeeker(r, 0) + err := ii.Unmarshal(rdr) + if err != nil { + return 0, err + } + return rdr.Seek(0, io.SeekCurrent) +} + func (ii *insertionIndex) Codec() multicodec.Code { return insertionIndexCodec } diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index f0a15e782a..141088b713 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -182,8 +182,11 @@ func OpenReadOnly(path string, opts ...carv2.Option) (*ReadOnly, error) { } func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { - bcid, data, err := util.ReadNode(internalio.NewOffsetReadSeeker(b.backing, idx), b.opts.ZeroLengthSectionAsEOF) - return bcid, data, err + r, err := internalio.NewOffsetReadSeekerWithError(b.backing, idx) + if err != nil { + return cid.Cid{}, nil, err + } + return util.ReadNode(r, b.opts.ZeroLengthSectionAsEOF) } // DeleteBlock is unsupported and always errors. @@ -441,7 +444,11 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { } } - thisItemForNxt := rdr.Offset() + thisItemForNxt, err := rdr.Seek(0, io.SeekCurrent) + if err != nil { + maybeReportError(ctx, err) + return + } _, c, err := cid.CidFromReader(rdr) if err != nil { maybeReportError(ctx, err) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 090633c05a..de43999f76 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -86,12 +86,12 @@ func AllowDuplicatePuts(allow bool) carv2.Option { // successfully. On resumption the roots argument and WithDataPadding option must match the // previous instantiation of ReadWrite blockstore that created the file. More explicitly, the file // resuming from must: -// 1. start with a complete CARv2 car.Pragma. -// 2. contain a complete CARv1 data header with root CIDs matching the CIDs passed to the -// constructor, starting at offset optionally padded by WithDataPadding, followed by zero or -// more complete data sections. If any corrupt data sections are present the resumption will fail. -// Note, if set previously, the blockstore must use the same WithDataPadding option as before, -// since this option is used to locate the CARv1 data payload. +// 1. start with a complete CARv2 car.Pragma. +// 2. contain a complete CARv1 data header with root CIDs matching the CIDs passed to the +// constructor, starting at offset optionally padded by WithDataPadding, followed by zero or +// more complete data sections. If any corrupt data sections are present the resumption will fail. +// Note, if set previously, the blockstore must use the same WithDataPadding option as before, +// since this option is used to locate the CARv1 data payload. // // Note, resumption should be used with WithCidDeduplication, so that blocks that are successfully // written into the file are not re-written. Unless, the user explicitly wants duplicate blocks. @@ -139,7 +139,10 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.Option) (*ReadWri offset = 0 } rwbs.dataWriter = internalio.NewOffsetWriter(rwbs.f, offset) - v1r := internalio.NewOffsetReadSeeker(rwbs.f, offset) + v1r, err := internalio.NewOffsetReadSeekerWithError(rwbs.f, offset) + if err != nil { + return nil, err + } rwbs.ronly.backing = v1r rwbs.ronly.idx = rwbs.idx rwbs.ronly.carv2Closer = rwbs.f @@ -190,7 +193,11 @@ func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid) error { // Check if file was finalized by trying to read the CARv2 header. // We check because if finalized the CARv1 reader behaviour needs to be adjusted since // EOF will not signify end of CARv1 payload. i.e. index is most likely present. - _, err = headerInFile.ReadFrom(internalio.NewOffsetReadSeeker(b.f, carv2.PragmaSize)) + r, err := internalio.NewOffsetReadSeekerWithError(b.f, carv2.PragmaSize) + if err != nil { + return err + } + _, err = headerInFile.ReadFrom(r) // If reading CARv2 header succeeded, and CARv1 offset in header is not zero then the file is // most-likely finalized. Check padding and truncate the file to remove index. diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 86ab06ea12..bc78083abb 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -842,20 +842,15 @@ func TestOpenReadWrite_WritesIdentityCIDsWhenOptionIsEnabled(t *testing.T) { expectedOffset := len(object) + 1 // Assert index is iterable and has exactly one record with expected multihash and offset. - switch idx := gotIdx.(type) { - case index.IterableIndex: - var i int - err := idx.ForEach(func(mh multihash.Multihash, offset uint64) error { - i++ - require.Equal(t, idmh, mh) - require.Equal(t, uint64(expectedOffset), offset) - return nil - }) - require.NoError(t, err) - require.Equal(t, 1, i) - default: - require.Failf(t, "unexpected index type", "wanted %v but got %v", multicodec.CarMultihashIndexSorted, idx.Codec()) - } + var count int + err = gotIdx.ForEach(func(mh multihash.Multihash, offset uint64) error { + count++ + require.Equal(t, idmh, mh) + require.Equal(t, uint64(expectedOffset), offset) + return nil + }) + require.NoError(t, err) + require.Equal(t, 1, count) } func TestOpenReadWrite_ErrorsWhenWritingTooLargeOfACid(t *testing.T) { diff --git a/ipld/car/v2/fuzz_test.go b/ipld/car/v2/fuzz_test.go index 2b74fb6b73..8187457b52 100644 --- a/ipld/car/v2/fuzz_test.go +++ b/ipld/car/v2/fuzz_test.go @@ -96,11 +96,15 @@ func FuzzIndex(f *testing.F) { if err != nil { return } - index := subject.IndexReader() - if index == nil { + indexRdr := subject.IndexReader() + if indexRdr == nil { return } - data, err := io.ReadAll(index) + _, n, err := index.ReadFromWithSize(indexRdr) + if err != nil { + return + } + data, err := io.ReadAll(io.NewSectionReader(indexRdr, 0, n)) if err != nil { f.Fatal(err) } diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 10195b43a3..204bc12213 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -45,8 +45,14 @@ type ( // Marshal encodes the index in serial form. Marshal(w io.Writer) (uint64, error) // Unmarshal decodes the index from its serial form. + // Deprecated: This function is slurpy and will copy everything into memory. Unmarshal(r io.Reader) error + // UnmarshalLazyRead is the safe alternative to to Unmarshal. + // Instead of slurping it will keep a reference to the the io.ReaderAt passed in + // and ask for data as needed. + UnmarshalLazyRead(r io.ReaderAt) (indexSize int64, err error) + // Load inserts a number of records into the index. // Note that Index will load all given records. Any filtering of the records such as // exclusion of CIDs with multihash.IDENTITY code must occur prior to calling this function. @@ -66,18 +72,6 @@ type ( // meaning that no callbacks happen, // ErrNotFound is returned. GetAll(cid.Cid, func(uint64) bool) error - } - - // IterableIndex extends Index in cases where the Index is able to - // provide an iterator for getting the list of all multihashes in the - // index. - // - // Note that it is possible for an index to contain multiple offsets for - // a given multihash. - // - // See: IterableIndex.ForEach, Index.GetAll. - IterableIndex interface { - Index // ForEach takes a callback function that will be called // on each entry in the index. The arguments to the callback are @@ -93,6 +87,12 @@ type ( // The order of calls to the given function is deterministic, but entirely index-specific. ForEach(func(multihash.Multihash, uint64) error) error } + + // IterableIndex is an index which support iterating over it's elements + // Deprecated: IterableIndex has been moved into Index. Just use Index now. + IterableIndex interface { + Index + } ) // GetFirst is a wrapper over Index.GetAll, returning the offset for the first @@ -136,18 +136,30 @@ func WriteTo(idx Index, w io.Writer) (uint64, error) { // ReadFrom reads index from r. // The reader decodes the index by reading the first byte to interpret the encoding. // Returns error if the encoding is not known. -func ReadFrom(r io.Reader) (Index, error) { - code, err := varint.ReadUvarint(internalio.ToByteReader(r)) +func ReadFrom(r io.ReaderAt) (Index, error) { + idx, _, err := ReadFromWithSize(r) + return idx, err +} + +// ReadFromWithSize is just like ReadFrom but return the size of the Index. +// The size is only valid when err != nil. +func ReadFromWithSize(r io.ReaderAt) (Index, int64, error) { + code, err := varint.ReadUvarint(internalio.NewOffsetReadSeeker(r, 0)) if err != nil { - return nil, err + return nil, 0, err } codec := multicodec.Code(code) idx, err := New(codec) if err != nil { - return nil, err + return nil, 0, err + } + rdr, err := internalio.NewOffsetReadSeekerWithError(r, int64(varint.UvarintSize(code))) + if err != nil { + return nil, 0, err } - if err := idx.Unmarshal(r); err != nil { - return nil, err + n, err := idx.UnmarshalLazyRead(rdr) + if err != nil { + return nil, 0, err } - return idx, nil + return idx, n, nil } diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 9e58a7cbbd..16367ed526 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -8,12 +8,19 @@ import ( "io" "sort" + "github.com/ipld/go-car/v2/internal/errsort" + internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" ) +type sizedReaderAt interface { + io.ReaderAt + Size() int64 +} + var _ Index = (*multiWidthIndex)(nil) type ( @@ -25,7 +32,7 @@ type ( singleWidthIndex struct { width uint32 len uint64 // in struct, len is #items. when marshaled, it's saved as #bytes. - index []byte + index sizedReaderAt } multiWidthIndex map[uint32]singleWidthIndex ) @@ -53,36 +60,72 @@ func (s *singleWidthIndex) Marshal(w io.Writer) (uint64, error) { return 0, err } l += 4 - if err := binary.Write(w, binary.LittleEndian, int64(len(s.index))); err != nil { + sz := s.index.Size() + if err := binary.Write(w, binary.LittleEndian, sz); err != nil { return l, err } l += 8 - n, err := w.Write(s.index) + n, err := io.Copy(w, io.NewSectionReader(s.index, 0, sz)) return l + uint64(n), err } +// Unmarshal decodes the index from its serial form. +// Deprecated: This function is slurpy and will copy the index in memory. func (s *singleWidthIndex) Unmarshal(r io.Reader) error { - if err := binary.Read(r, binary.LittleEndian, &s.width); err != nil { + var width uint32 + if err := binary.Read(r, binary.LittleEndian, &width); err != nil { return err } - if s.width == 0 { - return errors.New("malformed car width index cannot be 0") + var dataLen uint64 + if err := binary.Read(r, binary.LittleEndian, &dataLen); err != nil { + return err } - if err := binary.Read(r, binary.LittleEndian, &s.len); err != nil { + + if err := s.checkUnmarshalLengths(width, dataLen, 0); err != nil { + return err + } + + buf := make([]byte, dataLen) + if _, err := io.ReadFull(r, buf); err != nil { return err } - const maxSingleWidthIndexSize = 1024 * 1024 - if s.len > maxSingleWidthIndexSize { - return errors.New("single width index is too big") + s.index = bytes.NewReader(buf) + return nil +} + +func (s *singleWidthIndex) UnmarshalLazyRead(r io.ReaderAt) (indexSize int64, err error) { + var b [12]byte + _, err = internalio.FullReadAt(r, b[:], 0) + if err != nil { + return 0, err + } + + width := binary.LittleEndian.Uint32(b[:4]) + dataLen := binary.LittleEndian.Uint64(b[4:12]) + if err := s.checkUnmarshalLengths(width, dataLen, uint64(len(b))); err != nil { + return 0, err } - s.index = make([]byte, s.len) - s.len /= uint64(s.width) - _, err := io.ReadFull(r, s.index) - return err + s.index = io.NewSectionReader(r, int64(len(b)), int64(dataLen)) + return int64(dataLen) + int64(len(b)), nil } -func (s *singleWidthIndex) Less(i int, digest []byte) bool { - return bytes.Compare(digest[:], s.index[i*int(s.width):((i+1)*int(s.width)-8)]) <= 0 +func (s *singleWidthIndex) checkUnmarshalLengths(width uint32, dataLen, extra uint64) error { + if width <= 8 { + return errors.New("malformed index; width must be bigger than 8") + } + if int32(width) < 0 { + return errors.New("index too big; singleWidthIndex width is overflowing int32") + } + oldDataLen, dataLen := dataLen, dataLen+extra + if oldDataLen > dataLen { + return errors.New("index too big; singleWidthIndex len is overflowing") + } + if int64(dataLen) < 0 { + return errors.New("index too big; singleWidthIndex len is overflowing int64") + } + s.width = width + s.len = dataLen / uint64(width) + return nil } func (s *singleWidthIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { @@ -94,18 +137,35 @@ func (s *singleWidthIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { } func (s *singleWidthIndex) getAll(d []byte, fn func(uint64) bool) error { - idx := sort.Search(int(s.len), func(i int) bool { - return s.Less(i, d) + digestLen := int64(s.width) - 8 + b := make([]byte, digestLen) + idxI, err := errsort.Search(int(s.len), func(i int) (bool, error) { + digestStart := int64(i) * int64(s.width) + _, err := internalio.FullReadAt(s.index, b, digestStart) + if err != nil { + return false, err + } + return bytes.Compare(d, b) <= 0, nil }) + if err != nil { + return err + } + idx := int64(idxI) var any bool for ; uint64(idx) < s.len; idx++ { - digestStart := idx * int(s.width) - offsetEnd := (idx + 1) * int(s.width) + digestStart := idx * int64(s.width) + offsetEnd := digestStart + int64(s.width) digestEnd := offsetEnd - 8 - if bytes.Equal(d[:], s.index[digestStart:digestEnd]) { + digestLen := digestEnd - digestStart + b := make([]byte, offsetEnd-digestStart) + _, err := internalio.FullReadAt(s.index, b, digestStart) + if err != nil { + return err + } + if bytes.Equal(d, b[:digestLen]) { any = true - offset := binary.LittleEndian.Uint64(s.index[digestEnd:offsetEnd]) + offset := binary.LittleEndian.Uint64(b[digestLen:]) if !fn(offset) { // User signalled to stop searching; therefore, break. break @@ -139,13 +199,19 @@ func (s *singleWidthIndex) Load(items []Record) error { } func (s *singleWidthIndex) forEachDigest(f func(digest []byte, offset uint64) error) error { - segmentCount := len(s.index) / int(s.width) - for i := 0; i < segmentCount; i++ { - digestStart := i * int(s.width) - offsetEnd := (i + 1) * int(s.width) + segmentCount := s.index.Size() / int64(s.width) + for i := int64(0); i < segmentCount; i++ { + digestStart := i * int64(s.width) + offsetEnd := digestStart + int64(s.width) digestEnd := offsetEnd - 8 - digest := s.index[digestStart:digestEnd] - offset := binary.LittleEndian.Uint64(s.index[digestEnd:offsetEnd]) + digestLen := digestEnd - digestStart + b := make([]byte, offsetEnd-digestStart) + _, err := internalio.FullReadAt(s.index, b, digestStart) + if err != nil { + return err + } + digest := b[:digestLen] + offset := binary.LittleEndian.Uint64(b[digestLen:]) if err := f(digest, offset); err != nil { return err } @@ -212,6 +278,37 @@ func (m *multiWidthIndex) Unmarshal(r io.Reader) error { return nil } +func (m *multiWidthIndex) UnmarshalLazyRead(r io.ReaderAt) (sum int64, err error) { + var b [4]byte + _, err = internalio.FullReadAt(r, b[:], 0) + if err != nil { + return 0, err + } + count := binary.LittleEndian.Uint32(b[:4]) + if int32(count) < 0 { + return 0, errors.New("index too big; multiWidthIndex count is overflowing int32") + } + sum += int64(len(b)) + for ; count > 0; count-- { + s := singleWidthIndex{} + or, err := internalio.NewOffsetReadSeekerWithError(r, sum) + if err != nil { + return 0, err + } + n, err := s.UnmarshalLazyRead(or) + if err != nil { + return 0, err + } + oldSum := sum + sum += n + if sum < oldSum { + return 0, errors.New("index too big; multiWidthIndex len is overflowing int64") + } + (*m)[s.width] = s + } + return sum, nil +} + func (m *multiWidthIndex) Load(items []Record) error { // Split cids on their digest length idxs := make(map[int][]digestRecord) @@ -241,13 +338,23 @@ func (m *multiWidthIndex) Load(items []Record) error { s := singleWidthIndex{ width: uint32(rcrdWdth), len: uint64(len(lst)), - index: compact, + index: bytes.NewReader(compact), } (*m)[uint32(width)+8] = s } return nil } +func (m *multiWidthIndex) ForEach(f func(multihash.Multihash, uint64) error) error { + return m.forEachDigest(func(digest []byte, offset uint64) error { + mh, err := multihash.Cast(digest) + if err != nil { + return err + } + return f(mh, offset) + }) +} + func (m *multiWidthIndex) forEachDigest(f func(digest []byte, offset uint64) error) error { sizes := make([]uint32, 0, len(*m)) for k := range *m { diff --git a/ipld/car/v2/index/indexsorted_test.go b/ipld/car/v2/index/indexsorted_test.go index 5c1ee44956..f7e038a01b 100644 --- a/ipld/car/v2/index/indexsorted_test.go +++ b/ipld/car/v2/index/indexsorted_test.go @@ -1,6 +1,7 @@ package index import ( + "bytes" "encoding/binary" "testing" @@ -51,7 +52,7 @@ func TestSingleWidthIndex_GetAll(t *testing.T) { subject := &singleWidthIndex{ width: 9, len: uint64(l), - index: buf, + index: bytes.NewReader(buf), } var foundCount int diff --git a/ipld/car/v2/index/mhindexsorted.go b/ipld/car/v2/index/mhindexsorted.go index 55975b8e5f..0200f70076 100644 --- a/ipld/car/v2/index/mhindexsorted.go +++ b/ipld/car/v2/index/mhindexsorted.go @@ -2,17 +2,18 @@ package index import ( "encoding/binary" + "errors" "io" "sort" "github.com/ipfs/go-cid" + internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" ) var ( - _ Index = (*MultihashIndexSorted)(nil) - _ IterableIndex = (*MultihashIndexSorted)(nil) + _ Index = (*MultihashIndexSorted)(nil) ) type ( @@ -46,6 +47,29 @@ func (m *multiWidthCodedIndex) Unmarshal(r io.Reader) error { return m.multiWidthIndex.Unmarshal(r) } +func (m *multiWidthCodedIndex) UnmarshalLazyRead(r io.ReaderAt) (int64, error) { + var b [8]byte + _, err := internalio.FullReadAt(r, b[:], 0) + if err != nil { + return 0, err + } + m.code = binary.LittleEndian.Uint64(b[:8]) + rdr, err := internalio.NewOffsetReadSeekerWithError(r, int64(len(b))) + if err != nil { + return 0, err + } + sum, err := m.multiWidthIndex.UnmarshalLazyRead(rdr) + if err != nil { + return 0, err + } + oldSum := sum + sum += int64(len(b)) + if sum < oldSum { + return 0, errors.New("index too big; multiWidthCodedIndex len is overflowing") + } + return sum, nil +} + func (m *multiWidthCodedIndex) forEach(f func(mh multihash.Multihash, offset uint64) error) error { return m.multiWidthIndex.forEachDigest(func(digest []byte, offset uint64) error { mh, err := multihash.Encode(digest, m.code) @@ -107,6 +131,37 @@ func (m *MultihashIndexSorted) Unmarshal(r io.Reader) error { return nil } +func (m *MultihashIndexSorted) UnmarshalLazyRead(r io.ReaderAt) (sum int64, err error) { + var b [4]byte + _, err = internalio.FullReadAt(r, b[:], 0) + if err != nil { + return 0, err + } + sum += int64(len(b)) + count := binary.LittleEndian.Uint32(b[:4]) + if int32(count) < 0 { + return 0, errors.New("index too big; MultihashIndexSorted count is overflowing int32") + } + for ; count > 0; count-- { + mwci := newMultiWidthCodedIndex() + or, err := internalio.NewOffsetReadSeekerWithError(r, sum) + if err != nil { + return 0, err + } + n, err := mwci.UnmarshalLazyRead(or) + if err != nil { + return 0, err + } + oldSum := sum + sum += n + if sum < oldSum { + return 0, errors.New("index too big; MultihashIndexSorted sum is overflowing int64") + } + m.put(mwci) + } + return sum, nil +} + func (m *MultihashIndexSorted) put(mwci *multiWidthCodedIndex) { (*m)[mwci.code] = mwci } diff --git a/ipld/car/v2/index/mhindexsorted_test.go b/ipld/car/v2/index/mhindexsorted_test.go index 79fc9c5f01..d97d1e6f14 100644 --- a/ipld/car/v2/index/mhindexsorted_test.go +++ b/ipld/car/v2/index/mhindexsorted_test.go @@ -58,7 +58,7 @@ func TestMultiWidthCodedIndex_StableIterate(t *testing.T) { err = subject.Load(records) require.NoError(t, err) - iterable := subject.(index.IterableIndex) + iterable := subject.(index.Index) mh := make([]multihash.Multihash, 0, len(records)) require.NoError(t, iterable.ForEach(func(m multihash.Multihash, _ uint64) error { mh = append(mh, m) diff --git a/ipld/car/v2/index/testutil/equal_index.go b/ipld/car/v2/index/testutil/equal_index.go new file mode 100644 index 0000000000..1314d25e8f --- /dev/null +++ b/ipld/car/v2/index/testutil/equal_index.go @@ -0,0 +1,65 @@ +package testutil + +import ( + "sync" + "testing" + + "github.com/ipld/go-car/v2/index" + + "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" +) + +// insertUint64 perform one round of insertion sort on the last element +func insertUint64(s []uint64) { + switch len(s) { + case 0, 1: + return + default: + cur := s[len(s)-1] + for j := len(s) - 1; j > 0; { + j-- + if cur >= s[j] { + s[j+1] = cur + break + } + s[j+1] = s[j] + } + } +} + +func AssertIndenticalIndexes(t *testing.T, a, b index.Index) { + var wg sync.Mutex + wg.Lock() + // key is multihash.Multihash.HexString + var aCount uint + aMap := make(map[string][]uint64) + go func() { + defer wg.Unlock() + a.ForEach(func(mh multihash.Multihash, off uint64) error { + aCount++ + str := mh.HexString() + slice, _ := aMap[str] + slice = append(slice, off) + insertUint64(slice) + aMap[str] = slice + return nil + }) + }() + + var bCount uint + bMap := make(map[string][]uint64) + a.ForEach(func(mh multihash.Multihash, off uint64) error { + bCount++ + str := mh.HexString() + slice, _ := bMap[str] + slice = append(slice, off) + insertUint64(slice) + bMap[str] = slice + return nil + }) + wg.Lock() + + require.Equal(t, aCount, bCount) + require.Equal(t, aMap, bMap) +} diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 11f0530fb9..64a73adcf0 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -8,6 +8,7 @@ import ( "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" @@ -103,7 +104,11 @@ func TestGenerateIndex(t *testing.T) { if tt.wantIndexer != nil { want = tt.wantIndexer(t) } - require.Equal(t, want, got) + if want == nil { + require.Nil(t, got) + } else { + testutil.AssertIndenticalIndexes(t, want, got) + } } }) t.Run("GenerateIndexFromFile_"+tt.name, func(t *testing.T) { diff --git a/ipld/car/v2/internal/errsort/search.go b/ipld/car/v2/internal/errsort/search.go new file mode 100644 index 0000000000..fb88617940 --- /dev/null +++ b/ipld/car/v2/internal/errsort/search.go @@ -0,0 +1,54 @@ +/* +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package errsort + +// Search is like sort.Search but accepts an erroring closure. +// If it errors the search is terminated immediately +func Search(n int, f func(int) (bool, error)) (int, error) { + // Define f(-1) == false and f(n) == true. + // Invariant: f(i-1) == false, f(j) == true. + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + less, err := f(h) + if err != nil { + return 0, err + } + if !less { + i = h + 1 // preserves f(i-1) == false + } else { + j = h // preserves f(j) == true + } + } + // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. + return i, nil +} diff --git a/ipld/car/v2/internal/io/fullReaderAt.go b/ipld/car/v2/internal/io/fullReaderAt.go new file mode 100644 index 0000000000..57f2668590 --- /dev/null +++ b/ipld/car/v2/internal/io/fullReaderAt.go @@ -0,0 +1,20 @@ +package io + +import "io" + +func FullReadAt(r io.ReaderAt, b []byte, off int64) (sum int64, err error) { + for int64(len(b)) > sum { + n, err := r.ReadAt(b[sum:], off+sum) + sum += int64(n) + if err != nil { + if err == io.EOF { + if sum < int64(len(b)) { + return sum, io.ErrUnexpectedEOF + } + return sum, nil + } + return sum, err + } + } + return sum, nil +} diff --git a/ipld/car/v2/internal/io/offset_read_seeker.go b/ipld/car/v2/internal/io/offset_read_seeker.go index 4b701351fb..bbdcf4c63d 100644 --- a/ipld/car/v2/internal/io/offset_read_seeker.go +++ b/ipld/car/v2/internal/io/offset_read_seeker.go @@ -1,63 +1,123 @@ package io -import "io" +import ( + "errors" + "io" +) var ( - _ io.ReaderAt = (*OffsetReadSeeker)(nil) - _ io.ReadSeeker = (*OffsetReadSeeker)(nil) + _ io.ReaderAt = (*offsetReadSeeker)(nil) + _ io.ReadSeeker = (*offsetReadSeeker)(nil) ) -// OffsetReadSeeker implements Read, and ReadAt on a section +// offsetReadSeeker implements Read, and ReadAt on a section // of an underlying io.ReaderAt. -// The main difference between io.SectionReader and OffsetReadSeeker is that +// The main difference between io.SectionReader and offsetReadSeeker is that // NewOffsetReadSeeker does not require the user to know the number of readable bytes. // // It also partially implements Seek, where the implementation panics if io.SeekEnd is passed. -// This is because, OffsetReadSeeker does not know the end of the file therefore cannot seek relative +// This is because, offsetReadSeeker does not know the end of the file therefore cannot seek relative // to it. -type OffsetReadSeeker struct { +type offsetReadSeeker struct { r io.ReaderAt base int64 off int64 + b [1]byte // avoid alloc in ReadByte +} + +type ReadSeekerAt interface { + io.Reader + io.ReaderAt + io.Seeker + io.ByteReader } -// NewOffsetReadSeeker returns an OffsetReadSeeker that reads from r +// NewOffsetReadSeeker returns an ReadSeekerAt that reads from r // starting offset offset off and stops with io.EOF when r reaches its end. // The Seek function will panic if whence io.SeekEnd is passed. -func NewOffsetReadSeeker(r io.ReaderAt, off int64) *OffsetReadSeeker { - return &OffsetReadSeeker{r, off, off} +func NewOffsetReadSeeker(r io.ReaderAt, off int64) ReadSeekerAt { + nr, err := NewOffsetReadSeekerWithError(r, off) + if err != nil { + return erroringReader{err} + } + return nr +} + +func NewOffsetReadSeekerWithError(r io.ReaderAt, off int64) (ReadSeekerAt, error) { + if or, ok := r.(*offsetReadSeeker); ok { + oldBase := or.base + newBase := or.base + off + if newBase < oldBase { + return nil, errors.New("NewOffsetReadSeeker overflow int64") + } + return &offsetReadSeeker{ + r: or.r, + base: newBase, + off: newBase, + }, nil + } + return &offsetReadSeeker{ + r: r, + base: off, + off: off, + }, nil } -func (o *OffsetReadSeeker) Read(p []byte) (n int, err error) { +func (o *offsetReadSeeker) Read(p []byte) (n int, err error) { n, err = o.r.ReadAt(p, o.off) - o.off += int64(n) + oldOffset := o.off + off := oldOffset + int64(n) + if off < oldOffset { + return 0, errors.New("ReadAt offset overflow") + } + o.off = off return } -func (o *OffsetReadSeeker) ReadAt(p []byte, off int64) (n int, err error) { +func (o *offsetReadSeeker) ReadAt(p []byte, off int64) (n int, err error) { if off < 0 { return 0, io.EOF } + oldOffset := off off += o.base + if off < oldOffset { + return 0, errors.New("ReadAt offset overflow") + } return o.r.ReadAt(p, off) } -func (o *OffsetReadSeeker) ReadByte() (byte, error) { - b := []byte{0} - _, err := o.Read(b) - return b[0], err +func (o *offsetReadSeeker) ReadByte() (byte, error) { + _, err := o.Read(o.b[:]) + return o.b[0], err } -func (o *OffsetReadSeeker) Offset() int64 { +func (o *offsetReadSeeker) Offset() int64 { return o.off } -func (o *OffsetReadSeeker) Seek(offset int64, whence int) (int64, error) { +func (o *offsetReadSeeker) Seek(offset int64, whence int) (int64, error) { switch whence { case io.SeekStart: - o.off = offset + o.base + oldOffset := offset + off := offset + o.base + if off < oldOffset { + return 0, errors.New("Seek offset overflow") + } + o.off = off case io.SeekCurrent: - o.off += offset + oldOffset := o.off + if offset < 0 { + if -offset > oldOffset { + return 0, errors.New("Seek offset underflow") + } + o.off = oldOffset + offset + } else { + off := oldOffset + offset + if off < oldOffset { + return 0, errors.New("Seek offset overflow") + } + o.off = off + } case io.SeekEnd: panic("unsupported whence: SeekEnd") } @@ -65,6 +125,26 @@ func (o *OffsetReadSeeker) Seek(offset int64, whence int) (int64, error) { } // Position returns the current position of this reader relative to the initial offset. -func (o *OffsetReadSeeker) Position() int64 { +func (o *offsetReadSeeker) Position() int64 { return o.off - o.base } + +type erroringReader struct { + err error +} + +func (e erroringReader) Read(_ []byte) (int, error) { + return 0, e.err +} + +func (e erroringReader) ReadAt(_ []byte, n int64) (int, error) { + return 0, e.err +} + +func (e erroringReader) ReadByte() (byte, error) { + return 0, e.err +} + +func (e erroringReader) Seek(_ int64, _ int) (int64, error) { + return 0, e.err +} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 9394a736bf..6651b2907b 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -109,11 +109,11 @@ func (r *Reader) DataReader() SectionReader { // IndexReader provides an io.Reader containing the index for the data payload if the index is // present. Otherwise, returns nil. // Note, this function will always return nil if the backing payload represents a CARv1. -func (r *Reader) IndexReader() io.Reader { +func (r *Reader) IndexReader() io.ReaderAt { if r.Version == 1 || !r.Header.HasIndex() { return nil } - return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) + return io.NewSectionReader(r.r, int64(r.Header.IndexOffset), int64(r.Header.DataSize)-int64(r.Header.IndexOffset)) } // Close closes the underlying reader if it was opened by OpenReader. diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index a0c6e3cdaf..f653b60b2c 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -7,6 +7,7 @@ import ( carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" "github.com/stretchr/testify/require" ) @@ -173,7 +174,7 @@ func TestReader_WithCarV2Consistency(t *testing.T) { require.NoError(t, err) wantIndex, err := carv2.GenerateIndex(subject.DataReader()) require.NoError(t, err) - require.Equal(t, wantIndex, gotIndex) + testutil.AssertIndenticalIndexes(t, wantIndex, gotIndex) }) } } @@ -186,8 +187,8 @@ func TestOpenReader_DoesNotPanicForReadersCreatedBeforeClosure(t *testing.T) { require.NoError(t, subject.Close()) buf := make([]byte, 1) - panicTest := func(r io.Reader) { - _, err := r.Read(buf) + panicTest := func(r io.ReaderAt) { + _, err := r.ReadAt(buf, 0) require.EqualError(t, err, "mmap: closed") } @@ -203,8 +204,8 @@ func TestOpenReader_DoesNotPanicForReadersCreatedAfterClosure(t *testing.T) { iReaderAfterClosure := subject.IndexReader() buf := make([]byte, 1) - panicTest := func(r io.Reader) { - _, err := r.Read(buf) + panicTest := func(r io.ReaderAt) { + _, err := r.ReadAt(buf, 0) require.EqualError(t, err, "mmap: closed") } diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 11b5ff1296..2044e17baf 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" "github.com/stretchr/testify/require" @@ -56,7 +57,7 @@ func TestWrapV1(t *testing.T) { require.NoError(t, err) gotIdx, err := index.ReadFrom(subject.IndexReader()) require.NoError(t, err) - require.Equal(t, wantIdx, gotIdx) + testutil.AssertIndenticalIndexes(t, wantIdx, gotIdx) } func TestExtractV1(t *testing.T) { From 6431951d859a67854b0d410a0e6e22183ab52a99 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 21 Jun 2022 17:30:11 +1000 Subject: [PATCH 217/291] fix index comparisons This commit was moved from ipld/go-car@bad4539d0c65c708ec1cecac74cff65cb561ce3c --- ipld/car/v2/blockstore/readwrite_test.go | 3 ++- ipld/car/v2/index/example_test.go | 24 ++++++++++++++++++----- ipld/car/v2/index/index_test.go | 3 ++- ipld/car/v2/index/testutil/equal_index.go | 8 +++++--- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index bc78083abb..0ccd53e09a 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -21,6 +21,7 @@ import ( carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" @@ -519,7 +520,7 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, err) wantIdx, err := carv2.GenerateIndex(v2r.DataReader()) require.NoError(t, err) - require.Equal(t, wantIdx, gotIdx) + testutil.AssertIndenticalIndexes(t, wantIdx, gotIdx) } func TestBlockstoreResumptionIsSupportedOnFinalizedFile(t *testing.T) { diff --git a/ipld/car/v2/index/example_test.go b/ipld/car/v2/index/example_test.go index 3e484afb93..c6f83ea286 100644 --- a/ipld/car/v2/index/example_test.go +++ b/ipld/car/v2/index/example_test.go @@ -5,10 +5,10 @@ import ( "io" "io/ioutil" "os" - "reflect" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" + "github.com/multiformats/go-multihash" ) // ExampleReadFrom unmarshalls an index from an indexed CARv2 file, and for each root CID prints the @@ -94,13 +94,27 @@ func ExampleWriteTo() { panic(err) } - // Expect indices to be equal. - if reflect.DeepEqual(idx, reReadIdx) { - fmt.Printf("Saved index file matches the index embedded in CARv2 at %v.\n", src) - } else { + // Expect indices to be equal - collect all of the multihashes and their + // offsets from the first and compare to the second + mha := make(map[string]uint64, 0) + _ = idx.ForEach(func(mh multihash.Multihash, off uint64) error { + mha[mh.HexString()] = off + return nil + }) + var count int + _ = reReadIdx.ForEach(func(mh multihash.Multihash, off uint64) error { + count++ + if expectedOffset, ok := mha[mh.HexString()]; !ok || expectedOffset != off { + panic("expected to get the same index as the CARv2 file") + } + return nil + }) + if count != len(mha) { panic("expected to get the same index as the CARv2 file") } + fmt.Printf("Saved index file matches the index embedded in CARv2 at %v.\n", src) + // Output: // Saved index file matches the index embedded in CARv2 at ../testdata/sample-wrapped-v2.car. } diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index 267beb0344..883bd5d60b 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -8,6 +8,7 @@ import ( "testing" blocks "github.com/ipfs/go-block-format" + "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" "github.com/multiformats/go-multicodec" @@ -112,7 +113,7 @@ func TestWriteTo(t *testing.T) { require.NoError(t, err) // Assert they are equal - require.Equal(t, wantIdx, gotIdx) + testutil.AssertIndenticalIndexes(t, wantIdx, gotIdx) } func TestMarshalledIndexStartsWithCodec(t *testing.T) { diff --git a/ipld/car/v2/index/testutil/equal_index.go b/ipld/car/v2/index/testutil/equal_index.go index 1314d25e8f..798985b948 100644 --- a/ipld/car/v2/index/testutil/equal_index.go +++ b/ipld/car/v2/index/testutil/equal_index.go @@ -4,12 +4,14 @@ import ( "sync" "testing" - "github.com/ipld/go-car/v2/index" - "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" ) +type Index interface { + ForEach(func(multihash.Multihash, uint64) error) error +} + // insertUint64 perform one round of insertion sort on the last element func insertUint64(s []uint64) { switch len(s) { @@ -28,7 +30,7 @@ func insertUint64(s []uint64) { } } -func AssertIndenticalIndexes(t *testing.T, a, b index.Index) { +func AssertIndenticalIndexes(t *testing.T, a, b Index) { var wg sync.Mutex wg.Lock() // key is multihash.Multihash.HexString From 58e8f1a2c2f65d93766fbda72cc24bcdddd2916d Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 21 Jun 2022 19:24:31 +1000 Subject: [PATCH 218/291] fix: revert to internalio.NewOffsetReadSeeker in Reader#IndexReader This commit was moved from ipld/go-car@a74d510a3200aac5ed8c25ed9a1b8232c89f625c --- ipld/car/v2/reader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 6651b2907b..fa94e6724d 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -113,7 +113,7 @@ func (r *Reader) IndexReader() io.ReaderAt { if r.Version == 1 || !r.Header.HasIndex() { return nil } - return io.NewSectionReader(r.r, int64(r.Header.IndexOffset), int64(r.Header.DataSize)-int64(r.Header.IndexOffset)) + return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) } // Close closes the underlying reader if it was opened by OpenReader. From 48d19ae14a69e272d87a34a211648861a4908a64 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 21 Jun 2022 19:29:52 +1000 Subject: [PATCH 219/291] fix: staticcheck catches This commit was moved from ipld/go-car@e64294da9c86bc64665a152fd73e0b68293b00f8 --- ipld/car/v2/index/mhindexsorted_test.go | 5 ++--- ipld/car/v2/index/testutil/equal_index.go | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ipld/car/v2/index/mhindexsorted_test.go b/ipld/car/v2/index/mhindexsorted_test.go index d97d1e6f14..7704d3a23a 100644 --- a/ipld/car/v2/index/mhindexsorted_test.go +++ b/ipld/car/v2/index/mhindexsorted_test.go @@ -58,16 +58,15 @@ func TestMultiWidthCodedIndex_StableIterate(t *testing.T) { err = subject.Load(records) require.NoError(t, err) - iterable := subject.(index.Index) mh := make([]multihash.Multihash, 0, len(records)) - require.NoError(t, iterable.ForEach(func(m multihash.Multihash, _ uint64) error { + require.NoError(t, subject.ForEach(func(m multihash.Multihash, _ uint64) error { mh = append(mh, m) return nil })) for i := 0; i < 10; i++ { candidate := make([]multihash.Multihash, 0, len(records)) - require.NoError(t, iterable.ForEach(func(m multihash.Multihash, _ uint64) error { + require.NoError(t, subject.ForEach(func(m multihash.Multihash, _ uint64) error { candidate = append(candidate, m) return nil })) diff --git a/ipld/car/v2/index/testutil/equal_index.go b/ipld/car/v2/index/testutil/equal_index.go index 798985b948..c5da756a52 100644 --- a/ipld/car/v2/index/testutil/equal_index.go +++ b/ipld/car/v2/index/testutil/equal_index.go @@ -41,7 +41,7 @@ func AssertIndenticalIndexes(t *testing.T, a, b Index) { a.ForEach(func(mh multihash.Multihash, off uint64) error { aCount++ str := mh.HexString() - slice, _ := aMap[str] + slice := aMap[str] slice = append(slice, off) insertUint64(slice) aMap[str] = slice @@ -54,7 +54,7 @@ func AssertIndenticalIndexes(t *testing.T, a, b Index) { a.ForEach(func(mh multihash.Multihash, off uint64) error { bCount++ str := mh.HexString() - slice, _ := bMap[str] + slice := bMap[str] slice = append(slice, off) insertUint64(slice) bMap[str] = slice From 6481ccfa60fa4f5505a0e51169c13bd659160387 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 21 Jun 2022 19:34:06 +1000 Subject: [PATCH 220/291] fix: don't use multiple-go-modules for fuzzing This commit was moved from ipld/go-car@5b911130533375f5d0874db6ed37bd874385927a --- ipld/car/.github/workflows/go-fuzz.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/ipld/car/.github/workflows/go-fuzz.yml b/ipld/car/.github/workflows/go-fuzz.yml index 4b51f69c19..3aa7f85305 100644 --- a/ipld/car/.github/workflows/go-fuzz.yml +++ b/ipld/car/.github/workflows/go-fuzz.yml @@ -21,7 +21,6 @@ jobs: go version go env - name: Run Fuzzing for 1m - uses: protocol/multiple-go-modules@v1.2 with: run: go test -v -fuzz=Fuzz${{ matrix.target }} -fuzztime=1m . v2: @@ -43,7 +42,6 @@ jobs: go version go env - name: Run Fuzzing for 1m - uses: protocol/multiple-go-modules@v1.2 with: run: | cd v2 From 674eb25762b078d8b3be931d267e5278f85f73b1 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 21 Jun 2022 14:28:43 +1000 Subject: [PATCH 221/291] fix: use CidFromReader() which has overread and OOM protection This commit was moved from ipld/go-car@79c91e22e8ade045b8e99b0af53f110e9f0df971 --- ipld/car/util/util.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipld/car/util/util.go b/ipld/car/util/util.go index fbd6a86bcf..59aa5e2835 100644 --- a/ipld/car/util/util.go +++ b/ipld/car/util/util.go @@ -26,7 +26,7 @@ type BytesReader interface { io.ByteReader } -// TODO: this belongs in the go-cid package +// Deprecated: ReadCid shouldn't be used directly, use CidFromReader from go-cid func ReadCid(buf []byte) (cid.Cid, int, error) { if len(buf) >= 2 && bytes.Equal(buf[:2], cidv0Pref) { i := 34 @@ -70,7 +70,7 @@ func ReadNode(br *bufio.Reader) (cid.Cid, []byte, error) { return cid.Cid{}, nil, err } - c, n, err := ReadCid(data) + n, c, err := cid.CidFromReader(bytes.NewReader(data)) if err != nil { return cid.Cid{}, nil, err } From 2eead95fa71c23cf49a9ba4b290855550fddd0a5 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 21 Jun 2022 14:45:58 +1000 Subject: [PATCH 222/291] feat: MaxAllowedSectionSize default to 32M This commit was moved from ipld/go-car@1f15d9d38efead479b1a494ecd5651df3eaeb64d --- ipld/car/util/util.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ipld/car/util/util.go b/ipld/car/util/util.go index 59aa5e2835..af2f38e420 100644 --- a/ipld/car/util/util.go +++ b/ipld/car/util/util.go @@ -12,12 +12,11 @@ import ( mh "github.com/multiformats/go-multihash" ) -// MaxAllowedHeaderSize hint about how big the header red are allowed to be. -// This value is a hint to avoid OOMs, a parser that cannot OOM because it is -// streaming for example, isn't forced to follow that value. -// Deprecated: You should use v2#NewReader instead since it allows for options -// to be passed in. -var MaxAllowedHeaderSize uint = 1024 +// MaxAllowedSectionSize dictates the maximum number of bytes that a CARv1 header +// or section is allowed to occupy without causing a decode to error. +// This cannot be supplied as an option, only adjusted as a global. You should +// use v2#NewReader instead since it allows for options to be passed in. +var MaxAllowedSectionSize uint = 32 << 20 // 32MiB var cidv0Pref = []byte{0x12, 0x20} @@ -124,8 +123,8 @@ func LdRead(r *bufio.Reader) ([]byte, error) { return nil, err } - if l > uint64(MaxAllowedHeaderSize) { // Don't OOM - return nil, errors.New("malformed car; header is bigger than util.MaxAllowedHeaderSize") + if l > uint64(MaxAllowedSectionSize) { // Don't OOM + return nil, errors.New("malformed car; header is bigger than util.MaxAllowedSectionSize") } buf := make([]byte, l) From aaed1d13f399c8063438183e186fbeed37ed762f Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 21 Jun 2022 16:56:14 +1000 Subject: [PATCH 223/291] feat: MaxAllowed{Header,Section}Size option This commit was moved from ipld/go-car@d91369e78463b55dc2c7a20f0dfe6d03adc48937 --- ipld/car/v2/block_reader.go | 10 +++-- ipld/car/v2/block_reader_test.go | 55 ++++++++++++++++++++++++ ipld/car/v2/blockstore/readonly.go | 12 +++--- ipld/car/v2/blockstore/readwrite.go | 6 +-- ipld/car/v2/blockstore/readwrite_test.go | 2 +- ipld/car/v2/car_test.go | 2 +- ipld/car/v2/index/index_test.go | 2 +- ipld/car/v2/index_gen.go | 12 +++--- ipld/car/v2/index_gen_test.go | 2 +- ipld/car/v2/internal/carv1/car.go | 34 +++++++++------ ipld/car/v2/internal/carv1/util/util.go | 14 +++--- ipld/car/v2/options.go | 48 ++++++++++++++++++++- ipld/car/v2/options_test.go | 12 ++++-- ipld/car/v2/reader.go | 9 ++-- ipld/car/v2/writer.go | 12 +++--- 15 files changed, 176 insertions(+), 56 deletions(-) diff --git a/ipld/car/v2/block_reader.go b/ipld/car/v2/block_reader.go index 456d8d3177..a74e3996a5 100644 --- a/ipld/car/v2/block_reader.go +++ b/ipld/car/v2/block_reader.go @@ -30,9 +30,11 @@ type BlockReader struct { // // See BlockReader.Next func NewBlockReader(r io.Reader, opts ...Option) (*BlockReader, error) { + options := ApplyOptions(opts...) + // Read CARv1 header or CARv2 pragma. // Both are a valid CARv1 header, therefore are read as such. - pragmaOrV1Header, err := carv1.ReadHeader(r) + pragmaOrV1Header, err := carv1.ReadHeader(r, options.MaxAllowedHeaderSize) if err != nil { return nil, err } @@ -40,7 +42,7 @@ func NewBlockReader(r io.Reader, opts ...Option) (*BlockReader, error) { // Populate the block reader version and options. br := &BlockReader{ Version: pragmaOrV1Header.Version, - opts: ApplyOptions(opts...), + opts: options, } // Expect either version 1 or 2. @@ -92,7 +94,7 @@ func NewBlockReader(r io.Reader, opts ...Option) (*BlockReader, error) { br.r = io.LimitReader(r, dataSize) // Populate br.Roots by reading the inner CARv1 data payload header. - header, err := carv1.ReadHeader(br.r) + header, err := carv1.ReadHeader(br.r, options.MaxAllowedHeaderSize) if err != nil { return nil, err } @@ -120,7 +122,7 @@ func NewBlockReader(r io.Reader, opts ...Option) (*BlockReader, error) { // immediately upon encountering a zero-length section without reading any further bytes from the // underlying io.Reader. func (br *BlockReader) Next() (blocks.Block, error) { - c, data, err := util.ReadNode(br.r, br.opts.ZeroLengthSectionAsEOF) + c, data, err := util.ReadNode(br.r, br.opts.ZeroLengthSectionAsEOF, br.opts.MaxAllowedSectionSize) if err != nil { return nil, err } diff --git a/ipld/car/v2/block_reader_test.go b/ipld/car/v2/block_reader_test.go index afffc806cd..384a6ed88c 100644 --- a/ipld/car/v2/block_reader_test.go +++ b/ipld/car/v2/block_reader_test.go @@ -1,12 +1,17 @@ package car_test import ( + "bytes" + "encoding/hex" "io" "os" "testing" + "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/internal/carv1" + mh "github.com/multiformats/go-multihash" + "github.com/multiformats/go-varint" "github.com/stretchr/testify/require" ) @@ -104,6 +109,56 @@ func TestBlockReader_WithCarV1Consistency(t *testing.T) { } } +func TestMaxSectionLength(t *testing.T) { + // headerHex is the zero-roots CARv1 header + const headerHex = "11a265726f6f7473806776657273696f6e01" + headerBytes, _ := hex.DecodeString(headerHex) + // 8 MiB block of zeros + block := make([]byte, 8<<20) + // CID for that block + pfx := cid.NewPrefixV1(cid.Raw, mh.SHA2_256) + cid, err := pfx.Sum(block) + require.NoError(t, err) + + // construct CAR + var buf bytes.Buffer + buf.Write(headerBytes) + buf.Write(varint.ToUvarint(uint64(len(cid.Bytes()) + len(block)))) + buf.Write(cid.Bytes()) + buf.Write(block) + + // try to read it + car, err := carv2.NewBlockReader(bytes.NewReader(buf.Bytes())) + require.NoError(t, err) + // error should occur on first section read + _, err = car.Next() + require.EqualError(t, err, "invalid section data, length of read beyond allowable maximum") + + // successful read by expanding the max section size + car, err = carv2.NewBlockReader(bytes.NewReader(buf.Bytes()), carv2.MaxAllowedSectionSize((8<<20)+40)) + require.NoError(t, err) + // can now read block and get our 8 MiB zeroed byte array + readBlock, err := car.Next() + require.NoError(t, err) + require.True(t, bytes.Equal(block, readBlock.RawData())) +} + +func TestMaxHeaderLength(t *testing.T) { + // headerHex is the is a 5 root CARv1 header + const headerHex = "de01a265726f6f747385d82a58250001711220785197229dc8bb1152945da58e2348f7e279eeded06cc2ca736d0e879858b501d82a58250001711220785197229dc8bb1152945da58e2348f7e279eeded06cc2ca736d0e879858b501d82a58250001711220785197229dc8bb1152945da58e2348f7e279eeded06cc2ca736d0e879858b501d82a58250001711220785197229dc8bb1152945da58e2348f7e279eeded06cc2ca736d0e879858b501d82a58250001711220785197229dc8bb1152945da58e2348f7e279eeded06cc2ca736d0e879858b5016776657273696f6e01" + headerBytes, _ := hex.DecodeString(headerHex) + c, _ := cid.Decode("bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae") + + // successful read + car, err := carv2.NewBlockReader(bytes.NewReader(headerBytes)) + require.NoError(t, err) + require.ElementsMatch(t, []cid.Cid{c, c, c, c, c}, car.Roots) + + // unsuccessful read, low allowable max header length (length - 3 because there are 2 bytes in the length varint prefix) + _, err = carv2.NewBlockReader(bytes.NewReader(headerBytes), carv2.MaxAllowedHeaderSize(uint64(len(headerBytes)-3))) + require.EqualError(t, err, "invalid header data, length of read beyond allowable maximum") +} + func requireReaderFromPath(t *testing.T, path string) io.Reader { f, err := os.Open(path) require.NoError(t, err) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 141088b713..c7c9127bd2 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -98,7 +98,7 @@ func NewReadOnly(backing io.ReaderAt, idx index.Index, opts ...carv2.Option) (*R opts: carv2.ApplyOptions(opts...), } - version, err := readVersion(backing) + version, err := readVersion(backing, opts...) if err != nil { return nil, err } @@ -135,7 +135,7 @@ func NewReadOnly(backing io.ReaderAt, idx index.Index, opts ...carv2.Option) (*R } } -func readVersion(at io.ReaderAt) (uint64, error) { +func readVersion(at io.ReaderAt, opts ...carv2.Option) (uint64, error) { var rr io.Reader switch r := at.(type) { case io.Reader: @@ -143,7 +143,7 @@ func readVersion(at io.ReaderAt) (uint64, error) { default: rr = internalio.NewOffsetReadSeeker(r, 0) } - return carv2.ReadVersion(rr) + return carv2.ReadVersion(rr, opts...) } func generateIndex(at io.ReaderAt, opts ...carv2.Option) (index.Index, error) { @@ -186,7 +186,7 @@ func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { if err != nil { return cid.Cid{}, nil, err } - return util.ReadNode(r, b.opts.ZeroLengthSectionAsEOF) + return util.ReadNode(r, b.opts.ZeroLengthSectionAsEOF, b.opts.MaxAllowedSectionSize) } // DeleteBlock is unsupported and always errors. @@ -401,7 +401,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { // TODO we may use this walk for populating the index, and we need to be able to iterate keys in this way somewhere for index generation. In general though, when it's asked for all keys from a blockstore with an index, we should iterate through the index when possible rather than linear reads through the full car. rdr := internalio.NewOffsetReadSeeker(b.backing, 0) - header, err := carv1.ReadHeader(rdr) + header, err := carv1.ReadHeader(rdr, b.opts.MaxAllowedHeaderSize) if err != nil { b.mu.RUnlock() // don't hold the mutex forever return nil, fmt.Errorf("error reading car header: %w", err) @@ -491,7 +491,7 @@ func (b *ReadOnly) HashOnRead(bool) { // Roots returns the root CIDs of the backing CAR. func (b *ReadOnly) Roots() ([]cid.Cid, error) { - header, err := carv1.ReadHeader(internalio.NewOffsetReadSeeker(b.backing, 0)) + header, err := carv1.ReadHeader(internalio.NewOffsetReadSeeker(b.backing, 0), b.opts.MaxAllowedHeaderSize) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index de43999f76..0fad9893bf 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -169,11 +169,11 @@ func (b *ReadWrite) initWithRoots(v2 bool, roots []cid.Cid) error { return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, b.dataWriter) } -func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid) error { +func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid, opts ...carv2.Option) error { // On resumption it is expected that the CARv2 Pragma, and the CARv1 header is successfully written. // Otherwise we cannot resume from the file. // Read pragma to assert if b.f is indeed a CARv2. - version, err := carv2.ReadVersion(b.f) + version, err := carv2.ReadVersion(b.f, opts...) if err != nil { // The file is not a valid CAR file and cannot resume from it. // Or the write must have failed before pragma was written. @@ -224,7 +224,7 @@ func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid) error { // Use the given CARv1 padding to instantiate the CARv1 reader on file. v1r := internalio.NewOffsetReadSeeker(b.ronly.backing, 0) - header, err := carv1.ReadHeader(v1r) + header, err := carv1.ReadHeader(v1r, b.opts.MaxAllowedHeaderSize) if err != nil { // Cannot read the CARv1 header; the file is most likely corrupt. return fmt.Errorf("error reading car header: %w", err) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 0ccd53e09a..3dc528665d 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -836,7 +836,7 @@ func TestOpenReadWrite_WritesIdentityCIDsWhenOptionIsEnabled(t *testing.T) { require.NoError(t, err) // Determine expected offset as the length of header plus one - header, err := carv1.ReadHeader(r.DataReader()) + header, err := carv1.ReadHeader(r.DataReader(), carv1.DefaultMaxAllowedHeaderSize) require.NoError(t, err) object, err := cbor.DumpObject(header) require.NoError(t, err) diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index 64519b2976..9e113259e3 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -37,7 +37,7 @@ func TestCarV2PragmaLength(t *testing.T) { } func TestCarV2PragmaIsValidCarV1Header(t *testing.T) { - v1h, err := carv1.ReadHeader(bytes.NewReader(carv2.Pragma)) + v1h, err := carv1.ReadHeader(bytes.NewReader(carv2.Pragma), carv1.DefaultMaxAllowedHeaderSize) assert.NoError(t, err, "cannot decode pragma as CBOR with CARv1 header structure") assert.Equal(t, &carv1.CarHeader{ Roots: nil, diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index 883bd5d60b..f895bc2ffb 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -78,7 +78,7 @@ func TestReadFrom(t *testing.T) { require.NoError(t, err) // Read the fame at offset and assert the frame corresponds to the expected block. - gotCid, gotData, err := util.ReadNode(crf, false) + gotCid, gotData, err := util.ReadNode(crf, false, carv1.DefaultMaxAllowedSectionSize) require.NoError(t, err) gotBlock, err := blocks.NewBlockWithCid(gotData, gotCid) require.NoError(t, err) diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 10c86dc341..33ba7800fd 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -37,8 +37,11 @@ func GenerateIndex(v1r io.Reader, opts ...Option) (index.Index, error) { // Note, the index is re-generated every time even if r is in CARv2 format and already has an index. // To read existing index when available see ReadOrGenerateIndex. func LoadIndex(idx index.Index, r io.Reader, opts ...Option) error { + // Parse Options. + o := ApplyOptions(opts...) + reader := internalio.ToByteReadSeeker(r) - pragma, err := carv1.ReadHeader(r) + pragma, err := carv1.ReadHeader(r, o.MaxAllowedHeaderSize) if err != nil { return fmt.Errorf("error reading car header: %w", err) } @@ -78,7 +81,7 @@ func LoadIndex(idx index.Index, r io.Reader, opts ...Option) error { dataOffset = int64(v2h.DataOffset) // Read the inner CARv1 header to skip it and sanity check it. - v1h, err := carv1.ReadHeader(reader) + v1h, err := carv1.ReadHeader(reader, o.MaxAllowedHeaderSize) if err != nil { return err } @@ -104,9 +107,6 @@ func LoadIndex(idx index.Index, r io.Reader, opts ...Option) error { // CARv2 header. sectionOffset -= dataOffset - // Parse Options. - o := ApplyOptions(opts...) - records := make([]index.Record, 0) for { // Read the section's length. @@ -188,7 +188,7 @@ func GenerateIndexFromFile(path string, opts ...Option) (index.Index, error) { // given reader to fulfill index lookup. func ReadOrGenerateIndex(rs io.ReadSeeker, opts ...Option) (index.Index, error) { // Read version. - version, err := ReadVersion(rs) + version, err := ReadVersion(rs, opts...) if err != nil { return nil, err } diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 64a73adcf0..cae76ddf85 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -256,7 +256,7 @@ func generateMultihashSortedIndex(t *testing.T, path string) *index.MultihashInd require.NoError(t, err) t.Cleanup(func() { require.NoError(t, f.Close()) }) reader := internalio.ToByteReadSeeker(f) - header, err := carv1.ReadHeader(reader) + header, err := carv1.ReadHeader(reader, carv1.DefaultMaxAllowedHeaderSize) require.NoError(t, err) require.Equal(t, uint64(1), header.Version) diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index 48b7c86be9..f62899b714 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -14,6 +14,9 @@ import ( "github.com/ipfs/go-merkledag" ) +const DefaultMaxAllowedHeaderSize uint64 = 32 << 20 // 32MiB +const DefaultMaxAllowedSectionSize uint64 = 8 << 20 // 8MiB + func init() { cbor.RegisterCborType(CarHeader{}) } @@ -56,9 +59,12 @@ func WriteCar(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.W return nil } -func ReadHeader(r io.Reader) (*CarHeader, error) { - hb, err := util.LdRead(r, false) +func ReadHeader(r io.Reader, maxReadBytes uint64) (*CarHeader, error) { + hb, err := util.LdRead(r, false, maxReadBytes) if err != nil { + if err == util.ErrSectionTooLarge { + err = util.ErrHeaderTooLarge + } return nil, err } @@ -106,21 +112,22 @@ func (cw *carWriter) writeNode(ctx context.Context, nd format.Node) error { } type CarReader struct { - r io.Reader - Header *CarHeader - zeroLenAsEOF bool + r io.Reader + Header *CarHeader + zeroLenAsEOF bool + maxAllowedSectionSize uint64 } func NewCarReaderWithZeroLengthSectionAsEOF(r io.Reader) (*CarReader, error) { - return newCarReader(r, true) + return NewCarReaderWithoutDefaults(r, true, DefaultMaxAllowedHeaderSize, DefaultMaxAllowedSectionSize) } func NewCarReader(r io.Reader) (*CarReader, error) { - return newCarReader(r, false) + return NewCarReaderWithoutDefaults(r, false, DefaultMaxAllowedHeaderSize, DefaultMaxAllowedSectionSize) } -func newCarReader(r io.Reader, zeroLenAsEOF bool) (*CarReader, error) { - ch, err := ReadHeader(r) +func NewCarReaderWithoutDefaults(r io.Reader, zeroLenAsEOF bool, maxAllowedHeaderSize uint64, maxAllowedSectionSize uint64) (*CarReader, error) { + ch, err := ReadHeader(r, maxAllowedHeaderSize) if err != nil { return nil, err } @@ -134,14 +141,15 @@ func newCarReader(r io.Reader, zeroLenAsEOF bool) (*CarReader, error) { } return &CarReader{ - r: r, - Header: ch, - zeroLenAsEOF: zeroLenAsEOF, + r: r, + Header: ch, + zeroLenAsEOF: zeroLenAsEOF, + maxAllowedSectionSize: maxAllowedSectionSize, }, nil } func (cr *CarReader) Next() (blocks.Block, error) { - c, data, err := util.ReadNode(cr.r, cr.zeroLenAsEOF) + c, data, err := util.ReadNode(cr.r, cr.zeroLenAsEOF, cr.maxAllowedSectionSize) if err != nil { return nil, err } diff --git a/ipld/car/v2/internal/carv1/util/util.go b/ipld/car/v2/internal/carv1/util/util.go index dd543ac50a..7963812e43 100644 --- a/ipld/car/v2/internal/carv1/util/util.go +++ b/ipld/car/v2/internal/carv1/util/util.go @@ -11,13 +11,16 @@ import ( cid "github.com/ipfs/go-cid" ) +var ErrSectionTooLarge = errors.New("invalid section data, length of read beyond allowable maximum") +var ErrHeaderTooLarge = errors.New("invalid header data, length of read beyond allowable maximum") + type BytesReader interface { io.Reader io.ByteReader } -func ReadNode(r io.Reader, zeroLenAsEOF bool) (cid.Cid, []byte, error) { - data, err := LdRead(r, zeroLenAsEOF) +func ReadNode(r io.Reader, zeroLenAsEOF bool, maxReadBytes uint64) (cid.Cid, []byte, error) { + data, err := LdRead(r, zeroLenAsEOF, maxReadBytes) if err != nil { return cid.Cid{}, nil, err } @@ -62,7 +65,7 @@ func LdSize(d ...[]byte) uint64 { return sum + uint64(s) } -func LdRead(r io.Reader, zeroLenAsEOF bool) ([]byte, error) { +func LdRead(r io.Reader, zeroLenAsEOF bool, maxReadBytes uint64) ([]byte, error) { l, err := varint.ReadUvarint(internalio.ToByteReader(r)) if err != nil { // If the length of bytes read is non-zero when the error is EOF then signal an unclean EOF. @@ -74,9 +77,8 @@ func LdRead(r io.Reader, zeroLenAsEOF bool) ([]byte, error) { return nil, io.EOF } - const maxAllowedHeaderSize = 1024 * 1024 - if l > maxAllowedHeaderSize { // Don't OOM - return nil, errors.New("invalid input, too big header") + if l > maxReadBytes { // Don't OOM + return nil, ErrSectionTooLarge } buf := make([]byte, l) diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index d2923b2144..d2e526c424 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -6,11 +6,29 @@ import ( "github.com/ipld/go-car/v2/index" "github.com/ipld/go-ipld-prime/traversal" "github.com/multiformats/go-multicodec" + + "github.com/ipld/go-car/v2/internal/carv1" ) // DefaultMaxIndexCidSize specifies the maximum size in byptes accepted as a section CID by CARv2 index. const DefaultMaxIndexCidSize = 2 << 10 // 2 KiB +// DefaultMaxAllowedHeaderSize specifies the default maximum size that a CARv1 +// decode (including within a CARv2 container) will allow a header to be without +// erroring. This is to prevent OOM errors where a header prefix includes a +// too-large size specifier. +// Currently set to 32 MiB. +const DefaultMaxAllowedHeaderSize = carv1.DefaultMaxAllowedHeaderSize + +// DefaultMaxAllowedHeaderSize specifies the default maximum size that a CARv1 +// decode (including within a CARv2 container) will allow a section to be +// without erroring. This is to prevent OOM errors where a section prefix +// includes a too-large size specifier. +// Typically IPLD blocks should be under 2 MiB (ideally under 1 MiB), so unless +// atypical data is expected, this should not be a large value. +// Currently set to 8 MiB. +const DefaultMaxAllowedSectionSize = carv1.DefaultMaxAllowedSectionSize + // Option describes an option which affects behavior when interacting with CAR files. type Option func(*Options) @@ -42,14 +60,20 @@ type Options struct { MaxTraversalLinks uint64 WriteAsCarV1 bool TraversalPrototypeChooser traversal.LinkTargetNodePrototypeChooser + + MaxAllowedHeaderSize uint64 + MaxAllowedSectionSize uint64 } // ApplyOptions applies given opts and returns the resulting Options. // This function should not be used directly by end users; it's only exposed as a // side effect of Option. func ApplyOptions(opt ...Option) Options { - var opts Options - opts.MaxTraversalLinks = math.MaxInt64 //default: traverse all + opts := Options{ + MaxTraversalLinks: math.MaxInt64, //default: traverse all + MaxAllowedHeaderSize: carv1.DefaultMaxAllowedHeaderSize, + MaxAllowedSectionSize: carv1.DefaultMaxAllowedSectionSize, + } for _, o := range opt { o(&opts) } @@ -128,3 +152,23 @@ func WithTraversalPrototypeChooser(t traversal.LinkTargetNodePrototypeChooser) O o.TraversalPrototypeChooser = t } } + +// MaxAllowedHeaderSize overrides the default maximum size (of 32 MiB) that a +// CARv1 decode (including within a CARv2 container) will allow a header to be +// without erroring. +func MaxAllowedHeaderSize(max uint64) Option { + return func(o *Options) { + o.MaxAllowedHeaderSize = max + } +} + +// MaxAllowedSectionSize overrides the default maximum size (of 8 MiB) that a +// CARv1 decode (including within a CARv2 container) will allow a header to be +// without erroring. +// Typically IPLD blocks should be under 2 MiB (ideally under 1 MiB), so unless +// atypical data is expected, this should not be a large value. +func MaxAllowedSectionSize(max uint64) Option { + return func(o *Options) { + o.MaxAllowedSectionSize = max + } +} diff --git a/ipld/car/v2/options_test.go b/ipld/car/v2/options_test.go index 7e060acf09..6daa473fef 100644 --- a/ipld/car/v2/options_test.go +++ b/ipld/car/v2/options_test.go @@ -12,9 +12,11 @@ import ( func TestApplyOptions_SetsExpectedDefaults(t *testing.T) { require.Equal(t, carv2.Options{ - IndexCodec: multicodec.CarMultihashIndexSorted, - MaxIndexCidSize: carv2.DefaultMaxIndexCidSize, - MaxTraversalLinks: math.MaxInt64, + IndexCodec: multicodec.CarMultihashIndexSorted, + MaxIndexCidSize: carv2.DefaultMaxIndexCidSize, + MaxTraversalLinks: math.MaxInt64, + MaxAllowedHeaderSize: 32 << 20, + MaxAllowedSectionSize: 8 << 20, }, carv2.ApplyOptions()) } @@ -30,6 +32,8 @@ func TestApplyOptions_AppliesOptions(t *testing.T) { BlockstoreAllowDuplicatePuts: true, BlockstoreUseWholeCIDs: true, MaxTraversalLinks: math.MaxInt64, + MaxAllowedHeaderSize: 101, + MaxAllowedSectionSize: 202, }, carv2.ApplyOptions( carv2.UseDataPadding(123), @@ -38,6 +42,8 @@ func TestApplyOptions_AppliesOptions(t *testing.T) { carv2.ZeroLengthSectionAsEOF(true), carv2.MaxIndexCidSize(789), carv2.StoreIdentityCIDs(true), + carv2.MaxAllowedHeaderSize(101), + carv2.MaxAllowedSectionSize(202), blockstore.AllowDuplicatePuts(true), blockstore.UseWholeCIDs(true), )) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index fa94e6724d..40c5d8c8d7 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -51,7 +51,7 @@ func NewReader(r io.ReaderAt, opts ...Option) (*Reader, error) { or := internalio.NewOffsetReadSeeker(r, 0) var err error - cr.Version, err = ReadVersion(or) + cr.Version, err = ReadVersion(or, opts...) if err != nil { return nil, err } @@ -75,7 +75,7 @@ func (r *Reader) Roots() ([]cid.Cid, error) { if r.roots != nil { return r.roots, nil } - header, err := carv1.ReadHeader(r.DataReader()) + header, err := carv1.ReadHeader(r.DataReader(), r.opts.MaxAllowedHeaderSize) if err != nil { return nil, err } @@ -126,8 +126,9 @@ func (r *Reader) Close() error { // ReadVersion reads the version from the pragma. // This function accepts both CARv1 and CARv2 payloads. -func ReadVersion(r io.Reader) (uint64, error) { - header, err := carv1.ReadHeader(r) +func ReadVersion(r io.Reader, opts ...Option) (uint64, error) { + o := ApplyOptions(opts...) + header, err := carv1.ReadHeader(r, o.MaxAllowedHeaderSize) if err != nil { return 0, err } diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index e82c4b3b38..f859dabe86 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -105,7 +105,7 @@ func WrapV1(src io.ReadSeeker, dst io.Writer, opts ...Option) error { // for example, it should use copy_file_range on recent Linux versions. // This API should be preferred over copying directly via Reader.DataReader, // as it should allow for better performance while always being at least as efficient. -func ExtractV1File(srcPath, dstPath string) (err error) { +func ExtractV1File(srcPath, dstPath string, opts ...Option) (err error) { src, err := os.Open(srcPath) if err != nil { return err @@ -115,7 +115,7 @@ func ExtractV1File(srcPath, dstPath string) (err error) { defer src.Close() // Detect CAR version. - version, err := ReadVersion(src) + version, err := ReadVersion(src, opts...) if err != nil { return err } @@ -220,7 +220,7 @@ func AttachIndex(path string, idx index.Index, offset uint64) error { // // Note that the roots are only replaced if their total serialized size exactly matches the total // serialized size of existing roots in CAR file. -func ReplaceRootsInFile(path string, roots []cid.Cid) (err error) { +func ReplaceRootsInFile(path string, roots []cid.Cid, opts ...Option) (err error) { f, err := os.OpenFile(path, os.O_RDWR, 0o666) if err != nil { return err @@ -232,8 +232,10 @@ func ReplaceRootsInFile(path string, roots []cid.Cid) (err error) { } }() + options := ApplyOptions(opts...) + // Read header or pragma; note that both are a valid CARv1 header. - header, err := carv1.ReadHeader(f) + header, err := carv1.ReadHeader(f, options.MaxAllowedHeaderSize) if err != nil { return err } @@ -267,7 +269,7 @@ func ReplaceRootsInFile(path string, roots []cid.Cid) (err error) { return err } var innerV1Header *carv1.CarHeader - innerV1Header, err = carv1.ReadHeader(f) + innerV1Header, err = carv1.ReadHeader(f, options.MaxAllowedHeaderSize) if err != nil { return err } From e69b00156536338dc281b4ce7354b42fb1763c31 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 22 Jun 2022 16:01:51 +1000 Subject: [PATCH 224/291] fix: explicitly disable serialization of insertionindex This commit was moved from ipld/go-car@82be1c37758ca012d0d43e141241790225c69474 --- ipld/car/v2/blockstore/insertionindex.go | 44 ++++-------------------- ipld/car/v2/blockstore/readonly.go | 1 + ipld/car/v2/index/index.go | 1 + 3 files changed, 9 insertions(+), 37 deletions(-) diff --git a/ipld/car/v2/blockstore/insertionindex.go b/ipld/car/v2/blockstore/insertionindex.go index 95971aa211..192eb5c34a 100644 --- a/ipld/car/v2/blockstore/insertionindex.go +++ b/ipld/car/v2/blockstore/insertionindex.go @@ -2,20 +2,21 @@ package blockstore import ( "bytes" - "encoding/binary" "errors" "fmt" "io" "github.com/ipfs/go-cid" "github.com/ipld/go-car/v2/index" - internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" "github.com/petar/GoLLRB/llrb" - cbor "github.com/whyrusleeping/cbor/go" ) +// This index is intended to be efficient for random-access, in-memory lookups +// and is not intended to be an index type that is attached to a CARv2. +// See flatten() for conversion of this data to a known, existing index type. + var ( errUnsupported = errors.New("not supported") insertionIndexCodec = multicodec.Code(0x300003) @@ -105,21 +106,7 @@ func (ii *insertionIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { } func (ii *insertionIndex) Marshal(w io.Writer) (uint64, error) { - l := uint64(0) - if err := binary.Write(w, binary.LittleEndian, int64(ii.items.Len())); err != nil { - return l, err - } - l += 8 - - var err error - iter := func(i llrb.Item) bool { - if err = cbor.Encode(w, i.(recordDigest).Record); err != nil { - return false - } - return true - } - ii.items.AscendGreaterOrEqual(ii.items.Min(), iter) - return l, err + return 0, fmt.Errorf("unimplemented, index type not intended for serialization") } func (ii *insertionIndex) ForEach(f func(multihash.Multihash, uint64) error) error { @@ -137,28 +124,11 @@ func (ii *insertionIndex) ForEach(f func(multihash.Multihash, uint64) error) err } func (ii *insertionIndex) Unmarshal(r io.Reader) error { - var length int64 - if err := binary.Read(r, binary.LittleEndian, &length); err != nil { - return err - } - d := cbor.NewDecoder(r) - for i := int64(0); i < length; i++ { - var rec index.Record - if err := d.Decode(&rec); err != nil { - return err - } - ii.items.InsertNoReplace(newRecordDigest(rec)) - } - return nil + return fmt.Errorf("unimplemented, index type not intended for deserialization") } func (ii *insertionIndex) UnmarshalLazyRead(r io.ReaderAt) (int64, error) { - rdr := internalio.NewOffsetReadSeeker(r, 0) - err := ii.Unmarshal(rdr) - if err != nil { - return 0, err - } - return rdr.Seek(0, io.SeekCurrent) + return 0, fmt.Errorf("unimplemented, index type not intended for deserialization") } func (ii *insertionIndex) Codec() multicodec.Code { diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index c7c9127bd2..307486d79d 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -47,6 +47,7 @@ type ReadOnly struct { // The backing containing the data payload in CARv1 format. backing io.ReaderAt + // The CARv1 content index. idx index.Index diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 204bc12213..eeff16add8 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -44,6 +44,7 @@ type ( // Marshal encodes the index in serial form. Marshal(w io.Writer) (uint64, error) + // Unmarshal decodes the index from its serial form. // Deprecated: This function is slurpy and will copy everything into memory. Unmarshal(r io.Reader) error From 108f8e15502c1758d5c0847fce4867459ea54dbc Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 23 Jun 2022 21:04:12 +1000 Subject: [PATCH 225/291] fix: tighter constraint of singleWidthIndex width, add index recommentation docs This commit was moved from ipld/go-car@73b8fe324796ad0316fb79621fb416663aec07ea --- ipld/car/v2/index/index.go | 11 ++++++++--- ipld/car/v2/index/indexsorted.go | 5 +++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index eeff16add8..ca2c2490b2 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -49,9 +49,12 @@ type ( // Deprecated: This function is slurpy and will copy everything into memory. Unmarshal(r io.Reader) error - // UnmarshalLazyRead is the safe alternative to to Unmarshal. - // Instead of slurping it will keep a reference to the the io.ReaderAt passed in - // and ask for data as needed. + // UnmarshalLazyRead lazily decodes the index from its serial form. It is a + // safer alternative to to Unmarshal, particularly when reading index data + // from untrusted sources (which is not recommended) but also in more + // constrained memory environments. + // Instead of slurping UnmarshalLazyRead will keep a reference to the the + // io.ReaderAt passed in and ask for data as needed. UnmarshalLazyRead(r io.ReaderAt) (indexSize int64, err error) // Load inserts a number of records into the index. @@ -137,6 +140,7 @@ func WriteTo(idx Index, w io.Writer) (uint64, error) { // ReadFrom reads index from r. // The reader decodes the index by reading the first byte to interpret the encoding. // Returns error if the encoding is not known. +// Attempting to read index data from untrusted sources is not recommended. func ReadFrom(r io.ReaderAt) (Index, error) { idx, _, err := ReadFromWithSize(r) return idx, err @@ -144,6 +148,7 @@ func ReadFrom(r io.ReaderAt) (Index, error) { // ReadFromWithSize is just like ReadFrom but return the size of the Index. // The size is only valid when err != nil. +// Attempting to read index data from untrusted sources is not recommended. func ReadFromWithSize(r io.ReaderAt) (Index, int64, error) { code, err := varint.ReadUvarint(internalio.NewOffsetReadSeeker(r, 0)) if err != nil { diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 16367ed526..b9f9a654e3 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -113,8 +113,9 @@ func (s *singleWidthIndex) checkUnmarshalLengths(width uint32, dataLen, extra ui if width <= 8 { return errors.New("malformed index; width must be bigger than 8") } - if int32(width) < 0 { - return errors.New("index too big; singleWidthIndex width is overflowing int32") + const maxWidth = 32 << 20 // 32MiB, to ~match the go-cid maximum + if width > maxWidth { + return errors.New("index too big; singleWidthIndex width is larger than allowed maximum") } oldDataLen, dataLen := dataLen, dataLen+extra if oldDataLen > dataLen { From 1cca428c92b287dd605161c24289bfe9d9a295ff Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 30 Jun 2022 11:35:57 +0100 Subject: [PATCH 226/291] Fix testutil assertion logic and update index generation tests Update index generation tests to assert indices are identical. Fix minor typo in the test utility name and a bug where the check was not using both index instances to assert they are identical. Also refactor the use of lock in favour of wait group for better readability of the assertion logic. This commit was moved from ipld/go-car@906d56920df534e8b1cdf754377e78db14208fe7 --- ipld/car/v2/blockstore/readwrite_test.go | 2 +- ipld/car/v2/index/index_test.go | 2 +- ipld/car/v2/index/testutil/equal_index.go | 18 +++-- ipld/car/v2/index_gen_test.go | 89 +++++++++-------------- ipld/car/v2/reader_test.go | 2 +- ipld/car/v2/writer_test.go | 2 +- 6 files changed, 49 insertions(+), 66 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 3dc528665d..6f64ac72f4 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -520,7 +520,7 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, err) wantIdx, err := carv2.GenerateIndex(v2r.DataReader()) require.NoError(t, err) - testutil.AssertIndenticalIndexes(t, wantIdx, gotIdx) + testutil.AssertIdenticalIndexes(t, wantIdx, gotIdx) } func TestBlockstoreResumptionIsSupportedOnFinalizedFile(t *testing.T) { diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index f895bc2ffb..92774d7c60 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -113,7 +113,7 @@ func TestWriteTo(t *testing.T) { require.NoError(t, err) // Assert they are equal - testutil.AssertIndenticalIndexes(t, wantIdx, gotIdx) + testutil.AssertIdenticalIndexes(t, wantIdx, gotIdx) } func TestMarshalledIndexStartsWithCodec(t *testing.T) { diff --git a/ipld/car/v2/index/testutil/equal_index.go b/ipld/car/v2/index/testutil/equal_index.go index c5da756a52..43d0b3e91a 100644 --- a/ipld/car/v2/index/testutil/equal_index.go +++ b/ipld/car/v2/index/testutil/equal_index.go @@ -30,15 +30,17 @@ func insertUint64(s []uint64) { } } -func AssertIndenticalIndexes(t *testing.T, a, b Index) { - var wg sync.Mutex - wg.Lock() +func AssertIdenticalIndexes(t *testing.T, a, b Index) { + var wg sync.WaitGroup // key is multihash.Multihash.HexString var aCount uint + var aErr error aMap := make(map[string][]uint64) + wg.Add(1) + go func() { - defer wg.Unlock() - a.ForEach(func(mh multihash.Multihash, off uint64) error { + defer wg.Done() + aErr = a.ForEach(func(mh multihash.Multihash, off uint64) error { aCount++ str := mh.HexString() slice := aMap[str] @@ -51,7 +53,7 @@ func AssertIndenticalIndexes(t *testing.T, a, b Index) { var bCount uint bMap := make(map[string][]uint64) - a.ForEach(func(mh multihash.Multihash, off uint64) error { + bErr := b.ForEach(func(mh multihash.Multihash, off uint64) error { bCount++ str := mh.HexString() slice := bMap[str] @@ -60,7 +62,9 @@ func AssertIndenticalIndexes(t *testing.T, a, b Index) { bMap[str] = slice return nil }) - wg.Lock() + wg.Wait() + require.NoError(t, aErr) + require.NoError(t, bErr) require.Equal(t, aCount, bCount) require.Equal(t, aMap, bMap) diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index cae76ddf85..43a9c2acab 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -1,6 +1,7 @@ package car_test import ( + "github.com/stretchr/testify/assert" "io" "os" "testing" @@ -14,25 +15,25 @@ import ( "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" "github.com/multiformats/go-varint" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGenerateIndex(t *testing.T) { - tests := []struct { + type testCase struct { name string carPath string opts []carv2.Option wantIndexer func(t *testing.T) index.Index wantErr bool - }{ + } + tests := []testCase{ { name: "CarV1IsIndexedAsExpected", carPath: "testdata/sample-v1.car", wantIndexer: func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1.car") require.NoError(t, err) - defer v1.Close() + t.Cleanup(func() { assert.NoError(t, v1.Close()) }) want, err := carv2.GenerateIndex(v1) require.NoError(t, err) return want @@ -44,7 +45,7 @@ func TestGenerateIndex(t *testing.T) { wantIndexer: func(t *testing.T) index.Index { v2, err := os.Open("testdata/sample-wrapped-v2.car") require.NoError(t, err) - defer v2.Close() + t.Cleanup(func() { assert.NoError(t, v2.Close()) }) reader, err := carv2.NewReader(v2) require.NoError(t, err) want, err := index.ReadFrom(reader.IndexReader()) @@ -59,7 +60,7 @@ func TestGenerateIndex(t *testing.T) { wantIndexer: func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1-with-zero-len-section.car") require.NoError(t, err) - defer v1.Close() + t.Cleanup(func() { assert.NoError(t, v1.Close()) }) want, err := carv2.GenerateIndex(v1, carv2.ZeroLengthSectionAsEOF(true)) require.NoError(t, err) return want @@ -72,7 +73,7 @@ func TestGenerateIndex(t *testing.T) { wantIndexer: func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1-with-zero-len-section2.car") require.NoError(t, err) - defer v1.Close() + t.Cleanup(func() { assert.NoError(t, v1.Close()) }) want, err := carv2.GenerateIndex(v1, carv2.ZeroLengthSectionAsEOF(true)) require.NoError(t, err) return want @@ -90,71 +91,49 @@ func TestGenerateIndex(t *testing.T) { wantErr: true, }, } + + requireWant := func(tt testCase, got index.Index, gotErr error) { + if tt.wantErr { + require.Error(t, gotErr) + } else { + require.NoError(t, gotErr) + var want index.Index + if tt.wantIndexer != nil { + want = tt.wantIndexer(t) + } + if want == nil { + require.Nil(t, got) + } else { + testutil.AssertIdenticalIndexes(t, want, got) + } + } + } + for _, tt := range tests { t.Run("ReadOrGenerateIndex_"+tt.name, func(t *testing.T) { carFile, err := os.Open(tt.carPath) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, carFile.Close()) }) - got, err := carv2.ReadOrGenerateIndex(carFile, tt.opts...) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - var want index.Index - if tt.wantIndexer != nil { - want = tt.wantIndexer(t) - } - if want == nil { - require.Nil(t, got) - } else { - testutil.AssertIndenticalIndexes(t, want, got) - } - } + got, gotErr := carv2.ReadOrGenerateIndex(carFile, tt.opts...) + requireWant(tt, got, gotErr) }) t.Run("GenerateIndexFromFile_"+tt.name, func(t *testing.T) { - got, err := carv2.GenerateIndexFromFile(tt.carPath, tt.opts...) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - var want index.Index - if tt.wantIndexer != nil { - want = tt.wantIndexer(t) - } - require.Equal(t, want, got) - } + got, gotErr := carv2.GenerateIndexFromFile(tt.carPath, tt.opts...) + requireWant(tt, got, gotErr) }) t.Run("LoadIndex_"+tt.name, func(t *testing.T) { carFile, err := os.Open(tt.carPath) require.NoError(t, err) got, err := index.New(multicodec.CarMultihashIndexSorted) require.NoError(t, err) - err = carv2.LoadIndex(got, carFile, tt.opts...) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - var want index.Index - if tt.wantIndexer != nil { - want = tt.wantIndexer(t) - } - require.Equal(t, want, got) - } + gotErr := carv2.LoadIndex(got, carFile, tt.opts...) + requireWant(tt, got, gotErr) }) t.Run("GenerateIndex_"+tt.name, func(t *testing.T) { carFile, err := os.Open(tt.carPath) require.NoError(t, err) - got, err := carv2.GenerateIndex(carFile, tt.opts...) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - var want index.Index - if tt.wantIndexer != nil { - want = tt.wantIndexer(t) - } - require.Equal(t, want, got) - } + got, gotErr := carv2.GenerateIndex(carFile, tt.opts...) + requireWant(tt, got, gotErr) }) } } diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index f653b60b2c..c010445fb8 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -174,7 +174,7 @@ func TestReader_WithCarV2Consistency(t *testing.T) { require.NoError(t, err) wantIndex, err := carv2.GenerateIndex(subject.DataReader()) require.NoError(t, err) - testutil.AssertIndenticalIndexes(t, wantIndex, gotIndex) + testutil.AssertIdenticalIndexes(t, wantIndex, gotIndex) }) } } diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 2044e17baf..1bf3ca3d98 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -57,7 +57,7 @@ func TestWrapV1(t *testing.T) { require.NoError(t, err) gotIdx, err := index.ReadFrom(subject.IndexReader()) require.NoError(t, err) - testutil.AssertIndenticalIndexes(t, wantIdx, gotIdx) + testutil.AssertIdenticalIndexes(t, wantIdx, gotIdx) } func TestExtractV1(t *testing.T) { From e6180e7a3ef3972168c42a8070d71fb7afa86a42 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 30 Jun 2022 14:42:26 +0100 Subject: [PATCH 227/291] Use a fix code as the multihash code for `CarIndexSorted` Previous changes added the `ForEach` interface to `Index` type which enables iteration through the index by multihash and offset. However, not all index types contain enough information to construct the full multihash. Namely, `CarIndexSorted` only stores the digest portion of the multihashes. In order to implement `ForEach` for this index type correctly uses the `uint64` max value as the code in the multihash and document the behaviour where the iterations over this index type should not rely on the returned code. Note that the max value is used as a code that doesn't match any existing multicodec.Code to avoid misleading users. This commit was moved from ipld/go-car@3aa026c977b11ed604de39268bac76d70ad2b978 --- ipld/car/v2/index/index.go | 7 ++++--- ipld/car/v2/index/indexsorted.go | 9 ++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index ca2c2490b2..74b7fbcf4f 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -5,14 +5,12 @@ import ( "fmt" "io" + "github.com/ipfs/go-cid" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" - "github.com/multiformats/go-varint" - - "github.com/ipfs/go-cid" ) // CarIndexNone is a sentinal value used as a multicodec code for the index indicating no index. @@ -82,6 +80,9 @@ type ( // the multihash of the element, and the offset in the car file // where the element appears. // + // Note that index with codec multicodec.CarIndexSorted does not store the multihash code. + // The multihashes passed to ForEach on this index type should only rely on the digest part. + // // If the callback returns a non-nil error, the iteration is aborted, // and the ForEach function returns the error to the user. // diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index b9f9a654e3..1e7ed3f636 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "math" "sort" "github.com/ipld/go-car/v2/internal/errsort" @@ -348,7 +349,13 @@ func (m *multiWidthIndex) Load(items []Record) error { func (m *multiWidthIndex) ForEach(f func(multihash.Multihash, uint64) error) error { return m.forEachDigest(func(digest []byte, offset uint64) error { - mh, err := multihash.Cast(digest) + // multicodec.CarIndexSorted does not contain the multihash code; it only contains the digest. + // To implement ForEach on this index kind we encode the digest with multihash code math.MaxUint64. + // The CarIndexSorted documents this behaviour and the user should not take the multihash code + // as the actual code of the multihashes of CAR blocks. + // The rationale for using math.MaxUint64 is to avoid using a reserved multihash code that could + // become error-prone later, including 0x00 which is a valid multihash code for IDENTITY. + mh, err := multihash.Encode(digest, math.MaxUint64) if err != nil { return err } From 0b4355f83ef942d3409037b0fdd2ca7af21d7a55 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 30 Jun 2022 16:07:28 +0100 Subject: [PATCH 228/291] Remove support for `ForEach` enumeration from car-index-sorted This index type does not store enough information to satisfy `ForEach`. It only contains the digest of mulithashes and not their code. Instead of some partial functionality simply return an error when `ForEach` is called on this function type. Because, there is no valid use for this index type and the user should ber regenerating the index to the newer `car-multihash-index-sorted` anyway. Update tests to include samples of both types and assert IO operations and index generation for both formats. This commit was moved from ipld/go-car@5c7e07edac3835e83ee3452507a9ba3bc94f8d2e --- ipld/car/v2/index/index.go | 6 +- ipld/car/v2/index/index_test.go | 62 +++++++++++++----- ipld/car/v2/index/indexsorted.go | 17 +---- ipld/car/v2/index/indexsorted_test.go | 12 ++++ .../sample-multihash-index-sorted.carindex | Bin 0 -> 41750 bytes 5 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 ipld/car/v2/testdata/sample-multihash-index-sorted.carindex diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 74b7fbcf4f..3a2b3f1d96 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -80,8 +80,10 @@ type ( // the multihash of the element, and the offset in the car file // where the element appears. // - // Note that index with codec multicodec.CarIndexSorted does not store the multihash code. - // The multihashes passed to ForEach on this index type should only rely on the digest part. + // Note that index with codec multicodec.CarIndexSorted does not support ForEach enumeration. + // Because this index type only contains the multihash digest and not the code. + // Calling ForEach on this index type will result in error. + // Use multicodec.CarMultihashIndexSorted index type instead. // // If the callback returns a non-nil error, the iteration is aborted, // and the ForEach function returns the error to the user. diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index 92774d7c60..0d380cafe5 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -55,6 +55,13 @@ func TestReadFrom(t *testing.T) { subject, err := ReadFrom(idxf) require.NoError(t, err) + idxf2, err := os.Open("../testdata/sample-multihash-index-sorted.carindex") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, idxf2.Close()) }) + + subjectInAltFormat, err := ReadFrom(idxf) + require.NoError(t, err) + crf, err := os.Open("../testdata/sample-v1.car") require.NoError(t, err) t.Cleanup(func() { require.NoError(t, crf.Close()) }) @@ -68,11 +75,17 @@ func TestReadFrom(t *testing.T) { } require.NoError(t, err) + wantCid := wantBlock.Cid() // Get offset from the index for a CID and assert it exists - gotOffset, err := GetFirst(subject, wantBlock.Cid()) + gotOffset, err := GetFirst(subject, wantCid) require.NoError(t, err) require.NotZero(t, gotOffset) + // Get offset from the index in alternative format for a CID and assert it exists + gotOffset2, err := GetFirst(subjectInAltFormat, wantCid) + require.NoError(t, err) + require.NotZero(t, gotOffset2) + // Seek to the offset on CARv1 file _, err = crf.Seek(int64(gotOffset), io.SeekStart) require.NoError(t, err) @@ -88,7 +101,7 @@ func TestReadFrom(t *testing.T) { func TestWriteTo(t *testing.T) { // Read sample index on file - idxf, err := os.Open("../testdata/sample-index.carindex") + idxf, err := os.Open("../testdata/sample-multihash-index-sorted.carindex") require.NoError(t, err) t.Cleanup(func() { require.NoError(t, idxf.Close()) }) @@ -117,18 +130,37 @@ func TestWriteTo(t *testing.T) { } func TestMarshalledIndexStartsWithCodec(t *testing.T) { - // Read sample index on file - idxf, err := os.Open("../testdata/sample-index.carindex") - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, idxf.Close()) }) - - // Unmarshall to get expected index - wantIdx, err := ReadFrom(idxf) - require.NoError(t, err) - // Assert the first two bytes are the corresponding multicodec code. - buf := new(bytes.Buffer) - _, err = WriteTo(wantIdx, buf) - require.NoError(t, err) - require.Equal(t, varint.ToUvarint(uint64(multicodec.CarIndexSorted)), buf.Bytes()[:2]) + tests := []struct { + path string + codec multicodec.Code + }{ + { + path: "../testdata/sample-multihash-index-sorted.carindex", + codec: multicodec.CarMultihashIndexSorted, + }, + { + path: "../testdata/sample-index.carindex", + codec: multicodec.CarIndexSorted, + }, + } + for _, test := range tests { + test := test + t.Run(test.codec.String(), func(t *testing.T) { + // Read sample index on file + idxf, err := os.Open(test.path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, idxf.Close()) }) + + // Unmarshall to get expected index + wantIdx, err := ReadFrom(idxf) + require.NoError(t, err) + + // Assert the first two bytes are the corresponding multicodec code. + buf := new(bytes.Buffer) + _, err = WriteTo(wantIdx, buf) + require.NoError(t, err) + require.Equal(t, varint.ToUvarint(uint64(test.codec)), buf.Bytes()[:2]) + }) + } } diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 1e7ed3f636..60d0e87d34 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "math" "sort" "github.com/ipld/go-car/v2/internal/errsort" @@ -347,20 +346,8 @@ func (m *multiWidthIndex) Load(items []Record) error { return nil } -func (m *multiWidthIndex) ForEach(f func(multihash.Multihash, uint64) error) error { - return m.forEachDigest(func(digest []byte, offset uint64) error { - // multicodec.CarIndexSorted does not contain the multihash code; it only contains the digest. - // To implement ForEach on this index kind we encode the digest with multihash code math.MaxUint64. - // The CarIndexSorted documents this behaviour and the user should not take the multihash code - // as the actual code of the multihashes of CAR blocks. - // The rationale for using math.MaxUint64 is to avoid using a reserved multihash code that could - // become error-prone later, including 0x00 which is a valid multihash code for IDENTITY. - mh, err := multihash.Encode(digest, math.MaxUint64) - if err != nil { - return err - } - return f(mh, offset) - }) +func (m *multiWidthIndex) ForEach(func(multihash.Multihash, uint64) error) error { + return fmt.Errorf("%s does not support ForEach enumeration; use %s instead", multicodec.CarIndexSorted, multicodec.CarMultihashIndexSorted) } func (m *multiWidthIndex) forEachDigest(f func(digest []byte, offset uint64) error) error { diff --git a/ipld/car/v2/index/indexsorted_test.go b/ipld/car/v2/index/indexsorted_test.go index f7e038a01b..8e1a452729 100644 --- a/ipld/car/v2/index/indexsorted_test.go +++ b/ipld/car/v2/index/indexsorted_test.go @@ -7,9 +7,21 @@ import ( "github.com/ipfs/go-merkledag" "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" ) +func TestSortedIndex_ErrorsOnForEach(t *testing.T) { + subject, err := New(multicodec.CarIndexSorted) + require.NoError(t, err) + err = subject.ForEach(func(multihash.Multihash, uint64) error { return nil }) + require.Error(t, err) + require.Equal(t, + "car-index-sorted does not support ForEach enumeration; use car-multihash-index-sorted instead", + err.Error(), + ) +} + func TestSortedIndexCodec(t *testing.T) { require.Equal(t, multicodec.CarIndexSorted, newSorted().Codec()) } diff --git a/ipld/car/v2/testdata/sample-multihash-index-sorted.carindex b/ipld/car/v2/testdata/sample-multihash-index-sorted.carindex new file mode 100644 index 0000000000000000000000000000000000000000..ddf8a5fb27bedb35b876822e92f2c6bb9bac7cdb GIT binary patch literal 41750 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�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 fFXv6DTmOE2|GI+}-4KXKw!?RDarG Date: Thu, 30 Jun 2022 20:00:02 +1000 Subject: [PATCH 229/291] feat: add Reader#Inspect() function to check basic validity of a CAR and return stats This commit was moved from ipld/go-car@1de342e2019eb49ea54abcc07211ad77da1c6276 --- ipld/car/v2/block_reader.go | 18 +-- ipld/car/v2/car.go | 21 +++- ipld/car/v2/car_test.go | 9 +- ipld/car/v2/reader.go | 147 ++++++++++++++++++++++++ ipld/car/v2/reader_test.go | 222 ++++++++++++++++++++++++++++++++++++ 5 files changed, 393 insertions(+), 24 deletions(-) diff --git a/ipld/car/v2/block_reader.go b/ipld/car/v2/block_reader.go index a74e3996a5..55ebd1cf65 100644 --- a/ipld/car/v2/block_reader.go +++ b/ipld/car/v2/block_reader.go @@ -64,20 +64,6 @@ func NewBlockReader(r io.Reader, opts ...Option) (*BlockReader, error) { if _, err := v2h.ReadFrom(r); err != nil { return nil, err } - // Assert the data payload offset validity. - // It must be at least 51 ( + ). - dataOffset := int64(v2h.DataOffset) - if dataOffset < PragmaSize+HeaderSize { - return nil, fmt.Errorf("invalid data payload offset: %v", dataOffset) - } - // Assert the data size validity. - // It must be larger than zero. - // Technically, it should be at least 11 bytes (i.e. a valid CARv1 header with no roots) but - // we let further parsing of the header to signal invalid data payload header. - dataSize := int64(v2h.DataSize) - if dataSize <= 0 { - return nil, fmt.Errorf("invalid data payload size: %v", dataSize) - } // Skip to the beginning of inner CARv1 data payload. // Note, at this point the pragma and CARv1 header have been read. @@ -86,12 +72,12 @@ func NewBlockReader(r io.Reader, opts ...Option) (*BlockReader, error) { // fast forward to the beginning of data payload by subtracting pragma and header size from // dataOffset. rs := internalio.ToByteReadSeeker(r) - if _, err := rs.Seek(dataOffset-PragmaSize-HeaderSize, io.SeekCurrent); err != nil { + if _, err := rs.Seek(int64(v2h.DataOffset)-PragmaSize-HeaderSize, io.SeekCurrent); err != nil { return nil, err } // Set br.r to a LimitReader reading from r limited to dataSize. - br.r = io.LimitReader(r, dataSize) + br.r = io.LimitReader(r, int64(v2h.DataSize)) // Populate br.Roots by reading the inner CARv1 data payload header. header, err := carv1.ReadHeader(br.r, options.MaxAllowedHeaderSize) diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index 194731363f..571eb1140b 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -2,7 +2,7 @@ package car import ( "encoding/binary" - "errors" + "fmt" "io" ) @@ -170,10 +170,21 @@ func (h *Header) ReadFrom(r io.Reader) (int64, error) { dataOffset := binary.LittleEndian.Uint64(buf[:8]) dataSize := binary.LittleEndian.Uint64(buf[8:16]) indexOffset := binary.LittleEndian.Uint64(buf[16:]) - if int64(dataOffset) < 0 || - int64(dataSize) < 0 || - int64(indexOffset) < 0 { - return n, errors.New("malformed car, overflowing offsets") + // Assert the data payload offset validity. + // It must be at least 51 ( + ). + if int64(dataOffset) < PragmaSize+HeaderSize { + return n, fmt.Errorf("invalid data payload offset: %v", dataOffset) + } + // Assert the data size validity. + // It must be larger than zero. + // Technically, it should be at least 11 bytes (i.e. a valid CARv1 header with no roots) but + // we let further parsing of the header to signal invalid data payload header. + if int64(dataSize) <= 0 { + return n, fmt.Errorf("invalid data payload size: %v", dataSize) + } + // Assert the index offset validity. + if int64(indexOffset) < 0 { + return n, fmt.Errorf("invalid index offset: %v", indexOffset) } h.DataOffset = dataOffset h.DataSize = dataSize diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index 9e113259e3..d993a37468 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -56,11 +56,12 @@ func TestHeader_WriteTo(t *testing.T) { "HeaderWithEmptyCharacteristicsIsWrittenAsExpected", carv2.Header{ Characteristics: carv2.Characteristics{}, + DataOffset: 99, }, []byte{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x63, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, }, @@ -114,12 +115,14 @@ func TestHeader_ReadFrom(t *testing.T) { []byte{ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x63, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, }, carv2.Header{ Characteristics: carv2.Characteristics{}, + DataOffset: 99, + DataSize: 100, }, false, }, diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 40c5d8c8d7..0208284c32 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -3,10 +3,15 @@ package car import ( "fmt" "io" + "math" "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" + "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-varint" "golang.org/x/exp/mmap" ) @@ -116,6 +121,148 @@ func (r *Reader) IndexReader() io.ReaderAt { return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) } +// CarStats is returned by an Inspect() call +type CarStats struct { + Version uint64 + Header Header + Roots []cid.Cid + RootsPresent bool + BlockCount uint64 + CodecCounts map[multicodec.Code]uint64 + MhTypeCounts map[multicodec.Code]uint64 + AvgCidLength uint64 + MaxCidLength uint64 + MinCidLength uint64 + AvgBlockLength uint64 + MaxBlockLength uint64 + MinBlockLength uint64 + IndexCodec multicodec.Code + IndexSize uint64 +} + +// Inspect does a quick scan of a CAR, performing basic validation of the format +// and returning a CarStats object that provides a high-level description of the +// contents of the CAR. +// Inspect works for CARv1 and CARv2 contents. A CARv1 will return an +// uninitialized Header value. +// Inspect will perform a basic check of a CARv2 index, where present, but this +// does not guarantee that the index is correct. Attempting to read index data +// from untrusted sources is not recommended. If required, further validation of +// an index can be performed by loading the index and performing a ForEach() and +// sanity checking that the offsets are within the data payload section of the +// CAR. However, re-generation of index data in this case is the recommended +// course of action. +func (r *Reader) Inspect() (CarStats, error) { + stats := CarStats{ + Version: r.Version, + Header: r.Header, + CodecCounts: make(map[multicodec.Code]uint64), + MhTypeCounts: make(map[multicodec.Code]uint64), + } + + var totalCidLength uint64 + var totalBlockLength uint64 + var minCidLength uint64 = math.MaxUint64 + var minBlockLength uint64 = math.MaxUint64 + + dr := r.DataReader() + bdr := internalio.ToByteReader(dr) + + // read roots, not using Roots(), because we need the offset setup in the data trader + header, err := carv1.ReadHeader(dr, r.opts.MaxAllowedHeaderSize) + if err != nil { + return CarStats{}, err + } + stats.Roots = header.Roots + var rootsPresentCount int + rootsPresent := make([]bool, len(stats.Roots)) + + // read block sections + for { + sectionLength, err := varint.ReadUvarint(bdr) + if err != nil { + if err == io.EOF { + // if the length of bytes read is non-zero when the error is EOF then signal an unclean EOF. + if sectionLength > 0 { + return CarStats{}, io.ErrUnexpectedEOF + } + // otherwise, this is a normal ending + break + } + } else if sectionLength == 0 && r.opts.ZeroLengthSectionAsEOF { + // normal ending for this read mode + break + } + if sectionLength > r.opts.MaxAllowedSectionSize { + return CarStats{}, util.ErrSectionTooLarge + } + + // decode just the CID bytes + cidLen, c, err := cid.CidFromReader(dr) + if err != nil { + return CarStats{}, err + } + + // is this a root block? (also account for duplicate root CIDs) + if rootsPresentCount < len(stats.Roots) { + for i, r := range stats.Roots { + if !rootsPresent[i] && c == r { + rootsPresent[i] = true + rootsPresentCount++ + } + } + } + + cp := c.Prefix() + codec := multicodec.Code(cp.Codec) + count := stats.CodecCounts[codec] + stats.CodecCounts[codec] = count + 1 + mhtype := multicodec.Code(cp.MhType) + count = stats.MhTypeCounts[mhtype] + stats.MhTypeCounts[mhtype] = count + 1 + + blockLength := sectionLength - uint64(cidLen) + dr.Seek(int64(blockLength), io.SeekCurrent) + + stats.BlockCount++ + totalCidLength += uint64(cidLen) + totalBlockLength += blockLength + if uint64(cidLen) < minCidLength { + minCidLength = uint64(cidLen) + } + if uint64(cidLen) > stats.MaxCidLength { + stats.MaxCidLength = uint64(cidLen) + } + if uint64(blockLength) < minBlockLength { + minBlockLength = uint64(blockLength) + } + if uint64(blockLength) > stats.MaxBlockLength { + stats.MaxBlockLength = uint64(blockLength) + } + } + + stats.RootsPresent = len(stats.Roots) == rootsPresentCount + if stats.BlockCount > 0 { + stats.MinCidLength = minCidLength + stats.MinBlockLength = minBlockLength + stats.AvgCidLength = totalCidLength / stats.BlockCount + stats.AvgBlockLength = totalBlockLength / stats.BlockCount + } + + if stats.Version != 1 && stats.Header.HasIndex() { + // performs an UnmarshalLazyRead which should have its own validation and + // is intended to be a fast initial scan + ind, size, err := index.ReadFromWithSize(r.IndexReader()) + if err != nil { + return CarStats{}, err + } + stats.IndexCodec = ind.Codec() + stats.IndexSize = uint64(size) + } + + return stats, nil +} + // Close closes the underlying reader if it was opened by OpenReader. func (r *Reader) Close() error { if r.closer != nil { diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index c010445fb8..58b9a3286f 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -1,14 +1,19 @@ package car_test import ( + "bytes" + "encoding/hex" "io" "os" + "strings" "testing" + "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" + "github.com/multiformats/go-multicodec" "github.com/stretchr/testify/require" ) @@ -268,3 +273,220 @@ func requireNewCarV1Reader(t *testing.T, r io.Reader, zerLenAsEOF bool) *carv1.C require.NoError(t, err) return cr } + +func TestInspect(t *testing.T) { + tests := []struct { + name string + path string + zerLenAsEOF bool + expectedStats carv2.CarStats + }{ + { + name: "IndexlessCarV2", + path: "testdata/sample-v2-indexless.car", + expectedStats: carv2.CarStats{ + Version: 2, + Header: carv2.Header{ + Characteristics: carv2.Characteristics{0, 0}, + DataOffset: 51, + DataSize: 479907, + IndexOffset: 0, + }, + Roots: []cid.Cid{mustCidDecode("bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy")}, + RootsPresent: true, + AvgBlockLength: 417, // 417.6644423260248 + MinBlockLength: 1, + MaxBlockLength: 1342, + AvgCidLength: 37, // 37.86939942802669 + MinCidLength: 14, + MaxCidLength: 38, + BlockCount: 1049, + CodecCounts: map[multicodec.Code]uint64{ + multicodec.Raw: 6, + multicodec.DagCbor: 1043, + }, + MhTypeCounts: map[multicodec.Code]uint64{ + multicodec.Identity: 6, + multicodec.Blake2b256: 1043, + }, + }, + }, + { + // same payload as IndexlessCarV2, so only difference is the Version & Header + name: "CarV1", + path: "testdata/sample-v1.car", + expectedStats: carv2.CarStats{ + Version: 1, + Header: carv2.Header{}, + Roots: []cid.Cid{mustCidDecode("bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy")}, + RootsPresent: true, + AvgBlockLength: 417, // 417.6644423260248 + MinBlockLength: 1, + MaxBlockLength: 1342, + AvgCidLength: 37, // 37.86939942802669 + MinCidLength: 14, + MaxCidLength: 38, + BlockCount: 1049, + CodecCounts: map[multicodec.Code]uint64{ + multicodec.Raw: 6, + multicodec.DagCbor: 1043, + }, + MhTypeCounts: map[multicodec.Code]uint64{ + multicodec.Identity: 6, + multicodec.Blake2b256: 1043, + }, + }, + }, + { + // same payload as IndexlessCarV2, so only difference is the Header + name: "CarV2ProducedByBlockstore", + path: "testdata/sample-rw-bs-v2.car", + expectedStats: carv2.CarStats{ + Version: 2, + Header: carv2.Header{ + DataOffset: 1464, + DataSize: 273, + IndexOffset: 1737, + }, + Roots: []cid.Cid{ + mustCidDecode("bafkreifuosuzujyf4i6psbneqtwg2fhplc2wxptc5euspa2gn3bwhnihfu"), + mustCidDecode("bafkreifc4hca3inognou377hfhvu2xfchn2ltzi7yu27jkaeujqqqdbjju"), + mustCidDecode("bafkreig5lvr4l6b4fr3un4xvzeyt3scevgsqjgrhlnwxw2unwbn5ro276u"), + }, + RootsPresent: true, + BlockCount: 3, + CodecCounts: map[multicodec.Code]uint64{multicodec.Raw: 3}, + MhTypeCounts: map[multicodec.Code]uint64{multicodec.Sha2_256: 3}, + AvgCidLength: 36, + MaxCidLength: 36, + MinCidLength: 36, + AvgBlockLength: 6, + MaxBlockLength: 9, + MinBlockLength: 4, + IndexCodec: multicodec.CarMultihashIndexSorted, + IndexSize: 148, + }, + }, + // same as CarV1 but with a zero-byte EOF to test options + { + name: "CarV1VersionWithZeroLenSectionIsOne", + path: "testdata/sample-v1-with-zero-len-section.car", + zerLenAsEOF: true, + expectedStats: carv2.CarStats{ + Version: 1, + Header: carv2.Header{}, + Roots: []cid.Cid{mustCidDecode("bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy")}, + RootsPresent: true, + AvgBlockLength: 417, // 417.6644423260248 + MinBlockLength: 1, + MaxBlockLength: 1342, + AvgCidLength: 37, // 37.86939942802669 + MinCidLength: 14, + MaxCidLength: 38, + BlockCount: 1049, + CodecCounts: map[multicodec.Code]uint64{ + multicodec.Raw: 6, + multicodec.DagCbor: 1043, + }, + MhTypeCounts: map[multicodec.Code]uint64{ + multicodec.Identity: 6, + multicodec.Blake2b256: 1043, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader, err := carv2.OpenReader(tt.path, carv2.ZeroLengthSectionAsEOF(tt.zerLenAsEOF)) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, reader.Close()) }) + stats, err := reader.Inspect() + require.NoError(t, err) + require.Equal(t, tt.expectedStats, stats) + }) + } +} + +func TestInspectError(t *testing.T) { + tests := []struct { + name string + carHex string + expectedOpenError string + expectedInspectError string + }{ + { + name: "BadCidV0", + carHex: "3aa265726f6f747381d8305825000130302030303030303030303030303030303030303030303030303030303030303030306776657273696f6e010130", + expectedInspectError: "expected 1 as the cid version number, got: 48", + }, + { + name: "BadHeaderLength", + carHex: "e0e0e0e0a7060c6f6c4cca943c236f4b196723489608edb42a8b8fa80b6776657273696f6e19", + expectedOpenError: "invalid header data, length of read beyond allowable maximum", + }, + { + name: "BadSectionLength", + carHex: "11a265726f6f7473806776657273696f6e01e0e0e0e0a7060155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000", + expectedInspectError: "invalid section data, length of read beyond allowable maximum", + }, + // the bad index tests are manually constructed from this single-block CARv2 by adjusting the Uint32 and Uint64 values in the index: + // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset + // 0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 01000000 1200000000000000 01000000 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000 + { + name: "BadIndexCountOverflow", + // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset + carHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 ffffffff 1200000000000000 01000000 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", + expectedInspectError: "index too big; MultihashIndexSorted count is overflowing int32", + }, + { + name: "BadIndexCountTooMany", + // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset + carHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 ffffff7f 1200000000000000 01000000 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", + expectedInspectError: "unexpected EOF", + }, + { + name: "BadIndexMultiWidthOverflow", + // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset + carHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 01000000 1200000000000000 ffffffff 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", + expectedInspectError: "index too big; multiWidthIndex count is overflowing int32", + }, + { + name: "BadIndexMultiWidthTooMany", + // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset + carHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 01000000 1200000000000000 ffffff7f 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", + expectedInspectError: "unexpected EOF", + }, + // we don't test any further into the index, to do that, a user should do a ForEach across the loaded index (and sanity check the offsets) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + car, _ := hex.DecodeString(strings.ReplaceAll(tt.carHex, " ", "")) + reader, err := carv2.NewReader(bytes.NewReader(car)) + if tt.expectedOpenError != "" { + require.Error(t, err) + require.Equal(t, err.Error(), tt.expectedOpenError) + return + } else { + require.NoError(t, err) + } + t.Cleanup(func() { require.NoError(t, reader.Close()) }) + _, err = reader.Inspect() + if tt.expectedInspectError != "" { + require.Error(t, err) + require.Equal(t, err.Error(), tt.expectedInspectError) + } else { + require.NoError(t, err) + } + }) + } +} + +func mustCidDecode(s string) cid.Cid { + c, err := cid.Decode(s) + if err != nil { + panic(err) + } + return c +} From 2df9663a7a3b76084b15a898bc90ad0513e250fa Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 1 Jul 2022 13:17:03 +1000 Subject: [PATCH 230/291] feat: add block hash validation to Inspect() This commit was moved from ipld/go-car@466dbf1db7950d5658752ddfed2730cd81893177 --- ipld/car/v2/block_reader.go | 2 +- ipld/car/v2/reader.go | 67 ++++++++++++++++++++++++++++++++++-- ipld/car/v2/reader_test.go | 68 +++++++++++++++++++++++++++++++++---- 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/ipld/car/v2/block_reader.go b/ipld/car/v2/block_reader.go index 55ebd1cf65..252885c315 100644 --- a/ipld/car/v2/block_reader.go +++ b/ipld/car/v2/block_reader.go @@ -119,7 +119,7 @@ func (br *BlockReader) Next() (blocks.Block, error) { } if !hashed.Equals(c) { - return nil, fmt.Errorf("mismatch in content integrity, name: %s, data: %s", c, hashed) + return nil, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", c, hashed) } return blocks.NewBlockWithCid(data, c) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 0208284c32..c3ef36535d 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -145,6 +145,13 @@ type CarStats struct { // contents of the CAR. // Inspect works for CARv1 and CARv2 contents. A CARv1 will return an // uninitialized Header value. +// +// If validateBlockHash is true, all block data in the payload will be hashed +// and compared to the CID for that block and an error will return if there +// is a mismatch. If false, block data will be skipped over and not checked. +// Performing a full block hash validation is similar to using a BlockReader and +// calling Next over all blocks. +// // Inspect will perform a basic check of a CARv2 index, where present, but this // does not guarantee that the index is correct. Attempting to read index data // from untrusted sources is not recommended. If required, further validation of @@ -152,7 +159,34 @@ type CarStats struct { // sanity checking that the offsets are within the data payload section of the // CAR. However, re-generation of index data in this case is the recommended // course of action. -func (r *Reader) Inspect() (CarStats, error) { +// +// Beyond the checks performed by Inspect, a valid / good CAR is somewhat +// use-case dependent. Factors to consider include: +// +// * Bad indexes, including incorrect offsets, duplicate entries, or other +// faulty data. Indexes should be re-generated, regardless, if you need to use +// them and have any reason to not trust the source. +// +// * Blocks use codecs that your system doesn't have access to—which may mean +// you can't traverse a DAG or use the contained data. CarStats#CodecCounts +// contains a list of codecs found in the CAR so this can be checked. +// +// * CIDs use multihashes that your system doesn't have access to—which will +// mean you can't validate block hashes are correct (using validateBlockHash +// in this case will result in a failure). CarStats#MhTypeCounts contains a +// list of multihashes found in the CAR so this can bechecked. +// +// * The presence of IDENTITY CIDs, which may not be supported (or desired) by +// the consumer of the CAR. CarStats#CodecCounts can determine the presence +// of IDENTITY CIDs. +// +// * Roots: the number of roots, duplicates, and whether they are related to the +// blocks contained within the CAR. CarStats contains a list of Roots and a +// RootsPresent bool so further checks can be performed. +// +// * DAG completeness is not checked. Any properties relating to the DAG, or +// DAGs contained within a CAR are the responsibility of the user to check. +func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { stats := CarStats{ Version: r.Version, Header: r.Header, @@ -189,7 +223,8 @@ func (r *Reader) Inspect() (CarStats, error) { // otherwise, this is a normal ending break } - } else if sectionLength == 0 && r.opts.ZeroLengthSectionAsEOF { + } + if sectionLength == 0 && r.opts.ZeroLengthSectionAsEOF { // normal ending for this read mode break } @@ -203,6 +238,13 @@ func (r *Reader) Inspect() (CarStats, error) { return CarStats{}, err } + if sectionLength < uint64(cidLen) { + // this case is handled different in the normal ReadNode() path since it + // slurps in the whole section bytes and decodes CID from there - so an + // error should come from a failing io.ReadFull + return CarStats{}, fmt.Errorf("section length shorter than CID length") + } + // is this a root block? (also account for duplicate root CIDs) if rootsPresentCount < len(stats.Roots) { for i, r := range stats.Roots { @@ -222,7 +264,26 @@ func (r *Reader) Inspect() (CarStats, error) { stats.MhTypeCounts[mhtype] = count + 1 blockLength := sectionLength - uint64(cidLen) - dr.Seek(int64(blockLength), io.SeekCurrent) + + if validateBlockHash { + // read the block data, hash it and compare it + buf := make([]byte, blockLength) + if _, err := io.ReadFull(dr, buf); err != nil { + return CarStats{}, err + } + + hashed, err := cp.Sum(buf) + if err != nil { + return CarStats{}, err + } + + if !hashed.Equals(c) { + return CarStats{}, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", c, hashed) + } + } else { + // otherwise, skip over it + dr.Seek(int64(blockLength), io.SeekCurrent) + } stats.BlockCount++ totalCidLength += uint64(cidLen) diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index 58b9a3286f..85605dadcb 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -278,6 +278,7 @@ func TestInspect(t *testing.T) { tests := []struct { name string path string + carHex string zerLenAsEOF bool expectedStats carv2.CarStats }{ @@ -394,14 +395,43 @@ func TestInspect(t *testing.T) { }, }, }, + { + // A case where this _could_ be a valid CAR if we allowed identity CIDs + // and not matching block contents to exist, there's no block bytes in + // this. It will only fail if you don't validate the CID matches the, + // bytes (see TestInspectError for that case). + name: "IdentityCID", + // 47 {version:1,roots:[identity cid]} 25 identity cid (dag-json {"identity":"block"}) + carHex: "2f a265726f6f747381d82a581a0001a90200147b226964656e74697479223a22626c6f636b227d6776657273696f6e01 19 01a90200147b226964656e74697479223a22626c6f636b227d", + expectedStats: carv2.CarStats{ + Version: 1, + Roots: []cid.Cid{mustCidDecode("baguqeaaupmrgszdfnz2gs5dzei5ceytmn5rwwit5")}, + RootsPresent: true, + BlockCount: 1, + CodecCounts: map[multicodec.Code]uint64{multicodec.DagJson: 1}, + MhTypeCounts: map[multicodec.Code]uint64{multicodec.Identity: 1}, + AvgCidLength: 25, + MaxCidLength: 25, + MinCidLength: 25, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - reader, err := carv2.OpenReader(tt.path, carv2.ZeroLengthSectionAsEOF(tt.zerLenAsEOF)) - require.NoError(t, err) + var reader *carv2.Reader + var err error + if tt.path != "" { + reader, err = carv2.OpenReader(tt.path, carv2.ZeroLengthSectionAsEOF(tt.zerLenAsEOF)) + require.NoError(t, err) + } else { + byts, err := hex.DecodeString(strings.ReplaceAll(tt.carHex, " ", "")) + require.NoError(t, err) + reader, err = carv2.NewReader(bytes.NewReader(byts), carv2.ZeroLengthSectionAsEOF(tt.zerLenAsEOF)) + require.NoError(t, err) + } t.Cleanup(func() { require.NoError(t, reader.Close()) }) - stats, err := reader.Inspect() + stats, err := reader.Inspect(false) require.NoError(t, err) require.Equal(t, tt.expectedStats, stats) }) @@ -414,6 +444,7 @@ func TestInspectError(t *testing.T) { carHex string expectedOpenError string expectedInspectError string + validateBlockHash bool }{ { name: "BadCidV0", @@ -430,6 +461,31 @@ func TestInspectError(t *testing.T) { carHex: "11a265726f6f7473806776657273696f6e01e0e0e0e0a7060155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000", expectedInspectError: "invalid section data, length of read beyond allowable maximum", }, + { + name: "BadSectionLength2", + carHex: "3aa265726f6f747381d8305825000130302030303030303030303030303030303030303030303030303030303030303030306776657273696f6e01200130302030303030303030303030303030303030303030303030303030303030303030303030303030303030", + expectedInspectError: "section length shorter than CID length", + validateBlockHash: true, + }, + { + name: "BadBlockHash(SanityCheck)", // this should pass because we don't ask the CID be validated even though it doesn't match + // header cid data + carHex: "11a265726f6f7473806776657273696f6e 012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca ffffffffffffffffffff", + }, + { + name: "BadBlockHash", // same as above, but we ask for CID validation + // header cid data + carHex: "11a265726f6f7473806776657273696f6e 012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca ffffffffffffffffffff", + validateBlockHash: true, + expectedInspectError: "mismatch in content integrity, expected: bafkreiab2rek7wjiazkfrt3hbnqpljmu24226alszdlh6ivic2abgjubzi, got: bafkreiaaqoxrddiyuy6gxnks6ioqytxhq5a7tchm2mm5htigznwiljukmm", + }, + { + name: "IdentityCID", // a case where this _could_ be a valid CAR if we allowed identity CIDs and not matching block contents to exist, there's no block bytes in this + // 47 {version:1,roots:[identity cid]} 25 identity cid (dag-json {"identity":"block"}) + carHex: "2f a265726f6f747381d82a581a0001a90200147b226964656e74697479223a22626c6f636b227d6776657273696f6e01 19 01a90200147b226964656e74697479223a22626c6f636b227d", + validateBlockHash: true, + expectedInspectError: "mismatch in content integrity, expected: baguqeaaupmrgszdfnz2gs5dzei5ceytmn5rwwit5, got: baguqeaaa", + }, // the bad index tests are manually constructed from this single-block CARv2 by adjusting the Uint32 and Uint64 values in the index: // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset // 0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 01000000 1200000000000000 01000000 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000 @@ -466,16 +522,16 @@ func TestInspectError(t *testing.T) { reader, err := carv2.NewReader(bytes.NewReader(car)) if tt.expectedOpenError != "" { require.Error(t, err) - require.Equal(t, err.Error(), tt.expectedOpenError) + require.Equal(t, tt.expectedOpenError, err.Error()) return } else { require.NoError(t, err) } t.Cleanup(func() { require.NoError(t, reader.Close()) }) - _, err = reader.Inspect() + _, err = reader.Inspect(tt.validateBlockHash) if tt.expectedInspectError != "" { require.Error(t, err) - require.Equal(t, err.Error(), tt.expectedInspectError) + require.Equal(t, tt.expectedInspectError, err.Error()) } else { require.NoError(t, err) } From 94eb990a7ada0fb66174b1d332542e7b913e7426 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 1 Jul 2022 05:22:51 +0200 Subject: [PATCH 231/291] test: add fuzzing for reader#Inspect This commit was moved from ipld/go-car@952fcb9c29769315109336b24e5a8611466f0fae --- ipld/car/.github/workflows/go-fuzz.yml | 2 +- ipld/car/v2/fuzz_test.go | 55 ++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/ipld/car/.github/workflows/go-fuzz.yml b/ipld/car/.github/workflows/go-fuzz.yml index 3aa7f85305..548a4a91de 100644 --- a/ipld/car/.github/workflows/go-fuzz.yml +++ b/ipld/car/.github/workflows/go-fuzz.yml @@ -27,7 +27,7 @@ jobs: strategy: fail-fast: true matrix: - target: [ "BlockReader", "Reader", "Index" ] + target: [ "BlockReader", "Reader", "Index", "Inspect" ] runs-on: ubuntu-latest name: Fuzz V2 ${{ matrix.target }} steps: diff --git a/ipld/car/v2/fuzz_test.go b/ipld/car/v2/fuzz_test.go index 8187457b52..c747375053 100644 --- a/ipld/car/v2/fuzz_test.go +++ b/ipld/car/v2/fuzz_test.go @@ -13,6 +13,7 @@ import ( car "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1" ) // v1FixtureStr is a clean carv1 single-block, single-root CAR @@ -116,3 +117,57 @@ func FuzzIndex(f *testing.F) { index.ReadFrom(bytes.NewReader(data)) }) } + +func FuzzInspect(f *testing.F) { + seedWithCarFiles(f) + + f.Fuzz(func(t *testing.T, data []byte) { + reader, err := car.NewReader(bytes.NewReader(data)) + if err != nil { + return + } + + // Do differential fuzzing between Inspect and the normal parser + _, inspectErr := reader.Inspect(true) + if inspectErr == nil { + return + } + + reader, err = car.NewReader(bytes.NewReader(data)) + if err != nil { + t.Fatal("second NewReader on same data failed", err.Error()) + } + + if i := reader.IndexReader(); i != nil { + _, err = index.ReadFrom(i) + if err != nil { + return + } + } + + dr := reader.DataReader() + + _, err = carv1.ReadHeader(dr, carv1.DefaultMaxAllowedHeaderSize) + if err != nil { + return + } + + blocks, err := car.NewBlockReader(dr) + if err != nil { + return + } + + for { + _, err := blocks.Next() + if err != nil { + if err == io.EOF { + break + } + // caught error as expected + return + } + } + + t.Fatal("Inspect found error but we red this file correctly:", inspectErr.Error()) + }) +} From 39fdfc066b261c9d866585035f6ad77afad95072 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 1 Jul 2022 15:49:42 +0100 Subject: [PATCH 232/291] Use streaming APIs to verify the hash of blocks in CAR `Inspect` `go-cid` exposes `Sum` API that facilitates calculation of the CID from `[]byte` payload. `go-multihash` now exposes `SumStream` which can calculate digest from `io.Reader` as well as `[]byte`. But, unfortunately the equivalent API does not exist in `go-cid`. To avoid copying the entire block into memory, implement CID calculation using the streaming multihash sum during inspection of CAR payload. This commit was moved from ipld/go-car@f2498bcfddbce5f67e94d13ef8d8b2117c37cd10 --- ipld/car/v2/reader.go | 42 +++++++++++++++++++++++++------------- ipld/car/v2/reader_test.go | 4 ++-- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index c3ef36535d..cd10b81dae 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -11,6 +11,7 @@ import ( "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-multihash" "github.com/multiformats/go-varint" "golang.org/x/exp/mmap" ) @@ -266,23 +267,36 @@ func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { blockLength := sectionLength - uint64(cidLen) if validateBlockHash { - // read the block data, hash it and compare it - buf := make([]byte, blockLength) - if _, err := io.ReadFull(dr, buf); err != nil { - return CarStats{}, err + // Use multihash.SumStream to avoid having to copy the entire block content into memory. + // The SumStream uses a buffered copy to write bytes into the hasher which will take + // advantage of streaming hash calculation depending on the hash function. + // TODO: introduce SumStream in go-cid to simplify the code here. + blockReader := io.LimitReader(dr, int64(blockLength)) + mhl := cp.MhLength + if mhtype == multicodec.Identity { + mhl = -1 } - - hashed, err := cp.Sum(buf) + mh, err := multihash.SumStream(blockReader, cp.MhType, mhl) if err != nil { return CarStats{}, err } - - if !hashed.Equals(c) { - return CarStats{}, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", c, hashed) + var wantCid cid.Cid + switch cp.Version { + case 0: + wantCid = cid.NewCidV0(mh) + case 1: + wantCid = cid.NewCidV1(cp.Codec, mh) + default: + return CarStats{}, fmt.Errorf("invalid cid version: %d", cp.Version) + } + if !wantCid.Equals(c) { + return CarStats{}, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", wantCid, c) } } else { // otherwise, skip over it - dr.Seek(int64(blockLength), io.SeekCurrent) + if _, err := dr.Seek(int64(blockLength), io.SeekCurrent); err != nil { + return CarStats{}, err + } } stats.BlockCount++ @@ -294,11 +308,11 @@ func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { if uint64(cidLen) > stats.MaxCidLength { stats.MaxCidLength = uint64(cidLen) } - if uint64(blockLength) < minBlockLength { - minBlockLength = uint64(blockLength) + if blockLength < minBlockLength { + minBlockLength = blockLength } - if uint64(blockLength) > stats.MaxBlockLength { - stats.MaxBlockLength = uint64(blockLength) + if blockLength > stats.MaxBlockLength { + stats.MaxBlockLength = blockLength } } diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index 85605dadcb..5686d8442d 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -477,14 +477,14 @@ func TestInspectError(t *testing.T) { // header cid data carHex: "11a265726f6f7473806776657273696f6e 012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca ffffffffffffffffffff", validateBlockHash: true, - expectedInspectError: "mismatch in content integrity, expected: bafkreiab2rek7wjiazkfrt3hbnqpljmu24226alszdlh6ivic2abgjubzi, got: bafkreiaaqoxrddiyuy6gxnks6ioqytxhq5a7tchm2mm5htigznwiljukmm", + expectedInspectError: "mismatch in content integrity, expected: bafkreiaaqoxrddiyuy6gxnks6ioqytxhq5a7tchm2mm5htigznwiljukmm, got: bafkreiab2rek7wjiazkfrt3hbnqpljmu24226alszdlh6ivic2abgjubzi", }, { name: "IdentityCID", // a case where this _could_ be a valid CAR if we allowed identity CIDs and not matching block contents to exist, there's no block bytes in this // 47 {version:1,roots:[identity cid]} 25 identity cid (dag-json {"identity":"block"}) carHex: "2f a265726f6f747381d82a581a0001a90200147b226964656e74697479223a22626c6f636b227d6776657273696f6e01 19 01a90200147b226964656e74697479223a22626c6f636b227d", validateBlockHash: true, - expectedInspectError: "mismatch in content integrity, expected: baguqeaaupmrgszdfnz2gs5dzei5ceytmn5rwwit5, got: baguqeaaa", + expectedInspectError: "mismatch in content integrity, expected: baguqeaaa, got: baguqeaaupmrgszdfnz2gs5dzei5ceytmn5rwwit5", }, // the bad index tests are manually constructed from this single-block CARv2 by adjusting the Uint32 and Uint64 values in the index: // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset From c8d51501eb84638e8b8b7b68a3286270daa1caee Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Sat, 2 Jul 2022 09:51:07 +0100 Subject: [PATCH 233/291] Use consistent CID mismatch error in `Inspect` and `BlockReader.Next` This reverts the earlier changes to get the message consistent. Note, the CID we expect is the one in the CAR payload, not the calculated CID for the block. This commit was moved from ipld/go-car@38f20d6e6c3ef292680ff2f14d05432a331a1f81 --- ipld/car/v2/reader.go | 10 +++++----- ipld/car/v2/reader_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index cd10b81dae..f67638af2c 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -280,17 +280,17 @@ func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { if err != nil { return CarStats{}, err } - var wantCid cid.Cid + var gotCid cid.Cid switch cp.Version { case 0: - wantCid = cid.NewCidV0(mh) + gotCid = cid.NewCidV0(mh) case 1: - wantCid = cid.NewCidV1(cp.Codec, mh) + gotCid = cid.NewCidV1(cp.Codec, mh) default: return CarStats{}, fmt.Errorf("invalid cid version: %d", cp.Version) } - if !wantCid.Equals(c) { - return CarStats{}, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", wantCid, c) + if !gotCid.Equals(c) { + return CarStats{}, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", c, gotCid) } } else { // otherwise, skip over it diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index 5686d8442d..85605dadcb 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -477,14 +477,14 @@ func TestInspectError(t *testing.T) { // header cid data carHex: "11a265726f6f7473806776657273696f6e 012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca ffffffffffffffffffff", validateBlockHash: true, - expectedInspectError: "mismatch in content integrity, expected: bafkreiaaqoxrddiyuy6gxnks6ioqytxhq5a7tchm2mm5htigznwiljukmm, got: bafkreiab2rek7wjiazkfrt3hbnqpljmu24226alszdlh6ivic2abgjubzi", + expectedInspectError: "mismatch in content integrity, expected: bafkreiab2rek7wjiazkfrt3hbnqpljmu24226alszdlh6ivic2abgjubzi, got: bafkreiaaqoxrddiyuy6gxnks6ioqytxhq5a7tchm2mm5htigznwiljukmm", }, { name: "IdentityCID", // a case where this _could_ be a valid CAR if we allowed identity CIDs and not matching block contents to exist, there's no block bytes in this // 47 {version:1,roots:[identity cid]} 25 identity cid (dag-json {"identity":"block"}) carHex: "2f a265726f6f747381d82a581a0001a90200147b226964656e74697479223a22626c6f636b227d6776657273696f6e01 19 01a90200147b226964656e74697479223a22626c6f636b227d", validateBlockHash: true, - expectedInspectError: "mismatch in content integrity, expected: baguqeaaa, got: baguqeaaupmrgszdfnz2gs5dzei5ceytmn5rwwit5", + expectedInspectError: "mismatch in content integrity, expected: baguqeaaupmrgszdfnz2gs5dzei5ceytmn5rwwit5, got: baguqeaaa", }, // the bad index tests are manually constructed from this single-block CARv2 by adjusting the Uint32 and Uint64 values in the index: // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset From 8685c69e72a2df3275d166ea18081dc1d7debfb2 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Sat, 2 Jul 2022 10:10:41 +0100 Subject: [PATCH 234/291] Benchmark `Reader.Inspect` with and without hash validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Benchmark the `Reader.Inspect` with and without hash validation using a randomly generated CARv2 file of size 10 MiB. Results from running the benchmark in parallel locally on MacOS `Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz` repeated 10 times: ``` Reader_InspectWithBlockValidation-8 5.30ms ±48% Reader_InspectWithoutBlockValidation-8 231µs ±42% name speed Reader_InspectWithBlockValidation-8 2.08GB/s ±35% Reader_InspectWithoutBlockValidation-8 46.8GB/s ±32% name alloc/op Reader_InspectWithBlockValidation-8 10.7MB ± 0% Reader_InspectWithoutBlockValidation-8 60.7kB ± 0% name allocs/op Reader_InspectWithBlockValidation-8 4.54k ± 0% Reader_InspectWithoutBlockValidation-8 2.29k ± 0% ``` This commit was moved from ipld/go-car@3a0f2a4b93df6696b7651c5c29a7b7281331abe5 --- ipld/car/v2/bench_test.go | 51 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/ipld/car/v2/bench_test.go b/ipld/car/v2/bench_test.go index 6e76693592..e413180bbd 100644 --- a/ipld/car/v2/bench_test.go +++ b/ipld/car/v2/bench_test.go @@ -119,6 +119,57 @@ func BenchmarkExtractV1UsingReader(b *testing.B) { }) } +// BenchmarkReader_InspectWithBlockValidation benchmarks Reader.Inspect with block hash validation +// for a randomly generated CARv2 file of size 10 MiB. +func BenchmarkReader_InspectWithBlockValidation(b *testing.B) { + path := filepath.Join(b.TempDir(), "bench-large-v2.car") + generateRandomCarV2File(b, path, 10<<20) // 10 MiB + defer os.Remove(path) + + info, err := os.Stat(path) + if err != nil { + b.Fatal(err) + } + b.SetBytes(info.Size()) + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + benchmarkInspect(b, path, true) + } + }) +} + +// BenchmarkReader_InspectWithoutBlockValidation benchmarks Reader.Inspect without block hash +// validation for a randomly generated CARv2 file of size 10 MiB. +func BenchmarkReader_InspectWithoutBlockValidation(b *testing.B) { + path := filepath.Join(b.TempDir(), "bench-large-v2.car") + generateRandomCarV2File(b, path, 10<<20) // 10 MiB + defer os.Remove(path) + + info, err := os.Stat(path) + if err != nil { + b.Fatal(err) + } + b.SetBytes(info.Size()) + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + benchmarkInspect(b, path, false) + } + }) +} + +func benchmarkInspect(b *testing.B, path string, validateBlockHash bool) { + reader, err := carv2.OpenReader(path) + if err != nil { + b.Fatal(err) + } + if _, err := reader.Inspect(validateBlockHash); err != nil { + b.Fatal(err) + } +} func generateRandomCarV2File(b *testing.B, path string, minTotalBlockSize int) { // Use fixed RNG for determinism across benchmarks. rng := rand.New(rand.NewSource(1413)) From 86c35fc117075a896784f754153faa5d388491e0 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Sun, 3 Jul 2022 09:22:25 +0100 Subject: [PATCH 235/291] Drop repeated package name from `CarStats` Cosmetic refactor to rename `car.CarStats` to `car.Stats`, which looks more fluent when using the API. This commit was moved from ipld/go-car@1fabdd7cf8d33f8b58aeea33f1ccc236bcd3b2bc --- ipld/car/v2/reader.go | 40 +++++++++++++++++++------------------- ipld/car/v2/reader_test.go | 12 ++++++------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index f67638af2c..df73f2937f 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -122,8 +122,8 @@ func (r *Reader) IndexReader() io.ReaderAt { return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) } -// CarStats is returned by an Inspect() call -type CarStats struct { +// Stats is returned by an Inspect() call +type Stats struct { Version uint64 Header Header Roots []cid.Cid @@ -142,7 +142,7 @@ type CarStats struct { } // Inspect does a quick scan of a CAR, performing basic validation of the format -// and returning a CarStats object that provides a high-level description of the +// and returning a Stats object that provides a high-level description of the // contents of the CAR. // Inspect works for CARv1 and CARv2 contents. A CARv1 will return an // uninitialized Header value. @@ -169,26 +169,26 @@ type CarStats struct { // them and have any reason to not trust the source. // // * Blocks use codecs that your system doesn't have access to—which may mean -// you can't traverse a DAG or use the contained data. CarStats#CodecCounts +// you can't traverse a DAG or use the contained data. Stats.CodecCounts // contains a list of codecs found in the CAR so this can be checked. // // * CIDs use multihashes that your system doesn't have access to—which will // mean you can't validate block hashes are correct (using validateBlockHash -// in this case will result in a failure). CarStats#MhTypeCounts contains a -// list of multihashes found in the CAR so this can bechecked. +// in this case will result in a failure). Stats.MhTypeCounts contains a +// list of multihashes found in the CAR so this can be checked. // // * The presence of IDENTITY CIDs, which may not be supported (or desired) by -// the consumer of the CAR. CarStats#CodecCounts can determine the presence +// the consumer of the CAR. Stats.CodecCounts can determine the presence // of IDENTITY CIDs. // // * Roots: the number of roots, duplicates, and whether they are related to the -// blocks contained within the CAR. CarStats contains a list of Roots and a +// blocks contained within the CAR. Stats contains a list of Roots and a // RootsPresent bool so further checks can be performed. // // * DAG completeness is not checked. Any properties relating to the DAG, or // DAGs contained within a CAR are the responsibility of the user to check. -func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { - stats := CarStats{ +func (r *Reader) Inspect(validateBlockHash bool) (Stats, error) { + stats := Stats{ Version: r.Version, Header: r.Header, CodecCounts: make(map[multicodec.Code]uint64), @@ -206,7 +206,7 @@ func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { // read roots, not using Roots(), because we need the offset setup in the data trader header, err := carv1.ReadHeader(dr, r.opts.MaxAllowedHeaderSize) if err != nil { - return CarStats{}, err + return Stats{}, err } stats.Roots = header.Roots var rootsPresentCount int @@ -219,7 +219,7 @@ func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { if err == io.EOF { // if the length of bytes read is non-zero when the error is EOF then signal an unclean EOF. if sectionLength > 0 { - return CarStats{}, io.ErrUnexpectedEOF + return Stats{}, io.ErrUnexpectedEOF } // otherwise, this is a normal ending break @@ -230,20 +230,20 @@ func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { break } if sectionLength > r.opts.MaxAllowedSectionSize { - return CarStats{}, util.ErrSectionTooLarge + return Stats{}, util.ErrSectionTooLarge } // decode just the CID bytes cidLen, c, err := cid.CidFromReader(dr) if err != nil { - return CarStats{}, err + return Stats{}, err } if sectionLength < uint64(cidLen) { // this case is handled different in the normal ReadNode() path since it // slurps in the whole section bytes and decodes CID from there - so an // error should come from a failing io.ReadFull - return CarStats{}, fmt.Errorf("section length shorter than CID length") + return Stats{}, fmt.Errorf("section length shorter than CID length") } // is this a root block? (also account for duplicate root CIDs) @@ -278,7 +278,7 @@ func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { } mh, err := multihash.SumStream(blockReader, cp.MhType, mhl) if err != nil { - return CarStats{}, err + return Stats{}, err } var gotCid cid.Cid switch cp.Version { @@ -287,15 +287,15 @@ func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { case 1: gotCid = cid.NewCidV1(cp.Codec, mh) default: - return CarStats{}, fmt.Errorf("invalid cid version: %d", cp.Version) + return Stats{}, fmt.Errorf("invalid cid version: %d", cp.Version) } if !gotCid.Equals(c) { - return CarStats{}, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", c, gotCid) + return Stats{}, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", c, gotCid) } } else { // otherwise, skip over it if _, err := dr.Seek(int64(blockLength), io.SeekCurrent); err != nil { - return CarStats{}, err + return Stats{}, err } } @@ -329,7 +329,7 @@ func (r *Reader) Inspect(validateBlockHash bool) (CarStats, error) { // is intended to be a fast initial scan ind, size, err := index.ReadFromWithSize(r.IndexReader()) if err != nil { - return CarStats{}, err + return Stats{}, err } stats.IndexCodec = ind.Codec() stats.IndexSize = uint64(size) diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index 85605dadcb..7c43d93118 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -280,12 +280,12 @@ func TestInspect(t *testing.T) { path string carHex string zerLenAsEOF bool - expectedStats carv2.CarStats + expectedStats carv2.Stats }{ { name: "IndexlessCarV2", path: "testdata/sample-v2-indexless.car", - expectedStats: carv2.CarStats{ + expectedStats: carv2.Stats{ Version: 2, Header: carv2.Header{ Characteristics: carv2.Characteristics{0, 0}, @@ -316,7 +316,7 @@ func TestInspect(t *testing.T) { // same payload as IndexlessCarV2, so only difference is the Version & Header name: "CarV1", path: "testdata/sample-v1.car", - expectedStats: carv2.CarStats{ + expectedStats: carv2.Stats{ Version: 1, Header: carv2.Header{}, Roots: []cid.Cid{mustCidDecode("bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy")}, @@ -342,7 +342,7 @@ func TestInspect(t *testing.T) { // same payload as IndexlessCarV2, so only difference is the Header name: "CarV2ProducedByBlockstore", path: "testdata/sample-rw-bs-v2.car", - expectedStats: carv2.CarStats{ + expectedStats: carv2.Stats{ Version: 2, Header: carv2.Header{ DataOffset: 1464, @@ -373,7 +373,7 @@ func TestInspect(t *testing.T) { name: "CarV1VersionWithZeroLenSectionIsOne", path: "testdata/sample-v1-with-zero-len-section.car", zerLenAsEOF: true, - expectedStats: carv2.CarStats{ + expectedStats: carv2.Stats{ Version: 1, Header: carv2.Header{}, Roots: []cid.Cid{mustCidDecode("bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy")}, @@ -403,7 +403,7 @@ func TestInspect(t *testing.T) { name: "IdentityCID", // 47 {version:1,roots:[identity cid]} 25 identity cid (dag-json {"identity":"block"}) carHex: "2f a265726f6f747381d82a581a0001a90200147b226964656e74697479223a22626c6f636b227d6776657273696f6e01 19 01a90200147b226964656e74697479223a22626c6f636b227d", - expectedStats: carv2.CarStats{ + expectedStats: carv2.Stats{ Version: 1, Roots: []cid.Cid{mustCidDecode("baguqeaaupmrgszdfnz2gs5dzei5ceytmn5rwwit5")}, RootsPresent: true, From 9da4299cafd14dcd8a969308cd8dafb456720a81 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 4 Jul 2022 09:41:53 +0100 Subject: [PATCH 236/291] Return error when section length is invalid `varint` Return potential error when reading section error as varint. Add test to verify the error is indeed returned. Use `errors.New` instead of `fmt.Errorf` when no formatting is needed in error message. This commit was moved from ipld/go-car@8bb203bef695893ae57a26a32ea2ac0b355de791 --- ipld/car/v2/reader.go | 4 +++- ipld/car/v2/reader_test.go | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index df73f2937f..c34a0672d5 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -1,6 +1,7 @@ package car import ( + "errors" "fmt" "io" "math" @@ -224,6 +225,7 @@ func (r *Reader) Inspect(validateBlockHash bool) (Stats, error) { // otherwise, this is a normal ending break } + return Stats{}, err } if sectionLength == 0 && r.opts.ZeroLengthSectionAsEOF { // normal ending for this read mode @@ -243,7 +245,7 @@ func (r *Reader) Inspect(validateBlockHash bool) (Stats, error) { // this case is handled different in the normal ReadNode() path since it // slurps in the whole section bytes and decodes CID from there - so an // error should come from a failing io.ReadFull - return Stats{}, fmt.Errorf("section length shorter than CID length") + return Stats{}, errors.New("section length shorter than CID length") } // is this a root block? (also account for duplicate root CIDs) diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index 7c43d93118..ed2b78e25b 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -467,6 +467,11 @@ func TestInspectError(t *testing.T) { expectedInspectError: "section length shorter than CID length", validateBlockHash: true, }, + { + name: "BadSectionLength3", + carHex: "11a265726f6f7473f66776657273696f6e0180", + expectedInspectError: "unexpected EOF", + }, { name: "BadBlockHash(SanityCheck)", // this should pass because we don't ask the CID be validated even though it doesn't match // header cid data From 8006b6f3a9f5948d9697736131394ed491ac1652 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 4 Jul 2022 20:19:12 +0100 Subject: [PATCH 237/291] Fix fuzz CI job This commit was moved from ipld/go-car@6f66dc0b59af60636b29b575ad581928f642c367 --- ipld/car/.github/workflows/go-fuzz.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ipld/car/.github/workflows/go-fuzz.yml b/ipld/car/.github/workflows/go-fuzz.yml index 548a4a91de..cb7b4b2724 100644 --- a/ipld/car/.github/workflows/go-fuzz.yml +++ b/ipld/car/.github/workflows/go-fuzz.yml @@ -1,4 +1,4 @@ -on: [push, pull_request] +on: [ push, pull_request ] name: Go Fuzz jobs: @@ -21,8 +21,7 @@ jobs: go version go env - name: Run Fuzzing for 1m - with: - run: go test -v -fuzz=Fuzz${{ matrix.target }} -fuzztime=1m . + run: go test -v -fuzz=Fuzz${{ matrix.target }} -fuzztime=1m . v2: strategy: fail-fast: true @@ -42,7 +41,6 @@ jobs: go version go env - name: Run Fuzzing for 1m - with: - run: | - cd v2 - go test -v -fuzz=Fuzz${{ matrix.target }} -fuzztime=1m . + run: | + cd v2 + go test -v -fuzz=Fuzz${{ matrix.target }} -fuzztime=1m . From 51fed4dfb3d0288d21801752ad98bf98a8077307 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Mon, 4 Jul 2022 20:08:34 +0100 Subject: [PATCH 238/291] Revert changes to `index.Index` while keeping most of security fixes Revert the changes to `index.Index` interface such that it is the same as the current go-car main branch head. Reverting the changes, however, means that unmarshalling untrusted indices is indeed dangerous and should not be done on untrusted files. Note, the `carv2.Reader` APIs are changed to return errors as well as readers when getting `DataReader` and `IndexReader`. This is to accommodate issues detected by fuzz testing while removing boiler plate code in internal IO reader conversion. This is a breaking change to the current API but should be straight forward to roll out. Remove index fuzz tests and change inspection to only read the index codec instead of reading the entire index. This commit was moved from ipld/go-car@4bc677484af39d1153e075ae162c5ebcc6e396a3 --- ipld/car/v2/bench_test.go | 6 +- ipld/car/v2/blockstore/readonly.go | 59 ++++++-- ipld/car/v2/blockstore/readonly_test.go | 4 +- ipld/car/v2/blockstore/readwrite.go | 13 +- ipld/car/v2/blockstore/readwrite_test.go | 49 +++--- ipld/car/v2/example_test.go | 6 +- ipld/car/v2/fuzz_test.go | 69 +++------ ipld/car/v2/index/example_test.go | 36 ++--- ipld/car/v2/index/index.go | 64 ++++---- ipld/car/v2/index/index_test.go | 6 +- ipld/car/v2/index/indexsorted.go | 141 ++++++------------ ipld/car/v2/index/indexsorted_test.go | 15 +- ipld/car/v2/index/mhindexsorted.go | 74 +++------ ipld/car/v2/index/mhindexsorted_test.go | 7 +- ipld/car/v2/index/testutil/equal_index.go | 71 --------- ipld/car/v2/index_gen.go | 12 +- ipld/car/v2/index_gen_test.go | 7 +- ipld/car/v2/internal/io/fullReaderAt.go | 20 --- ipld/car/v2/internal/io/offset_read_seeker.go | 30 +--- ipld/car/v2/reader.go | 37 +++-- ipld/car/v2/reader_test.go | 39 +++-- ipld/car/v2/writer_test.go | 11 +- 22 files changed, 304 insertions(+), 472 deletions(-) delete mode 100644 ipld/car/v2/index/testutil/equal_index.go delete mode 100644 ipld/car/v2/internal/io/fullReaderAt.go diff --git a/ipld/car/v2/bench_test.go b/ipld/car/v2/bench_test.go index e413180bbd..2f7dcc63d6 100644 --- a/ipld/car/v2/bench_test.go +++ b/ipld/car/v2/bench_test.go @@ -108,7 +108,11 @@ func BenchmarkExtractV1UsingReader(b *testing.B) { if err != nil { b.Fatal(err) } - _, err = io.Copy(dst, reader.DataReader()) + dr, err := reader.DataReader() + if err != nil { + b.Fatal(err) + } + _, err = io.Copy(dst, dr) if err != nil { b.Fatal(err) } diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 307486d79d..32c49046bd 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -120,15 +120,28 @@ func NewReadOnly(backing io.ReaderAt, idx index.Index, opts ...carv2.Option) (*R } if idx == nil { if v2r.Header.HasIndex() { - idx, err = index.ReadFrom(v2r.IndexReader()) + ir, err := v2r.IndexReader() if err != nil { return nil, err } - } else if idx, err = generateIndex(v2r.DataReader(), opts...); err != nil { - return nil, err + idx, err = index.ReadFrom(ir) + if err != nil { + return nil, err + } + } else { + dr, err := v2r.DataReader() + if err != nil { + return nil, err + } + if idx, err = generateIndex(dr, opts...); err != nil { + return nil, err + } } } - b.backing = v2r.DataReader() + b.backing, err = v2r.DataReader() + if err != nil { + return nil, err + } b.idx = idx return b, nil default: @@ -142,7 +155,11 @@ func readVersion(at io.ReaderAt, opts ...carv2.Option) (uint64, error) { case io.Reader: rr = r default: - rr = internalio.NewOffsetReadSeeker(r, 0) + var err error + rr, err = internalio.NewOffsetReadSeeker(r, 0) + if err != nil { + return 0, err + } } return carv2.ReadVersion(rr, opts...) } @@ -157,7 +174,11 @@ func generateIndex(at io.ReaderAt, opts ...carv2.Option) (index.Index, error) { return nil, err } default: - rs = internalio.NewOffsetReadSeeker(r, 0) + var err error + rs, err = internalio.NewOffsetReadSeeker(r, 0) + if err != nil { + return nil, err + } } // Note, we do not set any write options so that all write options fall back onto defaults. @@ -183,7 +204,7 @@ func OpenReadOnly(path string, opts ...carv2.Option) (*ReadOnly, error) { } func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { - r, err := internalio.NewOffsetReadSeekerWithError(b.backing, idx) + r, err := internalio.NewOffsetReadSeeker(b.backing, idx) if err != nil { return cid.Cid{}, nil, err } @@ -216,8 +237,11 @@ func (b *ReadOnly) Has(ctx context.Context, key cid.Cid) (bool, error) { var fnFound bool var fnErr error err := b.idx.GetAll(key, func(offset uint64) bool { - uar := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) - var err error + uar, err := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) + if err != nil { + fnErr = err + return false + } _, err = varint.ReadUvarint(uar) if err != nil { fnErr = err @@ -317,7 +341,11 @@ func (b *ReadOnly) GetSize(ctx context.Context, key cid.Cid) (int, error) { fnSize := -1 var fnErr error err := b.idx.GetAll(key, func(offset uint64) bool { - rdr := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) + rdr, err := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) + if err != nil { + fnErr = err + return false + } sectionLen, err := varint.ReadUvarint(rdr) if err != nil { fnErr = err @@ -401,7 +429,10 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { } // TODO we may use this walk for populating the index, and we need to be able to iterate keys in this way somewhere for index generation. In general though, when it's asked for all keys from a blockstore with an index, we should iterate through the index when possible rather than linear reads through the full car. - rdr := internalio.NewOffsetReadSeeker(b.backing, 0) + rdr, err := internalio.NewOffsetReadSeeker(b.backing, 0) + if err != nil { + return nil, err + } header, err := carv1.ReadHeader(rdr, b.opts.MaxAllowedHeaderSize) if err != nil { b.mu.RUnlock() // don't hold the mutex forever @@ -492,7 +523,11 @@ func (b *ReadOnly) HashOnRead(bool) { // Roots returns the root CIDs of the backing CAR. func (b *ReadOnly) Roots() ([]cid.Cid, error) { - header, err := carv1.ReadHeader(internalio.NewOffsetReadSeeker(b.backing, 0), b.opts.MaxAllowedHeaderSize) + ors, err := internalio.NewOffsetReadSeeker(b.backing, 0) + if err != nil { + return nil, err + } + header, err := carv1.ReadHeader(ors, b.opts.MaxAllowedHeaderSize) if err != nil { return nil, fmt.Errorf("error reading car header: %w", err) } diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index f55a1e50cd..5a947a75b3 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -226,7 +226,9 @@ func newV1ReaderFromV2File(t *testing.T, carv2Path string, zeroLenSectionAsEOF b t.Cleanup(func() { f.Close() }) v2r, err := carv2.NewReader(f) require.NoError(t, err) - v1r, err := newV1Reader(v2r.DataReader(), zeroLenSectionAsEOF) + dr, err := v2r.DataReader() + require.NoError(t, err) + v1r, err := newV1Reader(dr, zeroLenSectionAsEOF) require.NoError(t, err) return v1r } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 0fad9893bf..774f37ff69 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -139,7 +139,7 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.Option) (*ReadWri offset = 0 } rwbs.dataWriter = internalio.NewOffsetWriter(rwbs.f, offset) - v1r, err := internalio.NewOffsetReadSeekerWithError(rwbs.f, offset) + v1r, err := internalio.NewOffsetReadSeeker(rwbs.f, offset) if err != nil { return nil, err } @@ -169,11 +169,11 @@ func (b *ReadWrite) initWithRoots(v2 bool, roots []cid.Cid) error { return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, b.dataWriter) } -func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid, opts ...carv2.Option) error { +func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid) error { // On resumption it is expected that the CARv2 Pragma, and the CARv1 header is successfully written. // Otherwise we cannot resume from the file. // Read pragma to assert if b.f is indeed a CARv2. - version, err := carv2.ReadVersion(b.f, opts...) + version, err := carv2.ReadVersion(b.f) if err != nil { // The file is not a valid CAR file and cannot resume from it. // Or the write must have failed before pragma was written. @@ -193,7 +193,7 @@ func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid, opts ...carv2.Opti // Check if file was finalized by trying to read the CARv2 header. // We check because if finalized the CARv1 reader behaviour needs to be adjusted since // EOF will not signify end of CARv1 payload. i.e. index is most likely present. - r, err := internalio.NewOffsetReadSeekerWithError(b.f, carv2.PragmaSize) + r, err := internalio.NewOffsetReadSeeker(b.f, carv2.PragmaSize) if err != nil { return err } @@ -223,7 +223,10 @@ func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid, opts ...carv2.Opti } // Use the given CARv1 padding to instantiate the CARv1 reader on file. - v1r := internalio.NewOffsetReadSeeker(b.ronly.backing, 0) + v1r, err := internalio.NewOffsetReadSeeker(b.ronly.backing, 0) + if err != nil { + return err + } header, err := carv1.ReadHeader(v1r, b.opts.MaxAllowedHeaderSize) if err != nil { // Cannot read the CARv1 header; the file is most likely corrupt. diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 6f64ac72f4..11cc99a220 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -21,7 +21,6 @@ import ( carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" "github.com/ipld/go-car/v2/index" - "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" @@ -488,7 +487,9 @@ func TestBlockstoreResumption(t *testing.T) { wantPayloadReader, err := carv1.NewCarReader(v1f) require.NoError(t, err) - gotPayloadReader, err := carv1.NewCarReader(v2r.DataReader()) + dr, err := v2r.DataReader() + require.NoError(t, err) + gotPayloadReader, err := carv1.NewCarReader(dr) require.NoError(t, err) require.Equal(t, wantPayloadReader.Header, gotPayloadReader.Header) @@ -516,11 +517,15 @@ func TestBlockstoreResumption(t *testing.T) { // Assert index in resumed from file is identical to index generated from the data payload portion of the generated CARv2 file. _, err = v1f.Seek(0, io.SeekStart) require.NoError(t, err) - gotIdx, err := index.ReadFrom(v2r.IndexReader()) + ir, err := v2r.IndexReader() + require.NoError(t, err) + gotIdx, err := index.ReadFrom(ir) require.NoError(t, err) - wantIdx, err := carv2.GenerateIndex(v2r.DataReader()) + dr, err = v2r.DataReader() require.NoError(t, err) - testutil.AssertIdenticalIndexes(t, wantIdx, gotIdx) + wantIdx, err := carv2.GenerateIndex(dr) + require.NoError(t, err) + require.Equal(t, wantIdx, gotIdx) } func TestBlockstoreResumptionIsSupportedOnFinalizedFile(t *testing.T) { @@ -829,29 +834,37 @@ func TestOpenReadWrite_WritesIdentityCIDsWhenOptionIsEnabled(t *testing.T) { t.Cleanup(func() { require.NoError(t, r.Close()) }) require.True(t, r.Header.HasIndex()) - ir := r.IndexReader() + ir, err := r.IndexReader() + require.NoError(t, err) require.NotNil(t, ir) gotIdx, err := index.ReadFrom(ir) require.NoError(t, err) // Determine expected offset as the length of header plus one - header, err := carv1.ReadHeader(r.DataReader(), carv1.DefaultMaxAllowedHeaderSize) + dr, err := r.DataReader() + require.NoError(t, err) + header, err := carv1.ReadHeader(dr, carv1.DefaultMaxAllowedHeaderSize) require.NoError(t, err) object, err := cbor.DumpObject(header) require.NoError(t, err) expectedOffset := len(object) + 1 // Assert index is iterable and has exactly one record with expected multihash and offset. - var count int - err = gotIdx.ForEach(func(mh multihash.Multihash, offset uint64) error { - count++ - require.Equal(t, idmh, mh) - require.Equal(t, uint64(expectedOffset), offset) - return nil - }) - require.NoError(t, err) - require.Equal(t, 1, count) + switch idx := gotIdx.(type) { + case index.IterableIndex: + var i int + err := idx.ForEach(func(mh multihash.Multihash, offset uint64) error { + i++ + require.Equal(t, idmh, mh) + require.Equal(t, uint64(expectedOffset), offset) + return nil + }) + require.NoError(t, err) + require.Equal(t, 1, i) + default: + require.Failf(t, "unexpected index type", "wanted %v but got %v", multicodec.CarMultihashIndexSorted, idx.Codec()) + } } func TestOpenReadWrite_ErrorsWhenWritingTooLargeOfACid(t *testing.T) { @@ -914,7 +927,9 @@ func TestReadWrite_ReWritingCARv1WithIdentityCidIsIdenticalToOriginalWithOptions // Note, we hash instead of comparing bytes to avoid excessive memory usage when sample CARv1 is large. hasher := sha512.New() - gotWritten, err := io.Copy(hasher, v2r.DataReader()) + dr, err := v2r.DataReader() + require.NoError(t, err) + gotWritten, err := io.Copy(hasher, dr) require.NoError(t, err) gotSum := hasher.Sum(nil) diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go index 53dfa34918..57378aeaad 100644 --- a/ipld/car/v2/example_test.go +++ b/ipld/car/v2/example_test.go @@ -51,7 +51,11 @@ func ExampleWrapV1File() { if err != nil { panic(err) } - inner, err := ioutil.ReadAll(cr.DataReader()) + dr, err := cr.DataReader() + if err != nil { + panic(err) + } + inner, err := ioutil.ReadAll(dr) if err != nil { panic(err) } diff --git a/ipld/car/v2/fuzz_test.go b/ipld/car/v2/fuzz_test.go index c747375053..c45d83cb0b 100644 --- a/ipld/car/v2/fuzz_test.go +++ b/ipld/car/v2/fuzz_test.go @@ -12,7 +12,6 @@ import ( "testing" car "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" ) @@ -73,48 +72,15 @@ func FuzzReader(f *testing.F) { } subject.Roots() - ir := subject.IndexReader() - if ir != nil { - index.ReadFrom(ir) + _, err = subject.IndexReader() + if err != nil { + return } - car.GenerateIndex(subject.DataReader()) - }) -} - -func FuzzIndex(f *testing.F) { - files, err := filepath.Glob("testdata/*.car") - if err != nil { - f.Fatal(err) - } - for _, fname := range files { - func() { - file, err := os.Open(fname) - if err != nil { - f.Fatal(err) - } - defer file.Close() - subject, err := car.NewReader(file) - if err != nil { - return - } - indexRdr := subject.IndexReader() - if indexRdr == nil { - return - } - _, n, err := index.ReadFromWithSize(indexRdr) - if err != nil { - return - } - data, err := io.ReadAll(io.NewSectionReader(indexRdr, 0, n)) - if err != nil { - f.Fatal(err) - } - f.Add(data) - }() - } - - f.Fuzz(func(t *testing.T, data []byte) { - index.ReadFrom(bytes.NewReader(data)) + dr, err := subject.DataReader() + if err != nil { + return + } + car.GenerateIndex(dr) }) } @@ -137,15 +103,18 @@ func FuzzInspect(f *testing.F) { if err != nil { t.Fatal("second NewReader on same data failed", err.Error()) } - - if i := reader.IndexReader(); i != nil { - _, err = index.ReadFrom(i) - if err != nil { - return - } + i, err := reader.IndexReader() + if err != nil { + return + } + // FIXME: Once indexes are safe to parse, do not skip .car with index in the differential fuzzing. + if i == nil { + return + } + dr, err := reader.DataReader() + if err != nil { + return } - - dr := reader.DataReader() _, err = carv1.ReadHeader(dr, carv1.DefaultMaxAllowedHeaderSize) if err != nil { diff --git a/ipld/car/v2/index/example_test.go b/ipld/car/v2/index/example_test.go index c6f83ea286..d2a9da54b6 100644 --- a/ipld/car/v2/index/example_test.go +++ b/ipld/car/v2/index/example_test.go @@ -5,10 +5,10 @@ import ( "io" "io/ioutil" "os" + "reflect" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" - "github.com/multiformats/go-multihash" ) // ExampleReadFrom unmarshalls an index from an indexed CARv2 file, and for each root CID prints the @@ -28,7 +28,11 @@ func ExampleReadFrom() { } // Read and unmarshall index within CARv2 file. - idx, err := index.ReadFrom(cr.IndexReader()) + ir, err := cr.IndexReader() + if err != nil { + panic(err) + } + idx, err := index.ReadFrom(ir) if err != nil { panic(err) } @@ -62,7 +66,11 @@ func ExampleWriteTo() { }() // Read and unmarshall index within CARv2 file. - idx, err := index.ReadFrom(cr.IndexReader()) + ir, err := cr.IndexReader() + if err != nil { + panic(err) + } + idx, err := index.ReadFrom(ir) if err != nil { panic(err) } @@ -94,27 +102,13 @@ func ExampleWriteTo() { panic(err) } - // Expect indices to be equal - collect all of the multihashes and their - // offsets from the first and compare to the second - mha := make(map[string]uint64, 0) - _ = idx.ForEach(func(mh multihash.Multihash, off uint64) error { - mha[mh.HexString()] = off - return nil - }) - var count int - _ = reReadIdx.ForEach(func(mh multihash.Multihash, off uint64) error { - count++ - if expectedOffset, ok := mha[mh.HexString()]; !ok || expectedOffset != off { - panic("expected to get the same index as the CARv2 file") - } - return nil - }) - if count != len(mha) { + // Expect indices to be equal. + if reflect.DeepEqual(idx, reReadIdx) { + fmt.Printf("Saved index file matches the index embedded in CARv2 at %v.\n", src) + } else { panic("expected to get the same index as the CARv2 file") } - fmt.Printf("Saved index file matches the index embedded in CARv2 at %v.\n", src) - // Output: // Saved index file matches the index embedded in CARv2 at ../testdata/sample-wrapped-v2.car. } diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 3a2b3f1d96..911eaa7ae1 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -44,17 +44,12 @@ type ( Marshal(w io.Writer) (uint64, error) // Unmarshal decodes the index from its serial form. - // Deprecated: This function is slurpy and will copy everything into memory. + // Note, this function will copy the entire index into memory. + // + // Do not unmarshal index from untrusted CARv2 files. Instead the index should be + // regenerated from the CARv2 data payload. Unmarshal(r io.Reader) error - // UnmarshalLazyRead lazily decodes the index from its serial form. It is a - // safer alternative to to Unmarshal, particularly when reading index data - // from untrusted sources (which is not recommended) but also in more - // constrained memory environments. - // Instead of slurping UnmarshalLazyRead will keep a reference to the the - // io.ReaderAt passed in and ask for data as needed. - UnmarshalLazyRead(r io.ReaderAt) (indexSize int64, err error) - // Load inserts a number of records into the index. // Note that Index will load all given records. Any filtering of the records such as // exclusion of CIDs with multihash.IDENTITY code must occur prior to calling this function. @@ -74,17 +69,17 @@ type ( // meaning that no callbacks happen, // ErrNotFound is returned. GetAll(cid.Cid, func(uint64) bool) error + } + + // IterableIndex is an index which support iterating over it's elements + IterableIndex interface { + Index // ForEach takes a callback function that will be called // on each entry in the index. The arguments to the callback are // the multihash of the element, and the offset in the car file // where the element appears. // - // Note that index with codec multicodec.CarIndexSorted does not support ForEach enumeration. - // Because this index type only contains the multihash digest and not the code. - // Calling ForEach on this index type will result in error. - // Use multicodec.CarMultihashIndexSorted index type instead. - // // If the callback returns a non-nil error, the iteration is aborted, // and the ForEach function returns the error to the user. // @@ -94,12 +89,6 @@ type ( // The order of calls to the given function is deterministic, but entirely index-specific. ForEach(func(multihash.Multihash, uint64) error) error } - - // IterableIndex is an index which support iterating over it's elements - // Deprecated: IterableIndex has been moved into Index. Just use Index now. - IterableIndex interface { - Index - } ) // GetFirst is a wrapper over Index.GetAll, returning the offset for the first @@ -143,32 +132,29 @@ func WriteTo(idx Index, w io.Writer) (uint64, error) { // ReadFrom reads index from r. // The reader decodes the index by reading the first byte to interpret the encoding. // Returns error if the encoding is not known. +// // Attempting to read index data from untrusted sources is not recommended. -func ReadFrom(r io.ReaderAt) (Index, error) { - idx, _, err := ReadFromWithSize(r) - return idx, err -} - -// ReadFromWithSize is just like ReadFrom but return the size of the Index. -// The size is only valid when err != nil. -// Attempting to read index data from untrusted sources is not recommended. -func ReadFromWithSize(r io.ReaderAt) (Index, int64, error) { - code, err := varint.ReadUvarint(internalio.NewOffsetReadSeeker(r, 0)) +// Instead the index should be regenerated from the CARv2 data payload. +func ReadFrom(r io.Reader) (Index, error) { + codec, err := ReadCodec(r) if err != nil { - return nil, 0, err + return nil, err } - codec := multicodec.Code(code) idx, err := New(codec) if err != nil { - return nil, 0, err + return nil, err } - rdr, err := internalio.NewOffsetReadSeekerWithError(r, int64(varint.UvarintSize(code))) - if err != nil { - return nil, 0, err + if err := idx.Unmarshal(r); err != nil { + return nil, err } - n, err := idx.UnmarshalLazyRead(rdr) + return idx, nil +} + +// ReadCodec reads the codec of the index by decoding the first varint read from r. +func ReadCodec(r io.Reader) (multicodec.Code, error) { + code, err := varint.ReadUvarint(internalio.ToByteReader(r)) if err != nil { - return nil, 0, err + return 0, err } - return idx, n, nil + return multicodec.Code(code), nil } diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index 0d380cafe5..972b4c669a 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -8,7 +8,6 @@ import ( "testing" blocks "github.com/ipfs/go-block-format" - "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" "github.com/multiformats/go-multicodec" @@ -55,6 +54,9 @@ func TestReadFrom(t *testing.T) { subject, err := ReadFrom(idxf) require.NoError(t, err) + _, err = idxf.Seek(0, io.SeekStart) + require.NoError(t, err) + idxf2, err := os.Open("../testdata/sample-multihash-index-sorted.carindex") require.NoError(t, err) t.Cleanup(func() { require.NoError(t, idxf2.Close()) }) @@ -126,7 +128,7 @@ func TestWriteTo(t *testing.T) { require.NoError(t, err) // Assert they are equal - testutil.AssertIdenticalIndexes(t, wantIdx, gotIdx) + require.Equal(t, wantIdx, gotIdx) } func TestMarshalledIndexStartsWithCodec(t *testing.T) { diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 60d0e87d34..aeed4c11ce 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -5,22 +5,16 @@ import ( "encoding/binary" "errors" "fmt" + internalio "github.com/ipld/go-car/v2/internal/io" "io" "sort" - "github.com/ipld/go-car/v2/internal/errsort" - internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" ) -type sizedReaderAt interface { - io.ReaderAt - Size() int64 -} - var _ Index = (*multiWidthIndex)(nil) type ( @@ -32,7 +26,7 @@ type ( singleWidthIndex struct { width uint32 len uint64 // in struct, len is #items. when marshaled, it's saved as #bytes. - index sizedReaderAt + index []byte } multiWidthIndex map[uint32]singleWidthIndex ) @@ -60,24 +54,27 @@ func (s *singleWidthIndex) Marshal(w io.Writer) (uint64, error) { return 0, err } l += 4 - sz := s.index.Size() - if err := binary.Write(w, binary.LittleEndian, sz); err != nil { + if err := binary.Write(w, binary.LittleEndian, int64(len(s.index))); err != nil { return l, err } l += 8 - n, err := io.Copy(w, io.NewSectionReader(s.index, 0, sz)) + n, err := w.Write(s.index) return l + uint64(n), err } -// Unmarshal decodes the index from its serial form. -// Deprecated: This function is slurpy and will copy the index in memory. func (s *singleWidthIndex) Unmarshal(r io.Reader) error { var width uint32 if err := binary.Read(r, binary.LittleEndian, &width); err != nil { + if err == io.EOF { + return io.ErrUnexpectedEOF + } return err } var dataLen uint64 if err := binary.Read(r, binary.LittleEndian, &dataLen); err != nil { + if err == io.EOF { + return io.ErrUnexpectedEOF + } return err } @@ -89,26 +86,10 @@ func (s *singleWidthIndex) Unmarshal(r io.Reader) error { if _, err := io.ReadFull(r, buf); err != nil { return err } - s.index = bytes.NewReader(buf) + s.index = buf return nil } -func (s *singleWidthIndex) UnmarshalLazyRead(r io.ReaderAt) (indexSize int64, err error) { - var b [12]byte - _, err = internalio.FullReadAt(r, b[:], 0) - if err != nil { - return 0, err - } - - width := binary.LittleEndian.Uint32(b[:4]) - dataLen := binary.LittleEndian.Uint64(b[4:12]) - if err := s.checkUnmarshalLengths(width, dataLen, uint64(len(b))); err != nil { - return 0, err - } - s.index = io.NewSectionReader(r, int64(len(b)), int64(dataLen)) - return int64(dataLen) + int64(len(b)), nil -} - func (s *singleWidthIndex) checkUnmarshalLengths(width uint32, dataLen, extra uint64) error { if width <= 8 { return errors.New("malformed index; width must be bigger than 8") @@ -129,6 +110,10 @@ func (s *singleWidthIndex) checkUnmarshalLengths(width uint32, dataLen, extra ui return nil } +func (s *singleWidthIndex) Less(i int, digest []byte) bool { + return bytes.Compare(digest[:], s.index[i*int(s.width):((i+1)*int(s.width)-8)]) <= 0 +} + func (s *singleWidthIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { d, err := multihash.Decode(c.Hash()) if err != nil { @@ -138,35 +123,18 @@ func (s *singleWidthIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { } func (s *singleWidthIndex) getAll(d []byte, fn func(uint64) bool) error { - digestLen := int64(s.width) - 8 - b := make([]byte, digestLen) - idxI, err := errsort.Search(int(s.len), func(i int) (bool, error) { - digestStart := int64(i) * int64(s.width) - _, err := internalio.FullReadAt(s.index, b, digestStart) - if err != nil { - return false, err - } - return bytes.Compare(d, b) <= 0, nil + idx := sort.Search(int(s.len), func(i int) bool { + return s.Less(i, d) }) - if err != nil { - return err - } - idx := int64(idxI) var any bool for ; uint64(idx) < s.len; idx++ { - digestStart := idx * int64(s.width) - offsetEnd := digestStart + int64(s.width) + digestStart := idx * int(s.width) + offsetEnd := (idx + 1) * int(s.width) digestEnd := offsetEnd - 8 - digestLen := digestEnd - digestStart - b := make([]byte, offsetEnd-digestStart) - _, err := internalio.FullReadAt(s.index, b, digestStart) - if err != nil { - return err - } - if bytes.Equal(d, b[:digestLen]) { + if bytes.Equal(d[:], s.index[digestStart:digestEnd]) { any = true - offset := binary.LittleEndian.Uint64(b[digestLen:]) + offset := binary.LittleEndian.Uint64(s.index[digestEnd:offsetEnd]) if !fn(offset) { // User signalled to stop searching; therefore, break. break @@ -200,19 +168,13 @@ func (s *singleWidthIndex) Load(items []Record) error { } func (s *singleWidthIndex) forEachDigest(f func(digest []byte, offset uint64) error) error { - segmentCount := s.index.Size() / int64(s.width) - for i := int64(0); i < segmentCount; i++ { - digestStart := i * int64(s.width) - offsetEnd := digestStart + int64(s.width) + segmentCount := len(s.index) / int(s.width) + for i := 0; i < segmentCount; i++ { + digestStart := i * int(s.width) + offsetEnd := (i + 1) * int(s.width) digestEnd := offsetEnd - 8 - digestLen := digestEnd - digestStart - b := make([]byte, offsetEnd-digestStart) - _, err := internalio.FullReadAt(s.index, b, digestStart) - if err != nil { - return err - } - digest := b[:digestLen] - offset := binary.LittleEndian.Uint64(b[digestLen:]) + digest := s.index[digestStart:digestEnd] + offset := binary.LittleEndian.Uint64(s.index[digestEnd:offsetEnd]) if err := f(digest, offset); err != nil { return err } @@ -265,49 +227,38 @@ func (m *multiWidthIndex) Marshal(w io.Writer) (uint64, error) { } func (m *multiWidthIndex) Unmarshal(r io.Reader) error { + reader := internalio.ToByteReadSeeker(r) var l int32 - if err := binary.Read(r, binary.LittleEndian, &l); err != nil { - return err - } - for i := 0; i < int(l); i++ { - s := singleWidthIndex{} - if err := s.Unmarshal(r); err != nil { - return err + if err := binary.Read(reader, binary.LittleEndian, &l); err != nil { + if err == io.EOF { + return io.ErrUnexpectedEOF } - (*m)[s.width] = s + return err } - return nil -} - -func (m *multiWidthIndex) UnmarshalLazyRead(r io.ReaderAt) (sum int64, err error) { - var b [4]byte - _, err = internalio.FullReadAt(r, b[:], 0) + sum, err := reader.Seek(0, io.SeekCurrent) if err != nil { - return 0, err + return err } - count := binary.LittleEndian.Uint32(b[:4]) - if int32(count) < 0 { - return 0, errors.New("index too big; multiWidthIndex count is overflowing int32") + if int32(l) < 0 { + return errors.New("index too big; multiWidthIndex count is overflowing int32") } - sum += int64(len(b)) - for ; count > 0; count-- { + for i := 0; i < int(l); i++ { s := singleWidthIndex{} - or, err := internalio.NewOffsetReadSeekerWithError(r, sum) - if err != nil { - return 0, err + if err := s.Unmarshal(r); err != nil { + return err } - n, err := s.UnmarshalLazyRead(or) + n, err := reader.Seek(0, io.SeekCurrent) if err != nil { - return 0, err + return err } oldSum := sum sum += n if sum < oldSum { - return 0, errors.New("index too big; multiWidthIndex len is overflowing int64") + return errors.New("index too big; multiWidthIndex len is overflowing int64") } (*m)[s.width] = s } - return sum, nil + return nil } func (m *multiWidthIndex) Load(items []Record) error { @@ -339,17 +290,13 @@ func (m *multiWidthIndex) Load(items []Record) error { s := singleWidthIndex{ width: uint32(rcrdWdth), len: uint64(len(lst)), - index: bytes.NewReader(compact), + index: compact, } (*m)[uint32(width)+8] = s } return nil } -func (m *multiWidthIndex) ForEach(func(multihash.Multihash, uint64) error) error { - return fmt.Errorf("%s does not support ForEach enumeration; use %s instead", multicodec.CarIndexSorted, multicodec.CarMultihashIndexSorted) -} - func (m *multiWidthIndex) forEachDigest(f func(digest []byte, offset uint64) error) error { sizes := make([]uint32, 0, len(*m)) for k := range *m { diff --git a/ipld/car/v2/index/indexsorted_test.go b/ipld/car/v2/index/indexsorted_test.go index 8e1a452729..5c1ee44956 100644 --- a/ipld/car/v2/index/indexsorted_test.go +++ b/ipld/car/v2/index/indexsorted_test.go @@ -1,27 +1,14 @@ package index import ( - "bytes" "encoding/binary" "testing" "github.com/ipfs/go-merkledag" "github.com/multiformats/go-multicodec" - "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" ) -func TestSortedIndex_ErrorsOnForEach(t *testing.T) { - subject, err := New(multicodec.CarIndexSorted) - require.NoError(t, err) - err = subject.ForEach(func(multihash.Multihash, uint64) error { return nil }) - require.Error(t, err) - require.Equal(t, - "car-index-sorted does not support ForEach enumeration; use car-multihash-index-sorted instead", - err.Error(), - ) -} - func TestSortedIndexCodec(t *testing.T) { require.Equal(t, multicodec.CarIndexSorted, newSorted().Codec()) } @@ -64,7 +51,7 @@ func TestSingleWidthIndex_GetAll(t *testing.T) { subject := &singleWidthIndex{ width: 9, len: uint64(l), - index: bytes.NewReader(buf), + index: buf, } var foundCount int diff --git a/ipld/car/v2/index/mhindexsorted.go b/ipld/car/v2/index/mhindexsorted.go index 0200f70076..e0ef675de3 100644 --- a/ipld/car/v2/index/mhindexsorted.go +++ b/ipld/car/v2/index/mhindexsorted.go @@ -3,17 +3,18 @@ package index import ( "encoding/binary" "errors" + internalio "github.com/ipld/go-car/v2/internal/io" "io" "sort" "github.com/ipfs/go-cid" - internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" ) var ( - _ Index = (*MultihashIndexSorted)(nil) + _ Index = (*MultihashIndexSorted)(nil) + _ IterableIndex = (*MultihashIndexSorted)(nil) ) type ( @@ -42,34 +43,14 @@ func (m *multiWidthCodedIndex) Marshal(w io.Writer) (uint64, error) { func (m *multiWidthCodedIndex) Unmarshal(r io.Reader) error { if err := binary.Read(r, binary.LittleEndian, &m.code); err != nil { + if err == io.EOF { + return io.ErrUnexpectedEOF + } return err } return m.multiWidthIndex.Unmarshal(r) } -func (m *multiWidthCodedIndex) UnmarshalLazyRead(r io.ReaderAt) (int64, error) { - var b [8]byte - _, err := internalio.FullReadAt(r, b[:], 0) - if err != nil { - return 0, err - } - m.code = binary.LittleEndian.Uint64(b[:8]) - rdr, err := internalio.NewOffsetReadSeekerWithError(r, int64(len(b))) - if err != nil { - return 0, err - } - sum, err := m.multiWidthIndex.UnmarshalLazyRead(rdr) - if err != nil { - return 0, err - } - oldSum := sum - sum += int64(len(b)) - if sum < oldSum { - return 0, errors.New("index too big; multiWidthCodedIndex len is overflowing") - } - return sum, nil -} - func (m *multiWidthCodedIndex) forEach(f func(mh multihash.Multihash, offset uint64) error) error { return m.multiWidthIndex.forEachDigest(func(digest []byte, offset uint64) error { mh, err := multihash.Encode(digest, m.code) @@ -117,49 +98,38 @@ func (m *MultihashIndexSorted) sortedMultihashCodes() []uint64 { } func (m *MultihashIndexSorted) Unmarshal(r io.Reader) error { + reader := internalio.ToByteReadSeeker(r) var l int32 - if err := binary.Read(r, binary.LittleEndian, &l); err != nil { - return err - } - for i := 0; i < int(l); i++ { - mwci := newMultiWidthCodedIndex() - if err := mwci.Unmarshal(r); err != nil { - return err + if err := binary.Read(reader, binary.LittleEndian, &l); err != nil { + if err == io.EOF { + return io.ErrUnexpectedEOF } - m.put(mwci) + return err } - return nil -} - -func (m *MultihashIndexSorted) UnmarshalLazyRead(r io.ReaderAt) (sum int64, err error) { - var b [4]byte - _, err = internalio.FullReadAt(r, b[:], 0) + sum, err := reader.Seek(0, io.SeekCurrent) if err != nil { - return 0, err + return err } - sum += int64(len(b)) - count := binary.LittleEndian.Uint32(b[:4]) - if int32(count) < 0 { - return 0, errors.New("index too big; MultihashIndexSorted count is overflowing int32") + if int32(l) < 0 { + return errors.New("index too big; MultihashIndexSorted count is overflowing int32") } - for ; count > 0; count-- { + for i := 0; i < int(l); i++ { mwci := newMultiWidthCodedIndex() - or, err := internalio.NewOffsetReadSeekerWithError(r, sum) - if err != nil { - return 0, err + if err := mwci.Unmarshal(r); err != nil { + return err } - n, err := mwci.UnmarshalLazyRead(or) + n, err := reader.Seek(0, io.SeekCurrent) if err != nil { - return 0, err + return err } oldSum := sum sum += n if sum < oldSum { - return 0, errors.New("index too big; MultihashIndexSorted sum is overflowing int64") + return errors.New("index too big; MultihashIndexSorted len is overflowing int64") } m.put(mwci) } - return sum, nil + return nil } func (m *MultihashIndexSorted) put(mwci *multiWidthCodedIndex) { diff --git a/ipld/car/v2/index/mhindexsorted_test.go b/ipld/car/v2/index/mhindexsorted_test.go index 7704d3a23a..520157e447 100644 --- a/ipld/car/v2/index/mhindexsorted_test.go +++ b/ipld/car/v2/index/mhindexsorted_test.go @@ -53,11 +53,14 @@ func TestMultiWidthCodedIndex_StableIterate(t *testing.T) { records = append(records, generateIndexRecords(t, multihash.IDENTITY, rng)...) // Create a new mh sorted index and load randomly generated records into it. - subject, err := index.New(multicodec.CarMultihashIndexSorted) + idx, err := index.New(multicodec.CarMultihashIndexSorted) require.NoError(t, err) - err = subject.Load(records) + err = idx.Load(records) require.NoError(t, err) + subject, ok := idx.(index.IterableIndex) + require.True(t, ok) + mh := make([]multihash.Multihash, 0, len(records)) require.NoError(t, subject.ForEach(func(m multihash.Multihash, _ uint64) error { mh = append(mh, m) diff --git a/ipld/car/v2/index/testutil/equal_index.go b/ipld/car/v2/index/testutil/equal_index.go deleted file mode 100644 index 43d0b3e91a..0000000000 --- a/ipld/car/v2/index/testutil/equal_index.go +++ /dev/null @@ -1,71 +0,0 @@ -package testutil - -import ( - "sync" - "testing" - - "github.com/multiformats/go-multihash" - "github.com/stretchr/testify/require" -) - -type Index interface { - ForEach(func(multihash.Multihash, uint64) error) error -} - -// insertUint64 perform one round of insertion sort on the last element -func insertUint64(s []uint64) { - switch len(s) { - case 0, 1: - return - default: - cur := s[len(s)-1] - for j := len(s) - 1; j > 0; { - j-- - if cur >= s[j] { - s[j+1] = cur - break - } - s[j+1] = s[j] - } - } -} - -func AssertIdenticalIndexes(t *testing.T, a, b Index) { - var wg sync.WaitGroup - // key is multihash.Multihash.HexString - var aCount uint - var aErr error - aMap := make(map[string][]uint64) - wg.Add(1) - - go func() { - defer wg.Done() - aErr = a.ForEach(func(mh multihash.Multihash, off uint64) error { - aCount++ - str := mh.HexString() - slice := aMap[str] - slice = append(slice, off) - insertUint64(slice) - aMap[str] = slice - return nil - }) - }() - - var bCount uint - bMap := make(map[string][]uint64) - bErr := b.ForEach(func(mh multihash.Multihash, off uint64) error { - bCount++ - str := mh.HexString() - slice := bMap[str] - slice = append(slice, off) - insertUint64(slice) - bMap[str] = slice - return nil - }) - wg.Wait() - require.NoError(t, aErr) - require.NoError(t, bErr) - - require.Equal(t, aCount, bCount) - require.Equal(t, aMap, bMap) -} diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 33ba7800fd..b0b87453ec 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -209,10 +209,18 @@ func ReadOrGenerateIndex(rs io.ReadSeeker, opts ...Option) (index.Index, error) } // If index is present, then no need to generate; decode and return it. if v2r.Header.HasIndex() { - return index.ReadFrom(v2r.IndexReader()) + ir, err := v2r.IndexReader() + if err != nil { + return nil, err + } + return index.ReadFrom(ir) } // Otherwise, generate index from CARv1 payload wrapped within CARv2 format. - return GenerateIndex(v2r.DataReader(), opts...) + dr, err := v2r.DataReader() + if err != nil { + return nil, err + } + return GenerateIndex(dr, opts...) default: return nil, fmt.Errorf("unknown version %v", version) } diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 43a9c2acab..11011e81a7 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -9,7 +9,6 @@ import ( "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" - "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" @@ -48,7 +47,9 @@ func TestGenerateIndex(t *testing.T) { t.Cleanup(func() { assert.NoError(t, v2.Close()) }) reader, err := carv2.NewReader(v2) require.NoError(t, err) - want, err := index.ReadFrom(reader.IndexReader()) + ir, err := reader.IndexReader() + require.NoError(t, err) + want, err := index.ReadFrom(ir) require.NoError(t, err) return want }, @@ -104,7 +105,7 @@ func TestGenerateIndex(t *testing.T) { if want == nil { require.Nil(t, got) } else { - testutil.AssertIdenticalIndexes(t, want, got) + require.Equal(t, want, got) } } } diff --git a/ipld/car/v2/internal/io/fullReaderAt.go b/ipld/car/v2/internal/io/fullReaderAt.go deleted file mode 100644 index 57f2668590..0000000000 --- a/ipld/car/v2/internal/io/fullReaderAt.go +++ /dev/null @@ -1,20 +0,0 @@ -package io - -import "io" - -func FullReadAt(r io.ReaderAt, b []byte, off int64) (sum int64, err error) { - for int64(len(b)) > sum { - n, err := r.ReadAt(b[sum:], off+sum) - sum += int64(n) - if err != nil { - if err == io.EOF { - if sum < int64(len(b)) { - return sum, io.ErrUnexpectedEOF - } - return sum, nil - } - return sum, err - } - } - return sum, nil -} diff --git a/ipld/car/v2/internal/io/offset_read_seeker.go b/ipld/car/v2/internal/io/offset_read_seeker.go index bbdcf4c63d..b3899ab784 100644 --- a/ipld/car/v2/internal/io/offset_read_seeker.go +++ b/ipld/car/v2/internal/io/offset_read_seeker.go @@ -35,15 +35,7 @@ type ReadSeekerAt interface { // NewOffsetReadSeeker returns an ReadSeekerAt that reads from r // starting offset offset off and stops with io.EOF when r reaches its end. // The Seek function will panic if whence io.SeekEnd is passed. -func NewOffsetReadSeeker(r io.ReaderAt, off int64) ReadSeekerAt { - nr, err := NewOffsetReadSeekerWithError(r, off) - if err != nil { - return erroringReader{err} - } - return nr -} - -func NewOffsetReadSeekerWithError(r io.ReaderAt, off int64) (ReadSeekerAt, error) { +func NewOffsetReadSeeker(r io.ReaderAt, off int64) (ReadSeekerAt, error) { if or, ok := r.(*offsetReadSeeker); ok { oldBase := or.base newBase := or.base + off @@ -128,23 +120,3 @@ func (o *offsetReadSeeker) Seek(offset int64, whence int) (int64, error) { func (o *offsetReadSeeker) Position() int64 { return o.off - o.base } - -type erroringReader struct { - err error -} - -func (e erroringReader) Read(_ []byte) (int, error) { - return 0, e.err -} - -func (e erroringReader) ReadAt(_ []byte, n int64) (int, error) { - return 0, e.err -} - -func (e erroringReader) ReadByte() (byte, error) { - return 0, e.err -} - -func (e erroringReader) Seek(_ int64, _ int) (int64, error) { - return 0, e.err -} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index c34a0672d5..4628fd897e 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -56,8 +56,10 @@ func NewReader(r io.ReaderAt, opts ...Option) (*Reader, error) { } cr.opts = ApplyOptions(opts...) - or := internalio.NewOffsetReadSeeker(r, 0) - var err error + or, err := internalio.NewOffsetReadSeeker(r, 0) + if err != nil { + return nil, err + } cr.Version, err = ReadVersion(or, opts...) if err != nil { return nil, err @@ -82,7 +84,11 @@ func (r *Reader) Roots() ([]cid.Cid, error) { if r.roots != nil { return r.roots, nil } - header, err := carv1.ReadHeader(r.DataReader(), r.opts.MaxAllowedHeaderSize) + dr, err := r.DataReader() + if err != nil { + return nil, err + } + header, err := carv1.ReadHeader(dr, r.opts.MaxAllowedHeaderSize) if err != nil { return nil, err } @@ -106,9 +112,9 @@ type SectionReader interface { } // DataReader provides a reader containing the data payload in CARv1 format. -func (r *Reader) DataReader() SectionReader { +func (r *Reader) DataReader() (SectionReader, error) { if r.Version == 2 { - return io.NewSectionReader(r.r, int64(r.Header.DataOffset), int64(r.Header.DataSize)) + return io.NewSectionReader(r.r, int64(r.Header.DataOffset), int64(r.Header.DataSize)), nil } return internalio.NewOffsetReadSeeker(r.r, 0) } @@ -116,9 +122,9 @@ func (r *Reader) DataReader() SectionReader { // IndexReader provides an io.Reader containing the index for the data payload if the index is // present. Otherwise, returns nil. // Note, this function will always return nil if the backing payload represents a CARv1. -func (r *Reader) IndexReader() io.ReaderAt { +func (r *Reader) IndexReader() (io.Reader, error) { if r.Version == 1 || !r.Header.HasIndex() { - return nil + return nil, nil } return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) } @@ -139,7 +145,6 @@ type Stats struct { MaxBlockLength uint64 MinBlockLength uint64 IndexCodec multicodec.Code - IndexSize uint64 } // Inspect does a quick scan of a CAR, performing basic validation of the format @@ -201,7 +206,10 @@ func (r *Reader) Inspect(validateBlockHash bool) (Stats, error) { var minCidLength uint64 = math.MaxUint64 var minBlockLength uint64 = math.MaxUint64 - dr := r.DataReader() + dr, err := r.DataReader() + if err != nil { + return Stats{}, err + } bdr := internalio.ToByteReader(dr) // read roots, not using Roots(), because we need the offset setup in the data trader @@ -327,14 +335,15 @@ func (r *Reader) Inspect(validateBlockHash bool) (Stats, error) { } if stats.Version != 1 && stats.Header.HasIndex() { - // performs an UnmarshalLazyRead which should have its own validation and - // is intended to be a fast initial scan - ind, size, err := index.ReadFromWithSize(r.IndexReader()) + idxr, err := r.IndexReader() + if err != nil { + return Stats{}, err + } + idx, err := index.ReadFrom(idxr) if err != nil { return Stats{}, err } - stats.IndexCodec = ind.Codec() - stats.IndexSize = uint64(size) + stats.IndexCodec = idx.Codec() } return stats, nil diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index ed2b78e25b..cdeb74d756 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -11,7 +11,6 @@ import ( "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" - "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" "github.com/multiformats/go-multicodec" "github.com/stretchr/testify/require" @@ -141,7 +140,9 @@ func TestReader_WithCarV1Consistency(t *testing.T) { gotRoots, err := subject.Roots() require.NoError(t, err) require.Equal(t, wantReader.Header.Roots, gotRoots) - require.Nil(t, subject.IndexReader()) + ir, err := subject.IndexReader() + require.Nil(t, ir) + require.NoError(t, err) }) } } @@ -173,13 +174,16 @@ func TestReader_WithCarV2Consistency(t *testing.T) { require.NoError(t, err) require.Equal(t, wantReader.Header.Roots, gotRoots) - gotIndexReader := subject.IndexReader() + gotIndexReader, err := subject.IndexReader() + require.NoError(t, err) require.NotNil(t, gotIndexReader) gotIndex, err := index.ReadFrom(gotIndexReader) require.NoError(t, err) - wantIndex, err := carv2.GenerateIndex(subject.DataReader()) + dr, err := subject.DataReader() require.NoError(t, err) - testutil.AssertIdenticalIndexes(t, wantIndex, gotIndex) + wantIndex, err := carv2.GenerateIndex(dr) + require.NoError(t, err) + require.Equal(t, wantIndex, gotIndex) }) } } @@ -187,13 +191,15 @@ func TestReader_WithCarV2Consistency(t *testing.T) { func TestOpenReader_DoesNotPanicForReadersCreatedBeforeClosure(t *testing.T) { subject, err := carv2.OpenReader("testdata/sample-wrapped-v2.car") require.NoError(t, err) - dReaderBeforeClosure := subject.DataReader() - iReaderBeforeClosure := subject.IndexReader() + dReaderBeforeClosure, err := subject.DataReader() + require.NoError(t, err) + iReaderBeforeClosure, err := subject.IndexReader() + require.NoError(t, err) require.NoError(t, subject.Close()) buf := make([]byte, 1) - panicTest := func(r io.ReaderAt) { - _, err := r.ReadAt(buf, 0) + panicTest := func(r io.Reader) { + _, err := r.Read(buf) require.EqualError(t, err, "mmap: closed") } @@ -205,12 +211,14 @@ func TestOpenReader_DoesNotPanicForReadersCreatedAfterClosure(t *testing.T) { subject, err := carv2.OpenReader("testdata/sample-wrapped-v2.car") require.NoError(t, err) require.NoError(t, subject.Close()) - dReaderAfterClosure := subject.DataReader() - iReaderAfterClosure := subject.IndexReader() + dReaderAfterClosure, err := subject.DataReader() + require.NoError(t, err) + iReaderAfterClosure, err := subject.IndexReader() + require.NoError(t, err) buf := make([]byte, 1) - panicTest := func(r io.ReaderAt) { - _, err := r.ReadAt(buf, 0) + panicTest := func(r io.Reader) { + _, err := r.Read(buf) require.EqualError(t, err, "mmap: closed") } @@ -237,7 +245,9 @@ func TestReader_ReturnsNilWhenThereIsNoIndex(t *testing.T) { subject, err := carv2.OpenReader(tt.path) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, subject.Close()) }) - require.Nil(t, subject.IndexReader()) + ir, err := subject.IndexReader() + require.NoError(t, err) + require.Nil(t, ir) }) } } @@ -365,7 +375,6 @@ func TestInspect(t *testing.T) { MaxBlockLength: 9, MinBlockLength: 4, IndexCodec: multicodec.CarMultihashIndexSorted, - IndexSize: 148, }, }, // same as CarV1 but with a zero-byte EOF to test options diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 1bf3ca3d98..12dcd6a9c8 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/ipld/go-car/v2/index" - "github.com/ipld/go-car/v2/index/testutil" "github.com/ipld/go-car/v2/internal/carv1" "github.com/stretchr/testify/require" @@ -48,16 +47,20 @@ func TestWrapV1(t *testing.T) { require.NoError(t, err) wantPayload, err := ioutil.ReadAll(sf) require.NoError(t, err) - gotPayload, err := ioutil.ReadAll(subject.DataReader()) + dr, err := subject.DataReader() + require.NoError(t, err) + gotPayload, err := ioutil.ReadAll(dr) require.NoError(t, err) require.Equal(t, wantPayload, gotPayload) // Assert embedded index in CARv2 is same as index generated from the original CARv1. wantIdx, err := GenerateIndexFromFile(src) require.NoError(t, err) - gotIdx, err := index.ReadFrom(subject.IndexReader()) + ir, err := subject.IndexReader() + require.NoError(t, err) + gotIdx, err := index.ReadFrom(ir) require.NoError(t, err) - testutil.AssertIdenticalIndexes(t, wantIdx, gotIdx) + require.Equal(t, wantIdx, gotIdx) } func TestExtractV1(t *testing.T) { From da2c8b780df421260dd7b2523428d360b3b8b67e Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 6 Jul 2022 09:32:31 +0100 Subject: [PATCH 239/291] Revert changes to `insertionindex` Revert changes to serialization of `insertionindex` postponed until the streaming index work stream. This commit was moved from ipld/go-car@fb7948544918fbbb5bbd1a9461ce1fbbfde2e597 --- ipld/car/v2/blockstore/insertionindex.go | 42 +++++++++++++++++++----- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/ipld/car/v2/blockstore/insertionindex.go b/ipld/car/v2/blockstore/insertionindex.go index 192eb5c34a..1e480b3f93 100644 --- a/ipld/car/v2/blockstore/insertionindex.go +++ b/ipld/car/v2/blockstore/insertionindex.go @@ -2,6 +2,7 @@ package blockstore import ( "bytes" + "encoding/binary" "errors" "fmt" "io" @@ -11,6 +12,7 @@ import ( "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" "github.com/petar/GoLLRB/llrb" + cbor "github.com/whyrusleeping/cbor/go" ) // This index is intended to be efficient for random-access, in-memory lookups @@ -106,7 +108,37 @@ func (ii *insertionIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { } func (ii *insertionIndex) Marshal(w io.Writer) (uint64, error) { - return 0, fmt.Errorf("unimplemented, index type not intended for serialization") + l := uint64(0) + if err := binary.Write(w, binary.LittleEndian, int64(ii.items.Len())); err != nil { + return l, err + } + l += 8 + + var err error + iter := func(i llrb.Item) bool { + if err = cbor.Encode(w, i.(recordDigest).Record); err != nil { + return false + } + return true + } + ii.items.AscendGreaterOrEqual(ii.items.Min(), iter) + return l, err +} + +func (ii *insertionIndex) Unmarshal(r io.Reader) error { + var length int64 + if err := binary.Read(r, binary.LittleEndian, &length); err != nil { + return err + } + d := cbor.NewDecoder(r) + for i := int64(0); i < length; i++ { + var rec index.Record + if err := d.Decode(&rec); err != nil { + return err + } + ii.items.InsertNoReplace(newRecordDigest(rec)) + } + return nil } func (ii *insertionIndex) ForEach(f func(multihash.Multihash, uint64) error) error { @@ -123,14 +155,6 @@ func (ii *insertionIndex) ForEach(f func(multihash.Multihash, uint64) error) err return errr } -func (ii *insertionIndex) Unmarshal(r io.Reader) error { - return fmt.Errorf("unimplemented, index type not intended for deserialization") -} - -func (ii *insertionIndex) UnmarshalLazyRead(r io.ReaderAt) (int64, error) { - return 0, fmt.Errorf("unimplemented, index type not intended for deserialization") -} - func (ii *insertionIndex) Codec() multicodec.Code { return insertionIndexCodec } From 7503800fb67efe91633c075454d1799f754da5d1 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Wed, 6 Jul 2022 10:42:52 +0200 Subject: [PATCH 240/291] ci: remove the reverted FuzzIndex fuzzer This commit was moved from ipld/go-car@6d5bd66ccd7a960bc93ac27de65985da9849948a --- ipld/car/.github/workflows/go-fuzz.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/.github/workflows/go-fuzz.yml b/ipld/car/.github/workflows/go-fuzz.yml index cb7b4b2724..830fc9ec29 100644 --- a/ipld/car/.github/workflows/go-fuzz.yml +++ b/ipld/car/.github/workflows/go-fuzz.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: true matrix: - target: [ "BlockReader", "Reader", "Index", "Inspect" ] + target: [ "BlockReader", "Reader", "Inspect" ] runs-on: ubuntu-latest name: Fuzz V2 ${{ matrix.target }} steps: From bbcd79c8fa2a3f2e31b773d79f9c59fa6857672c Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 6 Jul 2022 16:27:40 +0100 Subject: [PATCH 241/291] Empty identity CID should be indexed when options are set There is an edge-case where if the storing identity CIDs are enabled in a CARv2, one could technically store an empty identity CID which ends up with a `singlewidthindex` width of 8. Such CAR files indeed exist out there and the validation changes introduced in `2.4.0` means such CAR file indices are no longer readable , even if regenerated. The question is should this be considered a valid index/readable index? This commit was moved from ipld/go-car@b2e14f01b32855928ef6fd1b8cd5148622e3662d --- ipld/car/v2/blockstore/readwrite_test.go | 50 ++++++++++++++++++++++++ ipld/car/v2/index/indexsorted.go | 4 +- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 11cc99a220..22525e7896 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "math/rand" "os" + "path" "path/filepath" "sync" "testing" @@ -943,3 +944,52 @@ func TestReadWrite_ReWritingCARv1WithIdentityCidIsIdenticalToOriginalWithOptions require.Equal(t, wantWritten, gotWritten) require.Equal(t, wantSum, gotSum) } + +func TestBlockstore_IdentityCidWithEmptyDataIsIndexed(t *testing.T) { + p := path.Join(t.TempDir(), "car-id-cid-empty.carv2") + var noData []byte + + mh, err := multihash.Sum(noData, multihash.IDENTITY, -1) + require.NoError(t, err) + w, err := blockstore.OpenReadWrite(p, nil, carv2.StoreIdentityCIDs(true)) + require.NoError(t, err) + + blk, err := blocks.NewBlockWithCid(noData, cid.NewCidV1(cid.Raw, mh)) + require.NoError(t, err) + + err = w.Put(context.TODO(), blk) + require.NoError(t, err) + require.NoError(t, w.Finalize()) + + r, err := carv2.OpenReader(p) + require.NoError(t, err) + defer func() { require.NoError(t, r.Close()) }() + + dr, err := r.DataReader() + require.NoError(t, err) + header, err := carv1.ReadHeader(dr, carv1.DefaultMaxAllowedHeaderSize) + require.NoError(t, err) + wantOffset, err := carv1.HeaderSize(header) + require.NoError(t, err) + + ir, err := r.IndexReader() + require.NoError(t, err) + idx, err := index.ReadFrom(ir) + require.NoError(t, err) + + itidx, ok := idx.(index.IterableIndex) + require.True(t, ok) + var count int + err = itidx.ForEach(func(m multihash.Multihash, u uint64) error { + dm, err := multihash.Decode(m) + require.NoError(t, err) + require.Equal(t, multicodec.Identity, multicodec.Code(dm.Code)) + require.Equal(t, 0, dm.Length) + require.Empty(t, dm.Digest) + require.Equal(t, wantOffset, u) + count++ + return nil + }) + require.NoError(t, err) + require.Equal(t, 1, count) +} diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index aeed4c11ce..ed94ed8f73 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -91,8 +91,8 @@ func (s *singleWidthIndex) Unmarshal(r io.Reader) error { } func (s *singleWidthIndex) checkUnmarshalLengths(width uint32, dataLen, extra uint64) error { - if width <= 8 { - return errors.New("malformed index; width must be bigger than 8") + if width < 8 { + return errors.New("malformed index; width must be at least 8") } const maxWidth = 32 << 20 // 32MiB, to ~match the go-cid maximum if width > maxWidth { From 5494ea899a8a99ffe21fa0791af1abc4786b374e Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 7 Jul 2022 11:44:47 +0100 Subject: [PATCH 242/291] Upgrade to the latest `go-car/v2` Upgrade to the latest `go-car/v2` and reflect changes in tests. This commit was moved from ipld/go-car@771fb74175cdd1d25a45ca2adcdbc35e65cabcfb --- ipld/car/cmd/car/detach.go | 6 +++++- ipld/car/cmd/car/get.go | 5 +++-- ipld/car/cmd/car/index.go | 11 +++++++++-- ipld/car/cmd/car/testdata/script/get-block.txt | 2 +- ipld/car/cmd/car/verify.go | 6 +++++- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/ipld/car/cmd/car/detach.go b/ipld/car/cmd/car/detach.go index da0c236b53..e04eba9dd5 100644 --- a/ipld/car/cmd/car/detach.go +++ b/ipld/car/cmd/car/detach.go @@ -32,7 +32,11 @@ func DetachCar(c *cli.Context) error { } defer outStream.Close() - _, err = io.Copy(outStream, r.IndexReader()) + ir, err := r.IndexReader() + if err != nil { + return err + } + _, err = io.Copy(outStream, ir) return err } diff --git a/ipld/car/cmd/car/get.go b/ipld/car/cmd/car/get.go index 84531b7a66..f5d5b1cdfa 100644 --- a/ipld/car/cmd/car/get.go +++ b/ipld/car/cmd/car/get.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io" "os" @@ -16,7 +17,7 @@ import ( _ "github.com/ipld/go-ipld-prime/codec/raw" "github.com/ipfs/go-cid" - ipfsbs "github.com/ipfs/go-ipfs-blockstore" + ipldfmt "github.com/ipfs/go-ipld-format" "github.com/ipfs/go-unixfsnode" "github.com/ipld/go-car" "github.com/ipld/go-car/v2/blockstore" @@ -135,7 +136,7 @@ func writeCarV2(ctx context.Context, rootCid cid.Cid, output string, bs *blockst if cl, ok := l.(cidlink.Link); ok { blk, err := bs.Get(ctx, cl.Cid) if err != nil { - if err == ipfsbs.ErrNotFound { + if ipldfmt.IsNotFound(err) { if strict { return nil, err } diff --git a/ipld/car/cmd/car/index.go b/ipld/car/cmd/car/index.go index 8fd2f7e87d..031df2ada8 100644 --- a/ipld/car/cmd/car/index.go +++ b/ipld/car/cmd/car/index.go @@ -36,7 +36,11 @@ func IndexCar(c *cli.Context) error { } defer outStream.Close() - _, err := io.Copy(outStream, r.DataReader()) + dr, err := r.DataReader() + if err != nil { + return err + } + _, err = io.Copy(outStream, dr) return err } @@ -65,7 +69,10 @@ func IndexCar(c *cli.Context) error { } defer outStream.Close() - v1r := r.DataReader() + v1r, err := r.DataReader() + if err != nil { + return err + } if r.Version == 1 { fi, err := os.Stat(c.Args().Get(0)) diff --git a/ipld/car/cmd/car/testdata/script/get-block.txt b/ipld/car/cmd/car/testdata/script/get-block.txt index 1e5fd7c27b..3f22cef1f3 100644 --- a/ipld/car/cmd/car/testdata/script/get-block.txt +++ b/ipld/car/cmd/car/testdata/script/get-block.txt @@ -16,4 +16,4 @@ cmp stdout ${INPUTS}/${SAMPLE_CID}.block # "get-block" on a missing CID. ! car get-block ${INPUTS}/sample-v1.car ${MISSING_CID} -stderr 'block not found' +stderr 'bafy2bzacebohz654namrgmwjjx4qmtwgxixsd7pn4tlanyrc3g3hwj75xxxxw not found' diff --git a/ipld/car/cmd/car/verify.go b/ipld/car/cmd/car/verify.go index 3a43de938e..faee3aa36d 100644 --- a/ipld/car/cmd/car/verify.go +++ b/ipld/car/cmd/car/verify.go @@ -86,7 +86,11 @@ func VerifyCar(c *cli.Context) error { // index if rx.Version == 2 && rx.Header.HasIndex() { - idx, err := index.ReadFrom(rx.IndexReader()) + ir, err := rx.IndexReader() + if err != nil { + return err + } + idx, err := index.ReadFrom(ir) if err != nil { return err } From 8af2916814bf67f78a775b37b6dcd9f433ca0a7b Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 12 Jul 2022 15:34:13 +0000 Subject: [PATCH 243/291] Only read index codec during inspection Instead of fully reading the index, only peak the index codec until streaming index read is implemented. This would offer a safer `Inspect()` call but with the tradeoff that overflow in index size is no longer detected. Hence the tests that are now removed. This commit was moved from ipld/go-car@903a2b0ee800a1921cc9b07f4fea6008a9ba4ce4 --- ipld/car/v2/reader.go | 3 +-- ipld/car/v2/reader_test.go | 24 ------------------------ 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 4628fd897e..f29469a027 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -339,11 +339,10 @@ func (r *Reader) Inspect(validateBlockHash bool) (Stats, error) { if err != nil { return Stats{}, err } - idx, err := index.ReadFrom(idxr) + stats.IndexCodec, err = index.ReadCodec(idxr) if err != nil { return Stats{}, err } - stats.IndexCodec = idx.Codec() } return stats, nil diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index cdeb74d756..5eaff8d03d 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -503,30 +503,6 @@ func TestInspectError(t *testing.T) { // the bad index tests are manually constructed from this single-block CARv2 by adjusting the Uint32 and Uint64 values in the index: // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset // 0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 01000000 1200000000000000 01000000 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000 - { - name: "BadIndexCountOverflow", - // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset - carHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 ffffffff 1200000000000000 01000000 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", - expectedInspectError: "index too big; MultihashIndexSorted count is overflowing int32", - }, - { - name: "BadIndexCountTooMany", - // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset - carHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 ffffff7f 1200000000000000 01000000 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", - expectedInspectError: "unexpected EOF", - }, - { - name: "BadIndexMultiWidthOverflow", - // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset - carHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 01000000 1200000000000000 ffffffff 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", - expectedInspectError: "index too big; multiWidthIndex count is overflowing int32", - }, - { - name: "BadIndexMultiWidthTooMany", - // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset - carHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 01000000 1200000000000000 ffffff7f 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", - expectedInspectError: "unexpected EOF", - }, // we don't test any further into the index, to do that, a user should do a ForEach across the loaded index (and sanity check the offsets) } From f439298055dbe6717ef5f7912eb8bbd124f860ea Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 14 Jul 2022 10:01:25 +0000 Subject: [PATCH 244/291] Separate `index.ReadFrom` tests This is to keep the tests in the repo, and have them run against `index.ReadFrom` without removing them completely now that inspection does not fully read the index. This commit was moved from ipld/go-car@e0b4de3e2593673b44fe003fd918a5a9c7bfa356 --- ipld/car/v2/reader_test.go | 54 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/ipld/car/v2/reader_test.go b/ipld/car/v2/reader_test.go index 5eaff8d03d..b398a870ac 100644 --- a/ipld/car/v2/reader_test.go +++ b/ipld/car/v2/reader_test.go @@ -529,6 +529,60 @@ func TestInspectError(t *testing.T) { } } +func TestIndex_ReadFromCorruptIndex(t *testing.T) { + tests := []struct { + name string + givenCarHex string + wantErr string + }{ + { + name: "BadIndexCountOverflow", + // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset + givenCarHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 ffffffff 1200000000000000 01000000 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", + wantErr: "index too big; MultihashIndexSorted count is overflowing int32", + }, + { + name: "BadIndexCountTooMany", + // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset + givenCarHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 ffffff7f 1200000000000000 01000000 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", + wantErr: "unexpected EOF", + }, + { + name: "BadIndexMultiWidthOverflow", + // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset + givenCarHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 01000000 1200000000000000 ffffffff 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", + wantErr: "index too big; multiWidthIndex count is overflowing int32", + }, + { + name: "BadIndexMultiWidthTooMany", + // pragma carv2 header carv1 icodec count codec count (swi) width dataLen mh offset + givenCarHex: "0aa16776657273696f6e02 00000000000000000000000000000000330000000000000041000000000000007400000000000000 11a265726f6f7473806776657273696f6e012e0155122001d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca00000000000000000000 8108 01000000 1200000000000000 ffffff7f 28000000 2800000000000000 01d448afd928065458cf670b60f5a594d735af0172c8d67f22a81680132681ca 1200000000000000", + wantErr: "unexpected EOF", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + car, _ := hex.DecodeString(strings.ReplaceAll(test.givenCarHex, " ", "")) + reader, err := carv2.NewReader(bytes.NewReader(car)) + require.NoError(t, err) + + ir, err := reader.IndexReader() + require.NoError(t, err) + require.NotNil(t, ir) + + gotIdx, err := index.ReadFrom(ir) + if test.wantErr == "" { + require.NoError(t, err) + require.NotNil(t, gotIdx) + } else { + require.Error(t, err) + require.Equal(t, test.wantErr, err.Error()) + require.Nil(t, gotIdx) + } + }) + } +} + func mustCidDecode(s string) cid.Cid { c, err := cid.Decode(s) if err != nil { From 179030bc59b29fc16e1e4dbf6aa536873692d521 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 15 Jul 2022 11:04:48 +1000 Subject: [PATCH 245/291] feat: add `car inspect` command to cmd pkg (#320) This commit was moved from ipld/go-car@3264624f19239211abfa12bf91471ceb0cfc9afc --- ipld/car/cmd/car/car.go | 12 ++ ipld/car/cmd/car/inspect.go | 127 ++++++++++++++++++ .../car/testdata/inputs/badheaderlength.car | 1 + .../car/testdata/inputs/badsectionlength.car | Bin 0 -> 70 bytes .../car/cmd/car/testdata/script/get-block.txt | 2 +- ipld/car/cmd/car/testdata/script/inspect.txt | 43 ++++++ 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 ipld/car/cmd/car/inspect.go create mode 100644 ipld/car/cmd/car/testdata/inputs/badheaderlength.car create mode 100644 ipld/car/cmd/car/testdata/inputs/badsectionlength.car create mode 100644 ipld/car/cmd/car/testdata/script/inspect.txt diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 90cf9425d8..9957c8a545 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -128,6 +128,18 @@ func main1() int { }, }, }, + { + Name: "inspect", + Usage: "verifies a car and prints a basic report about its contents", + Action: InspectCar, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "full", + Value: false, + Usage: "Check that the block data hash digests match the CIDs", + }, + }, + }, { Name: "list", Aliases: []string{"l", "ls"}, diff --git a/ipld/car/cmd/car/inspect.go b/ipld/car/cmd/car/inspect.go new file mode 100644 index 0000000000..76bb41ce9a --- /dev/null +++ b/ipld/car/cmd/car/inspect.go @@ -0,0 +1,127 @@ +package main + +import ( + "bytes" + "fmt" + "os" + "sort" + "strings" + + carv2 "github.com/ipld/go-car/v2" + "github.com/multiformats/go-multicodec" + "github.com/urfave/cli/v2" +) + +// InspectCar verifies a CAR and prints a basic report about its contents +func InspectCar(c *cli.Context) (err error) { + inStream := os.Stdin + if c.Args().Len() >= 1 { + inStream, err = os.Open(c.Args().First()) + if err != nil { + return err + } + } + + rd, err := carv2.NewReader(inStream) + if err != nil { + return err + } + stats, err := rd.Inspect(c.IsSet("full")) + if err != nil { + return err + } + + var v2s string + if stats.Version == 2 { + idx := "(none)" + if stats.IndexCodec != 0 { + idx = stats.IndexCodec.String() + } + var buf bytes.Buffer + stats.Header.Characteristics.WriteTo(&buf) + v2s = fmt.Sprintf(`Characteristics: %x +Data offset: %d +Data (payload) length: %d +Index offset: %d +Index type: %s +`, buf.Bytes(), stats.Header.DataOffset, stats.Header.DataSize, stats.Header.IndexOffset, idx) + } + + var roots strings.Builder + switch len(stats.Roots) { + case 0: + roots.WriteString(" (none)") + case 1: + roots.WriteString(" ") + roots.WriteString(stats.Roots[0].String()) + default: + for _, r := range stats.Roots { + roots.WriteString("\n\t") + roots.WriteString(r.String()) + } + } + + var codecs strings.Builder + { + keys := make([]int, len(stats.CodecCounts)) + i := 0 + for codec := range stats.CodecCounts { + keys[i] = int(codec) + i++ + } + sort.Ints(keys) + for _, code := range keys { + codec := multicodec.Code(code) + codecs.WriteString(fmt.Sprintf("\n\t%s: %d", codec, stats.CodecCounts[codec])) + } + } + + var hashers strings.Builder + { + keys := make([]int, len(stats.MhTypeCounts)) + i := 0 + for codec := range stats.MhTypeCounts { + keys[i] = int(codec) + i++ + } + sort.Ints(keys) + for _, code := range keys { + codec := multicodec.Code(code) + hashers.WriteString(fmt.Sprintf("\n\t%s: %d", codec, stats.MhTypeCounts[codec])) + } + } + + rp := "No" + if stats.RootsPresent { + rp = "Yes" + } + + pfmt := `Version: %d +%sRoots:%s +Root blocks present in data: %s +Block count: %d +Min / average / max block length (bytes): %d / %d / %d +Min / average / max CID length (bytes): %d / %d / %d +Block count per codec:%s +CID count per multihash:%s +` + + fmt.Printf( + pfmt, + stats.Version, + v2s, + roots.String(), + rp, + stats.BlockCount, + stats.MinBlockLength, + stats.AvgBlockLength, + stats.MaxBlockLength, + stats.MinCidLength, + stats.AvgCidLength, + stats.MaxCidLength, + codecs.String(), + hashers.String(), + ) + + return nil +} diff --git a/ipld/car/cmd/car/testdata/inputs/badheaderlength.car b/ipld/car/cmd/car/testdata/inputs/badheaderlength.car new file mode 100644 index 0000000000..4f6aa54873 --- /dev/null +++ b/ipld/car/cmd/car/testdata/inputs/badheaderlength.car @@ -0,0 +1 @@ + olLʔ<#oKg#H* gversion \ No newline at end of file diff --git a/ipld/car/cmd/car/testdata/inputs/badsectionlength.car b/ipld/car/cmd/car/testdata/inputs/badsectionlength.car new file mode 100644 index 0000000000000000000000000000000000000000..8569fb18d01a75ade5ba6885832c924ab1310f43 GIT binary patch literal 70 zcmWe!lv Date: Mon, 1 Aug 2022 09:30:18 +0200 Subject: [PATCH 246/291] blockstore: allow to pass a file to write in (#323) This allow the caller to control the file lifecycle and implement a cleaning strategy. An example would be: - open a file in a temporary folder - if the write suceed, close the file and move it to its final destination - on error, remove the file If the underlying filesystem support it, an anonymous file can be used instead: open then immediately delete, use the file descriptor to write. A crash before the end of the write process would release the used storage automatically. Co-authored-by: Rod Vagg This commit was moved from ipld/go-car@ed56551bf6e54d68b783a75ac396e45f053bcc98 --- ipld/car/v2/blockstore/readwrite.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 774f37ff69..542d0df42d 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -10,12 +10,13 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/multiformats/go-varint" + carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" - "github.com/multiformats/go-varint" ) var _ blockstore.Blockstore = (*ReadWrite)(nil) @@ -103,6 +104,18 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.Option) (*ReadWri if err != nil { return nil, fmt.Errorf("could not open read/write file: %w", err) } + rwbs, err := OpenReadWriteFile(f, roots, opts...) + if err != nil { + return nil, err + } + // close the file when finalizing + rwbs.ronly.carv2Closer = rwbs.f + return rwbs, nil +} + +// OpenReadWriteFile is similar as OpenReadWrite but lets you control the file lifecycle. +// You are responsible for closing the given file. +func OpenReadWriteFile(f *os.File, roots []cid.Cid, opts ...carv2.Option) (*ReadWrite, error) { stat, err := f.Stat() if err != nil { // Note, we should not get a an os.ErrNotExist here because the flags used to open file includes os.O_CREATE @@ -145,7 +158,6 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.Option) (*ReadWri } rwbs.ronly.backing = v1r rwbs.ronly.idx = rwbs.idx - rwbs.ronly.carv2Closer = rwbs.f if resume { if err = rwbs.resumeWithRoots(!rwbs.opts.WriteAsCarV1, roots); err != nil { From 51ec22e2dff4d58189ac1d05821a2f6478bc348b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Mon, 1 Aug 2022 10:36:44 +0200 Subject: [PATCH 247/291] OpenReadWriteFile: add test This commit was moved from ipld/go-car@02ff91a599ea2f47d45345712171d10cf7249a1f --- ipld/car/v2/blockstore/readwrite_test.go | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 22525e7896..bfa4e23c1c 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -945,6 +945,41 @@ func TestReadWrite_ReWritingCARv1WithIdentityCidIsIdenticalToOriginalWithOptions require.Equal(t, wantSum, gotSum) } +func TestReadWriteOpenFile(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + dir := t.TempDir() // auto cleanup + f, err := ioutil.TempFile(dir, "") + require.NoError(t, err) + + root := blocks.NewBlock([]byte("foo")) + + bs, err := blockstore.OpenReadWriteFile(f, []cid.Cid{root.Cid()}) + require.NoError(t, err) + + err = bs.Put(ctx, root) + require.NoError(t, err) + + roots, err := bs.Roots() + require.NoError(t, err) + _, err = bs.Has(ctx, roots[0]) + require.NoError(t, err) + _, err = bs.Get(ctx, roots[0]) + require.NoError(t, err) + _, err = bs.GetSize(ctx, roots[0]) + require.NoError(t, err) + + err = bs.Finalize() + require.NoError(t, err) + + _, err = f.Seek(0, 0) + require.NoError(t, err) // file should not be closed, let the caller do it + + err = f.Close() + require.NoError(t, err) +} + func TestBlockstore_IdentityCidWithEmptyDataIsIndexed(t *testing.T) { p := path.Join(t.TempDir(), "car-id-cid-empty.carv2") var noData []byte From ae1afa3948e92816008fc20219bbf72e65671b5e Mon Sep 17 00:00:00 2001 From: web3-bot Date: Wed, 24 Aug 2022 11:47:30 +0000 Subject: [PATCH 248/291] bump go.mod to Go 1.18 and run go fix This commit was moved from ipld/go-car@a18b68d3ea04856a079e7b1cd06abe59224beecb --- ipld/car/fuzz_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ipld/car/fuzz_test.go b/ipld/car/fuzz_test.go index 8ac04bbd07..cf68d946eb 100644 --- a/ipld/car/fuzz_test.go +++ b/ipld/car/fuzz_test.go @@ -1,5 +1,4 @@ //go:build go1.18 -// +build go1.18 package car_test From 0301bfb2eb528840bf6a56cd49c98c0ffd185e7d Mon Sep 17 00:00:00 2001 From: web3-bot Date: Wed, 24 Aug 2022 11:47:33 +0000 Subject: [PATCH 249/291] bump go.mod to Go 1.18 and run go fix This commit was moved from ipld/go-car@9e3e435a838b6e91968b6bb6810d108aa8147951 --- ipld/car/v2/fuzz_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ipld/car/v2/fuzz_test.go b/ipld/car/v2/fuzz_test.go index c45d83cb0b..32c548547a 100644 --- a/ipld/car/v2/fuzz_test.go +++ b/ipld/car/v2/fuzz_test.go @@ -1,5 +1,4 @@ //go:build go1.18 -// +build go1.18 package car_test From 354d1c8624a3bcc245aaa5f7a491a31a2d20efd8 Mon Sep 17 00:00:00 2001 From: web3-bot Date: Wed, 24 Aug 2022 11:47:37 +0000 Subject: [PATCH 250/291] run gofmt -s This commit was moved from ipld/go-car@ca1482a654f791455f397bc1a052c2dfbbf69d55 --- ipld/car/v2/blockstore/doc.go | 8 ++--- ipld/car/v2/index/doc.go | 1 - ipld/car/v2/internal/carv1/car.go | 5 +-- ipld/car/v2/internal/loader/writing_loader.go | 4 ++- ipld/car/v2/reader.go | 36 +++++++++---------- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index 479442e129..4aa4cfdfc9 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -3,10 +3,10 @@ // // The ReadOnly blockstore provides a read-only random access from a given data payload either in // unindexed CARv1 format or indexed/unindexed v2 format: -// * ReadOnly.NewReadOnly can be used to instantiate a new read-only blockstore for a given CARv1 -// or CARv2 data payload with an optional index override. -// * ReadOnly.OpenReadOnly can be used to instantiate a new read-only blockstore for a given CARv1 -// or CARv2 file with automatic index generation if the index is not present. +// - ReadOnly.NewReadOnly can be used to instantiate a new read-only blockstore for a given CARv1 +// or CARv2 data payload with an optional index override. +// - ReadOnly.OpenReadOnly can be used to instantiate a new read-only blockstore for a given CARv1 +// or CARv2 file with automatic index generation if the index is not present. // // The ReadWrite blockstore allows writing and reading of the blocks concurrently. The user of this // blockstore is responsible for calling ReadWrite.Finalize when finished writing blocks. diff --git a/ipld/car/v2/index/doc.go b/ipld/car/v2/index/doc.go index 41b860216c..b8062cc186 100644 --- a/ipld/car/v2/index/doc.go +++ b/ipld/car/v2/index/doc.go @@ -3,5 +3,4 @@ // // Index can be written or read using the following static functions: index.WriteTo and // index.ReadFrom. -// package index diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index f62899b714..8ad012aa62 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -229,8 +229,9 @@ func loadCarSlow(ctx context.Context, s Store, cr *CarReader) (*CarHeader, error // Matches checks whether two headers match. // Two headers are considered matching if: -// 1. They have the same version number, and -// 2. They contain the same root CIDs in any order. +// 1. They have the same version number, and +// 2. They contain the same root CIDs in any order. +// // Note, this function explicitly ignores the order of roots. // If order of roots matter use reflect.DeepEqual instead. func (h CarHeader) Matches(other CarHeader) bool { diff --git a/ipld/car/v2/internal/loader/writing_loader.go b/ipld/car/v2/internal/loader/writing_loader.go index b4d0e6efe1..0bf2ae3c8a 100644 --- a/ipld/car/v2/internal/loader/writing_loader.go +++ b/ipld/car/v2/internal/loader/writing_loader.go @@ -87,7 +87,9 @@ func (w *writingReader) Read(p []byte) (int, error) { // that block is also written as a CAR block to the provided io.Writer. Metadata // (the size of data written) is provided in the second return value. // The `initialOffset` is used to calculate the offsets recorded for the index, and will be -// included in the `.Size()` of the IndexTracker. +// +// included in the `.Size()` of the IndexTracker. +// // An indexCodec of `index.CarIndexNoIndex` can be used to not track these offsets. func TeeingLinkSystem(ls ipld.LinkSystem, w io.Writer, initialOffset uint64, indexCodec multicodec.Code) (ipld.LinkSystem, IndexTracker) { wo := writerOutput{ diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index f29469a027..ca6cad1e95 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -170,29 +170,29 @@ type Stats struct { // Beyond the checks performed by Inspect, a valid / good CAR is somewhat // use-case dependent. Factors to consider include: // -// * Bad indexes, including incorrect offsets, duplicate entries, or other -// faulty data. Indexes should be re-generated, regardless, if you need to use -// them and have any reason to not trust the source. +// - Bad indexes, including incorrect offsets, duplicate entries, or other +// faulty data. Indexes should be re-generated, regardless, if you need to use +// them and have any reason to not trust the source. // -// * Blocks use codecs that your system doesn't have access to—which may mean -// you can't traverse a DAG or use the contained data. Stats.CodecCounts -// contains a list of codecs found in the CAR so this can be checked. +// - Blocks use codecs that your system doesn't have access to—which may mean +// you can't traverse a DAG or use the contained data. Stats.CodecCounts +// contains a list of codecs found in the CAR so this can be checked. // -// * CIDs use multihashes that your system doesn't have access to—which will -// mean you can't validate block hashes are correct (using validateBlockHash -// in this case will result in a failure). Stats.MhTypeCounts contains a -// list of multihashes found in the CAR so this can be checked. +// - CIDs use multihashes that your system doesn't have access to—which will +// mean you can't validate block hashes are correct (using validateBlockHash +// in this case will result in a failure). Stats.MhTypeCounts contains a +// list of multihashes found in the CAR so this can be checked. // -// * The presence of IDENTITY CIDs, which may not be supported (or desired) by -// the consumer of the CAR. Stats.CodecCounts can determine the presence -// of IDENTITY CIDs. +// - The presence of IDENTITY CIDs, which may not be supported (or desired) by +// the consumer of the CAR. Stats.CodecCounts can determine the presence +// of IDENTITY CIDs. // -// * Roots: the number of roots, duplicates, and whether they are related to the -// blocks contained within the CAR. Stats contains a list of Roots and a -// RootsPresent bool so further checks can be performed. +// - Roots: the number of roots, duplicates, and whether they are related to the +// blocks contained within the CAR. Stats contains a list of Roots and a +// RootsPresent bool so further checks can be performed. // -// * DAG completeness is not checked. Any properties relating to the DAG, or -// DAGs contained within a CAR are the responsibility of the user to check. +// - DAG completeness is not checked. Any properties relating to the DAG, or +// DAGs contained within a CAR are the responsibility of the user to check. func (r *Reader) Inspect(validateBlockHash bool) (Stats, error) { stats := Stats{ Version: r.Version, From 444973fd849ff7c209afc72c8980de80a4531c70 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 24 Aug 2022 13:58:52 +1000 Subject: [PATCH 251/291] fix: remove use of ioutil This commit was moved from ipld/go-car@9be1c2e7e07a8ba3763201044b33124cddd1d5fb --- ipld/car/v2/blockstore/example_test.go | 3 +-- ipld/car/v2/blockstore/readonly_test.go | 3 +-- ipld/car/v2/blockstore/readwrite_test.go | 9 ++++----- ipld/car/v2/example_test.go | 7 +++---- ipld/car/v2/index/example_test.go | 3 +-- ipld/car/v2/internal/io/converter.go | 5 ++--- ipld/car/v2/selective.go | 3 +-- ipld/car/v2/writer_test.go | 11 +++++------ 8 files changed, 18 insertions(+), 26 deletions(-) diff --git a/ipld/car/v2/blockstore/example_test.go b/ipld/car/v2/blockstore/example_test.go index 198443c9d8..ec16383eb5 100644 --- a/ipld/car/v2/blockstore/example_test.go +++ b/ipld/car/v2/blockstore/example_test.go @@ -3,7 +3,6 @@ package blockstore_test import ( "context" "fmt" - "io/ioutil" "os" "path/filepath" "time" @@ -86,7 +85,7 @@ func ExampleOpenReadWrite() { thatBlock := merkledag.NewRawNode([]byte("lobster")).Block andTheOtherBlock := merkledag.NewRawNode([]byte("barreleye")).Block - tdir, err := ioutil.TempDir(os.TempDir(), "example-*") + tdir, err := os.MkdirTemp(os.TempDir(), "example-*") if err != nil { panic(err) } diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 5a947a75b3..a799d328fe 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "io" - "io/ioutil" "os" "testing" "time" @@ -277,7 +276,7 @@ func TestReadOnlyErrorAfterClose(t *testing.T) { } func TestNewReadOnly_CarV1WithoutIndexWorksAsExpected(t *testing.T) { - carV1Bytes, err := ioutil.ReadFile("../testdata/sample-v1.car") + carV1Bytes, err := os.ReadFile("../testdata/sample-v1.car") require.NoError(t, err) reader := bytes.NewReader(carV1Bytes) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index bfa4e23c1c..4553fd15f2 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -5,7 +5,6 @@ import ( "crypto/sha512" "fmt" "io" - "io/ioutil" "math/rand" "os" "path" @@ -360,7 +359,7 @@ func TestBlockstoreNullPadding(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - paddedV1, err := ioutil.ReadFile("../testdata/sample-v1-with-zero-len-section.car") + paddedV1, err := os.ReadFile("../testdata/sample-v1-with-zero-len-section.car") require.NoError(t, err) rbs, err := blockstore.NewReadOnly(bufferReaderAt(paddedV1), nil, @@ -665,7 +664,7 @@ func TestReadWriteResumptionFromNonV2FileIsError(t *testing.T) { func TestReadWriteResumptionMismatchingRootsIsError(t *testing.T) { tmpPath := requireTmpCopy(t, "../testdata/sample-wrapped-v2.car") - origContent, err := ioutil.ReadFile(tmpPath) + origContent, err := os.ReadFile(tmpPath) require.NoError(t, err) badRoot, err := cid.NewPrefixV1(cid.Raw, multihash.SHA2_256).Sum([]byte("bad root")) @@ -675,7 +674,7 @@ func TestReadWriteResumptionMismatchingRootsIsError(t *testing.T) { require.EqualError(t, err, "cannot resume on file with mismatching data header") require.Nil(t, subject) - newContent, err := ioutil.ReadFile(tmpPath) + newContent, err := os.ReadFile(tmpPath) require.NoError(t, err) // Expect the bad file to be left untouched; check the size first. @@ -950,7 +949,7 @@ func TestReadWriteOpenFile(t *testing.T) { defer cancel() dir := t.TempDir() // auto cleanup - f, err := ioutil.TempFile(dir, "") + f, err := os.CreateTemp(dir, "") require.NoError(t, err) root := blocks.NewBlock([]byte("foo")) diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go index 57378aeaad..532f87d6a5 100644 --- a/ipld/car/v2/example_test.go +++ b/ipld/car/v2/example_test.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "os" "path/filepath" @@ -19,7 +18,7 @@ func ExampleWrapV1File() { // Writing the result to testdata allows reusing that file in other tests, // and also helps ensure that the result is deterministic. src := "testdata/sample-v1.car" - tdir, err := ioutil.TempDir(os.TempDir(), "example-*") + tdir, err := os.MkdirTemp(os.TempDir(), "example-*") if err != nil { panic(err) } @@ -47,7 +46,7 @@ func ExampleWrapV1File() { fmt.Println("Has index:", cr.Header.HasIndex()) // Verify that the CARv1 remains exactly the same. - orig, err := ioutil.ReadFile(src) + orig, err := os.ReadFile(src) if err != nil { panic(err) } @@ -55,7 +54,7 @@ func ExampleWrapV1File() { if err != nil { panic(err) } - inner, err := ioutil.ReadAll(dr) + inner, err := io.ReadAll(dr) if err != nil { panic(err) } diff --git a/ipld/car/v2/index/example_test.go b/ipld/car/v2/index/example_test.go index d2a9da54b6..2b9cca7c66 100644 --- a/ipld/car/v2/index/example_test.go +++ b/ipld/car/v2/index/example_test.go @@ -3,7 +3,6 @@ package index_test import ( "fmt" "io" - "io/ioutil" "os" "reflect" @@ -76,7 +75,7 @@ func ExampleWriteTo() { } // Store the index alone onto destination file. - f, err := ioutil.TempFile(os.TempDir(), "example-index-*.carindex") + f, err := os.CreateTemp(os.TempDir(), "example-index-*.carindex") if err != nil { panic(err) } diff --git a/ipld/car/v2/internal/io/converter.go b/ipld/car/v2/internal/io/converter.go index 21011b6ec1..2b29d0a883 100644 --- a/ipld/car/v2/internal/io/converter.go +++ b/ipld/car/v2/internal/io/converter.go @@ -2,7 +2,6 @@ package io import ( "io" - "io/ioutil" "sync" ) @@ -97,10 +96,10 @@ func (drsb *discardingReadSeekerPlusByte) Seek(offset int64, whence int) (int64, if n < 0 { panic("unsupported rewind via whence: io.SeekStart") } - _, err := io.CopyN(ioutil.Discard, drsb, n) + _, err := io.CopyN(io.Discard, drsb, n) return drsb.offset, err case io.SeekCurrent: - _, err := io.CopyN(ioutil.Discard, drsb, offset) + _, err := io.CopyN(io.Discard, drsb, offset) return drsb.offset, err default: panic("unsupported whence: io.SeekEnd") diff --git a/ipld/car/v2/selective.go b/ipld/car/v2/selective.go index 39bb5f9112..5aaac55940 100644 --- a/ipld/car/v2/selective.go +++ b/ipld/car/v2/selective.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "math" "os" @@ -276,7 +275,7 @@ func traverse(ctx context.Context, ls *ipld.LinkSystem, root cid.Cid, s ipld.Nod if err != nil { return err } - _, err = io.Copy(ioutil.Discard, s) + _, err = io.Copy(io.Discard, s) if err != nil { return err } diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 12dcd6a9c8..a38ef34ade 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -3,7 +3,6 @@ package car import ( "context" "io" - "io/ioutil" "os" "path/filepath" "testing" @@ -45,11 +44,11 @@ func TestWrapV1(t *testing.T) { // Assert CARv1 data payloads are identical. _, err = sf.Seek(0, io.SeekStart) require.NoError(t, err) - wantPayload, err := ioutil.ReadAll(sf) + wantPayload, err := io.ReadAll(sf) require.NoError(t, err) dr, err := subject.DataReader() require.NoError(t, err) - gotPayload, err := ioutil.ReadAll(dr) + gotPayload, err := io.ReadAll(dr) require.NoError(t, err) require.Equal(t, wantPayload, gotPayload) @@ -73,7 +72,7 @@ func TestExtractV1(t *testing.T) { require.NoError(t, carv1.WriteCar(context.Background(), dagSvc, generateRootCid(t, dagSvc), v1f)) _, err = v1f.Seek(0, io.SeekStart) require.NoError(t, err) - wantV1, err := ioutil.ReadAll(v1f) + wantV1, err := io.ReadAll(v1f) require.NoError(t, err) // Wrap the produced CARv1 into a CARv2 to use for testing. @@ -83,13 +82,13 @@ func TestExtractV1(t *testing.T) { // Assert extract from CARv2 file is as expected. dstPath := filepath.Join(t.TempDir(), "extract-file-test-v1.car") require.NoError(t, ExtractV1File(v2path, dstPath)) - gotFromFile, err := ioutil.ReadFile(dstPath) + gotFromFile, err := os.ReadFile(dstPath) require.NoError(t, err) require.Equal(t, wantV1, gotFromFile) // Assert extract from CARv2 file in-place is as expected require.NoError(t, ExtractV1File(v2path, v2path)) - gotFromInPlaceFile, err := ioutil.ReadFile(v2path) + gotFromInPlaceFile, err := os.ReadFile(v2path) require.NoError(t, err) require.Equal(t, wantV1, gotFromInPlaceFile) } From 0599510d4100e6bbf67daa57df3fe88c932e73a1 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 2 Sep 2022 20:53:57 +1000 Subject: [PATCH 252/291] feat: Has() and Get() will respect StoreIdentityCIDs option When StoreIdentityCIDs is set, it will defer to the index to check whether the blocks are in the CAR. When the CAR is a v1 and StoreIdentityCIDs is set, the index will contain the identity CIDs. When it's a v2 with an existing index, however that index was created will determine whether the identity CIDs are that are in the CAR are found. When StoreIdentityCIDs is not set, Has() will always return true and Get() will always return the block. This commit was moved from ipld/go-car@02d658faa7dfbc01920fc42ad7210c3505d049f4 --- ipld/car/v2/blockstore/readonly.go | 46 +++++++++++++++++-------- ipld/car/v2/blockstore/readonly_test.go | 39 ++++++++++++++++++--- ipld/car/v2/index_gen.go | 4 +++ ipld/car/v2/options.go | 11 ++++-- 4 files changed, 80 insertions(+), 20 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 32c49046bd..bfca69112a 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -217,14 +217,23 @@ func (b *ReadOnly) DeleteBlock(_ context.Context, _ cid.Cid) error { } // Has indicates if the store contains a block that corresponds to the given key. -// This function always returns true for any given key with multihash.IDENTITY code. +// This function always returns true for any given key with multihash.IDENTITY +// code unless the StoreIdentityCIDs option is on, in which case it will defer +// to the index to check for the existence of the block; the index may or may +// not contain identity CIDs included in this CAR, depending on whether +// StoreIdentityCIDs was on when the index was created. If the CAR is a CARv1 +// and StoreIdentityCIDs is on, then the index will contain identity CIDs and +// this will always return true. func (b *ReadOnly) Has(ctx context.Context, key cid.Cid) (bool, error) { - // Check if the given CID has multihash.IDENTITY code - // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. - if _, ok, err := isIdentity(key); err != nil { - return false, err - } else if ok { - return true, nil + if !b.opts.StoreIdentityCIDs { + // If we don't store identity CIDs then we can return them straight away as if they are here, + // otherwise we need to check for their existence. + // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. + if _, ok, err := isIdentity(key); err != nil { + return false, err + } else if ok { + return true, nil + } } b.mu.RLock() @@ -269,14 +278,23 @@ func (b *ReadOnly) Has(ctx context.Context, key cid.Cid) (bool, error) { } // Get gets a block corresponding to the given key. -// This API will always return true if the given key has multihash.IDENTITY code. +// This function always returns the block for any given key with +// multihash.IDENTITY code unless the StoreIdentityCIDs option is on, in which +// case it will defer to the index to check for the existence of the block; the +// index may or may not contain identity CIDs included in this CAR, depending on +// whether StoreIdentityCIDs was on when the index was created. If the CAR is a +// CARv1 and StoreIdentityCIDs is on, then the index will contain identity CIDs +// and this will always return true. func (b *ReadOnly) Get(ctx context.Context, key cid.Cid) (blocks.Block, error) { - // Check if the given CID has multihash.IDENTITY code - // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. - if digest, ok, err := isIdentity(key); err != nil { - return nil, err - } else if ok { - return blocks.NewBlockWithCid(digest, key) + if !b.opts.StoreIdentityCIDs { + // If we don't store identity CIDs then we can return them straight away as if they are here, + // otherwise we need to check for their existence. + // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. + if digest, ok, err := isIdentity(key); err != nil { + return nil, err + } else if ok { + return blocks.NewBlockWithCid(digest, key) + } } b.mu.RLock() diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index a799d328fe..6b1b009f89 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -14,6 +14,7 @@ import ( "github.com/ipfs/go-merkledag" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/internal/carv1" + "github.com/multiformats/go-multicodec" "github.com/stretchr/testify/require" ) @@ -33,31 +34,53 @@ func TestReadOnly(t *testing.T) { name string v1OrV2path string opts []carv2.Option + noIdCids bool }{ { "OpenedWithCarV1", "../testdata/sample-v1.car", []carv2.Option{UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, + // index is made, but identity CIDs are included so they'll be found + false, + }, + { + "OpenedWithCarV1_NoIdentityCID", + "../testdata/sample-v1.car", + []carv2.Option{UseWholeCIDs(true)}, + // index is made, identity CIDs are not included, but we always short-circuit when StoreIdentityCIDs(false) + false, }, { "OpenedWithCarV2", "../testdata/sample-wrapped-v2.car", []carv2.Option{UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, + // index already exists, but was made without identity CIDs, but opening with StoreIdentityCIDs(true) means we check the index + true, + }, + { + "OpenedWithCarV2_NoIdentityCID", + "../testdata/sample-wrapped-v2.car", + []carv2.Option{UseWholeCIDs(true)}, + // index already exists, it was made without identity CIDs, but we always short-circuit when StoreIdentityCIDs(false) + false, }, { "OpenedWithCarV1ZeroLenSection", "../testdata/sample-v1-with-zero-len-section.car", []carv2.Option{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + false, }, { "OpenedWithAnotherCarV1ZeroLenSection", "../testdata/sample-v1-with-zero-len-section2.car", []carv2.Option{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.TODO() + subject, err := OpenReadOnly(tt.v1OrV2path, tt.opts...) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, subject.Close()) }) @@ -89,7 +112,13 @@ func TestReadOnly(t *testing.T) { // Assert blockstore contains key. has, err := subject.Has(ctx, key) require.NoError(t, err) - require.True(t, has) + if key.Prefix().MhType == uint64(multicodec.Identity) && tt.noIdCids { + // fixture wasn't made with StoreIdentityCIDs, but we opened it with StoreIdentityCIDs, + // so they aren't there to find + require.False(t, has) + } else { + require.True(t, has) + } // Assert size matches block raw data length. gotSize, err := subject.GetSize(ctx, key) @@ -98,9 +127,11 @@ func TestReadOnly(t *testing.T) { require.Equal(t, wantSize, gotSize) // Assert block itself matches v1 payload block. - gotBlock, err := subject.Get(ctx, key) - require.NoError(t, err) - require.Equal(t, wantBlock, gotBlock) + if has { + gotBlock, err := subject.Get(ctx, key) + require.NoError(t, err) + require.Equal(t, wantBlock, gotBlock) + } // Assert write operations error require.Error(t, subject.Put(ctx, wantBlock)) diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index b0b87453ec..092ed434c1 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -34,6 +34,10 @@ func GenerateIndex(v1r io.Reader, opts ...Option) (index.Index, error) { // LoadIndex populates idx with index records generated from r. // The r may be in CARv1 or CARv2 format. // +// If the StoreIdentityCIDs option is set when calling LoadIndex, identity +// CIDs will be included in the index. By default this option is off, and +// identity CIDs will not be included in the index. +// // Note, the index is re-generated every time even if r is in CARv2 format and already has an index. // To read existing index when available see ReadOrGenerateIndex. func LoadIndex(idx index.Index, r io.Reader, opts ...Option) error { diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index d2e526c424..8b5fe9b4e4 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -127,8 +127,15 @@ func WithoutIndex() Option { // StoreIdentityCIDs sets whether to persist sections that are referenced by // CIDs with multihash.IDENTITY digest. -// When writing CAR files with this option, -// Characteristics.IsFullyIndexed will be set. +// When writing CAR files with this option, Characteristics.IsFullyIndexed will +// be set. +// +// By default, the blockstore interface will always return true for Has() called +// with identity CIDs, but when this option is turned on, it will defer to the +// index. +// +// When creating an index (or loading a CARv1 as a blockstore), when this option +// is on, identity CIDs will be included in the index. // // This option is disabled by default. func StoreIdentityCIDs(b bool) Option { From 8399426c4e7da1c17d5e08eb3e6b4eb6ff3796b8 Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 20 Oct 2022 11:26:59 +0200 Subject: [PATCH 253/291] add a `SkipNext` method on block reader (#338) * add a `SkipNext` method on block reader Co-authored-by: Rod Vagg This commit was moved from ipld/go-car@50e029b9990efc2a9f70df098445fe5a5cf10df6 --- ipld/car/v2/block_reader.go | 97 ++++++++++++++++++++++++- ipld/car/v2/block_reader_test.go | 36 +++++++++ ipld/car/v2/internal/carv1/util/util.go | 18 +++-- 3 files changed, 144 insertions(+), 7 deletions(-) diff --git a/ipld/car/v2/block_reader.go b/ipld/car/v2/block_reader.go index 252885c315..0c38412fd7 100644 --- a/ipld/car/v2/block_reader.go +++ b/ipld/car/v2/block_reader.go @@ -9,6 +9,7 @@ import ( "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-varint" ) // BlockReader facilitates iteration over CAR blocks for both CARv1 and CARv2. @@ -20,8 +21,10 @@ type BlockReader struct { Roots []cid.Cid // Used internally only, by BlockReader.Next during iteration over blocks. - r io.Reader - opts Options + r io.Reader + offset uint64 + readerSize int64 + opts Options } // NewBlockReader instantiates a new BlockReader facilitating iteration over blocks in CARv1 or @@ -52,6 +55,8 @@ func NewBlockReader(r io.Reader, opts ...Option) (*BlockReader, error) { // Simply populate br.Roots and br.r without modifying r. br.Roots = pragmaOrV1Header.Roots br.r = r + br.readerSize = -1 + br.offset, _ = carv1.HeaderSize(pragmaOrV1Header) case 2: // If the version is 2: // 1. Read CARv2 specific header to locate the inner CARv1 data payload offset and size. @@ -75,6 +80,8 @@ func NewBlockReader(r io.Reader, opts ...Option) (*BlockReader, error) { if _, err := rs.Seek(int64(v2h.DataOffset)-PragmaSize-HeaderSize, io.SeekCurrent); err != nil { return nil, err } + br.offset = uint64(v2h.DataOffset) + br.readerSize = int64(v2h.DataOffset + v2h.DataSize) // Set br.r to a LimitReader reading from r limited to dataSize. br.r = io.LimitReader(r, int64(v2h.DataSize)) @@ -122,5 +129,91 @@ func (br *BlockReader) Next() (blocks.Block, error) { return nil, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", c, hashed) } + ss := uint64(c.ByteLen()) + uint64(len(data)) + br.offset += uint64(varint.UvarintSize(ss)) + ss return blocks.NewBlockWithCid(data, c) } + +type BlockMetadata struct { + cid.Cid + Offset uint64 + Size uint64 +} + +// SkipNext jumps over the next block, returning metadata about what it is (the CID, offset, and size). +// Like Next it will return an io.EOF once it has reached the end. +// +// If the underlying reader used by the BlockReader is actually a ReadSeeker, this method will attempt to +// seek over the underlying data rather than reading it into memory. +func (br *BlockReader) SkipNext() (*BlockMetadata, error) { + sctSize, err := util.LdReadSize(br.r, br.opts.ZeroLengthSectionAsEOF, br.opts.MaxAllowedSectionSize) + if err != nil { + return nil, err + } + + if sctSize == 0 { + _, _, err := cid.CidFromBytes([]byte{}) + return nil, err + } + + cidSize, c, err := cid.CidFromReader(io.LimitReader(br.r, int64(sctSize))) + if err != nil { + return nil, err + } + + blkSize := sctSize - uint64(cidSize) + if brs, ok := br.r.(io.ReadSeeker); ok { + // carv1 and we don't know the size, so work it out and cache it + if br.readerSize == -1 { + cur, err := brs.Seek(0, io.SeekCurrent) + if err != nil { + return nil, err + } + end, err := brs.Seek(0, io.SeekEnd) + if err != nil { + return nil, err + } + br.readerSize = end + if _, err = brs.Seek(cur, io.SeekStart); err != nil { + return nil, err + } + } + // seek. + finalOffset, err := brs.Seek(int64(blkSize), io.SeekCurrent) + if err != nil { + return nil, err + } + if finalOffset != int64(br.offset)+int64(sctSize)+int64(varint.UvarintSize(sctSize)) { + return nil, fmt.Errorf("unexpected length") + } + if finalOffset > br.readerSize { + return nil, io.ErrUnexpectedEOF + } + br.offset = uint64(finalOffset) + return &BlockMetadata{ + c, + uint64(finalOffset) - sctSize - uint64(varint.UvarintSize(sctSize)), + blkSize, + }, nil + } + + // read to end. + readCnt, err := io.CopyN(io.Discard, br.r, int64(blkSize)) + if err != nil { + if err == io.EOF { + return nil, io.ErrUnexpectedEOF + } + return nil, err + } + if readCnt != int64(blkSize) { + return nil, fmt.Errorf("unexpected length") + } + origOffset := br.offset + br.offset += uint64(varint.UvarintSize(sctSize)) + sctSize + + return &BlockMetadata{ + c, + origOffset, + blkSize, + }, nil +} diff --git a/ipld/car/v2/block_reader_test.go b/ipld/car/v2/block_reader_test.go index 384a6ed88c..b5958c7f00 100644 --- a/ipld/car/v2/block_reader_test.go +++ b/ipld/car/v2/block_reader_test.go @@ -3,6 +3,7 @@ package car_test import ( "bytes" "encoding/hex" + "fmt" "io" "os" "testing" @@ -106,6 +107,41 @@ func TestBlockReader_WithCarV1Consistency(t *testing.T) { } } }) + t.Run(tt.name+"-skipping-reads", func(t *testing.T) { + r := requireReaderFromPath(t, tt.path) + subject, err := carv2.NewBlockReader(r, carv2.ZeroLengthSectionAsEOF(tt.zerLenAsEOF)) + require.NoError(t, err) + + require.Equal(t, tt.wantVersion, subject.Version) + + var wantReader *carv1.CarReader + switch tt.wantVersion { + case 1: + wantReader = requireNewCarV1ReaderFromV1File(t, tt.path, tt.zerLenAsEOF) + case 2: + wantReader = requireNewCarV1ReaderFromV2File(t, tt.path, tt.zerLenAsEOF) + default: + require.Failf(t, "invalid test-case", "unknown wantVersion %v", tt.wantVersion) + } + require.Equal(t, wantReader.Header.Roots, subject.Roots) + + for { + gotBlock, gotErr := subject.SkipNext() + wantBlock, wantErr := wantReader.Next() + if wantErr != nil && gotErr == nil { + fmt.Printf("want was %+v\n", wantReader) + fmt.Printf("want was err, got was %+v / %d\n", gotBlock, gotBlock.Size) + } + require.Equal(t, wantErr, gotErr) + if gotErr == io.EOF { + break + } + if gotErr == nil { + require.Equal(t, wantBlock.Cid(), gotBlock.Cid) + require.Equal(t, uint64(len(wantBlock.RawData())), gotBlock.Size) + } + } + }) } } diff --git a/ipld/car/v2/internal/carv1/util/util.go b/ipld/car/v2/internal/carv1/util/util.go index 7963812e43..00cd885b27 100644 --- a/ipld/car/v2/internal/carv1/util/util.go +++ b/ipld/car/v2/internal/carv1/util/util.go @@ -65,20 +65,28 @@ func LdSize(d ...[]byte) uint64 { return sum + uint64(s) } -func LdRead(r io.Reader, zeroLenAsEOF bool, maxReadBytes uint64) ([]byte, error) { +func LdReadSize(r io.Reader, zeroLenAsEOF bool, maxReadBytes uint64) (uint64, error) { l, err := varint.ReadUvarint(internalio.ToByteReader(r)) if err != nil { // If the length of bytes read is non-zero when the error is EOF then signal an unclean EOF. if l > 0 && err == io.EOF { - return nil, io.ErrUnexpectedEOF + return 0, io.ErrUnexpectedEOF } - return nil, err + return 0, err } else if l == 0 && zeroLenAsEOF { - return nil, io.EOF + return 0, io.EOF } if l > maxReadBytes { // Don't OOM - return nil, ErrSectionTooLarge + return 0, ErrSectionTooLarge + } + return l, nil +} + +func LdRead(r io.Reader, zeroLenAsEOF bool, maxReadBytes uint64) ([]byte, error) { + l, err := LdReadSize(r, zeroLenAsEOF, maxReadBytes) + if err != nil { + return nil, err } buf := make([]byte, l) From 528afb5a92ec0ec41450e965ecda3c371f5390af Mon Sep 17 00:00:00 2001 From: Antonio Navarro Perez Date: Fri, 21 Oct 2022 15:37:00 +0200 Subject: [PATCH 254/291] docs: Update commands list This commit was moved from ipld/go-car@df02e3e53629d69505e2ad9d45c1b85a120c394b --- ipld/car/cmd/car/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ipld/car/cmd/car/README.md b/ipld/car/cmd/car/README.md index b9a4ca9a83..ef04a0dc3f 100644 --- a/ipld/car/cmd/car/README.md +++ b/ipld/car/cmd/car/README.md @@ -16,11 +16,14 @@ USAGE: COMMANDS: create, c Create a car file detach-index Detach an index to a detached file + extract, x Extract the contents of a car when the car encodes UnixFS data filter, f Filter the CIDs in a car get-block, gb Get a block out of a car get-dag, gd Get a dag out of a car index, i write out the car with an index - list, l List the CIDs in a car + inspect verifies a car and prints a basic report about its contents + list, l, ls List the CIDs in a car + root Get the root CID of a car verify, v Verify a CAR is wellformed help, h Shows a list of commands or help for one command ``` From 3f6809177cecacb9ceb368e62656c225c3788ed6 Mon Sep 17 00:00:00 2001 From: Will Date: Fri, 18 Nov 2022 12:41:45 +0100 Subject: [PATCH 255/291] Add a debugging form for car files. (#341) * Add a debugging form for car files. This change adds two new sub-commands to the car CLI car debug file.car creates a patch-file-compatible representation of the content of the car file. Blocks will be represented in dag-json pretty-printed form. car compile file.patch will do the inverse process of building a car file from a debug patch file. CIDs will be re-compiled based on the contents of blocks, with links in parent blocks updated to point to the compiled values. Co-authored-by: Rod Vagg This commit was moved from ipld/go-car@dab0fd5bb19dead0da1377270f37be9acf858cf0 --- ipld/car/cmd/car/car.go | 26 ++ ipld/car/cmd/car/compile.go | 463 +++++++++++++++++++ ipld/car/cmd/car/testdata/script/compile.txt | 28 ++ 3 files changed, 517 insertions(+) create mode 100644 ipld/car/cmd/car/compile.go create mode 100644 ipld/car/cmd/car/testdata/script/compile.txt diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 9957c8a545..c66232f7d4 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -15,6 +15,19 @@ func main1() int { Name: "car", Usage: "Utility for working with car files", Commands: []*cli.Command{ + { + Name: "compile", + Usage: "compile a car file from a debug patch", + Action: CompileCar, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "output", + Aliases: []string{"o", "f"}, + Usage: "The file to write to", + TakesFile: true, + }, + }, + }, { Name: "create", Usage: "Create a car file", @@ -34,6 +47,19 @@ func main1() int { }, }, }, + { + Name: "debug", + Usage: "debug a car file", + Action: DebugCar, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "output", + Aliases: []string{"o", "f"}, + Usage: "The file to write to", + TakesFile: true, + }, + }, + }, { Name: "detach-index", Usage: "Detach an index to a detached file", diff --git a/ipld/car/cmd/car/compile.go b/ipld/car/cmd/car/compile.go new file mode 100644 index 0000000000..0a74d1c7e8 --- /dev/null +++ b/ipld/car/cmd/car/compile.go @@ -0,0 +1,463 @@ +package main + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "os" + "regexp" + "strings" + "unicode/utf8" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + carv1 "github.com/ipld/go-car" + "github.com/ipld/go-car/util" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/codec" + "github.com/ipld/go-ipld-prime/codec/dagjson" + "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/linking" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/node/basicnode" + "github.com/ipld/go-ipld-prime/storage/memstore" + "github.com/polydawn/refmt/json" + "github.com/urfave/cli/v2" + "golang.org/x/exp/slices" +) + +var ( + plusLineRegex = regexp.MustCompile(`^\+\+\+ ([\w-]+) ([\S]+ )?([\w]+)$`) +) + +// Compile is a command to translate between a human-debuggable patch-like format and a car file. +func CompileCar(c *cli.Context) error { + var err error + inStream := os.Stdin + if c.Args().Len() >= 1 { + inStream, err = os.Open(c.Args().First()) + if err != nil { + return err + } + } + + //parse headers. + br := bufio.NewReader(inStream) + header, _, err := br.ReadLine() + if err != nil { + return err + } + + v2 := strings.HasPrefix(string(header), "car compile --v2 ") + rest := strings.TrimPrefix(string(header), "car compile ") + if v2 { + rest = strings.TrimPrefix(rest, "--v2 ") + } + carName := strings.TrimSpace(rest) + + roots := make([]cid.Cid, 0) + for { + peek, err := br.Peek(4) + if err == io.EOF { + break + } else if err != nil { + return err + } + if bytes.Equal(peek, []byte("--- ")) { + break + } + rootLine, _, err := br.ReadLine() + if err != nil { + return err + } + if strings.HasPrefix(string(rootLine), "root ") { + var rCidS string + fmt.Sscanf(string(rootLine), "root %s", &rCidS) + rCid, err := cid.Parse(rCidS) + if err != nil { + return err + } + roots = append(roots, rCid) + } + } + + //parse blocks. + cidList := make([]cid.Cid, 0) + rawBlocks := make(map[cid.Cid][]byte) + rawCodecs := make(map[cid.Cid]string) + + for { + nextCid, mode, nextBlk, err := parsePatch(br) + if err == io.EOF { + break + } else if err != nil { + return err + } + rawBlocks[nextCid] = nextBlk + rawCodecs[nextCid] = mode + cidList = append(cidList, nextCid) + } + + // Re-create the original IPLD encoded blocks, but allowing for modifications of the + // patch data which may generate new CIDs; so we track the DAG relationships and + // rewrite CIDs in other referring where they get updated. + + // structure as a tree + childMap := make(map[cid.Cid][]cid.Cid) + for c := range rawBlocks { + if _, ok := childMap[c]; !ok { + childMap[c] = make([]cid.Cid, 0) + } + for d, blk := range rawBlocks { + if c.Equals(d) { + continue + } + if strings.Contains(string(blk), c.String()) { + if _, ok := childMap[d]; !ok { + childMap[d] = make([]cid.Cid, 0) + } + childMap[d] = append(childMap[d], c) + } else if strings.Contains(string(blk), string(c.Bytes())) { + if _, ok := childMap[d]; !ok { + childMap[d] = make([]cid.Cid, 0) + } + childMap[d] = append(childMap[d], c) + } + } + } + + // re-parse/re-build CIDs + outBlocks := make(map[cid.Cid][]byte) + for len(childMap) > 0 { + for origCid, kids := range childMap { + if len(kids) == 0 { + // compile to final cid + blk := rawBlocks[origCid] + finalCid, finalBlk, err := serializeBlock(c.Context, origCid.Prefix(), rawCodecs[origCid], blk) + if err != nil { + return err + } + outBlocks[finalCid] = finalBlk + idx := slices.Index(cidList, origCid) + cidList[idx] = finalCid + + // update other remaining nodes of the new cid. + for otherCid, otherKids := range childMap { + for i, otherKid := range otherKids { + if otherKid.Equals(origCid) { + if !finalCid.Equals(origCid) { + // update block + rawBlocks[otherCid] = bytes.ReplaceAll(rawBlocks[otherCid], origCid.Bytes(), finalCid.Bytes()) + rawBlocks[otherCid] = bytes.ReplaceAll(rawBlocks[otherCid], []byte(origCid.String()), []byte(finalCid.String())) + } + // remove from childMap + nok := append(otherKids[0:i], otherKids[i+1:]...) + childMap[otherCid] = nok + break // to next child map entry. + } + } + } + + delete(childMap, origCid) + } + } + } + + if !v2 { + // write output + outStream := os.Stdout + if c.IsSet("output") { + outFileName := c.String("output") + if outFileName == "" { + outFileName = carName + } + outFile, err := os.Create(outFileName) + if err != nil { + return err + } + defer outFile.Close() + outStream = outFile + } + + if err := carv1.WriteHeader(&carv1.CarHeader{ + Roots: roots, + Version: 1, + }, outStream); err != nil { + return err + } + for c, blk := range outBlocks { + if err := util.LdWrite(outStream, c.Bytes(), blk); err != nil { + return err + } + } + } else { + outFileName := c.String("output") + if outFileName == "" { + outFileName = carName + } + + if outFileName == "-" && !c.IsSet("output") { + return fmt.Errorf("cannot stream carv2's to stdout") + } + bs, err := blockstore.OpenReadWrite(outFileName, roots) + if err != nil { + return err + } + for _, bc := range cidList { + blk := outBlocks[bc] + ob, _ := blocks.NewBlockWithCid(blk, bc) + bs.Put(c.Context, ob) + } + return bs.Finalize() + } + + return nil +} + +func serializeBlock(ctx context.Context, codec cid.Prefix, encoding string, raw []byte) (cid.Cid, []byte, error) { + ls := cidlink.DefaultLinkSystem() + store := memstore.Store{Bag: map[string][]byte{}} + ls.SetReadStorage(&store) + ls.SetWriteStorage(&store) + b := basicnode.Prototype.Any.NewBuilder() + if encoding == "dag-json" { + if err := dagjson.Decode(b, bytes.NewBuffer(raw)); err != nil { + return cid.Undef, nil, err + } + } else if encoding == "raw" { + if err := b.AssignBytes(raw); err != nil { + return cid.Undef, nil, err + } + } else { + return cid.Undef, nil, fmt.Errorf("unknown encoding: %s", encoding) + } + lnk, err := ls.Store(linking.LinkContext{Ctx: ctx}, cidlink.LinkPrototype{Prefix: codec}, b.Build()) + if err != nil { + return cid.Undef, nil, err + } + outCid := lnk.(cidlink.Link).Cid + outBytes, outErr := store.Get(ctx, outCid.KeyString()) + return outCid, outBytes, outErr +} + +// DebugCar is a command to translate between a car file, and a human-debuggable patch-like format. +func DebugCar(c *cli.Context) error { + var err error + inStream := os.Stdin + inFile := "-" + if c.Args().Len() >= 1 { + inFile = c.Args().First() + inStream, err = os.Open(inFile) + if err != nil { + return err + } + } + + rd, err := carv2.NewBlockReader(inStream) + if err != nil { + return err + } + + // patch the header. + outStream := os.Stdout + if c.IsSet("output") { + outFileName := c.String("output") + outFile, err := os.Create(outFileName) + if err != nil { + return err + } + defer outFile.Close() + outStream = outFile + } + + outStream.WriteString("car compile ") + if rd.Version == 2 { + outStream.WriteString("--v2 ") + } + + outStream.WriteString(inFile + "\n") + for _, rt := range rd.Roots { + fmt.Fprintf(outStream, "root %s\n", rt.String()) + } + + // patch each block. + nxt, err := rd.Next() + if err != nil { + return err + } + for nxt != nil { + chunk, err := patch(c.Context, nxt.Cid(), nxt.RawData()) + if err != nil { + return err + } + outStream.Write(chunk) + + nxt, err = rd.Next() + if err == io.EOF { + return nil + } + } + + return nil +} + +func patch(ctx context.Context, c cid.Cid, blk []byte) ([]byte, error) { + ls := cidlink.DefaultLinkSystem() + store := memstore.Store{Bag: map[string][]byte{}} + ls.SetReadStorage(&store) + ls.SetWriteStorage(&store) + store.Put(ctx, c.KeyString(), blk) + node, err := ls.Load(linking.LinkContext{Ctx: ctx}, cidlink.Link{Cid: c}, basicnode.Prototype.Any) + if err != nil { + return nil, fmt.Errorf("could not load block: %q", err) + } + + outMode := "dag-json" + if node.Kind() == datamodel.Kind_Bytes && isPrintable(node) { + outMode = "raw" + } + finalBuf := bytes.NewBuffer(nil) + + if outMode == "dag-json" { + opts := dagjson.EncodeOptions{ + EncodeLinks: true, + EncodeBytes: true, + MapSortMode: codec.MapSortMode_Lexical, + } + if err := dagjson.Marshal(node, json.NewEncoder(finalBuf, json.EncodeOptions{Line: []byte{'\n'}, Indent: []byte{'\t'}}), opts); err != nil { + return nil, err + } + } else if outMode == "raw" { + nb, err := node.AsBytes() + if err != nil { + return nil, err + } + finalBuf.Write(nb) + } + + // figure out number of lines. + lcnt := strings.Count(finalBuf.String(), "\n") + crStr := " (no-end-cr)" + if finalBuf.Bytes()[len(finalBuf.Bytes())-1] == '\n' { + crStr = "" + } + + outBuf := bytes.NewBuffer(nil) + outBuf.WriteString("--- " + c.String() + "\n") + outBuf.WriteString("+++ " + outMode + crStr + " " + c.String() + "\n") + outBuf.WriteString(fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", 0, lcnt, 0, lcnt)) + outBuf.Write(finalBuf.Bytes()) + outBuf.WriteString("\n") + return outBuf.Bytes(), nil +} + +func isPrintable(n ipld.Node) bool { + b, err := n.AsBytes() + if err != nil { + return false + } + if !utf8.Valid(b) { + return false + } + if bytes.ContainsAny(b, string([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x10, 0x11, 0x12, 0x13, 0x14, 0x16, 0x17, 0x18, 0x19, 0x1c, 0x1d, 0x1e, 0x1f})) { + return false + } + // check if would confuse the 'end of patch' checker. + if bytes.Contains(b, []byte("\n--- ")) { + return false + } + return true +} + +func parsePatch(br *bufio.Reader) (cid.Cid, string, []byte, error) { + // read initial line to parse CID. + l1, isPrefix, err := br.ReadLine() + if err != nil { + return cid.Undef, "", nil, err + } + if isPrefix { + return cid.Undef, "", nil, fmt.Errorf("unexpected long header l1") + } + var cs string + if _, err := fmt.Sscanf(string(l1), "--- %s", &cs); err != nil { + return cid.Undef, "", nil, fmt.Errorf("could not parse patch cid line (%s): %q", l1, err) + } + l2, isPrefix, err := br.ReadLine() + if err != nil { + return cid.Undef, "", nil, err + } + if isPrefix { + return cid.Undef, "", nil, fmt.Errorf("unexpected long header l2") + } + var mode string + var noEndReturn bool + matches := plusLineRegex.FindSubmatch(l2) + if len(matches) >= 2 { + mode = string(matches[1]) + } + if len(matches) < 2 || string(matches[len(matches)-1]) != cs { + return cid.Undef, "", nil, fmt.Errorf("mismatched cid lines: %v", string(l2)) + } + if len(matches[2]) > 0 { + noEndReturn = (string(matches[2]) == "(no-end-cr) ") + } + c, err := cid.Parse(cs) + if err != nil { + return cid.Undef, "", nil, err + } + + // skip over @@ line. + l3, isPrefix, err := br.ReadLine() + if err != nil { + return cid.Undef, "", nil, err + } + if isPrefix { + return cid.Undef, "", nil, fmt.Errorf("unexpected long header l3") + } + if !strings.HasPrefix(string(l3), "@@") { + return cid.Undef, "", nil, fmt.Errorf("unexpected missing chunk prefix") + } + + // keep going until next chunk or end. + outBuf := bytes.NewBuffer(nil) + for { + peek, err := br.Peek(4) + if err != nil && err != io.EOF { + return cid.Undef, "", nil, err + } + if bytes.Equal(peek, []byte("--- ")) { + break + } + // accumulate to buffer. + l, err := br.ReadBytes('\n') + if l != nil { + outBuf.Write(l) + } + if err == io.EOF { + break + } else if err != nil { + return cid.Undef, "", nil, err + } + } + + ob := outBuf.Bytes() + + // remove the final line return + if len(ob) > 2 && bytes.Equal(ob[len(ob)-2:], []byte("\r\n")) { + ob = ob[:len(ob)-2] + } else if len(ob) > 1 && bytes.Equal(ob[len(ob)-1:], []byte("\n")) { + ob = ob[:len(ob)-1] + } + + if noEndReturn && len(ob) > 2 && bytes.Equal(ob[len(ob)-2:], []byte("\r\n")) { + ob = ob[:len(ob)-2] + } else if noEndReturn && len(ob) > 1 && bytes.Equal(ob[len(ob)-1:], []byte("\n")) { + ob = ob[:len(ob)-1] + } + + return c, mode, ob, nil +} diff --git a/ipld/car/cmd/car/testdata/script/compile.txt b/ipld/car/cmd/car/testdata/script/compile.txt new file mode 100644 index 0000000000..4607ca6c66 --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/compile.txt @@ -0,0 +1,28 @@ +# debug a car to patch +car debug -o out.patch ${INPUTS}/sample-v1.car +! stderr . +grep -count=1049 \+\+\+ out.patch + +# recompile to binary +car compile -o out.car out.patch +! stderr . + +# should have same blocks as it started with. +car ls out.car +stdout -count=1043 '^bafy' +stdout -count=6 '^bafk' + +# make a small car +car create --file=small.car foo.txt + +car debug -o small.patch small.car +! stderr . + +car compile -o new.car small.patch +! stderr . + +# confirm roundtrip is stable. +cmp small.car new.car + +-- foo.txt -- +hello world \ No newline at end of file From e3a1b784c55339b84c60ad47b94e95076738f1f8 Mon Sep 17 00:00:00 2001 From: Adin Schmahmann Date: Mon, 16 Jan 2023 00:26:48 -0500 Subject: [PATCH 256/291] feat(cmd): add index create subcommand to create an external carv2 index This commit was moved from ipld/go-car@7df51ce8b18b10ccdef60575174470f19486dc24 --- ipld/car/cmd/car/car.go | 5 +++++ ipld/car/cmd/car/index.go | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index c66232f7d4..70c9eb32a2 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -153,6 +153,11 @@ func main1() int { Usage: "Write output as a v1 or v2 format car", }, }, + Subcommands: []*cli.Command{{ + Name: "create", + Usage: "Write out a detached index", + Action: CreateIndex, + }}, }, { Name: "inspect", diff --git a/ipld/car/cmd/car/index.go b/ipld/car/cmd/car/index.go index 031df2ada8..fc6bd5d611 100644 --- a/ipld/car/cmd/car/index.go +++ b/ipld/car/cmd/car/index.go @@ -166,3 +166,45 @@ func IndexCar(c *cli.Context) error { _, err = index.WriteTo(idx, outStream) return err } + +// CreateIndex is a command to write out an index of the CAR file +func CreateIndex(c *cli.Context) error { + r, err := carv2.OpenReader(c.Args().Get(0)) + if err != nil { + return err + } + defer r.Close() + + outStream := os.Stdout + if c.Args().Len() >= 2 { + outStream, err = os.Create(c.Args().Get(1)) + if err != nil { + return err + } + } + defer outStream.Close() + + var mc multicodec.Code + if err := mc.Set(c.String("codec")); err != nil { + return err + } + idx, err := index.New(mc) + if err != nil { + return err + } + + dr, err := r.DataReader() + if err != nil { + return err + } + + if err := carv2.LoadIndex(idx, dr); err != nil { + return err + } + + if _, err := index.WriteTo(idx, outStream); err != nil { + return err + } + + return nil +} From d9db510a2d28c1ca712654c576028fff98c5731d Mon Sep 17 00:00:00 2001 From: Andrew Gillis Date: Wed, 18 Jan 2023 14:45:05 -0800 Subject: [PATCH 257/291] Update install instructions in README.md `go get` is no longer supported outside a module. To build and install a command, use 'go install' with a version. For more information, see https://golang.org/doc/go-get-install-deprecation This commit was moved from ipld/go-car@8d96b72babb190da5d08562a22f82b44ec05ff94 --- ipld/car/README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ipld/car/README.md b/ipld/car/README.md index 0f2157991f..91302e2f8a 100644 --- a/ipld/car/README.md +++ b/ipld/car/README.md @@ -30,15 +30,12 @@ Most users should use v2, especially for new software, since the v2 API transpar ## Install -To install the latest version of `go-car/v2` module, run: +To install the latest version of the `car` executable, run: ```shell script -go get github.com/ipld/go-car/v2 +go install github.com/ipld/go-car/cmd/car@latest ``` -Alternatively, to install the v0 module, run: -```shell script -go get github.com/ipld/go-car -``` +This will install the `car` executable into `$GOPATH/bin/` ## API Documentation From 06ff64e3087e1f86fa3b90c1378d8010e052673b Mon Sep 17 00:00:00 2001 From: Will Date: Mon, 23 Jan 2023 08:45:44 +0100 Subject: [PATCH 258/291] cleanup readme a bit to make the cli more discoverable (#353) This commit was moved from ipld/go-car@2e16e87304353f704b8aff0614b96681911f6e72 --- ipld/car/README.md | 30 +++++++++++++++++------------- ipld/car/cmd/car/README.md | 4 ++-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/ipld/car/README.md b/ipld/car/README.md index 91302e2f8a..af75fa1d73 100644 --- a/ipld/car/README.md +++ b/ipld/car/README.md @@ -7,18 +7,30 @@ go-car (go!) [![Go Reference](https://pkg.go.dev/badge/github.com/ipld/go-car.svg)](https://pkg.go.dev/github.com/ipld/go-car) [![Coverage Status](https://codecov.io/gh/ipld/go-car/branch/master/graph/badge.svg)](https://codecov.io/gh/ipld/go-car/branch/master) -> A library to interact with merkledags stored as a single file +> Work with car (Content addressed ARchive) files! -This is a Go implementation of the [CAR specifications](https://ipld.io/specs/transport/car/), both [CARv1](https://ipld.io/specs/transport/car/carv1/) and [CARv2](https://ipld.io/specs/transport/car/carv2/). +This is a Golang implementation of the [CAR specifications](https://ipld.io/specs/transport/car/), both [CARv1](https://ipld.io/specs/transport/car/carv1/) and [CARv2](https://ipld.io/specs/transport/car/carv2/). -Note that there are two major module versions: +As a format, there are two major module versions: * [`go-car/v2`](v2/) is geared towards reading and writing CARv2 files, and also supports consuming CARv1 files and using CAR files as an IPFS blockstore. -* `go-car` v0, in the root directory, just supports reading and writing CARv1 files. +* `go-car`, in the root directory, only supports reading and writing CARv1 files. Most users should use v2, especially for new software, since the v2 API transparently supports both CAR formats. +## Usage / Installation + +This repository provides a `car` binary that can be used for creating, extracting, and working with car files. + +To install the latest version of `car`, run: +```shell script +go install github.com/ipld/go-car/cmd/car@latest +``` + +More information about this binary is available in [`cmd/car`](cmd/car/) + + ## Features [CARv2](v2) features: @@ -28,14 +40,6 @@ Most users should use v2, especially for new software, since the v2 API transpar * Write CARv2 files via [Read-Write blockstore](https://pkg.go.dev/github.com/ipld/go-car/v2/blockstore#OpenReadWrite) API, with support for appending blocks to an existing CARv2 file, and resumption from a partially written CARv2 files. * Individual access to [inner CARv1 data payload]((https://pkg.go.dev/github.com/ipld/go-car/v2#Reader.DataReader)) and [index]((https://pkg.go.dev/github.com/ipld/go-car/v2#Reader.IndexReader)) of a CARv2 file via the `Reader` API. -## Install - -To install the latest version of the `car` executable, run: -```shell script -go install github.com/ipld/go-car/cmd/car@latest -``` - -This will install the `car` executable into `$GOPATH/bin/` ## API Documentation @@ -53,8 +57,8 @@ Here is a shortlist of other examples from the documentation ## Maintainers -* [Daniel Martí](https://github.com/mvdan) * [Masih Derkani](https://github.com/masih) +* [Will Scott](https://github.com/willscott) ## Contribute diff --git a/ipld/car/cmd/car/README.md b/ipld/car/cmd/car/README.md index ef04a0dc3f..901b75cb03 100644 --- a/ipld/car/cmd/car/README.md +++ b/ipld/car/cmd/car/README.md @@ -5,7 +5,7 @@ car - The CLI tool [![](https://img.shields.io/badge/project-ipld-orange.svg?style=flat-square)](https://github.com/ipld/ipld) [![](https://img.shields.io/badge/matrix-%23ipld-blue.svg?style=flat-square)](https://matrix.to/#/#ipld:ipfs.io) -> A CLI to interact with car files +> A CLI for interacting with car files ## Usage @@ -30,7 +30,7 @@ COMMANDS: ## Install -To install the latest version of `car` module, run: +To install the latest version of `car`, run: ```shell script go install github.com/ipld/go-car/cmd/car@latest ``` From bafac68d186a7e769e444c5a37d4ff6fbe1fb05f Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 27 Jan 2023 10:59:40 +0100 Subject: [PATCH 259/291] feat: upgrade from go-block-format to go-libipfs/blocks This commit was moved from ipld/go-car@377b78873a76a59450745c83d15079318e2bb58a --- ipld/car/car.go | 2 +- ipld/car/cmd/car/compile.go | 2 +- ipld/car/cmd/car/create.go | 2 +- ipld/car/selectivecar_test.go | 2 +- ipld/car/v2/block_reader.go | 2 +- ipld/car/v2/blockstore/example_test.go | 2 +- ipld/car/v2/blockstore/readonly.go | 2 +- ipld/car/v2/blockstore/readonly_test.go | 2 +- ipld/car/v2/blockstore/readwrite.go | 2 +- ipld/car/v2/blockstore/readwrite_test.go | 2 +- ipld/car/v2/index/index_test.go | 2 +- ipld/car/v2/internal/carv1/car.go | 2 +- ipld/car/v2/selective_test.go | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index bf773779a4..909a83ad9d 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -6,10 +6,10 @@ import ( "fmt" "io" - blocks "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" format "github.com/ipfs/go-ipld-format" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-merkledag" util "github.com/ipld/go-car/util" diff --git a/ipld/car/cmd/car/compile.go b/ipld/car/cmd/car/compile.go index 0a74d1c7e8..f6a1b49791 100644 --- a/ipld/car/cmd/car/compile.go +++ b/ipld/car/cmd/car/compile.go @@ -11,8 +11,8 @@ import ( "strings" "unicode/utf8" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + blocks "github.com/ipfs/go-libipfs/blocks" carv1 "github.com/ipld/go-car" "github.com/ipld/go-car/util" carv2 "github.com/ipld/go-car/v2" diff --git a/ipld/car/cmd/car/create.go b/ipld/car/cmd/car/create.go index 3a76c91316..7b50b6458e 100644 --- a/ipld/car/cmd/car/create.go +++ b/ipld/car/cmd/car/create.go @@ -7,8 +7,8 @@ import ( "io" "path" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-unixfsnode/data/builder" "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" diff --git a/ipld/car/selectivecar_test.go b/ipld/car/selectivecar_test.go index 59a4c8e1ff..33c2ce705d 100644 --- a/ipld/car/selectivecar_test.go +++ b/ipld/car/selectivecar_test.go @@ -5,9 +5,9 @@ import ( "context" "testing" - blocks "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-merkledag" dstest "github.com/ipfs/go-merkledag/test" car "github.com/ipld/go-car" diff --git a/ipld/car/v2/block_reader.go b/ipld/car/v2/block_reader.go index 0c38412fd7..27d134b059 100644 --- a/ipld/car/v2/block_reader.go +++ b/ipld/car/v2/block_reader.go @@ -4,8 +4,8 @@ import ( "fmt" "io" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" diff --git a/ipld/car/v2/blockstore/example_test.go b/ipld/car/v2/blockstore/example_test.go index ec16383eb5..9c7524a9c3 100644 --- a/ipld/car/v2/blockstore/example_test.go +++ b/ipld/car/v2/blockstore/example_test.go @@ -7,8 +7,8 @@ import ( "path/filepath" "time" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-merkledag" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index bfca69112a..ab1c6b5271 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -8,10 +8,10 @@ import ( "io" "sync" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" format "github.com/ipfs/go-ipld-format" + blocks "github.com/ipfs/go-libipfs/blocks" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 6b1b009f89..8a03597d2e 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - blocks "github.com/ipfs/go-block-format" + blocks "github.com/ipfs/go-libipfs/blocks\" "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" "github.com/ipfs/go-merkledag" diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 542d0df42d..6d45a0fa08 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -7,9 +7,9 @@ import ( "io" "os" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/multiformats/go-varint" carv2 "github.com/ipld/go-car/v2" diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 4553fd15f2..cbcd1d4c13 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -13,10 +13,10 @@ import ( "testing" "time" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" format "github.com/ipfs/go-ipld-format" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-merkledag" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index 972b4c669a..b7f83d3c48 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -7,7 +7,7 @@ import ( "path/filepath" "testing" - blocks "github.com/ipfs/go-block-format" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" "github.com/multiformats/go-multicodec" diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index 8ad012aa62..f56b382461 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -7,10 +7,10 @@ import ( "github.com/ipld/go-car/v2/internal/carv1/util" - blocks "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" format "github.com/ipfs/go-ipld-format" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-merkledag" ) diff --git a/ipld/car/v2/selective_test.go b/ipld/car/v2/selective_test.go index 924351d46a..9693309281 100644 --- a/ipld/car/v2/selective_test.go +++ b/ipld/car/v2/selective_test.go @@ -8,8 +8,8 @@ import ( "path" "testing" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-unixfsnode" "github.com/ipfs/go-unixfsnode/data/builder" "github.com/ipld/go-car/v2" From b4cd7d47161f7856a8e190c9241946baecb5270c Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 27 Jan 2023 14:29:53 +0100 Subject: [PATCH 260/291] fix: update go-block-format to the version that includes the stubs This commit was moved from ipld/go-car@020b14ce754924964ef37c66876688e9b84a17b1 --- ipld/car/v2/blockstore/readonly_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 8a03597d2e..0bb4df42ea 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -8,9 +8,9 @@ import ( "testing" "time" - blocks "github.com/ipfs/go-libipfs/blocks\" "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" + blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-merkledag" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/internal/carv1" From 8016547c7eec77d4c68eacc26d8b9c6000111f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Leszko?= Date: Fri, 27 Jan 2023 20:35:43 +0100 Subject: [PATCH 261/291] Allow using WalkOption in WriteCar function This commit was moved from ipld/go-car@620c2941db620ad850ea02f644caca7340539661 --- ipld/car/car.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ipld/car/car.go b/ipld/car/car.go index 909a83ad9d..026bbb7359 100644 --- a/ipld/car/car.go +++ b/ipld/car/car.go @@ -40,11 +40,11 @@ type carWriter struct { type WalkFunc func(format.Node) ([]*format.Link, error) -func WriteCar(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.Writer) error { - return WriteCarWithWalker(ctx, ds, roots, w, DefaultWalkFunc) +func WriteCar(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.Writer, options ...merkledag.WalkOption) error { + return WriteCarWithWalker(ctx, ds, roots, w, DefaultWalkFunc, options...) } -func WriteCarWithWalker(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.Writer, walk WalkFunc) error { +func WriteCarWithWalker(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.Writer, walk WalkFunc, options ...merkledag.WalkOption) error { h := &CarHeader{ Roots: roots, @@ -58,7 +58,7 @@ func WriteCarWithWalker(ctx context.Context, ds format.NodeGetter, roots []cid.C cw := &carWriter{ds: ds, w: w, walk: walk} seen := cid.NewSet() for _, r := range roots { - if err := merkledag.Walk(ctx, cw.enumGetLinks, r, seen.Visit); err != nil { + if err := merkledag.Walk(ctx, cw.enumGetLinks, r, seen.Visit, options...); err != nil { return err } } From f17129cc05a628c4939899740ff0e7fac138dfdc Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 27 Jan 2023 18:18:06 +1100 Subject: [PATCH 262/291] fix: use goreleaser instead This commit was moved from ipld/go-car@c839519a92d238c9f3b8d3ddfb2dec1c5ae7c5ca --- ipld/car/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/ipld/car/.gitignore b/ipld/car/.gitignore index 3b831d277f..b3f7c18ae7 100644 --- a/ipld/car/.gitignore +++ b/ipld/car/.gitignore @@ -1,3 +1,4 @@ car/car main coverage.txt +dist/ From 3706bb9a0a95c3ef9d10b4f8171d0ff7ae216883 Mon Sep 17 00:00:00 2001 From: Adin Schmahmann Date: Mon, 30 Jan 2023 11:03:31 -0500 Subject: [PATCH 263/291] test(cmd): add index creation test This commit was moved from ipld/go-car@4038bee446e9a87507783aea936493f0a5f9234b --- ipld/car/cmd/car/testdata/script/index-create.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 ipld/car/cmd/car/testdata/script/index-create.txt diff --git a/ipld/car/cmd/car/testdata/script/index-create.txt b/ipld/car/cmd/car/testdata/script/index-create.txt new file mode 100644 index 0000000000..bfdfe65c1d --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/index-create.txt @@ -0,0 +1,3 @@ +car index create ${INPUTS}/sample-v1.car sample-v1.car.idx +car detach-index ${INPUTS}/sample-wrapped-v2.car sample-wrapped-v2.car.idx +cmp sample-v1.car.idx sample-wrapped-v2.car.idx From 599eae33f8d0c6ca62160b679dade5bb9240ee20 Mon Sep 17 00:00:00 2001 From: hannahhoward Date: Fri, 13 Jan 2023 21:04:12 -0800 Subject: [PATCH 264/291] feat(blockstore): implement a streaming read only storage This commit was moved from ipld/go-car@b72cec8b70c1a634b327c92ac355ede7785c608f --- ipld/car/v2/blockstore/readonlystorage.go | 161 ++++++++++++++++++ .../car/v2/blockstore/readonlystorage_test.go | 116 +++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 ipld/car/v2/blockstore/readonlystorage.go create mode 100644 ipld/car/v2/blockstore/readonlystorage_test.go diff --git a/ipld/car/v2/blockstore/readonlystorage.go b/ipld/car/v2/blockstore/readonlystorage.go new file mode 100644 index 0000000000..dfd5021fa4 --- /dev/null +++ b/ipld/car/v2/blockstore/readonlystorage.go @@ -0,0 +1,161 @@ +package blockstore + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + + "github.com/ipfs/go-cid" + blockstore "github.com/ipfs/go-ipfs-blockstore" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/ipld/go-ipld-prime/storage" + "github.com/multiformats/go-varint" +) + +var _ storage.ReadableStorage = (*ReadOnlyStorage)(nil) +var _ storage.StreamingReadableStorage = (*ReadOnlyStorage)(nil) + +type ReadOnlyStorage struct { + ro *ReadOnly +} + +func NewReadOnlyStorage(backing io.ReaderAt, idx index.Index, opts ...carv2.Option) (*ReadOnlyStorage, error) { + ro, err := NewReadOnly(backing, idx, opts...) + if err != nil { + return nil, err + } + return &ReadOnlyStorage{ro: ro}, nil +} + +// OpenReadOnlyStorage opens a read-only blockstore from a CAR file (either v1 or v2), generating an index if it does not exist. +// Note, the generated index if the index does not exist is ephemeral and only stored in memory. +// See car.GenerateIndex and Index.Attach for persisting index onto a CAR file. +func OpenReadOnlyStorage(path string, opts ...carv2.Option) (*ReadOnlyStorage, error) { + ro, err := OpenReadOnly(path, opts...) + if err != nil { + return nil, err + } + return &ReadOnlyStorage{ro: ro}, nil +} + +func (ros *ReadOnlyStorage) Has(ctx context.Context, keyStr string) (bool, error) { + // Do the inverse of cid.KeyString(), + // which is how a valid key for this adapter must've been produced. + key, err := cidFromBinString(keyStr) + if err != nil { + return false, err + } + + return ros.ro.Has(ctx, key) +} + +func (ros *ReadOnlyStorage) Get(ctx context.Context, key string) ([]byte, error) { + // Do the inverse of cid.KeyString(), + // which is how a valid key for this adapter must've been produced. + k, err := cidFromBinString(key) + if err != nil { + return nil, err + } + + // Delegate the Get call. + block, err := ros.ro.Get(ctx, k) + if err != nil { + return nil, err + } + + // Unwrap the actual raw data for return. + // Discard the rest. (It's a shame there was an alloc for that structure.) + return block.RawData(), nil +} + +func (ros *ReadOnlyStorage) GetStream(ctx context.Context, keyStr string) (io.ReadCloser, error) { + // Do the inverse of cid.KeyString(), + // which is how a valid key for this adapter must've been produced. + key, err := cidFromBinString(keyStr) + if err != nil { + return nil, err + } + + // Check if the given CID has multihash.IDENTITY code + // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. + if digest, ok, err := isIdentity(key); err != nil { + return nil, err + } else if ok { + return io.NopCloser(bytes.NewReader(digest)), nil + } + + ros.ro.mu.RLock() + defer ros.ro.mu.RUnlock() + + if ros.ro.closed { + return nil, errClosed + } + + fnSize := -1 + var fnErr error + var foundOffset int64 + err = ros.ro.idx.GetAll(key, func(offset uint64) bool { + rdr := internalio.NewOffsetReadSeeker(ros.ro.backing, int64(offset)) + sectionLen, err := varint.ReadUvarint(rdr) + if err != nil { + fnErr = err + return false + } + cidLen, readCid, err := cid.CidFromReader(rdr) + if err != nil { + fnErr = err + return false + } + if ros.ro.opts.BlockstoreUseWholeCIDs { + if readCid.Equals(key) { + fnSize = int(sectionLen) - cidLen + foundOffset = rdr.Offset() + return false + } else { + return true // continue looking + } + } else { + if bytes.Equal(readCid.Hash(), key.Hash()) { + fnSize = int(sectionLen) - cidLen + foundOffset = rdr.Offset() + } + return false + } + }) + if errors.Is(err, index.ErrNotFound) { + return nil, blockstore.ErrNotFound + } else if err != nil { + return nil, err + } else if fnErr != nil { + return nil, fnErr + } + if fnSize == -1 { + return nil, blockstore.ErrNotFound + } + return io.NopCloser(io.NewSectionReader(ros.ro.backing, foundOffset, int64(fnSize))), nil +} + +func (ros *ReadOnlyStorage) Close() error { + return ros.ro.Close() +} + +func (ros *ReadOnlyStorage) Roots() ([]cid.Cid, error) { + return ros.ro.Roots() +} + +// Do the inverse of cid.KeyString(). +// (Unclear why go-cid doesn't offer a function for this itself.) +func cidFromBinString(key string) (cid.Cid, error) { + l, k, err := cid.CidFromBytes([]byte(key)) + if err != nil { + return cid.Undef, fmt.Errorf("key was not a cid: %w", err) + } + if l != len(key) { + return cid.Undef, fmt.Errorf("key was not a cid: had %d bytes leftover", len(key)-l) + } + return k, nil +} diff --git a/ipld/car/v2/blockstore/readonlystorage_test.go b/ipld/car/v2/blockstore/readonlystorage_test.go new file mode 100644 index 0000000000..d783c0c906 --- /dev/null +++ b/ipld/car/v2/blockstore/readonlystorage_test.go @@ -0,0 +1,116 @@ +package blockstore + +import ( + "context" + "io" + "os" + "testing" + "time" + + "github.com/ipfs/go-cid" + blockstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipfs/go-merkledag" + carv2 "github.com/ipld/go-car/v2" + "github.com/stretchr/testify/require" +) + +func TestReadOnlyStorageGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) { + subject, err := OpenReadOnlyStorage("../testdata/sample-v1.car") + require.NoError(t, err) + nonExistingKey := merkledag.NewRawNode([]byte("lobstermuncher")).Block.Cid() + + // Assert blockstore API returns blockstore.ErrNotFound + gotBlock, err := subject.Get(context.TODO(), string(nonExistingKey.Bytes())) + require.Equal(t, blockstore.ErrNotFound, err) + require.Nil(t, gotBlock) +} + +func TestReadOnlyStorage(t *testing.T) { + tests := []struct { + name string + v1OrV2path string + opts []carv2.Option + }{ + { + "OpenedWithCarV1", + "../testdata/sample-v1.car", + []carv2.Option{UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, + }, + { + "OpenedWithCarV2", + "../testdata/sample-wrapped-v2.car", + []carv2.Option{UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, + }, + { + "OpenedWithCarV1ZeroLenSection", + "../testdata/sample-v1-with-zero-len-section.car", + []carv2.Option{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + }, + { + "OpenedWithAnotherCarV1ZeroLenSection", + "../testdata/sample-v1-with-zero-len-section2.car", + []carv2.Option{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() + subject, err := OpenReadOnlyStorage(tt.v1OrV2path, tt.opts...) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, subject.Close()) }) + + f, err := os.Open(tt.v1OrV2path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + + reader, err := carv2.NewBlockReader(f, tt.opts...) + require.NoError(t, err) + + // Assert roots match v1 payload. + wantRoots := reader.Roots + gotRoots, err := subject.Roots() + require.NoError(t, err) + require.Equal(t, wantRoots, gotRoots) + + var wantCids []cid.Cid + for { + wantBlock, err := reader.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + key := wantBlock.Cid() + wantCids = append(wantCids, key) + + // Assert blockstore contains key. + has, err := subject.Has(ctx, key.KeyString()) + require.NoError(t, err) + require.True(t, has) + + // Assert block itself matches v1 payload block. + gotBlock, err := subject.Get(ctx, key.KeyString()) + require.NoError(t, err) + require.Equal(t, wantBlock.RawData(), gotBlock) + + reader, err := subject.GetStream(ctx, key.KeyString()) + require.NoError(t, err) + data, err := io.ReadAll(reader) + require.NoError(t, err) + require.Equal(t, wantBlock.RawData(), data) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + }) + } +} + +func TestNewReadOnlyStorageFailsOnUnknownVersion(t *testing.T) { + f, err := os.Open("../testdata/sample-rootless-v42.car") + require.NoError(t, err) + t.Cleanup(func() { f.Close() }) + subject, err := NewReadOnlyStorage(f, nil) + require.Errorf(t, err, "unsupported car version: 42") + require.Nil(t, subject) +} From 0e32290886fd8dd771af800d09c471e475e3ad6c Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 2 Feb 2023 15:53:15 +1100 Subject: [PATCH 265/291] feat: StorageCar as a Readable storage, separate from blockstore This commit was moved from ipld/go-car@369ddf3e702226bb2981722b22a6ba244e40fa43 --- ipld/car/v2/blockstore/readonlystorage.go | 161 ----------- .../car/v2/blockstore/readonlystorage_test.go | 116 -------- ipld/car/v2/blockstore/readwrite.go | 13 +- ipld/car/v2/internal/carv1/car.go | 16 ++ .../insertionindex}/insertionindex.go | 46 ++- ipld/car/v2/internal/store/identity.go | 16 ++ ipld/car/v2/internal/store/index.go | 30 ++ ipld/car/v2/internal/store/version.go | 17 ++ ipld/car/v2/storage/storage.go | 271 ++++++++++++++++++ ipld/car/v2/storage/storage_test.go | 122 ++++++++ 10 files changed, 501 insertions(+), 307 deletions(-) delete mode 100644 ipld/car/v2/blockstore/readonlystorage.go delete mode 100644 ipld/car/v2/blockstore/readonlystorage_test.go rename ipld/car/v2/{blockstore => internal/insertionindex}/insertionindex.go (84%) create mode 100644 ipld/car/v2/internal/store/identity.go create mode 100644 ipld/car/v2/internal/store/index.go create mode 100644 ipld/car/v2/internal/store/version.go create mode 100644 ipld/car/v2/storage/storage.go create mode 100644 ipld/car/v2/storage/storage_test.go diff --git a/ipld/car/v2/blockstore/readonlystorage.go b/ipld/car/v2/blockstore/readonlystorage.go deleted file mode 100644 index dfd5021fa4..0000000000 --- a/ipld/car/v2/blockstore/readonlystorage.go +++ /dev/null @@ -1,161 +0,0 @@ -package blockstore - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - - "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" - carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/index" - internalio "github.com/ipld/go-car/v2/internal/io" - "github.com/ipld/go-ipld-prime/storage" - "github.com/multiformats/go-varint" -) - -var _ storage.ReadableStorage = (*ReadOnlyStorage)(nil) -var _ storage.StreamingReadableStorage = (*ReadOnlyStorage)(nil) - -type ReadOnlyStorage struct { - ro *ReadOnly -} - -func NewReadOnlyStorage(backing io.ReaderAt, idx index.Index, opts ...carv2.Option) (*ReadOnlyStorage, error) { - ro, err := NewReadOnly(backing, idx, opts...) - if err != nil { - return nil, err - } - return &ReadOnlyStorage{ro: ro}, nil -} - -// OpenReadOnlyStorage opens a read-only blockstore from a CAR file (either v1 or v2), generating an index if it does not exist. -// Note, the generated index if the index does not exist is ephemeral and only stored in memory. -// See car.GenerateIndex and Index.Attach for persisting index onto a CAR file. -func OpenReadOnlyStorage(path string, opts ...carv2.Option) (*ReadOnlyStorage, error) { - ro, err := OpenReadOnly(path, opts...) - if err != nil { - return nil, err - } - return &ReadOnlyStorage{ro: ro}, nil -} - -func (ros *ReadOnlyStorage) Has(ctx context.Context, keyStr string) (bool, error) { - // Do the inverse of cid.KeyString(), - // which is how a valid key for this adapter must've been produced. - key, err := cidFromBinString(keyStr) - if err != nil { - return false, err - } - - return ros.ro.Has(ctx, key) -} - -func (ros *ReadOnlyStorage) Get(ctx context.Context, key string) ([]byte, error) { - // Do the inverse of cid.KeyString(), - // which is how a valid key for this adapter must've been produced. - k, err := cidFromBinString(key) - if err != nil { - return nil, err - } - - // Delegate the Get call. - block, err := ros.ro.Get(ctx, k) - if err != nil { - return nil, err - } - - // Unwrap the actual raw data for return. - // Discard the rest. (It's a shame there was an alloc for that structure.) - return block.RawData(), nil -} - -func (ros *ReadOnlyStorage) GetStream(ctx context.Context, keyStr string) (io.ReadCloser, error) { - // Do the inverse of cid.KeyString(), - // which is how a valid key for this adapter must've been produced. - key, err := cidFromBinString(keyStr) - if err != nil { - return nil, err - } - - // Check if the given CID has multihash.IDENTITY code - // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. - if digest, ok, err := isIdentity(key); err != nil { - return nil, err - } else if ok { - return io.NopCloser(bytes.NewReader(digest)), nil - } - - ros.ro.mu.RLock() - defer ros.ro.mu.RUnlock() - - if ros.ro.closed { - return nil, errClosed - } - - fnSize := -1 - var fnErr error - var foundOffset int64 - err = ros.ro.idx.GetAll(key, func(offset uint64) bool { - rdr := internalio.NewOffsetReadSeeker(ros.ro.backing, int64(offset)) - sectionLen, err := varint.ReadUvarint(rdr) - if err != nil { - fnErr = err - return false - } - cidLen, readCid, err := cid.CidFromReader(rdr) - if err != nil { - fnErr = err - return false - } - if ros.ro.opts.BlockstoreUseWholeCIDs { - if readCid.Equals(key) { - fnSize = int(sectionLen) - cidLen - foundOffset = rdr.Offset() - return false - } else { - return true // continue looking - } - } else { - if bytes.Equal(readCid.Hash(), key.Hash()) { - fnSize = int(sectionLen) - cidLen - foundOffset = rdr.Offset() - } - return false - } - }) - if errors.Is(err, index.ErrNotFound) { - return nil, blockstore.ErrNotFound - } else if err != nil { - return nil, err - } else if fnErr != nil { - return nil, fnErr - } - if fnSize == -1 { - return nil, blockstore.ErrNotFound - } - return io.NopCloser(io.NewSectionReader(ros.ro.backing, foundOffset, int64(fnSize))), nil -} - -func (ros *ReadOnlyStorage) Close() error { - return ros.ro.Close() -} - -func (ros *ReadOnlyStorage) Roots() ([]cid.Cid, error) { - return ros.ro.Roots() -} - -// Do the inverse of cid.KeyString(). -// (Unclear why go-cid doesn't offer a function for this itself.) -func cidFromBinString(key string) (cid.Cid, error) { - l, k, err := cid.CidFromBytes([]byte(key)) - if err != nil { - return cid.Undef, fmt.Errorf("key was not a cid: %w", err) - } - if l != len(key) { - return cid.Undef, fmt.Errorf("key was not a cid: had %d bytes leftover", len(key)-l) - } - return k, nil -} diff --git a/ipld/car/v2/blockstore/readonlystorage_test.go b/ipld/car/v2/blockstore/readonlystorage_test.go deleted file mode 100644 index d783c0c906..0000000000 --- a/ipld/car/v2/blockstore/readonlystorage_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package blockstore - -import ( - "context" - "io" - "os" - "testing" - "time" - - "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" - "github.com/ipfs/go-merkledag" - carv2 "github.com/ipld/go-car/v2" - "github.com/stretchr/testify/require" -) - -func TestReadOnlyStorageGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) { - subject, err := OpenReadOnlyStorage("../testdata/sample-v1.car") - require.NoError(t, err) - nonExistingKey := merkledag.NewRawNode([]byte("lobstermuncher")).Block.Cid() - - // Assert blockstore API returns blockstore.ErrNotFound - gotBlock, err := subject.Get(context.TODO(), string(nonExistingKey.Bytes())) - require.Equal(t, blockstore.ErrNotFound, err) - require.Nil(t, gotBlock) -} - -func TestReadOnlyStorage(t *testing.T) { - tests := []struct { - name string - v1OrV2path string - opts []carv2.Option - }{ - { - "OpenedWithCarV1", - "../testdata/sample-v1.car", - []carv2.Option{UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, - }, - { - "OpenedWithCarV2", - "../testdata/sample-wrapped-v2.car", - []carv2.Option{UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, - }, - { - "OpenedWithCarV1ZeroLenSection", - "../testdata/sample-v1-with-zero-len-section.car", - []carv2.Option{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, - }, - { - "OpenedWithAnotherCarV1ZeroLenSection", - "../testdata/sample-v1-with-zero-len-section2.car", - []carv2.Option{UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx := context.TODO() - subject, err := OpenReadOnlyStorage(tt.v1OrV2path, tt.opts...) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, subject.Close()) }) - - f, err := os.Open(tt.v1OrV2path) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, f.Close()) }) - - reader, err := carv2.NewBlockReader(f, tt.opts...) - require.NoError(t, err) - - // Assert roots match v1 payload. - wantRoots := reader.Roots - gotRoots, err := subject.Roots() - require.NoError(t, err) - require.Equal(t, wantRoots, gotRoots) - - var wantCids []cid.Cid - for { - wantBlock, err := reader.Next() - if err == io.EOF { - break - } - require.NoError(t, err) - - key := wantBlock.Cid() - wantCids = append(wantCids, key) - - // Assert blockstore contains key. - has, err := subject.Has(ctx, key.KeyString()) - require.NoError(t, err) - require.True(t, has) - - // Assert block itself matches v1 payload block. - gotBlock, err := subject.Get(ctx, key.KeyString()) - require.NoError(t, err) - require.Equal(t, wantBlock.RawData(), gotBlock) - - reader, err := subject.GetStream(ctx, key.KeyString()) - require.NoError(t, err) - data, err := io.ReadAll(reader) - require.NoError(t, err) - require.Equal(t, wantBlock.RawData(), data) - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) - defer cancel() - }) - } -} - -func TestNewReadOnlyStorageFailsOnUnknownVersion(t *testing.T) { - f, err := os.Open("../testdata/sample-rootless-v42.car") - require.NoError(t, err) - t.Cleanup(func() { f.Close() }) - subject, err := NewReadOnlyStorage(f, nil) - require.Errorf(t, err, "unsupported car version: 42") - require.Nil(t, subject) -} diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 6d45a0fa08..c6a7e9b8f0 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -16,6 +16,7 @@ import ( "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" + "github.com/ipld/go-car/v2/internal/insertionindex" internalio "github.com/ipld/go-car/v2/internal/io" ) @@ -35,7 +36,7 @@ type ReadWrite struct { f *os.File dataWriter *internalio.OffsetWriteSeeker - idx *insertionIndex + idx *insertionindex.InsertionIndex header carv2.Header opts carv2.Options @@ -134,7 +135,7 @@ func OpenReadWriteFile(f *os.File, roots []cid.Cid, opts ...carv2.Option) (*Read // Set the header fileld before applying options since padding options may modify header. rwbs := &ReadWrite{ f: f, - idx: newInsertionIndex(), + idx: insertionindex.NewInsertionIndex(), header: carv2.NewHeader(0), opts: carv2.ApplyOptions(opts...), } @@ -309,7 +310,7 @@ func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid) error { if err != nil { return err } - b.idx.insertNoReplace(c, uint64(sectionOffset)) + b.idx.InsertNoReplace(c, uint64(sectionOffset)) // Seek to the next section by skipping the block. // The section length includes the CID, so subtract it. @@ -366,7 +367,7 @@ func (b *ReadWrite) PutMany(ctx context.Context, blks []blocks.Block) error { } if !b.opts.BlockstoreAllowDuplicatePuts { - if b.ronly.opts.BlockstoreUseWholeCIDs && b.idx.hasExactCID(c) { + if b.ronly.opts.BlockstoreUseWholeCIDs && b.idx.HasExactCID(c) { continue // deduplicated by CID } if !b.ronly.opts.BlockstoreUseWholeCIDs { @@ -381,7 +382,7 @@ func (b *ReadWrite) PutMany(ctx context.Context, blks []blocks.Block) error { if err := util.LdWrite(b.dataWriter, c.Bytes(), bl.RawData()); err != nil { return err } - b.idx.insertNoReplace(c, n) + b.idx.InsertNoReplace(c, n) } return nil } @@ -428,7 +429,7 @@ func (b *ReadWrite) Finalize() error { defer b.ronly.closeWithoutMutex() // TODO if index not needed don't bother flattening it. - fi, err := b.idx.flatten(b.opts.IndexCodec) + fi, err := b.idx.Flatten(b.opts.IndexCodec) if err != nil { return err } diff --git a/ipld/car/v2/internal/carv1/car.go b/ipld/car/v2/internal/carv1/car.go index f56b382461..a38744a3ca 100644 --- a/ipld/car/v2/internal/carv1/car.go +++ b/ipld/car/v2/internal/carv1/car.go @@ -12,6 +12,7 @@ import ( format "github.com/ipfs/go-ipld-format" blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-merkledag" + internalio "github.com/ipld/go-car/v2/internal/io" ) const DefaultMaxAllowedHeaderSize uint64 = 32 << 20 // 32MiB @@ -59,6 +60,21 @@ func WriteCar(ctx context.Context, ds format.NodeGetter, roots []cid.Cid, w io.W return nil } +func ReadHeaderAt(at io.ReaderAt, maxReadBytes uint64) (*CarHeader, error) { + var rr io.Reader + switch r := at.(type) { + case io.Reader: + rr = r + default: + var err error + rr, err = internalio.NewOffsetReadSeeker(r, 0) + if err != nil { + return nil, err + } + } + return ReadHeader(rr, maxReadBytes) +} + func ReadHeader(r io.Reader, maxReadBytes uint64) (*CarHeader, error) { hb, err := util.LdRead(r, false, maxReadBytes) if err != nil { diff --git a/ipld/car/v2/blockstore/insertionindex.go b/ipld/car/v2/internal/insertionindex/insertionindex.go similarity index 84% rename from ipld/car/v2/blockstore/insertionindex.go rename to ipld/car/v2/internal/insertionindex/insertionindex.go index 1e480b3f93..449176acbf 100644 --- a/ipld/car/v2/blockstore/insertionindex.go +++ b/ipld/car/v2/internal/insertionindex/insertionindex.go @@ -1,4 +1,4 @@ -package blockstore +package insertionindex import ( "bytes" @@ -24,16 +24,18 @@ var ( insertionIndexCodec = multicodec.Code(0x300003) ) -type ( - insertionIndex struct { - items llrb.LLRB - } +type InsertionIndex struct { + items llrb.LLRB +} - recordDigest struct { - digest []byte - index.Record - } -) +func NewInsertionIndex() *InsertionIndex { + return &InsertionIndex{} +} + +type recordDigest struct { + digest []byte + index.Record +} func (r recordDigest) Less(than llrb.Item) bool { other, ok := than.(recordDigest) @@ -61,11 +63,11 @@ func newRecordFromCid(c cid.Cid, at uint64) recordDigest { return recordDigest{d.Digest, index.Record{Cid: c, Offset: at}} } -func (ii *insertionIndex) insertNoReplace(key cid.Cid, n uint64) { +func (ii *InsertionIndex) InsertNoReplace(key cid.Cid, n uint64) { ii.items.InsertNoReplace(newRecordFromCid(key, n)) } -func (ii *insertionIndex) Get(c cid.Cid) (uint64, error) { +func (ii *InsertionIndex) Get(c cid.Cid) (uint64, error) { d, err := multihash.Decode(c.Hash()) if err != nil { return 0, err @@ -83,7 +85,7 @@ func (ii *insertionIndex) Get(c cid.Cid) (uint64, error) { return r.Record.Offset, nil } -func (ii *insertionIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { +func (ii *InsertionIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { d, err := multihash.Decode(c.Hash()) if err != nil { return err @@ -107,7 +109,7 @@ func (ii *insertionIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { return nil } -func (ii *insertionIndex) Marshal(w io.Writer) (uint64, error) { +func (ii *InsertionIndex) Marshal(w io.Writer) (uint64, error) { l := uint64(0) if err := binary.Write(w, binary.LittleEndian, int64(ii.items.Len())); err != nil { return l, err @@ -125,7 +127,7 @@ func (ii *insertionIndex) Marshal(w io.Writer) (uint64, error) { return l, err } -func (ii *insertionIndex) Unmarshal(r io.Reader) error { +func (ii *InsertionIndex) Unmarshal(r io.Reader) error { var length int64 if err := binary.Read(r, binary.LittleEndian, &length); err != nil { return err @@ -141,7 +143,7 @@ func (ii *insertionIndex) Unmarshal(r io.Reader) error { return nil } -func (ii *insertionIndex) ForEach(f func(multihash.Multihash, uint64) error) error { +func (ii *InsertionIndex) ForEach(f func(multihash.Multihash, uint64) error) error { var errr error ii.items.AscendGreaterOrEqual(ii.items.Min(), func(i llrb.Item) bool { r := i.(recordDigest).Record @@ -155,11 +157,11 @@ func (ii *insertionIndex) ForEach(f func(multihash.Multihash, uint64) error) err return errr } -func (ii *insertionIndex) Codec() multicodec.Code { +func (ii *InsertionIndex) Codec() multicodec.Code { return insertionIndexCodec } -func (ii *insertionIndex) Load(rs []index.Record) error { +func (ii *InsertionIndex) Load(rs []index.Record) error { for _, r := range rs { rec := newRecordDigest(r) if rec.digest == nil { @@ -170,12 +172,8 @@ func (ii *insertionIndex) Load(rs []index.Record) error { return nil } -func newInsertionIndex() *insertionIndex { - return &insertionIndex{} -} - // flatten returns a formatted index in the given codec for more efficient subsequent loading. -func (ii *insertionIndex) flatten(codec multicodec.Code) (index.Index, error) { +func (ii *InsertionIndex) Flatten(codec multicodec.Code) (index.Index, error) { si, err := index.New(codec) if err != nil { return nil, err @@ -200,7 +198,7 @@ func (ii *insertionIndex) flatten(codec multicodec.Code) (index.Index, error) { // but it's separate as it allows us to compare Record.Cid directly, // whereas GetAll just provides Record.Offset. -func (ii *insertionIndex) hasExactCID(c cid.Cid) bool { +func (ii *InsertionIndex) HasExactCID(c cid.Cid) bool { d, err := multihash.Decode(c.Hash()) if err != nil { panic(err) diff --git a/ipld/car/v2/internal/store/identity.go b/ipld/car/v2/internal/store/identity.go new file mode 100644 index 0000000000..85dcadc5d8 --- /dev/null +++ b/ipld/car/v2/internal/store/identity.go @@ -0,0 +1,16 @@ +package store + +import ( + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" +) + +func IsIdentity(key cid.Cid) (digest []byte, ok bool, err error) { + dmh, err := multihash.Decode(key.Hash()) + if err != nil { + return nil, false, err + } + ok = dmh.Code == multihash.IDENTITY + digest = dmh.Digest + return digest, ok, nil +} diff --git a/ipld/car/v2/internal/store/index.go b/ipld/car/v2/internal/store/index.go new file mode 100644 index 0000000000..55b12755a2 --- /dev/null +++ b/ipld/car/v2/internal/store/index.go @@ -0,0 +1,30 @@ +package store + +import ( + "io" + + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" + internalio "github.com/ipld/go-car/v2/internal/io" +) + +func GenerateIndex(at io.ReaderAt, opts ...carv2.Option) (index.Index, error) { + var rs io.ReadSeeker + switch r := at.(type) { + case io.ReadSeeker: + rs = r + // The version may have been read from the given io.ReaderAt; therefore move back to the begining. + if _, err := rs.Seek(0, io.SeekStart); err != nil { + return nil, err + } + default: + var err error + rs, err = internalio.NewOffsetReadSeeker(r, 0) + if err != nil { + return nil, err + } + } + + // Note, we do not set any write options so that all write options fall back onto defaults. + return carv2.GenerateIndex(rs, opts...) +} diff --git a/ipld/car/v2/internal/store/version.go b/ipld/car/v2/internal/store/version.go new file mode 100644 index 0000000000..b43496ef7e --- /dev/null +++ b/ipld/car/v2/internal/store/version.go @@ -0,0 +1,17 @@ +package store + +import ( + "io" + + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/internal/carv1" +) + +func ReadVersion(at io.ReaderAt, opts ...carv2.Option) (uint64, error) { + o := carv2.ApplyOptions(opts...) + header, err := carv1.ReadHeaderAt(at, o.MaxAllowedHeaderSize) + if err != nil { + return 0, err + } + return header.Version, nil +} diff --git a/ipld/car/v2/storage/storage.go b/ipld/car/v2/storage/storage.go new file mode 100644 index 0000000000..3e41c9a1f5 --- /dev/null +++ b/ipld/car/v2/storage/storage.go @@ -0,0 +1,271 @@ +package storage + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "sync" + + "github.com/ipfs/go-cid" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/ipld/go-car/v2/internal/store" + ipldstorage "github.com/ipld/go-ipld-prime/storage" + "github.com/multiformats/go-varint" +) + +var errClosed = fmt.Errorf("cannot use a carv2 storage after closing") + +// compatible with the go-ipld-format ErrNotFound, match against +// interface{NotFound() bool} + +type ErrNotFound struct { + Cid cid.Cid +} + +func (e ErrNotFound) Error() string { + if e.Cid == cid.Undef { + return "ipld: could not find node" + } + return "ipld: could not find " + e.Cid.String() +} + +func (e ErrNotFound) NotFound() bool { + return true +} + +type ReadableCar interface { + ipldstorage.ReadableStorage + ipldstorage.StreamingReadableStorage + Roots() ([]cid.Cid, error) +} + +type WritableCar interface { + ipldstorage.WritableStorage + ipldstorage.StreamingWritableStorage +} + +var _ ipldstorage.ReadableStorage = (*StorageCar)(nil) +var _ ipldstorage.StreamingReadableStorage = (*StorageCar)(nil) +var _ ReadableCar = (*StorageCar)(nil) + +type StorageCar struct { + idx index.Index + // iidx *insertionindex.InsertionIndex + reader io.ReaderAt + writer io.Writer + // header carv2.Header + opts carv2.Options + + closed bool + mu sync.RWMutex +} + +func NewReadable(reader io.ReaderAt, idx index.Index, opts ...carv2.Option) (ReadableCar, error) { + sc := &StorageCar{ + opts: carv2.ApplyOptions(opts...), + } + + version, err := store.ReadVersion(reader, opts...) + if err != nil { + return nil, err + } + switch version { + case 1: + if idx == nil { + if idx, err = store.GenerateIndex(reader, opts...); err != nil { + return nil, err + } + } + sc.reader = reader + sc.idx = idx + return sc, nil + case 2: + v2r, err := carv2.NewReader(reader, opts...) + if err != nil { + return nil, err + } + if idx == nil { + if v2r.Header.HasIndex() { + ir, err := v2r.IndexReader() + if err != nil { + return nil, err + } + idx, err = index.ReadFrom(ir) + if err != nil { + return nil, err + } + } else { + dr, err := v2r.DataReader() + if err != nil { + return nil, err + } + if idx, err = store.GenerateIndex(dr, opts...); err != nil { + return nil, err + } + } + } + sc.reader, err = v2r.DataReader() + if err != nil { + return nil, err + } + sc.idx = idx + return sc, nil + default: + return nil, fmt.Errorf("unsupported car version: %v", version) + } +} + +func (sc *StorageCar) Roots() ([]cid.Cid, error) { + ors, err := internalio.NewOffsetReadSeeker(sc.reader, 0) + if err != nil { + return nil, err + } + header, err := carv1.ReadHeader(ors, sc.opts.MaxAllowedHeaderSize) + if err != nil { + return nil, fmt.Errorf("error reading car header: %w", err) + } + return header.Roots, nil +} + +func (sc *StorageCar) Has(ctx context.Context, keyStr string) (bool, error) { + keyCid, err := cid.Cast([]byte(keyStr)) + if err != nil { + return false, err + } + + if !sc.opts.StoreIdentityCIDs { + // If we don't store identity CIDs then we can return them straight away as if they are here, + // otherwise we need to check for their existence. + // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. + if _, ok, err := store.IsIdentity(keyCid); err != nil { + return false, err + } else if ok { + return true, nil + } + } + + sc.mu.RLock() + defer sc.mu.RUnlock() + + if sc.closed { + return false, errClosed + } + + var fnFound bool + var fnErr error + err = sc.idx.GetAll(keyCid, func(offset uint64) bool { + uar, err := internalio.NewOffsetReadSeeker(sc.reader, int64(offset)) + if err != nil { + fnErr = err + return false + } + _, err = varint.ReadUvarint(uar) + if err != nil { + fnErr = err + return false + } + _, readCid, err := cid.CidFromReader(uar) + if err != nil { + fnErr = err + return false + } + if sc.opts.BlockstoreUseWholeCIDs { + fnFound = readCid.Equals(keyCid) + return !fnFound // continue looking if we haven't found it + } else { + fnFound = bytes.Equal(readCid.Hash(), keyCid.Hash()) + return false + } + }) + if errors.Is(err, index.ErrNotFound) { + return false, nil + } else if err != nil { + return false, err + } + return fnFound, fnErr +} + +func (sc *StorageCar) Get(ctx context.Context, keyStr string) ([]byte, error) { + rdr, err := sc.GetStream(ctx, keyStr) + if err != nil { + return nil, err + } + return io.ReadAll(rdr) +} + +func (sc *StorageCar) GetStream(ctx context.Context, keyStr string) (io.ReadCloser, error) { + keyCid, err := cid.Cast([]byte(keyStr)) + if err != nil { + return nil, err + } + + if !sc.opts.StoreIdentityCIDs { + // If we don't store identity CIDs then we can return them straight away as if they are here, + // otherwise we need to check for their existence. + // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. + if digest, ok, err := store.IsIdentity(keyCid); err != nil { + return nil, err + } else if ok { + return io.NopCloser(bytes.NewReader(digest)), nil + } + } + + sc.mu.RLock() + defer sc.mu.RUnlock() + + if sc.closed { + return nil, errClosed + } + + fnSize := -1 + var fnErr error + var foundOffset int64 + err = sc.idx.GetAll(keyCid, func(offset uint64) bool { + rdr, err := internalio.NewOffsetReadSeeker(sc.reader, int64(offset)) + if err != nil { + fnErr = err + return false + } + sectionLen, err := varint.ReadUvarint(rdr) + if err != nil { + fnErr = err + return false + } + cidLen, readCid, err := cid.CidFromReader(rdr) + if err != nil { + fnErr = err + return false + } + if sc.opts.BlockstoreUseWholeCIDs { + if readCid.Equals(keyCid) { + fnSize = int(sectionLen) - cidLen + foundOffset = rdr.(interface{ Offset() int64 }).Offset() + return false + } else { + return true // continue looking + } + } else { + if bytes.Equal(readCid.Hash(), keyCid.Hash()) { + fnSize = int(sectionLen) - cidLen + foundOffset = rdr.(interface{ Offset() int64 }).Offset() + } + return false + } + }) + if errors.Is(err, index.ErrNotFound) { + return nil, ErrNotFound{Cid: keyCid} + } else if err != nil { + return nil, err + } else if fnErr != nil { + return nil, fnErr + } + if fnSize == -1 { + return nil, ErrNotFound{Cid: keyCid} + } + return io.NopCloser(io.NewSectionReader(sc.reader, foundOffset, int64(fnSize))), nil +} diff --git a/ipld/car/v2/storage/storage_test.go b/ipld/car/v2/storage/storage_test.go new file mode 100644 index 0000000000..10f71c86bd --- /dev/null +++ b/ipld/car/v2/storage/storage_test.go @@ -0,0 +1,122 @@ +package storage_test + +import ( + "context" + "io" + "os" + "testing" + + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-car/v2/storage" + "github.com/multiformats/go-multicodec" + "github.com/stretchr/testify/require" +) + +func TestReadable(t *testing.T) { + tests := []struct { + name string + v1OrV2path string + opts []carv2.Option + noIdCids bool + }{ + { + "OpenedWithCarV1", + "../testdata/sample-v1.car", + []carv2.Option{blockstore.UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, + // index is made, but identity CIDs are included so they'll be found + false, + }, + { + "OpenedWithCarV1_NoIdentityCID", + "../testdata/sample-v1.car", + []carv2.Option{blockstore.UseWholeCIDs(true)}, + // index is made, identity CIDs are not included, but we always short-circuit when StoreIdentityCIDs(false) + false, + }, + { + "OpenedWithCarV2", + "../testdata/sample-wrapped-v2.car", + []carv2.Option{blockstore.UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, + // index already exists, but was made without identity CIDs, but opening with StoreIdentityCIDs(true) means we check the index + true, + }, + { + "OpenedWithCarV2_NoIdentityCID", + "../testdata/sample-wrapped-v2.car", + []carv2.Option{blockstore.UseWholeCIDs(true)}, + // index already exists, it was made without identity CIDs, but we always short-circuit when StoreIdentityCIDs(false) + false, + }, + { + "OpenedWithCarV1ZeroLenSection", + "../testdata/sample-v1-with-zero-len-section.car", + []carv2.Option{blockstore.UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + false, + }, + { + "OpenedWithAnotherCarV1ZeroLenSection", + "../testdata/sample-v1-with-zero-len-section2.car", + []carv2.Option{blockstore.UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() + subjectReader, err := os.Open(tt.v1OrV2path) + require.NoError(t, err) + subject, err := storage.NewReadable(subjectReader, nil, tt.opts...) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, subjectReader.Close()) }) + + f, err := os.Open(tt.v1OrV2path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + + reader, err := carv2.NewBlockReader(f, tt.opts...) + require.NoError(t, err) + + // Assert roots match v1 payload. + wantRoots := reader.Roots + gotRoots, err := subject.Roots() + require.NoError(t, err) + require.Equal(t, wantRoots, gotRoots) + + for { + wantBlock, err := reader.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + key := wantBlock.Cid() + + // Assert blockstore contains key. + has, err := subject.Has(ctx, key.KeyString()) + require.NoError(t, err) + if key.Prefix().MhType == uint64(multicodec.Identity) && tt.noIdCids { + // fixture wasn't made with StoreIdentityCIDs, but we opened it with StoreIdentityCIDs, + // so they aren't there to find + require.False(t, has) + } else { + require.True(t, has) + } + + // Assert block itself matches v1 payload block. + if has { + gotBlock, err := subject.Get(ctx, key.KeyString()) + require.NoError(t, err) + require.Equal(t, wantBlock.RawData(), gotBlock) + + reader, err := subject.GetStream(ctx, key.KeyString()) + require.NoError(t, err) + data, err := io.ReadAll(reader) + require.NoError(t, err) + require.Equal(t, wantBlock.RawData(), data) + } + } + }) + } +} From d851b580d01f997967d4eb339ea56eaee299209b Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 3 Feb 2023 17:59:45 +1100 Subject: [PATCH 266/291] feat: add Writable functionality to StorageCar This commit was moved from ipld/go-car@b42a961df8d402396f121c2c0fe492b484b9a0b1 --- ipld/car/v2/blockstore/readonly.go | 18 +- ipld/car/v2/blockstore/readwrite.go | 3 +- ipld/car/v2/blockstore/readwrite_test.go | 2 +- .../internal/insertionindex/insertionindex.go | 24 +- ipld/car/v2/internal/io/converter.go | 38 ++ ipld/car/v2/storage/notfound.go | 39 ++ ipld/car/v2/storage/storage.go | 346 ++++++++++++------ ipld/car/v2/storage/storage_test.go | 255 ++++++++++++- 8 files changed, 568 insertions(+), 157 deletions(-) create mode 100644 ipld/car/v2/storage/notfound.go diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index ab1c6b5271..217771cbc5 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -17,7 +17,7 @@ import ( "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" - "github.com/multiformats/go-multihash" + "github.com/ipld/go-car/v2/internal/store" "github.com/multiformats/go-varint" "golang.org/x/exp/mmap" ) @@ -229,7 +229,7 @@ func (b *ReadOnly) Has(ctx context.Context, key cid.Cid) (bool, error) { // If we don't store identity CIDs then we can return them straight away as if they are here, // otherwise we need to check for their existence. // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. - if _, ok, err := isIdentity(key); err != nil { + if _, ok, err := store.IsIdentity(key); err != nil { return false, err } else if ok { return true, nil @@ -290,7 +290,7 @@ func (b *ReadOnly) Get(ctx context.Context, key cid.Cid) (blocks.Block, error) { // If we don't store identity CIDs then we can return them straight away as if they are here, // otherwise we need to check for their existence. // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. - if digest, ok, err := isIdentity(key); err != nil { + if digest, ok, err := store.IsIdentity(key); err != nil { return nil, err } else if ok { return blocks.NewBlockWithCid(digest, key) @@ -343,7 +343,7 @@ func (b *ReadOnly) Get(ctx context.Context, key cid.Cid) (blocks.Block, error) { func (b *ReadOnly) GetSize(ctx context.Context, key cid.Cid) (int, error) { // Check if the given CID has multihash.IDENTITY code // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. - if digest, ok, err := isIdentity(key); err != nil { + if digest, ok, err := store.IsIdentity(key); err != nil { return 0, err } else if ok { return len(digest), nil @@ -401,16 +401,6 @@ func (b *ReadOnly) GetSize(ctx context.Context, key cid.Cid) (int, error) { return fnSize, nil } -func isIdentity(key cid.Cid) (digest []byte, ok bool, err error) { - dmh, err := multihash.Decode(key.Hash()) - if err != nil { - return nil, false, err - } - ok = dmh.Code == multihash.IDENTITY - digest = dmh.Digest - return digest, ok, nil -} - // Put is not supported and always returns an error. func (b *ReadOnly) Put(context.Context, blocks.Block) error { return errReadOnly diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index c6a7e9b8f0..43b42d019e 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -18,6 +18,7 @@ import ( "github.com/ipld/go-car/v2/internal/carv1/util" "github.com/ipld/go-car/v2/internal/insertionindex" internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/ipld/go-car/v2/internal/store" ) var _ blockstore.Blockstore = (*ReadWrite)(nil) @@ -350,7 +351,7 @@ func (b *ReadWrite) PutMany(ctx context.Context, blks []blocks.Block) error { // If StoreIdentityCIDs option is disabled then treat IDENTITY CIDs like IdStore. if !b.opts.StoreIdentityCIDs { // Check for IDENTITY CID. If IDENTITY, ignore and move to the next block. - if _, ok, err := isIdentity(c); err != nil { + if _, ok, err := store.IsIdentity(c); err != nil { return err } else if ok { continue diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index cbcd1d4c13..04da36fe8d 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -47,7 +47,7 @@ func TestReadWriteGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) require.Nil(t, gotBlock) } -func TestBlockstoreX(t *testing.T) { +func TestBlockstore(t *testing.T) { originalCARv1Path := "../testdata/sample-v1.car" originalCARv1ComparePath := "../testdata/sample-v1-noidentity.car" originalCARv1ComparePathStat, err := os.Stat(originalCARv1ComparePath) diff --git a/ipld/car/v2/internal/insertionindex/insertionindex.go b/ipld/car/v2/internal/insertionindex/insertionindex.go index 449176acbf..67dcb8979c 100644 --- a/ipld/car/v2/internal/insertionindex/insertionindex.go +++ b/ipld/car/v2/internal/insertionindex/insertionindex.go @@ -68,21 +68,37 @@ func (ii *InsertionIndex) InsertNoReplace(key cid.Cid, n uint64) { } func (ii *InsertionIndex) Get(c cid.Cid) (uint64, error) { - d, err := multihash.Decode(c.Hash()) + record, err := ii.getRecord(c) if err != nil { return 0, err } + return record.Offset, nil +} + +func (ii *InsertionIndex) getRecord(c cid.Cid) (index.Record, error) { + d, err := multihash.Decode(c.Hash()) + if err != nil { + return index.Record{}, err + } entry := recordDigest{digest: d.Digest} e := ii.items.Get(entry) if e == nil { - return 0, index.ErrNotFound + return index.Record{}, index.ErrNotFound } r, ok := e.(recordDigest) if !ok { - return 0, errUnsupported + return index.Record{}, errUnsupported } - return r.Record.Offset, nil + return r.Record, nil +} + +func (ii *InsertionIndex) GetCid(c cid.Cid) (uint64, cid.Cid, error) { + record, err := ii.getRecord(c) + if err != nil { + return 0, cid.Undef, err + } + return record.Offset, record.Cid, nil } func (ii *InsertionIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { diff --git a/ipld/car/v2/internal/io/converter.go b/ipld/car/v2/internal/io/converter.go index 2b29d0a883..5550f86f2d 100644 --- a/ipld/car/v2/internal/io/converter.go +++ b/ipld/car/v2/internal/io/converter.go @@ -10,6 +10,7 @@ var ( _ io.ByteReader = (*readSeekerPlusByte)(nil) _ io.ByteReader = (*discardingReadSeekerPlusByte)(nil) _ io.ReadSeeker = (*discardingReadSeekerPlusByte)(nil) + _ io.ReadSeeker = (*readerAtSeeker)(nil) _ io.ReaderAt = (*readSeekerAt)(nil) ) @@ -42,6 +43,12 @@ type ( rs io.ReadSeeker mu sync.Mutex } + + readerAtSeeker struct { + ra io.ReaderAt + position int64 + mu sync.Mutex + } ) func ToByteReader(r io.Reader) io.ByteReader { @@ -61,6 +68,13 @@ func ToByteReadSeeker(r io.Reader) ByteReadSeeker { return &discardingReadSeekerPlusByte{Reader: r} } +func ToReadSeeker(ra io.ReaderAt) io.ReadSeeker { + if rs, ok := ra.(io.ReadSeeker); ok { + return rs + } + return &readerAtSeeker{ra: ra} +} + func ToReaderAt(rs io.ReadSeeker) io.ReaderAt { if ra, ok := rs.(io.ReaderAt); ok { return ra @@ -106,6 +120,30 @@ func (drsb *discardingReadSeekerPlusByte) Seek(offset int64, whence int) (int64, } } +func (ras *readerAtSeeker) Read(p []byte) (n int, err error) { + ras.mu.Lock() + defer ras.mu.Unlock() + n, err = ras.ra.ReadAt(p, ras.position) + ras.position += int64(n) + return n, err +} + +func (ras *readerAtSeeker) Seek(offset int64, whence int) (int64, error) { + ras.mu.Lock() + defer ras.mu.Unlock() + switch whence { + case io.SeekStart: + ras.position = offset + case io.SeekCurrent: + ras.position += offset + case io.SeekEnd: + panic("unsupported whence: io.SeekEnd") + default: + panic("unsupported whence") + } + return ras.position, nil +} + func (rsa *readSeekerAt) ReadAt(p []byte, off int64) (n int, err error) { rsa.mu.Lock() defer rsa.mu.Unlock() diff --git a/ipld/car/v2/storage/notfound.go b/ipld/car/v2/storage/notfound.go new file mode 100644 index 0000000000..82153e2f2e --- /dev/null +++ b/ipld/car/v2/storage/notfound.go @@ -0,0 +1,39 @@ +package storage + +import "github.com/ipfs/go-cid" + +// compatible with the go-ipld-format ErrNotFound, match against +// interface{NotFound() bool} +// this could go into go-ipld-prime, but for now we'll just exercise the +// feature-test pattern + +type ErrNotFound struct { + Cid cid.Cid +} + +func (e ErrNotFound) Error() string { + if e.Cid == cid.Undef { + return "ipld: could not find node" + } + return "ipld: could not find " + e.Cid.String() +} + +func (e ErrNotFound) NotFound() bool { + return true +} + +func (e ErrNotFound) Is(err error) bool { + switch err.(type) { + case ErrNotFound: + return true + default: + return false + } +} + +func IsNotFound(err error) bool { + if nf, ok := err.(interface{ NotFound() bool }); ok { + return nf.NotFound() + } + return false +} diff --git a/ipld/car/v2/storage/storage.go b/ipld/car/v2/storage/storage.go index 3e41c9a1f5..82ad0806e9 100644 --- a/ipld/car/v2/storage/storage.go +++ b/ipld/car/v2/storage/storage.go @@ -12,6 +12,8 @@ import ( carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" + "github.com/ipld/go-car/v2/internal/carv1/util" + "github.com/ipld/go-car/v2/internal/insertionindex" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipld/go-car/v2/internal/store" ipldstorage "github.com/ipld/go-ipld-prime/storage" @@ -20,33 +22,21 @@ import ( var errClosed = fmt.Errorf("cannot use a carv2 storage after closing") -// compatible with the go-ipld-format ErrNotFound, match against -// interface{NotFound() bool} - -type ErrNotFound struct { - Cid cid.Cid -} - -func (e ErrNotFound) Error() string { - if e.Cid == cid.Undef { - return "ipld: could not find node" - } - return "ipld: could not find " + e.Cid.String() -} - -func (e ErrNotFound) NotFound() bool { - return true -} - type ReadableCar interface { ipldstorage.ReadableStorage ipldstorage.StreamingReadableStorage Roots() ([]cid.Cid, error) } +// WritableCar is compatible with storage.WritableStorage but also returns +// the roots of the CAR. It does not implement ipld.StreamingWritableStorage +// as the CAR format does not support streaming data followed by its CID, so +// any streaming implementation would perform buffering and copy the +// existing storage.PutStream() implementation. type WritableCar interface { ipldstorage.WritableStorage - ipldstorage.StreamingWritableStorage + Roots() ([]cid.Cid, error) + Finalize() error } var _ ipldstorage.ReadableStorage = (*StorageCar)(nil) @@ -54,70 +44,160 @@ var _ ipldstorage.StreamingReadableStorage = (*StorageCar)(nil) var _ ReadableCar = (*StorageCar)(nil) type StorageCar struct { - idx index.Index - // iidx *insertionindex.InsertionIndex - reader io.ReaderAt - writer io.Writer - // header carv2.Header - opts carv2.Options + idx *insertionindex.InsertionIndex + reader io.ReaderAt + writer positionedWriter + dataWriter *internalio.OffsetWriteSeeker + header carv2.Header + opts carv2.Options closed bool mu sync.RWMutex } -func NewReadable(reader io.ReaderAt, idx index.Index, opts ...carv2.Option) (ReadableCar, error) { +type positionedWriter interface { + io.Writer + Position() int64 +} + +func NewReadable(reader io.ReaderAt, opts ...carv2.Option) (ReadableCar, error) { sc := &StorageCar{ opts: carv2.ApplyOptions(opts...), + idx: insertionindex.NewInsertionIndex(), } - version, err := store.ReadVersion(reader, opts...) + rr := internalio.ToReadSeeker(reader) + header, err := carv1.ReadHeader(rr, sc.opts.MaxAllowedHeaderSize) if err != nil { return nil, err } - switch version { + switch header.Version { case 1: - if idx == nil { - if idx, err = store.GenerateIndex(reader, opts...); err != nil { - return nil, err - } + rr.Seek(0, io.SeekStart) + if err := carv2.LoadIndex(sc.idx, rr, opts...); err != nil { + return nil, err } sc.reader = reader - sc.idx = idx - return sc, nil case 2: v2r, err := carv2.NewReader(reader, opts...) if err != nil { return nil, err } - if idx == nil { - if v2r.Header.HasIndex() { - ir, err := v2r.IndexReader() - if err != nil { - return nil, err - } - idx, err = index.ReadFrom(ir) - if err != nil { - return nil, err - } - } else { - dr, err := v2r.DataReader() - if err != nil { - return nil, err - } - if idx, err = store.GenerateIndex(dr, opts...); err != nil { - return nil, err - } - } - } - sc.reader, err = v2r.DataReader() + dr, err := v2r.DataReader() if err != nil { return nil, err } - sc.idx = idx - return sc, nil + if err := carv2.LoadIndex(sc.idx, dr, opts...); err != nil { + return nil, err + } + if sc.reader, err = v2r.DataReader(); err != nil { + return nil, err + } default: - return nil, fmt.Errorf("unsupported car version: %v", version) + return nil, fmt.Errorf("unsupported car version: %v", header.Version) + } + + return sc, nil +} + +func NewWritable(writer io.Writer, roots []cid.Cid, opts ...carv2.Option) (WritableCar, error) { + sc := &StorageCar{ + writer: &positionTrackingWriter{w: writer}, + idx: insertionindex.NewInsertionIndex(), + header: carv2.NewHeader(0), + opts: carv2.ApplyOptions(opts...), + } + + if p := sc.opts.DataPadding; p > 0 { + sc.header = sc.header.WithDataPadding(p) + } + if p := sc.opts.IndexPadding; p > 0 { + sc.header = sc.header.WithIndexPadding(p) + } + + offset := int64(sc.header.DataOffset) + if sc.opts.WriteAsCarV1 { + offset = 0 + } + + if writerAt, ok := writer.(io.WriterAt); ok { + sc.dataWriter = internalio.NewOffsetWriter(writerAt, offset) + } else { + if !sc.opts.WriteAsCarV1 { + return nil, fmt.Errorf("cannot write as carv2 to a non-seekable writer") + } + } + + if err := sc.initWithRoots(writer, !sc.opts.WriteAsCarV1, roots); err != nil { + return nil, err + } + + return sc, nil +} + +func (sc *StorageCar) initWithRoots(writer io.Writer, v2 bool, roots []cid.Cid) error { + if v2 { + if _, err := writer.Write(carv2.Pragma); err != nil { + return err + } + return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, sc.dataWriter) + } + return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, writer) +} + +func (sc *StorageCar) Put(ctx context.Context, keyStr string, data []byte) error { + keyCid, err := cid.Cast([]byte(keyStr)) + if err != nil { + return err + } + + sc.mu.Lock() + defer sc.mu.Unlock() + + // If StoreIdentityCIDs option is disabled then treat IDENTITY CIDs like IdStore. + if !sc.opts.StoreIdentityCIDs { + // Check for IDENTITY CID. If IDENTITY, ignore and move to the next block. + if _, ok, err := store.IsIdentity(keyCid); err != nil { + return err + } else if ok { + return nil + } + } + + // Check if its size is too big. + // If larger than maximum allowed size, return error. + // Note, we need to check this regardless of whether we have IDENTITY CID or not. + // Since multhihash codes other than IDENTITY can result in large digests. + cSize := uint64(len(keyCid.Bytes())) + if cSize > sc.opts.MaxIndexCidSize { + return &carv2.ErrCidTooLarge{MaxSize: sc.opts.MaxIndexCidSize, CurrentSize: cSize} + } + + // TODO: if we are write-only and BlockstoreAllowDuplicatePuts then we don't + // really need an index at all + if !sc.opts.BlockstoreAllowDuplicatePuts { + if sc.opts.BlockstoreUseWholeCIDs && sc.idx.HasExactCID(keyCid) { + return nil // deduplicated by CID + } + if !sc.opts.BlockstoreUseWholeCIDs { + _, err := sc.idx.Get(keyCid) + if err == nil { + return nil // deduplicated by hash + } + } + } + + w := sc.writer + if sc.dataWriter != nil { + w = sc.dataWriter + } + n := uint64(w.Position()) + if err := util.LdWrite(w, keyCid.Bytes(), data); err != nil { + return err } + sc.idx.InsertNoReplace(keyCid, n) + + return nil } func (sc *StorageCar) Roots() ([]cid.Cid, error) { @@ -156,38 +236,23 @@ func (sc *StorageCar) Has(ctx context.Context, keyStr string) (bool, error) { return false, errClosed } - var fnFound bool - var fnErr error - err = sc.idx.GetAll(keyCid, func(offset uint64) bool { - uar, err := internalio.NewOffsetReadSeeker(sc.reader, int64(offset)) - if err != nil { - fnErr = err - return false - } - _, err = varint.ReadUvarint(uar) + if sc.opts.BlockstoreUseWholeCIDs { + var foundCid cid.Cid + _, foundCid, err = sc.idx.GetCid(keyCid) if err != nil { - fnErr = err - return false - } - _, readCid, err := cid.CidFromReader(uar) - if err != nil { - fnErr = err - return false - } - if sc.opts.BlockstoreUseWholeCIDs { - fnFound = readCid.Equals(keyCid) - return !fnFound // continue looking if we haven't found it - } else { - fnFound = bytes.Equal(readCid.Hash(), keyCid.Hash()) - return false + if !foundCid.Equals(keyCid) { + return false, nil + } } - }) + } else { + _, err = sc.idx.Get(keyCid) + } if errors.Is(err, index.ErrNotFound) { return false, nil } else if err != nil { return false, err } - return fnFound, fnErr + return true, nil } func (sc *StorageCar) Get(ctx context.Context, keyStr string) ([]byte, error) { @@ -223,49 +288,96 @@ func (sc *StorageCar) GetStream(ctx context.Context, keyStr string) (io.ReadClos } fnSize := -1 - var fnErr error - var foundOffset int64 - err = sc.idx.GetAll(keyCid, func(offset uint64) bool { - rdr, err := internalio.NewOffsetReadSeeker(sc.reader, int64(offset)) - if err != nil { - fnErr = err - return false - } - sectionLen, err := varint.ReadUvarint(rdr) + var offset uint64 + if sc.opts.BlockstoreUseWholeCIDs { + var foundCid cid.Cid + offset, foundCid, err = sc.idx.GetCid(keyCid) if err != nil { - fnErr = err - return false - } - cidLen, readCid, err := cid.CidFromReader(rdr) - if err != nil { - fnErr = err - return false - } - if sc.opts.BlockstoreUseWholeCIDs { - if readCid.Equals(keyCid) { - fnSize = int(sectionLen) - cidLen - foundOffset = rdr.(interface{ Offset() int64 }).Offset() - return false - } else { - return true // continue looking - } - } else { - if bytes.Equal(readCid.Hash(), keyCid.Hash()) { - fnSize = int(sectionLen) - cidLen - foundOffset = rdr.(interface{ Offset() int64 }).Offset() + if !foundCid.Equals(keyCid) { + return nil, ErrNotFound{Cid: keyCid} } - return false } - }) + } else { + offset, err = sc.idx.Get(keyCid) + } if errors.Is(err, index.ErrNotFound) { return nil, ErrNotFound{Cid: keyCid} } else if err != nil { return nil, err - } else if fnErr != nil { - return nil, fnErr } + + rdr, err := internalio.NewOffsetReadSeeker(sc.reader, int64(offset)) + if err != nil { + return nil, err + } + sectionLen, err := varint.ReadUvarint(rdr) + if err != nil { + return nil, err + } + cidLen, _, err := cid.CidFromReader(rdr) + if err != nil { + return nil, err + } + fnSize = int(sectionLen) - cidLen + offset = uint64(rdr.(interface{ Offset() int64 }).Offset()) if fnSize == -1 { return nil, ErrNotFound{Cid: keyCid} } - return io.NopCloser(io.NewSectionReader(sc.reader, foundOffset, int64(fnSize))), nil + return io.NopCloser(io.NewSectionReader(sc.reader, int64(offset), int64(fnSize))), nil +} + +func (sc *StorageCar) Finalize() error { + if sc.opts.WriteAsCarV1 { + return nil + } + + wat, ok := sc.writer.(*positionTrackingWriter).w.(io.WriterAt) + if !ok { // should should already be checked at construction if this is a writable + return fmt.Errorf("cannot finalize a CARv2 without an io.WriterAt") + } + + sc.mu.Lock() + defer sc.mu.Unlock() + + if sc.closed { + // Allow duplicate Finalize calls, just like Close. + // Still error, just like ReadOnly.Close; it should be discarded. + return fmt.Errorf("called Finalize on a closed blockstore") + } + + // TODO check if add index option is set and don't write the index then set index offset to zero. + sc.header = sc.header.WithDataSize(uint64(sc.dataWriter.Position())) + sc.header.Characteristics.SetFullyIndexed(sc.opts.StoreIdentityCIDs) + + sc.closed = true + + fi, err := sc.idx.Flatten(sc.opts.IndexCodec) + if err != nil { + return err + } + if _, err := index.WriteTo(fi, internalio.NewOffsetWriter(wat, int64(sc.header.IndexOffset))); err != nil { + return err + } + var buf bytes.Buffer + sc.header.WriteTo(&buf) + if _, err := sc.header.WriteTo(internalio.NewOffsetWriter(wat, carv2.PragmaSize)); err != nil { + return err + } + + return nil +} + +type positionTrackingWriter struct { + w io.Writer + offset int64 +} + +func (ptw *positionTrackingWriter) Write(p []byte) (int, error) { + written, err := ptw.w.Write(p) + ptw.offset += int64(written) + return written, err +} + +func (ptw *positionTrackingWriter) Position() int64 { + return ptw.offset } diff --git a/ipld/car/v2/storage/storage_test.go b/ipld/car/v2/storage/storage_test.go index 10f71c86bd..0b34b50251 100644 --- a/ipld/car/v2/storage/storage_test.go +++ b/ipld/car/v2/storage/storage_test.go @@ -2,63 +2,62 @@ package storage_test import ( "context" + "crypto/sha512" + "errors" + "fmt" "io" + "math/rand" "os" + "path/filepath" "testing" + "time" + "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/storage" - "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" ) +var rng = rand.New(rand.NewSource(1413)) + func TestReadable(t *testing.T) { tests := []struct { name string v1OrV2path string opts []carv2.Option - noIdCids bool }{ { "OpenedWithCarV1", "../testdata/sample-v1.car", []carv2.Option{blockstore.UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, - // index is made, but identity CIDs are included so they'll be found - false, }, { "OpenedWithCarV1_NoIdentityCID", "../testdata/sample-v1.car", []carv2.Option{blockstore.UseWholeCIDs(true)}, - // index is made, identity CIDs are not included, but we always short-circuit when StoreIdentityCIDs(false) - false, }, { "OpenedWithCarV2", "../testdata/sample-wrapped-v2.car", []carv2.Option{blockstore.UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, - // index already exists, but was made without identity CIDs, but opening with StoreIdentityCIDs(true) means we check the index - true, }, { "OpenedWithCarV2_NoIdentityCID", "../testdata/sample-wrapped-v2.car", []carv2.Option{blockstore.UseWholeCIDs(true)}, - // index already exists, it was made without identity CIDs, but we always short-circuit when StoreIdentityCIDs(false) - false, }, { "OpenedWithCarV1ZeroLenSection", "../testdata/sample-v1-with-zero-len-section.car", []carv2.Option{blockstore.UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, - false, }, { "OpenedWithAnotherCarV1ZeroLenSection", "../testdata/sample-v1-with-zero-len-section2.car", []carv2.Option{blockstore.UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, - false, }, } @@ -67,7 +66,7 @@ func TestReadable(t *testing.T) { ctx := context.TODO() subjectReader, err := os.Open(tt.v1OrV2path) require.NoError(t, err) - subject, err := storage.NewReadable(subjectReader, nil, tt.opts...) + subject, err := storage.NewReadable(subjectReader, tt.opts...) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, subjectReader.Close()) }) @@ -96,13 +95,7 @@ func TestReadable(t *testing.T) { // Assert blockstore contains key. has, err := subject.Has(ctx, key.KeyString()) require.NoError(t, err) - if key.Prefix().MhType == uint64(multicodec.Identity) && tt.noIdCids { - // fixture wasn't made with StoreIdentityCIDs, but we opened it with StoreIdentityCIDs, - // so they aren't there to find - require.False(t, has) - } else { - require.True(t, has) - } + require.True(t, has) // Assert block itself matches v1 payload block. if has { @@ -117,6 +110,228 @@ func TestReadable(t *testing.T) { require.Equal(t, wantBlock.RawData(), data) } } + + // not exists + c := randCid() + has, err := subject.Has(ctx, c.KeyString()) + require.NoError(t, err) + require.False(t, has) + + _, err = subject.Get(ctx, c.KeyString()) + require.True(t, errors.Is(err, storage.ErrNotFound{})) + require.True(t, storage.IsNotFound(err)) + require.Contains(t, err.Error(), c.String()) + + // random identity, should only find this if we _don't_ store identity CIDs + storeIdentity := carv2.ApplyOptions(tt.opts...).StoreIdentityCIDs + c = randIdentityCid() + + has, err = subject.Has(ctx, c.KeyString()) + require.NoError(t, err) + require.Equal(t, !storeIdentity, has) + + got, err := subject.Get(ctx, c.KeyString()) + if !storeIdentity { + require.NoError(t, err) + mh, err := multihash.Decode(c.Hash()) + require.NoError(t, err) + require.Equal(t, mh.Digest, got) + } else { + require.True(t, errors.Is(err, storage.ErrNotFound{})) + require.True(t, storage.IsNotFound(err)) + require.Contains(t, err.Error(), c.String()) + } + }) + } +} + +func TestWritable(t *testing.T) { + originalCarV1Path := "../testdata/sample-v1.car" + + variants := []struct { + name string + compareCarV1 string + options []carv2.Option + expectedV1StartOffset int64 + }{ + // no options, expect a standard CARv2 with the noidentity inner CARv1 + {"carv2_noopt", "sample-v1-noidentity.car", []carv2.Option{}, int64(carv2.PragmaSize + carv2.HeaderSize)}, + // no options, expect a standard CARv2 with the noidentity inner CARv1 + {"carv2_identity", "sample-v1.car", []carv2.Option{carv2.StoreIdentityCIDs(true)}, int64(carv2.PragmaSize + carv2.HeaderSize)}, + // option to only write as a CARv1, expect the noidentity inner CARv1 + {"carv1", "sample-v1-noidentity.car", []carv2.Option{blockstore.WriteAsCarV1(true)}, int64(0)}, + // option to only write as a CARv1, expect the noidentity inner CARv1 + {"carv1_identity", "sample-v1.car", []carv2.Option{blockstore.WriteAsCarV1(true), carv2.StoreIdentityCIDs(true)}, int64(0)}, + } + + for _, variant := range variants { + t.Run(variant.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + opts := carv2.ApplyOptions(variant.options...) + + srcFile, err := os.Open(originalCarV1Path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, srcFile.Close()) }) + r, err := carv1.NewCarReader(srcFile) + require.NoError(t, err) + + path := filepath.Join("/tmp/", fmt.Sprintf("writable_%s.car", variant.name)) + dstFile, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) + require.NoError(t, err) + var writer io.Writer = &writerOnly{dstFile} + if !opts.WriteAsCarV1 { + writer = &writerAtOnly{dstFile} + } + ingester, err := storage.NewWritable(writer, r.Header.Roots, variant.options...) + require.NoError(t, err) + t.Cleanup(func() { dstFile.Close() }) + + cids := make([]cid.Cid, 0) + var idCidCount int + for { + b, err := r.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + err = ingester.Put(ctx, b.Cid().KeyString(), b.RawData()) + require.NoError(t, err) + cids = append(cids, b.Cid()) + + dmh, err := multihash.Decode(b.Cid().Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + idCidCount++ + } + + // try reading a random one: + candIndex := rng.Intn(len(cids)) + var candidate cid.Cid + for _, c := range cids { + if candIndex == 0 { + candidate = c + break + } + candIndex-- + } + has, err := ingester.Has(ctx, candidate.KeyString()) + require.NoError(t, err) + require.True(t, has) + + // not exists + has, err = ingester.Has(ctx, randCid().KeyString()) + require.NoError(t, err) + require.False(t, has) + + // random identity + has, err = ingester.Has(ctx, randIdentityCid().KeyString()) + require.NoError(t, err) + require.Equal(t, !opts.StoreIdentityCIDs, has) + } + + err = ingester.Finalize() + require.NoError(t, err) + + err = dstFile.Close() + require.NoError(t, err) + + reopen, err := os.Open(path) + require.NoError(t, err) + rd, err := carv2.NewReader(reopen) + require.NoError(t, err) + require.Equal(t, opts.WriteAsCarV1, rd.Version == 1) + require.NoError(t, reopen.Close()) + + robs, err := blockstore.OpenReadOnly(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, robs.Close()) }) + + allKeysCh, err := robs.AllKeysChan(ctx) + require.NoError(t, err) + numKeysCh := 0 + for c := range allKeysCh { + b, err := robs.Get(ctx, c) + require.NoError(t, err) + if !b.Cid().Equals(c) { + t.Fatal("wrong item returned") + } + numKeysCh++ + } + expectedCidCount := len(cids) + if !opts.StoreIdentityCIDs { + expectedCidCount -= idCidCount + } + require.Equal(t, expectedCidCount, numKeysCh, "AllKeysChan returned an unexpected amount of keys; expected %v but got %v", expectedCidCount, numKeysCh) + + for _, c := range cids { + b, err := robs.Get(ctx, c) + require.NoError(t, err) + if !b.Cid().Equals(c) { + t.Fatal("wrong item returned") + } + } + + comparePath := filepath.Join("../testdata/", variant.compareCarV1) + compareStat, err := os.Stat(comparePath) + require.NoError(t, err) + + wrote, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, wrote.Close()) }) + _, err = wrote.Seek(variant.expectedV1StartOffset, io.SeekStart) + require.NoError(t, err) + hasher := sha512.New() + gotWritten, err := io.Copy(hasher, io.LimitReader(wrote, compareStat.Size())) + require.NoError(t, err) + gotSum := hasher.Sum(nil) + + hasher.Reset() + compareV1, err := os.Open(comparePath) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, compareV1.Close()) }) + wantWritten, err := io.Copy(hasher, compareV1) + require.NoError(t, err) + wantSum := hasher.Sum(nil) + + require.Equal(t, wantWritten, gotWritten) + require.Equal(t, wantSum, gotSum) }) } } + +type writerOnly struct { + io.Writer +} + +func (w *writerOnly) Write(p []byte) (n int, err error) { + return w.Writer.Write(p) +} + +type writerAtOnly struct { + *os.File +} + +func (w *writerAtOnly) WriteAt(p []byte, off int64) (n int, err error) { + return w.File.WriteAt(p, off) +} + +func (w *writerAtOnly) Write(p []byte) (n int, err error) { + return w.File.Write(p) +} + +func randCid() cid.Cid { + b := make([]byte, 32) + rng.Read(b) + mh, _ := multihash.Encode(b, multihash.SHA2_256) + return cid.NewCidV1(cid.DagProtobuf, mh) +} + +func randIdentityCid() cid.Cid { + b := make([]byte, 32) + rng.Read(b) + mh, _ := multihash.Encode(b, multihash.IDENTITY) + return cid.NewCidV1(cid.Raw, mh) +} From a6bbce9c1a8d7cb2e188582bb6c544212beedc3c Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 6 Feb 2023 14:21:23 +1100 Subject: [PATCH 267/291] feat: ReadableWritable; dedupe shared code * New and Open(=resumable) functionality for ReadableWritable * Pull up blockstore options to carv2 package * Extracted shared blockstore code into internal/store This commit was moved from ipld/go-car@f9a08295ac7b1127a52a815c0c1fd6b05885c193 --- ipld/car/v2/blockstore/readonly.go | 159 +-- ipld/car/v2/blockstore/readwrite.go | 244 +--- .../internal/insertionindex/insertionindex.go | 8 - ipld/car/v2/internal/store/identity.go | 1 + ipld/car/v2/internal/store/index.go | 109 +- ipld/car/v2/internal/store/put.go | 54 + ipld/car/v2/internal/store/resume.go | 200 +++ ipld/car/v2/internal/store/version.go | 17 - ipld/car/v2/options.go | 50 + ipld/car/v2/storage/storage.go | 297 +++-- ipld/car/v2/storage/storage_test.go | 1157 +++++++++++++++-- 11 files changed, 1648 insertions(+), 648 deletions(-) create mode 100644 ipld/car/v2/internal/store/put.go create mode 100644 ipld/car/v2/internal/store/resume.go delete mode 100644 ipld/car/v2/internal/store/version.go diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 217771cbc5..c59bdcf685 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -1,7 +1,6 @@ package blockstore import ( - "bytes" "context" "errors" "fmt" @@ -15,7 +14,6 @@ import ( carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" - "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipld/go-car/v2/internal/store" "github.com/multiformats/go-varint" @@ -61,29 +59,7 @@ type contextKey string const asyncErrHandlerKey contextKey = "asyncErrorHandlerKey" -// UseWholeCIDs is a read option which makes a CAR blockstore identify blocks by -// whole CIDs, and not just their multihashes. The default is to use -// multihashes, which matches the current semantics of go-ipfs-blockstore v1. -// -// Enabling this option affects a number of methods, including read-only ones: -// -// • Get, Has, and HasSize will only return a block -// only if the entire CID is present in the CAR file. -// -// • AllKeysChan will return the original whole CIDs, instead of with their -// multicodec set to "raw" to just provide multihashes. -// -// • If AllowDuplicatePuts isn't set, -// Put and PutMany will deduplicate by the whole CID, -// allowing different CIDs with equal multihashes. -// -// Note that this option only affects the blockstore, and is ignored by the root -// go-car/v2 package. -func UseWholeCIDs(enable bool) carv2.Option { - return func(o *carv2.Options) { - o.BlockstoreUseWholeCIDs = enable - } -} +var UseWholeCIDs = carv2.UseWholeCIDs // NewReadOnly creates a new ReadOnly blockstore from the backing with a optional index as idx. // This function accepts both CARv1 and CARv2 backing. @@ -203,14 +179,6 @@ func OpenReadOnly(path string, opts ...carv2.Option) (*ReadOnly, error) { return robs, nil } -func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { - r, err := internalio.NewOffsetReadSeeker(b.backing, idx) - if err != nil { - return cid.Cid{}, nil, err - } - return util.ReadNode(r, b.opts.ZeroLengthSectionAsEOF, b.opts.MaxAllowedSectionSize) -} - // DeleteBlock is unsupported and always errors. func (b *ReadOnly) DeleteBlock(_ context.Context, _ cid.Cid) error { return errReadOnly @@ -243,38 +211,21 @@ func (b *ReadOnly) Has(ctx context.Context, key cid.Cid) (bool, error) { return false, errClosed } - var fnFound bool - var fnErr error - err := b.idx.GetAll(key, func(offset uint64) bool { - uar, err := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) - if err != nil { - fnErr = err - return false - } - _, err = varint.ReadUvarint(uar) - if err != nil { - fnErr = err - return false - } - _, readCid, err := cid.CidFromReader(uar) - if err != nil { - fnErr = err - return false - } - if b.opts.BlockstoreUseWholeCIDs { - fnFound = readCid.Equals(key) - return !fnFound // continue looking if we haven't found it - } else { - fnFound = bytes.Equal(readCid.Hash(), key.Hash()) - return false - } - }) + _, _, size, err := store.FindCid( + b.backing, + b.idx, + key, + b.opts.BlockstoreUseWholeCIDs, + b.opts.ZeroLengthSectionAsEOF, + b.opts.MaxAllowedSectionSize, + false, + ) if errors.Is(err, index.ErrNotFound) { return false, nil } else if err != nil { return false, err } - return fnFound, fnErr + return size > -1, nil } // Get gets a block corresponding to the given key. @@ -304,39 +255,21 @@ func (b *ReadOnly) Get(ctx context.Context, key cid.Cid) (blocks.Block, error) { return nil, errClosed } - var fnData []byte - var fnErr error - err := b.idx.GetAll(key, func(offset uint64) bool { - readCid, data, err := b.readBlock(int64(offset)) - if err != nil { - fnErr = err - return false - } - if b.opts.BlockstoreUseWholeCIDs { - if readCid.Equals(key) { - fnData = data - return false - } else { - return true // continue looking - } - } else { - if bytes.Equal(readCid.Hash(), key.Hash()) { - fnData = data - } - return false - } - }) + data, _, _, err := store.FindCid( + b.backing, + b.idx, + key, + b.opts.BlockstoreUseWholeCIDs, + b.opts.ZeroLengthSectionAsEOF, + b.opts.MaxAllowedSectionSize, + true, + ) if errors.Is(err, index.ErrNotFound) { return nil, format.ErrNotFound{Cid: key} } else if err != nil { - return nil, format.ErrNotFound{Cid: key} - } else if fnErr != nil { - return nil, fnErr - } - if fnData == nil { - return nil, format.ErrNotFound{Cid: key} + return nil, err } - return blocks.NewBlockWithCid(fnData, key) + return blocks.NewBlockWithCid(data, key) } // GetSize gets the size of an item corresponding to the given key. @@ -356,49 +289,21 @@ func (b *ReadOnly) GetSize(ctx context.Context, key cid.Cid) (int, error) { return 0, errClosed } - fnSize := -1 - var fnErr error - err := b.idx.GetAll(key, func(offset uint64) bool { - rdr, err := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) - if err != nil { - fnErr = err - return false - } - sectionLen, err := varint.ReadUvarint(rdr) - if err != nil { - fnErr = err - return false - } - cidLen, readCid, err := cid.CidFromReader(rdr) - if err != nil { - fnErr = err - return false - } - if b.opts.BlockstoreUseWholeCIDs { - if readCid.Equals(key) { - fnSize = int(sectionLen) - cidLen - return false - } else { - return true // continue looking - } - } else { - if bytes.Equal(readCid.Hash(), key.Hash()) { - fnSize = int(sectionLen) - cidLen - } - return false - } - }) + _, _, size, err := store.FindCid( + b.backing, + b.idx, + key, + b.opts.BlockstoreUseWholeCIDs, + b.opts.ZeroLengthSectionAsEOF, + b.opts.MaxAllowedSectionSize, + false, + ) if errors.Is(err, index.ErrNotFound) { return -1, format.ErrNotFound{Cid: key} } else if err != nil { return -1, err - } else if fnErr != nil { - return -1, fnErr - } - if fnSize == -1 { - return -1, format.ErrNotFound{Cid: key} } - return fnSize, nil + return size, nil } // Put is not supported and always returns an error. diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 43b42d019e..032565b15e 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -2,18 +2,14 @@ package blockstore import ( "context" - "errors" "fmt" - "io" "os" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" blocks "github.com/ipfs/go-libipfs/blocks" - "github.com/multiformats/go-varint" carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" "github.com/ipld/go-car/v2/internal/insertionindex" @@ -43,29 +39,8 @@ type ReadWrite struct { opts carv2.Options } -// WriteAsCarV1 is a write option which makes a CAR blockstore write the output -// as a CARv1 only, with no CARv2 header or index. Indexing is used internally -// during write but is discarded upon finalization. -// -// Note that this option only affects the blockstore, and is ignored by the root -// go-car/v2 package. -func WriteAsCarV1(asCarV1 bool) carv2.Option { - return func(o *carv2.Options) { - o.WriteAsCarV1 = asCarV1 - } -} - -// AllowDuplicatePuts is a write option which makes a CAR blockstore not -// deduplicate blocks in Put and PutMany. The default is to deduplicate, -// which matches the current semantics of go-ipfs-blockstore v1. -// -// Note that this option only affects the blockstore, and is ignored by the root -// go-car/v2 package. -func AllowDuplicatePuts(allow bool) carv2.Option { - return func(o *carv2.Options) { - o.BlockstoreAllowDuplicatePuts = allow - } -} +var WriteAsCarV1 = carv2.WriteAsCarV1 +var AllowDuplicatePuts = carv2.AllowDuplicatePuts // OpenReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs and options. // @@ -162,7 +137,20 @@ func OpenReadWriteFile(f *os.File, roots []cid.Cid, opts ...carv2.Option) (*Read rwbs.ronly.idx = rwbs.idx if resume { - if err = rwbs.resumeWithRoots(!rwbs.opts.WriteAsCarV1, roots); err != nil { + if err := store.ResumableVersion(f, rwbs.opts.WriteAsCarV1); err != nil { + return nil, err + } + if err := store.Resume( + f, + rwbs.ronly.backing, + rwbs.dataWriter, + rwbs.idx, + roots, + rwbs.header.DataOffset, + rwbs.opts.WriteAsCarV1, + rwbs.opts.MaxAllowedHeaderSize, + rwbs.opts.ZeroLengthSectionAsEOF, + ); err != nil { return nil, err } } else { @@ -183,152 +171,6 @@ func (b *ReadWrite) initWithRoots(v2 bool, roots []cid.Cid) error { return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, b.dataWriter) } -func (b *ReadWrite) resumeWithRoots(v2 bool, roots []cid.Cid) error { - // On resumption it is expected that the CARv2 Pragma, and the CARv1 header is successfully written. - // Otherwise we cannot resume from the file. - // Read pragma to assert if b.f is indeed a CARv2. - version, err := carv2.ReadVersion(b.f) - if err != nil { - // The file is not a valid CAR file and cannot resume from it. - // Or the write must have failed before pragma was written. - return err - } - switch { - case version == 1 && !v2: - case version == 2 && v2: - default: - // The file is not the expected version and we cannot resume from it. - return fmt.Errorf("cannot resume on CAR file with version %v", version) - } - - var headerInFile carv2.Header - - if v2 { - // Check if file was finalized by trying to read the CARv2 header. - // We check because if finalized the CARv1 reader behaviour needs to be adjusted since - // EOF will not signify end of CARv1 payload. i.e. index is most likely present. - r, err := internalio.NewOffsetReadSeeker(b.f, carv2.PragmaSize) - if err != nil { - return err - } - _, err = headerInFile.ReadFrom(r) - - // If reading CARv2 header succeeded, and CARv1 offset in header is not zero then the file is - // most-likely finalized. Check padding and truncate the file to remove index. - // Otherwise, carry on reading the v1 payload at offset determined from b.header. - if err == nil && headerInFile.DataOffset != 0 { - if headerInFile.DataOffset != b.header.DataOffset { - // Assert that the padding on file matches the given WithDataPadding option. - wantPadding := headerInFile.DataOffset - carv2.PragmaSize - carv2.HeaderSize - gotPadding := b.header.DataOffset - carv2.PragmaSize - carv2.HeaderSize - return fmt.Errorf( - "cannot resume from file with mismatched CARv1 offset; "+ - "`WithDataPadding` option must match the padding on file. "+ - "Expected padding value of %v but got %v", wantPadding, gotPadding, - ) - } else if headerInFile.DataSize == 0 { - // If CARv1 size is zero, since CARv1 offset wasn't, then the CARv2 header was - // most-likely partially written. Since we write the header last in Finalize then the - // file most-likely contains the index and we cannot know where it starts, therefore - // can't resume. - return errors.New("corrupt CARv2 header; cannot resume from file") - } - } - } - - // Use the given CARv1 padding to instantiate the CARv1 reader on file. - v1r, err := internalio.NewOffsetReadSeeker(b.ronly.backing, 0) - if err != nil { - return err - } - header, err := carv1.ReadHeader(v1r, b.opts.MaxAllowedHeaderSize) - if err != nil { - // Cannot read the CARv1 header; the file is most likely corrupt. - return fmt.Errorf("error reading car header: %w", err) - } - if !header.Matches(carv1.CarHeader{Roots: roots, Version: 1}) { - // Cannot resume if version and root does not match. - return errors.New("cannot resume on file with mismatching data header") - } - - if headerInFile.DataOffset != 0 { - // If header in file contains the size of car v1, then the index is most likely present. - // Since we will need to re-generate the index, as the one in file is flattened, truncate - // the file so that the Readonly.backing has the right set of bytes to deal with. - // This effectively means resuming from a finalized file will wipe its index even if there - // are no blocks put unless the user calls finalize. - if err := b.f.Truncate(int64(headerInFile.DataOffset + headerInFile.DataSize)); err != nil { - return err - } - } - - if v2 { - // Now that CARv2 header is present on file, clear it to avoid incorrect size and offset in - // header in case blocksotre is closed without finalization and is resumed from. - if err := b.unfinalize(); err != nil { - return fmt.Errorf("could not un-finalize: %w", err) - } - } - - // TODO See how we can reduce duplicate code here. - // The code here comes from car.GenerateIndex. - // Copied because we need to populate an insertindex, not a sorted index. - // Producing a sorted index via generate, then converting it to insertindex is not possible. - // Because Index interface does not expose internal records. - // This may be done as part of https://github.com/ipld/go-car/issues/95 - - offset, err := carv1.HeaderSize(header) - if err != nil { - return err - } - sectionOffset := int64(0) - if sectionOffset, err = v1r.Seek(int64(offset), io.SeekStart); err != nil { - return err - } - - for { - // Grab the length of the section. - // Note that ReadUvarint wants a ByteReader. - length, err := varint.ReadUvarint(v1r) - if err != nil { - if err == io.EOF { - break - } - return err - } - - // Null padding; by default it's an error. - if length == 0 { - if b.ronly.opts.ZeroLengthSectionAsEOF { - break - } else { - return fmt.Errorf("carv1 null padding not allowed by default; see WithZeroLegthSectionAsEOF") - } - } - - // Grab the CID. - n, c, err := cid.CidFromReader(v1r) - if err != nil { - return err - } - b.idx.InsertNoReplace(c, uint64(sectionOffset)) - - // Seek to the next section by skipping the block. - // The section length includes the CID, so subtract it. - if sectionOffset, err = v1r.Seek(int64(length)-int64(n), io.SeekCurrent); err != nil { - return err - } - } - // Seek to the end of last skipped block where the writer should resume writing. - _, err = b.dataWriter.Seek(sectionOffset, io.SeekStart) - return err -} - -func (b *ReadWrite) unfinalize() error { - _, err := new(carv2.Header).WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)) - return err -} - // Put puts a given block to the underlying datastore func (b *ReadWrite) Put(ctx context.Context, blk blocks.Block) error { // PutMany already checks b.ronly.closed. @@ -348,35 +190,17 @@ func (b *ReadWrite) PutMany(ctx context.Context, blks []blocks.Block) error { for _, bl := range blks { c := bl.Cid() - // If StoreIdentityCIDs option is disabled then treat IDENTITY CIDs like IdStore. - if !b.opts.StoreIdentityCIDs { - // Check for IDENTITY CID. If IDENTITY, ignore and move to the next block. - if _, ok, err := store.IsIdentity(c); err != nil { - return err - } else if ok { - continue - } - } - - // Check if its size is too big. - // If larger than maximum allowed size, return error. - // Note, we need to check this regardless of whether we have IDENTITY CID or not. - // Since multhihash codes other than IDENTITY can result in large digests. - cSize := uint64(len(c.Bytes())) - if cSize > b.opts.MaxIndexCidSize { - return &carv2.ErrCidTooLarge{MaxSize: b.opts.MaxIndexCidSize, CurrentSize: cSize} - } - - if !b.opts.BlockstoreAllowDuplicatePuts { - if b.ronly.opts.BlockstoreUseWholeCIDs && b.idx.HasExactCID(c) { - continue // deduplicated by CID - } - if !b.ronly.opts.BlockstoreUseWholeCIDs { - _, err := b.idx.Get(c) - if err == nil { - continue // deduplicated by hash - } - } + if should, err := store.ShouldPut( + b.idx, + c, + b.opts.MaxIndexCidSize, + b.opts.StoreIdentityCIDs, + b.opts.BlockstoreAllowDuplicatePuts, + b.opts.BlockstoreUseWholeCIDs, + ); err != nil { + return err + } else if !should { + continue } n := uint64(b.dataWriter.Position()) @@ -421,23 +245,11 @@ func (b *ReadWrite) Finalize() error { return fmt.Errorf("called Finalize on a closed blockstore") } - // TODO check if add index option is set and don't write the index then set index offset to zero. - b.header = b.header.WithDataSize(uint64(b.dataWriter.Position())) - b.header.Characteristics.SetFullyIndexed(b.opts.StoreIdentityCIDs) - // Note that we can't use b.Close here, as that tries to grab the same // mutex we're holding here. defer b.ronly.closeWithoutMutex() - // TODO if index not needed don't bother flattening it. - fi, err := b.idx.Flatten(b.opts.IndexCodec) - if err != nil { - return err - } - if _, err := index.WriteTo(fi, internalio.NewOffsetWriter(b.f, int64(b.header.IndexOffset))); err != nil { - return err - } - if _, err := b.header.WriteTo(internalio.NewOffsetWriter(b.f, carv2.PragmaSize)); err != nil { + if err := store.Finalize(b.f, b.header, b.idx, uint64(b.dataWriter.Position()), b.opts.StoreIdentityCIDs, b.opts.IndexCodec); err != nil { return err } diff --git a/ipld/car/v2/internal/insertionindex/insertionindex.go b/ipld/car/v2/internal/insertionindex/insertionindex.go index 67dcb8979c..7aac338ec3 100644 --- a/ipld/car/v2/internal/insertionindex/insertionindex.go +++ b/ipld/car/v2/internal/insertionindex/insertionindex.go @@ -93,14 +93,6 @@ func (ii *InsertionIndex) getRecord(c cid.Cid) (index.Record, error) { return r.Record, nil } -func (ii *InsertionIndex) GetCid(c cid.Cid) (uint64, cid.Cid, error) { - record, err := ii.getRecord(c) - if err != nil { - return 0, cid.Undef, err - } - return record.Offset, record.Cid, nil -} - func (ii *InsertionIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { d, err := multihash.Decode(c.Hash()) if err != nil { diff --git a/ipld/car/v2/internal/store/identity.go b/ipld/car/v2/internal/store/identity.go index 85dcadc5d8..d61a57c48d 100644 --- a/ipld/car/v2/internal/store/identity.go +++ b/ipld/car/v2/internal/store/identity.go @@ -5,6 +5,7 @@ import ( "github.com/multiformats/go-multihash" ) +// IsIdentity inspects the CID and determines whether it is an IDENTITY CID. func IsIdentity(key cid.Cid) (digest []byte, ok bool, err error) { dmh, err := multihash.Decode(key.Hash()) if err != nil { diff --git a/ipld/car/v2/internal/store/index.go b/ipld/car/v2/internal/store/index.go index 55b12755a2..c7d188600d 100644 --- a/ipld/car/v2/internal/store/index.go +++ b/ipld/car/v2/internal/store/index.go @@ -1,30 +1,109 @@ package store import ( + "bytes" "io" + "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1/util" + "github.com/ipld/go-car/v2/internal/insertionindex" internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-varint" ) -func GenerateIndex(at io.ReaderAt, opts ...carv2.Option) (index.Index, error) { - var rs io.ReadSeeker - switch r := at.(type) { - case io.ReadSeeker: - rs = r - // The version may have been read from the given io.ReaderAt; therefore move back to the begining. - if _, err := rs.Seek(0, io.SeekStart); err != nil { - return nil, err - } - default: - var err error - rs, err = internalio.NewOffsetReadSeeker(r, 0) +// FindCid can be used to either up the existence, size and offset of a block +// if it exists in CAR as specified by the index; and optionally the data bytes +// of the block. +func FindCid( + reader io.ReaderAt, + idx index.Index, + key cid.Cid, + useWholeCids bool, + zeroLenAsEOF bool, + maxReadBytes uint64, + readBytes bool, +) ([]byte, int64, int, error) { + + var fnData []byte + var fnOffset int64 + var fnLen int = -1 + var fnErr error + err := idx.GetAll(key, func(offset uint64) bool { + reader, err := internalio.NewOffsetReadSeeker(reader, int64(offset)) if err != nil { - return nil, err + fnErr = err + return false + } + var readCid cid.Cid + if readBytes { + readCid, fnData, err = util.ReadNode(reader, zeroLenAsEOF, maxReadBytes) + if err != nil { + fnErr = err + return false + } + fnLen = len(fnData) + } else { + sectionLen, err := varint.ReadUvarint(reader) + if err != nil { + fnErr = err + return false + } + var cidLen int + cidLen, readCid, err = cid.CidFromReader(reader) + if err != nil { + fnErr = err + return false + } + fnLen = int(sectionLen) - cidLen + fnOffset = int64(offset) + reader.(interface{ Position() int64 }).Position() } + if useWholeCids { + if !readCid.Equals(key) { + fnLen = -1 + return true // continue looking + } + return false + } else { + if !bytes.Equal(readCid.Hash(), key.Hash()) { + // weird, bad index, continue looking + fnLen = -1 + return true + } + return false + } + }) + if err != nil { + return nil, -1, -1, err + } + if fnErr != nil { + return nil, -1, -1, fnErr + } + if fnLen == -1 { + return nil, -1, -1, index.ErrNotFound } + return fnData, fnOffset, fnLen, nil +} + +// Finalize will write the index to the writer at the offset specified in the header. It should only +// be used for a CARv2 and when the CAR interface is being closed. +func Finalize(writer io.WriterAt, header carv2.Header, idx *insertionindex.InsertionIndex, dataSize uint64, storeIdentityCIDs bool, indexCodec multicodec.Code) error { + // TODO check if add index option is set and don't write the index then set index offset to zero. + header = header.WithDataSize(dataSize) + header.Characteristics.SetFullyIndexed(storeIdentityCIDs) - // Note, we do not set any write options so that all write options fall back onto defaults. - return carv2.GenerateIndex(rs, opts...) + // TODO if index not needed don't bother flattening it. + fi, err := idx.Flatten(indexCodec) + if err != nil { + return err + } + if _, err := index.WriteTo(fi, internalio.NewOffsetWriter(writer, int64(header.IndexOffset))); err != nil { + return err + } + if _, err := header.WriteTo(internalio.NewOffsetWriter(writer, carv2.PragmaSize)); err != nil { + return err + } + return nil } diff --git a/ipld/car/v2/internal/store/put.go b/ipld/car/v2/internal/store/put.go new file mode 100644 index 0000000000..c9c1867ff3 --- /dev/null +++ b/ipld/car/v2/internal/store/put.go @@ -0,0 +1,54 @@ +package store + +import ( + "github.com/ipfs/go-cid" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/internal/insertionindex" +) + +// ShouldPut returns true if the block should be put into the CAR according to the options provided +// and the index. It returns false if the block should not be put into the CAR, either because it +// is an identity block and StoreIdentityCIDs is false, or because it already exists and +// BlockstoreAllowDuplicatePuts is false. +func ShouldPut( + idx *insertionindex.InsertionIndex, + c cid.Cid, + maxIndexCidSize uint64, + storeIdentityCIDs bool, + blockstoreAllowDuplicatePuts bool, + blockstoreUseWholeCIDs bool, +) (bool, error) { + + // If StoreIdentityCIDs option is disabled then treat IDENTITY CIDs like IdStore. + if !storeIdentityCIDs { + // Check for IDENTITY CID. If IDENTITY, ignore and move to the next block. + if _, ok, err := IsIdentity(c); err != nil { + return false, err + } else if ok { + return false, nil + } + } + + // Check if its size is too big. + // If larger than maximum allowed size, return error. + // Note, we need to check this regardless of whether we have IDENTITY CID or not. + // Since multhihash codes other than IDENTITY can result in large digests. + cSize := uint64(len(c.Bytes())) + if cSize > maxIndexCidSize { + return false, &carv2.ErrCidTooLarge{MaxSize: maxIndexCidSize, CurrentSize: cSize} + } + + if !blockstoreAllowDuplicatePuts { + if blockstoreUseWholeCIDs && idx.HasExactCID(c) { + return false, nil // deduplicated by CID + } + if !blockstoreUseWholeCIDs { + _, err := idx.Get(c) + if err == nil { + return false, nil // deduplicated by hash + } + } + } + + return true, nil +} diff --git a/ipld/car/v2/internal/store/resume.go b/ipld/car/v2/internal/store/resume.go new file mode 100644 index 0000000000..0167b41547 --- /dev/null +++ b/ipld/car/v2/internal/store/resume.go @@ -0,0 +1,200 @@ +package store + +import ( + "errors" + "fmt" + "io" + + "github.com/ipfs/go-cid" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/ipld/go-car/v2/internal/insertionindex" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-varint" +) + +type ReaderWriterAt interface { + io.ReaderAt + io.Writer + io.WriterAt +} + +// ResumableVersion performs two tasks - check if there is a valid header at the start of the, +// reader, then check whether the version of that header matches what we expect. +func ResumableVersion(reader io.Reader, writeAsV1 bool) error { + version, err := carv2.ReadVersion(reader) + if err != nil { + // The file is not a valid CAR file and cannot resume from it. + // Or the write must have failed before pragma was written. + return err + } + + switch { + case version == 1 && writeAsV1: + case version == 2 && !writeAsV1: + default: + // The file is not the expected version and we cannot resume from it. + return fmt.Errorf("cannot resume on CAR file with version %v", version) + } + return nil +} + +// Resume will attempt to resume a CARv2 or CARv1 file by checking that there exists an existing +// CAR and that the CAR header details match what is being requested for resumption. +// Resumption of a CARv2 involves "unfinalizing" the header by resetting it back to a bare state +// and then truncating the file to remove the index. Truncation is important because it allows a +// non-finalized CARv2 to be resumed from as the header won't contain the DataSize of the payload +// body and if the file also contains an index, we cannot determine the end of the payload. +// Therefore, when using a resumed, existing and finalized, CARv2, whose body may not extend +// beyond the index and then closing without finalization (e.g. due to a crash), the file will no +// longer be parseable because we won't have DataSize, and we won't be able to determine it by +// parsing the payload to EOF. +func Resume( + rw ReaderWriterAt, + dataReader io.ReaderAt, + dataWriter *internalio.OffsetWriteSeeker, + idx *insertionindex.InsertionIndex, + roots []cid.Cid, + dataOffset uint64, + v1 bool, + maxAllowedHeaderSize uint64, + zeroLengthSectionAsEOF bool, +) error { + + var headerInFile carv2.Header + var v1r internalio.ReadSeekerAt + + if !v1 { + if _, ok := rw.(interface{ Truncate(size int64) error }); !ok { + return fmt.Errorf("cannot resume a CARv2 without the ability to truncate (e.g. an io.File)") + } + + // Check if file was finalized by trying to read the CARv2 header. + // We check because if finalized the CARv1 reader behaviour needs to be adjusted since + // EOF will not signify end of CARv1 payload. i.e. index is most likely present. + r, err := internalio.NewOffsetReadSeeker(rw, carv2.PragmaSize) + if err != nil { + return err + } + _, err = headerInFile.ReadFrom(r) + + // If reading CARv2 header succeeded, and CARv1 offset in header is not zero then the file is + // most-likely finalized. Check padding and truncate the file to remove index. + // Otherwise, carry on reading the v1 payload at offset determined from b.header. + if err == nil && headerInFile.DataOffset != 0 { + if headerInFile.DataOffset != dataOffset { + // Assert that the padding on file matches the given WithDataPadding option. + wantPadding := headerInFile.DataOffset - carv2.PragmaSize - carv2.HeaderSize + gotPadding := dataOffset - carv2.PragmaSize - carv2.HeaderSize + return fmt.Errorf( + "cannot resume from file with mismatched CARv1 offset; "+ + "`WithDataPadding` option must match the padding on file. "+ + "Expected padding value of %v but got %v", wantPadding, gotPadding, + ) + } else if headerInFile.DataSize == 0 { + // If CARv1 size is zero, since CARv1 offset wasn't, then the CARv2 header was + // most-likely partially written. Since we write the header last in Finalize then the + // file most-likely contains the index and we cannot know where it starts, therefore + // can't resume. + return errors.New("corrupt CARv2 header; cannot resume from file") + } + } + + v1r, err = internalio.NewOffsetReadSeeker(dataReader, 0) + if err != nil { + return err + } + } else { + var err error + v1r, err = internalio.NewOffsetReadSeeker(rw, 0) + if err != nil { + return err + } + } + + header, err := carv1.ReadHeader(v1r, maxAllowedHeaderSize) + if err != nil { + // Cannot read the CARv1 header; the file is most likely corrupt. + return fmt.Errorf("error reading car header: %w", err) + } + if !header.Matches(carv1.CarHeader{Roots: roots, Version: 1}) { + // Cannot resume if version and root does not match. + return errors.New("cannot resume on file with mismatching data header") + } + + if headerInFile.DataOffset != 0 { + // If header in file contains the size of car v1, then the index is most likely present. + // Since we will need to re-generate the index, as the one in file is flattened, truncate + // the file so that the Readonly.backing has the right set of bytes to deal with. + // This effectively means resuming from a finalized file will wipe its index even if there + // are no blocks put unless the user calls finalize. + if err := rw.(interface{ Truncate(size int64) error }).Truncate(int64(headerInFile.DataOffset + headerInFile.DataSize)); err != nil { + return err + } + } + + if !v1 { + // Now that CARv2 header is present on file, clear it to avoid incorrect size and offset in + // header in case blocksotre is closed without finalization and is resumed from. + wat, ok := rw.(io.WriterAt) + if !ok { // how would we get this far?? + return errors.New("cannot resume from file without io.WriterAt") + } + if _, err := new(carv2.Header).WriteTo(internalio.NewOffsetWriter(wat, carv2.PragmaSize)); err != nil { + return fmt.Errorf("could not un-finalize: %w", err) + } + } + + // TODO See how we can reduce duplicate code here. + // The code here comes from car.GenerateIndex. + // Copied because we need to populate an insertindex, not a sorted index. + // Producing a sorted index via generate, then converting it to insertindex is not possible. + // Because Index interface does not expose internal records. + // This may be done as part of https://github.com/ipld/go-car/issues/95 + + offset, err := carv1.HeaderSize(header) + if err != nil { + return err + } + sectionOffset := int64(0) + if sectionOffset, err = v1r.Seek(int64(offset), io.SeekStart); err != nil { + return err + } + + for { + // Grab the length of the section. + // Note that ReadUvarint wants a ByteReader. + length, err := varint.ReadUvarint(v1r) + if err != nil { + if err == io.EOF { + break + } + return err + } + + // Null padding; by default it's an error. + if length == 0 { + if zeroLengthSectionAsEOF { + break + } else { + return fmt.Errorf("carv1 null padding not allowed by default; see WithZeroLegthSectionAsEOF") + } + } + + // Grab the CID. + n, c, err := cid.CidFromReader(v1r) + if err != nil { + return err + } + idx.InsertNoReplace(c, uint64(sectionOffset)) + + // Seek to the next section by skipping the block. + // The section length includes the CID, so subtract it. + if sectionOffset, err = v1r.Seek(int64(length)-int64(n), io.SeekCurrent); err != nil { + return err + } + } + // Seek to the end of last skipped block where the writer should resume writing. + _, err = dataWriter.Seek(sectionOffset, io.SeekStart) + return err +} diff --git a/ipld/car/v2/internal/store/version.go b/ipld/car/v2/internal/store/version.go deleted file mode 100644 index b43496ef7e..0000000000 --- a/ipld/car/v2/internal/store/version.go +++ /dev/null @@ -1,17 +0,0 @@ -package store - -import ( - "io" - - carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/internal/carv1" -) - -func ReadVersion(at io.ReaderAt, opts ...carv2.Option) (uint64, error) { - o := carv2.ApplyOptions(opts...) - header, err := carv1.ReadHeaderAt(at, o.MaxAllowedHeaderSize) - if err != nil { - return 0, err - } - return header.Version, nil -} diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index 8b5fe9b4e4..92a5d11d93 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -179,3 +179,53 @@ func MaxAllowedSectionSize(max uint64) Option { o.MaxAllowedSectionSize = max } } + +// --------------------------------------------------- storage interface options + +// UseWholeCIDs is a read option which makes a CAR storage interface (blockstore +// or storage) identify blocks by whole CIDs, and not just their multihashes. +// The default is to use multihashes, which matches the current semantics of +// go-ipfs-blockstore v1. +// +// Enabling this option affects a number of methods, including read-only ones: +// +// - Get, Has, and HasSize will only return a block only if the entire CID is +// present in the CAR file. +// +// • AllKeysChan will return the original whole CIDs, instead of with their +// multicodec set to "raw" to just provide multihashes. +// +// • If AllowDuplicatePuts isn't set, Put and PutMany will deduplicate by the +// whole CID, allowing different CIDs with equal multihashes. +// +// Note that this option only affects the storage interfaces (blockstore +// or storage), and is ignored by the root go-car/v2 package. +func UseWholeCIDs(enable bool) Option { + return func(o *Options) { + o.BlockstoreUseWholeCIDs = enable + } +} + +// WriteAsCarV1 is a write option which makes a CAR interface (blockstore or +// storage) write the output as a CARv1 only, with no CARv2 header or index. +// Indexing is used internally during write but is discarded upon finalization. +// +// Note that this option only affects the storage interfaces (blockstore +// or storage), and is ignored by the root go-car/v2 package. +func WriteAsCarV1(asCarV1 bool) Option { + return func(o *Options) { + o.WriteAsCarV1 = asCarV1 + } +} + +// AllowDuplicatePuts is a write option which makes a CAR interface (blockstore +// or storage) not deduplicate blocks in Put and PutMany. The default is to +// deduplicate, which matches the current semantics of go-ipfs-blockstore v1. +// +// Note that this option only affects the storage interfaces (blockstore +// or storage), and is ignored by the root go-car/v2 package. +func AllowDuplicatePuts(allow bool) Option { + return func(o *Options) { + o.BlockstoreAllowDuplicatePuts = allow + } +} diff --git a/ipld/car/v2/storage/storage.go b/ipld/car/v2/storage/storage.go index 82ad0806e9..6bbed612cf 100644 --- a/ipld/car/v2/storage/storage.go +++ b/ipld/car/v2/storage/storage.go @@ -17,15 +17,20 @@ import ( internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipld/go-car/v2/internal/store" ipldstorage "github.com/ipld/go-ipld-prime/storage" - "github.com/multiformats/go-varint" ) -var errClosed = fmt.Errorf("cannot use a carv2 storage after closing") +var errClosed = errors.New("cannot use a CARv2 storage after closing") + +type ReaderWriterAt interface { + io.ReaderAt + io.Writer + io.WriterAt +} type ReadableCar interface { ipldstorage.ReadableStorage ipldstorage.StreamingReadableStorage - Roots() ([]cid.Cid, error) + Roots() []cid.Cid } // WritableCar is compatible with storage.WritableStorage but also returns @@ -35,20 +40,22 @@ type ReadableCar interface { // existing storage.PutStream() implementation. type WritableCar interface { ipldstorage.WritableStorage - Roots() ([]cid.Cid, error) + Roots() []cid.Cid Finalize() error } var _ ipldstorage.ReadableStorage = (*StorageCar)(nil) var _ ipldstorage.StreamingReadableStorage = (*StorageCar)(nil) var _ ReadableCar = (*StorageCar)(nil) +var _ ipldstorage.WritableStorage = (*StorageCar)(nil) type StorageCar struct { - idx *insertionindex.InsertionIndex + idx index.Index reader io.ReaderAt writer positionedWriter dataWriter *internalio.OffsetWriteSeeker header carv2.Header + roots []cid.Cid opts carv2.Options closed bool @@ -61,10 +68,7 @@ type positionedWriter interface { } func NewReadable(reader io.ReaderAt, opts ...carv2.Option) (ReadableCar, error) { - sc := &StorageCar{ - opts: carv2.ApplyOptions(opts...), - idx: insertionindex.NewInsertionIndex(), - } + sc := &StorageCar{opts: carv2.ApplyOptions(opts...)} rr := internalio.ToReadSeeker(reader) header, err := carv1.ReadHeader(rr, sc.opts.MaxAllowedHeaderSize) @@ -73,39 +77,66 @@ func NewReadable(reader io.ReaderAt, opts ...carv2.Option) (ReadableCar, error) } switch header.Version { case 1: + sc.roots = header.Roots + sc.reader = reader rr.Seek(0, io.SeekStart) + sc.idx = insertionindex.NewInsertionIndex() if err := carv2.LoadIndex(sc.idx, rr, opts...); err != nil { return nil, err } - sc.reader = reader case 2: v2r, err := carv2.NewReader(reader, opts...) if err != nil { return nil, err } - dr, err := v2r.DataReader() + sc.roots, err = v2r.Roots() if err != nil { return nil, err } - if err := carv2.LoadIndex(sc.idx, dr, opts...); err != nil { - return nil, err + if v2r.Header.HasIndex() { + ir, err := v2r.IndexReader() + if err != nil { + return nil, err + } + sc.idx, err = index.ReadFrom(ir) + if err != nil { + return nil, err + } + } else { + dr, err := v2r.DataReader() + if err != nil { + return nil, err + } + sc.idx = insertionindex.NewInsertionIndex() + if err := carv2.LoadIndex(sc.idx, dr, opts...); err != nil { + return nil, err + } } if sc.reader, err = v2r.DataReader(); err != nil { return nil, err } default: - return nil, fmt.Errorf("unsupported car version: %v", header.Version) + return nil, fmt.Errorf("unsupported CAR version: %v", header.Version) } return sc, nil } func NewWritable(writer io.Writer, roots []cid.Cid, opts ...carv2.Option) (WritableCar, error) { + sc, err := newWritable(writer, roots, opts...) + if err != nil { + return nil, err + } + return sc.init() +} + +func newWritable(writer io.Writer, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { sc := &StorageCar{ writer: &positionTrackingWriter{w: writer}, idx: insertionindex.NewInsertionIndex(), header: carv2.NewHeader(0), opts: carv2.ApplyOptions(opts...), + roots: roots, } if p := sc.opts.DataPadding; p > 0 { @@ -124,67 +155,120 @@ func NewWritable(writer io.Writer, roots []cid.Cid, opts ...carv2.Option) (Writa sc.dataWriter = internalio.NewOffsetWriter(writerAt, offset) } else { if !sc.opts.WriteAsCarV1 { - return nil, fmt.Errorf("cannot write as carv2 to a non-seekable writer") + return nil, fmt.Errorf("cannot write as CARv2 to a non-seekable writer") + } + } + + return sc, nil +} + +func newReadableWritable(rw ReaderWriterAt, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { + sc, err := newWritable(rw, roots, opts...) + if err != nil { + return nil, err + } + + sc.reader = rw + if !sc.opts.WriteAsCarV1 { + sc.reader, err = internalio.NewOffsetReadSeeker(rw, int64(sc.header.DataOffset)) + if err != nil { + return nil, err } } - if err := sc.initWithRoots(writer, !sc.opts.WriteAsCarV1, roots); err != nil { + return sc, nil +} + +func NewReadableWritable(rw ReaderWriterAt, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { + sc, err := newReadableWritable(rw, roots, opts...) + if err != nil { + return nil, err + } + if _, err := sc.init(); err != nil { + return nil, err + } + return sc, nil +} + +func OpenReadableWritable(rw ReaderWriterAt, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { + sc, err := newReadableWritable(rw, roots, opts...) + if err != nil { return nil, err } + // attempt to resume + rs, err := internalio.NewOffsetReadSeeker(rw, 0) + if err != nil { + return nil, err + } + if err := store.ResumableVersion(rs, sc.opts.WriteAsCarV1); err != nil { + return nil, err + } + if err := store.Resume( + rw, + sc.reader, + sc.dataWriter, + sc.idx.(*insertionindex.InsertionIndex), + roots, + sc.header.DataOffset, + sc.opts.WriteAsCarV1, + sc.opts.MaxAllowedHeaderSize, + sc.opts.ZeroLengthSectionAsEOF, + ); err != nil { + return nil, err + } return sc, nil } -func (sc *StorageCar) initWithRoots(writer io.Writer, v2 bool, roots []cid.Cid) error { - if v2 { - if _, err := writer.Write(carv2.Pragma); err != nil { - return err +func (sc *StorageCar) init() (WritableCar, error) { + if !sc.opts.WriteAsCarV1 { + if _, err := sc.writer.Write(carv2.Pragma); err != nil { + return nil, err } - return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, sc.dataWriter) } - return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, writer) + var w io.Writer = sc.dataWriter + if sc.dataWriter == nil { + w = sc.writer + } + if err := carv1.WriteHeader(&carv1.CarHeader{Roots: sc.roots, Version: 1}, w); err != nil { + return nil, err + } + return sc, nil +} + +func (sc *StorageCar) Roots() []cid.Cid { + return sc.roots } func (sc *StorageCar) Put(ctx context.Context, keyStr string, data []byte) error { keyCid, err := cid.Cast([]byte(keyStr)) if err != nil { - return err + return fmt.Errorf("bad CID key: %w", err) } sc.mu.Lock() defer sc.mu.Unlock() - // If StoreIdentityCIDs option is disabled then treat IDENTITY CIDs like IdStore. - if !sc.opts.StoreIdentityCIDs { - // Check for IDENTITY CID. If IDENTITY, ignore and move to the next block. - if _, ok, err := store.IsIdentity(keyCid); err != nil { - return err - } else if ok { - return nil - } + if sc.closed { + return errClosed } - // Check if its size is too big. - // If larger than maximum allowed size, return error. - // Note, we need to check this regardless of whether we have IDENTITY CID or not. - // Since multhihash codes other than IDENTITY can result in large digests. - cSize := uint64(len(keyCid.Bytes())) - if cSize > sc.opts.MaxIndexCidSize { - return &carv2.ErrCidTooLarge{MaxSize: sc.opts.MaxIndexCidSize, CurrentSize: cSize} + idx, ok := sc.idx.(*insertionindex.InsertionIndex) + if !ok || sc.writer == nil { + return fmt.Errorf("cannot put into a read-only CAR") } - // TODO: if we are write-only and BlockstoreAllowDuplicatePuts then we don't - // really need an index at all - if !sc.opts.BlockstoreAllowDuplicatePuts { - if sc.opts.BlockstoreUseWholeCIDs && sc.idx.HasExactCID(keyCid) { - return nil // deduplicated by CID - } - if !sc.opts.BlockstoreUseWholeCIDs { - _, err := sc.idx.Get(keyCid) - if err == nil { - return nil // deduplicated by hash - } - } + if should, err := store.ShouldPut( + idx, + keyCid, + sc.opts.MaxIndexCidSize, + sc.opts.StoreIdentityCIDs, + sc.opts.BlockstoreAllowDuplicatePuts, + sc.opts.BlockstoreUseWholeCIDs, + ); err != nil { + return err + } else if !should { + return nil } w := sc.writer @@ -195,27 +279,15 @@ func (sc *StorageCar) Put(ctx context.Context, keyStr string, data []byte) error if err := util.LdWrite(w, keyCid.Bytes(), data); err != nil { return err } - sc.idx.InsertNoReplace(keyCid, n) + idx.InsertNoReplace(keyCid, n) return nil } -func (sc *StorageCar) Roots() ([]cid.Cid, error) { - ors, err := internalio.NewOffsetReadSeeker(sc.reader, 0) - if err != nil { - return nil, err - } - header, err := carv1.ReadHeader(ors, sc.opts.MaxAllowedHeaderSize) - if err != nil { - return nil, fmt.Errorf("error reading car header: %w", err) - } - return header.Roots, nil -} - func (sc *StorageCar) Has(ctx context.Context, keyStr string) (bool, error) { keyCid, err := cid.Cast([]byte(keyStr)) if err != nil { - return false, err + return false, fmt.Errorf("bad CID key: %w", err) } if !sc.opts.StoreIdentityCIDs { @@ -236,23 +308,21 @@ func (sc *StorageCar) Has(ctx context.Context, keyStr string) (bool, error) { return false, errClosed } - if sc.opts.BlockstoreUseWholeCIDs { - var foundCid cid.Cid - _, foundCid, err = sc.idx.GetCid(keyCid) - if err != nil { - if !foundCid.Equals(keyCid) { - return false, nil - } - } - } else { - _, err = sc.idx.Get(keyCid) - } + _, _, size, err := store.FindCid( + sc.reader, + sc.idx, + keyCid, + sc.opts.BlockstoreUseWholeCIDs, + sc.opts.ZeroLengthSectionAsEOF, + sc.opts.MaxAllowedSectionSize, + false, + ) if errors.Is(err, index.ErrNotFound) { return false, nil } else if err != nil { return false, err } - return true, nil + return size > -1, nil } func (sc *StorageCar) Get(ctx context.Context, keyStr string) ([]byte, error) { @@ -264,9 +334,13 @@ func (sc *StorageCar) Get(ctx context.Context, keyStr string) ([]byte, error) { } func (sc *StorageCar) GetStream(ctx context.Context, keyStr string) (io.ReadCloser, error) { + if sc.reader == nil { + return nil, fmt.Errorf("cannot read from a write-only CAR") + } + keyCid, err := cid.Cast([]byte(keyStr)) if err != nil { - return nil, err + return nil, fmt.Errorf("bad CID key: %w", err) } if !sc.opts.StoreIdentityCIDs { @@ -287,46 +361,30 @@ func (sc *StorageCar) GetStream(ctx context.Context, keyStr string) (io.ReadClos return nil, errClosed } - fnSize := -1 - var offset uint64 - if sc.opts.BlockstoreUseWholeCIDs { - var foundCid cid.Cid - offset, foundCid, err = sc.idx.GetCid(keyCid) - if err != nil { - if !foundCid.Equals(keyCid) { - return nil, ErrNotFound{Cid: keyCid} - } - } - } else { - offset, err = sc.idx.Get(keyCid) - } + _, offset, size, err := store.FindCid( + sc.reader, + sc.idx, + keyCid, + sc.opts.BlockstoreUseWholeCIDs, + sc.opts.ZeroLengthSectionAsEOF, + sc.opts.MaxAllowedSectionSize, + false, + ) if errors.Is(err, index.ErrNotFound) { return nil, ErrNotFound{Cid: keyCid} } else if err != nil { return nil, err } - - rdr, err := internalio.NewOffsetReadSeeker(sc.reader, int64(offset)) - if err != nil { - return nil, err - } - sectionLen, err := varint.ReadUvarint(rdr) - if err != nil { - return nil, err - } - cidLen, _, err := cid.CidFromReader(rdr) - if err != nil { - return nil, err - } - fnSize = int(sectionLen) - cidLen - offset = uint64(rdr.(interface{ Offset() int64 }).Offset()) - if fnSize == -1 { - return nil, ErrNotFound{Cid: keyCid} - } - return io.NopCloser(io.NewSectionReader(sc.reader, int64(offset), int64(fnSize))), nil + return io.NopCloser(io.NewSectionReader(sc.reader, offset, int64(size))), nil } func (sc *StorageCar) Finalize() error { + idx, ok := sc.idx.(*insertionindex.InsertionIndex) + if !ok || sc.writer == nil { + // ignore this, it's not writable + return nil + } + if sc.opts.WriteAsCarV1 { return nil } @@ -342,29 +400,12 @@ func (sc *StorageCar) Finalize() error { if sc.closed { // Allow duplicate Finalize calls, just like Close. // Still error, just like ReadOnly.Close; it should be discarded. - return fmt.Errorf("called Finalize on a closed blockstore") + return fmt.Errorf("called Finalize on a closed storage CAR") } - // TODO check if add index option is set and don't write the index then set index offset to zero. - sc.header = sc.header.WithDataSize(uint64(sc.dataWriter.Position())) - sc.header.Characteristics.SetFullyIndexed(sc.opts.StoreIdentityCIDs) - sc.closed = true - fi, err := sc.idx.Flatten(sc.opts.IndexCodec) - if err != nil { - return err - } - if _, err := index.WriteTo(fi, internalio.NewOffsetWriter(wat, int64(sc.header.IndexOffset))); err != nil { - return err - } - var buf bytes.Buffer - sc.header.WriteTo(&buf) - if _, err := sc.header.WriteTo(internalio.NewOffsetWriter(wat, carv2.PragmaSize)); err != nil { - return err - } - - return nil + return store.Finalize(wat, sc.header, idx, uint64(sc.dataWriter.Position()), sc.opts.StoreIdentityCIDs, sc.opts.IndexCodec) } type positionTrackingWriter struct { diff --git a/ipld/car/v2/storage/storage_test.go b/ipld/car/v2/storage/storage_test.go index 0b34b50251..ba1d018789 100644 --- a/ipld/car/v2/storage/storage_test.go +++ b/ipld/car/v2/storage/storage_test.go @@ -1,6 +1,9 @@ package storage_test +// TODO: test readable can't write and writable can't read + import ( + "bytes" "context" "crypto/sha512" "errors" @@ -9,82 +12,100 @@ import ( "math/rand" "os" "path/filepath" + "sync" "testing" "time" "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/storage" + "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" + mh "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" ) var rng = rand.New(rand.NewSource(1413)) +var rngLk sync.Mutex func TestReadable(t *testing.T) { tests := []struct { - name string - v1OrV2path string - opts []carv2.Option + name string + inputPath string + opts []carv2.Option + noIdCids bool }{ { "OpenedWithCarV1", "../testdata/sample-v1.car", - []carv2.Option{blockstore.UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, + []carv2.Option{carv2.UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, + false, }, { "OpenedWithCarV1_NoIdentityCID", "../testdata/sample-v1.car", - []carv2.Option{blockstore.UseWholeCIDs(true)}, + []carv2.Option{carv2.UseWholeCIDs(true)}, + false, }, { "OpenedWithCarV2", "../testdata/sample-wrapped-v2.car", - []carv2.Option{blockstore.UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, + []carv2.Option{carv2.UseWholeCIDs(true), carv2.StoreIdentityCIDs(true)}, + // index already exists, but was made without identity CIDs, but opening with StoreIdentityCIDs(true) means we check the index + true, }, { "OpenedWithCarV2_NoIdentityCID", "../testdata/sample-wrapped-v2.car", - []carv2.Option{blockstore.UseWholeCIDs(true)}, + []carv2.Option{carv2.UseWholeCIDs(true)}, + false, }, { "OpenedWithCarV1ZeroLenSection", "../testdata/sample-v1-with-zero-len-section.car", - []carv2.Option{blockstore.UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + []carv2.Option{carv2.UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + false, }, { "OpenedWithAnotherCarV1ZeroLenSection", "../testdata/sample-v1-with-zero-len-section2.car", - []carv2.Option{blockstore.UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + []carv2.Option{carv2.UseWholeCIDs(true), carv2.ZeroLengthSectionAsEOF(true)}, + false, + }, + { + "IndexlessV2", + "../testdata/sample-v2-indexless.car", + []carv2.Option{carv2.UseWholeCIDs(true)}, + false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx := context.TODO() - subjectReader, err := os.Open(tt.v1OrV2path) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + // Setup new StorageCar + inputReader, err := os.Open(tt.inputPath) require.NoError(t, err) - subject, err := storage.NewReadable(subjectReader, tt.opts...) + t.Cleanup(func() { require.NoError(t, inputReader.Close()) }) + readable, err := storage.NewReadable(inputReader, tt.opts...) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, subjectReader.Close()) }) - f, err := os.Open(tt.v1OrV2path) + // Setup BlockReader to compare against + actualReader, err := os.Open(tt.inputPath) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, f.Close()) }) - - reader, err := carv2.NewBlockReader(f, tt.opts...) + t.Cleanup(func() { require.NoError(t, actualReader.Close()) }) + actual, err := carv2.NewBlockReader(actualReader, tt.opts...) require.NoError(t, err) // Assert roots match v1 payload. - wantRoots := reader.Roots - gotRoots, err := subject.Roots() - require.NoError(t, err) - require.Equal(t, wantRoots, gotRoots) + require.Equal(t, actual.Roots, readable.Roots()) for { - wantBlock, err := reader.Next() + wantBlock, err := actual.Next() if err == io.EOF { break } @@ -92,18 +113,24 @@ func TestReadable(t *testing.T) { key := wantBlock.Cid() - // Assert blockstore contains key. - has, err := subject.Has(ctx, key.KeyString()) + // Assert StorageCar contains key. + has, err := readable.Has(ctx, key.KeyString()) require.NoError(t, err) - require.True(t, has) + if key.Prefix().MhType == uint64(multicodec.Identity) && tt.noIdCids { + // fixture wasn't made with StoreIdentityCIDs, but we opened it with StoreIdentityCIDs, + // so they aren't there to find + require.False(t, has) + } else { + require.True(t, has) + } // Assert block itself matches v1 payload block. if has { - gotBlock, err := subject.Get(ctx, key.KeyString()) + gotBlock, err := readable.Get(ctx, key.KeyString()) require.NoError(t, err) require.Equal(t, wantBlock.RawData(), gotBlock) - reader, err := subject.GetStream(ctx, key.KeyString()) + reader, err := readable.GetStream(ctx, key.KeyString()) require.NoError(t, err) data, err := io.ReadAll(reader) require.NoError(t, err) @@ -111,13 +138,13 @@ func TestReadable(t *testing.T) { } } - // not exists + // test not exists c := randCid() - has, err := subject.Has(ctx, c.KeyString()) + has, err := readable.Has(ctx, c.KeyString()) require.NoError(t, err) require.False(t, has) - _, err = subject.Get(ctx, c.KeyString()) + _, err = readable.Get(ctx, c.KeyString()) require.True(t, errors.Is(err, storage.ErrNotFound{})) require.True(t, storage.IsNotFound(err)) require.Contains(t, err.Error(), c.String()) @@ -126,11 +153,11 @@ func TestReadable(t *testing.T) { storeIdentity := carv2.ApplyOptions(tt.opts...).StoreIdentityCIDs c = randIdentityCid() - has, err = subject.Has(ctx, c.KeyString()) + has, err = readable.Has(ctx, c.KeyString()) require.NoError(t, err) require.Equal(t, !storeIdentity, has) - got, err := subject.Get(ctx, c.KeyString()) + got, err := readable.Get(ctx, c.KeyString()) if !storeIdentity { require.NoError(t, err) mh, err := multihash.Decode(c.Hash()) @@ -145,6 +172,15 @@ func TestReadable(t *testing.T) { } } +func TestReadableBadVersion(t *testing.T) { + f, err := os.Open("../testdata/sample-rootless-v42.car") + require.NoError(t, err) + t.Cleanup(func() { f.Close() }) + subject, err := storage.NewReadable(f) + require.Errorf(t, err, "unsupported car version: 42") + require.Nil(t, subject) +} + func TestWritable(t *testing.T) { originalCarV1Path := "../testdata/sample-v1.car" @@ -154,154 +190,971 @@ func TestWritable(t *testing.T) { options []carv2.Option expectedV1StartOffset int64 }{ - // no options, expect a standard CARv2 with the noidentity inner CARv1 {"carv2_noopt", "sample-v1-noidentity.car", []carv2.Option{}, int64(carv2.PragmaSize + carv2.HeaderSize)}, - // no options, expect a standard CARv2 with the noidentity inner CARv1 {"carv2_identity", "sample-v1.car", []carv2.Option{carv2.StoreIdentityCIDs(true)}, int64(carv2.PragmaSize + carv2.HeaderSize)}, - // option to only write as a CARv1, expect the noidentity inner CARv1 - {"carv1", "sample-v1-noidentity.car", []carv2.Option{blockstore.WriteAsCarV1(true)}, int64(0)}, - // option to only write as a CARv1, expect the noidentity inner CARv1 - {"carv1_identity", "sample-v1.car", []carv2.Option{blockstore.WriteAsCarV1(true), carv2.StoreIdentityCIDs(true)}, int64(0)}, + {"carv1", "sample-v1-noidentity.car", []carv2.Option{carv2.WriteAsCarV1(true)}, int64(0)}, + {"carv1_identity", "sample-v1.car", []carv2.Option{carv2.WriteAsCarV1(true), carv2.StoreIdentityCIDs(true)}, int64(0)}, } - for _, variant := range variants { - t.Run(variant.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() + for _, mode := range []string{"WithRead", "WithoutRead"} { + t.Run(mode, func(t *testing.T) { + for _, variant := range variants { + t.Run(variant.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + opts := carv2.ApplyOptions(variant.options...) + + // Setup input file using standard CarV1 reader + srcFile, err := os.Open(originalCarV1Path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, srcFile.Close()) }) + r, err := carv1.NewCarReader(srcFile) + require.NoError(t, err) + + path := filepath.Join(t.TempDir(), fmt.Sprintf("writable_%s_%s.car", mode, variant.name)) + var dstFile *os.File + + var writable *storage.StorageCar + if mode == "WithoutRead" { + dstFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + require.NoError(t, err) + t.Cleanup(func() { dstFile.Close() }) + var writer io.Writer = &writerOnly{dstFile} + if !opts.WriteAsCarV1 { + writer = &writerAtOnly{dstFile} + } + w, err := storage.NewWritable(writer, r.Header.Roots, variant.options...) + require.NoError(t, err) + writable = w.(*storage.StorageCar) + } else { + dstFile, err = os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644) + require.NoError(t, err) + t.Cleanup(func() { dstFile.Close() }) + writable, err = storage.NewReadableWritable(dstFile, r.Header.Roots, variant.options...) + require.NoError(t, err) + } + + require.Equal(t, r.Header.Roots, writable.Roots()) + + cids := make([]cid.Cid, 0) + var idCidCount int + for { + // read from source + b, err := r.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + // write to dest + err = writable.Put(ctx, b.Cid().KeyString(), b.RawData()) + require.NoError(t, err) + cids = append(cids, b.Cid()) + + dmh, err := multihash.Decode(b.Cid().Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + idCidCount++ + } + + if mode == "WithRead" { + // writable is a ReadableWritable / StorageCar + + // read back out the one we just wrote + gotBlock, err := writable.Get(ctx, b.Cid().KeyString()) + require.NoError(t, err) + require.Equal(t, b.RawData(), gotBlock) + + reader, err := writable.GetStream(ctx, b.Cid().KeyString()) + require.NoError(t, err) + data, err := io.ReadAll(reader) + require.NoError(t, err) + require.Equal(t, b.RawData(), data) + + // try reading a random one: + candIndex := rng.Intn(len(cids)) + var candidate cid.Cid + for _, c := range cids { + if candIndex == 0 { + candidate = c + break + } + candIndex-- + } + has, err := writable.Has(ctx, candidate.KeyString()) + require.NoError(t, err) + require.True(t, has) + + // not exists + c := randCid() + has, err = writable.Has(ctx, c.KeyString()) + require.NoError(t, err) + require.False(t, has) + _, err = writable.Get(ctx, c.KeyString()) + require.True(t, errors.Is(err, storage.ErrNotFound{})) + require.True(t, storage.IsNotFound(err)) + require.Contains(t, err.Error(), c.String()) + + // random identity, should only find this if we _don't_ store identity CIDs + c = randIdentityCid() + has, err = writable.Has(ctx, c.KeyString()) + require.NoError(t, err) + require.Equal(t, !opts.StoreIdentityCIDs, has) + + got, err := writable.Get(ctx, c.KeyString()) + if !opts.StoreIdentityCIDs { + require.NoError(t, err) + mh, err := multihash.Decode(c.Hash()) + require.NoError(t, err) + require.Equal(t, mh.Digest, got) + } else { + require.True(t, errors.Is(err, storage.ErrNotFound{})) + require.True(t, storage.IsNotFound(err)) + require.Contains(t, err.Error(), c.String()) + } + } + } + + err = writable.Finalize() + require.NoError(t, err) + + err = dstFile.Close() + require.NoError(t, err) + + // test header version using carv2 reader + reopen, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, reopen.Close()) }) + rd, err := carv2.NewReader(reopen) + require.NoError(t, err) + require.Equal(t, opts.WriteAsCarV1, rd.Version == 1) + + // now compare the binary contents of the written file to the expected file + comparePath := filepath.Join("../testdata/", variant.compareCarV1) + compareStat, err := os.Stat(comparePath) + require.NoError(t, err) + + wrote, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, wrote.Close()) }) + _, err = wrote.Seek(variant.expectedV1StartOffset, io.SeekStart) + require.NoError(t, err) + hasher := sha512.New() + gotWritten, err := io.Copy(hasher, io.LimitReader(wrote, compareStat.Size())) + require.NoError(t, err) + gotSum := hasher.Sum(nil) + + hasher.Reset() + compareV1, err := os.Open(comparePath) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, compareV1.Close()) }) + wantWritten, err := io.Copy(hasher, compareV1) + require.NoError(t, err) + wantSum := hasher.Sum(nil) + + require.Equal(t, wantWritten, gotWritten) + require.Equal(t, wantSum, gotSum) + }) + } + }) + } +} + +func TestCannotWriteableV2WithoutWriterAt(t *testing.T) { + w, err := storage.NewWritable(&writerOnly{os.Stdout}, []cid.Cid{}) + require.Error(t, err) + require.Nil(t, w) +} + +func TestErrorsWhenWritingCidTooLarge(t *testing.T) { + maxAllowedCidSize := uint64(20) + + path := filepath.Join(t.TempDir(), "writable-with-id-enabled-too-large.car") + out, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, out.Close()) }) + subject, err := storage.NewWritable(out, []cid.Cid{}, carv2.MaxIndexCidSize(maxAllowedCidSize)) + require.NoError(t, err) + + // normal block but shorten the CID to make it acceptable + testCid, testData := randBlock() + mh, err := mh.Decode(testCid.Hash()) + require.NoError(t, err) + dig := mh.Digest[:10] + shortMh, err := multihash.Encode(dig, mh.Code) + require.NoError(t, err) + testCid = cid.NewCidV1(mh.Code, shortMh) - opts := carv2.ApplyOptions(variant.options...) + err = subject.Put(context.TODO(), testCid.KeyString(), testData) + require.NoError(t, err) - srcFile, err := os.Open(originalCarV1Path) + // standard CID but too long for options + testCid, testData = randBlock() + err = subject.Put(context.TODO(), testCid.KeyString(), testData) + require.Equal(t, &carv2.ErrCidTooLarge{MaxSize: maxAllowedCidSize, CurrentSize: uint64(testCid.ByteLen())}, err) +} + +func TestConcurrentUse(t *testing.T) { + dst, err := os.OpenFile(filepath.Join(t.TempDir(), "readwrite.car"), os.O_CREATE|os.O_RDWR, 0644) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, dst.Close()) }) + wbs, err := storage.NewReadableWritable(dst, nil) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + require.NoError(t, err) + t.Cleanup(func() { wbs.Finalize() }) + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + testCid, testData := randBlock() + + has, err := wbs.Has(ctx, testCid.KeyString()) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, srcFile.Close()) }) - r, err := carv1.NewCarReader(srcFile) + require.False(t, has) + + err = wbs.Put(ctx, testCid.KeyString(), testData) require.NoError(t, err) - path := filepath.Join("/tmp/", fmt.Sprintf("writable_%s.car", variant.name)) - dstFile, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) + got, err := wbs.Get(ctx, testCid.KeyString()) require.NoError(t, err) - var writer io.Writer = &writerOnly{dstFile} - if !opts.WriteAsCarV1 { - writer = &writerAtOnly{dstFile} + require.Equal(t, testData, got) + }() + } + wg.Wait() +} + +func TestNullPadding(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + paddedV1, err := os.ReadFile("../testdata/sample-v1-with-zero-len-section.car") + require.NoError(t, err) + + readable, err := storage.NewReadable(bufferReaderAt(paddedV1), carv2.ZeroLengthSectionAsEOF(true)) + require.NoError(t, err) + + roots := readable.Roots() + require.Len(t, roots, 1) + has, err := readable.Has(ctx, roots[0].KeyString()) + require.NoError(t, err) + require.True(t, has) + + actual, err := carv2.NewBlockReader(bytes.NewReader(paddedV1), carv2.ZeroLengthSectionAsEOF(true)) + require.NoError(t, err) + + for { + wantBlock, err := actual.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + b, err := readable.Get(ctx, wantBlock.Cid().KeyString()) + require.NoError(t, err) + require.Equal(t, wantBlock.RawData(), b) + } +} + +func TestPutSameHashes(t *testing.T) { + tdir := t.TempDir() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + // This writable allows duplicate puts, and identifies by multihash as per the default. + pathAllowDups := filepath.Join(tdir, "writable-allowdup.car") + dstAllowDups, err := os.OpenFile(pathAllowDups, os.O_CREATE|os.O_RDWR, 0644) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, dstAllowDups.Close()) }) + wbsAllowDups, err := storage.NewReadableWritable(dstAllowDups, nil, carv2.AllowDuplicatePuts(true)) + require.NoError(t, err) + + // This writable deduplicates puts by CID. + pathByCID := filepath.Join(tdir, "writable-dedup-wholecid.car") + dstByCID, err := os.OpenFile(pathByCID, os.O_CREATE|os.O_RDWR, 0644) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, dstByCID.Close()) }) + wbsByCID, err := storage.NewReadableWritable(dstByCID, nil, carv2.UseWholeCIDs(true)) + require.NoError(t, err) + + // This writable deduplicates puts by multihash + pathByHash := filepath.Join(tdir, "writable-dedup-byhash.car") + dstByHash, err := os.OpenFile(pathByHash, os.O_CREATE|os.O_RDWR, 0644) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, dstByHash.Close()) }) + wbsByHash, err := storage.NewReadableWritable(dstByHash, nil) + require.NoError(t, err) + + var blockList []struct { + cid cid.Cid + data []byte + } + + appendBlock := func(data []byte, version, codec uint64) { + c, err := cid.Prefix{ + Version: version, + Codec: codec, + MhType: multihash.SHA2_256, + MhLength: -1, + }.Sum(data) + require.NoError(t, err) + blockList = append(blockList, struct { + cid cid.Cid + data []byte + }{c, data}) + } + + // Two raw blocks, meaning we have two unique multihashes. + // However, we have multiple CIDs for each multihash. + // We also have two duplicate CIDs. + data1 := []byte("foo bar") + appendBlock(data1, 0, cid.DagProtobuf) + appendBlock(data1, 1, cid.DagProtobuf) + appendBlock(data1, 1, cid.DagCBOR) + appendBlock(data1, 1, cid.DagCBOR) // duplicate CID + + data2 := []byte("foo bar baz") + appendBlock(data2, 0, cid.DagProtobuf) + appendBlock(data2, 1, cid.DagProtobuf) + appendBlock(data2, 1, cid.DagProtobuf) // duplicate CID + appendBlock(data2, 1, cid.DagCBOR) + + countBlocks := func(path string) int { + f, err := os.Open(path) + require.NoError(t, err) + rdr, err := carv2.NewBlockReader(f) + require.NoError(t, err) + + n := 0 + for { + _, err := rdr.Next() + if err == io.EOF { + break } - ingester, err := storage.NewWritable(writer, r.Header.Roots, variant.options...) + n++ + } + return n + } + + putBlockList := func(writable *storage.StorageCar) { + for i, block := range blockList { + // Has should never error here. + // The first block should be missing. + // Others might not, given the duplicate hashes. + has, err := writable.Has(ctx, block.cid.KeyString()) + require.NoError(t, err) + if i == 0 { + require.False(t, has) + } + + err = writable.Put(ctx, block.cid.KeyString(), block.data) + require.NoError(t, err) + + // Has and Get need to work right after a Put + has, err = writable.Has(ctx, block.cid.KeyString()) require.NoError(t, err) - t.Cleanup(func() { dstFile.Close() }) + require.True(t, has) + + got, err := writable.Get(ctx, block.cid.KeyString()) + require.NoError(t, err) + require.Equal(t, block.data, got) + } + } + + putBlockList(wbsAllowDups) + err = wbsAllowDups.Finalize() + require.NoError(t, err) + require.Equal(t, len(blockList), countBlocks(pathAllowDups)) + + // Put the same list of blocks to the CAR that deduplicates by CID. + // We should end up with two fewer blocks, as two are entire CID duplicates. + putBlockList(wbsByCID) + err = wbsByCID.Finalize() + require.NoError(t, err) + require.Equal(t, len(blockList)-2, countBlocks(pathByCID)) + + // Put the same list of blocks to the CAR that deduplicates by CID. + // We should end up with just two blocks, as the original set of blocks only + // has two distinct multihashes. + putBlockList(wbsByHash) + err = wbsByHash.Finalize() + require.NoError(t, err) + require.Equal(t, 2, countBlocks(pathByHash)) +} + +func TestReadableCantWrite(t *testing.T) { + inp, err := os.Open("../testdata/sample-v1.car") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, inp.Close()) }) + readable, err := storage.NewReadable(inp) + require.NoError(t, err) + require.ErrorContains(t, readable.(*storage.StorageCar).Put(context.Background(), randCid().KeyString(), []byte("bar")), "read-only") + // Finalize() is nonsense for a readable, but it should be safe + require.NoError(t, readable.(*storage.StorageCar).Finalize()) +} + +func TestWritableCantRead(t *testing.T) { + // an io.Writer with no io.WriterAt capabilities + path := filepath.Join(t.TempDir(), "writable.car") + out, err := os.Create(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, out.Close()) }) + + // This should fail because the writer is not an io.WriterAt + _, err = storage.NewWritable(&writerOnly{out}, nil) + require.ErrorContains(t, err, "CARv2") + require.ErrorContains(t, err, "non-seekable") + + writable, err := storage.NewWritable(&writerOnly{out}, nil, carv2.WriteAsCarV1(true)) + require.NoError(t, err) + + _, err = writable.(*storage.StorageCar).Get(context.Background(), randCid().KeyString()) + require.ErrorContains(t, err, "write-only") + + _, err = writable.(*storage.StorageCar).GetStream(context.Background(), randCid().KeyString()) + require.ErrorContains(t, err, "write-only") + + require.NoError(t, writable.Finalize()) +} + +func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + testCid1, testData1 := randBlock() + testCid2, testData2 := randBlock() + + wantRoots := []cid.Cid{testCid1, testCid2} + path := filepath.Join(t.TempDir(), "readwrite-with-padding.car") + writer, err := os.Create(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, writer.Close()) }) + + wantCarV1Padding := uint64(1413) + wantIndexPadding := uint64(1314) + subject, err := storage.NewReadableWritable( + writer, + wantRoots, + carv2.UseDataPadding(wantCarV1Padding), + carv2.UseIndexPadding(wantIndexPadding)) + require.NoError(t, err) + require.NoError(t, subject.Put(ctx, testCid1.KeyString(), testData1)) + require.NoError(t, subject.Put(ctx, testCid2.KeyString(), testData2)) + require.NoError(t, subject.Finalize()) + + // Assert CARv2 header contains right offsets. + gotCarV2, err := carv2.OpenReader(path) + t.Cleanup(func() { gotCarV2.Close() }) + require.NoError(t, err) + wantCarV1Offset := carv2.PragmaSize + carv2.HeaderSize + wantCarV1Padding + wantIndexOffset := wantCarV1Offset + gotCarV2.Header.DataSize + wantIndexPadding + require.Equal(t, wantCarV1Offset, gotCarV2.Header.DataOffset) + require.Equal(t, wantIndexOffset, gotCarV2.Header.IndexOffset) + require.NoError(t, gotCarV2.Close()) - cids := make([]cid.Cid, 0) - var idCidCount int + f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { f.Close() }) + + // Assert reading CARv1 directly at offset and size is as expected. + gotCarV1, err := carv1.NewCarReader(io.NewSectionReader(f, int64(wantCarV1Offset), int64(gotCarV2.Header.DataSize))) + require.NoError(t, err) + require.Equal(t, wantRoots, gotCarV1.Header.Roots) + gotBlock, err := gotCarV1.Next() + require.NoError(t, err) + require.Equal(t, testCid1, gotBlock.Cid()) + require.Equal(t, testData1, gotBlock.RawData()) + gotBlock, err = gotCarV1.Next() + require.NoError(t, err) + require.Equal(t, testCid2, gotBlock.Cid()) + require.Equal(t, testData2, gotBlock.RawData()) + + _, err = gotCarV1.Next() + require.Equal(t, io.EOF, err) + + // Assert reading index directly from file is parsable and has expected CIDs. + stat, err := f.Stat() + require.NoError(t, err) + indexSize := stat.Size() - int64(wantIndexOffset) + gotIdx, err := index.ReadFrom(io.NewSectionReader(f, int64(wantIndexOffset), indexSize)) + require.NoError(t, err) + _, err = index.GetFirst(gotIdx, testCid1) + require.NoError(t, err) + _, err = index.GetFirst(gotIdx, testCid2) + require.NoError(t, err) +} + +func TestResumption(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + srcPath := "../testdata/sample-v1.car" + + v1f, err := os.Open(srcPath) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, v1f.Close()) }) + rd, err := carv2.NewReader(v1f) + require.NoError(t, err) + roots, err := rd.Roots() + require.NoError(t, err) + + blockSource := func() <-chan simpleBlock { + v1f, err := os.Open(srcPath) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, v1f.Close()) }) + r, err := carv1.NewCarReader(v1f) + require.NoError(t, err) + ret := make(chan simpleBlock) + + go func() { for { b, err := r.Next() if err == io.EOF { + close(ret) break } require.NoError(t, err) + ret <- simpleBlock{cid: b.Cid(), data: b.RawData()} + } + }() - err = ingester.Put(ctx, b.Cid().KeyString(), b.RawData()) - require.NoError(t, err) - cids = append(cids, b.Cid()) + return ret + } - dmh, err := multihash.Decode(b.Cid().Hash()) - require.NoError(t, err) - if dmh.Code == multihash.IDENTITY { - idCidCount++ - } + path := filepath.Join(t.TempDir(), "readwrite-resume.car") + writer, err := os.Create(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, writer.Close()) }) + // Create an incomplete CARv2 file with no blocks put. + subject, err := storage.NewReadableWritable(writer, roots, carv2.UseWholeCIDs(true)) + require.NoError(t, err) - // try reading a random one: - candIndex := rng.Intn(len(cids)) - var candidate cid.Cid - for _, c := range cids { - if candIndex == 0 { - candidate = c - break - } - candIndex-- - } - has, err := ingester.Has(ctx, candidate.KeyString()) - require.NoError(t, err) - require.True(t, has) + // For each block resume on the same file, putting blocks one at a time. + var wantBlockCountSoFar, idCidCount int + wantBlocks := make(map[cid.Cid]simpleBlock) + for b := range blockSource() { + wantBlockCountSoFar++ + wantBlocks[b.cid] = b - // not exists - has, err = ingester.Has(ctx, randCid().KeyString()) - require.NoError(t, err) - require.False(t, has) + dmh, err := multihash.Decode(b.cid.Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + idCidCount++ + } - // random identity - has, err = ingester.Has(ctx, randIdentityCid().KeyString()) - require.NoError(t, err) - require.Equal(t, !opts.StoreIdentityCIDs, has) + // 30% chance of subject failing; more concretely: re-instantiating the StorageCar with the same + // file without calling Finalize. The higher this percentage the slower the test runs + // considering the number of blocks in the original CARv1 test payload. + resume := rng.Float32() <= 0.3 + // If testing resume case, then flip a coin to decide whether to finalize before the StorageCar + // re-instantiation or not. Note, both cases should work for resumption since we do not + // limit resumption to unfinalized files. + finalizeBeforeResumption := rng.Float32() <= 0.5 + if resume { + if finalizeBeforeResumption { + require.NoError(t, subject.Finalize()) } - err = ingester.Finalize() + _, err := writer.Seek(0, io.SeekStart) require.NoError(t, err) - - err = dstFile.Close() + subject, err = storage.OpenReadableWritable(writer, roots, carv2.UseWholeCIDs(true)) require.NoError(t, err) + } + require.NoError(t, subject.Put(ctx, b.cid.KeyString(), b.data)) - reopen, err := os.Open(path) - require.NoError(t, err) - rd, err := carv2.NewReader(reopen) - require.NoError(t, err) - require.Equal(t, opts.WriteAsCarV1, rd.Version == 1) - require.NoError(t, reopen.Close()) + // With 10% chance test read operations on an resumed read-write StorageCar. + // We don't test on every put to reduce test runtime. + testRead := rng.Float32() <= 0.1 + if testRead { + // Assert read operations on the read-write StorageCar are as expected when resumed from an + // existing file + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + t.Cleanup(cancel) + for k, wantBlock := range wantBlocks { + has, err := subject.Has(ctx, k.KeyString()) + require.NoError(t, err) + require.True(t, has) + gotBlock, err := subject.Get(ctx, k.KeyString()) + require.NoError(t, err) + require.Equal(t, wantBlock.data, gotBlock) + } + // Assert the number of blocks in file are as expected calculated via AllKeysChan + require.Equal(t, wantBlockCountSoFar, len(wantBlocks)) + } + } - robs, err := blockstore.OpenReadOnly(path) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, robs.Close()) }) + // Finalize the StorageCar to complete partially written CARv2 file. + subject, err = storage.OpenReadableWritable(writer, roots, carv2.UseWholeCIDs(true)) + require.NoError(t, err) + require.NoError(t, subject.Finalize()) - allKeysCh, err := robs.AllKeysChan(ctx) - require.NoError(t, err) - numKeysCh := 0 - for c := range allKeysCh { - b, err := robs.Get(ctx, c) - require.NoError(t, err) - if !b.Cid().Equals(c) { - t.Fatal("wrong item returned") + // Assert resumed from file is a valid CARv2 with index. + v2f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, v2f.Close()) }) + v2r, err := carv2.NewReader(v2f) + require.NoError(t, err) + require.True(t, v2r.Header.HasIndex()) + + // Assert CARv1 payload in file matches the original CARv1 payload. + _, err = v1f.Seek(0, io.SeekStart) + require.NoError(t, err) + wantPayloadReader, err := carv1.NewCarReader(v1f) + require.NoError(t, err) + + dr, err := v2r.DataReader() + require.NoError(t, err) + gotPayloadReader, err := carv1.NewCarReader(dr) + require.NoError(t, err) + + require.Equal(t, wantPayloadReader.Header, gotPayloadReader.Header) + for { + wantNextBlock, wantErr := wantPayloadReader.Next() + if wantErr == io.EOF { + gotNextBlock, gotErr := gotPayloadReader.Next() + require.Equal(t, wantErr, gotErr) + require.Nil(t, gotNextBlock) + break + } + require.NoError(t, wantErr) + + dmh, err := multihash.Decode(wantNextBlock.Cid().Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + continue + } + + gotNextBlock, gotErr := gotPayloadReader.Next() + require.NoError(t, gotErr) + require.Equal(t, wantNextBlock, gotNextBlock) + } + + // Assert index in resumed from file is identical to index generated from the data payload portion of the generated CARv2 file. + _, err = v1f.Seek(0, io.SeekStart) + require.NoError(t, err) + ir, err := v2r.IndexReader() + require.NoError(t, err) + gotIdx, err := index.ReadFrom(ir) + require.NoError(t, err) + dr, err = v2r.DataReader() + require.NoError(t, err) + wantIdx, err := carv2.GenerateIndex(dr) + require.NoError(t, err) + require.Equal(t, wantIdx, gotIdx) +} + +func TestResumptionV1(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + srcPath := "../testdata/sample-v1.car" + + v1f, err := os.Open(srcPath) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, v1f.Close()) }) + rd, err := carv2.NewReader(v1f) + require.NoError(t, err) + roots, err := rd.Roots() + require.NoError(t, err) + + blockSource := func() <-chan simpleBlock { + v1f, err := os.Open(srcPath) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, v1f.Close()) }) + r, err := carv1.NewCarReader(v1f) + require.NoError(t, err) + ret := make(chan simpleBlock) + + go func() { + for { + b, err := r.Next() + if err == io.EOF { + close(ret) + break } - numKeysCh++ + require.NoError(t, err) + ret <- simpleBlock{cid: b.Cid(), data: b.RawData()} } - expectedCidCount := len(cids) - if !opts.StoreIdentityCIDs { - expectedCidCount -= idCidCount + }() + + return ret + } + + path := filepath.Join(t.TempDir(), "readwrite-resume-v1.car") + writer, err := os.Create(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, writer.Close()) }) + // Create an incomplete CARv2 file with no blocks put. + subject, err := storage.NewReadableWritable(writer, roots, carv2.UseWholeCIDs(true), carv2.WriteAsCarV1(true)) + require.NoError(t, err) + + // For each block resume on the same file, putting blocks one at a time. + var wantBlockCountSoFar, idCidCount int + wantBlocks := make(map[cid.Cid]simpleBlock) + for b := range blockSource() { + wantBlockCountSoFar++ + wantBlocks[b.cid] = b + + dmh, err := multihash.Decode(b.cid.Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + idCidCount++ + } + + // 30% chance of subject failing; more concretely: re-instantiating the StorageCar with the same + // file without calling Finalize. The higher this percentage the slower the test runs + // considering the number of blocks in the original CARv1 test payload. + resume := rng.Float32() <= 0.3 + // If testing resume case, then flip a coin to decide whether to finalize before the StorageCar + // re-instantiation or not. Note, both cases should work for resumption since we do not + // limit resumption to unfinalized files. + finalizeBeforeResumption := rng.Float32() <= 0.5 + if resume { + if finalizeBeforeResumption { + require.NoError(t, subject.Finalize()) } - require.Equal(t, expectedCidCount, numKeysCh, "AllKeysChan returned an unexpected amount of keys; expected %v but got %v", expectedCidCount, numKeysCh) - for _, c := range cids { - b, err := robs.Get(ctx, c) + _, err := writer.Seek(0, io.SeekStart) + require.NoError(t, err) + subject, err = storage.OpenReadableWritable(writer, roots, carv2.UseWholeCIDs(true), carv2.WriteAsCarV1(true)) + require.NoError(t, err) + } + require.NoError(t, subject.Put(ctx, b.cid.KeyString(), b.data)) + + // With 10% chance test read operations on an resumed read-write StorageCar. + // We don't test on every put to reduce test runtime. + testRead := rng.Float32() <= 0.1 + if testRead { + // Assert read operations on the read-write StorageCar are as expected when resumed from an + // existing file + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + t.Cleanup(cancel) + for k, wantBlock := range wantBlocks { + has, err := subject.Has(ctx, k.KeyString()) require.NoError(t, err) - if !b.Cid().Equals(c) { - t.Fatal("wrong item returned") - } + require.True(t, has) + gotBlock, err := subject.Get(ctx, k.KeyString()) + require.NoError(t, err) + require.Equal(t, wantBlock.data, gotBlock) } + // Assert the number of blocks in file are as expected calculated via AllKeysChan + require.Equal(t, wantBlockCountSoFar, len(wantBlocks)) + } + } - comparePath := filepath.Join("../testdata/", variant.compareCarV1) - compareStat, err := os.Stat(comparePath) - require.NoError(t, err) + // Finalize the StorageCar to complete partially written CARv2 file. + subject, err = storage.OpenReadableWritable(writer, roots, carv2.UseWholeCIDs(true), carv2.WriteAsCarV1(true)) + require.NoError(t, err) + require.NoError(t, subject.Finalize()) - wrote, err := os.Open(path) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, wrote.Close()) }) - _, err = wrote.Seek(variant.expectedV1StartOffset, io.SeekStart) - require.NoError(t, err) - hasher := sha512.New() - gotWritten, err := io.Copy(hasher, io.LimitReader(wrote, compareStat.Size())) - require.NoError(t, err) - gotSum := hasher.Sum(nil) + // Assert resumed from file is a valid CARv2 with index. + v2f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, v2f.Close()) }) + v2r, err := carv2.NewReader(v2f) + require.NoError(t, err) + require.False(t, v2r.Header.HasIndex()) + require.Equal(t, uint64(1), v2r.Version) - hasher.Reset() - compareV1, err := os.Open(comparePath) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, compareV1.Close()) }) - wantWritten, err := io.Copy(hasher, compareV1) - require.NoError(t, err) - wantSum := hasher.Sum(nil) + _, err = v1f.Seek(0, io.SeekStart) + require.NoError(t, err) + wantPayloadReader, err := carv1.NewCarReader(v1f) + require.NoError(t, err) - require.Equal(t, wantWritten, gotWritten) - require.Equal(t, wantSum, gotSum) - }) + dr, err := v2r.DataReader() // since this is a v1 we're just reading from the top with this + require.NoError(t, err) + gotPayloadReader, err := carv1.NewCarReader(dr) + require.NoError(t, err) + + require.Equal(t, wantPayloadReader.Header, gotPayloadReader.Header) + for { + wantNextBlock, wantErr := wantPayloadReader.Next() + if wantErr == io.EOF { + gotNextBlock, gotErr := gotPayloadReader.Next() + require.Equal(t, wantErr, gotErr) + require.Nil(t, gotNextBlock) + break + } + require.NoError(t, wantErr) + + dmh, err := multihash.Decode(wantNextBlock.Cid().Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + continue + } + + gotNextBlock, gotErr := gotPayloadReader.Next() + require.NoError(t, gotErr) + require.Equal(t, wantNextBlock, gotNextBlock) } } +func TestResumptionIsSupportedOnFinalizedFile(t *testing.T) { + path := filepath.Join(t.TempDir(), "readwrite-resume-finalized.car") + v2f, err := os.Create(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, v2f.Close()) }) + // Create an incomplete CARv2 file with no blocks put. + subject, err := storage.NewReadableWritable(v2f, []cid.Cid{}) + require.NoError(t, err) + require.NoError(t, subject.Finalize()) + + reopen, err := os.OpenFile(path, os.O_RDWR, 0o666) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, reopen.Close()) }) + subject, err = storage.NewReadableWritable(reopen, []cid.Cid{}) + require.NoError(t, err) + t.Cleanup(func() { subject.Finalize() }) +} + +func TestReadWriteErrorsOnlyWhenFinalized(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + testCid1, testData1 := randBlock() + testCid2, testData2 := randBlock() + + wantRoots := []cid.Cid{testCid1, testCid2} + path := filepath.Join(t.TempDir(), "readwrite-finalized-panic.car") + writer, err := os.Create(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, writer.Close()) }) + + subject, err := storage.NewReadableWritable(writer, wantRoots) + require.NoError(t, err) + + require.NoError(t, subject.Put(ctx, testCid1.KeyString(), testData1)) + require.NoError(t, subject.Put(ctx, testCid2.KeyString(), testData2)) + + gotBlock, err := subject.Get(ctx, testCid1.KeyString()) + require.NoError(t, err) + require.Equal(t, testData1, gotBlock) + + gotRoots := subject.Roots() + require.Equal(t, wantRoots, gotRoots) + + has, err := subject.Has(ctx, testCid1.KeyString()) + require.NoError(t, err) + require.True(t, has) + + require.NoError(t, subject.Finalize()) + require.Error(t, subject.Finalize()) + + _, ok := (interface{})(subject).(io.Closer) + require.False(t, ok) + + _, err = subject.Get(ctx, testCid1.KeyString()) + require.Error(t, err) + require.Error(t, err) + _, err = subject.Has(ctx, testCid2.KeyString()) + require.Error(t, err) + + require.Error(t, subject.Put(ctx, testCid1.KeyString(), testData1)) +} + +func TestReadWriteResumptionMismatchingRootsIsError(t *testing.T) { + tmpPath := requireTmpCopy(t, "../testdata/sample-wrapped-v2.car") + + origContent, err := os.ReadFile(tmpPath) + require.NoError(t, err) + + badRoot := randCid() + writer, err := os.OpenFile(tmpPath, os.O_RDWR, 0o666) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, writer.Close()) }) + subject, err := storage.OpenReadableWritable(writer, []cid.Cid{badRoot}) + require.EqualError(t, err, "cannot resume on file with mismatching data header") + require.Nil(t, subject) + + newContent, err := os.ReadFile(tmpPath) + require.NoError(t, err) + + // Expect the bad file to be left untouched; check the size first. + // If the sizes mismatch, printing a huge diff would not help us. + require.Equal(t, len(origContent), len(newContent)) + require.Equal(t, origContent, newContent) +} + +func requireTmpCopy(t *testing.T, src string) string { + srcF, err := os.Open(src) + require.NoError(t, err) + defer func() { require.NoError(t, srcF.Close()) }() + stats, err := srcF.Stat() + require.NoError(t, err) + + dst := filepath.Join(t.TempDir(), stats.Name()) + dstF, err := os.Create(dst) + require.NoError(t, err) + defer func() { require.NoError(t, dstF.Close()) }() + + _, err = io.Copy(dstF, srcF) + require.NoError(t, err) + return dst +} + +func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing.T) { + testCid1, testData1 := randBlock() + wantRoots := []cid.Cid{testCid1} + path := filepath.Join(t.TempDir(), "readwrite-resume-with-padding.car") + writer, err := os.Create(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, writer.Close()) }) + subject, err := storage.NewReadableWritable( + writer, + wantRoots, + carv2.UseDataPadding(1413)) + require.NoError(t, err) + require.NoError(t, subject.Put(context.TODO(), testCid1.KeyString(), testData1)) + require.NoError(t, subject.Finalize()) + + subject, err = storage.OpenReadableWritable( + writer, + wantRoots, + carv2.UseDataPadding(1314)) + require.EqualError(t, err, "cannot resume from file with mismatched CARv1 offset; "+ + "`WithDataPadding` option must match the padding on file. "+ + "Expected padding value of 1413 but got 1314") + require.Nil(t, subject) +} + +func TestOperationsErrorWithBadCidStrings(t *testing.T) { + testCid, testData := randBlock() + path := filepath.Join(t.TempDir(), "badkeys.car") + writer, err := os.Create(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, writer.Close()) }) + subject, err := storage.NewReadableWritable(writer, []cid.Cid{}) + require.NoError(t, err) + + require.NoError(t, subject.Put(context.TODO(), testCid.KeyString(), testData)) + require.ErrorContains(t, subject.Put(context.TODO(), fmt.Sprintf("%s/nope", testCid.KeyString()), testData), "bad CID key") + require.ErrorContains(t, subject.Put(context.TODO(), "nope", testData), "bad CID key") + + has, err := subject.Has(context.TODO(), testCid.KeyString()) + require.NoError(t, err) + require.True(t, has) + has, err = subject.Has(context.TODO(), fmt.Sprintf("%s/nope", testCid.KeyString())) + require.ErrorContains(t, err, "bad CID key") + require.False(t, has) + has, err = subject.Has(context.TODO(), "nope") + require.ErrorContains(t, err, "bad CID key") + require.False(t, has) + + got, err := subject.Get(context.TODO(), testCid.KeyString()) + require.NoError(t, err) + require.NotNil(t, got) + got, err = subject.Get(context.TODO(), fmt.Sprintf("%s/nope", testCid.KeyString())) + require.ErrorContains(t, err, "bad CID key") + require.Nil(t, got) + got, err = subject.Get(context.TODO(), "nope") + require.ErrorContains(t, err, "bad CID key") + require.Nil(t, got) +} + type writerOnly struct { io.Writer } @@ -322,16 +1175,46 @@ func (w *writerAtOnly) Write(p []byte) (n int, err error) { return w.File.Write(p) } +func randBlock() (cid.Cid, []byte) { + data := make([]byte, 1024) + rngLk.Lock() + rng.Read(data) + rngLk.Unlock() + h, err := mh.Sum(data, mh.SHA2_512, -1) + if err != nil { + panic(err) + } + return cid.NewCidV1(cid.Raw, h), data +} + func randCid() cid.Cid { b := make([]byte, 32) + rngLk.Lock() rng.Read(b) + rngLk.Unlock() mh, _ := multihash.Encode(b, multihash.SHA2_256) return cid.NewCidV1(cid.DagProtobuf, mh) } func randIdentityCid() cid.Cid { b := make([]byte, 32) + rngLk.Lock() rng.Read(b) + rngLk.Unlock() mh, _ := multihash.Encode(b, multihash.IDENTITY) return cid.NewCidV1(cid.Raw, mh) } + +type bufferReaderAt []byte + +func (b bufferReaderAt) ReadAt(p []byte, off int64) (int, error) { + if off >= int64(len(b)) { + return 0, io.EOF + } + return copy(p, b[off:]), nil +} + +type simpleBlock struct { + cid cid.Cid + data []byte +} From cd86ec8a55071af637ba9269a5ce4a283af7bdc9 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 6 Feb 2023 15:56:22 +1100 Subject: [PATCH 268/291] feat: docs for StorageCar interfaces This commit was moved from ipld/go-car@3b0851e9c3cb4463865d8fdb31acb3a101e95040 --- ipld/car/v2/internal/store/resume.go | 1 - ipld/car/v2/options.go | 4 +- ipld/car/v2/storage/doc.go | 73 +++++++++++++++++++++++ ipld/car/v2/storage/storage.go | 86 +++++++++++++++++++++++++--- ipld/car/v2/storage/storage_test.go | 8 +-- 5 files changed, 157 insertions(+), 15 deletions(-) create mode 100644 ipld/car/v2/storage/doc.go diff --git a/ipld/car/v2/internal/store/resume.go b/ipld/car/v2/internal/store/resume.go index 0167b41547..a1f97de0f5 100644 --- a/ipld/car/v2/internal/store/resume.go +++ b/ipld/car/v2/internal/store/resume.go @@ -15,7 +15,6 @@ import ( type ReaderWriterAt interface { io.ReaderAt - io.Writer io.WriterAt } diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index 92a5d11d93..dbde3f3e15 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -189,8 +189,8 @@ func MaxAllowedSectionSize(max uint64) Option { // // Enabling this option affects a number of methods, including read-only ones: // -// - Get, Has, and HasSize will only return a block only if the entire CID is -// present in the CAR file. +// • Get, Has, and HasSize will only return a block only if the entire CID is +// present in the CAR file. // // • AllKeysChan will return the original whole CIDs, instead of with their // multicodec set to "raw" to just provide multihashes. diff --git a/ipld/car/v2/storage/doc.go b/ipld/car/v2/storage/doc.go new file mode 100644 index 0000000000..43ddb75785 --- /dev/null +++ b/ipld/car/v2/storage/doc.go @@ -0,0 +1,73 @@ +// package storage provides a CAR abstraction for the +// github.com/ipld/go-ipld-prime/storage interfaces in the form of a StorageCar. +// +// StorageCar as ReadableStorage provides basic Get and Has operations. It also +// implements StreamingReadableStorage for the more efficient GetStreaming +// operation which is easily supported by the CAR format. +// +// StorageCar as WritableStorage provides the Put operation. It does not +// implement StreamingWritableStorage because the CAR format requires CIDs to +// be written before the blocks themselves, which is not possible with +// StreamingWritableStorage without buffering. Therefore, the PutStream function +// in github.com/ipld/go-ipld-prime/storage will provide equivalent +// functionality if it were to be implemented here. +// +// StorageCar can be used with an IPLD LinkSystem, defined by +// github.com/ipld/go-ipld-prime/linking, with the +// linking.SetReadStorage and linking.SetWriteStorage functions, to provide +// read and/or write to and/or from a CAR format as required. +// +// The focus of the StorageCar interfaces is to use the minimal possible IO +// interface for the operation(s) being performed. +// +// • OpenReadable requires an io.ReaderAt as seeking is required for +// random-access reads as a ReadableStore. +// +// • NewWritable requires an io.Writer when used to write a CARv1 as this format +// can be written in a continuous stream as blocks are written through a +// WritableStore (i.e. when the WriteAsCarV1 option is turned on). When used to +// write a CARv2, the default mode, a random-access io.WriterAt is required as +// the CARv2 header must be written after the payload is finalized and index +// written in order to indicate payload location in the output. The plain +// Writable store may be used to stream CARv1 contents without buffering; +// only storing CIDs in memory for de-duplication (where required) and to still +// allow Has operations. +// +// • NewReadableWritable requires an io.ReaderAt and an io.Writer as it combines +// the functionality of a NewWritable with OpenReadable, being able to random- +// access read any written blocks. +// +// • OpenReadableWritable requires an io.ReaderAt, an io.Writer and an +// io.WriterAt as it extends the NewReadableWritable functionality with the +// ability to resume an existing CAR. In addition, if the CAR being resumed is +// a CARv2, the IO object being provided must have a Truncate() method (e.g. +// an io.File) in order to properly manage CAR lifecycle and avoid writing a +// corrupt CAR. +// +// The following options are available to customize the behavior of the +// StorageCar: +// +// • WriteAsCarV1 +// +// • StoreIdentityCIDs +// +// • AllowDuplicatePuts +// +// • UseWholeCIDs +// +// • ZeroLengthSectionAsEOF +// +// • UseIndexCodec +// +// • UseDataPadding +// +// • UseIndexPadding +// +// • MaxIndexCidSize +// +// • MaxAllowedHeaderSize +// +// • MaxAllowedSectionSize +// + +package storage diff --git a/ipld/car/v2/storage/storage.go b/ipld/car/v2/storage/storage.go index 6bbed612cf..9e765865b7 100644 --- a/ipld/car/v2/storage/storage.go +++ b/ipld/car/v2/storage/storage.go @@ -21,7 +21,7 @@ import ( var errClosed = errors.New("cannot use a CARv2 storage after closing") -type ReaderWriterAt interface { +type ReaderAtWriterAt interface { io.ReaderAt io.Writer io.WriterAt @@ -44,10 +44,8 @@ type WritableCar interface { Finalize() error } -var _ ipldstorage.ReadableStorage = (*StorageCar)(nil) -var _ ipldstorage.StreamingReadableStorage = (*StorageCar)(nil) var _ ReadableCar = (*StorageCar)(nil) -var _ ipldstorage.WritableStorage = (*StorageCar)(nil) +var _ WritableCar = (*StorageCar)(nil) type StorageCar struct { idx index.Index @@ -67,7 +65,23 @@ type positionedWriter interface { Position() int64 } -func NewReadable(reader io.ReaderAt, opts ...carv2.Option) (ReadableCar, error) { +// OpenReadable opens a CARv1 or CARv2 file for reading as a ReadableStorage +// and StreamingReadableStorage as defined by +// github.com/ipld/go-ipld-prime/storage. +// +// The returned ReadableStorage is compatible with a linksystem SetReadStorage +// method as defined by github.com/ipld/go-ipld-prime/linking +// to provide a block source backed by a CAR. +// +// When opening a CAR, an initial scan is performed to generate an index, or +// load an index from a CARv2 index where available. This index data is kept in +// memory while the CAR is being used in order to provide efficient random +// Get access to blocks and Has operations. +// +// The Readable supports StreamingReadableStorage, which allows for efficient +// GetStreaming operations straight out of the underlying CAR where the +// linksystem can make use of it. +func OpenReadable(reader io.ReaderAt, opts ...carv2.Option) (ReadableCar, error) { sc := &StorageCar{opts: carv2.ApplyOptions(opts...)} rr := internalio.ToReadSeeker(reader) @@ -122,6 +136,29 @@ func NewReadable(reader io.ReaderAt, opts ...carv2.Option) (ReadableCar, error) return sc, nil } +// NewWritable creates a new WritableStorage as defined by +// github.com/ipld/go-ipld-prime/storage that writes a CARv1 or CARv2 format to +// the given io.Writer. +// +// The returned WritableStorage is compatible with a linksystem SetWriteStorage +// method as defined by github.com/ipld/go-ipld-prime/linking +// to provide a block sink backed by a CAR. +// +// The WritableStorage supports Put operations, which will write +// blocks to the CAR in the order they are received. +// +// When writing a CARv2 format (the default), the provided writer must be +// compatible with io.WriterAt in order to provide random access as the CARv2 +// header must be written after the blocks in order to indicate the size of the +// CARv2 data payload. +// +// A CARv1 (generated using the WriteAsCarV1 option) only requires an io.Writer +// and can therefore stream CAR contents as blocks are written while still +// providing Has operations and the ability to avoid writing duplicate blocks +// as required. +// +// When writing a CARv2 format, it is important to call the Finalize method on +// the returned WritableStorage in order to write the CARv2 header and index. func NewWritable(writer io.Writer, roots []cid.Cid, opts ...carv2.Option) (WritableCar, error) { sc, err := newWritable(writer, roots, opts...) if err != nil { @@ -162,7 +199,7 @@ func newWritable(writer io.Writer, roots []cid.Cid, opts ...carv2.Option) (*Stor return sc, nil } -func newReadableWritable(rw ReaderWriterAt, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { +func newReadableWritable(rw ReaderAtWriterAt, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { sc, err := newWritable(rw, roots, opts...) if err != nil { return nil, err @@ -179,7 +216,15 @@ func newReadableWritable(rw ReaderWriterAt, roots []cid.Cid, opts ...carv2.Optio return sc, nil } -func NewReadableWritable(rw ReaderWriterAt, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { +// NewReadableWritable creates a new StorageCar that is able to provide both +// StorageReader and StorageWriter functionality. +// +// The returned StorageCar is compatible with a linksystem SetReadStorage and +// SetWriteStorage methods as defined by github.com/ipld/go-ipld-prime/linking. +// +// When writing a CARv2 format, it is important to call the Finalize method on +// the returned WritableStorage in order to write the CARv2 header and index. +func NewReadableWritable(rw ReaderAtWriterAt, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { sc, err := newReadableWritable(rw, roots, opts...) if err != nil { return nil, err @@ -190,7 +235,15 @@ func NewReadableWritable(rw ReaderWriterAt, roots []cid.Cid, opts ...carv2.Optio return sc, nil } -func OpenReadableWritable(rw ReaderWriterAt, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { +// OpenReadableWritable creates a new StorageCar that is able to provide both +// StorageReader and StorageWriter functionality. +// +// The returned StorageCar is compatible with a linksystem SetReadStorage and +// SetWriteStorage methods as defined by github.com/ipld/go-ipld-prime/linking. +// +// It attempts to resume a CARv2 file that was previously written to by +// NewWritable, or NewReadableWritable. +func OpenReadableWritable(rw ReaderAtWriterAt, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { sc, err := newReadableWritable(rw, roots, opts...) if err != nil { return nil, err @@ -236,10 +289,14 @@ func (sc *StorageCar) init() (WritableCar, error) { return sc, nil } +// Roots returns the roots of the CAR. func (sc *StorageCar) Roots() []cid.Cid { return sc.roots } +// Put adds a block to the CAR, where the block is identified by the given CID +// provided in string form. The keyStr value must be a valid CID binary string +// (not a multibase string representation), i.e. generated with CID#KeyString(). func (sc *StorageCar) Put(ctx context.Context, keyStr string, data []byte) error { keyCid, err := cid.Cast([]byte(keyStr)) if err != nil { @@ -284,6 +341,9 @@ func (sc *StorageCar) Put(ctx context.Context, keyStr string, data []byte) error return nil } +// Has returns true if the CAR contains a block identified by the given CID +// provided in string form. The keyStr value must be a valid CID binary string +// (not a multibase string representation), i.e. generated with CID#KeyString(). func (sc *StorageCar) Has(ctx context.Context, keyStr string) (bool, error) { keyCid, err := cid.Cast([]byte(keyStr)) if err != nil { @@ -325,6 +385,9 @@ func (sc *StorageCar) Has(ctx context.Context, keyStr string) (bool, error) { return size > -1, nil } +// Get returns the block bytes identified by the given CID provided in string +// form. The keyStr value must be a valid CID binary string (not a multibase +// string representation), i.e. generated with CID#KeyString(). func (sc *StorageCar) Get(ctx context.Context, keyStr string) ([]byte, error) { rdr, err := sc.GetStream(ctx, keyStr) if err != nil { @@ -333,6 +396,9 @@ func (sc *StorageCar) Get(ctx context.Context, keyStr string) ([]byte, error) { return io.ReadAll(rdr) } +// GetStream returns a stream of the block bytes identified by the given CID +// provided in string form. The keyStr value must be a valid CID binary string +// (not a multibase string representation), i.e. generated with CID#KeyString(). func (sc *StorageCar) GetStream(ctx context.Context, keyStr string) (io.ReadCloser, error) { if sc.reader == nil { return nil, fmt.Errorf("cannot read from a write-only CAR") @@ -378,6 +444,10 @@ func (sc *StorageCar) GetStream(ctx context.Context, keyStr string) (io.ReadClos return io.NopCloser(io.NewSectionReader(sc.reader, offset, int64(size))), nil } +// Finalize writes the CAR index to the underlying writer if the CAR being +// written is a CARv2. It also writes a finalized CARv2 header which details +// payload location. This should be called on a writable StorageCar in order to +// avoid data loss. func (sc *StorageCar) Finalize() error { idx, ok := sc.idx.(*insertionindex.InsertionIndex) if !ok || sc.writer == nil { diff --git a/ipld/car/v2/storage/storage_test.go b/ipld/car/v2/storage/storage_test.go index ba1d018789..928157e3b8 100644 --- a/ipld/car/v2/storage/storage_test.go +++ b/ipld/car/v2/storage/storage_test.go @@ -91,7 +91,7 @@ func TestReadable(t *testing.T) { inputReader, err := os.Open(tt.inputPath) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, inputReader.Close()) }) - readable, err := storage.NewReadable(inputReader, tt.opts...) + readable, err := storage.OpenReadable(inputReader, tt.opts...) require.NoError(t, err) // Setup BlockReader to compare against @@ -176,7 +176,7 @@ func TestReadableBadVersion(t *testing.T) { f, err := os.Open("../testdata/sample-rootless-v42.car") require.NoError(t, err) t.Cleanup(func() { f.Close() }) - subject, err := storage.NewReadable(f) + subject, err := storage.OpenReadable(f) require.Errorf(t, err, "unsupported car version: 42") require.Nil(t, subject) } @@ -436,7 +436,7 @@ func TestNullPadding(t *testing.T) { paddedV1, err := os.ReadFile("../testdata/sample-v1-with-zero-len-section.car") require.NoError(t, err) - readable, err := storage.NewReadable(bufferReaderAt(paddedV1), carv2.ZeroLengthSectionAsEOF(true)) + readable, err := storage.OpenReadable(bufferReaderAt(paddedV1), carv2.ZeroLengthSectionAsEOF(true)) require.NoError(t, err) roots := readable.Roots() @@ -590,7 +590,7 @@ func TestReadableCantWrite(t *testing.T) { inp, err := os.Open("../testdata/sample-v1.car") require.NoError(t, err) t.Cleanup(func() { require.NoError(t, inp.Close()) }) - readable, err := storage.NewReadable(inp) + readable, err := storage.OpenReadable(inp) require.NoError(t, err) require.ErrorContains(t, readable.(*storage.StorageCar).Put(context.Background(), randCid().KeyString(), []byte("bar")), "read-only") // Finalize() is nonsense for a readable, but it should be safe From bdfb88dc883913fc5f6a3744cf3e4a8754e6c248 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 6 Feb 2023 22:02:01 +1100 Subject: [PATCH 269/291] fix: minor lint & windows fd test problems This commit was moved from ipld/go-car@676dd5f08af665789b30851a18aa02b81fef132a --- ipld/car/v2/blockstore/readwrite.go | 7 ++++--- ipld/car/v2/storage/storage_test.go | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 032565b15e..85c9efd5bf 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -129,7 +129,8 @@ func OpenReadWriteFile(f *os.File, roots []cid.Cid, opts ...carv2.Option) (*Read offset = 0 } rwbs.dataWriter = internalio.NewOffsetWriter(rwbs.f, offset) - v1r, err := internalio.NewOffsetReadSeeker(rwbs.f, offset) + var v1r internalio.ReadSeekerAt + v1r, err = internalio.NewOffsetReadSeeker(rwbs.f, offset) if err != nil { return nil, err } @@ -137,10 +138,10 @@ func OpenReadWriteFile(f *os.File, roots []cid.Cid, opts ...carv2.Option) (*Read rwbs.ronly.idx = rwbs.idx if resume { - if err := store.ResumableVersion(f, rwbs.opts.WriteAsCarV1); err != nil { + if err = store.ResumableVersion(f, rwbs.opts.WriteAsCarV1); err != nil { return nil, err } - if err := store.Resume( + if err = store.Resume( f, rwbs.ronly.backing, rwbs.dataWriter, diff --git a/ipld/car/v2/storage/storage_test.go b/ipld/car/v2/storage/storage_test.go index 928157e3b8..ea00f1d5ec 100644 --- a/ipld/car/v2/storage/storage_test.go +++ b/ipld/car/v2/storage/storage_test.go @@ -23,7 +23,6 @@ import ( "github.com/ipld/go-car/v2/storage" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" - mh "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" ) @@ -379,7 +378,7 @@ func TestErrorsWhenWritingCidTooLarge(t *testing.T) { // normal block but shorten the CID to make it acceptable testCid, testData := randBlock() - mh, err := mh.Decode(testCid.Hash()) + mh, err := multihash.Decode(testCid.Hash()) require.NoError(t, err) dig := mh.Digest[:10] shortMh, err := multihash.Encode(dig, mh.Code) @@ -526,6 +525,7 @@ func TestPutSameHashes(t *testing.T) { countBlocks := func(path string) int { f, err := os.Open(path) require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) rdr, err := carv2.NewBlockReader(f) require.NoError(t, err) @@ -1065,10 +1065,11 @@ func TestReadWriteResumptionMismatchingRootsIsError(t *testing.T) { badRoot := randCid() writer, err := os.OpenFile(tmpPath, os.O_RDWR, 0o666) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, writer.Close()) }) + t.Cleanup(func() { writer.Close() }) subject, err := storage.OpenReadableWritable(writer, []cid.Cid{badRoot}) require.EqualError(t, err, "cannot resume on file with mismatching data header") require.Nil(t, subject) + require.NoError(t, writer.Close()) newContent, err := os.ReadFile(tmpPath) require.NoError(t, err) @@ -1180,7 +1181,7 @@ func randBlock() (cid.Cid, []byte) { rngLk.Lock() rng.Read(data) rngLk.Unlock() - h, err := mh.Sum(data, mh.SHA2_512, -1) + h, err := multihash.Sum(data, multihash.SHA2_512, -1) if err != nil { panic(err) } From 2fdb14e01e17887ddb6934bf89021bd311d15536 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 6 Feb 2023 22:03:44 +1100 Subject: [PATCH 270/291] chore: add experimental note This commit was moved from ipld/go-car@9bfbf3ecac68000a093648c5bba6b86c8057b344 --- ipld/car/v2/storage/doc.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ipld/car/v2/storage/doc.go b/ipld/car/v2/storage/doc.go index 43ddb75785..eed6a1460c 100644 --- a/ipld/car/v2/storage/doc.go +++ b/ipld/car/v2/storage/doc.go @@ -1,6 +1,10 @@ // package storage provides a CAR abstraction for the // github.com/ipld/go-ipld-prime/storage interfaces in the form of a StorageCar. // +// THIS PACKAGE IS EXPERIMENTAL. Breaking changes may be introduced in +// semver-minor releases before this package stabilizes. Use with caution and +// prefer the blockstore API if stability is required. +// // StorageCar as ReadableStorage provides basic Get and Has operations. It also // implements StreamingReadableStorage for the more efficient GetStreaming // operation which is easily supported by the CAR format. From 1ad58eada38897e8b544dd29bba347e20a1c5468 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 6 Feb 2023 22:32:23 +1100 Subject: [PATCH 271/291] chore: move insertionindex into store pkg This commit was moved from ipld/go-car@2afb69abaf72926034e8428aab5725cfe1274cb6 --- ipld/car/v2/blockstore/readwrite.go | 5 ++--- ipld/car/v2/internal/store/index.go | 3 +-- .../{insertionindex => store}/insertionindex.go | 2 +- ipld/car/v2/internal/store/put.go | 3 +-- ipld/car/v2/internal/store/resume.go | 3 +-- ipld/car/v2/storage/storage.go | 13 ++++++------- 6 files changed, 12 insertions(+), 17 deletions(-) rename ipld/car/v2/internal/{insertionindex => store}/insertionindex.go (99%) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 85c9efd5bf..3f5f7841b4 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -12,7 +12,6 @@ import ( carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" - "github.com/ipld/go-car/v2/internal/insertionindex" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipld/go-car/v2/internal/store" ) @@ -33,7 +32,7 @@ type ReadWrite struct { f *os.File dataWriter *internalio.OffsetWriteSeeker - idx *insertionindex.InsertionIndex + idx *store.InsertionIndex header carv2.Header opts carv2.Options @@ -111,7 +110,7 @@ func OpenReadWriteFile(f *os.File, roots []cid.Cid, opts ...carv2.Option) (*Read // Set the header fileld before applying options since padding options may modify header. rwbs := &ReadWrite{ f: f, - idx: insertionindex.NewInsertionIndex(), + idx: store.NewInsertionIndex(), header: carv2.NewHeader(0), opts: carv2.ApplyOptions(opts...), } diff --git a/ipld/car/v2/internal/store/index.go b/ipld/car/v2/internal/store/index.go index c7d188600d..62c9d2a02a 100644 --- a/ipld/car/v2/internal/store/index.go +++ b/ipld/car/v2/internal/store/index.go @@ -8,7 +8,6 @@ import ( carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1/util" - "github.com/ipld/go-car/v2/internal/insertionindex" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-varint" @@ -89,7 +88,7 @@ func FindCid( // Finalize will write the index to the writer at the offset specified in the header. It should only // be used for a CARv2 and when the CAR interface is being closed. -func Finalize(writer io.WriterAt, header carv2.Header, idx *insertionindex.InsertionIndex, dataSize uint64, storeIdentityCIDs bool, indexCodec multicodec.Code) error { +func Finalize(writer io.WriterAt, header carv2.Header, idx *InsertionIndex, dataSize uint64, storeIdentityCIDs bool, indexCodec multicodec.Code) error { // TODO check if add index option is set and don't write the index then set index offset to zero. header = header.WithDataSize(dataSize) header.Characteristics.SetFullyIndexed(storeIdentityCIDs) diff --git a/ipld/car/v2/internal/insertionindex/insertionindex.go b/ipld/car/v2/internal/store/insertionindex.go similarity index 99% rename from ipld/car/v2/internal/insertionindex/insertionindex.go rename to ipld/car/v2/internal/store/insertionindex.go index 7aac338ec3..f52fb3f2e4 100644 --- a/ipld/car/v2/internal/insertionindex/insertionindex.go +++ b/ipld/car/v2/internal/store/insertionindex.go @@ -1,4 +1,4 @@ -package insertionindex +package store import ( "bytes" diff --git a/ipld/car/v2/internal/store/put.go b/ipld/car/v2/internal/store/put.go index c9c1867ff3..f82d0c1ea9 100644 --- a/ipld/car/v2/internal/store/put.go +++ b/ipld/car/v2/internal/store/put.go @@ -3,7 +3,6 @@ package store import ( "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/internal/insertionindex" ) // ShouldPut returns true if the block should be put into the CAR according to the options provided @@ -11,7 +10,7 @@ import ( // is an identity block and StoreIdentityCIDs is false, or because it already exists and // BlockstoreAllowDuplicatePuts is false. func ShouldPut( - idx *insertionindex.InsertionIndex, + idx *InsertionIndex, c cid.Cid, maxIndexCidSize uint64, storeIdentityCIDs bool, diff --git a/ipld/car/v2/internal/store/resume.go b/ipld/car/v2/internal/store/resume.go index a1f97de0f5..ff47223733 100644 --- a/ipld/car/v2/internal/store/resume.go +++ b/ipld/car/v2/internal/store/resume.go @@ -8,7 +8,6 @@ import ( "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/internal/carv1" - "github.com/ipld/go-car/v2/internal/insertionindex" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-varint" ) @@ -52,7 +51,7 @@ func Resume( rw ReaderWriterAt, dataReader io.ReaderAt, dataWriter *internalio.OffsetWriteSeeker, - idx *insertionindex.InsertionIndex, + idx *InsertionIndex, roots []cid.Cid, dataOffset uint64, v1 bool, diff --git a/ipld/car/v2/storage/storage.go b/ipld/car/v2/storage/storage.go index 9e765865b7..a6f92ae22b 100644 --- a/ipld/car/v2/storage/storage.go +++ b/ipld/car/v2/storage/storage.go @@ -13,7 +13,6 @@ import ( "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" - "github.com/ipld/go-car/v2/internal/insertionindex" internalio "github.com/ipld/go-car/v2/internal/io" "github.com/ipld/go-car/v2/internal/store" ipldstorage "github.com/ipld/go-ipld-prime/storage" @@ -94,7 +93,7 @@ func OpenReadable(reader io.ReaderAt, opts ...carv2.Option) (ReadableCar, error) sc.roots = header.Roots sc.reader = reader rr.Seek(0, io.SeekStart) - sc.idx = insertionindex.NewInsertionIndex() + sc.idx = store.NewInsertionIndex() if err := carv2.LoadIndex(sc.idx, rr, opts...); err != nil { return nil, err } @@ -121,7 +120,7 @@ func OpenReadable(reader io.ReaderAt, opts ...carv2.Option) (ReadableCar, error) if err != nil { return nil, err } - sc.idx = insertionindex.NewInsertionIndex() + sc.idx = store.NewInsertionIndex() if err := carv2.LoadIndex(sc.idx, dr, opts...); err != nil { return nil, err } @@ -170,7 +169,7 @@ func NewWritable(writer io.Writer, roots []cid.Cid, opts ...carv2.Option) (Writa func newWritable(writer io.Writer, roots []cid.Cid, opts ...carv2.Option) (*StorageCar, error) { sc := &StorageCar{ writer: &positionTrackingWriter{w: writer}, - idx: insertionindex.NewInsertionIndex(), + idx: store.NewInsertionIndex(), header: carv2.NewHeader(0), opts: carv2.ApplyOptions(opts...), roots: roots, @@ -261,7 +260,7 @@ func OpenReadableWritable(rw ReaderAtWriterAt, roots []cid.Cid, opts ...carv2.Op rw, sc.reader, sc.dataWriter, - sc.idx.(*insertionindex.InsertionIndex), + sc.idx.(*store.InsertionIndex), roots, sc.header.DataOffset, sc.opts.WriteAsCarV1, @@ -310,7 +309,7 @@ func (sc *StorageCar) Put(ctx context.Context, keyStr string, data []byte) error return errClosed } - idx, ok := sc.idx.(*insertionindex.InsertionIndex) + idx, ok := sc.idx.(*store.InsertionIndex) if !ok || sc.writer == nil { return fmt.Errorf("cannot put into a read-only CAR") } @@ -449,7 +448,7 @@ func (sc *StorageCar) GetStream(ctx context.Context, keyStr string) (io.ReadClos // payload location. This should be called on a writable StorageCar in order to // avoid data loss. func (sc *StorageCar) Finalize() error { - idx, ok := sc.idx.(*insertionindex.InsertionIndex) + idx, ok := sc.idx.(*store.InsertionIndex) if !ok || sc.writer == nil { // ignore this, it's not writable return nil From 38af65947ac6300bd4da7fdb3f75bbd04868293c Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 8 Feb 2023 12:54:53 +1100 Subject: [PATCH 272/291] fix: return errors for unsupported operations This commit was moved from ipld/go-car@48ea0794819d9210a3468089c3d29481b63a6750 --- ipld/car/v2/internal/io/converter.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ipld/car/v2/internal/io/converter.go b/ipld/car/v2/internal/io/converter.go index 5550f86f2d..2e2844bd0e 100644 --- a/ipld/car/v2/internal/io/converter.go +++ b/ipld/car/v2/internal/io/converter.go @@ -1,6 +1,7 @@ package io import ( + "errors" "io" "sync" ) @@ -108,7 +109,7 @@ func (drsb *discardingReadSeekerPlusByte) Seek(offset int64, whence int) (int64, case io.SeekStart: n := offset - drsb.offset if n < 0 { - panic("unsupported rewind via whence: io.SeekStart") + return 0, errors.New("unsupported rewind via whence: io.SeekStart") } _, err := io.CopyN(io.Discard, drsb, n) return drsb.offset, err @@ -116,7 +117,7 @@ func (drsb *discardingReadSeekerPlusByte) Seek(offset int64, whence int) (int64, _, err := io.CopyN(io.Discard, drsb, offset) return drsb.offset, err default: - panic("unsupported whence: io.SeekEnd") + return 0, errors.New("unsupported whence: io.SeekEnd") } } @@ -137,9 +138,9 @@ func (ras *readerAtSeeker) Seek(offset int64, whence int) (int64, error) { case io.SeekCurrent: ras.position += offset case io.SeekEnd: - panic("unsupported whence: io.SeekEnd") + return 0, errors.New("unsupported whence: io.SeekEnd") default: - panic("unsupported whence") + return 0, errors.New("unsupported whence") } return ras.position, nil } From 050974f1ff79c8eee8f83649b6ffcb18c84783a8 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 8 Feb 2023 13:15:30 +1100 Subject: [PATCH 273/291] fix(doc): fix storage package doc formatting This commit was moved from ipld/go-car@317491d313d02b11fe40165c38ec4aba74806309 --- ipld/car/v2/storage/doc.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ipld/car/v2/storage/doc.go b/ipld/car/v2/storage/doc.go index eed6a1460c..21e049b21b 100644 --- a/ipld/car/v2/storage/doc.go +++ b/ipld/car/v2/storage/doc.go @@ -1,4 +1,4 @@ -// package storage provides a CAR abstraction for the +// Package storage provides a CAR abstraction for the // github.com/ipld/go-ipld-prime/storage interfaces in the form of a StorageCar. // // THIS PACKAGE IS EXPERIMENTAL. Breaking changes may be introduced in @@ -72,6 +72,4 @@ // • MaxAllowedHeaderSize // // • MaxAllowedSectionSize -// - package storage From 21784f07c855b9aecb49f13104fc3701963097e0 Mon Sep 17 00:00:00 2001 From: web3-bot Date: Wed, 8 Feb 2023 12:55:15 +0000 Subject: [PATCH 274/291] stop using the deprecated io/ioutil package This commit was moved from ipld/go-car@4b4d55a1c3cb3161ce3940f197d567692a85ba04 --- ipld/car/v2/index/indexsorted.go | 3 ++- ipld/car/v2/index/mhindexsorted.go | 3 ++- ipld/car/v2/index_gen_test.go | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index ed94ed8f73..ab1462dc43 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -5,10 +5,11 @@ import ( "encoding/binary" "errors" "fmt" - internalio "github.com/ipld/go-car/v2/internal/io" "io" "sort" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-multicodec" "github.com/ipfs/go-cid" diff --git a/ipld/car/v2/index/mhindexsorted.go b/ipld/car/v2/index/mhindexsorted.go index e0ef675de3..598f1701fd 100644 --- a/ipld/car/v2/index/mhindexsorted.go +++ b/ipld/car/v2/index/mhindexsorted.go @@ -3,10 +3,11 @@ package index import ( "encoding/binary" "errors" - internalio "github.com/ipld/go-car/v2/internal/io" "io" "sort" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/ipfs/go-cid" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 11011e81a7..61fd055da6 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -1,11 +1,12 @@ package car_test import ( - "github.com/stretchr/testify/assert" "io" "os" "testing" + "github.com/stretchr/testify/assert" + "github.com/ipfs/go-cid" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" From 2506711a6eb731d1b75cb71d2cff4797c82f1487 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 9 Feb 2023 15:17:50 +1100 Subject: [PATCH 275/291] fix: switch to crypto/rand.Read This commit was moved from ipld/go-car@17628413d82ee031aa49a1102ac1b3b2aafe1a3e --- ipld/car/util/util_test.go | 3 ++- ipld/car/v2/internal/carv1/util/util_test.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ipld/car/util/util_test.go b/ipld/car/util/util_test.go index e85aed334a..3f8f59af46 100644 --- a/ipld/car/util/util_test.go +++ b/ipld/car/util/util_test.go @@ -2,6 +2,7 @@ package util_test import ( "bytes" + crand "crypto/rand" "math/rand" "testing" @@ -15,7 +16,7 @@ func TestLdSize(t *testing.T) { data := make([][]byte, 5) for j := 0; j < 5; j++ { data[j] = make([]byte, rand.Intn(30)) - _, err := rand.Read(data[j]) + _, err := crand.Read(data[j]) require.NoError(t, err) } size := util.LdSize(data...) diff --git a/ipld/car/v2/internal/carv1/util/util_test.go b/ipld/car/v2/internal/carv1/util/util_test.go index 76828be9ab..27719183a8 100644 --- a/ipld/car/v2/internal/carv1/util/util_test.go +++ b/ipld/car/v2/internal/carv1/util/util_test.go @@ -2,6 +2,7 @@ package util_test import ( "bytes" + crand "crypto/rand" "math/rand" "testing" @@ -16,7 +17,7 @@ func TestLdSize(t *testing.T) { data := make([][]byte, 5) for j := 0; j < 5; j++ { data[j] = make([]byte, rand.Intn(30)) - _, err := rand.Read(data[j]) + _, err := crand.Read(data[j]) require.NoError(t, err) } size := util.LdSize(data...) From e8e0049acd1ed32bfb1323d95ead8652ccd7461a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 14 Feb 2023 20:14:36 +0100 Subject: [PATCH 276/291] blockstore: fast path for AllKeysChan using the index close https://github.com/ipld/go-car/issues/242 Crude benchmark: ``` func BenchmarkAllKeysChan(b *testing.B) { path := filepath.Join(b.TempDir(), "bench-large-v2.car") generateRandomCarV2File(b, path, 10<<20) // 10 MiB defer os.Remove(path) bs, err := blockstore.OpenReadWrite(path, nil) if err != nil { b.Fatal(err) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { c, err := bs.AllKeysChan(context.Background()) if err != nil { b.Fatal(err) } for range c { } } } func BenchmarkAllKeysChanUseWholeCIDs(b *testing.B) { path := filepath.Join(b.TempDir(), "bench-large-v2.car") generateRandomCarV2File(b, path, 10<<20) // 10 MiB defer os.Remove(path) bs, err := blockstore.OpenReadWrite(path, nil, carv2.UseWholeCIDs(true)) if err != nil { b.Fatal(err) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { c, err := bs.AllKeysChan(context.Background()) if err != nil { b.Fatal(err) } for range c { } } } ``` Before: > BenchmarkAllKeysChan-12 885 1256865 ns/op 88911 B/op 1617 allocs/op > BenchmarkAllKeysChanUseWholeCIDs-12 1010 1253543 ns/op 57861 B/op 976 allocs/op After > BenchmarkAllKeysChan-12 8971 135906 ns/op 30864 B/op 642 allocs/op > BenchmarkAllKeysChanUseWholeCIDs-12 13904 86140 ns/op 144 B/op 2 allocs/op BenchmarkAllKeysChan --- 9.25X faster, allocate 2.9X less memory BenchmarkAllKeysChanUseWholeCID --- 14.5X faster, allocate 401X less memory. This commit was moved from ipld/go-car@a4629d3384d214f62e7aa466be9b86b29cc69124 --- ipld/car/v2/blockstore/readwrite.go | 33 +++++++++++++++++++- ipld/car/v2/internal/store/insertionindex.go | 22 ++++++++----- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 3f5f7841b4..29cb9cedbc 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -260,7 +260,38 @@ func (b *ReadWrite) Finalize() error { } func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - return b.ronly.AllKeysChan(ctx) + if ctx.Err() != nil { + return nil, ctx.Err() + } + + b.ronly.mu.Lock() + defer b.ronly.mu.Unlock() + + if b.ronly.closed { + return nil, errClosed + } + + out := make(chan cid.Cid) + + go func() { + defer close(out) + err := b.idx.ForEachCid(func(c cid.Cid, _ uint64) error { + if !b.opts.BlockstoreUseWholeCIDs { + c = cid.NewCidV1(cid.Raw, c.Hash()) + } + select { + case out <- c: + case <-ctx.Done(): + return ctx.Err() + } + return nil + }) + if err != nil { + maybeReportError(ctx, err) + } + }() + + return out, nil } func (b *ReadWrite) Has(ctx context.Context, key cid.Cid) (bool, error) { diff --git a/ipld/car/v2/internal/store/insertionindex.go b/ipld/car/v2/internal/store/insertionindex.go index f52fb3f2e4..ffdf96fa49 100644 --- a/ipld/car/v2/internal/store/insertionindex.go +++ b/ipld/car/v2/internal/store/insertionindex.go @@ -152,17 +152,23 @@ func (ii *InsertionIndex) Unmarshal(r io.Reader) error { } func (ii *InsertionIndex) ForEach(f func(multihash.Multihash, uint64) error) error { - var errr error + var err error ii.items.AscendGreaterOrEqual(ii.items.Min(), func(i llrb.Item) bool { r := i.(recordDigest).Record - err := f(r.Cid.Hash(), r.Offset) - if err != nil { - errr = err - return false - } - return true + err = f(r.Cid.Hash(), r.Offset) + return err == nil + }) + return err +} + +func (ii *InsertionIndex) ForEachCid(f func(cid.Cid, uint64) error) error { + var err error + ii.items.AscendGreaterOrEqual(ii.items.Min(), func(i llrb.Item) bool { + r := i.(recordDigest).Record + err = f(r.Cid, r.Offset) + return err == nil }) - return errr + return err } func (ii *InsertionIndex) Codec() multicodec.Code { From 41a43838859e46cb9e6ce6e5cf32f8d94b14a074 Mon Sep 17 00:00:00 2001 From: Hector Sanjuan Date: Mon, 6 Mar 2023 23:14:18 +0000 Subject: [PATCH 277/291] feat: add WithTrustedCar() reader option (#381) * Add NextInsecure() method Attempt to make CAR traversal a little bit faster by not forcing users to hash every single in a CAR. * Add TrustedCAR option to BlockReader (remove NextInsecure()) * Add test for TrustedCAR option * fix: apply suggestions from code review --------- Co-authored-by: Rod Vagg This commit was moved from ipld/go-car@e9a77cb2313fe6f942cf03cc7fedd4fcf4151d1d --- ipld/car/v2/block_reader.go | 14 +++++++------ ipld/car/v2/block_reader_test.go | 34 ++++++++++++++++++++++++++++++++ ipld/car/v2/options.go | 9 +++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/ipld/car/v2/block_reader.go b/ipld/car/v2/block_reader.go index 27d134b059..fff477a6f1 100644 --- a/ipld/car/v2/block_reader.go +++ b/ipld/car/v2/block_reader.go @@ -120,13 +120,15 @@ func (br *BlockReader) Next() (blocks.Block, error) { return nil, err } - hashed, err := c.Prefix().Sum(data) - if err != nil { - return nil, err - } + if !br.opts.TrustedCAR { + hashed, err := c.Prefix().Sum(data) + if err != nil { + return nil, err + } - if !hashed.Equals(c) { - return nil, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", c, hashed) + if !hashed.Equals(c) { + return nil, fmt.Errorf("mismatch in content integrity, expected: %s, got: %s", c, hashed) + } } ss := uint64(c.ByteLen()) + uint64(len(data)) diff --git a/ipld/car/v2/block_reader_test.go b/ipld/car/v2/block_reader_test.go index b5958c7f00..ca6c2404d6 100644 --- a/ipld/car/v2/block_reader_test.go +++ b/ipld/car/v2/block_reader_test.go @@ -179,6 +179,40 @@ func TestMaxSectionLength(t *testing.T) { require.True(t, bytes.Equal(block, readBlock.RawData())) } +func TestTrustedCAR(t *testing.T) { + // headerHex is the zero-roots CARv1 header + const headerHex = "11a265726f6f7473806776657273696f6e01" + headerBytes, _ := hex.DecodeString(headerHex) + // block of zeros + block := make([]byte, 5) + // CID for that block + pfx := cid.NewPrefixV1(cid.Raw, mh.SHA2_256) + cid, err := pfx.Sum(block) + require.NoError(t, err) + + // Modify the block so it won't match CID anymore + block[2] = 0xFF + // construct CAR + var buf bytes.Buffer + buf.Write(headerBytes) + buf.Write(varint.ToUvarint(uint64(len(cid.Bytes()) + len(block)))) + buf.Write(cid.Bytes()) + buf.Write(block) + + // try to read it as trusted + car, err := carv2.NewBlockReader(bytes.NewReader(buf.Bytes()), carv2.WithTrustedCAR(true)) + require.NoError(t, err) + _, err = car.Next() + require.NoError(t, err) + + // Try to read it as untrusted - should fail + car, err = carv2.NewBlockReader(bytes.NewReader(buf.Bytes()), carv2.WithTrustedCAR(false)) + require.NoError(t, err) + // error should occur on first section read + _, err = car.Next() + require.EqualError(t, err, "mismatch in content integrity, expected: bafkreieikviivlpbn3cxhuq6njef37ikoysaqxa2cs26zxleqxpay2bzuq, got: bafkreidgklrppelx4fxcsna7cxvo3g7ayedfojkqeuus6kz6e4hy7gukmy") +} + func TestMaxHeaderLength(t *testing.T) { // headerHex is the is a 5 root CARv1 header const headerHex = "de01a265726f6f747385d82a58250001711220785197229dc8bb1152945da58e2348f7e279eeded06cc2ca736d0e879858b501d82a58250001711220785197229dc8bb1152945da58e2348f7e279eeded06cc2ca736d0e879858b501d82a58250001711220785197229dc8bb1152945da58e2348f7e279eeded06cc2ca736d0e879858b501d82a58250001711220785197229dc8bb1152945da58e2348f7e279eeded06cc2ca736d0e879858b501d82a58250001711220785197229dc8bb1152945da58e2348f7e279eeded06cc2ca736d0e879858b5016776657273696f6e01" diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index dbde3f3e15..e28589e890 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -60,6 +60,7 @@ type Options struct { MaxTraversalLinks uint64 WriteAsCarV1 bool TraversalPrototypeChooser traversal.LinkTargetNodePrototypeChooser + TrustedCAR bool MaxAllowedHeaderSize uint64 MaxAllowedSectionSize uint64 @@ -160,6 +161,14 @@ func WithTraversalPrototypeChooser(t traversal.LinkTargetNodePrototypeChooser) O } } +// WithTrustedCAR specifies whether CIDs match the block data as they are read +// from the CAR files. +func WithTrustedCAR(t bool) Option { + return func(o *Options) { + o.TrustedCAR = t + } +} + // MaxAllowedHeaderSize overrides the default maximum size (of 32 MiB) that a // CARv1 decode (including within a CARv2 container) will allow a header to be // without erroring. From f4e1df129ecebef3bdf30ecba8a88f682f1a2537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Thu, 16 Feb 2023 16:34:22 +0100 Subject: [PATCH 278/291] ReadWrite: add an alternative FinalizeReadOnly+Close flow The goal being to be able to keep reading blocks and the index after safely finalizing the file on disk. Typically for indexing purpose. This commit was moved from ipld/go-car@b6ef2a4924430e92c46b115651d4a2f01b65644b --- ipld/car/v2/blockstore/readwrite.go | 78 +++++++++++++++++++----- ipld/car/v2/blockstore/readwrite_test.go | 64 ++++++++++++++++++- 2 files changed, 125 insertions(+), 17 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 29cb9cedbc..7cbd0ca29c 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -18,6 +18,10 @@ import ( var _ blockstore.Blockstore = (*ReadWrite)(nil) +var ( + errFinalized = fmt.Errorf("cannot write in a carv2 blockstore after finalize") +) + // ReadWrite implements a blockstore that stores blocks in CARv2 format. // Blocks put into the blockstore can be read back once they are successfully written. // This implementation is preferable for a write-heavy workload. @@ -35,6 +39,8 @@ type ReadWrite struct { idx *store.InsertionIndex header carv2.Header + finalized bool // also protected by ronly.mu + opts carv2.Options } @@ -109,10 +115,11 @@ func OpenReadWriteFile(f *os.File, roots []cid.Cid, opts ...carv2.Option) (*Read // Instantiate block store. // Set the header fileld before applying options since padding options may modify header. rwbs := &ReadWrite{ - f: f, - idx: store.NewInsertionIndex(), - header: carv2.NewHeader(0), - opts: carv2.ApplyOptions(opts...), + f: f, + idx: store.NewInsertionIndex(), + header: carv2.NewHeader(0), + opts: carv2.ApplyOptions(opts...), + finalized: false, } rwbs.ronly.opts = rwbs.opts @@ -186,6 +193,9 @@ func (b *ReadWrite) PutMany(ctx context.Context, blks []blocks.Block) error { if b.ronly.closed { return errClosed } + if b.finalized { + return errFinalized + } for _, bl := range blks { c := bl.Cid() @@ -227,30 +237,70 @@ func (b *ReadWrite) Discard() { // Finalize finalizes this blockstore by writing the CARv2 header, along with flattened index // for more efficient subsequent read. +// This is the equivalent to calling FinalizeReadOnly and Close. // After this call, the blockstore can no longer be used. func (b *ReadWrite) Finalize() error { + b.ronly.mu.Lock() + defer b.ronly.mu.Unlock() + + if err := b.finalizeReadOnlyWithoutMutex(); err != nil { + return err + } + if err := b.closeWithoutMutex(); err != nil { + return err + } + return nil +} + +// Finalize finalizes this blockstore by writing the CARv2 header, along with flattened index +// for more efficient subsequent read, but keep it open read-only. +// This call should be complemented later by a call to Close. +func (b *ReadWrite) FinalizeReadOnly() error { + b.ronly.mu.Lock() + defer b.ronly.mu.Unlock() + + return b.finalizeReadOnlyWithoutMutex() +} + +func (b *ReadWrite) finalizeReadOnlyWithoutMutex() error { if b.opts.WriteAsCarV1 { // all blocks are already properly written to the CARv1 inner container and there's // no additional finalization required at the end of the file for a complete v1 - b.ronly.Close() + b.finalized = true return nil } - b.ronly.mu.Lock() - defer b.ronly.mu.Unlock() - if b.ronly.closed { // Allow duplicate Finalize calls, just like Close. // Still error, just like ReadOnly.Close; it should be discarded. - return fmt.Errorf("called Finalize on a closed blockstore") + return fmt.Errorf("called Finalize or FinalizeReadOnly on a closed blockstore") + } + if b.finalized { + return fmt.Errorf("called Finalize or FinalizeReadOnly on an already finalized blockstore") } - // Note that we can't use b.Close here, as that tries to grab the same - // mutex we're holding here. - defer b.ronly.closeWithoutMutex() + b.finalized = true - if err := store.Finalize(b.f, b.header, b.idx, uint64(b.dataWriter.Position()), b.opts.StoreIdentityCIDs, b.opts.IndexCodec); err != nil { - return err + return store.Finalize(b.f, b.header, b.idx, uint64(b.dataWriter.Position()), b.opts.StoreIdentityCIDs, b.opts.IndexCodec) +} + +// Close closes the blockstore. +// After this call, the blockstore can no longer be used. +func (b *ReadWrite) Close() error { + b.ronly.mu.Lock() + defer b.ronly.mu.Unlock() + + return b.closeWithoutMutex() +} + +func (b *ReadWrite) closeWithoutMutex() error { + if !b.opts.WriteAsCarV1 && !b.finalized { + return fmt.Errorf("called Close without FinalizeReadOnly first") + } + if b.ronly.closed { + // Allow duplicate Close calls + // Still error, just like ReadOnly.Close; it should be discarded. + return fmt.Errorf("called Close on a closed blockstore") } if err := b.ronly.closeWithoutMutex(); err != nil { diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 04da36fe8d..45fcb21ac9 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -577,9 +577,6 @@ func TestReadWritePanicsOnlyWhenFinalized(t *testing.T) { require.NoError(t, subject.Finalize()) require.Error(t, subject.Finalize()) - _, ok := (interface{})(subject).(io.Closer) - require.False(t, ok) - _, err = subject.Get(ctx, oneTestBlockCid) require.Error(t, err) _, err = subject.GetSize(ctx, anotherTestBlockCid) @@ -1027,3 +1024,64 @@ func TestBlockstore_IdentityCidWithEmptyDataIsIndexed(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, count) } + +func TestBlockstoreFinalizeReadOnly(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + root := blocks.NewBlock([]byte("foo")) + + p := filepath.Join(t.TempDir(), "readwrite.car") + bs, err := blockstore.OpenReadWrite(p, []cid.Cid{root.Cid()}) + require.NoError(t, err) + + err = bs.Put(ctx, root) + require.NoError(t, err) + + roots, err := bs.Roots() + require.NoError(t, err) + _, err = bs.Has(ctx, roots[0]) + require.NoError(t, err) + _, err = bs.Get(ctx, roots[0]) + require.NoError(t, err) + _, err = bs.GetSize(ctx, roots[0]) + require.NoError(t, err) + _, err = bs.AllKeysChan(ctx) + require.NoError(t, err) + + // soft finalize, we can still read, but not write + err = bs.FinalizeReadOnly() + require.NoError(t, err) + + _, err = bs.Roots() + require.NoError(t, err) + _, err = bs.Has(ctx, roots[0]) + require.NoError(t, err) + _, err = bs.Get(ctx, roots[0]) + require.NoError(t, err) + _, err = bs.GetSize(ctx, roots[0]) + require.NoError(t, err) + _, err = bs.AllKeysChan(ctx) + require.NoError(t, err) + + err = bs.Put(ctx, root) + require.Error(t, err) + + // final close, nothing works anymore + err = bs.Close() + require.NoError(t, err) + + _, err = bs.Roots() + require.Error(t, err) + _, err = bs.Has(ctx, roots[0]) + require.Error(t, err) + _, err = bs.Get(ctx, roots[0]) + require.Error(t, err) + _, err = bs.GetSize(ctx, roots[0]) + require.Error(t, err) + _, err = bs.AllKeysChan(ctx) + require.Error(t, err) + + err = bs.Put(ctx, root) + require.Error(t, err) +} From 7349b74b2b8711224d2f367c48d838d3d05c1408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 7 Mar 2023 12:15:23 +0100 Subject: [PATCH 279/291] blockstore: try to close during Finalize(), even in case of previous error Co-authored-by: Rod Vagg This commit was moved from ipld/go-car@34f4b63d45e446dbd833c0488204ec9a263275e3 --- ipld/car/v2/blockstore/readwrite.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 7cbd0ca29c..e605414f27 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -243,11 +243,10 @@ func (b *ReadWrite) Finalize() error { b.ronly.mu.Lock() defer b.ronly.mu.Unlock() - if err := b.finalizeReadOnlyWithoutMutex(); err != nil { - return err - } - if err := b.closeWithoutMutex(); err != nil { - return err + for _, err := range []error{b.finalizeReadOnlyWithoutMutex(), b.closeWithoutMutex()} { + if err != nil { + return err + } } return nil } From 7ecc10dc83088535a5c9c95811725714fe85c3ab Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 6 Mar 2023 22:14:17 +1100 Subject: [PATCH 280/291] fix: if we don't read the full block data, don't error on !EOF This commit was moved from ipld/go-car@dbd9059c689272c22f2dd6f805cccc0e398625ad --- ipld/car/cmd/car/inspect.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ipld/car/cmd/car/inspect.go b/ipld/car/cmd/car/inspect.go index 76bb41ce9a..f320500cd4 100644 --- a/ipld/car/cmd/car/inspect.go +++ b/ipld/car/cmd/car/inspect.go @@ -3,6 +3,7 @@ package main import ( "bytes" "fmt" + "io" "os" "sort" "strings" @@ -22,7 +23,7 @@ func InspectCar(c *cli.Context) (err error) { } } - rd, err := carv2.NewReader(inStream) + rd, err := carv2.NewReader(inStream, carv2.ZeroLengthSectionAsEOF(true)) if err != nil { return err } @@ -31,6 +32,15 @@ func InspectCar(c *cli.Context) (err error) { return err } + if stats.Version == 1 && c.IsSet("full") { // check that we've read all the data + got, err := inStream.Read(make([]byte, 1)) // force EOF + if err != nil && err != io.EOF { + return err + } else if got > 0 { + return fmt.Errorf("unexpected data after EOF: %d", got) + } + } + var v2s string if stats.Version == 2 { idx := "(none)" From 0904e49f59e278f7cc385849b13d9a0bb7022add Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 6 Mar 2023 22:15:01 +1100 Subject: [PATCH 281/291] feat: extract specific path, accept stdin as streaming input This commit was moved from ipld/go-car@15e65826705b0a1d37302e8a5b1a9af899ee2a8b --- ipld/car/cmd/car/car.go | 8 +- ipld/car/cmd/car/extract.go | 354 ++++++++++++------ .../car/testdata/script/create-extract.txt | 2 +- 3 files changed, 255 insertions(+), 109 deletions(-) diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 70c9eb32a2..81cec5a384 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -79,10 +79,16 @@ func main1() int { &cli.StringFlag{ Name: "file", Aliases: []string{"f"}, - Usage: "The car file to extract from", + Usage: "The car file to extract from, or '-' to read from stdin", Required: true, TakesFile: true, }, + &cli.StringFlag{ + Name: "path", + Aliases: []string{"p"}, + Usage: "The unixfs path to extract", + Required: false, + }, &cli.BoolFlag{ Name: "verbose", Aliases: []string{"v"}, diff --git a/ipld/car/cmd/car/extract.go b/ipld/car/cmd/car/extract.go index ae2da87513..db4f207f74 100644 --- a/ipld/car/cmd/car/extract.go +++ b/ipld/car/cmd/car/extract.go @@ -1,23 +1,27 @@ package main import ( - "bytes" + "context" "errors" "fmt" "io" "os" "path" "path/filepath" + "strings" + "sync" "github.com/ipfs/go-cid" "github.com/ipfs/go-unixfsnode" "github.com/ipfs/go-unixfsnode/data" "github.com/ipfs/go-unixfsnode/file" - "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-car/v2" + carstorage "github.com/ipld/go-car/v2/storage" dagpb "github.com/ipld/go-codec-dagpb" "github.com/ipld/go-ipld-prime" cidlink "github.com/ipld/go-ipld-prime/linking/cid" basicnode "github.com/ipld/go-ipld-prime/node/basic" + "github.com/ipld/go-ipld-prime/storage" "github.com/urfave/cli/v2" ) @@ -33,92 +37,109 @@ func ExtractCar(c *cli.Context) error { outputDir = c.Args().First() } - bs, err := blockstore.OpenReadOnly(c.String("file")) - if err != nil { - return err - } + var store storage.ReadableStorage + var roots []cid.Cid - ls := cidlink.DefaultLinkSystem() - ls.TrustedStorage = true - ls.StorageReadOpener = func(_ ipld.LinkContext, l ipld.Link) (io.Reader, error) { - cl, ok := l.(cidlink.Link) - if !ok { - return nil, fmt.Errorf("not a cidlink") + if c.String("file") == "-" { + var err error + store, roots, err = NewStdinReadStorage(c.App.Reader) + if err != nil { + return err } - blk, err := bs.Get(c.Context, cl.Cid) + } else { + carFile, err := os.Open(c.String("file")) if err != nil { - return nil, err + return err + } + store, err = carstorage.OpenReadable(carFile) + if err != nil { + return err } - return bytes.NewBuffer(blk.RawData()), nil + roots = store.(carstorage.ReadableCar).Roots() } - roots, err := bs.Roots() + ls := cidlink.DefaultLinkSystem() + ls.TrustedStorage = true + ls.SetReadStorage(store) + + path, err := pathSegments(c.String("path")) if err != nil { return err } + var extractedFiles int for _, root := range roots { - if err := extractRoot(c, &ls, root, outputDir); err != nil { + count, err := extractRoot(c, &ls, root, outputDir, path) + if err != nil { return err } + extractedFiles += count + } + if extractedFiles == 0 { + fmt.Fprintf(c.App.ErrWriter, "no files extracted\n") + } else { + fmt.Fprintf(c.App.ErrWriter, "extracted %d file(s)\n", extractedFiles) } return nil } -func extractRoot(c *cli.Context, ls *ipld.LinkSystem, root cid.Cid, outputDir string) error { +func extractRoot(c *cli.Context, ls *ipld.LinkSystem, root cid.Cid, outputDir string, path []string) (int, error) { if root.Prefix().Codec == cid.Raw { if c.IsSet("verbose") { fmt.Fprintf(c.App.ErrWriter, "skipping raw root %s\n", root) } - return nil + return 0, nil } pbn, err := ls.Load(ipld.LinkContext{}, cidlink.Link{Cid: root}, dagpb.Type.PBNode) if err != nil { - return err + return 0, err } pbnode := pbn.(dagpb.PBNode) ufn, err := unixfsnode.Reify(ipld.LinkContext{}, pbnode, ls) if err != nil { - return err + return 0, err } outputResolvedDir, err := filepath.EvalSymlinks(outputDir) if err != nil { - return err + return 0, err } if _, err := os.Stat(outputResolvedDir); os.IsNotExist(err) { if err := os.Mkdir(outputResolvedDir, 0755); err != nil { - return err + return 0, err } } - if err := extractDir(c, ls, ufn, outputResolvedDir, "/"); err != nil { + count, err := extractDir(c, ls, ufn, outputResolvedDir, "/", path) + if err != nil { if !errors.Is(err, ErrNotDir) { - return fmt.Errorf("%s: %w", root, err) + return 0, fmt.Errorf("%s: %w", root, err) } + + // if it's not a directory, it's a file. ufsData, err := pbnode.LookupByString("Data") if err != nil { - return err + return 0, err } ufsBytes, err := ufsData.AsBytes() if err != nil { - return err + return 0, err } ufsNode, err := data.DecodeUnixFSData(ufsBytes) if err != nil { - return err + return 0, err } if ufsNode.DataType.Int() == data.Data_File || ufsNode.DataType.Int() == data.Data_Raw { if err := extractFile(c, ls, pbnode, filepath.Join(outputResolvedDir, "unknown")); err != nil { - return err + return 0, err } } - return nil + return 1, nil } - return nil + return count, nil } func resolvePath(root, pth string) (string, error) { @@ -139,99 +160,131 @@ func resolvePath(root, pth string) (string, error) { return joined, nil } -func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, outputPath string) error { +func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, outputPath string, matchPath []string) (int, error) { dirPath, err := resolvePath(outputRoot, outputPath) if err != nil { - return err + return 0, err } // make the directory. if err := os.MkdirAll(dirPath, 0755); err != nil { - return err + return 0, err } - if n.Kind() == ipld.Kind_Map { - mi := n.MapIterator() - for !mi.Done() { - key, val, err := mi.Next() - if err != nil { - return err - } - ks, err := key.AsString() - if err != nil { - return err - } - nextRes, err := resolvePath(outputRoot, path.Join(outputPath, ks)) - if err != nil { - return err - } - if c.IsSet("verbose") { - fmt.Fprintf(c.App.Writer, "%s\n", nextRes) - } + if n.Kind() != ipld.Kind_Map { + return 0, ErrNotDir + } - if val.Kind() != ipld.Kind_Link { - return fmt.Errorf("unexpected map value for %s at %s", ks, outputPath) - } - // a directory may be represented as a map of name: if unixADL is applied - vl, err := val.AsLink() - if err != nil { - return err - } - dest, err := ls.Load(ipld.LinkContext{}, vl, basicnode.Prototype.Any) - if err != nil { - return err + subPath := matchPath + if len(matchPath) > 0 { + subPath = matchPath[1:] + } + + extractElement := func(name string, n ipld.Node) (int, error) { + nextRes, err := resolvePath(outputRoot, path.Join(outputPath, name)) + if err != nil { + return 0, err + } + if c.IsSet("verbose") { + fmt.Fprintf(c.App.Writer, "%s\n", nextRes) + } + + if n.Kind() != ipld.Kind_Link { + return 0, fmt.Errorf("unexpected map value for %s at %s", name, outputPath) + } + // a directory may be represented as a map of name: if unixADL is applied + vl, err := n.AsLink() + if err != nil { + return 0, err + } + dest, err := ls.Load(ipld.LinkContext{}, vl, basicnode.Prototype.Any) + if err != nil { + if nf, ok := err.(interface{ NotFound() bool }); ok && nf.NotFound() { + fmt.Fprintf(c.App.ErrWriter, "data for directory entry not found: %s (skipping...)\n", name) + return 0, nil } - // degenerate files are handled here. - if dest.Kind() == ipld.Kind_Bytes { - if err := extractFile(c, ls, dest, nextRes); err != nil { - return err - } - continue - } else { - // dir / pbnode - pbb := dagpb.Type.PBNode.NewBuilder() - if err := pbb.AssignNode(dest); err != nil { - return err - } - dest = pbb.Build() + return 0, err + } + // degenerate files are handled here. + if dest.Kind() == ipld.Kind_Bytes { + if err := extractFile(c, ls, dest, nextRes); err != nil { + return 0, err } - pbnode := dest.(dagpb.PBNode) + return 1, nil + } - // interpret dagpb 'data' as unixfs data and look at type. - ufsData, err := pbnode.LookupByString("Data") - if err != nil { - return err - } - ufsBytes, err := ufsData.AsBytes() + // dir / pbnode + pbb := dagpb.Type.PBNode.NewBuilder() + if err := pbb.AssignNode(dest); err != nil { + return 0, err + } + pbnode := pbb.Build().(dagpb.PBNode) + + // interpret dagpb 'data' as unixfs data and look at type. + ufsData, err := pbnode.LookupByString("Data") + if err != nil { + return 0, err + } + ufsBytes, err := ufsData.AsBytes() + if err != nil { + return 0, err + } + ufsNode, err := data.DecodeUnixFSData(ufsBytes) + if err != nil { + return 0, err + } + + switch ufsNode.DataType.Int() { + case data.Data_Directory, data.Data_HAMTShard: + ufn, err := unixfsnode.Reify(ipld.LinkContext{}, pbnode, ls) if err != nil { - return err + return 0, err } - ufsNode, err := data.DecodeUnixFSData(ufsBytes) - if err != nil { - return err + return extractDir(c, ls, ufn, outputRoot, path.Join(outputPath, name), subPath) + case data.Data_File, data.Data_Raw: + if err := extractFile(c, ls, pbnode, nextRes); err != nil { + return 0, err } - if ufsNode.DataType.Int() == data.Data_Directory || ufsNode.DataType.Int() == data.Data_HAMTShard { - ufn, err := unixfsnode.Reify(ipld.LinkContext{}, pbnode, ls) - if err != nil { - return err - } - - if err := extractDir(c, ls, ufn, outputRoot, path.Join(outputPath, ks)); err != nil { - return err - } - } else if ufsNode.DataType.Int() == data.Data_File || ufsNode.DataType.Int() == data.Data_Raw { - if err := extractFile(c, ls, pbnode, nextRes); err != nil { - return err - } - } else if ufsNode.DataType.Int() == data.Data_Symlink { - data := ufsNode.Data.Must().Bytes() - if err := os.Symlink(string(data), nextRes); err != nil { - return err - } + return 1, nil + case data.Data_Symlink: + data := ufsNode.Data.Must().Bytes() + if err := os.Symlink(string(data), nextRes); err != nil { + return 0, err } + return 1, nil + default: + return 0, fmt.Errorf("unknown unixfs type: %d", ufsNode.DataType.Int()) + } + } + + // specific path segment + if len(matchPath) > 0 { + val, err := n.LookupByString(matchPath[0]) + if err != nil { + return 0, err + } + return extractElement(matchPath[0], val) + } + + // everything + var count int + mi := n.MapIterator() + for !mi.Done() { + key, val, err := mi.Next() + if err != nil { + return 0, err } - return nil + ks, err := key.AsString() + if err != nil { + return 0, err + } + ecount, err := extractElement(ks, val) + if err != nil { + return 0, err + } + count += ecount } - return ErrNotDir + + return count, nil } func extractFile(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputName string) error { @@ -253,3 +306,90 @@ func extractFile(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputName st return err } + +// TODO: dedupe this with lassie, probably into go-unixfsnode +func pathSegments(path string) ([]string, error) { + segments := strings.Split(path, "/") + filtered := make([]string, 0, len(segments)) + for i := 0; i < len(segments); i++ { + if segments[i] == "" { + // Allow one leading and one trailing '/' at most + if i == 0 || i == len(segments)-1 { + continue + } + return nil, fmt.Errorf("invalid empty path segment at position %d", i) + } + if segments[i] == "." || segments[i] == ".." { + return nil, fmt.Errorf("'%s' is unsupported in paths", segments[i]) + } + filtered = append(filtered, segments[i]) + } + return filtered, nil +} + +var _ storage.ReadableStorage = (*stdinReadStorage)(nil) + +type stdinReadStorage struct { + blocks map[string][]byte + done bool + lk *sync.RWMutex + cond *sync.Cond +} + +func NewStdinReadStorage(reader io.Reader) (*stdinReadStorage, []cid.Cid, error) { + var lk sync.RWMutex + srs := &stdinReadStorage{ + blocks: make(map[string][]byte), + lk: &lk, + cond: sync.NewCond(&lk), + } + rdr, err := car.NewBlockReader(reader) + if err != nil { + return nil, nil, err + } + go func() { + for { + blk, err := rdr.Next() + if err == io.EOF { + srs.lk.Lock() + srs.done = true + srs.lk.Unlock() + return + } + if err != nil { + panic(err) + } + srs.lk.Lock() + srs.blocks[string(blk.Cid().Hash())] = blk.RawData() + srs.cond.Broadcast() + srs.lk.Unlock() + } + }() + return srs, rdr.Roots, nil +} + +func (srs *stdinReadStorage) Has(ctx context.Context, key string) (bool, error) { + _, err := srs.Get(ctx, key) + if err != nil { + return false, err + } + return true, nil +} + +func (srs *stdinReadStorage) Get(ctx context.Context, key string) ([]byte, error) { + c, err := cid.Cast([]byte(key)) + if err != nil { + return nil, err + } + srs.lk.Lock() + defer srs.lk.Unlock() + for { + if data, ok := srs.blocks[string(c.Hash())]; ok { + return data, nil + } + if srs.done { + return nil, carstorage.ErrNotFound{Cid: c} + } + srs.cond.Wait() + } +} diff --git a/ipld/car/cmd/car/testdata/script/create-extract.txt b/ipld/car/cmd/car/testdata/script/create-extract.txt index 648bafc493..6dac510b23 100644 --- a/ipld/car/cmd/car/testdata/script/create-extract.txt +++ b/ipld/car/cmd/car/testdata/script/create-extract.txt @@ -1,8 +1,8 @@ car create --file=out.car foo.txt bar.txt mkdir out car extract -v -f out.car out -! stderr . stdout -count=2 'txt$' +stderr -count=1 '^extracted 2 file\(s\)$' car create --file=out2.car out/foo.txt out/bar.txt cmp out.car out2.car From 30034dabd956dc3eb10ef9d8d9751bb1d9d942c1 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 6 Mar 2023 22:49:12 +1100 Subject: [PATCH 282/291] feat: extract accepts '-' as an output path for stdout This commit was moved from ipld/go-car@ba7e4d732f81b98791ac3af04a9c71a372507cd5 --- ipld/car/cmd/car/car.go | 9 +++-- ipld/car/cmd/car/extract.go | 76 ++++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 81cec5a384..7fb0a71560 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -71,10 +71,11 @@ func main1() int { }}, }, { - Name: "extract", - Aliases: []string{"x"}, - Usage: "Extract the contents of a car when the car encodes UnixFS data", - Action: ExtractCar, + Name: "extract", + Aliases: []string{"x"}, + Usage: "Extract the contents of a car when the car encodes UnixFS data", + Action: ExtractCar, + ArgsUsage: "[output directory|-]", Flags: []cli.Flag{ &cli.StringFlag{ Name: "file", diff --git a/ipld/car/cmd/car/extract.go b/ipld/car/cmd/car/extract.go index db4f207f74..b14d5cdb1e 100644 --- a/ipld/car/cmd/car/extract.go +++ b/ipld/car/cmd/car/extract.go @@ -103,15 +103,19 @@ func extractRoot(c *cli.Context, ls *ipld.LinkSystem, root cid.Cid, outputDir st return 0, err } - outputResolvedDir, err := filepath.EvalSymlinks(outputDir) - if err != nil { - return 0, err - } - if _, err := os.Stat(outputResolvedDir); os.IsNotExist(err) { - if err := os.Mkdir(outputResolvedDir, 0755); err != nil { + var outputResolvedDir string + if outputDir != "-" { + outputResolvedDir, err = filepath.EvalSymlinks(outputDir) + if err != nil { return 0, err } + if _, err := os.Stat(outputResolvedDir); os.IsNotExist(err) { + if err := os.Mkdir(outputResolvedDir, 0755); err != nil { + return 0, err + } + } } + count, err := extractDir(c, ls, ufn, outputResolvedDir, "/", path) if err != nil { if !errors.Is(err, ErrNotDir) { @@ -131,8 +135,12 @@ func extractRoot(c *cli.Context, ls *ipld.LinkSystem, root cid.Cid, outputDir st if err != nil { return 0, err } + var outputName string + if outputDir != "-" { + outputName = filepath.Join(outputResolvedDir, "unknown") + } if ufsNode.DataType.Int() == data.Data_File || ufsNode.DataType.Int() == data.Data_Raw { - if err := extractFile(c, ls, pbnode, filepath.Join(outputResolvedDir, "unknown")); err != nil { + if err := extractFile(c, ls, pbnode, outputName); err != nil { return 0, err } } @@ -161,13 +169,15 @@ func resolvePath(root, pth string) (string, error) { } func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, outputPath string, matchPath []string) (int, error) { - dirPath, err := resolvePath(outputRoot, outputPath) - if err != nil { - return 0, err - } - // make the directory. - if err := os.MkdirAll(dirPath, 0755); err != nil { - return 0, err + if outputRoot != "" { + dirPath, err := resolvePath(outputRoot, outputPath) + if err != nil { + return 0, err + } + // make the directory. + if err := os.MkdirAll(dirPath, 0755); err != nil { + return 0, err + } } if n.Kind() != ipld.Kind_Map { @@ -180,12 +190,16 @@ func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, ou } extractElement := func(name string, n ipld.Node) (int, error) { - nextRes, err := resolvePath(outputRoot, path.Join(outputPath, name)) - if err != nil { - return 0, err - } - if c.IsSet("verbose") { - fmt.Fprintf(c.App.Writer, "%s\n", nextRes) + var nextRes string + if outputRoot != "" { + var err error + nextRes, err = resolvePath(outputRoot, path.Join(outputPath, name)) + if err != nil { + return 0, err + } + if c.IsSet("verbose") { + fmt.Fprintf(c.App.Writer, "%s\n", nextRes) + } } if n.Kind() != ipld.Kind_Link { @@ -246,6 +260,9 @@ func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, ou } return 1, nil case data.Data_Symlink: + if nextRes == "" { + return 0, fmt.Errorf("cannot extract a symlink to stdout") + } data := ufsNode.Data.Must().Bytes() if err := os.Symlink(string(data), nextRes); err != nil { return 0, err @@ -265,6 +282,10 @@ func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, ou return extractElement(matchPath[0], val) } + if outputPath == "-" && len(matchPath) == 0 { + return 0, fmt.Errorf("cannot extract a directory to stdout, use a path to extract a specific file") + } + // everything var count int mi := n.MapIterator() @@ -296,14 +317,17 @@ func extractFile(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputName st if err != nil { return err } - - f, err := os.Create(outputName) - if err != nil { - return err + var f *os.File + if outputName == "" { + f = os.Stdout + } else { + f, err = os.Create(outputName) + if err != nil { + return err + } + defer f.Close() } - defer f.Close() _, err = io.Copy(f, nlr) - return err } From e1368b197bc3a177bbba4b4bf2ef22f43fd02d21 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 7 Mar 2023 20:56:42 +1100 Subject: [PATCH 283/291] chore: add test cases for extract modes This commit was moved from ipld/go-car@5647641f655cc1880488b2559ff7c3d70a7222a0 --- ipld/car/cmd/car/extract.go | 2 +- .../inputs/simple-unixfs-missing-blocks.car | Bin 0 -> 1620 bytes .../cmd/car/testdata/inputs/simple-unixfs.car | Bin 0 -> 1933 bytes .../wikipedia-cryptographic-hash-function.car | Bin 0 -> 161731 bytes ipld/car/cmd/car/testdata/script/extract.txt | 97 ++++++++++++++++++ 5 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 ipld/car/cmd/car/testdata/inputs/simple-unixfs-missing-blocks.car create mode 100644 ipld/car/cmd/car/testdata/inputs/simple-unixfs.car create mode 100644 ipld/car/cmd/car/testdata/inputs/wikipedia-cryptographic-hash-function.car create mode 100644 ipld/car/cmd/car/testdata/script/extract.txt diff --git a/ipld/car/cmd/car/extract.go b/ipld/car/cmd/car/extract.go index b14d5cdb1e..c742cf4c77 100644 --- a/ipld/car/cmd/car/extract.go +++ b/ipld/car/cmd/car/extract.go @@ -213,7 +213,7 @@ func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, ou dest, err := ls.Load(ipld.LinkContext{}, vl, basicnode.Prototype.Any) if err != nil { if nf, ok := err.(interface{ NotFound() bool }); ok && nf.NotFound() { - fmt.Fprintf(c.App.ErrWriter, "data for directory entry not found: %s (skipping...)\n", name) + fmt.Fprintf(c.App.ErrWriter, "data for entry not found: %s (skipping...)\n", name) return 0, nil } return 0, err diff --git a/ipld/car/cmd/car/testdata/inputs/simple-unixfs-missing-blocks.car b/ipld/car/cmd/car/testdata/inputs/simple-unixfs-missing-blocks.car new file mode 100644 index 0000000000000000000000000000000000000000..7e808871384b1890dd492276eaa8ea5bf7cc151d GIT binary patch literal 1620 zcmcCmlv-Bl&LX1gpwNn3N zzhv)j&FHvo*SVu~E{`BPZ|~vgn4OZ79eytMefCp`F&U_qi;06#Pe|cO$QcLYh~0-B z>|!c8yepr~mX8-sGyZSazL(dP1AG(xgFZ+`Jwt}l( zEOj%J5TludJ;-07LJECnOiEE~qSM0~?+AzhF= z<8JdD`^f+BdcZ2l#d{nkm?x)(#2TH>eDFEKXzG#hbzVZO9(pAeB@#R!Q$gM*7-wKV z*;+44^3!?V`*|(<)O{UoiOhAeoZic9{JbJ5ay@ktmxyHSal{JI6s4b zpZykt;)&{olU}}6u05;%bKSq&r9zBGFr~3-qRA#kmtyyc)Hh3=vS0J9Ql&6&*PO54 zo^Pp{C3HVah|w70ePFZ_@)$H)Ej)nH3RJ#F+JD9-oeyieSj_fuDm@R8)Z^XZa`c<7 zs^~x0q{zZgQ03b{CYxZG3%!-vjl9n`Y)_0geo_30xAba9l;<0M;#$U zg;<>sQ3o_t+mUh&P(TQ=x}%s% zFs8w74RZBp$O|!V{I@B`FJ|F-ZthiWq6aE7zwVgv(D?MH?^QyqjtEmh$&6qY1}8Iu zUSc$dr5%FVfzbk{bXU61R)OdHJtOoouH5WlU-q13_HT=&F1PruUVnCagTA#8qa{3- z5zM?`?{5>{e|AOvSJkK#8<~GeF7i$@EvmDRa#l!hc){VwR^}$e>Wc6_FscY9bg-#} k5}^>QGs0A08YdJ9&{SyX2uXzmg8}RaLdB;Lt0%${0FM8K+yDRo literal 0 HcmV?d00001 diff --git a/ipld/car/cmd/car/testdata/inputs/simple-unixfs.car b/ipld/car/cmd/car/testdata/inputs/simple-unixfs.car new file mode 100644 index 0000000000000000000000000000000000000000..55b8a571a9478462f93b04e4164814d17f0b9853 GIT binary patch literal 1933 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�(AsN1(udpy5WJUk2 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 Mv0_(~>wfzG14P_fZvX%Q literal 0 HcmV?d00001 diff --git a/ipld/car/cmd/car/testdata/script/extract.txt b/ipld/car/cmd/car/testdata/script/extract.txt new file mode 100644 index 0000000000..4e5061ef45 --- /dev/null +++ b/ipld/car/cmd/car/testdata/script/extract.txt @@ -0,0 +1,97 @@ +# full DAG export, everything in the CAR +mkdir actual-full +car extract -f ${INPUTS}/simple-unixfs.car actual-full +stderr '^extracted 9 file\(s\)$' +cmp actual-full/a/1/A.txt expected/a/1/A.txt +cmp actual-full/a/2/B.txt expected/a/2/B.txt +cmp actual-full/a/3/C.txt expected/a/3/C.txt +cmp actual-full/b/5/E.txt expected/b/5/E.txt +cmp actual-full/b/6/F.txt expected/b/6/F.txt +cmp actual-full/b/4/D.txt expected/b/4/D.txt +cmp actual-full/c/9/I.txt expected/c/9/I.txt +cmp actual-full/c/7/G.txt expected/c/7/G.txt +cmp actual-full/c/8/H.txt expected/c/8/H.txt + +# full DAG export, everything in the CAR, accepted from stdin +mkdir actual-stdin +stdin ${INPUTS}/simple-unixfs.car +car extract -f - actual-stdin +stderr '^extracted 9 file\(s\)$' +cmp actual-stdin/a/1/A.txt expected/a/1/A.txt +cmp actual-stdin/a/2/B.txt expected/a/2/B.txt +cmp actual-stdin/a/3/C.txt expected/a/3/C.txt +cmp actual-stdin/b/5/E.txt expected/b/5/E.txt +cmp actual-stdin/b/6/F.txt expected/b/6/F.txt +cmp actual-stdin/b/4/D.txt expected/b/4/D.txt +cmp actual-stdin/c/9/I.txt expected/c/9/I.txt +cmp actual-stdin/c/7/G.txt expected/c/7/G.txt +cmp actual-stdin/c/8/H.txt expected/c/8/H.txt + +# full DAG export, everything in the CAR, but the CAR is missing blocks (incomplete DAG) +mkdir actual-missing +car extract -f ${INPUTS}/simple-unixfs-missing-blocks.car actual-missing +stderr -count=1 'data for entry not found: 4 \(skipping\.\.\.\)' +stderr -count=1 'data for entry not found: E.txt \(skipping\.\.\.\)' +stderr -count=1 'data for entry not found: 6 \(skipping\.\.\.\)' +stderr -count=1 '^extracted 6 file\(s\)$' +cmp actual-missing/a/1/A.txt expected/a/1/A.txt +cmp actual-missing/a/2/B.txt expected/a/2/B.txt +cmp actual-missing/a/3/C.txt expected/a/3/C.txt +! exists actual-missing/b/5/E.txt +! exists actual-missing/b/6/F.txt +! exists actual-missing/b/4/D.txt +cmp actual-missing/c/9/I.txt expected/c/9/I.txt +cmp actual-missing/c/7/G.txt expected/c/7/G.txt +cmp actual-missing/c/8/H.txt expected/c/8/H.txt + +# path-based partial export, everything under the path specified (also without leading / in path) +mkdir actual-partial +car extract -f ${INPUTS}/simple-unixfs.car -p b actual-partial +stderr '^extracted 3 file\(s\)$' +! exists actual-partial/a/1/A.txt +! exists actual-partial/a/2/B.txt +! exists actual-partial/a/3/C.txt +cmp actual-partial/b/5/E.txt expected/b/5/E.txt +cmp actual-partial/b/6/F.txt expected/b/6/F.txt +cmp actual-partial/b/4/D.txt expected/b/4/D.txt +! exists actual-partial/c/9/I.txt +! exists actual-partial/c/7/G.txt +! exists actual-partial/c/8/H.txt + +# path-based single-file export (also with leading /) +mkdir actual-single +car extract -f ${INPUTS}/simple-unixfs.car -p /a/2/B.txt actual-single +stderr '^extracted 1 file\(s\)$' +! exists actual-single/a/1/A.txt +cmp actual-single/a/2/B.txt expected/a/2/B.txt +! exists actual-single/a/3/C.txt +! exists actual-single/b/5/E.txt +! exists actual-single/b/6/F.txt +! exists actual-single/b/4/D.txt +! exists actual-single/c/9/I.txt +! exists actual-single/c/7/G.txt +! exists actual-single/c/8/H.txt + +# car with only one file, nested inside sharded directory, output to stdout +car extract -f ${INPUTS}/wikipedia-cryptographic-hash-function.car -p wiki/Cryptographic_hash_function - +stderr '^extracted 1 file\(s\)$' +stdout -count=1 '^ Cryptographic hash function$' + +-- expected/a/1/A.txt -- +a1A +-- expected/a/2/B.txt -- +a2B +-- expected/a/3/C.txt -- +a3C +-- expected/b/5/E.txt -- +b5E +-- expected/b/6/F.txt -- +b6F +-- expected/b/4/D.txt -- +b4D +-- expected/c/9/I.txt -- +c9I +-- expected/c/7/G.txt -- +c7G +-- expected/c/8/H.txt -- +c8H \ No newline at end of file From 663fd91cd2297dfebc326bbd563440ed36c1b9f9 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 7 Mar 2023 21:04:17 +1100 Subject: [PATCH 284/291] fix: update cmd/car/README with latest description This commit was moved from ipld/go-car@5e5e40aaf3920be0359aec7680fc32d4ea9bc253 --- ipld/car/cmd/car/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipld/car/cmd/car/README.md b/ipld/car/cmd/car/README.md index 901b75cb03..995850fd73 100644 --- a/ipld/car/cmd/car/README.md +++ b/ipld/car/cmd/car/README.md @@ -14,7 +14,9 @@ USAGE: car [global options] command [command options] [arguments...] COMMANDS: + compile compile a car file from a debug patch create, c Create a car file + debug debug a car file detach-index Detach an index to a detached file extract, x Extract the contents of a car when the car encodes UnixFS data filter, f Filter the CIDs in a car From 0771beee5e0f651a6a436fd2416a2bce89fd13a2 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 7 Mar 2023 21:47:30 +1100 Subject: [PATCH 285/291] fix: make -f optional, read from stdin if omitted This commit was moved from ipld/go-car@413fe0dcde5f3cd16f0b18aa59585517e23fee59 --- ipld/car/cmd/car/car.go | 4 ++-- ipld/car/cmd/car/extract.go | 19 ++++++++++++++++++- ipld/car/cmd/car/testdata/script/extract.txt | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 7fb0a71560..d2356484cc 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -80,8 +80,8 @@ func main1() int { &cli.StringFlag{ Name: "file", Aliases: []string{"f"}, - Usage: "The car file to extract from, or '-' to read from stdin", - Required: true, + Usage: "The car file to extract from, or stdin if omitted", + Required: false, TakesFile: true, }, &cli.StringFlag{ diff --git a/ipld/car/cmd/car/extract.go b/ipld/car/cmd/car/extract.go index c742cf4c77..67c6620bd5 100644 --- a/ipld/car/cmd/car/extract.go +++ b/ipld/car/cmd/car/extract.go @@ -8,6 +8,7 @@ import ( "os" "path" "path/filepath" + "runtime" "strings" "sync" @@ -40,7 +41,23 @@ func ExtractCar(c *cli.Context) error { var store storage.ReadableStorage var roots []cid.Cid - if c.String("file") == "-" { + if c.String("file") == "" { + if f, ok := c.App.Reader.(*os.File); ok { + stat, err := f.Stat() + if err != nil { + return err + } + if (stat.Mode() & os.ModeCharDevice) != 0 { + // Is a terminal. In reality the user is unlikely to actually paste + // CAR data into this terminal, but this message may serve to make + // them aware that they can/should pipe data into this command. + stopKeys := "Ctrl+D" + if runtime.GOOS == "windows" { + stopKeys = "Ctrl+Z, Enter" + } + fmt.Fprintf(c.App.ErrWriter, "Reading from stdin; use %s to end\n", stopKeys) + } + } var err error store, roots, err = NewStdinReadStorage(c.App.Reader) if err != nil { diff --git a/ipld/car/cmd/car/testdata/script/extract.txt b/ipld/car/cmd/car/testdata/script/extract.txt index 4e5061ef45..8985790868 100644 --- a/ipld/car/cmd/car/testdata/script/extract.txt +++ b/ipld/car/cmd/car/testdata/script/extract.txt @@ -15,7 +15,7 @@ cmp actual-full/c/8/H.txt expected/c/8/H.txt # full DAG export, everything in the CAR, accepted from stdin mkdir actual-stdin stdin ${INPUTS}/simple-unixfs.car -car extract -f - actual-stdin +car extract actual-stdin stderr '^extracted 9 file\(s\)$' cmp actual-stdin/a/1/A.txt expected/a/1/A.txt cmp actual-stdin/a/2/B.txt expected/a/2/B.txt From 9127d28f3b487a82941a282c78932284621d7d96 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 7 Mar 2023 21:53:50 +1100 Subject: [PATCH 286/291] fix: error when no files extracted This commit was moved from ipld/go-car@2d5490984123d403b979b123aae7eaeabb92c1e9 --- ipld/car/cmd/car/extract.go | 2 +- ipld/car/cmd/car/testdata/script/extract.txt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ipld/car/cmd/car/extract.go b/ipld/car/cmd/car/extract.go index 67c6620bd5..d842b26a25 100644 --- a/ipld/car/cmd/car/extract.go +++ b/ipld/car/cmd/car/extract.go @@ -93,7 +93,7 @@ func ExtractCar(c *cli.Context) error { extractedFiles += count } if extractedFiles == 0 { - fmt.Fprintf(c.App.ErrWriter, "no files extracted\n") + return cli.Exit("no files extracted", 1) } else { fmt.Fprintf(c.App.ErrWriter, "extracted %d file(s)\n", extractedFiles) } diff --git a/ipld/car/cmd/car/testdata/script/extract.txt b/ipld/car/cmd/car/testdata/script/extract.txt index 8985790868..7dd25aafb1 100644 --- a/ipld/car/cmd/car/testdata/script/extract.txt +++ b/ipld/car/cmd/car/testdata/script/extract.txt @@ -72,6 +72,10 @@ cmp actual-single/a/2/B.txt expected/a/2/B.txt ! exists actual-single/c/7/G.txt ! exists actual-single/c/8/H.txt +# extract that doesn't yield any files should error +! car extract -f ${INPUTS}/simple-unixfs-missing-blocks.car -p b +stderr '^no files extracted$' + # car with only one file, nested inside sharded directory, output to stdout car extract -f ${INPUTS}/wikipedia-cryptographic-hash-function.car -p wiki/Cryptographic_hash_function - stderr '^extracted 1 file\(s\)$' From 8a1244426508c2b580cacfabf90bf52431001903 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Wed, 8 Mar 2023 15:01:31 +1100 Subject: [PATCH 287/291] fix: let `extract` skip missing unixfs shard links allows non-path "full" extraction of a unixfs CAR that doesn't have complete data Ref: https://github.com/ipfs/go-unixfsnode/pull/44 This commit was moved from ipld/go-car@22c855bbc572a02b0e6b85ac6945259ec20d43c3 --- ipld/car/cmd/car/extract.go | 11 +++++++++-- ipld/car/cmd/car/testdata/script/extract.txt | 18 +++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/ipld/car/cmd/car/extract.go b/ipld/car/cmd/car/extract.go index d842b26a25..f9373fd37b 100644 --- a/ipld/car/cmd/car/extract.go +++ b/ipld/car/cmd/car/extract.go @@ -230,7 +230,7 @@ func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, ou dest, err := ls.Load(ipld.LinkContext{}, vl, basicnode.Prototype.Any) if err != nil { if nf, ok := err.(interface{ NotFound() bool }); ok && nf.NotFound() { - fmt.Fprintf(c.App.ErrWriter, "data for entry not found: %s (skipping...)\n", name) + fmt.Fprintf(c.App.ErrWriter, "data for entry not found: %s (skipping...)\n", path.Join(outputPath, name)) return 0, nil } return 0, err @@ -305,10 +305,15 @@ func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, ou // everything var count int + var shardSkip int mi := n.MapIterator() for !mi.Done() { key, val, err := mi.Next() if err != nil { + if nf, ok := err.(interface{ NotFound() bool }); ok && nf.NotFound() { + shardSkip++ + continue + } return 0, err } ks, err := key.AsString() @@ -321,7 +326,9 @@ func extractDir(c *cli.Context, ls *ipld.LinkSystem, n ipld.Node, outputRoot, ou } count += ecount } - + if shardSkip > 0 { + fmt.Fprintf(c.App.ErrWriter, "data for entry not found for %d unknown sharded entries (skipped...)\n", shardSkip) + } return count, nil } diff --git a/ipld/car/cmd/car/testdata/script/extract.txt b/ipld/car/cmd/car/testdata/script/extract.txt index 7dd25aafb1..aa2713beb5 100644 --- a/ipld/car/cmd/car/testdata/script/extract.txt +++ b/ipld/car/cmd/car/testdata/script/extract.txt @@ -30,9 +30,9 @@ cmp actual-stdin/c/8/H.txt expected/c/8/H.txt # full DAG export, everything in the CAR, but the CAR is missing blocks (incomplete DAG) mkdir actual-missing car extract -f ${INPUTS}/simple-unixfs-missing-blocks.car actual-missing -stderr -count=1 'data for entry not found: 4 \(skipping\.\.\.\)' -stderr -count=1 'data for entry not found: E.txt \(skipping\.\.\.\)' -stderr -count=1 'data for entry not found: 6 \(skipping\.\.\.\)' +stderr -count=1 'data for entry not found: /b/4 \(skipping\.\.\.\)' +stderr -count=1 'data for entry not found: /b/5/E.txt \(skipping\.\.\.\)' +stderr -count=1 'data for entry not found: /b/6 \(skipping\.\.\.\)' stderr -count=1 '^extracted 6 file\(s\)$' cmp actual-missing/a/1/A.txt expected/a/1/A.txt cmp actual-missing/a/2/B.txt expected/a/2/B.txt @@ -81,6 +81,18 @@ car extract -f ${INPUTS}/wikipedia-cryptographic-hash-function.car -p wiki/Crypt stderr '^extracted 1 file\(s\)$' stdout -count=1 '^ Cryptographic hash function$' +# car with only one file, full extract, lots of errors +mkdir actual-wiki +car extract -f ${INPUTS}/wikipedia-cryptographic-hash-function.car actual-wiki +stderr '^extracted 1 file\(s\)$' +stderr -count=1 '^data for entry not found for 570 unknown sharded entries \(skipped\.\.\.\)$' +# random sampling of expected skip errors +stderr -count=1 '^data for entry not found: /wiki/1969_Men''s_World_Ice_Hockey_Championships \(skipping\.\.\.\)$' +stderr -count=1 '^data for entry not found: /wiki/Wrestle_mania_30 \(skipping\.\.\.\)$' +stderr -count=1 '^data for entry not found: /zimdump_version \(skipping\.\.\.\)$' +stderr -count=1 '^data for entry not found: /favicon.ico \(skipping\.\.\.\)$' +stderr -count=1 '^data for entry not found: /index.html \(skipping\.\.\.\)$' + -- expected/a/1/A.txt -- a1A -- expected/a/2/B.txt -- From 040544995144393afef587c485ddcf9125525b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 14 Mar 2023 12:57:44 +0100 Subject: [PATCH 288/291] ReadWrite: faster Has() by using the in-memory index instead of reading on disk Before: // BenchmarkHas-8 190216 6368 ns/op 744 B/op 16 allocs/op After // BenchmarkHas-8 1419169 845.6 ns/op 320 B/op 6 allocs/op ``` func BenchmarkHas(b *testing.B) { ctx := context.TODO() path := filepath.Join(b.TempDir(), "bench-large-v2.car") generateRandomCarV2File(b, path, 200<<20) // 10 MiB defer os.Remove(path) subject, err := blockstore.OpenReadWrite(path, nil) c, err := subject.AllKeysChan(ctx) require.NoError(b, err) var allCids []cid.Cid for c2 := range c { allCids = append(allCids, c2) } b.ReportAllocs() b.ResetTimer() var idx int for i := 0; i < b.N; i++ { _, _ = subject.Has(ctx, allCids[idx]) // require.NoError(b, err) // require.True(b, has) idx = (idx + 1) % len(allCids) } } ``` This commit was moved from ipld/go-car@d51b4a127544b128806aea86062f4bc7bc7b3776 --- ipld/car/v2/blockstore/readwrite.go | 24 ++++++++++++++- ipld/car/v2/index/index.go | 6 ++-- ipld/car/v2/internal/store/insertionindex.go | 31 ++++++++++++++++++-- ipld/car/v2/internal/store/put.go | 8 +++-- 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index e605414f27..5ba24f9c53 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -344,7 +344,29 @@ func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { } func (b *ReadWrite) Has(ctx context.Context, key cid.Cid) (bool, error) { - return b.ronly.Has(ctx, key) + if !b.opts.StoreIdentityCIDs { + // If we don't store identity CIDs then we can return them straight away as if they are here, + // otherwise we need to check for their existence. + // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. + if _, ok, err := store.IsIdentity(key); err != nil { + return false, err + } else if ok { + return true, nil + } + } + + if ctx.Err() != nil { + return false, ctx.Err() + } + + b.ronly.mu.Lock() + defer b.ronly.mu.Unlock() + + if b.ronly.closed { + return false, errClosed + } + + return b.idx.HasMultihash(key.Hash()) } func (b *ReadWrite) Get(ctx context.Context, key cid.Cid) (blocks.Block, error) { diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 911eaa7ae1..1634dda5d5 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -13,7 +13,7 @@ import ( "github.com/multiformats/go-varint" ) -// CarIndexNone is a sentinal value used as a multicodec code for the index indicating no index. +// CarIndexNone is a sentinel value used as a multicodec code for the index indicating no index. const CarIndexNone = 0x300000 type ( @@ -46,7 +46,7 @@ type ( // Unmarshal decodes the index from its serial form. // Note, this function will copy the entire index into memory. // - // Do not unmarshal index from untrusted CARv2 files. Instead the index should be + // Do not unmarshal index from untrusted CARv2 files. Instead, the index should be // regenerated from the CARv2 data payload. Unmarshal(r io.Reader) error @@ -84,7 +84,7 @@ type ( // and the ForEach function returns the error to the user. // // An index may contain multiple offsets corresponding to the same multihash, e.g. via duplicate blocks. - // In such cases, the given function may be called multiple times with the same multhihash but different offset. + // In such cases, the given function may be called multiple times with the same multihash but different offset. // // The order of calls to the given function is deterministic, but entirely index-specific. ForEach(func(multihash.Multihash, uint64) error) error diff --git a/ipld/car/v2/internal/store/insertionindex.go b/ipld/car/v2/internal/store/insertionindex.go index ffdf96fa49..bc25fffbcf 100644 --- a/ipld/car/v2/internal/store/insertionindex.go +++ b/ipld/car/v2/internal/store/insertionindex.go @@ -212,10 +212,10 @@ func (ii *InsertionIndex) Flatten(codec multicodec.Code) (index.Index, error) { // but it's separate as it allows us to compare Record.Cid directly, // whereas GetAll just provides Record.Offset. -func (ii *InsertionIndex) HasExactCID(c cid.Cid) bool { +func (ii *InsertionIndex) HasExactCID(c cid.Cid) (bool, error) { d, err := multihash.Decode(c.Hash()) if err != nil { - panic(err) + return false, err } entry := recordDigest{digest: d.Digest} @@ -235,5 +235,30 @@ func (ii *InsertionIndex) HasExactCID(c cid.Cid) bool { return true } ii.items.AscendGreaterOrEqual(entry, iter) - return found + return found, nil +} + +func (ii *InsertionIndex) HasMultihash(mh multihash.Multihash) (bool, error) { + d, err := multihash.Decode(mh) + if err != nil { + return false, err + } + entry := recordDigest{digest: d.Digest} + + found := false + iter := func(i llrb.Item) bool { + existing := i.(recordDigest) + if !bytes.Equal(existing.digest, entry.digest) { + // We've already looked at all entries with matching digests. + return false + } + if bytes.Equal(existing.Record.Cid.Hash(), mh) { + found = true + return false + } + // Continue looking in ascending order. + return true + } + ii.items.AscendGreaterOrEqual(entry, iter) + return found, nil } diff --git a/ipld/car/v2/internal/store/put.go b/ipld/car/v2/internal/store/put.go index f82d0c1ea9..2a74dd0d00 100644 --- a/ipld/car/v2/internal/store/put.go +++ b/ipld/car/v2/internal/store/put.go @@ -38,8 +38,12 @@ func ShouldPut( } if !blockstoreAllowDuplicatePuts { - if blockstoreUseWholeCIDs && idx.HasExactCID(c) { - return false, nil // deduplicated by CID + if blockstoreUseWholeCIDs { + has, err := idx.HasExactCID(c) + if err != nil { + return false, err + } + return !has, nil // deduplicated by CID } if !blockstoreUseWholeCIDs { _, err := idx.Get(c) From ca32fefcd1a543897aeeeda7eac6f759ac3429ec Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 17 Mar 2023 17:34:41 +1100 Subject: [PATCH 289/291] fix: handle (and test) WholeCID vs not; fast Has() path for storage This commit was moved from ipld/go-car@c58b233d27e147e4adefa0388a8190102d4e8bf8 --- ipld/car/v2/blockstore/readwrite.go | 24 +++----- ipld/car/v2/blockstore/readwrite_test.go | 50 +++++++++++++++++ .../internal/store/{put.go => indexcheck.go} | 27 +++++++++ ipld/car/v2/storage/storage.go | 26 ++++++--- ipld/car/v2/storage/storage_test.go | 55 +++++++++++++++++++ 5 files changed, 159 insertions(+), 23 deletions(-) rename ipld/car/v2/internal/store/{put.go => indexcheck.go} (70%) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 5ba24f9c53..85c564978a 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -344,21 +344,6 @@ func (b *ReadWrite) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { } func (b *ReadWrite) Has(ctx context.Context, key cid.Cid) (bool, error) { - if !b.opts.StoreIdentityCIDs { - // If we don't store identity CIDs then we can return them straight away as if they are here, - // otherwise we need to check for their existence. - // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. - if _, ok, err := store.IsIdentity(key); err != nil { - return false, err - } else if ok { - return true, nil - } - } - - if ctx.Err() != nil { - return false, ctx.Err() - } - b.ronly.mu.Lock() defer b.ronly.mu.Unlock() @@ -366,7 +351,14 @@ func (b *ReadWrite) Has(ctx context.Context, key cid.Cid) (bool, error) { return false, errClosed } - return b.idx.HasMultihash(key.Hash()) + return store.Has( + b.idx, + key, + b.opts.MaxIndexCidSize, + b.opts.StoreIdentityCIDs, + b.opts.BlockstoreAllowDuplicatePuts, + b.opts.BlockstoreUseWholeCIDs, + ) } func (b *ReadWrite) Get(ctx context.Context, key cid.Cid) (blocks.Block, error) { diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 45fcb21ac9..5766c2d3bd 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -1085,3 +1085,53 @@ func TestBlockstoreFinalizeReadOnly(t *testing.T) { err = bs.Put(ctx, root) require.Error(t, err) } + +func TestWholeCID(t *testing.T) { + for _, whole := range []bool{true, false} { + whole := whole + t.Run(fmt.Sprintf("whole=%t", whole), func(t *testing.T) { + t.Parallel() + ctx := context.Background() + path := filepath.Join(t.TempDir(), fmt.Sprintf("writable_%t.car", whole)) + rw, err := blockstore.OpenReadWrite(path, []cid.Cid{}, carv2.UseWholeCIDs(whole)) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, rw.Finalize()) }) + + require.NoError(t, rw.Put(ctx, oneTestBlockWithCidV1)) + has, err := rw.Has(ctx, oneTestBlockWithCidV1.Cid()) + require.NoError(t, err) + require.True(t, has) + + pref := oneTestBlockWithCidV1.Cid().Prefix() + pref.Codec = cid.DagCBOR + pref.Version = 1 + cpb1, err := pref.Sum(oneTestBlockWithCidV1.RawData()) + require.NoError(t, err) + + has, err = rw.Has(ctx, cpb1) + require.NoError(t, err) + require.Equal(t, has, !whole) + + require.NoError(t, rw.Put(ctx, anotherTestBlockWithCidV0)) + has, err = rw.Has(ctx, anotherTestBlockWithCidV0.Cid()) + require.NoError(t, err) + require.True(t, has) + has, err = rw.Has(ctx, cpb1) + require.NoError(t, err) + require.Equal(t, has, !whole) + + pref = anotherTestBlockWithCidV0.Cid().Prefix() + pref.Codec = cid.DagJSON + pref.Version = 1 + cpb2, err := pref.Sum(anotherTestBlockWithCidV0.RawData()) + require.NoError(t, err) + + has, err = rw.Has(ctx, cpb2) + require.NoError(t, err) + require.Equal(t, has, !whole) + has, err = rw.Has(ctx, cpb1) + require.NoError(t, err) + require.Equal(t, has, !whole) + }) + } +} diff --git a/ipld/car/v2/internal/store/put.go b/ipld/car/v2/internal/store/indexcheck.go similarity index 70% rename from ipld/car/v2/internal/store/put.go rename to ipld/car/v2/internal/store/indexcheck.go index 2a74dd0d00..04b673e05c 100644 --- a/ipld/car/v2/internal/store/put.go +++ b/ipld/car/v2/internal/store/indexcheck.go @@ -55,3 +55,30 @@ func ShouldPut( return true, nil } + +// Has returns true if the block exists in in the store according to the various +// rules associated with the options. Similar to ShouldPut, but for the simpler +// Has() case. +func Has( + idx *InsertionIndex, + c cid.Cid, + maxIndexCidSize uint64, + storeIdentityCIDs bool, + blockstoreAllowDuplicatePuts bool, + blockstoreUseWholeCIDs bool, +) (bool, error) { + + // If StoreIdentityCIDs option is disabled then treat IDENTITY CIDs like IdStore. + if !storeIdentityCIDs { + if _, ok, err := IsIdentity(c); err != nil { + return false, err + } else if ok { + return true, nil + } + } + + if blockstoreUseWholeCIDs { + return idx.HasExactCID(c) + } + return idx.HasMultihash(c.Hash()) +} diff --git a/ipld/car/v2/storage/storage.go b/ipld/car/v2/storage/storage.go index a6f92ae22b..1a8b499b9f 100644 --- a/ipld/car/v2/storage/storage.go +++ b/ipld/car/v2/storage/storage.go @@ -349,6 +349,25 @@ func (sc *StorageCar) Has(ctx context.Context, keyStr string) (bool, error) { return false, fmt.Errorf("bad CID key: %w", err) } + sc.mu.RLock() + defer sc.mu.RUnlock() + + if sc.closed { + return false, errClosed + } + + if idx, ok := sc.idx.(*store.InsertionIndex); ok && sc.writer != nil { + // writable CAR, fast path using InsertionIndex + return store.Has( + idx, + keyCid, + sc.opts.MaxIndexCidSize, + sc.opts.StoreIdentityCIDs, + sc.opts.BlockstoreAllowDuplicatePuts, + sc.opts.BlockstoreUseWholeCIDs, + ) + } + if !sc.opts.StoreIdentityCIDs { // If we don't store identity CIDs then we can return them straight away as if they are here, // otherwise we need to check for their existence. @@ -360,13 +379,6 @@ func (sc *StorageCar) Has(ctx context.Context, keyStr string) (bool, error) { } } - sc.mu.RLock() - defer sc.mu.RUnlock() - - if sc.closed { - return false, errClosed - } - _, _, size, err := store.FindCid( sc.reader, sc.idx, diff --git a/ipld/car/v2/storage/storage_test.go b/ipld/car/v2/storage/storage_test.go index ea00f1d5ec..12b9ebce32 100644 --- a/ipld/car/v2/storage/storage_test.go +++ b/ipld/car/v2/storage/storage_test.go @@ -1156,6 +1156,61 @@ func TestOperationsErrorWithBadCidStrings(t *testing.T) { require.Nil(t, got) } +func TestWholeCID(t *testing.T) { + for _, whole := range []bool{true, false} { + whole := whole + t.Run(fmt.Sprintf("whole=%t", whole), func(t *testing.T) { + t.Parallel() + ctx := context.Background() + path := filepath.Join(t.TempDir(), fmt.Sprintf("writable_%t.car", whole)) + out, err := os.Create(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, out.Close()) }) + store, err := storage.NewReadableWritable(out, []cid.Cid{}, carv2.UseWholeCIDs(whole)) + require.NoError(t, err) + + c1, b1 := randBlock() + c2, b2 := randBlock() + + require.NoError(t, store.Put(ctx, c1.KeyString(), b1)) + has, err := store.Has(ctx, c1.KeyString()) + require.NoError(t, err) + require.True(t, has) + + pref := c1.Prefix() + pref.Codec = cid.DagProtobuf + pref.Version = 1 + cpb1, err := pref.Sum(b1) + require.NoError(t, err) + + has, err = store.Has(ctx, cpb1.KeyString()) + require.NoError(t, err) + require.Equal(t, has, !whole) + + require.NoError(t, store.Put(ctx, c2.KeyString(), b1)) + has, err = store.Has(ctx, c2.KeyString()) + require.NoError(t, err) + require.True(t, has) + has, err = store.Has(ctx, cpb1.KeyString()) + require.NoError(t, err) + require.Equal(t, has, !whole) + + pref = c2.Prefix() + pref.Codec = cid.DagProtobuf + pref.Version = 1 + cpb2, err := pref.Sum(b2) + require.NoError(t, err) + + has, err = store.Has(ctx, cpb2.KeyString()) + require.NoError(t, err) + require.Equal(t, has, !whole) + has, err = store.Has(ctx, cpb1.KeyString()) + require.NoError(t, err) + require.Equal(t, has, !whole) + }) + } +} + type writerOnly struct { io.Writer } From 4cb37910dd3f937456ec8c521831583eefc45ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Thu, 16 Mar 2023 16:20:04 +0100 Subject: [PATCH 290/291] blockstore: only close the file on error in OpenReadWrite, not OpenReadWriteFile OpenReadWriteFile gives full control of the file to the caller, so it should not decide to close the file, even on error. This commit was moved from ipld/go-car@794f22231c0526e1123ee2a11bbc1567f9a011ae --- ipld/car/v2/blockstore/readwrite.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 85c564978a..0f9cd4caed 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -86,6 +86,12 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.Option) (*ReadWri if err != nil { return nil, fmt.Errorf("could not open read/write file: %w", err) } + // If construction of blockstore fails, make sure to close off the open file. + defer func() { + if err != nil { + f.Close() + } + }() rwbs, err := OpenReadWriteFile(f, roots, opts...) if err != nil { return nil, err @@ -100,17 +106,11 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.Option) (*ReadWri func OpenReadWriteFile(f *os.File, roots []cid.Cid, opts ...carv2.Option) (*ReadWrite, error) { stat, err := f.Stat() if err != nil { - // Note, we should not get a an os.ErrNotExist here because the flags used to open file includes os.O_CREATE + // Note, we should not get an os.ErrNotExist here because the flags used to open file includes os.O_CREATE return nil, err } // Try and resume by default if the file size is non-zero. resume := stat.Size() != 0 - // If construction of blockstore fails, make sure to close off the open file. - defer func() { - if err != nil { - f.Close() - } - }() // Instantiate block store. // Set the header fileld before applying options since padding options may modify header. From c270c83fbd8be200d709fa4b7cc71d7a0ff31e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Fri, 10 Mar 2023 18:13:35 +0100 Subject: [PATCH 291/291] blockstore: give a direct access to the index for read operations This commit was moved from ipld/go-car@2f5967471b1b5b74ccb07b4283b95a241b717b13 --- ipld/car/v2/blockstore/readonly.go | 6 +++ ipld/car/v2/blockstore/readonly_test.go | 67 +++++++++++++++++++++++- ipld/car/v2/blockstore/readwrite.go | 7 +++ ipld/car/v2/blockstore/readwrite_test.go | 48 +++++++++++++++-- ipld/car/v2/index/index.go | 2 +- 5 files changed, 123 insertions(+), 7 deletions(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index c59bdcf685..a102b20c4d 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -179,6 +179,12 @@ func OpenReadOnly(path string, opts ...carv2.Option) (*ReadOnly, error) { return robs, nil } +// Index gives direct access to the index. +// You should never add records on your own there. +func (b *ReadOnly) Index() index.Index { + return b.idx +} + // DeleteBlock is unsupported and always errors. func (b *ReadOnly) DeleteBlock(_ context.Context, _ cid.Cid) error { return errReadOnly diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 0bb4df42ea..f2bc7f1051 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -12,10 +12,14 @@ import ( format "github.com/ipfs/go-ipld-format" blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-merkledag" - carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/internal/carv1" "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" + + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/ipld/go-car/v2/internal/store" ) func TestReadOnlyGetReturnsBlockstoreNotFoundWhenCidDoesNotExist(t *testing.T) { @@ -331,3 +335,62 @@ func TestNewReadOnly_CarV1WithoutIndexWorksAsExpected(t *testing.T) { require.NoError(t, err) require.Equal(t, wantBlock, gotBlock) } + +func TestReadOnlyIndex(t *testing.T) { + tests := []struct { + name string + path string + wantCIDs []cid.Cid + }{ + { + "IndexCarV1", + "../testdata/sample-v1.car", + listCids(t, newV1ReaderFromV1File(t, "../testdata/sample-v1.car", false)), + }, + { + "IndexCarV2", + "../testdata/sample-wrapped-v2.car", + listCids(t, newV1ReaderFromV2File(t, "../testdata/sample-wrapped-v2.car", false)), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + subject, err := OpenReadOnly(tt.path, UseWholeCIDs(true)) + require.NoError(t, err) + + idx := subject.Index() + + for _, c := range tt.wantCIDs { + _, isIdentity, err := store.IsIdentity(c) + require.NoError(t, err) + if isIdentity { + // the index doesn't hold identity CIDs + continue + } + _, err = index.GetFirst(idx, c) + require.NoError(t, err) + } + + if idx, ok := idx.(index.IterableIndex); ok { + expected := make([]multihash.Multihash, 0, len(tt.wantCIDs)) + for _, c := range tt.wantCIDs { + _, isIdentity, err := store.IsIdentity(c) + require.NoError(t, err) + if isIdentity { + // the index doesn't hold identity CIDs + continue + } + expected = append(expected, c.Hash()) + } + + var got []multihash.Multihash + err = idx.ForEach(func(m multihash.Multihash, u uint64) error { + got = append(got, m) + return nil + }) + require.NoError(t, err) + require.ElementsMatch(t, expected, got) + } + }) + } +} diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 0f9cd4caed..e33f13104d 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -10,6 +10,7 @@ import ( blocks "github.com/ipfs/go-libipfs/blocks" carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" @@ -178,6 +179,12 @@ func (b *ReadWrite) initWithRoots(v2 bool, roots []cid.Cid) error { return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, b.dataWriter) } +// Index gives direct access to the index. +// You should never add records on your own there. +func (b *ReadWrite) Index() index.Index { + return b.idx +} + // Put puts a given block to the underlying datastore func (b *ReadWrite) Put(ctx context.Context, blk blocks.Block) error { // PutMany already checks b.ronly.closed. diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 5766c2d3bd..40731e5489 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -18,14 +18,15 @@ import ( format "github.com/ipfs/go-ipld-format" blocks "github.com/ipfs/go-libipfs/blocks" "github.com/ipfs/go-merkledag" - carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/blockstore" - "github.com/ipld/go-car/v2/index" - "github.com/ipld/go-car/v2/internal/carv1" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-multihash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1" ) var ( @@ -1135,3 +1136,42 @@ func TestWholeCID(t *testing.T) { }) } } + +func TestReadWriteIndex(t *testing.T) { + tmpPath := requireTmpCopy(t, "../testdata/sample-wrapped-v2.car") + + root := cid.MustParse("bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy") + subject, err := blockstore.OpenReadWrite(tmpPath, []cid.Cid{root}) + require.NoError(t, err) + + defer func() { + err = subject.Finalize() + require.NoError(t, err) + }() + + var wantCids []cid.Cid + var wantMh []multihash.Multihash + ch, err := subject.AllKeysChan(context.Background()) + require.NoError(t, err) + for c := range ch { + wantCids = append(wantCids, c) + wantMh = append(wantMh, c.Hash()) + } + + idx := subject.Index() + + for _, c := range wantCids { + _, err = index.GetFirst(idx, c) + require.NoError(t, err) + } + + if idx, ok := idx.(index.IterableIndex); ok { + var got []multihash.Multihash + err = idx.ForEach(func(m multihash.Multihash, u uint64) error { + got = append(got, m) + return nil + }) + require.NoError(t, err) + require.ElementsMatch(t, wantMh, got) + } +} diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 1634dda5d5..af16fcc1e1 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -134,7 +134,7 @@ func WriteTo(idx Index, w io.Writer) (uint64, error) { // Returns error if the encoding is not known. // // Attempting to read index data from untrusted sources is not recommended. -// Instead the index should be regenerated from the CARv2 data payload. +// Instead, the index should be regenerated from the CARv2 data payload. func ReadFrom(r io.Reader) (Index, error) { codec, err := ReadCodec(r) if err != nil {

    oI>s!@CYwgFh=$ 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 Date: Wed, 1 Sep 2021 14:21:41 +0100 Subject: [PATCH 154/291] Implement new index type that also includes mutltihash code Implement a new CARv2 index that contains enough information to reconstruct the multihashes of the data payload, since `CarIndexSorted` only includes multihash digests. The new index builds on top of the existing `IndexSorted` by adding an additional layer of grouping the multi-width indices by their multihash code. Note, this index intentionally ignores any given record with `multihash.IDENTITY` CID hash. Add a test that asserts offsets for the same CID across sorted index and new multihash sorted index are consistent. Add tests that assert marshal unmarshalling of the new index type is as expected, and it does not load records with `multihash.IDENTITY` digest. Relates to: - https://github.com/multiformats/multicodec/pull/227 Fixes: - https://github.com/ipld/go-car/issues/214 This commit was moved from ipld/go-car@42b9e28c3297412b78511f76c003ddd4d682fcc4 --- ipld/car/v2/index/index.go | 2 + ipld/car/v2/index/mhindexsorted.go | 158 ++++++++++++++++++++++++ ipld/car/v2/index/mhindexsorted_test.go | 107 ++++++++++++++++ ipld/car/v2/index_gen_test.go | 128 ++++++++++++++++--- 4 files changed, 380 insertions(+), 15 deletions(-) create mode 100644 ipld/car/v2/index/mhindexsorted.go create mode 100644 ipld/car/v2/index/mhindexsorted_test.go diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 3408dfbd28..cc9ff70bca 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -72,6 +72,8 @@ func New(codec multicodec.Code) (Index, error) { switch codec { case multicodec.CarIndexSorted: return newSorted(), nil + case multicodec.CarMultihashIndexSorted: + return newMultihashSorted(), nil default: return nil, fmt.Errorf("unknwon index codec: %v", codec) } diff --git a/ipld/car/v2/index/mhindexsorted.go b/ipld/car/v2/index/mhindexsorted.go new file mode 100644 index 0000000000..95ab64743e --- /dev/null +++ b/ipld/car/v2/index/mhindexsorted.go @@ -0,0 +1,158 @@ +package index + +import ( + "encoding/binary" + "io" + "sort" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-multihash" +) + +type ( + // multihashIndexSorted maps multihash code (i.e. hashing algorithm) to multiWidthCodedIndex. + // This index ignores any Record with multihash.IDENTITY. + multihashIndexSorted map[uint64]*multiWidthCodedIndex + // multiWidthCodedIndex stores multihash code for each multiWidthIndex. + multiWidthCodedIndex struct { + multiWidthIndex + code uint64 + } +) + +func newMultiWidthCodedIndex() *multiWidthCodedIndex { + return &multiWidthCodedIndex{ + multiWidthIndex: make(multiWidthIndex), + } +} + +func (m *multiWidthCodedIndex) Marshal(w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, m.code); err != nil { + return err + } + return m.multiWidthIndex.Marshal(w) +} + +func (m *multiWidthCodedIndex) Unmarshal(r io.Reader) error { + if err := binary.Read(r, binary.LittleEndian, &m.code); err != nil { + return err + } + return m.multiWidthIndex.Unmarshal(r) +} + +func (m *multihashIndexSorted) Codec() multicodec.Code { + return multicodec.CarMultihashIndexSorted +} + +func (m *multihashIndexSorted) Marshal(w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, int32(len(*m))); err != nil { + return err + } + // The codes are unique, but ranging over a map isn't deterministic. + // As per the CARv2 spec, we must order buckets by digest length. + // TODO update CARv2 spec to reflect this for the new index type. + codes := m.sortedMultihashCodes() + + for _, code := range codes { + mwci := (*m)[code] + if err := mwci.Marshal(w); err != nil { + return err + } + } + return nil +} + +func (m *multihashIndexSorted) sortedMultihashCodes() []uint64 { + codes := make([]uint64, 0, len(*m)) + for code := range *m { + codes = append(codes, code) + } + sort.Slice(codes, func(i, j int) bool { + return codes[i] < codes[j] + }) + return codes +} + +func (m *multihashIndexSorted) Unmarshal(r io.Reader) error { + var l int32 + if err := binary.Read(r, binary.LittleEndian, &l); err != nil { + return err + } + for i := 0; i < int(l); i++ { + mwci := newMultiWidthCodedIndex() + if err := mwci.Unmarshal(r); err != nil { + return err + } + m.put(mwci) + } + return nil +} + +func (m *multihashIndexSorted) put(mwci *multiWidthCodedIndex) { + (*m)[mwci.code] = mwci +} + +func (m *multihashIndexSorted) Load(records []Record) error { + // TODO optimize load by avoiding multihash decoding twice. + // This implementation decodes multihashes twice: once here to group by code, and once in the + // internals of multiWidthIndex to group by digest length. The code can be optimized by + // combining the grouping logic into one step where the multihash of a CID is only decoded once. + // The optimization would need refactoring of the IndexSorted compaction logic. + + // Group records by multihash code + byCode := make(map[uint64][]Record) + for _, record := range records { + dmh, err := multihash.Decode(record.Hash()) + if err != nil { + return err + } + code := dmh.Code + // Ignore IDENTITY multihash in the index. + if code == multihash.IDENTITY { + continue + } + recsByCode, ok := byCode[code] + if !ok { + recsByCode = make([]Record, 0) + byCode[code] = recsByCode + } + byCode[code] = append(recsByCode, record) + } + + // Load each record group. + for code, recsByCode := range byCode { + mwci := newMultiWidthCodedIndex() + mwci.code = code + if err := mwci.Load(recsByCode); err != nil { + return err + } + m.put(mwci) + } + return nil +} + +func (m *multihashIndexSorted) GetAll(cid cid.Cid, f func(uint64) bool) error { + hash := cid.Hash() + dmh, err := multihash.Decode(hash) + if err != nil { + return err + } + mwci, err := m.get(dmh) + if err != nil { + return err + } + return mwci.GetAll(cid, f) +} + +func (m *multihashIndexSorted) get(dmh *multihash.DecodedMultihash) (*multiWidthCodedIndex, error) { + if codedIdx, ok := (*m)[dmh.Code]; ok { + return codedIdx, nil + } + return nil, ErrNotFound +} + +func newMultihashSorted() Index { + index := make(multihashIndexSorted) + return &index +} diff --git a/ipld/car/v2/index/mhindexsorted_test.go b/ipld/car/v2/index/mhindexsorted_test.go new file mode 100644 index 0000000000..ced8a921a1 --- /dev/null +++ b/ipld/car/v2/index/mhindexsorted_test.go @@ -0,0 +1,107 @@ +package index_test + +import ( + "bytes" + "fmt" + "math/rand" + "testing" + + "github.com/multiformats/go-multicodec" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2/index" + "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" +) + +func TestMutilhashSortedIndex_Codec(t *testing.T) { + subject, err := index.New(multicodec.CarMultihashIndexSorted) + require.NoError(t, err) + require.Equal(t, multicodec.CarMultihashIndexSorted, subject.Codec()) +} + +func TestMultiWidthCodedIndex_LoadDoesNotLoadIdentityMultihash(t *testing.T) { + rng := rand.New(rand.NewSource(1413)) + identityRecords := generateIndexRecords(t, multihash.IDENTITY, rng) + nonIdentityRecords := generateIndexRecords(t, multihash.SHA2_256, rng) + records := append(identityRecords, nonIdentityRecords...) + + subject, err := index.New(multicodec.CarMultihashIndexSorted) + require.NoError(t, err) + err = subject.Load(records) + require.NoError(t, err) + + // Assert index does not contain any records with IDENTITY multihash code. + for _, r := range identityRecords { + wantCid := r.Cid + err = subject.GetAll(wantCid, func(o uint64) bool { + require.Fail(t, "subject should not contain any records with IDENTITY multihash code") + return false + }) + require.Equal(t, index.ErrNotFound, err) + } + + // Assert however, index does contain the non IDENTITY records. + requireContainsAll(t, subject, nonIdentityRecords) +} + +func TestMultiWidthCodedIndex_MarshalUnmarshal(t *testing.T) { + rng := rand.New(rand.NewSource(1413)) + records := generateIndexRecords(t, multihash.SHA2_256, rng) + + // Create a new mh sorted index and load randomly generated records into it. + subject, err := index.New(multicodec.CarMultihashIndexSorted) + require.NoError(t, err) + err = subject.Load(records) + require.NoError(t, err) + + // Marshal the index. + buf := new(bytes.Buffer) + err = subject.Marshal(buf) + require.NoError(t, err) + + // Unmarshal it back to another instance of mh sorted index. + umSubject, err := index.New(multicodec.CarMultihashIndexSorted) + require.NoError(t, err) + err = umSubject.Unmarshal(buf) + require.NoError(t, err) + + // Assert original records are present in both index instances with expected offset. + requireContainsAll(t, subject, records) + requireContainsAll(t, umSubject, records) +} + +func generateIndexRecords(t *testing.T, hasherCode uint64, rng *rand.Rand) []index.Record { + var records []index.Record + recordCount := rng.Intn(99) + 1 // Up to 100 records + for i := 0; i < recordCount; i++ { + records = append(records, index.Record{ + Cid: generateCidV1(t, hasherCode, rng), + Offset: rng.Uint64(), + }) + } + return records +} + +func generateCidV1(t *testing.T, hasherCode uint64, rng *rand.Rand) cid.Cid { + data := []byte(fmt.Sprintf("🌊d-%d", rng.Uint64())) + mh, err := multihash.Sum(data, hasherCode, -1) + require.NoError(t, err) + return cid.NewCidV1(cid.Raw, mh) +} + +func requireContainsAll(t *testing.T, subject index.Index, nonIdentityRecords []index.Record) { + for _, r := range nonIdentityRecords { + wantCid := r.Cid + wantOffset := r.Offset + + var gotOffsets []uint64 + err := subject.GetAll(wantCid, func(o uint64) bool { + gotOffsets = append(gotOffsets, o) + return false + }) + require.NoError(t, err) + require.Equal(t, 1, len(gotOffsets)) + require.Equal(t, wantOffset, gotOffsets[0]) + } +} diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 058d07e6e0..635a07cb2d 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -1,9 +1,19 @@ -package car +package car_test import ( + "io" "os" "testing" + "github.com/multiformats/go-multihash" + + "github.com/ipfs/go-cid" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/internal/carv1" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-varint" + "github.com/ipld/go-car/v2/index" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -13,19 +23,19 @@ func TestReadOrGenerateIndex(t *testing.T) { tests := []struct { name string carPath string - readOpts []ReadOption + readOpts []carv2.ReadOption wantIndexer func(t *testing.T) index.Index wantErr bool }{ { "CarV1IsIndexedAsExpected", "testdata/sample-v1.car", - []ReadOption{}, + []carv2.ReadOption{}, func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1.car") require.NoError(t, err) defer v1.Close() - want, err := GenerateIndex(v1) + want, err := carv2.GenerateIndex(v1) require.NoError(t, err) return want }, @@ -34,12 +44,12 @@ func TestReadOrGenerateIndex(t *testing.T) { { "CarV2WithIndexIsReturnedAsExpected", "testdata/sample-wrapped-v2.car", - []ReadOption{}, + []carv2.ReadOption{}, func(t *testing.T) index.Index { v2, err := os.Open("testdata/sample-wrapped-v2.car") require.NoError(t, err) defer v2.Close() - reader, err := NewReader(v2) + reader, err := carv2.NewReader(v2) require.NoError(t, err) want, err := index.ReadFrom(reader.IndexReader()) require.NoError(t, err) @@ -50,12 +60,12 @@ func TestReadOrGenerateIndex(t *testing.T) { { "CarV1WithZeroLenSectionIsGeneratedAsExpected", "testdata/sample-v1-with-zero-len-section.car", - []ReadOption{ZeroLengthSectionAsEOF(true)}, + []carv2.ReadOption{carv2.ZeroLengthSectionAsEOF(true)}, func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1-with-zero-len-section.car") require.NoError(t, err) defer v1.Close() - want, err := GenerateIndex(v1, ZeroLengthSectionAsEOF(true)) + want, err := carv2.GenerateIndex(v1, carv2.ZeroLengthSectionAsEOF(true)) require.NoError(t, err) return want }, @@ -64,12 +74,12 @@ func TestReadOrGenerateIndex(t *testing.T) { { "AnotherCarV1WithZeroLenSectionIsGeneratedAsExpected", "testdata/sample-v1-with-zero-len-section2.car", - []ReadOption{ZeroLengthSectionAsEOF(true)}, + []carv2.ReadOption{carv2.ZeroLengthSectionAsEOF(true)}, func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1-with-zero-len-section2.car") require.NoError(t, err) defer v1.Close() - want, err := GenerateIndex(v1, ZeroLengthSectionAsEOF(true)) + want, err := carv2.GenerateIndex(v1, carv2.ZeroLengthSectionAsEOF(true)) require.NoError(t, err) return want }, @@ -78,14 +88,14 @@ func TestReadOrGenerateIndex(t *testing.T) { { "CarV1WithZeroLenSectionWithoutOptionIsError", "testdata/sample-v1-with-zero-len-section.car", - []ReadOption{}, + []carv2.ReadOption{}, func(t *testing.T) index.Index { return nil }, true, }, { "CarOtherThanV1OrV2IsError", "testdata/sample-rootless-v42.car", - []ReadOption{}, + []carv2.ReadOption{}, func(t *testing.T) index.Index { return nil }, true, }, @@ -95,7 +105,7 @@ func TestReadOrGenerateIndex(t *testing.T) { carFile, err := os.Open(tt.carPath) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, carFile.Close()) }) - got, err := ReadOrGenerateIndex(carFile, tt.readOpts...) + got, err := carv2.ReadOrGenerateIndex(carFile, tt.readOpts...) if tt.wantErr { require.Error(t, err) } else { @@ -121,7 +131,7 @@ func TestGenerateIndexFromFile(t *testing.T) { v1, err := os.Open("testdata/sample-v1.car") require.NoError(t, err) defer v1.Close() - want, err := GenerateIndex(v1) + want, err := carv2.GenerateIndex(v1) require.NoError(t, err) return want }, @@ -142,7 +152,7 @@ func TestGenerateIndexFromFile(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := GenerateIndexFromFile(tt.carPath) + got, err := carv2.GenerateIndexFromFile(tt.carPath) if tt.wantErr { require.Error(t, err) } else { @@ -153,3 +163,91 @@ func TestGenerateIndexFromFile(t *testing.T) { }) } } + +func TestMultihashIndexSortedConsistencyWithIndexSorted(t *testing.T) { + path := "testdata/sample-v1.car" + + sortedIndex, err := carv2.GenerateIndexFromFile(path) + require.NoError(t, err) + require.Equal(t, multicodec.CarIndexSorted, sortedIndex.Codec()) + + f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + br, err := carv2.NewBlockReader(f) + require.NoError(t, err) + + subject := generateMultihashSortedIndex(t, path) + for { + wantNext, err := br.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + dmh, err := multihash.Decode(wantNext.Cid().Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + continue + } + + wantCid := wantNext.Cid() + var wantOffsets []uint64 + err = sortedIndex.GetAll(wantCid, func(o uint64) bool { + wantOffsets = append(wantOffsets, o) + return false + }) + require.NoError(t, err) + + var gotOffsets []uint64 + err = subject.GetAll(wantCid, func(o uint64) bool { + gotOffsets = append(gotOffsets, o) + return false + }) + + require.NoError(t, err) + require.Equal(t, wantOffsets, gotOffsets) + } +} + +func generateMultihashSortedIndex(t *testing.T, path string) index.Index { + f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + reader := internalio.ToByteReadSeeker(f) + header, err := carv1.ReadHeader(reader) + require.NoError(t, err) + require.Equal(t, uint64(1), header.Version) + + idx, err := index.New(multicodec.CarMultihashIndexSorted) + require.NoError(t, err) + records := make([]index.Record, 0) + + var sectionOffset int64 + sectionOffset, err = reader.Seek(0, io.SeekCurrent) + require.NoError(t, err) + + for { + sectionLen, err := varint.ReadUvarint(reader) + if err == io.EOF { + break + } + require.NoError(t, err) + + if sectionLen == 0 { + break + } + + cidLen, c, err := cid.CidFromReader(reader) + require.NoError(t, err) + records = append(records, index.Record{Cid: c, Offset: uint64(sectionOffset)}) + remainingSectionLen := int64(sectionLen) - int64(cidLen) + sectionOffset, err = reader.Seek(remainingSectionLen, io.SeekCurrent) + require.NoError(t, err) + } + + err = idx.Load(records) + require.NoError(t, err) + + return idx +} From 9df5a4e5bd0b99c1974ee452f22d6a32d18590dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 7 Sep 2021 15:43:09 +0200 Subject: [PATCH 155/291] avoid allocating on every byte read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need a ByteReader for some APIs, such as reading CIDs. However, our entrypoints often don't, such as Reader or ReaderAt. Thus, we have a type that does the wrapping to support ReadByte. We were converting a non-pointer to an interface, which forcibly allocates, since interfaces must contain pointers. Fix that by making the ReadByte methods use pointer receivers. name old time/op new time/op delta ReadBlocks-16 1.18ms ± 2% 1.15ms ± 5% -2.73% (p=0.003 n=11+11) name old speed new speed delta ReadBlocks-16 441MB/s ± 2% 453MB/s ± 5% +2.85% (p=0.003 n=11+11) name old alloc/op new alloc/op delta ReadBlocks-16 1.33MB ± 0% 1.29MB ± 0% -2.41% (p=0.000 n=12+12) name old allocs/op new allocs/op delta ReadBlocks-16 13.5k ± 0% 11.5k ± 0% -14.79% (p=0.000 n=12+12) This commit was moved from ipld/go-car@7a82ec58a3c84adb19224987581f4aead0c55cba --- ipld/car/v2/internal/io/converter.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipld/car/v2/internal/io/converter.go b/ipld/car/v2/internal/io/converter.go index 65350d34be..5435471f1f 100644 --- a/ipld/car/v2/internal/io/converter.go +++ b/ipld/car/v2/internal/io/converter.go @@ -63,11 +63,11 @@ func ToReaderAt(rs io.ReadSeeker) io.ReaderAt { return &readSeekerAt{rs: rs} } -func (rb readerPlusByte) ReadByte() (byte, error) { +func (rb *readerPlusByte) ReadByte() (byte, error) { return readByte(rb) } -func (rsb readSeekerPlusByte) ReadByte() (byte, error) { +func (rsb *readSeekerPlusByte) ReadByte() (byte, error) { return readByte(rsb) } From dd0c4220ec583650bda62dea909a0b969055c66c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 7 Sep 2021 16:31:55 +0200 Subject: [PATCH 156/291] avoid another alloc per read byte MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Like the last commit, avoid an extra allocation per read byte. In this case, we created a one-byte buffer for each readByte call. Unfortunately, since io.Reader is an interface, the compiler can't know if it holds onto the memory, so the buffer escapes and cannot be placed in the stack. To sidestep this issue, reuse a preallocated buffer. We know this is fine, because we only do sequential reads. name old time/op new time/op delta ReadBlocks-16 1.15ms ± 5% 1.09ms ± 4% -5.13% (p=0.000 n=11+11) name old speed new speed delta ReadBlocks-16 453MB/s ± 5% 478MB/s ± 4% +5.41% (p=0.000 n=11+11) name old alloc/op new alloc/op delta ReadBlocks-16 1.29MB ± 0% 1.30MB ± 0% +0.48% (p=0.000 n=12+12) name old allocs/op new allocs/op delta ReadBlocks-16 11.5k ± 0% 9.5k ± 0% -17.35% (p=0.000 n=12+12) This commit was moved from ipld/go-car@ccffb5c06ed2c79d67dba037a4754b4212480790 --- ipld/car/v2/internal/io/converter.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/ipld/car/v2/internal/io/converter.go b/ipld/car/v2/internal/io/converter.go index 5435471f1f..21011b6ec1 100644 --- a/ipld/car/v2/internal/io/converter.go +++ b/ipld/car/v2/internal/io/converter.go @@ -17,15 +17,21 @@ var ( type ( readerPlusByte struct { io.Reader + + byteBuf [1]byte // escapes via io.Reader.Read; preallocate } readSeekerPlusByte struct { io.ReadSeeker + + byteBuf [1]byte // escapes via io.Reader.Read; preallocate } discardingReadSeekerPlusByte struct { io.Reader offset int64 + + byteBuf [1]byte // escapes via io.Reader.Read; preallocate } ByteReadSeeker interface { @@ -43,7 +49,7 @@ func ToByteReader(r io.Reader) io.ByteReader { if br, ok := r.(io.ByteReader); ok { return br } - return &readerPlusByte{r} + return &readerPlusByte{Reader: r} } func ToByteReadSeeker(r io.Reader) ByteReadSeeker { @@ -51,7 +57,7 @@ func ToByteReadSeeker(r io.Reader) ByteReadSeeker { return brs } if rs, ok := r.(io.ReadSeeker); ok { - return &readSeekerPlusByte{rs} + return &readSeekerPlusByte{ReadSeeker: rs} } return &discardingReadSeekerPlusByte{Reader: r} } @@ -64,15 +70,18 @@ func ToReaderAt(rs io.ReadSeeker) io.ReaderAt { } func (rb *readerPlusByte) ReadByte() (byte, error) { - return readByte(rb) + _, err := io.ReadFull(rb, rb.byteBuf[:]) + return rb.byteBuf[0], err } func (rsb *readSeekerPlusByte) ReadByte() (byte, error) { - return readByte(rsb) + _, err := io.ReadFull(rsb, rsb.byteBuf[:]) + return rsb.byteBuf[0], err } func (drsb *discardingReadSeekerPlusByte) ReadByte() (byte, error) { - return readByte(drsb) + _, err := io.ReadFull(drsb, drsb.byteBuf[:]) + return drsb.byteBuf[0], err } func (drsb *discardingReadSeekerPlusByte) Read(p []byte) (read int, err error) { @@ -98,12 +107,6 @@ func (drsb *discardingReadSeekerPlusByte) Seek(offset int64, whence int) (int64, } } -func readByte(r io.Reader) (byte, error) { - var p [1]byte - _, err := io.ReadFull(r, p[:]) - return p[0], err -} - func (rsa *readSeekerAt) ReadAt(p []byte, off int64) (n int, err error) { rsa.mu.Lock() defer rsa.mu.Unlock() From 0e8f05f98432712de1b69c5ccfb0b6b1f036568e Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 7 Sep 2021 17:36:08 +0200 Subject: [PATCH 157/291] Expose the ability to iterate over records in `MultihasIndexSorted` Implement API that allows iteration over multihashes and offsets in `MultihasIndexSorted`. The API is specific to this index type; therefore, the type is now exported along with its constructor funtion. Write test that asserts `GetAll` and `ForEach` behave consistently. Relates to #95 This commit was moved from ipld/go-car@1398bba4351c96adaccb92464e45769339f17a9d --- ipld/car/v2/index/index.go | 4 +-- ipld/car/v2/index/indexsorted.go | 26 +++++++++++++++ ipld/car/v2/index/mhindexsorted.go | 46 ++++++++++++++++++++------- ipld/car/v2/index_gen_test.go | 51 ++++++++++++++++++++++++++++-- 4 files changed, 110 insertions(+), 17 deletions(-) diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index cc9ff70bca..3a28a49a39 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -44,7 +44,7 @@ type ( // Load inserts a number of records into the index. Load([]Record) error - // Get looks up all blocks matching a given CID, + // GetAll looks up all blocks matching a given CID, // calling a function for each one of their offsets. // // If the function returns false, GetAll stops. @@ -73,7 +73,7 @@ func New(codec multicodec.Code) (Index, error) { case multicodec.CarIndexSorted: return newSorted(), nil case multicodec.CarMultihashIndexSorted: - return newMultihashSorted(), nil + return NewMultihashSorted(), nil default: return nil, fmt.Errorf("unknwon index codec: %v", codec) } diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 5f37eee446..5af6f4da28 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -13,6 +13,8 @@ import ( "github.com/multiformats/go-multihash" ) +var _ Index = (*multiWidthIndex)(nil) + type ( digestRecord struct { digest []byte @@ -120,6 +122,21 @@ func (s *singleWidthIndex) Load(items []Record) error { return nil } +func (s *singleWidthIndex) forEachDigest(f func(digest []byte, offset uint64) error) error { + segmentCount := len(s.index) / int(s.width) + for i := 0; i < segmentCount; i++ { + digestStart := i * int(s.width) + offsetEnd := (i + 1) * int(s.width) + digestEnd := offsetEnd - 8 + digest := s.index[digestStart:digestEnd] + offset := binary.LittleEndian.Uint64(s.index[digestEnd:offsetEnd]) + if err := f(digest, offset); err != nil { + return err + } + } + return nil +} + func (m *multiWidthIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { d, err := multihash.Decode(c.Hash()) if err != nil { @@ -210,6 +227,15 @@ func (m *multiWidthIndex) Load(items []Record) error { return nil } +func (m *multiWidthIndex) forEachDigest(f func(digest []byte, offset uint64) error) error { + for _, swi := range *m { + if err := swi.forEachDigest(f); err != nil { + return err + } + } + return nil +} + func newSorted() Index { m := make(multiWidthIndex) return &m diff --git a/ipld/car/v2/index/mhindexsorted.go b/ipld/car/v2/index/mhindexsorted.go index 95ab64743e..75d3097163 100644 --- a/ipld/car/v2/index/mhindexsorted.go +++ b/ipld/car/v2/index/mhindexsorted.go @@ -10,10 +10,12 @@ import ( "github.com/multiformats/go-multihash" ) +var _ Index = (*MultihashIndexSorted)(nil) + type ( - // multihashIndexSorted maps multihash code (i.e. hashing algorithm) to multiWidthCodedIndex. + // MultihashIndexSorted maps multihash code (i.e. hashing algorithm) to multiWidthCodedIndex. // This index ignores any Record with multihash.IDENTITY. - multihashIndexSorted map[uint64]*multiWidthCodedIndex + MultihashIndexSorted map[uint64]*multiWidthCodedIndex // multiWidthCodedIndex stores multihash code for each multiWidthIndex. multiWidthCodedIndex struct { multiWidthIndex @@ -41,11 +43,21 @@ func (m *multiWidthCodedIndex) Unmarshal(r io.Reader) error { return m.multiWidthIndex.Unmarshal(r) } -func (m *multihashIndexSorted) Codec() multicodec.Code { +func (m *multiWidthCodedIndex) forEach(f func(mh multihash.Multihash, offset uint64) error) error { + return m.multiWidthIndex.forEachDigest(func(digest []byte, offset uint64) error { + mh, err := multihash.Encode(digest, m.code) + if err != nil { + return err + } + return f(mh, offset) + }) +} + +func (m *MultihashIndexSorted) Codec() multicodec.Code { return multicodec.CarMultihashIndexSorted } -func (m *multihashIndexSorted) Marshal(w io.Writer) error { +func (m *MultihashIndexSorted) Marshal(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, int32(len(*m))); err != nil { return err } @@ -63,7 +75,7 @@ func (m *multihashIndexSorted) Marshal(w io.Writer) error { return nil } -func (m *multihashIndexSorted) sortedMultihashCodes() []uint64 { +func (m *MultihashIndexSorted) sortedMultihashCodes() []uint64 { codes := make([]uint64, 0, len(*m)) for code := range *m { codes = append(codes, code) @@ -74,7 +86,7 @@ func (m *multihashIndexSorted) sortedMultihashCodes() []uint64 { return codes } -func (m *multihashIndexSorted) Unmarshal(r io.Reader) error { +func (m *MultihashIndexSorted) Unmarshal(r io.Reader) error { var l int32 if err := binary.Read(r, binary.LittleEndian, &l); err != nil { return err @@ -89,11 +101,11 @@ func (m *multihashIndexSorted) Unmarshal(r io.Reader) error { return nil } -func (m *multihashIndexSorted) put(mwci *multiWidthCodedIndex) { +func (m *MultihashIndexSorted) put(mwci *multiWidthCodedIndex) { (*m)[mwci.code] = mwci } -func (m *multihashIndexSorted) Load(records []Record) error { +func (m *MultihashIndexSorted) Load(records []Record) error { // TODO optimize load by avoiding multihash decoding twice. // This implementation decodes multihashes twice: once here to group by code, and once in the // internals of multiWidthIndex to group by digest length. The code can be optimized by @@ -132,7 +144,7 @@ func (m *multihashIndexSorted) Load(records []Record) error { return nil } -func (m *multihashIndexSorted) GetAll(cid cid.Cid, f func(uint64) bool) error { +func (m *MultihashIndexSorted) GetAll(cid cid.Cid, f func(uint64) bool) error { hash := cid.Hash() dmh, err := multihash.Decode(hash) if err != nil { @@ -145,14 +157,24 @@ func (m *multihashIndexSorted) GetAll(cid cid.Cid, f func(uint64) bool) error { return mwci.GetAll(cid, f) } -func (m *multihashIndexSorted) get(dmh *multihash.DecodedMultihash) (*multiWidthCodedIndex, error) { +// ForEach calls f for every multihash and its associated offset stored by this index. +func (m *MultihashIndexSorted) ForEach(f func(mh multihash.Multihash, offset uint64) error) error { + for _, mwci := range *m { + if err := mwci.forEach(f); err != nil { + return err + } + } + return nil +} + +func (m *MultihashIndexSorted) get(dmh *multihash.DecodedMultihash) (*multiWidthCodedIndex, error) { if codedIdx, ok := (*m)[dmh.Code]; ok { return codedIdx, nil } return nil, ErrNotFound } -func newMultihashSorted() Index { - index := make(multihashIndexSorted) +func NewMultihashSorted() *MultihashIndexSorted { + index := make(MultihashIndexSorted) return &index } diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 635a07cb2d..762c38762b 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -210,7 +210,53 @@ func TestMultihashIndexSortedConsistencyWithIndexSorted(t *testing.T) { } } -func generateMultihashSortedIndex(t *testing.T, path string) index.Index { +func TestMultihashSorted_ForEachIsConsistentWithGetAll(t *testing.T) { + path := "testdata/sample-v1.car" + f, err := os.Open(path) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, f.Close()) }) + + br, err := carv2.NewBlockReader(f) + require.NoError(t, err) + subject := generateMultihashSortedIndex(t, path) + + gotForEach := make(map[string]uint64) + err = subject.ForEach(func(mh multihash.Multihash, offset uint64) error { + gotForEach[mh.String()] = offset + return nil + }) + require.NoError(t, err) + + for { + b, err := br.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + c := b.Cid() + dmh, err := multihash.Decode(c.Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + continue + } + + wantMh := c.Hash() + + var wantOffset uint64 + err = subject.GetAll(c, func(u uint64) bool { + wantOffset = u + return false + }) + require.NoError(t, err) + + s := wantMh.String() + gotOffset, ok := gotForEach[s] + require.True(t, ok) + require.Equal(t, wantOffset, gotOffset) + } +} + +func generateMultihashSortedIndex(t *testing.T, path string) *index.MultihashIndexSorted { f, err := os.Open(path) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, f.Close()) }) @@ -219,8 +265,7 @@ func generateMultihashSortedIndex(t *testing.T, path string) index.Index { require.NoError(t, err) require.Equal(t, uint64(1), header.Version) - idx, err := index.New(multicodec.CarMultihashIndexSorted) - require.NoError(t, err) + idx := index.NewMultihashSorted() records := make([]index.Record, 0) var sectionOffset int64 From c98783b0ddc939e4c15d23ea79b2315a2280e920 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 7 Sep 2021 19:08:14 +0200 Subject: [PATCH 158/291] Fix index GetAll infinite loop if function always returns `true` Refine `GetAll` logic so that it stops iteration if either function returns false or there are no more entries to search. Fixes #216 This commit was moved from ipld/go-car@2bff9fa743d09860709e7b20ff72b1ac1a853fe9 --- ipld/car/v2/index/indexsorted.go | 22 ++++++++++++------- ipld/car/v2/index/indexsorted_test.go | 31 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 5af6f4da28..e789350832 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -87,15 +87,21 @@ func (s *singleWidthIndex) getAll(d []byte, fn func(uint64) bool) error { idx := sort.Search(int(s.len), func(i int) bool { return s.Less(i, d) }) - if uint64(idx) == s.len { - return ErrNotFound - } - any := false - for bytes.Equal(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) { - any = true - offset := binary.LittleEndian.Uint64(s.index[(idx+1)*int(s.width)-8 : (idx+1)*int(s.width)]) - if !fn(offset) { + var any bool + for ; uint64(idx) < s.len; idx++ { + digestStart := idx * int(s.width) + offsetEnd := (idx + 1) * int(s.width) + digestEnd := offsetEnd - 8 + if bytes.Equal(d[:], s.index[digestStart:digestEnd]) { + any = true + offset := binary.LittleEndian.Uint64(s.index[digestEnd:offsetEnd]) + if !fn(offset) { + // User signalled to stop searching; therefore, break. + break + } + } else { + // No more matches; therefore, break. break } } diff --git a/ipld/car/v2/index/indexsorted_test.go b/ipld/car/v2/index/indexsorted_test.go index 767937b79a..3a939d5c8c 100644 --- a/ipld/car/v2/index/indexsorted_test.go +++ b/ipld/car/v2/index/indexsorted_test.go @@ -1,6 +1,7 @@ package index import ( + "encoding/binary" "testing" "github.com/ipfs/go-merkledag" @@ -31,3 +32,33 @@ func TestSortedIndex_GetReturnsNotFoundWhenCidDoesNotExist(t *testing.T) { }) } } + +func TestSingleWidthIndex_GetAll(t *testing.T) { + l := 4 + width := 9 + buf := make([]byte, width*l) + + // Populate the index bytes as total of four records. + // The last record should not match the getAll. + for i := 0; i < l; i++ { + if i < l-1 { + buf[i*width] = 1 + } else { + buf[i*width] = 2 + } + binary.LittleEndian.PutUint64(buf[(i*width)+1:(i*width)+width], uint64(14)) + } + subject := &singleWidthIndex{ + width: 9, + len: uint64(l), + index: buf, + } + + var foundCount int + err := subject.getAll([]byte{1}, func(u uint64) bool { + foundCount++ + return true + }) + require.NoError(t, err) + require.Equal(t, 3, foundCount) +} From a016d21425767ce0c0214b4310589248227eecdc Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Thu, 2 Sep 2021 14:09:11 +0100 Subject: [PATCH 159/291] Ignore records with `IDENTITY` CID in `IndexSorted` Skip `IDENTITY` CIDs in `IndexSorted`, as required by CARv2 specification. Re-generate `sample-wrapped-v2.car` testdata, since its previous index contained CIDs with `IDENTITY` multihash. Gracefully handle CIDs with `IDENTITY` multihash code in the `blockstore` API. Since `Get`, `GetSize` and `Has` interfaces rely on the index, decode given keys directly to satisfy calls for such CIDs. See: - https://ipld.io/specs/transport/car/carv2/#index-format Fixes #215 This commit was moved from ipld/go-car@acd3ead768ebba6ea81d31a7164f1fc031f5c276 --- ipld/car/v2/blockstore/doc.go | 12 +++++- ipld/car/v2/blockstore/readonly.go | 40 ++++++++++++++++++- ipld/car/v2/blockstore/readwrite.go | 7 ++++ ipld/car/v2/blockstore/readwrite_test.go | 37 +++++++++++++---- ipld/car/v2/index/index.go | 4 ++ ipld/car/v2/index/indexsorted.go | 7 ++++ ipld/car/v2/index/indexsorted_test.go | 44 ++++++++++++++++++++- ipld/car/v2/testdata/sample-wrapped-v2.car | Bin 521859 -> 521696 bytes 8 files changed, 139 insertions(+), 12 deletions(-) diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index a82723419c..6b96b7a6ef 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -1,4 +1,4 @@ -// package blockstore implements the IPFS blockstore interface backed by a CAR file. +// Package blockstore implements the IPFS blockstore interface backed by a CAR file. // This package provides two flavours of blockstore: ReadOnly and ReadWrite. // // The ReadOnly blockstore provides a read-only random access from a given data payload either in @@ -15,4 +15,14 @@ // instantiated from the same file path using OpenReadOnly. // A user may resume reading/writing from files produced by an instance of ReadWrite blockstore. The // resumption is attempted automatically, if the path passed to OpenReadWrite exists. +// +// Note that the blockstore implementations in this package behave similarly to IPFS IdStore wrapper +// when given CIDs with multihash.IDENTITY code. +// More specifically, for CIDs with multhash.IDENTITY code: +// * blockstore.Has will always return true. +// * blockstore.Get will always succeed, returning the multihash digest of the given CID. +// * blockstore.GetSize will always succeed, returning the multihash digest length of the given CID. +// * blockstore.Put and blockstore.PutMany will always succeed without performing any operation. +// +// See: https://pkg.go.dev/github.com/ipfs/go-ipfs-blockstore#NewIdStore package blockstore diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 7e165bd245..75113c6359 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -10,8 +10,6 @@ import ( "golang.org/x/exp/mmap" - "github.com/multiformats/go-varint" - blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" blockstore "github.com/ipfs/go-ipfs-blockstore" @@ -20,6 +18,8 @@ import ( "github.com/ipld/go-car/v2/internal/carv1" "github.com/ipld/go-car/v2/internal/carv1/util" internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-multihash" + "github.com/multiformats/go-varint" ) var _ blockstore.Blockstore = (*ReadOnly)(nil) @@ -187,7 +187,16 @@ func (b *ReadOnly) DeleteBlock(_ cid.Cid) error { } // Has indicates if the store contains a block that corresponds to the given key. +// This function always returns true for any given key with multihash.IDENTITY code. func (b *ReadOnly) Has(key cid.Cid) (bool, error) { + // Check if the given CID has multihash.IDENTITY code + // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. + if _, ok, err := isIdentity(key); err != nil { + return false, err + } else if ok { + return true, nil + } + b.mu.RLock() defer b.mu.RUnlock() @@ -227,7 +236,16 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { } // Get gets a block corresponding to the given key. +// This API will always return true if the given key has multihash.IDENTITY code. func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { + // Check if the given CID has multihash.IDENTITY code + // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. + if digest, ok, err := isIdentity(key); err != nil { + return nil, err + } else if ok { + return blocks.NewBlockWithCid(digest, key) + } + b.mu.RLock() defer b.mu.RUnlock() @@ -272,6 +290,14 @@ func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { // GetSize gets the size of an item corresponding to the given key. func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { + // Check if the given CID has multihash.IDENTITY code + // Note, we do this without locking, since there is no shared information to lock for in order to perform the check. + if digest, ok, err := isIdentity(key); err != nil { + return 0, err + } else if ok { + return len(digest), nil + } + b.mu.RLock() defer b.mu.RUnlock() @@ -320,6 +346,16 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { return fnSize, nil } +func isIdentity(key cid.Cid) (digest []byte, ok bool, err error) { + dmh, err := multihash.Decode(key.Hash()) + if err != nil { + return nil, false, err + } + ok = dmh.Code == multihash.IDENTITY + digest = dmh.Digest + return digest, ok, nil +} + // Put is not supported and always returns an error. func (b *ReadOnly) Put(blocks.Block) error { return errReadOnly diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index ae482a7f9a..a359615324 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -304,6 +304,13 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { for _, bl := range blks { c := bl.Cid() + // Check for IDENTITY CID. If IDENTITY, ignore and move to the next block. + if _, ok, err := isIdentity(c); err != nil { + return err + } else if ok { + continue + } + if !b.wopts.BlockstoreAllowDuplicatePuts { if b.ronly.ropts.BlockstoreUseWholeCIDs && b.idx.hasExactCID(c) { continue // deduplicated by CID diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index a30bbb235d..cfdad46422 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -64,6 +64,7 @@ func TestBlockstore(t *testing.T) { t.Cleanup(func() { ingester.Finalize() }) cids := make([]cid.Cid, 0) + var idCidCount int for { b, err := r.Next() if err == io.EOF { @@ -80,6 +81,12 @@ func TestBlockstore(t *testing.T) { if has, err := ingester.Has(candidate); !has || err != nil { t.Fatalf("expected to find %s but didn't: %s", candidate, err) } + + dmh, err := multihash.Decode(b.Cid().Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + idCidCount++ + } } for _, c := range cids { @@ -107,9 +114,8 @@ func TestBlockstore(t *testing.T) { } numKeysCh++ } - if numKeysCh != len(cids) { - t.Fatalf("AllKeysChan returned an unexpected amount of keys; expected %v but got %v", len(cids), numKeysCh) - } + expectedCidCount := len(cids) - idCidCount + require.Equal(t, expectedCidCount, numKeysCh, "AllKeysChan returned an unexpected amount of keys; expected %v but got %v", expectedCidCount, numKeysCh) for _, c := range cids { b, err := robs.Get(c) @@ -347,7 +353,7 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, err) // For each block resume on the same file, putting blocks one at a time. - var wantBlockCountSoFar int + var wantBlockCountSoFar, idCidCount int wantBlocks := make(map[cid.Cid]blocks.Block) for { b, err := r.Next() @@ -358,6 +364,12 @@ func TestBlockstoreResumption(t *testing.T) { wantBlockCountSoFar++ wantBlocks[b.Cid()] = b + dmh, err := multihash.Decode(b.Cid().Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + idCidCount++ + } + // 30% chance of subject failing; more concretely: re-instantiating blockstore with the same // file without calling Finalize. The higher this percentage the slower the test runs // considering the number of blocks in the original CARv1 test payload. @@ -402,7 +414,7 @@ func TestBlockstoreResumption(t *testing.T) { gotBlockCountSoFar++ } // Assert the number of blocks in file are as expected calculated via AllKeysChan - require.Equal(t, wantBlockCountSoFar, gotBlockCountSoFar) + require.Equal(t, wantBlockCountSoFar-idCidCount, gotBlockCountSoFar) } } subject.Discard() @@ -433,22 +445,31 @@ func TestBlockstoreResumption(t *testing.T) { require.Equal(t, wantPayloadReader.Header, gotPayloadReader.Header) for { wantNextBlock, wantErr := wantPayloadReader.Next() - gotNextBlock, gotErr := gotPayloadReader.Next() if wantErr == io.EOF { + gotNextBlock, gotErr := gotPayloadReader.Next() require.Equal(t, wantErr, gotErr) + require.Nil(t, gotNextBlock) break } require.NoError(t, wantErr) + + dmh, err := multihash.Decode(wantNextBlock.Cid().Hash()) + require.NoError(t, err) + if dmh.Code == multihash.IDENTITY { + continue + } + + gotNextBlock, gotErr := gotPayloadReader.Next() require.NoError(t, gotErr) require.Equal(t, wantNextBlock, gotNextBlock) } - // Assert index in resumed from file is identical to index generated directly from original CARv1 payload. + // Assert index in resumed from file is identical to index generated from the data payload portion of the generated CARv2 file. _, err = v1f.Seek(0, io.SeekStart) require.NoError(t, err) gotIdx, err := index.ReadFrom(v2r.IndexReader()) require.NoError(t, err) - wantIdx, err := carv2.GenerateIndex(v1f) + wantIdx, err := carv2.GenerateIndex(v2r.DataReader()) require.NoError(t, err) require.Equal(t, wantIdx, gotIdx) } diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 3a28a49a39..071e8e6e1e 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -29,6 +29,10 @@ type ( // blocks even if the CID's encoding multicodec differs. Other index // implementations might index the entire CID, the entire multihash, or // just part of a multihash's digest. + // + // In accordance with the CARv2 specification, Index will never contain information about CIDs + // with multihash.IDENTITY code. + // See: https://ipld.io/specs/transport/car/carv2/#index-format Index interface { // Codec provides the multicodec code that the index implements. // diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index e789350832..c6165ffa76 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -206,6 +206,13 @@ func (m *multiWidthIndex) Load(items []Record) error { if err != nil { return err } + + // Ignore records with IDENTITY as required by CARv2 spec. + // See: https://ipld.io/specs/transport/car/carv2/#index-format + if decHash.Code == multihash.IDENTITY { + continue + } + digest := decHash.Digest idx, ok := idxs[len(digest)] if !ok { diff --git a/ipld/car/v2/index/indexsorted_test.go b/ipld/car/v2/index/indexsorted_test.go index 3a939d5c8c..46322e5435 100644 --- a/ipld/car/v2/index/indexsorted_test.go +++ b/ipld/car/v2/index/indexsorted_test.go @@ -4,6 +4,9 @@ import ( "encoding/binary" "testing" + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" + "github.com/ipfs/go-merkledag" "github.com/multiformats/go-multicodec" "github.com/stretchr/testify/require" @@ -13,7 +16,7 @@ func TestSortedIndexCodec(t *testing.T) { require.Equal(t, multicodec.CarIndexSorted, newSorted().Codec()) } -func TestSortedIndex_GetReturnsNotFoundWhenCidDoesNotExist(t *testing.T) { +func TestIndexSorted_GetReturnsNotFoundWhenCidDoesNotExist(t *testing.T) { nonExistingKey := merkledag.NewRawNode([]byte("lobstermuncher")).Block.Cid() tests := []struct { name string @@ -62,3 +65,42 @@ func TestSingleWidthIndex_GetAll(t *testing.T) { require.NoError(t, err) require.Equal(t, 3, foundCount) } + +func TestIndexSorted_IgnoresIdentityCids(t *testing.T) { + data := []byte("🐟 in da 🌊d") + // Generate a record with IDENTITY multihash + idMh, err := multihash.Sum(data, multihash.IDENTITY, -1) + require.NoError(t, err) + idRec := Record{ + Cid: cid.NewCidV1(cid.Raw, idMh), + Offset: 1, + } + // Generate a record with non-IDENTITY multihash + nonIdMh, err := multihash.Sum(data, multihash.SHA2_256, -1) + require.NoError(t, err) + noIdRec := Record{ + Cid: cid.NewCidV1(cid.Raw, nonIdMh), + Offset: 2, + } + + subject := newSorted() + err = subject.Load([]Record{idRec, noIdRec}) + require.NoError(t, err) + + // Assert record with IDENTITY CID is not present. + err = subject.GetAll(idRec.Cid, func(u uint64) bool { + require.Fail(t, "no IDENTITY record shoul be found") + return false + }) + require.Equal(t, ErrNotFound, err) + + // Assert record with non-IDENTITY CID is indeed present. + var found bool + err = subject.GetAll(noIdRec.Cid, func(gotOffset uint64) bool { + found = true + require.Equal(t, noIdRec.Offset, gotOffset) + return false + }) + require.NoError(t, err) + require.True(t, found) +} diff --git a/ipld/car/v2/testdata/sample-wrapped-v2.car b/ipld/car/v2/testdata/sample-wrapped-v2.car index 7307bff71a854c4e6cdad611b4cc46097e4dbe23..232ba3136f8928d7a19f062527b1cb773ab28046 100644 GIT binary patch delta 31 ncmZpEEC1lNd_xOk3sVbo3rh=Y3)>d<8$pbW+iQZ^*%t!<$vF!} delta 195 zcmaFxTE6+Md_xOk3sVbo3rh=Y3)>d<8$tD~3=9lHK&%49Adr@sqi?96T$G>Z|B?|& zBr`9wq>GUWEFugf)xa#6`rORC)S?L}8Vd5uQ;QadLUf1#Rf8}HKy1iOEXqzTF; Date: Wed, 8 Sep 2021 05:45:55 -0700 Subject: [PATCH 160/291] Add carve utility for updating the index of a car{v1,v2} file (#219) * add car utility for updating the index of a car{v1,v2} file This commit was moved from ipld/go-car@2b19e2e305d4a9d3976c968ef524b60c7372fda9 --- ipld/car/v2/cmd/car/car.go | 158 +++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 ipld/car/v2/cmd/car/car.go diff --git a/ipld/car/v2/cmd/car/car.go b/ipld/car/v2/cmd/car/car.go new file mode 100644 index 0000000000..586cd75f7e --- /dev/null +++ b/ipld/car/v2/cmd/car/car.go @@ -0,0 +1,158 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "log" + "os" + + "github.com/ipfs/go-cid" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" + icarv1 "github.com/ipld/go-car/v2/internal/carv1" + "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-varint" + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Name: "car", + Usage: "Utility for working with car files", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "codec", + Aliases: []string{"c"}, + Usage: "The type of index to write", + Value: multicodec.CarMultihashIndexSorted.String(), + }, + }, + Commands: []*cli.Command{ + { + Name: "index", + Aliases: []string{"i"}, + Usage: "write out the car with an index", + Action: func(c *cli.Context) error { + r, err := carv2.OpenReader(c.Args().Get(0)) + if err != nil { + return err + } + defer r.Close() + + var idx index.Index + if c.String("codec") != "none" { + var mc multicodec.Code + if err := mc.Set(c.String("codec")); err != nil { + return err + } + idx, err = index.New(mc) + if err != nil { + return err + } + } + + outStream := os.Stdout + if c.Args().Len() >= 2 { + outStream, err = os.Create(c.Args().Get(1)) + if err != nil { + return err + } + } + defer outStream.Close() + + v1r := r.DataReader() + + v2Header := carv2.NewHeader(r.Header.DataSize) + if c.String("codec") == "none" { + v2Header.IndexOffset = 0 + if _, err := outStream.Write(carv2.Pragma); err != nil { + return err + } + if _, err := v2Header.WriteTo(outStream); err != nil { + return err + } + if _, err := io.Copy(outStream, v1r); err != nil { + return err + } + return nil + } + + if _, err := outStream.Write(carv2.Pragma); err != nil { + return err + } + if _, err := v2Header.WriteTo(outStream); err != nil { + return err + } + + // collect records as we go through the v1r + hdr, err := icarv1.ReadHeader(v1r) + if err != nil { + return fmt.Errorf("error reading car header: %w", err) + } + if err := icarv1.WriteHeader(hdr, outStream); err != nil { + return err + } + + records := make([]index.Record, 0) + var sectionOffset int64 + if sectionOffset, err = v1r.Seek(0, io.SeekCurrent); err != nil { + return err + } + + br := bufio.NewReader(v1r) + for { + // Read the section's length. + sectionLen, err := varint.ReadUvarint(br) + if err != nil { + if err == io.EOF { + break + } + return err + } + if _, err := outStream.Write(varint.ToUvarint(sectionLen)); err != nil { + return err + } + + // Null padding; by default it's an error. + // TODO: integrate corresponding ReadOption + if sectionLen == 0 { + // TODO: pad writer to expected length. + break + } + + // Read the CID. + cidLen, c, err := cid.CidFromReader(br) + if err != nil { + return err + } + records = append(records, index.Record{Cid: c, Offset: uint64(sectionOffset)}) + if _, err := c.WriteBytes(outStream); err != nil { + return err + } + + // Seek to the next section by skipping the block. + // The section length includes the CID, so subtract it. + remainingSectionLen := int64(sectionLen) - int64(cidLen) + if _, err := io.CopyN(outStream, br, remainingSectionLen); err != nil { + return err + } + sectionOffset += int64(sectionLen) + int64(varint.UvarintSize(sectionLen)) + } + + if err := idx.Load(records); err != nil { + return err + } + + return index.WriteTo(idx, outStream) + }, + }, + }, + } + + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + os.Exit(1) + } +} From 003b295b5e46cc76c12f729758175507cc4a251b Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 8 Sep 2021 13:00:48 +0200 Subject: [PATCH 161/291] Make `MultihashIndexSorted` the default index codec for CARv2 Provide an option to choose the index codec when writing a CARv2, with default value of `multicodec.CarMultihashIndexSorted`. Implement utility functions to work with options with ability to split and merge between different flavours to reduce boilerplate code across the repo. Re-generate sample files with default index. Fixes #224 This commit was moved from ipld/go-car@29b166da5c28ba952d597767b73ba7c5fa4afc70 --- ipld/car/v2/block_reader.go | 8 +-- ipld/car/v2/blockstore/insertionindex.go | 9 ++-- ipld/car/v2/blockstore/readonly.go | 8 +-- ipld/car/v2/blockstore/readwrite.go | 11 +++-- ipld/car/v2/index_gen.go | 54 +++++++++++---------- ipld/car/v2/index_gen_test.go | 2 +- ipld/car/v2/internal/options/options.go | 19 ++++++++ ipld/car/v2/options.go | 32 ++++++++++++ ipld/car/v2/reader.go | 4 +- ipld/car/v2/testdata/sample-rw-bs-v2.car | Bin 1875 -> 1887 bytes ipld/car/v2/testdata/sample-wrapped-v2.car | Bin 521696 -> 521708 bytes ipld/car/v2/writer.go | 22 +++++++-- 12 files changed, 118 insertions(+), 51 deletions(-) create mode 100644 ipld/car/v2/internal/options/options.go diff --git a/ipld/car/v2/block_reader.go b/ipld/car/v2/block_reader.go index 5c33502945..8cd23e72c2 100644 --- a/ipld/car/v2/block_reader.go +++ b/ipld/car/v2/block_reader.go @@ -37,14 +37,10 @@ func NewBlockReader(r io.Reader, opts ...ReadOption) (*BlockReader, error) { return nil, err } - // Populate the block reader version. + // Populate the block reader version and options. br := &BlockReader{ Version: pragmaOrV1Header.Version, - } - - // Populate read options - for _, o := range opts { - o(&br.ropts) + ropts: ApplyReadOptions(opts...), } // Expect either version 1 or 2. diff --git a/ipld/car/v2/blockstore/insertionindex.go b/ipld/car/v2/blockstore/insertionindex.go index 00d414656f..7cca61b311 100644 --- a/ipld/car/v2/blockstore/insertionindex.go +++ b/ipld/car/v2/blockstore/insertionindex.go @@ -7,10 +7,9 @@ import ( "fmt" "io" + "github.com/ipfs/go-cid" "github.com/ipld/go-car/v2/index" "github.com/multiformats/go-multicodec" - - "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" "github.com/petar/GoLLRB/llrb" cbor "github.com/whyrusleeping/cbor/go" @@ -154,9 +153,9 @@ func newInsertionIndex() *insertionIndex { return &insertionIndex{} } -// flatten returns a 'indexsorted' formatted index for more efficient subsequent loading -func (ii *insertionIndex) flatten() (index.Index, error) { - si, err := index.New(multicodec.CarIndexSorted) +// flatten returns a formatted index in the given codec for more efficient subsequent loading. +func (ii *insertionIndex) flatten(codec multicodec.Code) (index.Index, error) { + si, err := index.New(codec) if err != nil { return nil, err } diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 75113c6359..4e7e82027f 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -94,9 +94,9 @@ func UseWholeCIDs(enable bool) carv2.ReadOption { // // There is no need to call ReadOnly.Close on instances returned by this function. func NewReadOnly(backing io.ReaderAt, idx index.Index, opts ...carv2.ReadOption) (*ReadOnly, error) { - b := &ReadOnly{} - for _, opt := range opts { - opt(&b.ropts) + ropts := carv2.ApplyReadOptions(opts...) + b := &ReadOnly{ + ropts: ropts, } version, err := readVersion(backing) @@ -155,6 +155,8 @@ func generateIndex(at io.ReaderAt, opts ...carv2.ReadOption) (index.Index, error default: rs = internalio.NewOffsetReadSeeker(r, 0) } + + // Note, we do not set any write options so that all write options fall back onto defaults. return carv2.GenerateIndex(rs, opts...) } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index a359615324..7b14b55f97 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -116,14 +116,19 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.ReadWriteOption) header: carv2.NewHeader(0), } + var ro []carv2.ReadOption + var wo []carv2.WriteOption for _, opt := range opts { switch opt := opt.(type) { case carv2.ReadOption: - opt(&rwbs.ronly.ropts) + ro = append(ro, opt) case carv2.WriteOption: - opt(&rwbs.wopts) + wo = append(wo, opt) } } + rwbs.ronly.ropts = carv2.ApplyReadOptions(ro...) + rwbs.wopts = carv2.ApplyWriteOptions(wo...) + if p := rwbs.wopts.DataPadding; p > 0 { rwbs.header = rwbs.header.WithDataPadding(p) } @@ -366,7 +371,7 @@ func (b *ReadWrite) Finalize() error { defer b.ronly.closeWithoutMutex() // TODO if index not needed don't bother flattening it. - fi, err := b.idx.flatten() + fi, err := b.idx.flatten(b.wopts.IndexCodec) if err != nil { return err } diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 11bf36beed..91ebb58911 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -5,40 +5,40 @@ import ( "io" "os" - internalio "github.com/ipld/go-car/v2/internal/io" - - "github.com/ipld/go-car/v2/index" - "github.com/multiformats/go-multicodec" - - "github.com/multiformats/go-varint" - "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2/index" "github.com/ipld/go-car/v2/internal/carv1" + internalio "github.com/ipld/go-car/v2/internal/io" + "github.com/multiformats/go-varint" ) // GenerateIndex generates index for a given car in v1 format. -// The index can be stored using index.Save into a file or serialized using index.WriteTo. +// The generated index will be in multicodec.CarMultihashIndexSorted, the default index codec. +// The index can be stored in serialized format using index.WriteTo. +// See LoadIndex. func GenerateIndex(v1r io.Reader, opts ...ReadOption) (index.Index, error) { - var o ReadOptions - for _, opt := range opts { - opt(&o) + idx := index.NewMultihashSorted() + if err := LoadIndex(idx, v1r, opts...); err != nil { + return nil, err } + return idx, nil +} +// LoadIndex populates idx with index records generated from v1r. +// The v1r must be data payload in CARv1 format. +func LoadIndex(idx index.Index, v1r io.Reader, opts ...ReadOption) error { reader := internalio.ToByteReadSeeker(v1r) header, err := carv1.ReadHeader(reader) if err != nil { - return nil, fmt.Errorf("error reading car header: %w", err) + return fmt.Errorf("error reading car header: %w", err) } if header.Version != 1 { - return nil, fmt.Errorf("expected version to be 1, got %v", header.Version) + return fmt.Errorf("expected version to be 1, got %v", header.Version) } - idx, err := index.New(multicodec.CarIndexSorted) - if err != nil { - return nil, err - } - records := make([]index.Record, 0) + // Parse Options. + ropts := ApplyReadOptions(opts...) // Record the start of each section, with first section starring from current position in the // reader, i.e. right after the header, since we have only read the header so far. @@ -48,9 +48,10 @@ func GenerateIndex(v1r io.Reader, opts ...ReadOption) (index.Index, error) { // We get it through Seek to only depend on APIs of a typical io.Seeker. // This would also reduce refactoring in case the utility reader is moved. if sectionOffset, err = reader.Seek(0, io.SeekCurrent); err != nil { - return nil, err + return err } + records := make([]index.Record, 0) for { // Read the section's length. sectionLen, err := varint.ReadUvarint(reader) @@ -58,22 +59,22 @@ func GenerateIndex(v1r io.Reader, opts ...ReadOption) (index.Index, error) { if err == io.EOF { break } - return nil, err + return err } // Null padding; by default it's an error. if sectionLen == 0 { - if o.ZeroLengthSectionAsEOF { + if ropts.ZeroLengthSectionAsEOF { break } else { - return nil, fmt.Errorf("carv1 null padding not allowed by default; see ZeroLengthSectionAsEOF") + return fmt.Errorf("carv1 null padding not allowed by default; see ZeroLengthSectionAsEOF") } } // Read the CID. cidLen, c, err := cid.CidFromReader(reader) if err != nil { - return nil, err + return err } records = append(records, index.Record{Cid: c, Offset: uint64(sectionOffset)}) @@ -81,19 +82,20 @@ func GenerateIndex(v1r io.Reader, opts ...ReadOption) (index.Index, error) { // The section length includes the CID, so subtract it. remainingSectionLen := int64(sectionLen) - int64(cidLen) if sectionOffset, err = reader.Seek(remainingSectionLen, io.SeekCurrent); err != nil { - return nil, err + return err } } if err := idx.Load(records); err != nil { - return nil, err + return err } - return idx, nil + return nil } // GenerateIndexFromFile walks a car v1 file at the give path and generates an index of cid->byte offset. // The index can be stored using index.Save into a file or serialized using index.WriteTo. +// See GenerateIndex. func GenerateIndexFromFile(path string) (index.Index, error) { f, err := os.Open(path) if err != nil { diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 762c38762b..601fb8cb51 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -169,7 +169,7 @@ func TestMultihashIndexSortedConsistencyWithIndexSorted(t *testing.T) { sortedIndex, err := carv2.GenerateIndexFromFile(path) require.NoError(t, err) - require.Equal(t, multicodec.CarIndexSorted, sortedIndex.Codec()) + require.Equal(t, multicodec.CarMultihashIndexSorted, sortedIndex.Codec()) f, err := os.Open(path) require.NoError(t, err) diff --git a/ipld/car/v2/internal/options/options.go b/ipld/car/v2/internal/options/options.go new file mode 100644 index 0000000000..3add2b5a43 --- /dev/null +++ b/ipld/car/v2/internal/options/options.go @@ -0,0 +1,19 @@ +// Package options provides utilities to work with ReadOption and WriteOption used internally. +package options + +import carv2 "github.com/ipld/go-car/v2" + +// SplitReadWriteOptions splits the rwopts by type into ReadOption and WriteOption slices. +func SplitReadWriteOptions(rwopts ...carv2.ReadWriteOption) ([]carv2.ReadOption, []carv2.WriteOption) { + var ropts []carv2.ReadOption + var wopts []carv2.WriteOption + for _, opt := range rwopts { + switch opt := opt.(type) { + case carv2.ReadOption: + ropts = append(ropts, opt) + case carv2.WriteOption: + wopts = append(wopts, opt) + } + } + return ropts, wopts +} diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index 86aaa9e55d..ed6502cc91 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -1,5 +1,7 @@ package car +import "github.com/multiformats/go-multicodec" + // ReadOptions holds the configured options after applying a number of // ReadOption funcs. // @@ -11,6 +13,15 @@ type ReadOptions struct { BlockstoreUseWholeCIDs bool } +// ApplyReadOptions applies ropts and returns the resulting ReadOptions. +func ApplyReadOptions(ropts ...ReadOption) ReadOptions { + var opts ReadOptions + for _, opt := range ropts { + opt(&opts) + } + return opts +} + // ReadOption describes an option which affects behavior when parsing CAR files. type ReadOption func(*ReadOptions) @@ -26,10 +37,24 @@ var _ ReadWriteOption = ReadOption(nil) type WriteOptions struct { DataPadding uint64 IndexPadding uint64 + IndexCodec multicodec.Code BlockstoreAllowDuplicatePuts bool } +// ApplyWriteOptions applies the given ropts and returns the resulting WriteOptions. +func ApplyWriteOptions(ropts ...WriteOption) WriteOptions { + var opts WriteOptions + for _, opt := range ropts { + opt(&opts) + } + // Set defaults for zero valued fields. + if opts.IndexCodec == 0 { + opts.IndexCodec = multicodec.CarMultihashIndexSorted + } + return opts +} + // WriteOption describes an option which affects behavior when encoding CAR files. type WriteOption func(*WriteOptions) @@ -67,3 +92,10 @@ func UseIndexPadding(p uint64) WriteOption { o.IndexPadding = p } } + +// UseIndexCodec is a write option which sets the codec used for index generation. +func UseIndexCodec(c multicodec.Code) WriteOption { + return func(o *WriteOptions) { + o.IndexCodec = c + } +} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index 2f7cbb18e4..b9742ec72c 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -48,9 +48,7 @@ func NewReader(r io.ReaderAt, opts ...ReadOption) (*Reader, error) { cr := &Reader{ r: r, } - for _, o := range opts { - o(&cr.ropts) - } + cr.ropts = ApplyReadOptions(opts...) or := internalio.NewOffsetReadSeeker(r, 0) var err error diff --git a/ipld/car/v2/testdata/sample-rw-bs-v2.car b/ipld/car/v2/testdata/sample-rw-bs-v2.car index 9f7b56df358e0e3a0eb3734743b13cb3d88a12f4..22e357249441fd690a9d77a4223d009a6bea586e 100644 GIT binary patch delta 26 dcmcc2cb{*=NjAPl4n_tB1|c8@fyt-YIsjer1>^t# delta 14 Vcmcc5cbRX)Nj9bij>*^AIsh&(1<(Kh diff --git a/ipld/car/v2/testdata/sample-wrapped-v2.car b/ipld/car/v2/testdata/sample-wrapped-v2.car index 232ba3136f8928d7a19f062527b1cb773ab28046..35c4d36899c9947bac061b783fee9ac3615b5d16 100644 GIT binary patch delta 44 xcmaFxTK>&z`Gyw87N!>F7M2#)7Pc+y*Mj&OIT#rj7!)=E2_V>hBZ!@2F#u?>4Q>Dc delta 32 ocmaF!TK>Un`Gyw87N!>F7M2#)7Pc+y*MgWDIJQ3tV&_;40Oe8)ng9R* diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index c344828159..2a22414db6 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -6,9 +6,8 @@ import ( "io" "os" - internalio "github.com/ipld/go-car/v2/internal/io" - "github.com/ipld/go-car/v2/index" + internalio "github.com/ipld/go-car/v2/internal/io" ) // ErrAlreadyV1 signals that the given payload is already in CARv1 format. @@ -47,15 +46,30 @@ func WrapV1File(srcPath, dstPath string) error { // WrapV1 takes a CARv1 file and wraps it as a CARv2 file with an index. // The resulting CARv2 file's inner CARv1 payload is left unmodified, // and does not use any padding before the innner CARv1 or index. -func WrapV1(src io.ReadSeeker, dst io.Writer) error { +func WrapV1(src io.ReadSeeker, dst io.Writer, opts ...ReadWriteOption) error { // TODO: verify src is indeed a CARv1 to prevent misuse. // GenerateIndex should probably be in charge of that. - idx, err := GenerateIndex(src) + var ro []ReadOption + var wo []WriteOption + for _, opt := range opts { + switch opt := opt.(type) { + case ReadOption: + ro = append(ro, opt) + case WriteOption: + wo = append(wo, opt) + } + } + wopts := ApplyWriteOptions(wo...) + idx, err := index.New(wopts.IndexCodec) if err != nil { return err } + if err := LoadIndex(idx, src, ro...); err != nil { + return err + } + // Use Seek to learn the size of the CARv1 before reading it. v1Size, err := src.Seek(0, io.SeekEnd) if err != nil { From 80c32df9e5ec1d23aff502a0bd1e36d0f2ab65f1 Mon Sep 17 00:00:00 2001 From: Will Date: Wed, 8 Sep 2021 08:38:10 -0700 Subject: [PATCH 162/291] Add `car split` command (#226) Add `car split` command to detach index This commit was moved from ipld/go-car@385b612ddece835b9f44ed585d6bf492452f98d5 --- ipld/car/v2/cmd/car/car.go | 142 ++++------------------------------- ipld/car/v2/cmd/car/index.go | 131 ++++++++++++++++++++++++++++++++ ipld/car/v2/cmd/car/split.go | 35 +++++++++ 3 files changed, 180 insertions(+), 128 deletions(-) create mode 100644 ipld/car/v2/cmd/car/index.go create mode 100644 ipld/car/v2/cmd/car/split.go diff --git a/ipld/car/v2/cmd/car/car.go b/ipld/car/v2/cmd/car/car.go index 586cd75f7e..9f481cb413 100644 --- a/ipld/car/v2/cmd/car/car.go +++ b/ipld/car/v2/cmd/car/car.go @@ -1,18 +1,10 @@ package main import ( - "bufio" - "fmt" - "io" "log" "os" - "github.com/ipfs/go-cid" - carv2 "github.com/ipld/go-car/v2" - "github.com/ipld/go-car/v2/index" - icarv1 "github.com/ipld/go-car/v2/internal/carv1" "github.com/multiformats/go-multicodec" - "github.com/multiformats/go-varint" "github.com/urfave/cli/v2" ) @@ -20,133 +12,27 @@ func main() { app := &cli.App{ Name: "car", Usage: "Utility for working with car files", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "codec", - Aliases: []string{"c"}, - Usage: "The type of index to write", - Value: multicodec.CarMultihashIndexSorted.String(), - }, - }, Commands: []*cli.Command{ { Name: "index", Aliases: []string{"i"}, Usage: "write out the car with an index", - Action: func(c *cli.Context) error { - r, err := carv2.OpenReader(c.Args().Get(0)) - if err != nil { - return err - } - defer r.Close() - - var idx index.Index - if c.String("codec") != "none" { - var mc multicodec.Code - if err := mc.Set(c.String("codec")); err != nil { - return err - } - idx, err = index.New(mc) - if err != nil { - return err - } - } - - outStream := os.Stdout - if c.Args().Len() >= 2 { - outStream, err = os.Create(c.Args().Get(1)) - if err != nil { - return err - } - } - defer outStream.Close() - - v1r := r.DataReader() - - v2Header := carv2.NewHeader(r.Header.DataSize) - if c.String("codec") == "none" { - v2Header.IndexOffset = 0 - if _, err := outStream.Write(carv2.Pragma); err != nil { - return err - } - if _, err := v2Header.WriteTo(outStream); err != nil { - return err - } - if _, err := io.Copy(outStream, v1r); err != nil { - return err - } - return nil - } - - if _, err := outStream.Write(carv2.Pragma); err != nil { - return err - } - if _, err := v2Header.WriteTo(outStream); err != nil { - return err - } - - // collect records as we go through the v1r - hdr, err := icarv1.ReadHeader(v1r) - if err != nil { - return fmt.Errorf("error reading car header: %w", err) - } - if err := icarv1.WriteHeader(hdr, outStream); err != nil { - return err - } - - records := make([]index.Record, 0) - var sectionOffset int64 - if sectionOffset, err = v1r.Seek(0, io.SeekCurrent); err != nil { - return err - } - - br := bufio.NewReader(v1r) - for { - // Read the section's length. - sectionLen, err := varint.ReadUvarint(br) - if err != nil { - if err == io.EOF { - break - } - return err - } - if _, err := outStream.Write(varint.ToUvarint(sectionLen)); err != nil { - return err - } - - // Null padding; by default it's an error. - // TODO: integrate corresponding ReadOption - if sectionLen == 0 { - // TODO: pad writer to expected length. - break - } - - // Read the CID. - cidLen, c, err := cid.CidFromReader(br) - if err != nil { - return err - } - records = append(records, index.Record{Cid: c, Offset: uint64(sectionOffset)}) - if _, err := c.WriteBytes(outStream); err != nil { - return err - } - - // Seek to the next section by skipping the block. - // The section length includes the CID, so subtract it. - remainingSectionLen := int64(sectionLen) - int64(cidLen) - if _, err := io.CopyN(outStream, br, remainingSectionLen); err != nil { - return err - } - sectionOffset += int64(sectionLen) + int64(varint.UvarintSize(sectionLen)) - } - - if err := idx.Load(records); err != nil { - return err - } - - return index.WriteTo(idx, outStream) + Action: IndexCar, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "codec", + Aliases: []string{"c"}, + Usage: "The type of index to write", + Value: multicodec.CarMultihashIndexSorted.String(), + }, }, }, + { + Name: "split", + Aliases: []string{"s"}, + Usage: "Split an index to a detached file", + Action: SplitCar, + }, }, } diff --git a/ipld/car/v2/cmd/car/index.go b/ipld/car/v2/cmd/car/index.go new file mode 100644 index 0000000000..4470e3d0e4 --- /dev/null +++ b/ipld/car/v2/cmd/car/index.go @@ -0,0 +1,131 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "os" + + "github.com/ipfs/go-cid" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" + icarv1 "github.com/ipld/go-car/v2/internal/carv1" + "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-varint" + "github.com/urfave/cli/v2" +) + +// IndexCar is a command to add an index to a car +func IndexCar(c *cli.Context) error { + r, err := carv2.OpenReader(c.Args().Get(0)) + if err != nil { + return err + } + defer r.Close() + + var idx index.Index + if c.String("codec") != "none" { + var mc multicodec.Code + if err := mc.Set(c.String("codec")); err != nil { + return err + } + idx, err = index.New(mc) + if err != nil { + return err + } + } + + outStream := os.Stdout + if c.Args().Len() >= 2 { + outStream, err = os.Create(c.Args().Get(1)) + if err != nil { + return err + } + } + defer outStream.Close() + + v1r := r.DataReader() + + v2Header := carv2.NewHeader(r.Header.DataSize) + if c.String("codec") == "none" { + v2Header.IndexOffset = 0 + if _, err := outStream.Write(carv2.Pragma); err != nil { + return err + } + if _, err := v2Header.WriteTo(outStream); err != nil { + return err + } + if _, err := io.Copy(outStream, v1r); err != nil { + return err + } + return nil + } + + if _, err := outStream.Write(carv2.Pragma); err != nil { + return err + } + if _, err := v2Header.WriteTo(outStream); err != nil { + return err + } + + // collect records as we go through the v1r + hdr, err := icarv1.ReadHeader(v1r) + if err != nil { + return fmt.Errorf("error reading car header: %w", err) + } + if err := icarv1.WriteHeader(hdr, outStream); err != nil { + return err + } + + records := make([]index.Record, 0) + var sectionOffset int64 + if sectionOffset, err = v1r.Seek(0, io.SeekCurrent); err != nil { + return err + } + + br := bufio.NewReader(v1r) + for { + // Read the section's length. + sectionLen, err := varint.ReadUvarint(br) + if err != nil { + if err == io.EOF { + break + } + return err + } + if _, err := outStream.Write(varint.ToUvarint(sectionLen)); err != nil { + return err + } + + // Null padding; by default it's an error. + // TODO: integrate corresponding ReadOption + if sectionLen == 0 { + // TODO: pad writer to expected length. + break + } + + // Read the CID. + cidLen, c, err := cid.CidFromReader(br) + if err != nil { + return err + } + records = append(records, index.Record{Cid: c, Offset: uint64(sectionOffset)}) + if _, err := c.WriteBytes(outStream); err != nil { + return err + } + + // Seek to the next section by skipping the block. + // The section length includes the CID, so subtract it. + remainingSectionLen := int64(sectionLen) - int64(cidLen) + if _, err := io.CopyN(outStream, br, remainingSectionLen); err != nil { + return err + } + sectionOffset += int64(sectionLen) + int64(varint.UvarintSize(sectionLen)) + } + + if err := idx.Load(records); err != nil { + return err + } + + return index.WriteTo(idx, outStream) +} diff --git a/ipld/car/v2/cmd/car/split.go b/ipld/car/v2/cmd/car/split.go new file mode 100644 index 0000000000..7733e19c34 --- /dev/null +++ b/ipld/car/v2/cmd/car/split.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + "io" + "os" + + carv2 "github.com/ipld/go-car/v2" + "github.com/urfave/cli/v2" +) + +// SplitCar is a command to output the index part of a car. +func SplitCar(c *cli.Context) error { + r, err := carv2.OpenReader(c.Args().Get(0)) + if err != nil { + return err + } + defer r.Close() + + if !r.Header.HasIndex() { + return fmt.Errorf("no index present") + } + + outStream := os.Stdout + if c.Args().Len() >= 2 { + outStream, err = os.Create(c.Args().Get(1)) + if err != nil { + return err + } + } + defer outStream.Close() + + _, err = io.Copy(outStream, r.IndexReader()) + return err +} From 0828bdc3116891b09708fb29c9981326dd639775 Mon Sep 17 00:00:00 2001 From: Will Date: Fri, 10 Sep 2021 02:17:31 -0700 Subject: [PATCH 163/291] Add `list` and `filter` commands (#227) * better name for detaching index command * add list and filter commands This commit was moved from ipld/go-car@afcc7b3d15e2d402035ea2ca447d102a3f7468fb --- ipld/car/v2/cmd/car/car.go | 26 +++++- ipld/car/v2/cmd/car/{split.go => detach.go} | 4 +- ipld/car/v2/cmd/car/filter.go | 98 +++++++++++++++++++++ ipld/car/v2/cmd/car/list.go | 53 +++++++++++ 4 files changed, 175 insertions(+), 6 deletions(-) rename ipld/car/v2/cmd/car/{split.go => detach.go} (83%) create mode 100644 ipld/car/v2/cmd/car/filter.go create mode 100644 ipld/car/v2/cmd/car/list.go diff --git a/ipld/car/v2/cmd/car/car.go b/ipld/car/v2/cmd/car/car.go index 9f481cb413..df9629386e 100644 --- a/ipld/car/v2/cmd/car/car.go +++ b/ipld/car/v2/cmd/car/car.go @@ -28,10 +28,28 @@ func main() { }, }, { - Name: "split", - Aliases: []string{"s"}, - Usage: "Split an index to a detached file", - Action: SplitCar, + Name: "detach-index", + Usage: "Detach an index to a detached file", + Action: DetachCar, + }, + { + Name: "list", + Aliases: []string{"l"}, + Usage: "List the CIDs in a car", + Action: ListCar, + }, + { + Name: "filter", + Aliases: []string{"f"}, + Usage: "Filter the CIDs in a car", + Action: FilterCar, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "cid-file", + Usage: "A file to read CIDs from", + TakesFile: true, + }, + }, }, }, } diff --git a/ipld/car/v2/cmd/car/split.go b/ipld/car/v2/cmd/car/detach.go similarity index 83% rename from ipld/car/v2/cmd/car/split.go rename to ipld/car/v2/cmd/car/detach.go index 7733e19c34..276d73b47e 100644 --- a/ipld/car/v2/cmd/car/split.go +++ b/ipld/car/v2/cmd/car/detach.go @@ -9,8 +9,8 @@ import ( "github.com/urfave/cli/v2" ) -// SplitCar is a command to output the index part of a car. -func SplitCar(c *cli.Context) error { +// DetachCar is a command to output the index part of a car. +func DetachCar(c *cli.Context) error { r, err := carv2.OpenReader(c.Args().Get(0)) if err != nil { return err diff --git a/ipld/car/v2/cmd/car/filter.go b/ipld/car/v2/cmd/car/filter.go new file mode 100644 index 0000000000..cb58b4c156 --- /dev/null +++ b/ipld/car/v2/cmd/car/filter.go @@ -0,0 +1,98 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "strings" + + "github.com/ipfs/go-cid" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" + icarv1 "github.com/ipld/go-car/v2/internal/carv1" + "github.com/urfave/cli/v2" +) + +// FilterCar is a command to select a subset of a car by CID. +func FilterCar(c *cli.Context) error { + r, err := carv2.OpenReader(c.Args().Get(0)) + if err != nil { + return err + } + defer r.Close() + + if c.Args().Len() < 2 { + return fmt.Errorf("an output filename must be provided") + } + roots, err := r.Roots() + if err != nil { + return err + } + bs, err := blockstore.OpenReadWrite(c.Args().Get(1), roots) + if err != nil { + return err + } + + // Get the set of CIDs from stdin. + inStream := os.Stdin + if c.IsSet("cidFile") { + inStream, err = os.Open(c.String("cidFile")) + if err != nil { + return err + } + defer inStream.Close() + } + cidMap, err := parseCIDS(inStream) + if err != nil { + return err + } + fmt.Printf("filtering to %d cids\n", len(cidMap)) + + rd, err := icarv1.NewCarReader(r.DataReader()) + if err != nil { + return err + } + + for { + blk, err := rd.Next() + if err != nil { + if err == io.EOF { + break + } + return err + } + if _, ok := cidMap[blk.Cid()]; ok { + if err := bs.Put(blk); err != nil { + return err + } + } + } + return bs.Finalize() +} + +func parseCIDS(r io.Reader) (map[cid.Cid]struct{}, error) { + cids := make(map[cid.Cid]struct{}) + br := bufio.NewReader(r) + for { + line, _, err := br.ReadLine() + if err != nil { + if err == io.EOF { + return cids, nil + } + return nil, err + } + trimLine := strings.TrimSpace(string(line)) + if len(trimLine) == 0 { + continue + } + c, err := cid.Parse(trimLine) + if err != nil { + return nil, err + } + if _, ok := cids[c]; ok { + fmt.Fprintf(os.Stderr, "duplicate cid: %s\n", c) + } + cids[c] = struct{}{} + } +} diff --git a/ipld/car/v2/cmd/car/list.go b/ipld/car/v2/cmd/car/list.go new file mode 100644 index 0000000000..e9cf1f7e1c --- /dev/null +++ b/ipld/car/v2/cmd/car/list.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "io" + "os" + + carv2 "github.com/ipld/go-car/v2" + "github.com/urfave/cli/v2" +) + +// ListCar is a command to output the cids in a car. +func ListCar(c *cli.Context) error { + inStream := os.Stdin + var err error + if c.Args().Len() >= 1 { + inStream, err = os.Open(c.Args().First()) + if err != nil { + return err + } + defer inStream.Close() + } + rd, err := carv2.NewBlockReader(inStream) + if err != nil { + return err + } + + outStream := os.Stdout + if c.Args().Len() >= 2 { + outStream, err = os.Create(c.Args().Get(1)) + if err != nil { + return err + } + } + defer outStream.Close() + + if err != nil { + return err + } + + for { + blk, err := rd.Next() + if err != nil { + if err == io.EOF { + break + } + return err + } + fmt.Fprintf(outStream, "%s\n", blk.Cid()) + } + + return err +} From 71afb300d38f0b7772ffe5ff6b60a7553e6202c4 Mon Sep 17 00:00:00 2001 From: Will Date: Fri, 10 Sep 2021 06:53:19 -0700 Subject: [PATCH 164/291] add interface describing iteration (#228) * add interface describing iteration This commit was moved from ipld/go-car@1220a0ccb28ea7da8d08ab89377a037ce5a36650 --- ipld/car/v2/index/index.go | 17 +++++++++++++++++ ipld/car/v2/index/mhindexsorted.go | 1 + 2 files changed, 18 insertions(+) diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 071e8e6e1e..8abd1d3a30 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -8,6 +8,7 @@ import ( internalio "github.com/ipld/go-car/v2/internal/io" "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-multihash" "github.com/multiformats/go-varint" @@ -58,6 +59,22 @@ type ( // ErrNotFound is returned. GetAll(cid.Cid, func(uint64) bool) error } + + // IterableIndex extends Index in cases where the Index is able to + // provide an iterator for getting the list of all entries in the + // index. + IterableIndex interface { + Index + + // ForEach takes a callback function that will be called + // on each entry in the index. The arguments to the callback are + // the multihash of the element, and the offset in the car file + // where the element appears. + // + // If the callback returns a non-nil error, the iteration is aborted, + // and the ForEach function returns the error to the user. + ForEach(func(multihash.Multihash, uint64) error) error + } ) // GetFirst is a wrapper over Index.GetAll, returning the offset for the first diff --git a/ipld/car/v2/index/mhindexsorted.go b/ipld/car/v2/index/mhindexsorted.go index 75d3097163..15a731c80f 100644 --- a/ipld/car/v2/index/mhindexsorted.go +++ b/ipld/car/v2/index/mhindexsorted.go @@ -11,6 +11,7 @@ import ( ) var _ Index = (*MultihashIndexSorted)(nil) +var _ IterableIndex = (*MultihashIndexSorted)(nil) type ( // MultihashIndexSorted maps multihash code (i.e. hashing algorithm) to multiWidthCodedIndex. From a61644972f168c6a8bbddc9484e5960761fe6210 Mon Sep 17 00:00:00 2001 From: Will Date: Sat, 11 Sep 2021 08:49:33 -0700 Subject: [PATCH 165/291] use file size when loading from v1 car (#229) This commit was moved from ipld/go-car@44693776b044002eaf45988ddb82e514749ded5d --- ipld/car/v2/cmd/car/index.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ipld/car/v2/cmd/car/index.go b/ipld/car/v2/cmd/car/index.go index 4470e3d0e4..13836c6f33 100644 --- a/ipld/car/v2/cmd/car/index.go +++ b/ipld/car/v2/cmd/car/index.go @@ -46,6 +46,13 @@ func IndexCar(c *cli.Context) error { v1r := r.DataReader() + if r.Version == 1 { + fi, err := os.Stat(c.Args().Get(0)) + if err != nil { + return err + } + r.Header.DataSize = uint64(fi.Size()) + } v2Header := carv2.NewHeader(r.Header.DataSize) if c.String("codec") == "none" { v2Header.IndexOffset = 0 From 99b49ea6828e0a0816c0ecb7b8ff4f7007c00cdc Mon Sep 17 00:00:00 2001 From: Will Date: Sun, 12 Sep 2021 05:30:33 -0700 Subject: [PATCH 166/291] add `get block` to car cli (#230) add `get block` to car cli for extracting an individual block by CID from a car This commit was moved from ipld/go-car@5390c3359d08412773a688f25c006bc73690c429 --- ipld/car/v2/cmd/car/car.go | 42 ++++++++++++++++++++--------------- ipld/car/v2/cmd/car/get.go | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 ipld/car/v2/cmd/car/get.go diff --git a/ipld/car/v2/cmd/car/car.go b/ipld/car/v2/cmd/car/car.go index df9629386e..5afb3c06fd 100644 --- a/ipld/car/v2/cmd/car/car.go +++ b/ipld/car/v2/cmd/car/car.go @@ -13,6 +13,30 @@ func main() { Name: "car", Usage: "Utility for working with car files", Commands: []*cli.Command{ + { + Name: "detach-index", + Usage: "Detach an index to a detached file", + Action: DetachCar, + }, + { + Name: "filter", + Aliases: []string{"f"}, + Usage: "Filter the CIDs in a car", + Action: FilterCar, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "cid-file", + Usage: "A file to read CIDs from", + TakesFile: true, + }, + }, + }, + { + Name: "get-block", + Aliases: []string{"gb"}, + Usage: "Get a block out of a car", + Action: GetCarBlock, + }, { Name: "index", Aliases: []string{"i"}, @@ -27,30 +51,12 @@ func main() { }, }, }, - { - Name: "detach-index", - Usage: "Detach an index to a detached file", - Action: DetachCar, - }, { Name: "list", Aliases: []string{"l"}, Usage: "List the CIDs in a car", Action: ListCar, }, - { - Name: "filter", - Aliases: []string{"f"}, - Usage: "Filter the CIDs in a car", - Action: FilterCar, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "cid-file", - Usage: "A file to read CIDs from", - TakesFile: true, - }, - }, - }, }, } diff --git a/ipld/car/v2/cmd/car/get.go b/ipld/car/v2/cmd/car/get.go new file mode 100644 index 0000000000..72aaff4ba4 --- /dev/null +++ b/ipld/car/v2/cmd/car/get.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "os" + + "github.com/ipfs/go-cid" + "github.com/ipld/go-car/v2/blockstore" + "github.com/urfave/cli/v2" +) + +// GetCarBlock is a command to get a block out of a car +func GetCarBlock(c *cli.Context) error { + if c.Args().Len() < 2 { + return fmt.Errorf("usage: car get-block [output file]") + } + + bs, err := blockstore.OpenReadOnly(c.Args().Get(0)) + if err != nil { + return err + } + + // string to CID + blkCid, err := cid.Parse(c.Args().Get(1)) + if err != nil { + return err + } + + blk, err := bs.Get(blkCid) + if err != nil { + return err + } + + outStream := os.Stdout + if c.Args().Len() >= 3 { + outStream, err = os.Create(c.Args().Get(2)) + if err != nil { + return err + } + defer outStream.Close() + } + + _, err = outStream.Write(blk.RawData()) + return err +} From 1649aa733ee4f8dc72d00d28d9b2041cacd30528 Mon Sep 17 00:00:00 2001 From: Will Date: Sun, 12 Sep 2021 06:07:35 -0700 Subject: [PATCH 167/291] Separate CLI to separate module (#231) refactor cmd module `v2/cmd/car` -> `cmd/car` This commit was moved from ipld/go-car@d252cb92b99f082f8aa31feca43dd125b99c2342 --- ipld/car/{v2 => }/cmd/car/car.go | 0 ipld/car/{v2 => }/cmd/car/detach.go | 0 ipld/car/{v2 => }/cmd/car/filter.go | 4 ++-- ipld/car/{v2 => }/cmd/car/get.go | 0 ipld/car/{v2 => }/cmd/car/index.go | 9 +++++---- ipld/car/{v2 => }/cmd/car/list.go | 0 6 files changed, 7 insertions(+), 6 deletions(-) rename ipld/car/{v2 => }/cmd/car/car.go (100%) rename ipld/car/{v2 => }/cmd/car/detach.go (100%) rename ipld/car/{v2 => }/cmd/car/filter.go (94%) rename ipld/car/{v2 => }/cmd/car/get.go (100%) rename ipld/car/{v2 => }/cmd/car/index.go (94%) rename ipld/car/{v2 => }/cmd/car/list.go (100%) diff --git a/ipld/car/v2/cmd/car/car.go b/ipld/car/cmd/car/car.go similarity index 100% rename from ipld/car/v2/cmd/car/car.go rename to ipld/car/cmd/car/car.go diff --git a/ipld/car/v2/cmd/car/detach.go b/ipld/car/cmd/car/detach.go similarity index 100% rename from ipld/car/v2/cmd/car/detach.go rename to ipld/car/cmd/car/detach.go diff --git a/ipld/car/v2/cmd/car/filter.go b/ipld/car/cmd/car/filter.go similarity index 94% rename from ipld/car/v2/cmd/car/filter.go rename to ipld/car/cmd/car/filter.go index cb58b4c156..5650065fe8 100644 --- a/ipld/car/v2/cmd/car/filter.go +++ b/ipld/car/cmd/car/filter.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/ipfs/go-cid" + carv1 "github.com/ipld/go-car" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" - icarv1 "github.com/ipld/go-car/v2/internal/carv1" "github.com/urfave/cli/v2" ) @@ -49,7 +49,7 @@ func FilterCar(c *cli.Context) error { } fmt.Printf("filtering to %d cids\n", len(cidMap)) - rd, err := icarv1.NewCarReader(r.DataReader()) + rd, err := carv1.NewCarReader(r.DataReader()) if err != nil { return err } diff --git a/ipld/car/v2/cmd/car/get.go b/ipld/car/cmd/car/get.go similarity index 100% rename from ipld/car/v2/cmd/car/get.go rename to ipld/car/cmd/car/get.go diff --git a/ipld/car/v2/cmd/car/index.go b/ipld/car/cmd/car/index.go similarity index 94% rename from ipld/car/v2/cmd/car/index.go rename to ipld/car/cmd/car/index.go index 13836c6f33..a1fa6d8644 100644 --- a/ipld/car/v2/cmd/car/index.go +++ b/ipld/car/cmd/car/index.go @@ -7,9 +7,9 @@ import ( "os" "github.com/ipfs/go-cid" + carv1 "github.com/ipld/go-car" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/index" - icarv1 "github.com/ipld/go-car/v2/internal/carv1" "github.com/multiformats/go-multicodec" "github.com/multiformats/go-varint" "github.com/urfave/cli/v2" @@ -76,11 +76,12 @@ func IndexCar(c *cli.Context) error { } // collect records as we go through the v1r - hdr, err := icarv1.ReadHeader(v1r) + br := bufio.NewReader(v1r) + hdr, err := carv1.ReadHeader(br) if err != nil { return fmt.Errorf("error reading car header: %w", err) } - if err := icarv1.WriteHeader(hdr, outStream); err != nil { + if err := carv1.WriteHeader(hdr, outStream); err != nil { return err } @@ -89,8 +90,8 @@ func IndexCar(c *cli.Context) error { if sectionOffset, err = v1r.Seek(0, io.SeekCurrent); err != nil { return err } + sectionOffset -= int64(br.Buffered()) - br := bufio.NewReader(v1r) for { // Read the section's length. sectionLen, err := varint.ReadUvarint(br) diff --git a/ipld/car/v2/cmd/car/list.go b/ipld/car/cmd/car/list.go similarity index 100% rename from ipld/car/v2/cmd/car/list.go rename to ipld/car/cmd/car/list.go From 30a38c1093e74a56d6818468ae37f38e33626e71 Mon Sep 17 00:00:00 2001 From: Will Date: Sun, 12 Sep 2021 23:52:40 -0700 Subject: [PATCH 168/291] Add `car get-dag` command (#232) * clean up filter command * Add `get-dag` subcommand This commit was moved from ipld/go-car@c51a79e5fc1cf958015afcc058e17c5cb1648afb --- ipld/car/cmd/car/car.go | 17 ++++++ ipld/car/cmd/car/filter.go | 24 ++++---- ipld/car/cmd/car/get.go | 116 +++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 10 deletions(-) diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 5afb3c06fd..69059b1d38 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -37,6 +37,23 @@ func main() { Usage: "Get a block out of a car", Action: GetCarBlock, }, + { + Name: "get-dag", + Aliases: []string{"gd"}, + Usage: "Get a dag out of a car", + Action: GetCarDag, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "selector", + Aliases: []string{"s"}, + Usage: "A selector over the dag", + }, + &cli.BoolFlag{ + Name: "strict", + Usage: "Fail if the selector finds links to blocks not in the original car", + }, + }, + }, { Name: "index", Aliases: []string{"i"}, diff --git a/ipld/car/cmd/car/filter.go b/ipld/car/cmd/car/filter.go index 5650065fe8..a941a8b369 100644 --- a/ipld/car/cmd/car/filter.go +++ b/ipld/car/cmd/car/filter.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/ipfs/go-cid" - carv1 "github.com/ipld/go-car" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" "github.com/urfave/cli/v2" @@ -16,20 +15,16 @@ import ( // FilterCar is a command to select a subset of a car by CID. func FilterCar(c *cli.Context) error { - r, err := carv2.OpenReader(c.Args().Get(0)) - if err != nil { - return err - } - defer r.Close() - if c.Args().Len() < 2 { return fmt.Errorf("an output filename must be provided") } - roots, err := r.Roots() + + fd, err := os.Open(c.Args().First()) if err != nil { return err } - bs, err := blockstore.OpenReadWrite(c.Args().Get(1), roots) + defer fd.Close() + rd, err := carv2.NewBlockReader(fd) if err != nil { return err } @@ -49,7 +44,16 @@ func FilterCar(c *cli.Context) error { } fmt.Printf("filtering to %d cids\n", len(cidMap)) - rd, err := carv1.NewCarReader(r.DataReader()) + outRoots := make([]cid.Cid, 0) + for _, r := range rd.Roots { + if _, ok := cidMap[r]; ok { + outRoots = append(outRoots, r) + } + } + if len(outRoots) == 0 { + fmt.Fprintf(os.Stderr, "warning: no roots defined after filtering\n") + } + bs, err := blockstore.OpenReadWrite(c.Args().Get(1), outRoots) if err != nil { return err } diff --git a/ipld/car/cmd/car/get.go b/ipld/car/cmd/car/get.go index 72aaff4ba4..9cdc7a4cca 100644 --- a/ipld/car/cmd/car/get.go +++ b/ipld/car/cmd/car/get.go @@ -1,11 +1,29 @@ package main import ( + "bytes" "fmt" + "io" "os" + _ "github.com/ipld/go-codec-dagpb" + _ "github.com/ipld/go-ipld-prime/codec/cbor" + _ "github.com/ipld/go-ipld-prime/codec/dagcbor" + _ "github.com/ipld/go-ipld-prime/codec/dagjson" + _ "github.com/ipld/go-ipld-prime/codec/json" + _ "github.com/ipld/go-ipld-prime/codec/raw" + + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + ipfsbs "github.com/ipfs/go-ipfs-blockstore" "github.com/ipld/go-car/v2/blockstore" + "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/linking" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/node/basicnode" + "github.com/ipld/go-ipld-prime/traversal" + "github.com/ipld/go-ipld-prime/traversal/selector" + selectorParser "github.com/ipld/go-ipld-prime/traversal/selector/parse" "github.com/urfave/cli/v2" ) @@ -43,3 +61,101 @@ func GetCarBlock(c *cli.Context) error { _, err = outStream.Write(blk.RawData()) return err } + +// GetCarDag is a command to get a dag out of a car +func GetCarDag(c *cli.Context) error { + if c.Args().Len() < 3 { + return fmt.Errorf("usage: car get-dag [-s selector] ") + } + + bs, err := blockstore.OpenReadOnly(c.Args().Get(0)) + if err != nil { + return err + } + + // string to CID + blkCid, err := cid.Parse(c.Args().Get(1)) + if err != nil { + return err + } + + outStore, err := blockstore.OpenReadWrite(c.Args().Get(2), []cid.Cid{blkCid}) + if err != nil { + return err + } + + ls := cidlink.DefaultLinkSystem() + ls.StorageReadOpener = func(_ linking.LinkContext, l datamodel.Link) (io.Reader, error) { + if cl, ok := l.(*cidlink.Link); ok { + blk, err := bs.Get(cl.Cid) + if err != nil { + if err == ipfsbs.ErrNotFound { + if c.Bool("strict") { + return nil, err + } + return nil, traversal.SkipMe{} + } + return nil, err + } + return bytes.NewBuffer(blk.RawData()), nil + } + return nil, fmt.Errorf("unknown link type: %T", l) + } + ls.StorageWriteOpener = func(_ linking.LinkContext) (io.Writer, linking.BlockWriteCommitter, error) { + buf := bytes.NewBuffer(nil) + return buf, func(l datamodel.Link) error { + if cl, ok := l.(*cidlink.Link); ok { + blk, err := blocks.NewBlockWithCid(buf.Bytes(), cl.Cid) + if err != nil { + return err + } + return outStore.Put(blk) + } + return fmt.Errorf("unknown link type: %T", l) + }, nil + } + + rootlnk := cidlink.Link{ + Cid: blkCid, + } + node, err := ls.Load(linking.LinkContext{}, rootlnk, basicnode.Prototype.Any) + if err != nil { + return err + } + + // selector traversal + s := selectorParser.CommonSelector_MatchAllRecursively + if c.IsSet("selector") { + sn, err := selectorParser.ParseJSONSelector(c.String("selector")) + if err != nil { + return err + } + s, err = selector.CompileSelector(sn) + if err != nil { + return err + } + } + + lnkProto := cidlink.LinkPrototype{ + Prefix: blkCid.Prefix(), + } + err = traversal.WalkMatching(node, s, func(p traversal.Progress, n datamodel.Node) error { + if p.LastBlock.Link != nil { + if cl, ok := p.LastBlock.Link.(*cidlink.Link); ok { + lnkProto = cidlink.LinkPrototype{ + Prefix: cl.Prefix(), + } + } + } + _, err = ls.Store(linking.LinkContext{}, lnkProto, n) + if err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + + return outStore.Finalize() +} From ddf7bef0fe53e9fe473b64a635ac8062a2a0d8ba Mon Sep 17 00:00:00 2001 From: Will Date: Tue, 14 Sep 2021 00:15:49 -0700 Subject: [PATCH 169/291] integrate `car/` cli into `cmd/car` (#233) * integrate `car/` cli into `cmd/car` This commit was moved from ipld/go-car@4fd3550e490d9425190b9d1562e136da68f8785a --- ipld/car/car/main.go | 114 ------------------------------------- ipld/car/cmd/car/car.go | 6 ++ ipld/car/cmd/car/verify.go | 99 ++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 114 deletions(-) delete mode 100644 ipld/car/car/main.go create mode 100644 ipld/car/cmd/car/verify.go diff --git a/ipld/car/car/main.go b/ipld/car/car/main.go deleted file mode 100644 index e1be8830ee..0000000000 --- a/ipld/car/car/main.go +++ /dev/null @@ -1,114 +0,0 @@ -package main - -import ( - "bufio" - "encoding/json" - "fmt" - "io" - "os" - - "github.com/ipld/go-car" - - cli "github.com/urfave/cli" -) - -var headerCmd = cli.Command{ - Name: "header", - Action: func(c *cli.Context) error { - if !c.Args().Present() { - return fmt.Errorf("must pass a car file to inspect") - } - arg := c.Args().First() - - fi, err := os.Open(arg) - if err != nil { - return err - } - defer fi.Close() - - ch, err := car.ReadHeader(bufio.NewReader(fi)) - if err != nil { - return err - } - - b, err := json.MarshalIndent(ch, "", " ") - if err != nil { - return err - } - fmt.Println(string(b)) - return nil - }, -} - -var verifyCmd = cli.Command{ - Name: "verify", - Action: func(c *cli.Context) error { - if !c.Args().Present() { - return fmt.Errorf("must pass a car file to inspect") - } - arg := c.Args().First() - - fi, err := os.Open(arg) - if err != nil { - return err - } - defer fi.Close() - - cr, err := car.NewCarReader(fi) - if err != nil { - return err - } - - for { - _, err := cr.Next() - if err == io.EOF { - return nil - } - if err != nil { - return err - } - } - }, -} - -var lsCmd = cli.Command{ - Name: "ls", - Action: func(c *cli.Context) error { - if !c.Args().Present() { - return fmt.Errorf("must pass a car file to inspect") - } - arg := c.Args().First() - - fi, err := os.Open(arg) - if err != nil { - return err - } - defer fi.Close() - - cr, err := car.NewCarReader(fi) - if err != nil { - return err - } - - for { - blk, err := cr.Next() - if err == io.EOF { - return nil - } - if err != nil { - return err - } - fmt.Println(blk.Cid()) - } - }, -} - -func main() { - app := cli.NewApp() - app.Commands = []cli.Command{ - headerCmd, - lsCmd, - verifyCmd, - } - app.Run(os.Args) -} diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 69059b1d38..523372ed0b 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -74,6 +74,12 @@ func main() { Usage: "List the CIDs in a car", Action: ListCar, }, + { + Name: "verify", + Aliases: []string{"v"}, + Usage: "Verify a CAR is wellformed", + Action: VerifyCar, + }, }, } diff --git a/ipld/car/cmd/car/verify.go b/ipld/car/cmd/car/verify.go new file mode 100644 index 0000000000..c9c753d783 --- /dev/null +++ b/ipld/car/cmd/car/verify.go @@ -0,0 +1,99 @@ +package main + +import ( + "fmt" + "io" + "os" + + "github.com/ipfs/go-cid" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" + "github.com/urfave/cli/v2" +) + +// VerifyCar is a command to check a files validity +func VerifyCar(c *cli.Context) error { + if c.Args().Len() == 0 { + return fmt.Errorf("usage: car verify ") + } + + // header + rx, err := carv2.OpenReader(c.Args().First()) + if err != nil { + return err + } + defer rx.Close() + roots, err := rx.Roots() + if err != nil { + return err + } + if len(roots) == 0 { + return fmt.Errorf("no roots listed in car header") + } + rootMap := make(map[cid.Cid]struct{}) + for _, r := range roots { + rootMap[r] = struct{}{} + } + + if rx.Version == 2 { + if rx.Header.DataSize == 0 { + return fmt.Errorf("size of wrapped v1 car listed as '0'") + } + + flen, err := os.Stat(c.Args().First()) + if err != nil { + return err + } + lengthToIndex := carv2.PragmaSize + carv2.HeaderSize + rx.Header.DataOffset + rx.Header.DataSize + if uint64(flen.Size()) > lengthToIndex && rx.Header.IndexOffset == 0 { + return fmt.Errorf("header claims no index, but extra bytes in file beyond data size") + } + if rx.Header.IndexOffset < lengthToIndex { + return fmt.Errorf("index offset overlaps with data") + } + } + + // blocks + fd, err := os.Open(c.Args().First()) + if err != nil { + return err + } + rd, err := carv2.NewBlockReader(fd) + if err != nil { + return err + } + + cidList := make([]cid.Cid, 0) + for { + blk, err := rd.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + delete(rootMap, blk.Cid()) + cidList = append(cidList, blk.Cid()) + } + + if len(rootMap) > 0 { + return fmt.Errorf("header lists root(s) not present as a block: %v", rootMap) + } + + // index + if rx.Version == 2 && rx.Header.HasIndex() { + idx, err := index.ReadFrom(rx.IndexReader()) + if err != nil { + return err + } + for _, c := range cidList { + if err := idx.GetAll(c, func(_ uint64) bool { + return true + }); err != nil { + return fmt.Errorf("could not look up known cid %s in index: %w", c, err) + } + } + } + + return nil +} From 5b9ab3208a53515b6fb171c5916bbae89d750457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 13 Sep 2021 14:32:29 +0200 Subject: [PATCH 170/291] cmd/car: add first testscript tests As an introductory proof of concept, covering "list" and "get-block". The script language is shell-like, with some test helpers, and narrowed down to just a linear list of commands. See: https://pkg.go.dev/github.com/rogpeppe/go-internal/testscript This commit was moved from ipld/go-car@7544041c42bbf97c169fdd2440ac18e7898530d3 --- ipld/car/cmd/car/car.go | 9 +++-- ipld/car/cmd/car/script_test.go | 34 ++++++++++++++++++ ...x4qmtwgxixsd7pn4tlanyrc3g3hwj75hlxrw.block | Bin 0 -> 907 bytes .../car/cmd/car/testdata/inputs/sample-v1.car | Bin 0 -> 479907 bytes .../car/testdata/inputs/sample-wrapped-v2.car | Bin 0 -> 521708 bytes .../car/cmd/car/testdata/script/get-block.txt | 19 ++++++++++ ipld/car/cmd/car/testdata/script/list.txt | 16 +++++++++ 7 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 ipld/car/cmd/car/script_test.go create mode 100644 ipld/car/cmd/car/testdata/inputs/bafy2bzacebohz654namrgmwjjx4qmtwgxixsd7pn4tlanyrc3g3hwj75hlxrw.block create mode 100644 ipld/car/cmd/car/testdata/inputs/sample-v1.car create mode 100644 ipld/car/cmd/car/testdata/inputs/sample-wrapped-v2.car create mode 100644 ipld/car/cmd/car/testdata/script/get-block.txt create mode 100644 ipld/car/cmd/car/testdata/script/list.txt diff --git a/ipld/car/cmd/car/car.go b/ipld/car/cmd/car/car.go index 523372ed0b..12a1f1f1b5 100644 --- a/ipld/car/cmd/car/car.go +++ b/ipld/car/cmd/car/car.go @@ -8,7 +8,9 @@ import ( "github.com/urfave/cli/v2" ) -func main() { +func main() { os.Exit(main1()) } + +func main1() int { app := &cli.App{ Name: "car", Usage: "Utility for working with car files", @@ -85,7 +87,8 @@ func main() { err := app.Run(os.Args) if err != nil { - log.Fatal(err) - os.Exit(1) + log.Println(err) + return 1 } + return 0 } diff --git a/ipld/car/cmd/car/script_test.go b/ipld/car/cmd/car/script_test.go new file mode 100644 index 0000000000..dcf8f6b7a8 --- /dev/null +++ b/ipld/car/cmd/car/script_test.go @@ -0,0 +1,34 @@ +package main + +import ( + "flag" + "os" + "path/filepath" + "testing" + + "github.com/rogpeppe/go-internal/testscript" +) + +func TestMain(m *testing.M) { + os.Exit(testscript.RunMain(m, map[string]func() int{ + "car": main1, + })) +} + +var update = flag.Bool("u", false, "update testscript output files") + +func TestScript(t *testing.T) { + t.Parallel() + testscript.Run(t, testscript.Params{ + Dir: filepath.Join("testdata", "script"), + Setup: func(env *testscript.Env) error { + wd, err := os.Getwd() + if err != nil { + return err + } + env.Setenv("INPUTS", filepath.Join(wd, "testdata", "inputs")) + return nil + }, + UpdateScripts: *update, + }) +} diff --git a/ipld/car/cmd/car/testdata/inputs/bafy2bzacebohz654namrgmwjjx4qmtwgxixsd7pn4tlanyrc3g3hwj75hlxrw.block b/ipld/car/cmd/car/testdata/inputs/bafy2bzacebohz654namrgmwjjx4qmtwgxixsd7pn4tlanyrc3g3hwj75hlxrw.block new file mode 100644 index 0000000000000000000000000000000000000000..257d8522bd8bdf42b89f1f4b072bf1fb30bbee49 GIT binary patch literal 907 zcmeBmW;n~#7?H3-PVc86|Lt0X2=~1wuS{KZbQ@F6#Z||A%9l0%nHlhr^G(g4+y}qx ztTnf*%4O8vN?_jM{+X*=L*eb;cN#bD*riEpepdE15HaX3n8@wNztQiv#Ca2yskt1_ z`;wyGwme?a9C2XLv7G0HTB3{ZG0Qsde$zEw@6~&Uy4;sv4v9QGC%nf|_5a1UTW5a~ zILhy&tQlk)Zo2%qT~%T~_qWv#zW*!Qr~T{r0ofz9HP>q9u*p2FV4S$=AWucHQo|gU z;5jbyyz3;4Mckc#pXR+d#e2cQ+4)+h)i-)S3i4AqWbWW7Eb;$OwS>f38S(mbt11lZyywxryT#Vi}gX(%_LTx=ZmN93(PPma*-BYC(l}> z8uYlGEh1sPnL??;Dc8E-xc0J$?8CP;;x8Skn>~5c+0`HQxtk816|V{F+P`q!niV^y zP3~Q`bgHoKn=394-mCj_%*s8q;?1+z#|budx4pME2CgmR$X46fCZ+6V@;dsG%gOJ? zIm&Iajm;4Xe-=kvnJVwI=-+=`_u!dsTCqBJcYNJ9J0VRm;{M%?#gdW&%#6)9v?A0Q z7z-CXVN!^9uwA9&eR%eajqAQN*#!!f#byQYh6rn?3b@?**JtaLoOXIpycQrYx|OaS4&`YW{j>itL28)YSJ;AH9&2_U7|(K5#W_-O-pa^=#Pk zclzfN7}VY=uW^a3_@Hp`ltV-!(*XwB0;xDz^%X#S))_nAd(xSGlvifXf&;nyX1A=g zV{V_#Uv~Ji&+%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� 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 Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ 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�))Ql(v8hmmZlds4Ky~+?@9HxaF6h_xH{) z0^OkI4z=r2Qqicgi##AQ*FT9yFXRX4V8MOFbAQpd zvUu1nulZq7U&!?#Z7g+!H0+3%#ugXq~?$zttuZDN)JH~VIs>#bd{x7V!eBEAlR zYJT;un3Z$NAR1%Y#&Msf2BA+_lUi6q`}B>*I4f|WdEms(el@vHa{{GppGe7M=y3w3=Y8!HY06_u%iC7er* z8YtTmtYq1xF1_m_mLYkU=iAks?A8a=E9Ww>cdd<=XL;v5yGX(17Xi{J_v%3ab+&ze zQ4hkTF4E^YSGQKZPB}|K&2dx0q1YU|PTD6TsZ9ju8N}T{a{JV(NZPzjCUIDz30-(v z_@Rk<>W!h&C31dsrDEFK&z^T%eyaxoBnWZ0y$9yWTg;=b&v(y-HC$|o#O@-j*+C=7 z;=8|oY)*R@T#Xbc^%MM&-?cTU=&l*s4EZUTgzJy!oKf<+hVYJ&^*LYbWtPI`%iud{ z0CGCPN>UqJrty2k$x~&V$x|uHt=$^?mA3>K$a${8QpYu)(*qZr0Z7&aE7=ghi&Tow zkvBZUy=hz`f&OtPV}h|Q$>dy#2b-2Ok%1aDP!{H%yMZQ*eSUfSk;6uw{H93UsrL`d z}v(JBAdeZ4TW-N91?P?jfH$y>%3SzUe;=k&8z zE=WWy)|`^LyqkV5y&@v_<`|l0nTi@UQ2HiViF@dydB#g7qDxe|1B*BM9>U!b>~<4* zqld?R%MPQb$q3vMfV|8-cLPoO`}`uk(UN9usFZqYNfW!VN=^W@n@&b2mQet@sp}wa zd)Djfam{z{?hLQ^xUKnEn ztxSbcoJULMOq&vyZ^A-#mq+5$EGSX57nGx{zQLDg#!UFl?fsK^G?=1NKG@i^vG^08 zIMIX#CA#r&tLYb2UTi^OIkRYTV7tuNs@f!8p#O``?xrtp?tC@SpI*>xw^Pv7pu5oqM zP4LyT0BN(}-g6>cpu}1zYtRExr>tQzj=Q> z$CA6>o_Bm#RGxI5bEFy*+*W`L*1c&9sI%?!iwyfcCR@SsyMt|=))H_7cfEE;d^UnG z=mEqSkaY7k}k$yA{V+>ytQ-`ou( z2yu7kX7-G{Qe18oi$G(hGTl?t>*62V4T?2VZ3;d_)a5FGQ3@dI_Oqu}q>LsJnA?VD zm=e|vsOdD@bIS1`tmt1Ete~mGCx#;4wzR$kCC@u2VN42guW_M&Vwq z?Wa5=|32-5fuI^SP@XJUNhvXF0S|wZgeuGU$<}06%S?ht>?sa!b;;Gc^U{Vo!8I;{ z5@7e-4K!iw^NZo#o`H17;Fu=R)tVeH@k~OIoDTxgmG9@C&GRiSJRpa-8%S=SS`tRd zbwVLR!nB=IM3;fvA-eSN-ihmT!%Ye;l)|_}r@y%yND$&~7y%T`Z8JSnA^gXEHN2gY zpNM2|a7pR?wKxK_>Fph)!RrZtY*Fw>zJ9fazDvP(m3G|(PwGR=1d87j0%6e7Cc<>@ zR!S?L02mtp(m=sV;=FILFDReAAYH$3t4UR`pgQzA;)Q7Beib^xk7$~-;5*!a@;kvw z6pI7kqT|v#k1gFho6YjH7Bo@Q+T^fUKl?$V!tEr{!337A{flJ=N!$lOB97 zrR#M>>_W4-mU-_pzquPo5aMnHztTKitx1#0p0~^HyM1ZKDjV3&x$nYRNqIYNzp~I* zqXtSg1%G6W?9u=alUgS!j1Z~fK^j`P8*DHSqgyDB6L>LcTw??)0m>o;E2&US7F4TS zJC<@GQxE;vRT%UK*KcbnWS$LpEzK#0zs;sb4U`@VR#F*pMFk$~<(o>ZlKxkNWY^|L zb@C`raW537@CYU~#e)$6DDTsGFNz2hQrTDIvH;sHScmpB`trRKI>z_6ADS^xv(5Vk z_teq~hM`#?jzG}|ep(z^g8XuaoGcDn?1#FmEd=Q^G%)v0xIVYN;Hl*OEv5maa$naF z)*>k}vo}HAa=biT8fUzD?|v9lkOSd(G%nnk6#K+;kRAslw@>X!whWW?YZqmy7=+>$ zhd+|Mf~~FP8dg^}%G6=Vi^AHi37hN;B z>Ar&0|KbcfxG*(91}xamXNyi@nXfy!#j#HJ!rq!d#=ToK>)r!dbIPc$Er~ z>n5;gQAiTawXv!QYlC#JunsLez})=2bOK67-J4*5y2U=f^x8@A!yo5qF_$1IrqN=> zd5TKYsqOUoR=6%=)xvOgFT~wIa{JUqW2Y`4Bw}V?8Dr%$oM}>W%qI3QwgMjtz~f_T z)EUqD=58QCh`aCCozUXUihTwjYw(J*yV?8A(VQ$ znY+-RJ)op-*{FMUBdSzNy#fbp1|a1Xtb`iG=f-I^AMsyA8D@&r^DB+kO$_NDDNb=-3y$J zf|^tk=GmHcK*#54Q^mEc7pcqZg`vw<9- zZ36*m{J}QR$70VA-;Yr{w)bGy!fI-9Q9jhwl=ID(TyP&bIdR6RC~yx7f#f_tS8(`= z;*K!F!V(~awP6sZ8k4j2aS$Cc3@&v+a|WIbjIaGp z(Jrhn>Q`SAYb!E$p~S4fak7~iZo)stkX+HM&tfjBGb&2IkGFi$MU4u(32-M8c@-i6VZ`*WWMHfCRLi?)7^t#zq=!0Xg8f}JWgGq-4SS(so z2%>I`k;GLkD^231cj;VJF0rkFkpsc#XhlwWFkpi~dyO*$>s0)#6XUB+U44CXE3?~B z{R8MOMX*-9#^;P5s#+|*X$9?j<UaY#tiJfFZm@q0IGRy94FWXTHMaAk;`rJcI$Xoc8{_@0i{2cLs@Uu zSH6S1*F1f2>5_e#_K#?wo$DX@*$6-<5bA#CXUG;hP>1^ts)4R%g!)v65Didbk+-&H zg`+SwFs5KsbeQocFm~x&YgFSpX001jxeA7ADG@o;$Aj4BVVv_yfbIF{OY5sCX?p34a-0MZd<87)~a4?W$6Sut& z3G{?32o_wp+X)A`$+x_BLXe?=ezjAziR)8E2K>oDjN#13IxxEzm-=INQr{57%|-q)LIC@h z5d42qp#MA}fc-)U=5A?%v|0)_-M@8SfJuGK2IdKQ5e~<6GHvC0pr;PZYc0*rg5&^L zMI1w?uxn^P0*b$cfP?MuboVnMXuVFT^l6p{(e8kRAUCBWhyLYa4A(PjE@m{%?yOrg z$k@E`Y2@i;H1~)e?)(P{0Z@ng-3S3J1Wq-N|tX?1ccJu+sCZB*bpo z`gWF!9mz5)7Xz#IoCKyPH9I=t z1f(zWw8vCjfQ0pdRD#3$O|U<)qx8eDJ{Pa&{gc=ng}T|Lc?yrIWo-Jp(-Iq1Zn!F6 z$y_!n1wk4G)Oc{Cis8VGI&86(A2zCNMWB0Ij;M4~RAoww{_dN}B}S^rE5)oquVs;q&6_?_R@o6SkjOxx#;8R+AZK3Gqgy zxxyNirL^r(t`r3ZJHF}7@tEWsJaSS4IZ_Xg>{>ab&%3cG|)bbs(LW#KOlAH#gFs^rv0AGaR zqLBnzI%eW~&XTRz(q~%E(AOOvLbOyA1(uv-xPdAsfVo*xHVBP}{w%49px(yT$Jn>j z&l;ij8dgCd$X)}`Q=j)54vQ;4@d#wE0SJA*?KMC)d4NXxcOU)`J2!_A1=_BG`tWNY zcnGBh8U#N{X-zo0uMDcgxvwzinmr2$G2?txn<#CQa{H|Qb@&i{e-b!&2&Hx3D>V!B zgewTaRfNh3x;_8vl=cYOFQg*@O(oxTB%njCP|WiXqR7kGSm++Bq(To`&?H>du2j(6 zs1(e`s?~2Io|jleZ-?qgP$L-Vsb7>c1sTCWr1skg7Wle6x}>gBADVNKeJ#aL!Lc|# zpu8m@u{6#D=BhcNmN+(SE`;~*?QHGa;KE0kM+(C)drzwpIsE)XY%`Gv!%89jd54$V zS5DXGrT@AOE`+}<^v{j&A$(z>P7Sq{15Ccf^*T@gkIeGFn{* zU%C9CQ6~EBg^{@Qi$7u~zvl+me}RQ|Km4d#9^=d|dq5WYgpS`vu8TKK-{!bcsJ3*; z@PyYnVqxi`t_X2HTpm=${~!wm>Tus-*dZ9sAw&;Fh7cfR2*?-E%sny$^n@!2(N#pq$pgyBe~Ano zA#Z}jfkO4JU(N5%+?FU1yhwVM=nbDxrly;uCmfZ~F**7iKD%EQ2SWUdz<(|RBYr{P zkr`)2%Y6T5&ku^g?=u-^32Bw#>6lfTQqXXyUlQG;$dZ2U7N2uIuq%1$Zyab3E01P} zt#@$=W(Hnc`|<%1_=Vw1{9z)3_4gTPFZ!IEB5CIfm%{9@stJC1?By{*`1=1K0t0op z-wlBgAqWiQi=RYb;|ppEY&z{M!*Y+u2_w9?3e6JLLuis=G>+jM7iHtQ3Wo?m;J!Ts z271C3gap3)#tHiFmp>GN{{^sa@4M|nZ}{fH4vA*4BwEHNZBFN2Qm)Lr(w_5;*IYM{ ze;HUK{RQhk7p#%Kfc3DKcM`K0o%79ug7py2i%(mwL*@uk1g_6!n@_+A;3r9LuOXYk zYDCj*Z|nSBE+1t&%z)xV3YT@mJ;;f#{l_}?9}uCF!SKMp64&ab>zRe{^#O4@)ukmVt5RyC}yT&JSeW)O}puq z-&LiWelBI?#nv*1R*(E)X^Jm(Vki5I5D)&^UtB**Tp!9ER0{u8{jU3fi*P*(8WnN| zCVX-g&p8|Y3y*pWrp$uZXYk`&A3qaLk_a!Ru3K`{UeCQHPt7+YlLhH)!1G175NQ@KKK1!1la z6Um}XJ+f+ba@}JWKsx_Yi*{sXZ*-_ndkAU)B)V6D2gq6AQaRt1Q9meSIfr(=jH(DB zbdcdZhfZwWz}gFoys@_PXPBQ8*DmK95B@U7g7TNK{JD$;qmb6SJFp;I^6HZSWqC0 z1;`ga$yjs{OT8?ZhEDBhXTNl;K4-0m@CiqhZx`)SElC@n)V*{#l>Lka=m}RC)SqW8 zPeUp@Bb0_7?%eZ4k86{uZoI4B@2|l0@`HrLnym($jg#}<@gN|<{naLbKo|8(==kx>l-J{hJ6mr42SWis9qxAn zR#XVE0{P-6ftBax&dU_n`DkXiMV9AadcqY3 z?dO3t!}0>v>LSCGGEUgL&8y}oMy_JndO5Pe^0(*Te{2;j*R~MJHl8hPG>27`8`c!{RQaYGZWUXxw{D-*8&AO-_O(P`_md=me{^-;q0lt96(d55@b`~l$_ zb6Iv^eqg=glm7^5-EEN1X?y>&dhg>I0|QJtoQBiZ|3SD0>TtgsT%$wa8pszv3D*tx z#nh5KOE(9tGwQ{h8`Ze^z+ z;J}v>-3sw-$qxE%Y84M`itO#G%yU9xz}>+E!yf|e*8#(rc6dcqY3^XHL$^bR@)1MkqJQm~+9 zb`6>hIoEhV$4BM#Q;+rSyw1ouIi>9(JCNZ16YBpC2LG>XJtztIh=an?)8;rnw~?04 zk#NHibJ~ZHR~Rj)>ru<=i(zZyzl;Q6{v`o_E(yT=LIU3OHB#51KRmJYf0G1&ZygFB zuYO(eH6cI`^t)9mJS+*=&c{d3ZZ3>DUZ2ObO<%y9&zp4CFn3ozSHz&bKq#m0;3Pnu zZXw6P%EbrIAJ6JzpIzJwC2CHprTIJ>d$2_48PYFfx*st%Laz4+jPI9R6*)7($q90FRs3Jx?A){D%2d7vf$IYA7 zVML7Vmr)R`zZB%pr65>eC`hwS$30KD_~DHIn-m0m&ygTQb2@s*-a7xkPC*i?Na7UG zjaZ*>T^2J&yNmOoA&w^SxpTxiKgG_S{E(9ery%|&+%ny8d${`F}g+1|+!u znA^X@0RGpt9)f}#qOCwQ1VSx<2FLG4EwCWeBJk3?6!WL+gPz(9+y$`BJX>=Td1|vm zD`v}g4YLc~1AZB`!2U}u{#=O01N@0;)6 z)_^{EfBHB_!5=~`NaEK;FN)pZ=1#q%SD~9*)56~vgo5dRbKYYZXD4VBxSXy<{J6P*DLCf8G~@q4^8o*MB4{m9S%N^}G0m41?1&_@}E$~7q;8#CNi zjSn)Ma0-9}og28Ug_udfWwAR3&>50Ku@^B;DB@1p)X1VI_(a%1Q)HfCl^uO zO~tnK4j1T;6jM#Yer;uK3inuy?Tz&byJ;xVV;jcjJk`oNaO3lz$SNVy%9~6j(X_5Q z4XdxV-a6iR{u>Jc()pK#e7V#a>eC*Ag#d}}9kvH@*8aox2PHQ^!}E6|H`owzqvSE^ zn#{63#?dF+?FcGfHs88%DYLkqOwAYFDpm((?3a-noWJDe&m}iFU&u|}i%EgRLi!u3 z|2N|%;7gnwk%-ycSpE=lGuuH%X(dNDZSQkc=sakV>z%&<>{YU>fuh0tUQ-&QMlp!?OZ;PAUMk71YOMt2(O`7z(Fw>s2BZi7>onKV0`+n9_HF7 z=7qG>FE+S8JReEYv00PAn|4}g@LqgnaqgF4Fz#Or{&O)H_X`HsC@nojweb$o{=bRA z;45q>pR3iY(*GeCJYgJ}wKcrai+YO|R~?@%ZhKV9S&81pnR!|=)+Bm!?%)_a9mc>~ z?P(DrorsUr5_zhPT10zHb3Q3A7s)l_RLboiUgYqv1Q>u`=_fH*pq#^S>uqvq`y;k( zmBHY0xisZ7fvY{zMd94G4TiQK;c)k3FwhgOFnGTNgGHTX5a$QzPtD2=&cmC8y}4l0 zn^!~~*o3$qG(IzV>D;#?{6K=g<_P~m;TovJ{cdoL+l%-$?_!kl>hMd;HU6zf^4mt4 zb;Dci70zqVGtOYqUv&r~i3ol0`LbwdHJvn{;EB->PwRH9YS2*qp0>l2_ZXh%9lGU9 zaetT|3z0d_q}JSoB8R6p!!48@k1!p>r?Bs_4*Ixs#x|VGqlZolzF5x5M0lXWJ2C0j z)O>EZaG8b92yv9J4R?@Kk^)Oz)yn(^Q%jdto;BM!;CQg|@g~Ns8K#7wD7aD7slHN- zut(Sk7G-Q=oLSZ-)pV$QV3vZKy2^WXd`)0~C?=M9w zo`U0KW}j%Y$@I z9UJ%vmc<~PLhFz5SrgXWEhJpg1j}&Q3~Q97<{cf1uBUpaukUEw3EEf<8ZHw#t(v(R zysOSjoO?HkAb=AgqE!1LwX7GZ%j<>JbYaWB%jY@zzmQ&`AeaE#@h~xA_AJvR z3+xydZ#yZJkMiC;O6~>rMZ_D4=i*!=l`!mNVlfslZQ2TyjR#>W6G(Ef zlA5c0ZIci`nwX_&3UHF12#!xeA?y*Xe>d!9PQ+-{OLX9Z&1G zCXJ@xw=|YyijiZ$xu=1XpChz`nPKPpu$Q;zy&N|s51N1@Sv<{CO%w0r z`S?_tnW2VfiV1J?oL}#rD|^pO$g|LG=Bf_kl+=Ya+91?@7ePy4Re`Ba(a}Rt)d``Y zcy=hB@|5BFMu55-0mAc9*SJ143hx4MHCF+*3&flWr}Q@)jfT=F8ET&&o9(_WJa`M) z#`g&|?fK_e*DoJCel6`5lNn63THv}sasV@(Rbkw3fCtNoIV z^PTHANV8xKvZQXo`ItsD#odZE4!(x7iS;58NrA;5ckPMnE0#yfqRY=rPovH}Lm_(W z&U3X)hi8be^nrO6(|l2pT(J407cLq`!tDCpMpJLvo(Yzox-(E2MbMTIHga;FwX9#gxdQQa5~kGo`0fVy^mm3-L|27$3y>C@~d zyaE(boEpoLt9+@pHA0QmbQjN6nl%(OgtpkWwZKJ?jUj1u+HysoEI2A;E?PfyLhP%cFXYYl?5j zr#x!E2)gY;gd^gLa%x9HVO`?Gii^PC8RW;a4`c>;mMFXS+Nnl5R|TnNXMo?`^wE-5 zj)P|3Mds1Dlg_m;r|+NiTj3WGO;Vl6Kx9p?!EEREe@y3gubD)a+p;sv1+RIP561bn z2if!KMK>>1esxa3(aq9Dj(8W9oxi7G4$tK9f8wKw{CAn^~=E7qQM`(X?AH>roolehL(8+xE?0K3)}l%%CsnTvvzH;1n?YTj%U zybtpzWa2!T8uo*(-9WSykQ@j`M=Ns5g8>@^+FPpW|Km6Q*1XS~>i_KH5qw!pm$71$ zy>)_*BR=S2y3Ri*6k~W~7(_aYtft|c+BAhuwHa(8ywx#Y7{xp$^CM6HzPt6ki;E?C zDeF4{zv*In9u&{Uj?R-mwbwqN3wY>Pv|qg=zmQsavm@vHyY<^|ne1|6_;c;41a_kv zo~oZX_+olwaJ0-}@S$lAjjZeC$rZiv4*9E6X!hn*OevBU)c7So^7D}{rXfjJKvT1bw2KEh{Q=a8elmQa z9y8!>vi&OC2Fb=;O!-(*Pa@il=n>B>3uHJJQ^fwOaCrN}7eG(A!r=dW_`;V(zms6} zv0$T=5ng+>`-wV_CYR02V<&cM%!Ew4Lqwpu3dxD|V@&yJ6DVw*FxqFY>dVnv!A0&& z+(cT)WRA2ju=$oB0i^T)NPfg2c1Dg+v}>;nB#^g$yAo7DS5rbJ6rd^jy9wnG$5jp? zdZ?2vco0I%9b|S2uT*<5yF$RXjuG1(j!Dsu7vq*>hempQQLa+dFC(=0e+liMyE{bq zh0vBX64B+a9rLz6D4{)tDwgNRAi)w`8gWr3nEQ^DUZBJc49h$@ULsc5v^ne_5uLw; z_Gg!Ci+T|avLU3cUI!(#Yfb1`{x++wRHs)5va_b8VtYIm+2A&{)uk$KY|-2Q2MH}u zhx=|CKLpu3gy^AoA^taB2;hsKAfTHRPPe>atiiH9QLi4-;qb8hp7mG#Bz zwSjNo@b~jVpeI~m2!07K#2W`8`I(LTvEXQ}*11mdDz%V05V{xheb}%~A`)I2`c`HO zNbt8Sv*iWr-5I45&AqTVfX4s- zqrOfckjvhLUJSRs_X~XJ7s4>=mM__P>F5Lt+8Jtrgp-ebjJw@@{2Z4Y_LFu8DJn|- zU&b&9{xXa|mthcnVHia>sJ-r~rd)h_P=?X=F@g6QAw5yK3>JZQP%E~^?l#f+r@5w_ z&ykFar3ml;Wf(_U!)v)J(9rGFBh;_8&h&D3{_`)Twg8z=wx8`3jn2Jv@E6 zNH8)DT34`h^r4opJ8fQu2A#V&NP84U#HpX;-w| zY-59ll~l{)Fn$>&Av!Q6@qhoxuNp>JSHW_*;u2`4;2bYs?{YJrICa6=9euVKzkiRC zfU7LjD+Y;`hyA4_R%X!oO@Neykpq8^AccpT3~uW&IYBbd64vS#ZKJMx=CFTkT-+rU$BuCV=d;Z#f+(O zA~e31u13Plm;O`513SyxY=uamrH-3AmsZSsqty780wQ(@cd5BaPm9wPlPH{KO${3N zQ8ToE6B5YqP67R6?b@@GpU*G&s|!Z@hR`J5#3XQ&ra*W|cIl3P0iUv%Mt%>gd=FtT zl@fd;YwjA$`IlO7gYD*y33RQG7}xAQ=BnGo>|glNjq7RQQX*XH6R~}5kVa^%PW;L# zsm3Jv_~3b~CfpOpC}8DsM{046omx^dlirJ-OP{&5<(~iQ)s4`B|wQE-xrsTO`VJ zW5U#4IyF&1mc@VPc)$bud5aV@v#{Ii4H-{9687KNSyqvV&!DK$9KFXwM%V7JV|(99 z4_q@S)S=3Y>I$CnU=W{zTl$KpPpUr3Sjm~YE> zGkbVf{C;QyiEd7@PlvX&_eX9AvuUcUKE@>YaG%dF@E;Fg-L8Dgshq;?dJTnOC{`m* zNdpPvSOZyJ_7?7OR1);>PcR^zu;@vFSwAKngsJ^P&Pe7_2O7OhuWS}an3rPAO_F=! zAb3$9Vqz$U4@AMCME`S#pa^ThhoExaK8Qh5zr9}c_E;j$v@x|$|7_)CuYa&K;CT9Vel3C!8H68@*~+A2Bw{}5A0g8Px&yn zo!kwND-QGWHIh?*K#h|Idbk%~y{1^rG*!&2+e!4^D8~7Ex{nZRi4V8fO<4?_(jJC; zH-Fh;hM4%ks~Kvf#jsamVS8W?a>5S!DUtS zrfyitH2q!89C9^7_GLAb^0yG^Bx=f zW7q0TM2M__9vA9=_)&o3NDna->~V3Rx#f3T%@7lN?zJ4q89%w4F(P=j)|P9zBmiG( zAv%jMV6x5W?IJxrMD6>4s8VQi7mk=1vYzSNYYEUJ;Png%IJX;oFiMSKZ-eF#%4jdPo7kWb(d~C$a>c@!3-hO2tep0W`T@!9FX{)h zdkX1yKm*{res_pG@I#0mDrKCQcz$mg()VoSCfX;H1coe>4RzTU;W}HlgZqn7U5ykZ z2YCDPj)9jUO|al&)X##=gy62(#|oe4=Ej_Wi4M9%PrZ#Jbc;uH-hRbWsKZqCG)4hU-H6Yy@Ngn8!#!V>q~GN{*8N893R7q)Y$X;Viq^% z>B~Z2^E%{yUWT}tx9D;=7`1M>OP6zq>Ri?=%zG4EuGWsc6+TC{}9_z zrISHxg3ZJ1L^d_y*Y?F;b{vB#Y#~c<8Qw+s(YpP3=8!rWR+7f)kLwAtE3OOb(O6FN5 zdRd*V=H&atbgRPS^jnXY+N8&^@Jze#JDNx)z+yvD?E!V-kkiL)IPfjM^nT|6my0;Zy2stnkXC&+Dl3OG-tTX zY2?SrPWE3I6Av=XyGN5CqSy`4%c2J1>4=5u)ol#%Nrq5^Cr1X}N9hLjCpJvtDYuI#w~8Fr3Sa@!XbGQT1Cg{ z+iW(h)Js0b+U`5rgD|pUn}qCAoL!*7PvtUf?;8j|FGKtVMcr1VKpX4mp8d-GhEo^X zELQU9)BSTtjEGBA&@D-@;Xf}!2p+j}kzV1hi%!*!6^;LdeqHl5YNOI^wB8c_`; z{dMYU0^Zku`zG)B+m1}36bXhJiKGE;ba&x7A0h?~a z{6|do_dTUDlshonZ09nv{hO{>){cAasI$%(h3~ZwD2FumwpZ3el57gwfvo$J@fGEc z>OL$4H2At@n{3w}9kbFs_==^RAnk!5f}eI0#S4`xcx^^QL&QKF*|os4^T@HN;tG~O z^7Fs4Vgc%K-!aES96dRN=%G%j5R*VwEI`iq$rX#Q?l8?cX;>B>%;eGxDY94w}cYXjYy|7f;18WqJ(rym!x!vG}0CzNF&`y zNSD+Py$^oT;qz>^_c?y_-0&Y`%(ch0_gdF_zvf(X&Xuqm_qAOrFvIWLr2;#DID;y! zsoiOl&AXeX(~)t#ePoP;HMZV@+BqpZnfSSn8(FshDk%YG_I*joN#4b`f2SoSqu^&s zN={uTU~l3#F8~Ps5D2EAxlXvJ!Pk9Pn!5%fa|0&MCH9H0Yy51qwU2t4=AINmKs|-r zi|+q*D1&!_GXG+fA^L(cIW_t>o2X1aq?|YS`lMe$%m8WN5V2vsLK>YK-^@yJz1F)t z1Lx||be-Ug*pDRc0%d--;C9E?BiusHYCig$C?j&Ig99x)b8C8%`G^w5-rPV8^Obn4 zvBiOFA*w@x#Ni)^GQbwxH#<29d;)?gXefhsiZZ|^<0nxD;$_q|p2(hN{O3+-kTh0a z+wK*&3SdNCj5g$(j_6SEApv;bqYSViCn$seyP*uU72o!lhObAn#V+-%t(Z6Lk^*{i zn@OodmQl_uP5;c-uCu@lPqJh@&fdZU0?L4pAp)#+G8yg$(}Zx7v+Eg3$qyg%DmTA_X4>)Y61 znB1uHA$0!IGdfkwrq++k-QJk>uO%+z{qL-H-CJ;^O7ie0);y=YTlJ~Gq1VA|R_sZk zQdV46k`lasqY-^|zom~nfcnS-glbn{{?U)-`5%c?09$asoxJ0p$~&;h_(^#`zD~H8 z_jZd+oL4cOB%omGHSQ9w#*IrY&S0$7w#`EmkO2JeCsU_@?4e0g543-omXxI9LZtm`J+7Jww;mH^CID2aR&&WFnshZ~{0RW-1C4?%j z>Pzb;-=d~b8N6C_ExydlQtzZ6bR@{2u>3j_Ah;lbe=!M=d?A61T?bx)!bDrW^O8V0 z9RA2wc)-%rvD#bBxwE%}G3+m8#`EhY6ym4mk+FOJNFgp9{hxL84`gmvuC(y#_Mejk zggYwd!J##ds_I|_DIMEA?4^X6#8n0fj}gEk$d{y;|`*n<1*NPys!1b|J(Pm%!c z3=S%>`DTP52tQbF-T@W0tmh*0boN;NPzHnBLOP%#}oDw zEbq0g3Bo-aJ=ML$M||QU!kztvDd;w^I|{${;R0s(eIKs>a}xLmpg}1ShV(h(%EREx z$tVl2UbNu)?eHvd%G@oEcfN$%qjCM$K?C6hH2jO9f%FSBq(!-Bb*QQI@|_nN(1dQ! zGJ48%Pqw|Q|0HCRc~75K42;ADu(Hmf}}|Y>@TM$K>`TBhX!CnPN0G4cY_9++@Lpj&+L_C z#`j*C=b+snt}774t{!?ct&LxDx$+Lx*VbCV48L!!^(R5Y-^drI^`lG)mHJCN^3j`z z0VT`sk*X3%NU(AFWnFa$$pEG_C$-c2ul!{B#5Ik&jD8gy4M*4uV@O1F@_M)al0Tida zOf>;zYiGkjXIY)9(~XC1xw%g|03;Qqg9Y_C%i? zH#cQRXc>EWRSx6B$O&5W)5Jni29=q&iHEN?8T&vzQQ6^Nf`WEOnre$xOL6WVtdDT; zeSdfpPZpw!L9je|@imson&`Wx(7*$v_0IRH@QU8y0hakLl zF&;)=_hl1SGoLQJapjsO)#Ldjsw&M#YiTvDqvdG2sLE05Qf?9G^xMU*SWjwIvv+*u zW#8Wxj5bm9N}+dhZ60=fOCEhQ>RCCm{#74dotD<{D8*yh2{Pe&B5Ae3m1Fyu;~O;T zaynLm;6+Ac-X5-)Uby2MC_e1G@tW?J+2(Z9x7_KhL#4*m43^RGu5vK7WXAXd}!tuliW zpk^fwATeD&4yWRmzv<#KEF!rkQeXbN3 zm!;ph>NPeRB79$R|MvZDd?}l42mQC+?dLCSU)Fj_oXoeOtCZR zF6JR-hcd{(S=t@B&8(&oLsbk4S8pm|;1Ym;PTVHbL70qc@8eeKU)_ZmP^EXG@kf75 zCFtAifj4N2;X{B6^=$!a-YWzt~AK*S}O-F8r7s`c) z4JkiUyVV`hzIB~{j*}m^2<;ZWUMvLw9wLjB3YFs4L4suoXMFB(For&u}B6^VR@H*{!m^O@DBH6MJ$4Kgp=UHEOC(Yb(2nPqF{}H+VJ@uml!F!udJ=ddx z{tEqEH0V2TgFf;vPD)Z&m{*Zk0$=rcofIf1@>UE&iG&%*@+(q$jz<1XnnE0 z;8!nI6JY;nsxKm&KN;D?1V_~vS@F@4tT|_76NggnI5clEF$j_1rWJIPGSjS+2jjL1 ztZczsG{wuPb$_6f0&H*j#z_I8>mCTEAT{U%5S^yc0((`yd2$dksz5LWE%}P*)V%_> zjDFI+@-tXWgA%2`j9PJDY`G6AQBCWa5x_^A)dxET>KV%OUPu7Z_wE(2At&w?@$cqd zB?z>sInQJc=&60WT8*Gqu8#xWvu<^Akyd>vUK}Hi@7G3zzzqM3MuZ?FUxDBV{hyl> zk+t>3Z=}*Eb$V)}X$WgBTYM-^lme(SQ-mj9qCx7F`%fc6U}g^#zfFig`R_C#{{P*v zaFQB-ns-JJKC^%U-<&Xy5GoUqpS6vVKWTnXI+?7=tbe zEsL1C8_MRg;GqngU#}lt!S(CUR_KSx3nu#wI~0P>agltHDH?m)FAkfKxsb1h|9T*Pt~Kv# zJ40ZG|3y2)e<|4jOME*MKm!(KKc$w{swgJwunHQ>VU5g}Z(=8&NwlQR&(P{d+~DnI zoVXYA>!^j~+|(i^a>Ka8s3U1iq*+)hrP+E>EeV$Ydd$sDU%O!bC?>Oij#^GyNnx-< zgH0A*Pz#9ELivSS;*isgww0Dw{?nJ*0BrR0J?#pgucJr&&vo>-%`A;<&n#nL?(vVN z`XZ9_t0GAxirISaCLb`Cr`-@1&cJ2s3B!qJ=FJPrvSxc0mKh`R2T}{Lz2&!~7LrqH z0X7*wNi8A@8q5|%i%l^wTVciUeCn}@Q^*~TMn0pJ@WZ#ebM8X|NKUE62jV}THLxKk z)IxfaN_O^v{+Ui>Ak_80&SN$~n?yxoQ@&i?l4i3z8-@ev<;k+7LuZRa(imiSW>4yl zxD&dua`jpiJ_XO9iUY#9d@NNE@e(@`=X^|!#WCGizybD|7l6|Pr|ge(=BEV#aDauG z{^}eDY_h)Z9Cu!B3QR3Q_XTGUTIkwtryqt94RZ|{zBiEx5wL#uHDd6r_OIh6((`lE z&ggx*sIHF?cL_IKo45v_TR)$KM1w3$k!s_Nh>K_bk(mBH(VmlrHjl4X@(rk8a1)5! zMD>N6qVw|m7m`c%RxYAF|AuJK?(ppzyK$yK8iB^AV)WNr?AheAO0$rWp4WL_5-sby z^Cxl>kiF%%<0jHmZUQzLXECL=u}$R&ZbWE#cpi~bz%P}eay~7LC|}P^m9Jf}cNzVR zlwF$dzI+|E_;HCnTO|8zFg=Oy!0Q+Y{DY$I;icDf{G5;g((kzm*pL%$BKw26DUe@8 zReJj=q#}LXy(J=ux%u1KMAJp9M}4IOvsjLIq`$UK0%rJq>m*<(aAsD{EANUsTYj&_ zucvMdc`7%Lc|w_E*13;W1Ub?MC8CFpC3O5cc_%x!yf+r>JYX#v!%(xnIUvln-eo^v zW6rfu#Upn60NG4nd*q*!_ety?CNmEd0_6*N2T|UszsUO|sdA~hfw5;B7xMmZkaxQ* zWeXT5QQ_LI0Bq|A!-QUekPJS?f<~#_%ZkuheBFPbyaU@?emi+5JC%1}lkt=CE=d|5 z)YGWkC;`4@-zlkN@-CznTpfNF#b=ND2l_hDo`|~zQr=#hAZ6^fG@cVW`AjmrirspMr@%e6Oi?;~D(>^Jo zFO#rSv`xA-*M_6yvR9mdZx?0dtr^9i8jl)P|AQUjxs`t zZdauNF5nOL&ym2i)0?WN>UY5(U625XBtY|p1j6U*pF14ebw0fyfqw%DWL=Rzk|LTm zbifc-5qpE+IFIF9odxV-fTP${j zP$#}Sb}I?ySlO;^W*91K^KFc2nerA5B!K*T5&$;jgaj!5U=sMGHj|e#{As#1Ps%2p z0V9;<{iA{WVf(qCUg7Oh@Kvq}l1% zRj9Hn80`|2KS=ma)Y3krOn(kGM2OpIZf$HoBd_cD>Mp+XY)VOA0rj+nNrX$~=uS4X z1zqjsfGhLrId^3{Efoaw)dD{k`8~F5_85>OVvALlhV#Xs@u-35J`5LdGPEOxQUx5w zw|}DPOezR}zf~cTy=I3=JMK&VsBdaUZZrS=yXQ+(j1%>yNP!q>)f1ZX`PoGoR=#s9 z2lvWUjRY^Xb|4y5346hP*cxB2ea09c9pM)2-sWhDm!Ung`Anufa6^e0{ynjng$2gJ zM4W3yk?$4hhUm1ig*S8#C@zJyS@uy23bogtJ2te8ntDCdor4kIyWJSPfEJCMa^T^b zsv@oa$)RGydx~uL&c+A4&S=A7PIAG_RI2C!zJa)5R;s(WwN;pF-OsBDXNc)C@#hN% z)J0%03$J(9!Aj;oEm9{9+wtZg1Irpmcw5<-E?vkP``9$Z^TW;8p&al`M2F?l(qS35 zNWfxYYS=EdMBEr9+ah|Kwii?t^ccBKSOStW-{1QX^$#^w6kn{<}2J+iOySk|1{<@Tv(-psDZQgiHV!Dwe(7fcf|JU~;^D~ciN zpQe|ibXh8>dgGJOKvl~2j;($*qX=~i?e#eMWXW|hU04gA5Q;YuuY+xLrD5L|FYazy zrP&c7yU{hTkLjlWp<$WnCTq0O>{g71{KiOgHR`BI$=Zw!qehYFQ`7|zq zYhntO_Tv7OmnYc?51+nob>at$G@_~5W5tF0QshUHfUQ_Jx=Lyx&!Wlx=+27%l+-;e zCx|Il6V^HjlX*#8ObGD%XX0h-@dkGuQ0&9(QMM}sZoxs379~7d_+4Us!ia8QqrL4MKp>6?hmWb=Zw`18gYrrg z_}#V6G>X|qz*vctX5}leA(+38(FL_z4-$tjJ9p6qnk9_*@asB1pKZAe2YC1&C_Q8iS(JGEXU!h0kq>0%liXt?@aektdkBh!bX|N-Z}0K!JcaM zvDC?GL5is9!D;D2RS$iNItyk6-{$V$9k5UK)FqKvp#D(*;vPnlh?Y8DC1rk{PX&Uk zoWK2ZZ~KGJZNhho&hZ}k&%@#Q%yNQ#-(OdtLWh)N0*}Xc2u$h#@5s&WufoYibt{J! z@$pTz3mSdS8!u*?rz}|-g-cF-eOI?umK&1-!lczyP+u0AQa~xR61|+j^@whcRcZ-3 zb}qjixt;4^DM=$=LGS%Ui@z#OQm?zpAp8qgwWl?{7<5HAR!3@jF{xqkcd$J$Rk$a! zx1G8nL_KY5_}7nKTyv|fxw)7*2PcFQ34^nbZ<&0L_^FaO4i=icc0fUG5=Onfos=BC z+90+TG6c0uUt{_`#}L5*Q-X2XhQN4xy|$e?+p_nM8SbCFgBf(K!;3`4g*9zLWyF5{qq^rvum;4}i;V*ATF4C-e zKb`(`q-?PVwiC)d*ZbG!B0SF3x#*_V?LDugovI&bQ^!j0l6#oZgzzB8G-8_fLT zpL6SH-#3U6h~67QxNz%1bnEHAxb*>zBLsYWMj|)P>((2~*uthBwH1gxp^?R4Y~T&6 zVGW}|@ZR!tPDaH{)e8I3RR6wtk0<9tPTVFBYi<5oKEK%wteB9tie_rI2IQ=NJs+?( zKbrdFC`2UAJCzEwwbxpLXVG0NsbbCZ6A|_h8${(QV~{uV|twpY2lY zMafbBM6R@6UoQRH01=qs$#39sc2@qG zD8oPVsW*2C#z%Ug8PUVIAFc*A)l7Jmy$!A&iZ;_R72EHQwLX2HfNe?NyiXw9)&znn zXcs$CoYt~DeI=2Pb+BI01&K#ZI$i2+u{Jrp!+Muv`Q1lfFu@G;U&nNm7fkp6v!uhC z2(QkFt~yG2Z^Dd5%clFvL-4TZIXQea)S&*O2*g}4X)tg-?W7Mr)Tm%!e+ja|Ntwv? z&t)Q){!=D$)4;$AG!XkslJ&tpSDgHJ9|d6e!gPLmvk?wBk&kZuZ!+DqQ!ChEfSSeW z%?f`y)9E3Y4_`8Q7bFx$*8Qfi)nck_$G%u|XzrQfkqC1o=i>R9&ZINUiBxXo7KDKO z2MD{h)=s{zlW^iSJhD4RyMDQRHUBu%o$>{+uXI+)K*(&o@7^<_GQAYGqiFX*X@-T& znh$S#l%Q6z0i1=t?TH#AB!KFBz5q7lgfFQ6V7}OW?u~&J-)N`BoIN5KX830Jel0?^ zyo7eZ(X5?7bW`)!UWvdAJ%w~Ve9lBj{}~eaXFe5JKaDeA3s`yXDT)AllYToCp*lqo zK0NxtknlxA#$Cf^wJbZmRdC;GXkq1BZ>{cqatV<8`0G%F>H==;;sc&sPunCF^5AV++aGq$abPJ&OEgC<5#&{UnM|geZEq z$?kuo^%{(W1sDDle)sxIZ0=`B1aC|-?+!8RLjtJ3M-gB{PEdsU4@MEm$oebXa@TMc z=dcj$ibUUR&ohk$b%<`W=%TrHdzmJG?JEY%&{Js4YT_P@8F0Fa4`-e;~|ywd(x+AVhe=(p8Ps$p}wG&e=)T% zeW4bwggvi-7z&DW=eh!m^#4fBe&0i}5BwDV{Czgi&_E+ zpgE+pINFI{q-&u|FouDOh_T755iW_i4n5)8Sl#fNONcbgs)>YMNEsM_Sd0uHdxya1f< zyKewvq0_Jou=n$~100&uyj+L=p@>6)*5@v$?wVK(?nTZ?)66ameBhmY3mT4DgQ+Je zb?p%M)%1ciS&M7p?#gc`KCKG}8*exs)vJ{k<2=}@Qq!{KhV~h!ieBr4C060BqNB&7 zV!R2N>bLV0g7x6?s5~kOYH_=Xw5-;B9`}UQZL>SzHNEk$yI;t7P(EPh;!ct4Z>B6!PjvsIaT4}5yiIYV!t$i~5ucUF^V9N~H@YiC_iAe-b!z}Ku8`LqBX`7Q#j+;WWqa_IJw zFcSYemuFm+_H#$0l^?fSCB^*Qi~(6!l4A%g`T(?E5OexXhP-y}5*1_Xu3owZ-*@ zi!o(vUV1N`xIWCL^+Xo0Z9CG8RaV1o?P7mF(#WvJZg1U)Iu&CkNisSP#4aJRFEZWKGM&(>1kd$uM(=R z(&9Xm;TzqQep5=G8ElNd2T)OKMB$gtES0-@T}?0lYDqqFv;`p}D<-vqduF5NO0_sC zt6^K}cKIbzR0vIN{#rQ+t{qW=y~KT@k@EZC!x1rLfh5fG>Hu+c%ENN8qV7uRUS7{L%&^Qsaf>q*yZfP9@;o#>< zpXSUlNDR^8$wF`K7cq-+lBCZY@vipK0TVnHGi4G1gQZCT(4N^hfwBD=(?2dR7q{gk zFSnS3q5k#LiTZ@SSX3<+Sge)^MF&K#%*R8w$g)OBUuKpv)Co@!Pu`~-TBtLSZ_dTB z#db5~$5fKAqIv6!h#ZadME+LJ9Z$7XTU9Z`%J;jRYQ`cM4ze85r%4=r2>UkzOw@hrt-yI*4Rh~lQb5Jt`;54#m25{Vbr z`_AtrAuK)Mro%>#%27$DN|L)jB9(TSL*(KySiwo1+hbWK$ z+VA~4U_(y)JK8_kziZ_f#1w29#ACRrJJiTm`O%K~GA-7NY~2L8o;=Ips}^VABg~Js z@>oH5jJLfOrDf;?2NlVaH=Wr>Nb3C6?RYde(c>Neo=`DmIJ~R69iNWys|%# z4uLJWZ+3DJkPie?(CCovln#MS#!u3rW+sE1HLK&tXT+$ezN0<0>O5Yp&7vZ8WCK@4 zN^u%bt^uL@o(_QxIiW+k{|!1MgiO_r8Ejg-oahK|J6$p01bsb|#qc;Mw&u0qO2mP} z*CtcI4F8KJQ^4{7&JY){e%7bZpdc}-o1up;wBhY!W2rPky46Hcf;J2rznfAsm6i?9 z^6Q|1?gASA#n8a^1sd*R5nJoG#Wq8n7aBws=Z-F~-XE*w5BeCh;%thcC{T`4^AdhJ z#5BD+WMAn58g8s@F~deF{M4i5!PDsc3q{{ajN=s?^wzH z7EbR&Nt(ryzW>*c3W(u<(T@rQXaFIi1B@>k;QkZQF*E#li3Mr_E5LlYd{01O(k4@i z+r^~Hv-d%-r1cud^l3k;Y}88}?d$A#+E!f9`Q(rdINRX16nx~WLpd*oRu9=~x)(SK z-du$~pkLyCsI61~2~sB*$g39pBpZ7mp4UcbqG z(nLKpqnTeZZvOe)JBs=Wlsa;@G)s*G$^!~sv76y)b})YC{AAHv`+&@V+rg!aQ-u#i zIi?3Ipv=^0;wpliS}4n9Ki!`h)k%ReAoJ<;@j|6)We~z+TPWUvDwZb_=UVpHO{39? z-M|9WBBCJf_d<+pT*@_h_9^9%epjMAKytu|OVBje+EYa)$UN}A(UvAau=vJhlyYJT z1;`Y~w-$wIQF`f+6|)S)mMDQ`Oe$i8L=UASs4b9|%x=Lwm(OSIH`BP!5%Ek<#NfCX zNrz7Gpi8~W#@(@4eWTr=O)O*Ph1aT1vTputlLC@K6Q$eXaus~l09(;x;Wy?RFUHp{b?o^bG1gLc_Uz&Y%3#%@3?F(mmp;bf zZ`v9dW@zu|?n^~we8{2!*Ya_#^EICuvET1W7j4|vF-;5Lu|+3v_yY%e6VjKn^-M^`N7-HgRt@2@GxE5WZJoxd;Nj1F^Jz}`$aV>Y%a{BSG zz0F4b?cA-3a`W--A*WaPu4Ge)dNVptbh!7=Rf3E%Nevz}I>80FDpU=JH(WZ34ho&ED2=UJ)BB{B4 zbP|}>5UkCe7`QtB($y3n0_?;n3cx4`4~D8a>up+ks93|Ylnv|9YFj~)ri=8!_aK=G zm*m@1?*&zq0zy$NNRKeZVVaSL_*u2X*?v0ujm(Z?lnhTUX`GyTmx2Qr&Hy+tus&n@ z$DMlP$()>e=M_k4)xVi!DEXi-DROkifhc~alU_BpIaS4kN zmV(>+rh}Vkk9EdC3tx&t5;LU#Fu*!+g+YReE80hYiYCa6S2A66BJ!$pQ6~4XLtmr6 z`|j6MZ@m{Bd-tbFHpeHo+*6bW-pau&j3g;t^-T_aCN&;eg7oWL6#8?!DCGG$3fWdlZ|DFqPKJb^ns$L9kF8Yh%z)r|e{&NBI z$yufaGb^*1rJ4PiwP()4-7juL99BsuXa6sR(+4v- z`JDfM_vQWh#nl{NT$DSCZ06)1cUQ~K>!NhenFkNEQAtlfz?V8Y?r;E8jgbt@(Gr;p zyVedjj=}Y#slJG7{%l+m=@Y&4p|2M7z&TwM4h1Qmr#Ifn19~f<1>n?ft(*B6hs~HKcUAMial}c7Y(`r31Il%xdJxi#JOVlgPp6w z+UwBOe!v&uHizDcqU+~m!g>OOh z8T;YP|0bS(1k1plXD#NA_zVAjx6(CG@ih1b{zp&RwXEeaDNk7LfHee&2W8V81&r5? z4OHQ~i2~1$rz5ryFZlU2E*a>fZ>59+D&F3sB9@LS!dZ9iC1p*jcl(hW^0y{so}v=4 zuk@3slxSYKMjONuyE$OZf&z2xe&u0| zTK>h@XTeoTYxnYAJ z{YcGz-=Ny>PAw=}q-$gRfWMRqnwQKL6~!tvx*y}l)6 z^)m4vL@lQ~Ij{xy%}x$Nya@zT&_Yv8r@RGhGJcY`0tmG^4h8R)JxqYHMuuG9Df@!%A2wk@4G80g~{GD znk0Mlsd!h@L>8q<+dh!PX4zLCH+W`*qDc}H`jGqJ?m%+%q=A<$u@(^iOt*YhK zM4WPIB++|Ep;0Ri?=&exApy+a0}ikuCxFBJzX3Q|DRk`NdnO+RgaexYbJKqYa6tH4=Vc+dN1bn=+ikDi zNcN>0?9S&+e`yHuz7&36d@^6KqA1?(*Rc@u1q=O)S?KZ?7P5G)X0bMv`+n%WEF|jX z=yJ%!G0Cz|5JtZvOz0kYYa}FX&Qq$w9hF!~Cg6gF{!SLcBthO$RG+4qJSPj;zP{G# zr`D)Oy(WvEsu*Q5MP06K4to#sS`se*lGY=QKahohEx6x~g;-8m2-sx&Bnzd%d3HH* zuzT4{E1Pa{&|@Y9;rTsq^wkf{v!GnpjuwCfu$;2c@L#DPz=oW#5X(vQ;%p=5KdCPy zu$-YY$kI9O5Tt4e>1(LnG1yjEd5CrMAstpZ{_X>=dh}i1ZGkhU*0i=TMwj;SWhT&@ ziY^tP-Ogi=v3rEPJ%J+y{~^rb-B)Y^_L&#hgcRLRT65;71p%9YnVyD_KCRR&2YNN~D4YxEdu<7r_ChP~$_PJK6*#8G&6R-vM+hG&yDK-I{jGx4&-GMCM z%n|7#Ylf99=#`|BTUSjVH2dGqdaAB0FB6P%!aZt<^c4%{+eJfR(9v zcHWBDo%@XmhEH%I0c_tZHn1Tlip_SR*xxC3p{)Y(<|_3EWwhAwJtHgnDI*jYzkAFO zd_Cn4BORgyzAElzYQ0#xP zV)K1bY@;;$9iLl~E`;Y*?028&)+(TfH94^(Vm z3+}g5Z1z*d1~wT#sn{FjkR`!3SFR-S*e-J4|KMv^xw!;iKTFw@PnfsQn<5PfVESr8hy(`8?p*F7pvTx*ujSXk<%zEJGHQ?UuN=I885qU`+7so2IJ zH!`VH-^}A%&osS#GqLlI3MRW~henJ`U=A(XtYrKTRBT`i?zdBHj#I@3HW@#u*zx1D ziniWsQs`_mB^|MW&s6m#{VH8c5M^g7BL|2aPp-!1_+GJr4LMP4jtj;9PO;66O(1fv zs*~$AQ!Ct|+-h2JV=r8E$oKBZj8LtB(-8DHy?d}Q0_{=Rvo23$c5BJC9Q{1wep+;y zx5NPtBR(EUYj$Lu-aVM-HL2h#Vz`wtZ@B9;D>e1rwTr{AD%(KcxR^%BeLuQ}TT${c zd1mOsLHumGIdU*OO<;0MH=uFa%@)O<#k>B>E!rruRiX-}{TGF%%R;1H`C62H#r(O) zDbK3dG-Vw=_h5XaxO~$qWjM7tFP%Tj-?!D2Ve!L?R3^yK5-hO#H6{ zk8(O%kf##a?nd_7xII{=UAzSDi!@p{7WHx3uper*nS;}q&>*0t8c{x)i&_(tuiI*4 zB(~NJ;OWo1bl?<^q=oG@0Ov;>^iVZTa>^Q^kelN0ZJI$#Mg9v@@`?QS>xR;G(Rmy3 zSQKt)GZ5G#s-XtcS%dXdDW~fqT$MZy$T}%l{Oa>e;yGmr#^!!!wR`Sb~JzrjCS)0>` z$u%!+n-|xg++Zy7E*QhblzA7Wd&kKr+XxYnSkuVm-4H~~bYaY-MCW0y7-66<*D4aI!E@$X>qkuf81*_@L!b+qev4a(R#cSiMF_T& zh@4n5tY8?NnnG%vK`Sn5FS}1EJT9zIR&LqRkixUudf0M1;HSMhs=p{eknHgYY3v0!>y@UBxM z%4Co?naPB$j@O=!pFlcgm6J3614d5qlU@y5`Uo5SqsWx>7lL9~amZ>cIT*u{lN%P> z4mCHosIq0qR1dq1II&*cOB96^&>`=F*ub0C@`w8=c@wF)iQ0p~ZQ-%x%GDlA zq4vH$&jXqlM(GuHWQd2Ed9<$u-ICM6EWTH9f;A*j7M*msarY}&8FoEMG-GKcO7M7w z`voM>^6%p`98>nU55y2A*b-YC6O8OuV|x0ydi<1N4pBQZ{`Uq#xz^V%}_Wf&t{1^TV#{I`)&KIUNrGd+Q!1(vsWKF3XHI_j9m49)`Dt| zvi4Z_IOE~SVvN3PEq_LyB7#@?v`5x#{bSC|>$*wr%K_{b{Xxbw={*roP@BY@uSF{7 zC3~o%ixkZEmJIO%VoHzo9O*v9eDGtDeK&L!@n+5N;0;U}zv^69LLp{GLe=M+o$Rib z_*bfNGHqX6eRA{op&a+i(Lq<3S5fc@6+JITjg^z{GhcnWB3WvcxJXC39He{Qjv)Px zuz!GRY26}5GPzdjgW6KFBEp&0M&@`qKc%HR?li~TQbhR&7E|Nbef@%q2h86*Uh+=j zc?zRkJy0)lB{||I!bu}nI?8w4uk4ErqWkpJsjc{2$8REXSJ zjd33_$0q)AVSQ;CHttKx+l*9xkKOK-46n14%V#LQhdCtE){sGmrWZAB(TK$Ec@3xF zdCM_Oj{Y@)>E}&kQ8?N4xq9DQ8*|kO7)dO}VmveN9tzD>E3)xanGtPPTujK%tz1`? z^5y-WjHN(vjOdgxiko?)ldMZ=GaXGe5^A~3*&yQ*K!D#r^PXR9B4DR~cshh%Hj!XV zS;DL&3mF)(oz%UzYs+MLb9L!3Z77*j!TX}vB zjin?QB7Z5)feL(BiN)!CDNDKD9FJ<)jyyywN#A_&OEDKzQ-$Lkx||)Y0vfRVh0@OjvescH)k1qLePJRUeMNJ+VBW2 z`Cd}=u65ebE5APCr^(Gfawz|vR<0*EPzr}jzb4H8%elH}y;o?HiqTfOWJ;wPY-_sIh`W&bR|xMEHs`iuFC1!}KXy?6q^6TZ?rw30+z>SX z`euMtC?88MgIe>!>s3+OR`snP&GX;dE9K&ir0CcL~Nv zdZ8K7!?+)=1~%19c$K{kt{#du(=ip>?~b)TeW8KviQl}?AoM2#!4$MsW}K%&;;l#pPqE&mMD6&3GowJ>`fwgeW5Je}`g+3jOP#lJf#8|HTJVf?uFgy)r!pV8}9i z?gJ@cO8Fz$_(Dx{5tlrtjo&_=oSKSW26m zQ5k$^aDJ$q(yM}c+Ek!M5o!~Bb??LDX$!{fHBa+4Nn$Ktzjm0kAI|L^$r7Oc=F5svAx4U032X_+Rv~M zur_0-EClRT{_R+Z>oiKQ&nKS9+J_rcb+_r5PSmbllR(f%tb9U$_|olB9*)$&lPLWt z_%(8JPBdh|&uX`0X#yE(`z+%aVWT>&}F#*&XtFlUs78U7+KqO78+s62avrh-Kf?+If^b=i^$P{tG>Gi&Y*xF0oL!8R#qH!+)b8xJ%39I$m+@VfWiH+G? zoG@>}-%gFwhaZ#9wIO?%s;U2Wv|WdcxEXV^1|`Nw&9Jy^)5o2aXyr;8tVO&`LcbOK zINus#*8n=dllUhu$w5wrkB6cV=(36MSJ}{<2Ss9V>hq#Vj8gnnJ0z-FP<2FP{Q@K) zKVjNxYq^Lw&gDF6W-LS6qaCaiK^~*!tTXS7nSs0X#9HYpi7zLBP>waU9JY;`JB?Oi znL52e$x=iP7E{x@@77i3yKH+z&aC)kYI-q`=Q~Uk)8|~wK4I9GVcu~t)z+M7a5ZAV zfcGa;-M!tvmcoTm&g$4R9_^bkiTVM>I>r;5=ok%QAXBs`<9BMV`rGjam>Db&~TBI z1T`xz)Rzm_1~U_jUEw&I7uTZ#dnGx^4(1&g1tJSyBbDXB;X#t?<5GNk)E5z`7>w?? z0~Q%ek2eRC&bHx`K-gCcOO7vx6r)=e)^T#wS9BA=edgr`<_4TG{o{`M2>4Ep`qDRY z1%evHXZ_#k`Mtt=rlI=w5qxUN7M+D$iamx5f0ZX?!T|oIjTd;}A4M1*F?wTI#3)bH zOS~*^LacckK~9`crO#IUG!1feSWzDgg^UTFB+<@xO`fKyjqQ^@Qo1Hs-CuV$F5v?* zl$JewZ`Wos=ytvT$ZPX59E?SPz-I%y>liT-aGEdhq+5Hg3`2(~S2OWb{R`3AD$YQJ z4((x;H1J+^!F!qvFU8;4m>e05p{%{A*+Hnc081r~>F9Nc>0^#1Swn{nKz4H|4?#A1 zc+9bNGyS0>3Zv)cW!X3g-gj}<+*k{~A8Y&fl<@QLGrYLqYXTYc%S~(+(t1M;MrVc(n zcW%}6(6)V@#L$WuA5nR6cS<6M4_9>~ohFnhs`^I5W`_u@U4mO*fVGg7dg6z%nT(}3 zSuAw{slyPBy6LTx&O=Mcfq8wzN#o=} zZ6Q9GS&Umvr-sh;WX!nMJi+YJB5Qj zATshtA$W+kV0?Ais7|3n`f^`;n%0NM&M$lDAIo?I(uEINKXq8xKn|q#VUja*+{rYb zHn%v8^ydlQ7-{X@TJ9Wvezj4b&i4KbUt*12liXZ+>M-&}@^L{j0BeNu`X(B=tJwo) zHND(d0X@;{sFYmBJ9>{3;dy-o#tk2KBE8n=d?w{5j?fmZlS^0f#I)s=yBS|YlIQ@= zZnL41;3q@jkk^|;M7pZHiU=9Hr8;hjORVK|&?5_GN-BMO)*XUPska&sq4HopZz2kG zZM!Q^DLrNg0;p1wWmwR3Sq{jSO7C;Y*9I?j)!=?P=tD)|gj4QzWHnPAuw4hwaVPoI zX7I%OT80$Y=_w!B$uZxT4J6EA#>GEq#2F1P9vW(PF3VkM9nYqi zgSG9lPE8D_mddAkKSWx%?B|54UBf-3AB_MHJ8h_5txLOXi72tTrSqrYTaImODC-C! z=PgNrq1sI)F|6C@BsansW*t@s`;dP<=3A6%jc;fl;1A8Qb6y}pHbfel{d|#RMsDvl70a)x#8mIW{wf>xX9of6p=B zN3isY>1|KPkiT5&i(|g?9`jxM5|Ozc(ZES!2tE+><@Yl<0bC$gRR-KLPNV8FKj3?> zEzc+}Khidwg};ONkl<;)3>ESt)+G+{b-*5)`-lhKM8Da`Np3F;p`3sQ_zn2 zxKE!P*sJo*lY?+}2ZAYR$9&wUQ7~Z3=qICKXycmt!^p@Xn(FP7*WA=6s0iMnZ*lZO z$+m#Y)Oo>ILISwIkAeXkauNmOzKDW-7X=%S95;`|zrx(Tn&;n00z;z{uc!^Dcsblw zT1tY|pJeoFr!-)Oe{!cZ5TaoJjOlp~4}tBp-)s{goCtzo3fkcz_vzu`VDUrIntdAB zuxVpBnZ6;)^*==dr@8(*E~?*9<(!m8F{^R;}1ktV0+6q zJ2?pa34$qTsCxMn_<&8uPXgZ)J+IqS;f;}X>kEIHfu{$JV}7lgd7O%drTFn!^@;mP_NEXbqb8(7&!E;o?Z zQf1X&9=t$>KI0*FN8AbBSh;#F3ZH^!P{jdZTt1d6hG0w%&r;IVn4t__>c8S+-9J3-0N&UIf1O zD~Oo;;_^x>TnE8!f~m9&4O|L^(Fy7uWWSEEE}xsQlt3rAUUM9qNk;kjOkg{9 z;F2tx4)q3(v!avB)qjq#PBPh&4c_W`UF7^*fk;@QUkFRKqet#>AVsUy1!4Ug2umQg zpk9u3*r2HO&UNPetCc(CuL9~jYSSA7v!6zS4nNEXO<2Ut0@gJ%qG)<9W|E-Y_Gfz6S@( z##`w?a_ngMSxaf4s6|4>X;pBztCnTz8W#UUVa6E?jWz@!`8oYbHh+_Hb$qn~lBkk* zLA$U8v?A^#uPSw5JS8ijQU}rFS?3`f)cbV}UicTzR#@uCnDi(N>DBDz>03Dlu}5z% zH$n7p!`-I8bBFKM=Trd?ySMr)P)h@vRPfF(Sdow?;&Y#I65r!Tg)>EkP>e59_&&Nz5#p$&Pw#@MO_FWyGYp5^7_C-w@VS4aG^$MG zu3pG?Z73rq6Y7bP!(kBb@vKY1Jx#0}wDlYMJ*D7kmN)!@hilVu_^Qmqj4894v$uxC?R5-}W8}y>h8iS8Xrl}re+kvrrIzkC zCVc1MTI}y;T;jjhi@15cvAdt3K2_HSYzDIay|Gb03ep?eaQOU! zNt>ImOSmng=L58=KdI5pxcE@2`U-3)1Y$hOa$YZ<-Fc3YEzvQVrM@x<2lK%PX6AO1 zfTlu)PxK~v0kSx0&FppSR$&&MH4J|PUC_2zfzS)`w{+8;kBeZ!H2^Y+WL#w2ue~CXr0c!$)$}4Ggv#zx6pAvG ztjX#Atej?S_tyIUe7BBz;QzC z$g0_dvh3inR687PL1gz(RQ7|c*@|nI6Xl=V~FNIrwTBHxEy1S`?=yxnkCv+ z4Pmjdk#3<{g5ak7bKb{@kSrAF}mxDIZzEqV;hs|PH(=v zkx^KFL)+Ntv&N4kyKbY~yo~hJC%V2gNO1(->Dov1&v^(Z=e=l(eZr>r7cp26Jp}PD z9)cv|+^rVZn@|zw^$_}zk)`&LNE zLMTGX{(&9>u)XEC^ALDX(*=OND&IUg2sr>Cn1YrrzDzoj=g@a*;$w*0 z+ZAM~Zm=A1UBED$;%vb-Yjlq2S9}2p;QijW0XF2sx8c3;ZNBquFlS906K_U5E82Xj z$N%0H2~OS_UnYPy*@wW#j5IP~{|t=10V|AFCfyPhVJ9rS5`3+Y2V<7hjgpUkQfa4) zuNE@lYxEe{XI?~)|BL7`uu_~n@c*>>05)5mLa-j5XJ!|K05%Awz-o+6W8MCbD;r=1 z$Zy;F_0IXay$uG-eetPHjY_5K_pp;FC;6Lz4|Fup63<$1UtiUJxT7LytuNSzfc%^UHdKQ0nMNt$1LX94mRWz$b!q^+@%j)NHeRlLr$s1l`Y;F?=Fl#Nvc+hXl-evS8?{i zYgPerA&Gl;+{h*kejT;&ots)*=+S#US=))a$&Ffy9JL{zNx75r`+DC_>sMZYP%S{;mfub-Z2M{f zs*F>rML;-8twO^TiDh+Rqdt;FNX1X3!^fCnnCxY*FOI!!I3$Ses9J!E?5TzAOf9EZ z3+v1xXLNp1Ms0Pz67RxABgnN^Nvl2==@B^cTgf#(LAZND{HFs2DA0@RlKbJu;sg{q zLFft8a)PTK7dYcYJJ1C z(6{Bc0}lH>aDXb~a7$L(*dezAJIeK8WEP(EWp>^Wg)kMK7O-SkE*P?BF5EIzZ)-(aDLh1I=- zUn6br7LG&vMi`}7FHUH^Bf`I3v>*tvsP^H-A7~+&6yfm_BYloTIAfi?P_CiZ%HYnPKbE&eJ_nKwOVF# z{VNUa8gdiWBrl24;(o$N?ay}|Y(3x*!DhC+Y%3_c&L?+_EEi<^OMHohmZR20dfC>s zLUB#WuM}w>+CTaeMr7rOJHs-LU|$!y1RRRv)WR1zd0M;J}6r`|NZRSS;E_33_5kXK5rOSuqKr#MjgyT zRyx(%#d;0P!>hAetQIQJlH1{srtQ!f?if)s6W3g%&wQQJaBe=`nEwmCqJvXRQp+00 zXj732l3QXMA9uOh!qCIV4<8EZ$dstA>MK}VI6Rrwe-~R+MrCsIy5%&Q>#&Ki5k%&8 zL7*vLaDUb(cxL5PrA6rC9zw@SMyn4TTvO|sdpQK%wt5-MnT zU1XVitthAYQUG_Q8}G97GD@*8R}GFRMWTeB_IbQw3&oJ3k4@{>?t<6BVYG&_L5E5& zbg?haUedeonmbt;zLg-`%Te{V+;jq(+iu8AUbu#X9t|?|CZ1lBvkxTF)vpU(K3T<0 z*0@--ZlHT~pkcck3(H(&Mt=sn8KM=(Y7f1|2K9BBOYQ?uenfeNyY1CA-hx-o*BH=T zl(`#UP&7a#oRw`=~|poGy#84=1Wyz3ucjg7AJ zfWed6fo>j3je+uoL!p1%GM9@fd&^vWL}r(s3`G_$+R!&Q1W!U!QZ$1FRH=sbcVl4a z5VVS*f5`~Cs?_4k4ogpvaFIzKOr;^=jr{a`IVz%+O=Nui`i@%Uq%ZJc{R9kQEeXR? z!5ERf=OZN-)wkTDiZY0j50<&iVywFJ=1z^v&`E%noZff{cod-2;A;vlT&t?G1gn?& zb(t5(u`{ogMY3udk$RqBhHeNb*i?bn#pgZ_U#!lBfJ2zCnly6zX_*%WIEn&`u9Y!- z+F9mx!puwbTjq76fuJAGz++DRxS7`{^f+zGYWGq1OR8uIInlE-D@rOs<-HRTBx;Dn zbz86gsjAPGxtw;H3;ET?GKL&t50CGUH_)z_&JFI*M-~0!Efz^O`GTHt5UX3bKYesz zYLGokvdn4G`8+EQqV4)N>$^Xs9F0}&2Z76aats>ZXY6KFby8JU6YSMk}-`Clj|1AXuE(?aJ zXJ~Tuv^Jy&t0cq`AZBzsrVSDLWJhWeZHG+TN~V->ne%=5gj;O4DEX+VNmFWVyR7~| z8v;FWzn!o+_k{&i8K)H1+lH<(J7uqrGb;VK&30y(BjJ#WadXxO+%IY&rDpPH^z(WhaHJ5 zD6F7$5w~zf#?hJz5%W<^a~Wc-qmWj|v4r5}!fd+__TW-sOwvunowc!?*`&{OBQ^9_ zGF(AYG4Aw&q%TZy^<&;2nkdst$O&V8foReEP~_4l$#LYRk(THQJgZF?FztFQai1L6 zo=s_Ee8rnO#1*eCF3Y0Qsm&CBX6j=mZ7lI_CV7ZY7is|N?5aphSDBxB z9zB<3v73)}S1&WoV~zpvpN!N`GIx)R$)qcG8lYtlv4o5uq|UKKLDTrPTJe3MlwABt?8Jm|G( zjM59QI2XTu*~?Fot&<{}2Ff!g<~*md*fXK(km%}!2y*P=2#nrM2wWD7Aw?ucbF9wD zenigTjn3qHO`Bzo3T$`A6WZ{c3$-#l??R=6d}$3zkOwo&@kx@Y2w zow$o3Nu#4uHPX8X&mo~qdHC^oHJ@>@3X(POMFHUp1_j(`+iu-#pVG4)*+Z+FJb#5X zhSZs7VvKD1@EMbOEtTa=tUh26SURp75p$$(=mpPvavd<7{5sQ_-a8 zmAUh9PBWF<(t=Up5{?3CJ+sMr8)-HvCl-fAf2FW1bI=<&i79vm4e!N(#;pN}0o5;f z9zj^`4R7?mV8r7NqF=^Ga#QeSs}0CZC(L%fwKWGTOf}z3JAF>MgPpKddr>T%^NEL- zxn5>Oc_h}Z^Ov=G@g`$(p8)@~S3R_8V$d#=T%0oZQRKf4O{k!C=|{81Li056#K}h@ z4WKjL?vK0}g@=uZN2MlG#P|KoKXf!bArIxN-1Q8Pe1sL6b0nph^-~Jzrk^PX`Gr$e z8e`)@{P4u9frm6-hNp+gPl-2zVD`T9QPr#2Bl$c-y8IhMvl+GBy}=2uM$qBI;DOSn zL+#a{H#qU->)-?m$(myzO1>=(*~rXbQC(NstvT!|G(8IHEuO{lk<8C^pEHrL29z+m z@4Qb9c?XYTTc!O}pld9?#|HDgj;K*xcyd6_Y)!n5lt2F|r- zc;xCI3{Jq_4IP>5xIYMWWih6zpiU`{uTj6Mt%z_qTu!avkult_OF%h~oq)~}c8nGw zB;Sz)UF=0rSOa;^7MqeJkYV8|7vKe?2_^kgR`RdxTz~;-C6;1LgaTRkUFc_1n}AX+7X)2edq%y#0t9GqA}#&;E+5^5BuS9E<|w#tEPWKKx=-C`W-hp;=}na? zia#&`1^Tx9kbs_GJAxC0o^0m=uKmFYpjYLGJ3qmQ!wEu9HaNkxpE?6QMyE`j19k;r zXww-yGVhLZnEN3%q~cZ>aPtV1pmf5*m0i&%fCO6 zHOBgeRG9EV)&_LVv()+YsWZ^g6z<=%HlWHnlC?qHH`D9qHBr}6!g!);LX3gZ>2z%X ztg_wOa)^>_)uGhg<9qK6P~hI<<$kzp<^*FY#~pOAWTli%@-8LQeb+pt9E7PXbUose z8WjFE88;2*(+hc-zs`JIXXg8h2OT88nU9W{pQblP;1bVq&4(o$f7kZ5J6V)>^+&sD zGgP_EIl7vAV)T{*cp2$stkU8o5uLB`=g`nz$w|`F6)3p|;Jfed zMhO)7U+hK+)QNnkD+_3T{rK+vW z$FFPOxsTnxPg@IVWpv!gloq(~BEohz{0;GEEsl@YnT-C=Kk2++4Ev{#{=d_wbFPo~ zd>350UhG+)&Iu=+N_}hJY5FCeS`-x3sGPO$zvEKBleX{4skT&7u^-=jKo9JoHGjb| z`q_w#Zpn9LUbF_Wd$knu4{YCozAZnr?j`gCsXx9>oeamw~x4(?j4b_4lf zvNCPDq>FOYfP`W-5hN4iRqv4ho2Kf&cts~DF4*UMP7W3AzTg?0||G)tEte~Q->g}hqIz}J(-y`0hl_aURbRp4l zriJ`@Ehg-BR!a%UNk!?i7W0HHru4TK^NNj<#zLu0YtmVZ`8TwfI5k|{+>Os>bg3-p zc|Lz$yt7e8D3F`hVZPuqMgUK6?hkA+fxa!jU5m-H-(mt)#wlA&;p-PO$BLF-!g5wa z@r>VmXU=1`TZ=hSO>gs-ME#M+G$e@UXp0F{WUs~KIem-yG)gcFZMl?BnAW~kNMh0O zEwX5_oM=~=+7rG6NB01p{T34_?nsLXv}@qOo(t%K_no z2(~Xbi6>r462II(05Ej0IpWhypm=|t)@b4imu+=baEnx)+jNR-zeNf!1wJzaIPO-e zx*G=zx>(Pyb`nYuIDC-=!^a#*71FyI-pSW(y)6Tu$GT*-k|T(*L1C(G?plgOV5>P% zbrczGSY7%$`6z8mbuo5nm7K}I`Ykizyd+Hj_-}iiS)YON}mK z-!#+nYDO+~uWwj1gOL*m4V-w-0&8*C0IJZqV_@k$wTi&=>h<}1kQ3OtpVT4V%F*YAjs*C%xr|EiYMsLZR17pWQ19T-u2s3Grqy)$I5Se>kFHw zGNp<5SedL=2QCAiG)0~j>!;^h$W3Nl?GX#y z5{yl2m<06m>^YvnRSR`%>wwXv=2PWCUgvP0Ci9#{hBMEJ5NJFgS5SjAwYon^p96W%Q@`Ahh$z2;%~CP=(KnoX$7K==k=>(yN>k>|MRo z>m+vc06>>(nzWxtKR3RArbr~0r2~Ia)^9!pn+=yO--6AS_-!K(7y=t7i1$!83=kF% zh5m7qkMN83CLdLj3sLFJ77dcXFPmJmw_4i#S+1*D=+(}Q! zrhr+Yrd27fUDG}-7aTZ7w-{$6pk``Jk@xf1eLj}1e-Y&=P0ty-PsncBZ|o*7G>yuQ z%fnCk-(>gRsXLQz$5Yfczdt^ZduvuCzphz9_|KXZQ%x&lU7JH6FXi?*nA>2#nW_?|aE@ISe#po4YnQ+DiZFl{VP> z^(4Ojdhp+WJwr2Ht3yAYJLCM0bsQ>;*7mCs;b2#3TImUvd;Mu>C1KK;M?%j@`Wb>;|fgQ?mP!23xi)(q-0yLpeWyU?T^{}r?Fx?UM?`zFwHhQV|SOd~P~iW_ z&Z7Ykf&J0T_c2XF&P_zO`AT|GF{pKssVjofB32?fF|GslhQv% zm_aAOyVk{NfZ*4*&%eYLg6;i%1_SZ#cME@9)gp8ccEW(X%)42&Qe1o9TvAMiEN2d3 z0*Tu+Fst&{`IPtAKFxF$susdt(aGg;EQ-s+5S1L8yq`E^!uR2l3I_JEeD_>?i`MaG{vM2)P1AK&HI z8+`N3A}6$n{5Ojb&AWF!CrNyK{mdeNgGEf=P!SS7@l2o*=fLaMLmoU=aV;0>k@hN- z_jU#iEQa|XXc3@q%Wr29{(Xx8RmLeT!g$dICU@;Eu}q+RCpUQ!+wL_{3Y5<4b8quQ z%$o7rKS6@{k6Hw%$eu;`Pj3;oNec|nQmcq1LGXJ;7UK~%fz)$PNz!sAx6U=@)cBU} zTLdWXh(&fth9Wznu)WwXzUU8D@s{#BPHf+`QB0tTAHu>)677 zY_>=XKXTs=67AL!a9vDVcsQI}EQ$XaY4egZJJLe$?T=anT0xO*zA9z; z1K9%fZTaokBCyXEpvpKUTfpLgjZv5X7$5yG4S{J<(Ng5Ywb*>K2p2ggf0E(yz!obCBB z@q1(;m*yGp8E0c_Om-;`hy!%ZGvXk`3=q~F`e{!<9H7wso*B)cGJN?g@-&Aj?E&n0 z$@|{6*{Owen1?cz$3Q+#F0a{5X{CSFT7d|DM@wTZ5|(DX4W(d zf2#1)PJhYZG^V|s@WIkNk$C@>S_(d6B2Jk%`BPRLI@Lj+La8y(&nIR0RYOlr{JPdv z@Yt;@jo8)>i&NdlIprzBOe3QPvl4YB^mY|JJ|qm}1eHSUpVzux@C1MC)gr^wde*w0 zuys}X*1F=h#OGj76Ts%4bs+o=9SCL*zvLn^PtY&>lQyxaJ)my7?Xy)J7!JyR0*4Ux zm+D_2A_TPpQ{BT*Fj_Vn`~LB!ZEXtv zF`qpeZRsw&d5cOke^vcq{WYlJw|Nb(*bTYNV!53Z_#q0J1*g@9ZsJE{$ zZ!lkHlf~Uw*q$JWTG2*AR2W8(xx&hq|I(EBItmHj>lPc#%pIGLRr$^@E5mcMvEL&K ze42wDrZx=acZbnP8PvG9WL&I(-XU0XIq>T;z9@RR=l3$Y%werP;+!|7TM!%N^B))a z+oGFBVkP34{$qf)Q&9OyQMDd!i7i77zqPfMO84IPKzpq?YkJHZ#3eI-pCg;2whAr3Y zk_skAHJ#&@YwOPC^iI=BF`b=+W~kVgTO-M3gn+tNz*A`Hp)J-*K^MF1&fTbD3Qz6; zsaFab8KT@;x!bOq65dnf6UBx-@6hK-?|R3@55R=6!HUJ{&^~O&yCUVu>rI7~6s{0Z zfbwCGKLvuA!+i#mX!@4Fq`hnBGBm^kgo$mWChJE9E9iU#(XQBDxN#A@5_JnRieH!U zA!%P))rER~t6`ZLkM&VOQVd-4m^nU`YiWza*0#w_ly8gpZl%_#aa>^Lsa{`5uw(Td z3a?PCPByw~A~KSlOSXDLVsE4IA9fJ;?yRVFgA5j>grJv4rneJc2HWY*hl{Kg*~U?u zLz=1Q0Yen~1QI^vPaswv3jO02@m;FlTf~k`NTXb?OJCrbKZnD>tp&dnUAB<+PJ%Xu!zssc67tlC6q}BJEvQWl*8N}99riJy{b3*!1db| zX0U6&4(LM12D*{lOc~NzYg$a^%Wy*Y?SlTPPcn6s^>Vv$Ua2WD$re8k=%C4Uf*sjz zM7%SgpAhIO-++zf`EQ zPf|vDIyl)v^CVtgOEYA?pTi6gpVv?OD*^1riTZ3!uOww{YkFh)XaQSTK|J?6?IjSVheZ zkM5`K#&i!pyvz6KsxCXEA^hujvsTeqkbZHs(y4;ygBhdZl!PrO2O!i{N0O*TEDpvd zfv)*$#wLLV@eciGKPU9(OuquU*MaZ5jRVz|7ZFcp!D7Ok%%u7du0lxD6h)nDd_)j3F!t-zDSW2R|KC;c7Wg>zx|G$?vK!-u zmsKo#+5N!7VrIl}6;MJ3b|T<&epm(noebLl^;w$V(Vzte{$F1Tr20*r7>QMPOwi=2 z(T}T6d{(i#aQ}ELn4M=Y!UY3U4HJ8E4zc znCc9EpeF465T7`~uY3kx)+LxSK`^d1)kGZY3eMG5ux;KyP@O;z+;3+$k$t-XRmLgp zRzxN=lNrsKygv{4G#?Wt`yapyUjBO;^g&1$WjQb>@*fEP4M5Y;5B&&06Y}?` zb-i+;Nv?*O3RVvx2TP8%7T6REX4L&)PS1hM`azAB3+2}dL*z^tf3Yysz6k@3;|X1) z(8%b*afKm%*Cqq0RwD!ks>lbF_Jyx;Oqq;B0}SW>i|$MtBTrIU~~#VG#hj+ z-9Dx;&f8wHzI`#SAx`1NEq9{g0O!!>S*e`%6ySObFS1jI6#qbB06lQOoiIfAg#lC< zhg-7R#tyk1*io(zBeU?NHv^JMDrZx|@bfebHWqB|V!8xLqziW<@9U4fbPeJkt3x-l8HT?YJ08oUHRxlvXDv>I;ynsZ5tQW9*r$ zi{@WFbJ z$|Ro=HxL&NGy4Z(1n7bL?Jy#?j}f5CI3-4kCupwZ5L``W@qBeYNk4t39w|>GKc5M^ zEuTnk68m8wBuMNiMu3X!VMOeR1WtgF`Ew~>pw){FSSub1!8%`kB_;wxuvp4|JDkX% z&F<2h>H~}b1s=JD6JX?_-M!1Hky)EVZ#pobG`SWZQP5-3Vd(4*Ddj&<{c<<>VAmp` zz+ba#(Fxe_b1-s(8#u08>e<6x95}gTGnKSn55YG?q_pv^*Qk3SrLYQNX2%Uq{W`aZ zow?;Nc8kV0w;(zWIQn_b+4CLOEw1$iFj-VjnixP9?9}&D#92bkVVn4$#1r76ER-iD z2AsL&-*n53o3>A!9$Mk09@8ySmn`ie=$qJCb;HaiKCa)*xTe&$CgOC-hpg7*k?TV1 zALtgK2ky6Xi}=1N6|lG;ugmU`=lF6g(KvA;wIpjb&NrgjH)QnoBVX(7NEc* zw{QZtKEzKKXe*5b4S%5|bZ=(vr}Y*T{*0|{$&LS5BAkT^gO+;2x5iGAV#RmLfa zgZna+&uUkJl5@UFm~_QK>#ZcT(EH?QHTxaXDaIjY8Ay=CQQ`m<*&~j`5nr5uI9abZ ztDmrBN7Wk;2hx%>yl8zw^I@D{j*{SVBPaA_c%p-2?|}mMj=ev8ILZl#nNnv zaV#OYxiH)A0}TOP^Grj1u(E*&hkn`6M*?d2yJgc>1%Kg1&x4YXhdQ~TwKsr{Ng9e3)YI1)X>~l+qAbH>vi|tPtJ@rSJ24!hj=Qf z%n?x_f^L=_wyi3=q78JX3eK2Wu2%LzwsyXC2N?ffGh;m5t zO@$?zmZ<_{7^F%cNC_XTCgIxAb>9)dhK2fiE@b7Jz^#*Sc0-3xL-@shz>*gF>z zNiz{7dFX8dN(2st{&7o62|D+dlm>L|X#4jT^hj5;BXJkD+|AlagV0%LE8cKh2Huh( zk&k`;@mPc3cS-5SJ6m>^nT$yI)e*tB$4{y33$MmRjr&!W6UFO)zf&1-JD#`tn3aBzpnNkT|M7H(8ofh)8Zm_R?Luac(fq zvgtmkuTu*WBzZKY0xGhXQc0dZrMhRtLzbQwWI`>wE8B)r@FG*2S^TxFwoM;ObAdr{ zb?-q+1r&HBr2;zY@Q^8h(w{@2VF47CyAs@QZgkcnHxJK|I>CRqiCt#St|Bic+sl}t z=f1xp80Zc7;lVq>d3+}bJ=xKHlKZpw`3n}C`QUobk>f~F_fGqS8_+|-Y)M+Cc3VD# z$jvOV`gMemJR`(kObDHCgn(Syxd6JHzP5B+LR=M7V%8mi8$}z-;P{w@f`JVIQTT@6 z=u+oGh|8;<8@K+cX*nYV-BnF(ZS#}u8v-E=hQXz)@H~3O;+Uc@ISkz3UtjnvTh7SyMS<21_CAFSQVuB~?AKqWvxC-9HGc18697rQ^k{i%-7TRDn#X6>!{ zF>c$rr2TcKd&Q9^>Rrhyu8i%63M6{GTi;?AWWzrQb9J=6XM z+FhTZ=WMi5i^NgIuCk|c<=Q-^=xjA!Q=;#SHf8QHt*k98geTW zylSNCGHb<@2JIzwQB^L$OjE&)HCaKUz%i`j95JYP`vIKXAg@j}jW?`mnQ4jK}OeAZ}=jv}4fz3{10@Jmk6PM}< zf`MUgxVTLGx&c9G#!mAB#n%qONh$v8jY)n`eO;ny$&DI*!EhJvWpFt~P(*-W9?KGw zV}ybk^}_8fLlMM{bnv_y2#y?9It*28*dIFt4Xu~kUMmDW z&JOM4SI?k`9yU*Vj$cqrr>v{t{){R4{9cD({X9te5VL^{dMNad>kuR)-|G;p>|n4L zbdv&WXSCFMzLtG{*JB_D4SCEi7=11w7iuMkeQ;CWA()E7$I&h|iXAlvBh_@_%h2UH zBCuyLAvZ(saP-!JGtKg96pFx}AC@8)$GKzbljYzM%vZ= zo=%`w`-eB`1l^Y>2t8R(r}TbL=aoxl*0@)sS8|~m)iAE?R14#=%`1l`3s$n^*LOME zi2XVnNuSy1FSe2XHydqt;-a^ZbR#+aZ`x=lUvu_Vo~>^8Ut*)eK>AV75{jM2Yw44W zirA{E@>*EU%-%E*F@`R)L~jU>ZzHDkF%{gZCOGhOO>s_%SJ8AfXcCqp2T?nHV!U1S zN#uUIjrN5B^e>%K81=5{7-hXAEz+;mcxe4wG_G_}DhOHn2e#9`Z;Qlf=Y#~w92Evo zkv(C^oL(67a;T195^(WHhc3hU*Y}R|Miv|PdIcdf55YpBUE!!d5C%}-FA)a+{Vu92 zf|Ye!bqzIATVST}>vcc?Y$H@}@zxsMT`cj>poh|E4lbe1_wu2iSvy)8#!l(M&QdurbW zHsUKc^wLfWyon>mx4FYG39%=CBGyFH@iBrz!O_Z0{7JH!KzA1GCOXstTi;4>H`4v446HFtZDa;+X< z1Ss&AU}R8a5UDIhd-e_Wx&%Sa#;DPYY?Y0vr)?5whB7$lHk1dulmP|)cDt0F03$#v zI!@shpx5rVbBpXS^naGEP%VUrKnO@$QQ=VHn=#@E?>e$!@N z3Jp1A4q3zWql!6*FJ3>$A@f^Uty=nAybz}p>8{zmV|B|G8p#+9(<*wUDA@QB z=JxFs^Gt;|+RX&3YhAUWT@eqR?sPnGYIm>6Q;#3WV$$+QQxqpl&Z&2b067buo1pkS z#8+@*?R7?0J^a1kN(i3R<7UN` zOa5P?IldG9;f}6Ik`c9uMyBeT@ZE$*iCe{gCHf!4=D@ zm8zWWMk-CJOqvPGj2E}_P-ay5Ct4kG`OXbZU4I;M+uVgst6e~93PVu#W9NX!rW+!w zV5Jo&=V#7TXEe1XtP5N^kHb3WzyrHSl#&Ait7#cutY2!)V)fV?z16$$_;sW;h&BuJ z79uh3jm#oCX{X-U>AV!95%bdgb2`{iP+%U19^qQgKr2M1l*lizJhkUY9At_YHVsTf zBj~`lATk)qB|Le+kcA~da)(L)QT9;ipEr7&zc+eY>>U{uK0laWv8ZJHy!e4klA*DM zQYNugby$K85Ona)&*#fY6G5By`FbAWzVD}f;3$u7M3%``w>)Qy&NMpfObTcX! zdURR6Wpc_^|8+@$?6H#q@W{lbdkmJh^`;(O$@Juwx#W_pZXddQsfZ!()pfFa5B^CI zdjIut179br;qornP`xXFZTqe|u)V8_gMrI}o!2uoxk`7{(9F>0(6eVu%QV2Rdnv5) z;>b>rcZDuv@Yd4u1;2YAoh=E*@v^tRqe%-)fA2aHU#|oIpX-NrYB^G+&iC1piUy>9!5)=51fO698I`7pR9-pOI=V44)-2?PyM;+s|d(<}W`MsI<> zEk7^`?t!Fdb{gMsAs`&NM}Rw$eCqjH8m<&P2vsK_31kb;3jFOh0s22D&I$Hp z1Uk!fUqe7I^KYji`Tc(2i{x(0u66P=YY#rw1l?zQU{oy>GZO(xfDo+P-=^4pWv?H& z26C?-I5RU%Y1-d+Y|EX$&gQYA-&hy84?*STc?RQ~m<_&N8Pu_29U(nliR8q=UGWSt zXh__G0nuZT^y=zjG?%tpBQ3QtU(+&=O*9%izkltlIUhS%hWg1YsSE0b=TPnI%ME+| z!1um{!9D1BB%M3o6EqfF{Sgc6^H?h3tBI2PF9$TnjW9uvm;HxFK=wX~Ay5?a439Hi zJ}diRn!@n#J89k6+uNvqfLW8MUB~VQ0{ds{g*jBkYb8@Rm{$fbrt`m$^Ia}L{Lsw~ zZI~bWt`dIKO#I^1n5#|oK*da4^;7u!aFnAL9wl|%>A(ViA&(Wo=g z-%&kG;*|`mF}{A+e1S5*6wyjG74@A||JsA#!uB9S>5=F9L(v!B_bO;Ss&x9o7)f3V zdFScdG|@rsU?}cK@-iL-PS?v?FE3csp!$_Q6q1Y|GW*Qd2<2E@4nGl&^jxTlYt~y7 zvgnp|84kA$k*XP`S=OA%NVr{Z)I@=FZPfdg>=#C&m9QoA55M*UKfBd+i8cA2+xAW& zm{LTqoXb1;kn$BC>vB;nGkrY*DQsAXANqkW%yh!ybD7e+C`~*=V8;m6pYgY=xe4Lt z)K@lz>!L8e*AJW(4^lXkRRaO}Q0O1m4@?C6wI5iT3j%b@=sCu4!A=DyoLDfKzBXo> z^2_G~GMi9t%VwRO-i70Or46z#K~9$(e74WAMQv{A?v79HhOpk0PKvPAJFuQtywzE=p!)1f%}Bor zm2N`!U8inv`kJ5uJP|yc>dx1syD1Pi2Yp@dtyP`hWT5X7J{N;}`-y)V%WdBG&1#Ip zM)uj*UzY;LCaBK-G$7z%yo1sd7LL+hoELOswO5z;#N2WV&q*M!pjVF;cly7 zhM7m{PVA@;$GlKV!I3P@+9;F5+P~94=Y9UN=8PAYp`Aq&W5JuIw{LoBOIJWn(8au2V}l1%3>JQsn(YWEuBG#rR-dc{GD3pJt7@Wn5*xJAxn zej?cfwSy31USCCe=8b!g8C8(ZCeE-xauKTL&72Q5TU@XV^uEqyE6EusAy~0WJlR62 zXtuC<8@kIP%bmBL-s)?i;w|rYYrd8sBl7MFw_?SY0ApGC%Qkl)-}b8&l;~M_=sMx8 z3KXYnSl0}~4MO?IXq>0k_2XdiK@W*@+r~~V z{|M&tvsXEWBlw)Q{i7s(3Rg$Ep3O~j$a*#qS5sorC}@OaU-h}!2a!>LSbP5pIYvY? zMlDiZhk`Ng%T3PfHcwDpAKd4UkJd_RQ7C`Z4Yk;Gj`s>Xj`qei1u|$HQ>pD$Xy?Jg zY{IY`kDciz%Gk9%Xp|8cRCM7vE5 z8v*Kvy}`~tB40ZOzy0B;UHtYD$>%_*vgpEPc(!4JawAI7y%BNOM`M42a ze&OXIo{D)4-n%qYa{gd2=^`I-1}lD&^rHAAQ__l5bb4)#)4SqqTS#+ce~UFl@n6?p zA%E=t3Ud19ZM2W@3I@him?#Te;7-@sS;tl!UrBRd-yVA<*85M%&A&64bTMP}e`w{ZmB=#znSJZ8n7^df zFCqWw$+4FLB3K8&sk7&XASHyk#k9-`-$^f*kA}~>R~6WJJGtA(;h(Ddx82wS1NJ&A zJ)&^Bve|FiU%^MgYuB@zL|OX%{veKwaax$$|1ic$`_T8b#$(7=lT5hpEuIpl__H7v z>n<`=>e&TM+{N(HJbr&gO|$ft=FYH-DOlz}97y9n{wsOM49#)jFz(@;n}R}&|5VRE zWiSco+ww!Mb%GJ16NH{@FiBxQO9XmVez@}!>@#wL(35RRqOjjb1oRl4vX5vO%{x93$#O!AcdoSL_kIM`iK-x-$(Rf5%MYWD;G@A zgcvD$UmE?&1&Xc4GOTc?XG2ab@RD^0%L0J{_m&0jy_pK+-#@-57~A?ep*MFy)b0^6 zo3(-uhVv=J(;EFc9_-u+_jE$x84;8nk#(2*&$KY0I4>@6_rtS{G}rOQMqi$zMhj!| z`xM+%Gwxm1b*Fkb#!$_GZ>#s7#r~rY^!51R(Lcc$&0OU5Zo zoGC|)uu2?1WL(MoBSzdgqv0Hukb_X{(<}gdf0bVZ(}!Eb5r9UulM@OMLuInSoY z_>vH5(+GME94zBg_lpdJNYTp@1@ybZ;uJdn?}SyLz`ftr-SZI7fF`{h3I!Uy+Gm54 zj<+^HSD+hq+|BO9Pmo0E$TR9efniGE{0|V(LLUzQIyNYtvEeUfgT*&C%qUy2YO>0P zxE_}c9zn^BQ)r!zkG!VUHEM}s<$IykU9n5Xa0r5hLb$r7&e-s89+D;WfgZIHk4=mE zm~5bt74hzLeV)czU9zU{S?O0nT5|7EO<}G@5!9Q$#%Aq5kPSc&+;7JQrF}L4RmLgV z;FSAFrV+H@{07&sWtIyfj9qb@#916}0jt~T5_<_n7bHmOC>wx^?6E=V^lX^^c_LD{u9Z(`0?e{=!w|<>4l+J|l7YoDkn=qy}oIbLm>=qy$R~U#gIMEeIRYWZ9 zuqf`bak^E*hUxb>%kP6(c@R0@s=s|EjDJ%YqT-iTXZgZ8>J#fFBFqHR&0aO{M6o!Sy=OVr+$+$JB3C!!I zvDgg(Fs*|hYp&y4GgCD@Mt=YaQa&mSpdx$1P(HmdilGI&N=bKgnRA$1Cc4F=n_@!~ zWmni<#QM9$j7NCzA9PFr1^#ZrfXFc0gy}RP&oFG*$m}?`CU-@-4l}{SK*+zSCEP+z z|KJ}33f%k0+z*c%{TyNZ1dId^X*m-pz%xxeE!IvJ*NQKs!%?L+oTa`_$ZJG<{}S1+ z!-(=3M*d=qSbf9DM2t_$=iUv*yT`@InzxL^+&uN9M9cZ+;g@$W^Nt&JxV_8;Iac&k z;E`v$oMGhO#E5tieipS?Y!%)yF(Op6y5`qJOu*FU4t{$^Ycu`HdgMep8G$R_a$MwM zH0&RU5ugX|x5J3aK1P5l0R2?fPX~78A>OjVB$a|~11XDgV$znZ5_*jZ75Ml>yHc;U2 zh7s<8X}gRf+y}~w54kJdB*!JEN{8+tK-+w>kwuhvITU)Z@gz{-(Tykn&oS~7+yZ}h zO=q5$P;1dX&v~rZ$AUB4Nu54`%Kj}gKDlcH$ZyEKE|Bc4BS3sB(i<`&V&9{U@p7^DUv>2gKpmm=3V zZ+Gv?KJQH4`uLb@J=*&JWA82ls@k>(&~Lg!8UzXHMwF6P5NSa=q(iz>Nk!~qugs}ZLZAGvU4?@}D? zm3Ehw0Tfgpz(A^rMcy1cvUu>sh(g?P-8x`_-`B1CLy7YPG?e*PVPajXdvoB5P3lZE z#8bUwDu+RDg6y2Tx9M2k-x>aS8dAQ{&>yTJi?14bXA;+-_p*GB|Fi=>u}?j(c-P58_&w)(xq7$^joNK4roOB5&NO zgNfhfrouOC?SHVxkO}{wPohE+nUbvd8)^vH1NX~m=)sYOfZdF9Y6zK?*%lDnuP`{1 zv{1^|MK+5>+Gp0b6CT*Aq?aitXb*<<;Cl@LYjUWe2j|xiyw>JkUQLEsKxBIr>2`^| znVU_1^WEVO3YDccGyl0H;*yaz^p$_nO`;A84D@_J@!mElSmkv}tzrVS|mOQ#^hi zoE}`j=?{jJ~e}>B>L4*whXF!L{eKS`42w5Kh1zxL*!VDo1bvb~DZiC#&hC%C6h-UcQa~{sVZFcAz+GOIP?ltD8RN zZj%2b6a)(r+Jvk`K$fZ} z^QvCyp8GJ!ROKmy6!4~d(~kK{0p1%Afow-OS4>+NYvqc|stxbH)C)~BlzAh&xj2_A zv&CvsqUvwuK#CZP<{$8`rDp)0`4kxXo_M9ESlfOS{3 zWcWq%gD6wESM!$!=?Emy*7+ZGXLp+6wE7tlv^!9jrWD_L!x#o}+bMg01oj4f9_cVq zks-aMAKZ0F?+1N0pWaO=(mlZQHrVu~wD+}ysd+q-W>$wkF@DX;C0#}Nk|$&f&3KdS z^z>|Eo8y54m-o{U>-cZLhfB0kLa4@- z_NK#JxSk@9`jkp3Kk!;+_a`JOS2ww2r1@fLK30zlWlb~&s>1*@y68^NOC(r9I%@2}rLCGl3P{5jE~*J7^R}*X(Gxk-*j{_pxz)^pC=Rw8c(eoY-BPMs zt1Q%*H@lyPm(%6jpf10bRmn%SQy3!$#C5dn;rM}zL72zhWSdjZj!V?|J^#H;fpwmrwie{hctJo%SstcQ3yBbp`uDgN2v&n@`{Iy;v z&UE3^ktwj{M7b4_c-w+~K$p>O9;;7lQu+t2Q4Eax*vKG%s~1|TK`~oC_C^j z-Rq#}$+s?(y2)hV+{(8|1UOP|11{Zpg{6Ap*90uRI1&2W6&=1Bd02Ef#HP@l(RSK6 z6*-zIAgMnK!#uDG%cQ_yk16%wRVboo2Z(8WeUrAMr!c9z=BTeOB>G#XZm#G6>-#J` z+m$Q@VqyAIE1=NY$&Hgb^R3pkYN~3`Rv5&J2ie-(!XtcQ$Mr%V#t%J}G%;mc#*kro zH}S^Xs4kPp4LRGfN@`Gol9YA#=OtSMa^@iX zw=)M-P4zTSo?fWXJO^b1wU*;o`uVj8v`hL#nn560Dn*kekx{9N%{}gKnUESl-w!6-BWWqYx0SPb?(vqI!R zTtdeSF|m*mGaOOylo6Hc zzLBhToTvf2=J$!(8BRL^mZqHexc{S}N99a`b#8y_Q_fJM^bDbAD`%>DRF`Zscw4u- z4c8WW7ZY{z1s?xqtWKmwcgO4E_gXkkJL>&EPlu|f)}b3acWgH+?&rqch`0{U%f_u3 zO5JUg+YA6Q)dx`1Or8JqD*1P+$rHf5gXy)hqq_U;^?6ZE{nzY3BWkC{2&2$VtPrr`D-zu8sGvWr^SfvSB$hj?bh*BvRiz6fssD}BleWyI0)ogqI)|f z(hD4+4@|*srYS|XCZ~w5%hqxgUH%O*0<3TOaM3`_lci~wtLh!OSkW2DaIpsQY$UIVj?c7#%L|Mkl( z?`PEjC^tYJ98ZwLD8eyDfCc`>7%_Qz$9ku$P>Ap0B#GqJ*dFzTzIO_&N+fad_3yJk zvOPUskP0mDa6#(ds-689jGW=Br*#X--aC9Ir9utZr1Ey|3`V8fZ=6MU=?V1lU+G@E z$y)sC=eb4w)NaYUuE~da&2B@)(2sA|Obrc*%xU>4^uPV1d7}TUy9pt9=+64;ey-*g#@bV^z5kx?E4woiL0l=6;!G$MyJc04(tEZ{T$D z!8n63&M=5_hO3?-^t8l5Ty5sPtKxat#4;0^b9+%1o+Agd>?4y4m&BYOos;O+&m)e; zsfqJk5;NRjKnx@Gf!9N14y`xOY;z}fw9Sy09+7!6zt7A4al|2`E9+h)lZWZMAkG-4dYKe0%&_7+gcl)ZXY#e5fz4=CZ*e;o>5^UKg zz6~%e&F_f=tjQs9G|x|*H`P>{oaX5f=$O%dD~VZGvw!KNb}ShiZAc4M;VaD zL1=@M=`%*42=QmCm)qG#o64>kr$fvG|8W|k@XbSidJUdk_(DTx)X<}^8hWGpAr{NR z`AOG>hW-Q%1=^4VJd7M<9)u^LEUM7%u#H-;HXfVT&C?U7-d{Jd_zg7#tZ(_{G^BN; zAz(M-oErL27)moY~kC^Gw)eAVC5l#+Y;Z%39t)eW8)4g*6r#}HsToI5|4WZPx zWgQD7RJQt4s~$Szl~+X&(4*exkRTmF`wig)tZ(_{;G}&7Ctx?@oN$T_yEJeaFKA|w z@s-3C$!x7UFrUnPKt^SDffRNH;@vthEbZ^%1gyy+oV3pmr;meC8D%XKNdzu2{0E#P zjDzUb91hT1W^<20;||E^yN=-mEbupm(+!*b?M5#+S&?RSrLsVRcW8yM+Qrugl37f$%PTJfBz~N+Xr*F; za2&mjE(WL0xSbr!@vE_FqQD7U$qw#a3{D*uru+T3blrWm5GVA)Rn+eDuHn*hNMPKw z<>u-O6Z|-8cP-{jHL>5&Sit(0U(Q%MN5%qnGtOx&97#YNFB~B}IUm&UHoxb<74wLW zTF?;{E(Ke&?&Ey5@E3PByan_+~m9I$<_3nbdgE0iMDq0FG= zPh4N6gni;`b^BYsSX<$|6?B=HbwJtzc2q2s)C+%+@ydXd7W{{%jqk}s@M_6Jf~>W zwJwId{sfM7D0s=vWMr=?o5`9BF_U9WD7D>Acp9py2@fieMZELiH{=+wzU7zWnC=nB zfZdF9a%@mJNZZ^kDc>NZTDkRc{(UM{XRk>uSf#XEk?)MKwQ9hybid~quqKBb(>*`O zIw4%225&q1nGKg$d}!q2(k9f&=mx2iCFj@3c)sHZa?CMcf&UvE^JKp*BeanVc8@B| zaq$Z1%&bA`mC5W6`+a$Dr_rq}g`AEg&<}VLEdY&}W4%AHAsIU~K^upwWPEKf9fNCq zD1xwT>gP#7_tX-&F6b%Z$-#l8=iA|KZZ-QZ6M&empb#^RcYK z9fkK7X>>nB$$VhN%Kqsm{X;4-0_$6TISD*Gk^r!qaZU-)zeeC_nd8^`gpTqY4l*XP z{=Tq>tGBb=ZMuF-H71T|Fsz5)O8{7tLkT=QzXU$o!d{bJHj9oAIS6?a(8HM>>nKW?n+7if+%?h54IYigiC72 z5-1BNx9VCQu10Xdx(v=u?oRwZoS+;QgWDk%R_A^pg7?ppz{68ZKstd+NGq)ixrB}+ zCc0Hj%YB&5dCM(MfyHcDb;I1U`=7dvf2Zo+4ZFwUDo%u#2`?mYMhUoll|ZRAp%xdJ ze&XJR1pWjGAc=Y~(vk=}q~(OYqj{&GLzFc2wpO4|9@4MNWFszw`8OOB0M@tsauU!x zk^r!qaZU+zcrIKHL07!$EBt807MuBz^0dW-S{d_jEPLLBpkTw{8O(a$O8{7tLkZ}e zUjpMZ*;ee1*FDL`7@zbE)5z#Fv$#|GnY$UM-+8`N%aMO90bqgu8xq)-oqCz}etWX{ zrJzXyF;1q*C6``XR=qpv-ZmaZSXbGOB_Pn5J7I!N{YZZ3f!!XP_m*L)48eoVZhsxR zyyj2E^w)l#1oTcV0YtPEk>^aTPjsz`hFwEm4Fb~;y z=I@{q04^kOMhQIrDuH4QDvG(6wy{?(B=9Fl;HJ{M)RK9e&4n)76%mmYfv!?_wZTCF z*DmNv1DCL1kKa%N!1|V7P6GNz5&(8H&M5&3@5avhAaH`aW;ZHHnt8%|ixN6@O(@&0 z=4S@oRP#j#!_xm=0>GLaNzp{iNsa{-HDbdD>$j6TM7I+tE_nnR;KyQXx!ei&Hbe^F)T z42w*EOenCWt49ak1(SrtEqYcep{{ms9vF=_vd)Fm&ExZIyv-|uLiykVc6i`jE9l{Y zcP|usub1vVxrTsXKP!o)+7u9Fj*WkjRd-Yrh^&XU>0Q)RV{Hg8v>nyz9i!(Z!TzuWfZz`LZ6 zFqEWk9V=C*nhbFW7xS}H=}XKi+(Uu~g`%?{*mAK6^Xs1&ShUF3i&M+atm3;^Q|RWp z#@ky57Qd}8%M$UOpil@{9mAbQxbKOkS6-=5CTHUTFU-98xkX2Iwb&AlPKb~cnY$Hn zTp@bRCpLb*FAw|rhzHLyk?4cIQf<2F48^CHu%m1qW>TB%)25%lg)2r<^dkB8mKJYEoL?*S(&o6oXb)v!+`ube=# zdc~eJJ7O<$*jeD9)e(~?9SRKsaP7T>dV2=fn>v+ud{TngHM2AEpd6`Vh|B?tf>4+0 z{gjN!bad9ZQ&`j6(sG}fF5C-cx~6}4mv3s#D{11@tfsOY5L1#tLQT%r?uKoU=(LrI zWf;BN+!voIXZPcY-_pBflVTsqc?o$&rwx(F4jUt@Uh7SZUlO~D5k)aSchY!t&GY-gy5UO|w3f8gD-dH%Ko_iHLb zP9fHeWe;_kEe4v$Xy0bOK28CWE4=bUL0{wiEWya;*Xjrx9KM~{QIp^hAJNel_l^&|lT=9^tCb)o zAv2JCDNCl2*hSivllL?zdV2M?3#TJ&(9cV$^iQ2q8MCKt=3=Fz6uJ-S#w%0Mg_SLU zu`g=(nLP1Z>smSh{c$N38<)`=O81h0*^89wj474t*OV$>f-@K3OHnBLznN0OZ3a)+hXPxc?H*F9ga+aQ<4>!W{oSe=Bql9?z|G%aX#EFx5-k$LrsL zUGu+K|L!bA*&9NGTF^^^UV1BceI*MkL)=D8;+3X}#q&>n2c#!H?vF{Se)+3rfP$FB zOo9>syIY6PtkcO&8^8P)JKwE+2I|olv=)YL-~}-r)a+`+VBOi@)c?{rtuN??2|Y9Q z(NTiHUK@YwtCc9fvO(elPDohQx^V~D(n1)C_9MNDYK>j$Htgje8+rCRB5 zT-!K*p1Tb$-2De1H0}1)-8mlwY($1pOO5|G-HoJ(w~S6YK~DNDZTsDi5>#G#&_NKn zpd6367kYmZ$Gk{#iEkNyA}8xnM2N4s*yQP_GFC)*Q8|M0y(8p5l=t>WulcVq-=5y` zY!*pZ8=#>UFZ|)|KGG|&uXIkmx=d_pkl*o_4vc(t>0NQB8mvm_6@7A1<3b-Ij0oSN z1TZYa@AV3-$)R2i&#%{v)M~%>OT~>@N~X~DL-#cvECEz0%-!rJeW)u#@vYR4^$INT ze?zYY1+wJXLE)%HJoi}i@1cuA=T9uC>jS1PLyYa>yVUU>S0Muy_+L~Z`<;vhZ0_I$ z$3PN1Fy-gCxexMa;DlB9w)}<>!5SL&X;dn<;e-1LOOK2Ntc&>NjAeK{K|&gs5R!d6 zBD-RE7e3{V;N#^F`~<@F+RR1xB&k;j)s7}e3bgNt_l3;#ag*2OP>m2h4@md$Fpl7? zrdIIUmJ^J`aT>p#I7O%-;r>o*nI+|kFUsfFWtM?KKF3laL_!>n zNlbKsXyam}eYZw8u1=tqpfvK6WKrRm5{@Wk#n_i|ctK$IXx}RnN7!zW%};U8xE4Nm zl%^6~X7g4^!z#`(NfUdvVe3OChX^jPKv#lpQckQr4b zP$GnG!U%7=#+da!QOQH4Wm4HYTI0?tWFLS*e);o5nWt>|D|@~M4r_gfXNf`KJ#D23 zbHH|l1GH8?b@u74kQD=UOI7pRB)J7$*09&l@aY5R3L~ewXC$TJE z+1F9QNi|~}YJN`-@}#vT(Mn|0`*?x`Qf)oE9Hr5Dghu3ruwYFNWdMLiF;JwFVC0j@ zi?BufpO>f^o;p#x&5h81b3PEv=I$UvU0gYj`O*Z!fEec+9tLA*rP^l6e^TAQv-+Gp zRtvBz>b@C&F+p<12@?0OiCQMLB2=1$^{4Is%|z`|IU4EoV)pZ~KO|9;yY;{|tK>;s z@>q5nTk!M8IXC!UH2AwR-|iD-{6w={eELM~8lBB09^H-rwnViiLmLVKE+bwM>lrh_ zdwEh_k05&(|LG_FLncUo^(}wn;292bKSSu*&U`mIN-=@mjB}=#B{IZ=$$dgK`oja< zOw4pG-d3||QS{5oL8IkRDdJDtz_5(IPceZtIZQE)&Yxm#FHM5$B(su~s=o_Yy{||A z#Fzy{K6f1~-y)ec-ytCMIK>1O_`i{2R>Cyq$=zYgg*bMNhIC(h%Utm1LDJ~dkSu@A6BeXO^3lk6h-1vu_-;HUZsC=} zhIzAQIYKEu+O~gs>-;;AD|%`ZiEwed=ZdD7ftQj+~vhFSE@GqN|f?*kd4@qE64k2lLen>Xs zKCG4CBv^Kh&2J>EWt&Q^xi3xw_3~PO&h)+Fn`(B)gYUos4+r0$o)~fZKQAA7h7SO2 zRm_R6g*L5I9jWS{pZzIUg?-Q-9$L)ivhZ_?Xg6FpUBZBfofDxzi&7q;L2@i<#&!#a zO33N*%dsUfLw%dnp!J1Pdp$%*k6G`}Z+{*dj8Bb*V7ce^U_sG1tV2!ly*F_B<~BRr zjjr*3ysz&NJd%U4{o~NUqbXr{ley#))CC&Ohz5_ZXh_vD3e(f&e;;&#hCcxf;2F%~ z@qKRZqLzydGO@Esa}mQH*J`PU-SI2SbgmP#%B7-SaI zXjlEd0_y;o_>tU)W3VAokTj*{hx6X0V22w6U)R?R8%72%X7cqG+Nvh>Ut?+;1g)pL zTMa=ABmCN8tNA*uEusYJ*cPH#RTV$WCMbv|a__UZ-mAoMj&wF&LHzCav$U@v1w~$~ z*_EHQhjLbF>@YHTB1YN%>=l1w>&|1@N)NLXPJQX_HIkTiR01S1V($HM${qTf%^W$a zB9x3^jWQTw%W@N#6SwN0)76u0Kqs#&jJ(Xod<(7Ba#ik$ty~9yoJbT7(Qv8+(Xo>a zCaS2SLQs~T;iF@EGsR;AhhQwPC1IC9fuUC}Yhh3=x(@5Pu;TA+3GUFy)ak6P2s-!2 zmGQK%rzLnuTY_$T#_fkhRV-$LGaXPK7%vcz3g_IOghfKqnUyVQO-w7FaliZWGyUc$ zdphjH>%tY2NuuzwDv!g%YWLsR59n3Jd(BvKrOkLiCi6hAIH+t0+~$X^$&r;FS6iMf zBuZ&t0M=lrp9Mx~2W0 zl6Ufb<6_Q-^TDJIRt*fIGF2W;(!3npq%VZDhjVSaqwc>GsD72O{SX}+8tiZL!H*F^ zoZ5@8@MmPOEK5mf>IhJ8SDb~qpW z0@iF$3jWPwy&XGIbv-=x1OH7e<{T~Iekk~uFi#{=Kf)q@%%S{hq&xTG{3acsE?s5H z7JRz8J3pt_q0-kyPqO^*Ig`&5`jgsnroXBrA2*f`V&;`xE5cwHJ?)g>Z2?>ws+V9M7v7J`rhTWwz zUQkS|6GR=EyU^`D{aB))E!zGhI*xn3?uC6!|8%Q$pS-0M2Y~#4u7c*5aw^EaB_Dp^ zc@>e?Qp+Q;pA*A$Y4i2+TO6wuug|LhApnSqQq-Iq88QIy?M7=JUQ^=BH4(IQ_30Ss zRMpkbcw`Q!5CH90pAS~roxGbo)ND=v?OTI>_m?y_veG=+4c}jV3PH*rj41AzE}cvt z*1}rU)RxC=B!pq~j(zNf!u8)! zqaR%-3Qz^ZGW|Z60M_I%moPnlE&+m=jtm#Ua9PIQ&{$n~T<5iQI=Xz!Bwq(rs$)M! zd&O}s0W9!0&Lz$;R(Xa2$RZ7!*h^-@BDr4KW<# zs=X(R`8;69K1MCn8TM%*-n|DcCqjW4Q|_;S9oQ8g13(`0fH<98DhYN~aaZz+qo#!f z&&WVZ$qf`2XE-K`RVHI2+C^AHy!sfBzb*|d&Rv!PENy1@wpr4nnRl~!Nmboi<{M<| zwlRjp+u)Lnt)xc5aimbV$o?^NQXWpms=Ho06^Bh?o=*B$(aLsdzR{+h$ntoLDw_nSw24^0dwx;dVL_y*62UY14n>fiT=OaCVussGnDI{tVd|MjJ?yuLy+ zj@NVd)}^LR_0vMrTO;3dO-^zmx^D`$*kk0qIVgU6d6Cq;w+K_}g^@M#{|QZXz$_Hd zkNZ(u-xzhI&XHhE)_lJ}iTDH3yx8ZS;Kmgndp86d41U0gAI!JM``6?v*dbrxC4}F+ zeirk~E@xCx9(;5ib;0Inz62OwLA~uglhE$*KA?3}{+M+WF0pu#;32bFKrf@So@}tQ z?r+3bslPf|VEw?!_ghs_qOg&@MCC*t7$c0Ku3hZTV?Yp&<+ECEZ~7$~P&I6@xt4F+ zxPgVb$l?+J08W5!9rhvbzWj#_{@4Eju3w*QYX-2SrO!=ec#xkf-ckYxqx;p~wb>2( zJ#&5cGTu(O|CL0>dPr=EnhXl!5Ui_cC2OM|mc~&_uervB1}t@VLQCUS_2=?>2Onb! z49Kq2xo$%BaJHr3=eh7BMU}I~SGVW9;KNg=$NK9GEDKP|yYoI6!I-c3q!3~O^47DT ztcO>zbdN53*7qBh=26SDzD%*fW{;o_tgHFupl&Ar*V6^8Gjl!%GLCWpV86w4<^Ux4 zSKo8&>NpGCh|zFu;SpftpfWQ+y_0`0X6*najq^Gfmf80?0I(*9Ie^*ua{vg&XeKEm zksgr(I2Pv8EconUeIdGH6NS=-0df*@D0Kft-a)9sV6M8?Jm?(?(9MEyUa2L+* z&d5sdTgGG+ox_icNV&zBgz41(&C0+6AM+qN9lqFQ$pC*n_CIEY=oz*)0;@N2{jblN zjM%>;+Ox*{l6H}A`!*tjp?n6Finb!Y_vZ)o2m8Etj?#np>k{Cba;GcDXe{6SJeQkYxcvWf%<}hlx%XF>M~?6~!ueryr&M_tVXC2hpIeubwwx|dIje5a7Z7-Yh{x@>@k@}H%3W}j1Al5Gez+8)-JC^Bj;V??Fybh`AE4X_)}7`8-L1%P3he-ATY zO%7pZetww2T^dwrsx)@noJjLWCR*~hhnF*!byIjo&j@l~=%WMLG0cDkUcl@-n2q$* zItdLwE5ISe*A;El`D{2N?YF4c61$8t@cLn6H0*H|Y+!*urwaBNV0H!>0yEqZ$IzQ1 zC=y)7vG0x*5GAV@wI$6&dsOu)v-xJC*bMmQA>r}Y(J7gZ6z6Vtv>~o>a)4v*ssqqO``g-)FRnF_)~I> zl}Tf7B!a*FGe=i=7Z24-wkGur+ME0c&!|F^lta>^dicYT+FX zo({!q$3U&{s-5H#Nh#3A7U<^_m+VGJX^uGtEbxM3-*K$Di47KaOk8^&&3j?WT`rB- zFT5*mhH5vxwZ9GS?UnzxWE-%+pHsZ`$8hWi$Sst$$JSZ3?{P#-!>pG8I!j!`ZZ#8` z-3~heIhew*v)a#-o5h9P{$RPi_$s$>+%&C;1Ef&l)5^`HtvNX~?Nh96zz&!0Aln}5 z%igAd(IQuYB|Dx-@CLREx&8Wb(|%Hh;!m3N_OB!SfDfdNCm#?=mO7-pLEAe-Vkf&ZzwllkMS=jH_Icr0lOLJlv^0~NS2sXW)0TjH37?{R~>>o zWh7N{b746R9#ga#BgtS`mfy<_Sd&A!S)N~R8&n{lW;#a7jG$wjV&&)Bd&K+|pb4kv zoZ~F&Qz)Q>kL3m|@Ir3i$&DuR`9bKe8+`0Ktc7-Fa}NDrScC$N$J^KjYYgH6)$rrL zEU>`CzpT^A9qeb&&>2Gi&*gT8&kAgK?x+T93l{sHKr+(9H{t$JZ2ako4a;*A&oTF0 zhFw@}_U>y39_>ZTf+Srh=;x0miSDJ`08CAzbzzaW-9)&f0oP?FNj&0ei#{*w7H7C8 z2ftorF9G@xIcoK0a3;=kv$f(@i<^O-d8iBtTNbV;NVZ^9o`NQ>`D)rvscA)mkhWdf zXX&jbztmvO_T~1P+M->E28V00Z-%8cEb0Tm3`^E(9_WopFz!x__Ty1047~AHmkDQ+ z9~1=7QvYPTw4$-o-W9G=FoWk$=H@j7}aWF8c z*VkxVZ5`M#cc{w8Bg(29QQZ4p8+Q6-wTH^Y-7#E^_iI77AS#1KK zvMOkiv4Os~q(?mhYD?zn-)VLCHP(^)~bO8F~(DKxeXP_LS=l(~iR*+(j46M+A1 z&)r-S|6nS?dfn_wHUA5n(WwH_d>-M~GW6jdQdb2rl7w#q&{?>!tWGS00G8jJ2nDtt z`b4Og5hBbeACqg5vrb^Ebn-G?n+kw*`RdEtO4c6;s+zbD_rg_vTSoSy{)eNCM`m~U z@5GS@)7u}!-Tr>9}peYkFL6y)CLOSus))Q44P!rFE}C>}un2GN-kZfFf~)QMzp zzv9$uHchp?pUP8LTA(eJh^Jj{@u0L0#Aj8LGKc)I6B!0M+|XJm7WM)U;SDCwgShAh zrw`Y`1n0U3SSKIidq*N{dUFwc+T|y{a>LO8!Yks{Z5Ead03@9U`eTC+k2DsH6Le@y zBAtXpY*5YDn>0vnZX)*8eZD+aT-?ulwL#cyho8LP$AGZb_0|hm-6%KKqCFN=y8^NrsojQZX zXuZ_dobxU6%|UQ{|HZ38FYYnk%^?1m7cAC?5#6JLRJWo(u+b>=ipC|I4AfC{X4deL zhvZu~5Qr*9 zRn3VauoT*{B#c?%y}=UUVYIebP}U;+#9`^aRP)SNem)&D(h# zgf!0kE1zRMDCex`SkPe>KPIC0v=>8&yGJz8@ZTA3e~NL9aDxCL*9zr68usioq`WFx zV?(PBY_?f#3PozkO?`KTH5@`pra39Nb{~C?PFujyL?I4VV(Ron1b85)OPe2yLB6*; zNP=_I)qVCVZ;{KKc|UQY(DKy7P0m-vu!!n6P&~&Oz0$gc{)AslUfh!P)KGXp)ZdZO#L5xa{cU9ui$cUGo!fC1C5Qza`H93;QYjzYaI~ zZ#48Z*{#RiIPGwgqp0E|X}kNS>Zlu0M6m6?0%(MnZgf3Tlwi;3hC|IN{m)zdzqg+P zF^_k%8aDMh=eLLQS3Ac?cRH6hJUPqB?%U@Bwrp?*a&UCqX39sllg>QUI*$EZ%S zW7I2pUThybbPaJ&Kit&bUUClsV_M>H)ZPyM=hLGG z_#x$?f%RB_8*Vy7slGFWo~^2%)zOMGU_ZdWeeyHx@jXN6*|q~)9SxKLdyLLGP`20T zD)czHhKhv03z3VdwB@z^GopWe&RT4=+8c z2_VUp!|y3U-(&GJ6%rI+@E{m=I{J|S>u3JjQY%O8tuu6F*U<}?>gFyW%}X|=id=UfPJNNQUv$4gxbTf zZuu^0ljo+!ifWb|`8?nj%%)Q9A(_F>A_rht*56YESd&AFSf8IFDlO~yH4DqWv+$lF zS)skz=EHDGr4nGS$*LhX(jT4Tjt`9h7I=7Qg!_p+`V8;vKQz?8CZTyBoA;G_F?!)t z4y2Xw)uPRSS<1&X9Cy;1F6BX-_+t@n>Iu8cR`#MK6^08+LS7ZopGzg`{Nywy_o>s2 zv_*rznogEtDyhdqBztJx(;D`i8JFh#DX?AQw;3Llef{m+)!5+Zn-SaBbFT< zVVKP6`#K{)@^0?#URGJ;vT%v5X^UVKCCfpFklK*GFlsB>?}al zJyjIlMuGK=Z!a@3s8pLD)AsavHR*{bX;r1RX5=w?O#I78V37iwN;E(w-$a)ZAZG0 zJQ~rnnwz8DUP-%1T2sw2XNXqdMr5FO4OoB+=clRijJqpiESIK3H{4S>(x7^!xkr@K zX$i1*`m_8gZbh~XAP4D2iPgVhkqKS!ft8E#gKA*6EP?JvQt6sG<8D9j#-D(#n6 zfb00yafVQVExKPhFuY+&Do&R@x%dc4(hDv3G8k&vrCeu!eXaLt(VTEZh<(D&a@E5% zY))*ihtL-g8k%kTP4&7abZgAxmjO06_2S0{W%UpWpD%%jt>G@0Mw+Yj(CSmNK3%=L zOX*+d(t@bP5*m1ST11KnuaN~cioc9}N8vL(pC4|BSvL5zHFi%ixb-Rslh6B|-D0b0vyAZ$ z3a~E+7`CM&xk$hnJMl&jd+@6pzrrjW=qfT;gT1fcM_n+If9LR=!@o_QDcbLp>R_W@ zYZTdn6s<3&Bivh>?1LO98Yr+M+hbd~d^mX)mVjk*;nB2gzsa-S!^yMUs4l(K z{aXxNZiy61(6uRpHnZ_}KxbCWhfuaCL1nTN7*EY%dkCnI0R_bJc@6ZQoRYKsK5 z$W@!gAul727`j6grxP;M->c;-P-rg4YifTMk)Xdg8$K5%T!yoEJnr3ISIP>XI9MMf z9SEK)8{zOGV#%P>*NYc^8;7#FU~OMI+Po%nE6GX(3F>^|CJ`HYyZAugI(XPhPN zdSAt&8d-c&ib&v}JoE32dkZ|j0l)~#zIU`khNn+_Ueq>eaKV$1#|Yu`n{+1WLyf@8 zvcWH>XSaxbu0ruB{-=)Ohm3mz>#_bu%rgvTpCR;Y2X<5U9p}E4NPZIAsGCc#*Ohybb0#0k9aM-TtXEi*2~IXCa?;G^Ok0 zq*q0kr%x8g6uc3CWIM~4 zl8q}uNS@>4J{BjIN)D=YD5sa--~=r2F%Oi}F=eW5K@@U|MRrt32 zh7rLU8un>aDz@Q+`w2^r7IgsoG5>PLvOO|Zu2$JnP!4oQf6Mkzo;2a20t=F(UjpLY zDLiKQyP+`ef1a^yFO2mE8!P0ivE1P|cM5JWe+s@Z*1u;gEcVOE_gqcn3jPpd0ixps zgnS$#re=|$gSPI?fOqqeia0F@%d)Qu2MQTQo!(feH4v}yy3nJ{>%g(PDkoiqc&J(A z7U?h=E%j^RnRpa`nz4X=rE?lf#Vu?S`sqX!z)LMv7dKDL+DCe@?gK{pol+&G=d4)f zU|4qF8w*&ILu1*U-&jq{%sP$(IV@ujui0f{F2AjIL;$y4GQG^%O{#+3<70SiEMS2b z#yY>TQX!qkN3oSBV4DsYn9=N)SiRrNUJ7w8K)azvHKU9*d0dqmSm1wAmHG_EI>V6H zX*mY$cmKJ##dLMYY&6-J339&cmyFD+!T-|2&S_U2yCV z=2+-gj=83TcI96YE6KXx*uTdyoTy735)pEKQGW=>JTBiZEac}mRD?O)hK!%Wz!o{W z7`|{rep!!p`>ue)jni|i+RsQXw`qvngF^;&mhBe*iqI!i29I|Uq}~18NeroTKaFF+ zzS224MtB`XmKrJ{hT6m|gSGj_HRVCxR|iGeAvQts4`(E2(7>?lzvmdRCWjoeKR?H~ z0_8iD_gD&&xS9gS7z@m$ydIi9^~t;&3umZj)H=j=%rRhr7aTi3$6m01+S|$rj|%dU z^hQcD;3_81HYZPI!*dO}j`tSrM(1%YYG8r?MJ?(d!?7PAH(*`FFDEzqZ^t|ikSNM_ zc<~d}C8*?LNj)k>P<+Ni7244VK`GY^rP6%kkoTNdPIWCgOGLXOnmRQC z8>;6nqs)k<#*3{J!p`sJ2Ok_FiPL^XPR)+{##mf?#iB zfcB)feC(67*-Q1+bC1`LYMX={u={>u7Jqf1FOrJUr1Zvi2UZQtGj5$1`n}jEI_x*3 z?C4EnASC|!CL`@My$&W=`d#av@jMk4uSF&_6wW^GlrfbC9}0tjpv%>8;wvKINqv;I z9pC#h^974<0|koamlfX0HT3h0w>BGaFtqPQe!LP;>jGvkXB(PxL!w^0om5nUAx!15 zb`0|K%chQ1X|y9Z!_~P$0Eq z9K#}09(KqmArSk$QEwH?jJBmkWL;}d)X@@cqgg;_My5$*6w6s9d=s_*0R4lyuBwoy+^R6+Q6=NH3lxn#^a6 zi_5{v-@(+(cB-%j?+Wcra3s2gIZBFq1x3+s)z>~wt8<-+D2+mKyvX6T?b{b1!OEk< z?GQt(5C)Ajof7ptvA3AR)2U}hc@gx7G&P|Ot(rq&wO^&QG9jkgTS^!eLgv8em`Lt> zn2V%z3L(YM?C{Ji+26tyt3#+p%QC|)Os0<0#nwa;dFKEH-2)0ItKwo7s5?xrS^8wC z(}z_ob;LS#8efPa>=L|{yRxq&%4;Ou2juq=uUW1MbVPN62WLWaI~q!e=4?F!eZ_(8 z%Dupb2$cxCp8Kc_LqyAFsVZtlAr!G*HK7;RxFBv6O$+vN+(xbN>t8{ovp;hivlX5x}(=f6h?h%&GeH^fb zBcW0S!6pFx+Zyj~H7b~AB$r8HCM>SF<|Q&E=J5;V<29N(zSMzo46-J<4Zx5j!g_S# zM+_`oJP``4Cpi%ctS>uUd+q+>W#y~JFlFKzanszYwg3Z|U}g2)(VgqIHxaiR!$lGW z&5K=As$S8?&HCz-64jwTXzy@>PFzKX$iW9&P(kZg!Wl%Z-@MmUbwd{kCdcYP#xFZw zP^u1{)LOI~Evo0Rwh8&O!+*y!(`EIEnlqVLuc?u0)YN76JT!>u;?4$P^}y>HEmQSYw&xE@eMiF#&GK%D;1EQ=>`d0|v=n?+ zhGW2y|51I_`t~aE@fvT~rifgMki%m#-xx-xvd=M648N$jf3G$*K2cO+85G|8=Oy0u z7m4>DoOp+SO}w3EFkH-=!O2BVn|MEC9OApwzMDTb=~s`)xQoJbS><6xWT2?PzVimx zT2A7BrcVD>ZIhd^zHnB67efZ$4H=wax3NN0?O-ff z*Gl~B2U18fpToDnLf&t{i}{>%IX$+Te&cAF4zPakZ{5!s);FIa^lVF^A05pLy()yr zy^{CQH>3wiaWCVr&RLM!t*4GY3>I7Fp~mRytUr&Zk1lxn2lF)ID^DF<2^2Bx#)HXE z%hS2KZBE*ZDr(3=o64stq>EZJ)vuTXyMjs#KKa0J*#E;OvJ0O6YNhuK)1rNG2rS>- zJ0(wZ4Ebzx){N$Z*}DTj-a{uSYJjabRsVn3d&{6Yqh(E);O_2DaCav_aCdhP?hxD| z1b2tvPJm#+gS)#1*FbQYob&E`YwpaC%spS#S97QCu7V$X_wMfJ>0WF1yR^}b0*&?e zoVWbmf5GzfZ)yA=ejol5HRpdq^gmag`u$sZ`nO>Gi{)wFWWhIL!TXFU1sdCtn|DK+ z)rRo(JXEIyyO0{6yDb?ZaQ*(*^7LAzl{zF|#C#tqCHUC{#fBq3Q|L{cTqSKfJ28G(4a zF%pgk|L>;={!Q@z!=(`amCDnA>+}HB|v>qgDroSUTBCHYS z&Wd#^ON2D!^m8%B*gobm$s||d{I2!C4>{!mar1BIm;Y_Jav#6i zLHc>7hEd)-FH=7)=|& zz(kc{=}>o}a!2->^A7Rtu;y(3DzT1OQNhu_*Mol({O@}3U#|zL+zp3%;w)$jhfQA* zD-<%hoQEii`B6mj!{5cqBY&*>_aOXD@J|o|{^zqC{}V%i|Bu##{|N^ERyqd2LjAcq z1pUt+CFlwf3u`-OR%TOY2YbcCH~)MOc;_ECQ$)Q1|K~dxfBsYd2l)@y_U6uU|KSUc z4j$&th3ddJF#kyZiQ&n-`zD;rJ)&`C{|K#>5Hw?}B2dF=<>@C6}g#q;4n4r%#$RwtQ$uk`kTSuyB z-=PLHbtFe3CfgC~i+8OI17O}DSD+$fu@Y$d+B=%cZCkZ?R$%Gm?Mh72m-#PXPjt4l zLjP0W8;nLKfCMmBs zo!!aHVz-{5G)PR17j=48av@0HUpBC^Zi8@)CHSw7dGS27vI(*6 zBZ{jLLM=GX&mwNeAEEBgWdz%%SE<|XV5O>{8(^zA4xQOA5Z7Qk9Y zT%4BZsBb(Q6E+|LeI@n}Fk^N!CDvW{rK3={8VtJMv2p^fWFqQ*BsgkV<}8E!8%TM_ zn>5=;@ZGZz;;lo-jo&w$YG?^>^DD*P7djMzWlfNO(<&`Od-5t*>(Yf8v_y#aj?5Pf5=9_ij?xI{gJc50&{nD;R(@MVnaSgN9i2j2ryqJgx6 zpwN3#CH6OWQxV-*DsAIL4+=!!qW#6?=)iOWZ%_2&xE;U?VJwhe_ca2Ke7w_xfyqp> zOu`=3_I#VZ+|fw(aNThQ{~2hokaUk!fuTNSQ;{&IEw7{*)L)ADoa`wzB~ukA95Re> z9|i#&kor9klf73(#evA&{F4rLcPOuIf`s@98SMP4S*q$<>EA$do)jY(L{eElA({nc zj5d|G^nN|X3(`hlhrnGRwGm=t_=4oBxRna$!&Y+7DjJc!ph-rJ5invQFuuuMv)$;B zUH#Il0&qaK*p00Dvcp;UBI9G%7lT(WYbNAeX6Aa97aO7Dvg~*f0&#;JB*3ISlaNK+ zx@@>QK&`eTw%0sr56DI&=@VWeBDMa+3D|{#BM{>aTTP8VImRn~KOqbTv;Dc4PCf>l zG~`!0uUX*qI=~AQV!kSFzjEyXf4sc0`8Ij-L*1Q`3oA9RXPZSG%!B|qK42G$J(o+} zL4H=gi5a4l;I*_mf~a$hK#;JQhAMJ{Fp8w(2Y>_0oQb~t4VgR()N-F)48^3z1Lf;Z z0p66_I0>B+>AT`L=s^2`a^Sz&(1{4>v%90&bCwK+IZ;B-9ku-GR$^Yh_jciDvla-) z&Q4Xm(F*lho%z5(c0PI!LhQ2>81(lx8aPm=zR>RgN* z|i+gs8f_5=r3Ib@^;P?HT17vVX#_QA-QE zY~P%-Vu9p7YaU6gH5QpHhkT$pm`4jd>@fCqFvZG<^5QCcpWy>(1IWRCj$1t(%~*tF zxJ+%W`L2^G*<;x{Tt~K&pkYu`Zwp5@55x^Nd2$$P%=w*KZUo)ZOch_zfEoDmSGdRb z+f^dpYBOD4h(K};jdSq)V}b~nkM9*#&$*srKZ+F4GIVn()ud)=Pmufgf#iOnq9BG{ z;|qV9K#&O5GUy?<=@qO;`Los%_e|mfaW*{>@DEO`c4o}#H;0BXXRT5JnlKs0ya#SH zy{FB>5(?vwru|tTpg;KAp>t+O0MBp}zvcr*^WK$yBumL1;Q#C-%bCtQCsKC6%;L>7 z9*%;)3B(s39oLsROo+(cgyh3`PI}17aE&^0<(+V5($mso9O3FWdXStOiHjFeW>==x zpZ7(JrF`PoJU^wh9CIVb$D= zsDzAHf1+xn1OEMOSzHS{@OuP*62wi1jhs@hQAC}i5V$5&_*_ox?YC%nr>RUH&x1g> z1Neu4wrn)q`i@Skbrm~e7-e1ksvd<^r-UPoC$RlDfcmCi=_AXi9CWj zN)%wlybIt(Xe8ccib>Hu{%ObW;2T%YtwWnzaqH#F%ovADPd|o0Z2{;*gfI_?(Xgsi z)4N6a+{_;at*bpvHF825v26G813T|Y&I6Dyh!%IxUwf-ulCmedsUO;WUBbK?N2J#; z-X5MDupEGyJw5?;5nUqT38*{Y)$NF)(jz6*(tBK`2nIKn zp77#&MtkussVIit-UR>$k{W)>H{@1w-Vs>T0QF4s))njGbdlx^Fy}yW-*=r}4-0@@ zq=OkA71X-?dRMk2_Ptno(ul>OG;o5a+L_gpAvyt0k~AQ0NY@ku_N|*vq)|cu=febAuRKPUw!Fdd1J+Xd2$JK59y1t)BwJbbIEuPvZH?B5&Em+jVDjJOM#Q9zVhEhv zt5Jm>PP7mO>v1|Rxq^gM4ilII6z-*10URid`O;rP&Qq7*n&?;tusRm*1J|K$V@-7# zfBEk0P98Qo0{T$iPgy959J7R%(_kOWb5}gY+pxu`^N0 z`IXmatT3+VT9Ft3T1wx<^AI0c8O`40~G3c zm+{BU67#tRf?S*3}-{e)my( zi<)o>{Wr%Pq zkhwOX4+AAAheof&PV}xq|Kmi$y@7HJHL6{&jmr5Dd^`u&^4E6&UX0ipXt+lha+u1X zeDSb!I$Mu9MhJ`}Vbx5dA!{2k@bMrZ9)H_$97X~3lNftoFrV%BJ~N`}Uk?Qz@TyMW zf5Z-L=exc0F2fPI+`uIQ`S%jIFXN%ahs|oAEexg-47V)592U3$`(0Y_Q7O@dgaQh% ziwSiGlaAT7O`(yq_2ss397mMqs+CINW6P5P=WtVXPD&EMGbYu-65U{?Ln{So;~m%S zmz^>ruip|FM*-&}Q5WAGUc)%YB4|jJ4K21-NkFW)5F-^j@J>ZlPX6}QV&_6vAJB(2oJCs{s)Q%K z)kI!m`fa>;KR2p--heN@8r^Ftz7hZHG{7JB{unH==m)lsI@^PYf|zlZ`w{N#!rJ15~d%)1GGlirbft)X_S_KtRh*+JoI|L-8~m&nT=ToX@oz_B-?ws&0)zXM&5znDj6N;YUBOAdv_!5GLF zTnPg*L;1l7hE&SMhBCwviSh z89s32Hu*Cpp-bEYDGq=GFN2Fn;gqT1w$hov=@T>Z+52I>W3`GAVgnmx&FNk4a04K| zc*Qqj%I|ErxGLREdW^pgOz@8;!y(5xorP_oJtyxX6<>h#{bYbl#-v?0z(_fmS!UKF zuN2$~-jPlG_wUQt`zA7}$}gi1XQ7*<2&7P#*FatoAXNW3GbYbIt|R={iwxNb!V9^| z)=z?b;}OeIe@oMR_gWxs1k^Rt=MX-2{sz{E+!J-N;w5Fi5m<j{Q=6t<@VF&pa2?S#e}KQg-90n<0`T&;Ww}edK)?I9QD}5UkQ_vTe%ey+F;BJ! zob&FtMNcS|VivR4^7Qs0k@LhSCEH`bKO&@~#G;2jBX!y9oAGlS{O!SON->l8tdy?g zDVc{WEuUXNJcwxgul&Ai?%!sew?}6PWZAUIr0yp|kRryI&kt*lFtF4CybwuJl<*Vh zt6nPZaOYpEq!f3w`y4ftyFoahL^$3eDNcU_$^GU0!3Jy)js@NQqF_;jeC=$%wA!z; zm3F6nq3mQBty4l@8%nK=|J)bx&lfTI=zQi?0e#M7us)iOnC_|FVO_Qit|l{OLN3x< z#JN~dJczjn51dnQAi^^{REwnHV>pvDb!44(7QhYlSMwR=9Ki$t|A_e&z9&RvDEYT~lq*l}0_;)}&NMT01)L0OX znV34Vu4)&(9lDZh!knO>Gd2vrbE95_C&bc8c%btB3qO7olIqZ9mr0JX9MksjN<*P zRUzT#Y=>A8TU{76sUjlQmuNj#{U|ZYJ3*yIS_^>ZzwPs8p#$PZrkilSciR&?qG0KJ z7QqbJ|H=E32IjulQTTT_xca;o{vwd~f18;amj~(l%LPB?S3ti;uA>wtO5Ji%*-?PV zUi*0$`I|Gz*p-hW1!1SG^2*q3t`uOGf_P~y$BxNhm2I|hHW*Adh^1z_4th=o;%yP` z<7rXOodduh1!MDKS+w*UQ|#BzsvJYOc3de6+n<Mt*xZwJ`0Qh=U6>E=5J_Ky8XaI<@QVuwo)lxHWp7!-u1w_445nfiT+E#o#+= zQ2uW-FT~0~zEI`brpb<}ll`=K(kxE4*bhj60F&FQhnk0nS-e{13S+(m@c!*`nbkW0 zFV&Ud_f0~j%3i0r28GQV*J)axh64Vtz9a%K`6Jod^gqx*cz=%}*2Q^U2a|AKuhrKb zy)Rq}yI_X6H`IRLWAjMmyG{U-dtM>o(r-3{FaACuQpK(>J{YL*#6oIEQfJM^&)ZsO z{uZ!HO_9Ily2Y}r??{*s+!L%F@_sEWBQe42PqU=E!k(zfvjX(dkVBoLIF8~&Wj#ua z971(3`gQT}$LF*pVroIU+FnIeTmjFQG;IA(=;sJpR<^BJsAr;Ye7FSpH?O^yRfX`p zHkTDTSiRVf;o{Rk_XkLECaPHImM15$v{=N!Wi?%P3fOv6sb zZEgg!H5I3xj2mOuPMQ9W3yw&kqb@~#F{mYgXIf;7c$Zt+2h8}ui5x{jr4Kfr?yUT2 z&2Zp7c+nB0^6q#6yR@-GQ$JE5Nu^ylyQoG)Gv~e)O<6n9jZ-vMr|_G^V_mxfywJu8 z9cpVL+Ve*Z8zQ<9&$QsK4OY-c#i?nmofie0L}XV0cIkbtNef)+E1=LIJ8@i6Vq^KT15AdO zHd#V}6W#a%=@#DV&T(&7g`G%19|LoZ-HoNP3<-^J(Z$vS-gof9LG~?eb&J}SD9iCu zaZX#nKZXRz#ECy!MqARfvO_w(tU~OCDHCrG0$}Jq@j>kcDUO-|{qx^8(uz<5?SnBp z`ZfJ23^D&Vyqjh9NIlDPMwTvP)MEaCCpP7hapwIlz%%2b2OVWQDq~chBq8EvJH*dT zcywsN`nwWvG1kt)$me=@z&|F1#}{6o@8^fQyJV#{HeL2#4RtU^^eiNiF;Q~#YVoG& z0lZA=av@`W6JPk-zTR2}wS<48`kG$fUR&u{2e~oZ({HLW36jfdF*&(aOuc@Nu0Qol z(v(6(gvqFCn(lEox@l=fIepO?xD4IKe>BU(ukZ8J;+~}i)9MWrd;^K z)i#rf1qzU$aeih_#x_R}=kld(cW!v$ss!3UD?Sgz__55N^r}Q6pV{A2gY+rr@R%>a zt)Xw02%VRqV&Z{=*KWMGAsIXS^l^Eb?;RXyeuf31WRM|;Me@i&81EWSoFj$)Jxo^d zQI!_K&|>EPNF{POzzYkaH4gIt-cGoq;c%hAuInsM?3l`}_pCw-ZuOD74Wu0$5H}XO zXPP&NRNx=1%2t(@)N~WX6#b-Zu=$U+kJsvLcONnUbzm%fFe4^+y}dL$=+|Q*W~Iea zU(yb~7x{8P_BrFF#ee941^8o;h6(|*;8=Pmx$02C$aYW_Cu z6J$5n@~id-!=`-#1UEgKV`7;CrbhO;y_tw_Qu5%+kn&{lKwZL;8@6uL4Y9n#O>pcu zKvo#Ri_&0z$wO!19=ZJ)GuT+86UY~q79_#ZLrIHkEc9pu=)CM7YI0Gbv4K9~%nR-& zT!OiqW+4BL-m|<8tJWT2KcJ$OWhU0cIaye8u&{oMdOD(ySwfrv)mJRP()p_BmC4ob zjb4PZ8m1&hg*~G6a!{tRJVd3q!)&b~0e!3lZXU}IwWaXOiEQrCPN-scNy*QbljspEId*HB$t@6 z)|>eqD@9j*gI;1R>uY()ebY9YuY5UZ+5Oe;Due@K zC=nkbmb%QkaJI!o+N*$gu*>q}ra_30@>^n+0j=K)>f4LP*BV@6ALf^=kc-Ei$_wOWIV`!G}g+qj%7J zHmDhYE&+HsWMbrF1O;(z<0m6rJq}i?!H_F-b@Milxpp8{O>j-JOhIx=GQ16H!-}|b z-=KtHwQPP_N7)Izwo;hXzSJ~`Z{>)9erGs>{T$M>&>v35*A?McxplLb3W_G<6cy9j zT<|H4pcY2aLHg#t@XHXRIr4vXpxkzTXc1Rf5igZT`dQ@6_725XsJB`J$OBHWo)%*& zsKd@h747v>Ty&TZOam+SxA=t$=}dGlYQOKq0sc6NKEhPq{-jWg(i{_`sS|kRkf%i* z<2w9V{mH(cc;tc@6Nm?=*UQ7uCj>3|5mWt+WYQJPW_S?^%cPtwI;>>!=iij}vVeb_ zolE7f>hoL8DC(9W5oM0Mj?iK%l{Y>`i?qqCBy_7*V1PcZxDu`>YJb9#;wFAo#jlBD z>%@oX=*t?)Y^*gpev!oQ_kp-^CEIggbQ-SL_Xxxcy+sX$pjGkVMRJjx)2S|Q`>L9Y zgAH_STq!vWFp*6t%J6zG^)*Ot`Xx}2uz#jH@M3G#8T!nj_(AEho+F3Xf zN;xq`DlgZ$un(y3e~9z8w=aUc zRK7s@rWE*{<~p0#zZIO_lxNu>X>1C^mP;gqD!H3}YzBW}FZ3md(>Vmjuev-sc&Y{(W7v8!g$PQk7ielSR5P48gf`^+j?*(I&71G{3-Iys1af z+6(svsyEXoj~U(Uo+;;7(jwYeXllquOG=HVcmOXC?1{+DxYp{JX1}KyPm6$N`_uz7 zMxP*Cuq7lv9Vemn9uSYe?O4na1nLrAF0<#2EXj#m!RgFtQTj^0VbdF?Z&~7{$wrOF zq734*pyx)uKwFljQo@C2^y<-mtL|jjT>qTZJIBJ6!N_@%hTOhXRlqLaeC7k09UJVt z_5fIFypO%gEFv?wDpOrDR8KwWr-jWW(DOdu@h$^i+@4?jrR0SAm(%krFkywRfkPL6 zAuD=sF`v?TVIXdN7b@jX@B~UrRCY(kdQjIfb#?Ro7k%iWn6a2xX)qK4Qb4}&BQy%5 z_$kiHkw>VFUVYlS^CWrrN&8ysR8WxKJ()T5N(|&ZKR*4^=Y(T3#yhh8J6U^RDtY^Yv1y%x~km@MGtaV8mJ58ZO2u z-5_J{chyeR^z280Y8ETZ8%KB$4*LMjSYG=?sodGtIH~kgGnG)7V}(*PXY@)&!hRNa z5zu%$|3}ovdXdp{NveU zS5l6>lOrN&va?JFLBGD)(yzubI$aH)qf*{EjL3s{*}oJ?sje&OXsgzdI3u!wxLwY_ z2_<4f6sjJgF}eVM_yXjC0MF^wugG~hpTjgaX2_*f2njJ#&Y;RHY7J-|;I zV==QL?0~@~g3VMfj(tMTL_-AZ3i`nkC8WVP$Q-fN93M(5CwgGL5skU8aBpj_gzGsK z2?_=B;BS}4@go4d2)RJtM_!Xx#EWPtdf`TgH45_}qS$u!Zwlva!UW?Lr^W$!AavI( zZi}C}%y}Q#uOZ=W_-<@8m55@l3T`W4pEY5{^D}7vTo}f5=83E4{BV~flWJYuw-+Yu z73Uqwp)b_x11WD5hb{u3PnZTuRp&EdS*mnN;O*F29v-Yy8G=kqjlRULxc;Vj&^ypP ziLl$!K;r|-Sn!v#l?(KU636PLEuKH)>MVle#?i+}@bJe#zwx(&`kF!GNW$sWT?(wL zrnMvPD$G?vEw>bd(OnZAMLiNL2Xz>HHWDEK&%#;I^aJS(;#&SqDn|DUW!*M`MBlr! zNLet{1XZXvFtw!to`u_UHNNdGb27S6>f8~}MkC8MI4kLmb$!l0BMQYM-bc^@~xKT)n(4++g2(ysm|rd+p5 z9wuk4##@|5utjG`>WAe(5@G*>PSj+OL6o&fSfqe z1P5AK@$cPj<`*gyS6fp_%)$5R7Isg*HK=aYQqM}D^>yOBnpcSoi$Rxx9}($FlxvD6 z27@ucPjXz5oy!lhTiTSv0leZONDKT&$q-)B%dGtc-$h+dcaWA8g$6}g9bxtRMpP*? zfcT1w=#M;Qukv_J)5TaGeWvJ+QDpzbhP1N(LrTIPJhPvv8sJ%6Nr%~v__#L>`geRC z9KUvY_73udhk4j~3*735nexDW&^WNTT7Ew-Axp3$rt)HuLfQfu0kiI(H3-9Lg*Iu@ z##{*Y!$AKmZWjSBX{GKYQ$`kz?{19zE&>Z8nvPua=XeB^PTtbG0jQ1@Ki+@gxd`0o z5cqVKcJ5_`p)A~t(mnup`q`?e?en85Eofb&1eolhEKcX-fWI2+m2NJM7_Iavlnl(n3ZQ*R)K2t} zj-TlI5}NeQ;tBHX9v8(%U^!>)8)m`R?sj)sfz}aA^1Sz->t&B@QMdvV=rPn=4s%2U z6TM;SP-m#|Xd`a^0-7h5lqQO)N2wDG3MRf|f$!!nk{__`Om5D;?34X+bTUVy`wp-x zsUFZ&`%WkGHHHG2$=xFA%YqQL^XXm7*&@Ej`KQ}ofdoMRBI%vT-0ow5nk8`W>l=&e zlAwbPOVlR=U%!wn-u@bf?2!S|_nZvNGx}Lz$I7d5MOx4jvGTeFZhf?a+@xJD=UlMKxiUwbW zab_RM-SE}$KC~)IGjT0;ddht~rGkhDy$tHgM~Y}jAvlm%Qd12(PIwF-b=C}hp7~<$?b|g;4ew?W zt(d=#T(`2Y<=F%6P3mr<%F{x%lH*bjb+k`b0PLljfbw0R^PUnAYaWp= zJJEN^NV0{|w&hJ>lqRueAbqm+T$cvo@Q3%kyYdzFjv}9**FTi&RW;FzDWMf!7QIsh z@>DvhOORXP9nSGS7Q9QX#PH1l)5fO>i5nI}G~byJtK67jklniD0@=7w1)tj|`_r?u ztD@*v*G85ilR#Faj!R#@7)pu>vVf)`gz{yE68zQr66K0r`VP z%6>hQ3w7-YrDXN{X15!hzwp5wisJPFyfU>*p66D4^ex#mQym6pLI`_lza)M!-Zxn& zFe0AYHE@I08OgNY+=lm3ka7NS88w z2JS<2<=e}(^xg6MHUt_(O`gmsXJSSs^GXWfpDY(q>1N%pIarZ4NbxPR?ea+fLwIr?JLx+0>@=6ZD zx{IU@Ds7!cfz_yRXzh2SW7E$W1s=LDxP!l%ZPyjQ0(j-*zlc~AOyh5#&l<&H>BJW* zRri#MkWd}q&=8zgCaFMx;vttE@uP!Q(w1CAZKHbES;(v{`XY`I69YcKB)koJce{rg zz$-UG&n!Hmqnd4@?Zc+BJ1lHXEOt<-yI&nq<)Hl$k0Is^HQvkR;uTcul>k zPmC2jo_CAhrMT>X>N@$H3d^140EM)tdxvL0;}Ci5<@eW$Ptjs@=o z$OC1~9(41MKBu*aSY`uc~ z=JEF1=Xak=ICO?@<$Op&sXOfI{mzDD^Z@@<+|_Q&bdtVjvF`X=%fnJak~__b^_a?e zyZ%hqbwey|um|F%iXYl)3BlH~8_;@EW3P*e!W@FA;Zf`j34ve_pDD zkZ;_Ig|rP&TGuKoxwYvdQ=JgqkBX0!+TXr|F21h|19(=Itq*vLmY*__^1IAG%U!8# zP$vG3{6OL>h9^>qE$@RZ!WU*bVR{_rVR zJ|KO8x&AYkEn=4*8$WYV&KiYp(if5B*$il|aJ-Z~y@jHK0sd6O4uZ=WZ+^0_$SnGU zwf}N>R8p&LA12U>L3ArLdh^rm8fb5-+0bO)8PCIGd2&&X27mP9Mc(qBaLW1AWqKju z&FZKWSpz()ZWZr)-^0<%1`t$kohsVfd6lL7K5qB5MTU+#h`n7*C;|C*?BL!*=_rII zl@ZA2*%=f@6{rPk3qcn)!e#&|B5Ga31mf>bpOXHTmKUlbqp+S4moh4vV>|s4@$UDB zw+q}bvB_j;01h=$udXO_XTlb%pseDMhYfIjXX&yEr>b&cY;OdCm z-kItxO&bP}Jlr}Oi;~=?DzD?6hc0GrXnh*Ro|jSN5|A&NIM!_YS^LCcWO87xbL)uR zSt~vmSJB$`R3AjzXGx*q(|~rQIq5}0Mv{!96stbfo!gZDE2bti!h;zloDobrWw4>^ zIvAwygjf}f@!LPMxf6sT9E`ecC!mN zc`i92meB!yx+WAT#H3!gElRW<@A_l+(0zSxrs#{g-*faBcokb^qk#4^>00HRmB`qS z_>9vA22IpgyHGe5O#4-)f_!iS1 z7aw5&o^?B4Civ7CgtSwB<*?L&9u5XKAUsy?Cp zwnZK`sA8x5Lo!Vn?XWw!L%WfevWOCBJ*MumI|O@S_L+KB#8dbO#cEwyt+^`VhDB{F zzQzOFtYc14ouhjiZC{yTCzn%px#x+>MIHs6c|TVE!K%he$;FV&T%QrNUQdrR-#m}t zz}0F0fvwb^$~r>F`Wji!b}Sfzt+voFoM&|k$SXb5RGGAn^Zj*O&W5MAF`hQUUr=YP z1;U61taifZHb+Hw0iN}IAKR_)FxZCV@1Yfbv@(-DpYF~>Z<8-I`OrM`ofBY@0PRxm zW7NTowDDxD+8eHn%90?(b=h?-XGc1*R)LUsGz|eU8NjZ-P}}Sq_R}2rZ(?7fCykAK zVb^g~GyFI7-M1_5VwrzrHUaU_R|wQ8ah-PksF^fPj*@7TX6eH36m6B#pRCx$+Df1{ zYY5U;6=BqZn#~7|eRx}fOsCkKHLTaUTxJeIA0t~8oqH)|0>sVW&8Pmi2y8Ibh1oXX zJRuC{`oaBy1@P>}nj6;k`v$L9X8;ZZ50tq!IjOgXI`Wd*&f2DISRWYl=H?*;q?Ea5 z3wo4%B0>6s1K*kD2*R=kZg^b~WV$lfSrimgwpfXJBxS3-4Ml8P1N<|n{N9dWH^*!l z8WFG&TJYBM39eo@t)q*8dR_jaTKQmp9l&dFYB))neQZ#O^PH}>i|_hP^;F`FWT(F7 zV^&(ctKzOt43JlbB7)@%vb~vUb=B8FLdxjw`Zh{PY8dq9sN&1_0qhEcP1HhV?D}{c@frP_JlW zd@l%UgLpH1@BONCTLHXA@V2W+mQ$ZZa|bXWEpm0!e}TR4v$x&R!1 zyRI%Dv|rcg2giNjF6s5=_HVyD)x~iI=%ck9RiTL7xJH@HqR_R5TPZ;Xrf$@f^zA{+UM+UU|I1l-gF!fQMrPv>x4znYq~vHAgsv z*2bLEe?hG;+X?=)!1_6%k2=ylYZg@(0`Skw8n1xvja5sjeXf16M@Ywi!K*rco@|ODD4&d+~#Oa+Y@R28f5*mB?3y zwqnAc&8VCz9*+J49^+uEVH!(w?kaD3g;E0@6M%S_T^V!vxs%WmpnT69Vq{r4E`QLq z;6ou2();|)1<{E|47C2wJbOGB+A|SC4CUV5w(*CSCROlVr^20%{cq!Ud=+}e@H&8h z7IMxo1uvu3lBI9uF&Mk~;@er{Dd9tZ2YcnBVixGdt2YCAW#O^3R_&isFa~Bm&=4tJ zi!Iu4E0Ev2eVTX1cX|Ga@FQq_yG4X;pPkSCXXT~_q;yM;k`BAa*7^4{`nL26NMkzR z+bJ6WycTsTfpd>^v&Bu;t4rii9N$!xugsYR)6i~-zIPd03Pq>_@wGU+DyAtIJ|J-X z**iYGbvs23T~>ShR+3A2A1&3oeiKU^h=(OAx}eVPG0206eY|HifEUZj zcuz8ga~p7B#Y1qH%aKX`=}|GMclhM)=68`5{Ov5Dc|R+xyx%iloi7C-I~~PNFa_+M z*rI}@jy6bRKH>BV-@oXK0sdJj?p79E5!J4gcbhb3JA7Ah1k2DXi>_eT#po~QFo{*h z1^lx%J40fDJi>~$J#D@hR@!zdmf`lcYz>|Znv-_ClMR%9S&AvA* zp-owF_%cCU)4-~0ZWJ)cT>#Ht1;ouduu+HjU@rCHdZ3K!nC?V|J%mZi?$+}gmcFXC zX(9NP9DvtGW4{UccIZoL>0AOC!c)!s?uFWSx=xMP%;2KJ)B;BrZXoY%oDUJ1IVIne zC%kd`~2s!hQ^5#D=q9_`Os3B(2)XcWnzBL+wVYfq{$q6#Ng~}CDJ#Isie%$>d{*H zbph@9Zo>GNZu2Op_dx#HvT!fFHz$d2AzFQj4*6!z8T0t-3OhXg+}DY8i?IB$5OhX_ zE!V)|ymuOW4LQy^Lb?O?l*Ktk9${J*Ou2x$(H5c-t0;iOR)p1iWoE(bz?nemdr6I$ zU@0OZ4-rn;r6ly5$FGz>KYa!GvsKbkWHy>*+>%Y&!_JCiZ?!MgP9-mYqeF&9Eym1k zZdCxnVPOegE~HIi)JLP_PW8L@{3CnKv0GK>L~)|#H&`8!x+y@;j&0FkT^l0witej@ zUP7upTl`?DlvjnTkf}9fs%aeFQam8{;X{+gnew~p9;o${P|Ig6oam%S-#nA_K=|-x zB4xwTc+hzOA2#U8$ohhEm=uKwrz}twp}uicLe`t!K!!Y(Fu`IvBw+wJ?8$U_NKo)d zf1yW-Z7(c8h7Wvt8P>KpYb-W+o#pvWC3ynyZ14V6y{~a^Z#leZL5j0C;$cz_dU_$t8!4mzj9LHr?b>z52w?|ub4cn`%BCS~))G&a&MXuHxN09m7&Wq~9xLD&6wA@z-K(l8h!U^#Bfs#*5tV$n^~o^PE%^ zLI!Q?SU++cZxXmk>!q&j`DpDK=7IjeVP1Qf-T<>tc{Yz)UHMd1MkG+J2mRu)f1Vb?Sj7V4*kmqdgLIM8#bBds&EInpD6+!lIoEmAewhf4M|mt^Fd zEgTuR!+5u;y9NM1lL(nrJ7Uu|f5pqtWj=$z7S#B>hWs)Pz6SAT)z zvi94chQOl8pn68;;Gc)+qmECY!d?DM#nkGWqoF7nfySSm`03R1LmH{gkNS6Yqp@^l zxDqc6Ac>!-#h63}u&YY3Hi3T9Dg3?X<8-sEgv~CMDnGAxEF0VdSLE(?p+DpFh z7#g5`ICV3Sv!w1NlO2paVW#L0T1PJ>o{)y1^6q-dJ0b%9{p|!;anSFo(`fe5 zW?^2MSOjjfo1t;cUsSSe0|*HNR|;9v)Ge zOa0NQ^VeHdBt9PW&rXjzW$?8ieM9d?BDH$7XfEZ^3zSnVcW9dRZDIT&-sRv4oaiO% z41(qdou_=)B2lDdE26aA&Gg5a#>t)V@M7db%D>yXky0g!$jt-&h6_jjC2eV12^V-6 zybmva#kf@^6f%kbd^s@!5`1bXK>=vo!lgvr4%$;ftYbPF+c9XBC?p1gAu z8LEVT#uYTrlOH;sOtk<icBG6xXXrBL;4*`oM3Ji5H zQoYCp`(V5ejt$0>)TND<928!OeGJl9%bc3L72eWJo^5%Gv;Mx=elbLn((?nlpeTB@ z(h8a|Xr9xfyU3pWYvzJe^Ap4mo;K@_Hl9ZfPRUTJp&C&q3smQJLy*3`;;w1&!cXx- ziHS=@bUxttSEs}hx2Dd)BEve@vME)debJtbT5LhGn=x3%us7#lsgL#UWDK|?OyBk* zWqM&_>4cX)0Qx*@+V}Bl<*lgXO17ojQhU;3(C^>^+oqG7533A@@^%L+0iHc)wRlYZ z2>A#?4wK&bI#y(8zP*qVgT;Pn;_XU*C1KnS2YB|JEs(pbXj|oVboN$H8X`VWgzQY- zY$k?YS&91=8&Gxh1K`h_f;4;;x8jTYSnRtKb=aKv9jPh@cD_?{$Zi7q0_z*aBA~r_ z)1%J?vyKtZP?-1Edgwzm%J@+TM1+1QTu?uP6;Lr`ECl@X7AjP&6vi=IzmQiq4i!C) z5d9@#~uR=N!m zl~b$+F0XbNe>l;J7Rf;{0>9Cq3FKe>2c?pikqOg=}5V_ z>1zX~OaBL9R~1!9(`*Uu!QFzpySoPh1Pcxc5Zv9}-QC^YU4pv?cPF^R{qCCc{-5Vs zC3{!b%;~AFPQHRUL>&YtkK)*SOF(vj2vFp8M{OwYzB3T2tEg!7@hZEc@~>p{)#r)g?~>B(leCS52cxAbk~e z-}`VbJT~!v4;_hs#|mg}lY1z_U0~0+6ALuZOs;{>VYtuNpq1Jyt{>Ook|b(bRx-p(RbQnpV_Mk*( zLqb4p>FWrK5SfUyx05I7juBxNfS*TAqh&CXl%({!S9Y0O0z8SV_0Mw4rqpmx6#wto z7kwlRfPWr6K2z(OA(=K;QlEBHggFL#gFcw0uMU-~kBmf-SnfaKT>nEz4S^_~9FBvmW@i9o0td=`nzkI!Fa z!|zD8-)FG}*!3(&jJ7rS`VY#tkMbX}f|7ofo5-_$AL6|d%f;uBq4!N-+XBpU$*f8e z)4kQTGRLp=-?ow_(MrF(wk$iu zZO4Fl!kH0K+el~Hnioz9I^eEiO1xGcMxiD0qZ6Wm`p3QWS*b+nE2Y%h8dPNI=A6^! zdQ>EvvVM4l@%GCVpc+Ew0`=Z2Qq~SU;sg>m5qXiwm6{iAgvcy6=f-pYbjvh6^S`a0 zC;%6)?rNA)x`BQ+92)_Kw0uRL7;D&msew!qe3Xmk+?>!DEg(<5p>{Mcbnx80ws{UP zxW(E|P4kSG&Uf55Fm2cB4v68PK>c;z3jf_D;{LtE2>m{b6n-jJ8siHp$E>HeGJoY9 z0(y1F+bxh+-X^NyP2O50YeKW_)94ck9B_qjSq*Th%XNxYylFGs1K0o|-e%lu-N@NH zPdfZy`IA|t5?U+IDdTgB`#IACa89-_(PAKdTeaeIk^jVCJUmyMQgbz6+&II1RGDLt zJl+uaqpc~QLHpU>yEYg&*CX<_BoU{YJmzcs(gje%Ra&4nw%e`76(!P@og@( zRr&nN>`qDH_n9bM%>m%ohxG+pZ;S`7rJu6-U{-12bNT6ob=3tWL<(A!-@=haVH?1k z569WNeL5zGR9xq(k>;5j$)@1_TGW@z}eLC297b;FJ|o@d|Qa#X!9Muc45!OEegsK>M-2;dvZ;XcKOA{s(&coun!1bI^KT zxD8u)BoVTaB9nV5Q$QTP3rfCckmNDtIE|#>H~tEN@ZAAz-)wvwJTvCnk#OuQX#l%^ zNJEz@<%X`Vcf>N`^jq)){^&j$+w_;_k-S&azpV(G?|^*q6Fa=TThw!TB;Cl8Fw2R+ zCEk_-w^lb4;~*^k?0mQf{clJl9OwPfj_ zsu(2qKknT!0`5zD{f$&`=hZXNjQZB`;rx~A6A(2vc5l@|KmIgb*rF1!}hG74gt+o z^KVEjx;!(B(}sl_3x#QlaZTSzY7s$jgq^u5DyXH4!IB5rZ3>cMeh3__A1OPmY}q&a zD}V{pB6mW`Z4s}fK(TCp0GihqAjF!~i(B4xTykZh3D1sMN#fbQ)Iski&ranhBcTNr zJ_*Dd(5nuX>0h8QGh~BSgnGbm(m;ZIk%m&DBm^ zv4pV^r-lyL4TNjCZqy^&Bw6*BHJ}Z=oipj95Y6B;Hrzv}<{W+1zs~{q4HV*n%A=>; zN3@pAoZ{ZraScRDY4G-6%n!^mq!-n1aT%>W5hql23)wDCg@quP-tD=5@_%t2h!CS(>s4 z`h|d8hztI{9L&mDm3P>CxM*D!68${*(i@%&HhPR)XWZsCt`Xp0NGQXkI59Fp^3=B{ z$mxmzHC3t5DFf)m~@kdX&DK%lQb4+&Ehh%G`dt_Q?{7I zVav=#Pe26V^1m(;BnHj>2sJv*hm@e>MrrYtJYR&&)X;AHg%hO15`>il+4#9@LERIi z??iI)BZO67&L_o%t<&r`m*dLVHsf+tg@jqh$$0e0fgF&hp>GYJUWa7I+C!23)CL+F z0;VF;1HYh!EnaRM=b{#bLc)ObeaOHt7qR)4M6U~42P5?q;J28Hic2@3Ou)i5(VLJk zfxhR3;fpsnOG;X4!@;aS5tp#=W^SFOpK|=TFlT(0k@n&!q5+<(VcHThkTkcspVCG{ z!_a0GWwmcY7L>>qH1C1;m$2N{UKk-VOS{m8!w}EOD6sOO=WzwX1(WfE z>uAl)ev?FyT%qw@;a3V5=CpgEFXqBwr#Njql!FC`qxA5c3o#BK!>~F&EO~Dz^HpmdyPgR8YvqE%h(S0Hv`<5_JeZSBE|H4(U z(=AsNu}bwK%}@l4|Ae~L@4cAVqnvwEMR2V()N618{)L+v=Tpv_zw)i>C^ASXu8*%3 z<3At-6U_89No}CMRUzmj}PyWjoaf%8B*KU*6cT@bJ8yn@V2Tb<~3T zhr-kOd2mJRS+bOnv7!>vOt!?XQfO^Y7(}(3W5*KWbN_q=@;jo$&g|DwUEYtM}W$b;~M*YLHb*rNVO3nBHLE~O{CQ<+KP$74}@irDmcOe0{g68WM4 zE)jWkXd$22B2Q-TC+OCoC1O3I*Sxojz{9WIM-ZY8bjD&q`c@1*NHh?*c82SX@)Qj- zYgq7f7=9lyaDI_6V@@Ncbp8v{w?R0Ht?!O`{KA$0VM(m1un~i2s`uy9RcFNzJYAUZ z&s?f56c`$@) zKH&w{AhH5-QQz?|{AsR=&o^cLVYtGt;KBWx_NJ;Bl#Pwl;pT`U%wYlEqJB0;+-R!o zW-so;w}ox4Ie-&Sb+xD}EhFt4HD~t!NFD(4IVx?19=4B%-ksIXVMA9;W`W(Z5`}S0qo|P1>67{*rQmBo( z#(}z&moPAeop2e0V}_p|s6+p2%sLbkz(zAfv5C0MD1ZcvO91GGNv+W(iJ;vyofZ{0a*_~|kq^b^DCHT#Wqv9FL)`bT( z^LKeOxpG)?xRQ1VBsVbnQwWiQI!BIJ(sriE0%j9YgBS6lyk=bSp%*QzEgl6F@6Jc` z;3r`NMZ4?1?_j4b@;tn&*OR>BBrSJNMof9pa_m67v7t1rAKyebc_%tQ5B@!-a7Q>7 z!fmn^&{kJao#QArF5Us;;yB>wgb`K;>i13sC48zDanqi5 ztz!2>w#qQA=lL(Wnn?h^`0rcg)|vMkA$nAtE#Q+}FXs|X8fTj~R@ctPb!~xhDW`yc z@exW zWG#;gN3{O^0#aq8s0~o~LjW%E7x({diY7Ql*K9)v#hc~ZU+oy4kP^ApUdVu7>7Gx| zHv)1A2nw*jl?N}_^pmn8kM^wN0=a^vOrifWx4FsTKU=+bB>;94&MZ<3JQZLj#3gY$az8}G67CLLN zYuc&lHwEHNbfriz%UtDJMI@nZ={A{m%ew2c**^K*VE&E%{m#~rL>O4-iN2Q?Qa&;~ z7dVip3kF_4xq3Z>AK?hte_vvvy=0WiSxbTRz0Mj~KIIM$`rtAPm3T5bNuVq&_PXoS z9frW`?ugXYZ(*#zK{{OOh2)*e;Ec!IV{Na*JGGhX$V z9Yt+=@Ktk$Q*oc)$VWiFBxAQ`qLR%Q4{C=M8fb}dI22))G$!y*4LVBv*1=2l{0r)@ zOD4PQaxxr?OrJ#I!tik6LT$1ZPS%2iEmW)UzzrRXRr(I_mQ0b7cNWp3gMK~t_+h;@ z!gHL?bV4k0zueFQgZ9r4qRJVFBbhR>Af-k;qo#Y~6|qE0*8k$4B#Hcx30Dg-MJdr2 z!>JgMoMyNOL{c^$&-!To0tepW@#A!eHLK*+cMGrN%{r{-yb6%q?+Q^Y-4=*d%IiV_ zJ~BC&Oge$}$VQr*B+9TU3XPA+Od#GA8R^Opn8y;`eR{_D=Uo=rdN*y8VE@$slhrfW zw+=}(6o9uB!{5J}mup@nS_G8v1hJ=ig=(||BtsyGg}%s9)bqOIWB_uhJo9e828;d~ zIm1Vh(wN+N!>@guRPHbQB-9XE8@`5CYk+^L!Zr-^qAmYEHadMx9Ws&r8+o|p#WP>J zC*@3s(QV{93+m%ZH4~>SstmVaQ2kIHMX8=HG2&L-a5Qq5M;WX(jcA{X1)Wbx!FvPux~f?=3*$2i5U3+|6o5)vA7+GgfJ*qS|TMmrjaa z&EvGX;jn$i%FSY&UBJIIj%ObK$BvP0Lhb)L?dM6!tfCSOo(x+R)wlvyN@8DpK<9wc z%-x<>=g$sncd%5_gc%zREG%q}_tQ<=$d6&+FZ&=OXo37obLUBF{qpae9Vryz5sv#l z;gtGcVEx|nyQ;+#*h4B`cr8#*(-KjN4r(Re;k%_CH_fu3KUD3<_tzkcZzoesn^f

  • ecf&5+ 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 Date: Fri, 16 Jul 2021 12:04:25 +0100 Subject: [PATCH 123/291] Refactor utility io conversions into one internal package Improve reader type conversion by checking if type satisfies ReaderAt to avoid unnecessary wrapping. Move io converters into one place. Fixes: - https://github.com/ipld/go-car/issues/145 This commit was moved from ipld/go-car@f3fc595830a066d8ba80517b316b59a7c2233f5f --- ipld/car/v2/index_gen.go | 19 +------------------ ipld/car/v2/internal/io/converter.go | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 223939d204..859909cae5 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "os" - "sync" internalio "github.com/ipld/go-car/v2/internal/io" @@ -97,22 +96,6 @@ func GenerateIndexFromFile(path string) (index.Index, error) { return GenerateIndex(f) } -var _ io.ReaderAt = (*readSeekerAt)(nil) - -type readSeekerAt struct { - rs io.ReadSeeker - mu sync.Mutex -} - -func (rsa *readSeekerAt) ReadAt(p []byte, off int64) (n int, err error) { - rsa.mu.Lock() - defer rsa.mu.Unlock() - if _, err := rsa.rs.Seek(off, io.SeekStart); err != nil { - return 0, err - } - return rsa.rs.Read(p) -} - // ReadOrGenerateIndex accepts both CAR v1 and v2 format, and reads or generates an index for it. // When the given reader is in CAR v1 format an index is always generated. // For a payload in CAR v2 format, an index is only generated if Header.HasIndex returns false. @@ -137,7 +120,7 @@ func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { return GenerateIndex(rs) case 2: // Read CAR v2 format - v2r, err := NewReader(&readSeekerAt{rs: rs}) + v2r, err := NewReader(internalio.ToReaderAt(rs)) if err != nil { return nil, err } diff --git a/ipld/car/v2/internal/io/converter.go b/ipld/car/v2/internal/io/converter.go index 4c723ec46e..65350d34be 100644 --- a/ipld/car/v2/internal/io/converter.go +++ b/ipld/car/v2/internal/io/converter.go @@ -3,6 +3,7 @@ package io import ( "io" "io/ioutil" + "sync" ) var ( @@ -10,6 +11,7 @@ var ( _ io.ByteReader = (*readSeekerPlusByte)(nil) _ io.ByteReader = (*discardingReadSeekerPlusByte)(nil) _ io.ReadSeeker = (*discardingReadSeekerPlusByte)(nil) + _ io.ReaderAt = (*readSeekerAt)(nil) ) type ( @@ -30,6 +32,11 @@ type ( io.ReadSeeker io.ByteReader } + + readSeekerAt struct { + rs io.ReadSeeker + mu sync.Mutex + } ) func ToByteReader(r io.Reader) io.ByteReader { @@ -49,6 +56,13 @@ func ToByteReadSeeker(r io.Reader) ByteReadSeeker { return &discardingReadSeekerPlusByte{Reader: r} } +func ToReaderAt(rs io.ReadSeeker) io.ReaderAt { + if ra, ok := rs.(io.ReaderAt); ok { + return ra + } + return &readSeekerAt{rs: rs} +} + func (rb readerPlusByte) ReadByte() (byte, error) { return readByte(rb) } @@ -89,3 +103,12 @@ func readByte(r io.Reader) (byte, error) { _, err := io.ReadFull(r, p[:]) return p[0], err } + +func (rsa *readSeekerAt) ReadAt(p []byte, off int64) (n int, err error) { + rsa.mu.Lock() + defer rsa.mu.Unlock() + if _, err := rsa.rs.Seek(off, io.SeekStart); err != nil { + return 0, err + } + return rsa.rs.Read(p) +} From 520dcfae1229dbc0dac24431c151b0614d07483b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 15 Jul 2021 17:33:35 +0100 Subject: [PATCH 124/291] unify options and add more blockstore options We've agreed to have unified options, since many will be shared between the root and blockstore packages. Include docs, and update the tests. This commit was moved from ipld/go-car@7d8f54ffa8521591d11a6ca44312de47900792f6 --- ipld/car/v2/blockstore/readonly.go | 73 ++++++++++++++---- ipld/car/v2/blockstore/readwrite.go | 96 +++++++++++++----------- ipld/car/v2/blockstore/readwrite_test.go | 16 ++-- ipld/car/v2/index_gen.go | 39 ++++++---- ipld/car/v2/options.go | 67 +++++++++++++++++ ipld/car/v2/reader.go | 6 +- 6 files changed, 215 insertions(+), 82 deletions(-) create mode 100644 ipld/car/v2/options.go diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index cf799fe7a6..5b9c8535dd 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -24,7 +24,7 @@ import ( var _ blockstore.Blockstore = (*ReadOnly)(nil) -// ReadOnly provides a read-only Car Block Store. +// ReadOnly provides a read-only CAR Block Store. type ReadOnly struct { // mu allows ReadWrite to be safe for concurrent use. // It's in ReadOnly so that read operations also grab read locks, @@ -41,6 +41,35 @@ type ReadOnly struct { // If we called carv2.NewReaderMmap, remember to close it too. carv2Closer io.Closer + + ropts carv2.ReadOptions +} + +// UseWholeCIDs is a read option which makes a CAR blockstore identify blocks by +// whole CIDs, and not just their multihashes. The default is to use +// multihashes, which matches the current semantics of go-ipfs-blockstore v1. +// +// Enabling this option affects a number of methods, including read-only ones: +// +// • Get, Has, and HasSize will only return a block +// only if the entire CID is present in the CAR file. +// +// • DeleteBlock will delete a block only when the entire CID matches. +// +// • AllKeysChan will return the original whole CIDs, instead of with their +// multicodec set to "raw" to just provide multihashes. +// +// • If AllowDuplicatePuts isn't set, +// Put and PutMany will deduplicate by the whole CID, +// allowing different CIDs with equal multihashes. +// +// Note that this option only affects the blockstore, and is ignored by the root +// go-car/v2 package. +func UseWholeCIDs(enable bool) carv2.ReadOption { + return func(o *carv2.ReadOptions) { + // TODO: update methods like Get, Has, and AllKeysChan to obey this. + o.BlockstoreUseWholeCIDs = enable + } } // NewReadOnly creates a new ReadOnly blockstore from the backing with a optional index as idx. @@ -52,7 +81,12 @@ type ReadOnly struct { // * For a CAR v2 backing an index is only generated if Header.HasIndex returns false. // // There is no need to call ReadOnly.Close on instances returned by this function. -func NewReadOnly(backing io.ReaderAt, idx index.Index) (*ReadOnly, error) { +func NewReadOnly(backing io.ReaderAt, idx index.Index, opts ...carv2.ReadOption) (*ReadOnly, error) { + b := &ReadOnly{} + for _, opt := range opts { + opt(&b.ropts) + } + version, err := readVersion(backing) if err != nil { return nil, err @@ -60,13 +94,15 @@ func NewReadOnly(backing io.ReaderAt, idx index.Index) (*ReadOnly, error) { switch version { case 1: if idx == nil { - if idx, err = generateIndex(backing); err != nil { + if idx, err = generateIndex(backing, opts...); err != nil { return nil, err } } - return &ReadOnly{backing: backing, idx: idx}, nil + b.backing = backing + b.idx = idx + return b, nil case 2: - v2r, err := carv2.NewReader(backing) + v2r, err := carv2.NewReader(backing, opts...) if err != nil { return nil, err } @@ -76,11 +112,13 @@ func NewReadOnly(backing io.ReaderAt, idx index.Index) (*ReadOnly, error) { if err != nil { return nil, err } - } else if idx, err = generateIndex(v2r.CarV1Reader()); err != nil { + } else if idx, err = generateIndex(v2r.CarV1Reader(), opts...); err != nil { return nil, err } } - return &ReadOnly{backing: v2r.CarV1Reader(), idx: idx}, nil + b.backing = v2r.CarV1Reader() + b.idx = idx + return b, nil default: return nil, fmt.Errorf("unsupported car version: %v", version) } @@ -97,7 +135,7 @@ func readVersion(at io.ReaderAt) (uint64, error) { return carv2.ReadVersion(rr) } -func generateIndex(at io.ReaderAt) (index.Index, error) { +func generateIndex(at io.ReaderAt, opts ...carv2.ReadOption) (index.Index, error) { var rs io.ReadSeeker switch r := at.(type) { case io.ReadSeeker: @@ -105,19 +143,19 @@ func generateIndex(at io.ReaderAt) (index.Index, error) { default: rs = internalio.NewOffsetReadSeeker(r, 0) } - return carv2.GenerateIndex(rs) + return carv2.GenerateIndex(rs, opts...) } // OpenReadOnly opens a read-only blockstore from a CAR file (either v1 or v2), generating an index if it does not exist. // Note, the generated index if the index does not exist is ephemeral and only stored in memory. // See car.GenerateIndex and Index.Attach for persisting index onto a CAR file. -func OpenReadOnly(path string) (*ReadOnly, error) { +func OpenReadOnly(path string, opts ...carv2.ReadOption) (*ReadOnly, error) { f, err := mmap.Open(path) if err != nil { return nil, err } - robs, err := NewReadOnly(f, nil) + robs, err := NewReadOnly(f, nil, opts...) if err != nil { return nil, err } @@ -191,7 +229,7 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { return -1, err } rdr := internalio.NewOffsetReadSeeker(b.backing, int64(idx)) - frameLen, err := varint.ReadUvarint(rdr) + sectionLen, err := varint.ReadUvarint(rdr) if err != nil { return -1, blockstore.ErrNotFound } @@ -202,7 +240,7 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { if !readCid.Equals(key) { return -1, blockstore.ErrNotFound } - return int(frameLen) - cidLen, err + return int(sectionLen) - cidLen, err } // Put is not supported and always returns an error. @@ -249,9 +287,14 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { return // TODO: log this error } - // Null padding; treat it as EOF. + // Null padding; by default it's an error. if length == 0 { - break // TODO make this an optional behaviour; by default we should error + if b.ropts.ZeroLegthSectionAsEOF { + break + } else { + return // TODO: log this error + // return fmt.Errorf("carv1 null padding not allowed by default; see WithZeroLegthSectionAsEOF") + } } thisItemForNxt := rdr.Offset() diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 0829b2c7d9..a1c7fd84f7 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -39,33 +39,19 @@ type ReadWrite struct { idx *insertionIndex header carv2.Header - dedupCids bool + wopts carv2.WriteOptions } -// TODO consider exposing interfaces -type Option func(*ReadWrite) // TODO consider unifying with writer options - -// WithCarV1Padding sets the padding to be added between CAR v2 header and its data payload on Finalize. -func WithCarV1Padding(p uint64) Option { - return func(b *ReadWrite) { - b.header = b.header.WithCarV1Padding(p) - } -} - -// WithIndexPadding sets the padding between data payload and its index on Finalize. -func WithIndexPadding(p uint64) Option { - return func(b *ReadWrite) { - b.header = b.header.WithIndexPadding(p) - } -} - -// WithCidDeduplication makes Put calls ignore blocks if the blockstore already -// has the exact same CID. -// This can help avoid redundancy in a CARv1's list of CID-Block pairs. +// AllowDuplicatePuts is a write option which makes a CAR blockstore not +// deduplicate blocks in Put and PutMany. The default is to deduplicate, +// which matches the current semantics of go-ipfs-blockstore v1. // -// Note that this compares whole CIDs, not just multihashes. -func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and return an option to allow disabling dedupliation? - b.dedupCids = true +// Note that this option only affects the blockstore, and is ignored by the root +// go-car/v2 package. +func AllowDuplicatePuts(allow bool) carv2.WriteOption { + return func(o *carv2.WriteOptions) { + o.BlockstoreAllowDuplicatePuts = allow + } } // OpenReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs and options. @@ -80,7 +66,7 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re // header (i.e. the inner CAR v1 header) onto the file before returning. // // When the given path already exists, the blockstore will attempt to resume from it. -// On resumption the existing data frames in file are re-indexed, allowing the caller to continue +// On resumption the existing data sections in file are re-indexed, allowing the caller to continue // putting any remaining blocks without having to re-ingest blocks for which previous ReadWrite.Put // returned successfully. // @@ -93,7 +79,7 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re // 1. start with a complete CAR v2 car.Pragma. // 2. contain a complete CAR v1 data header with root CIDs matching the CIDs passed to the // constructor, starting at offset optionally padded by WithCarV1Padding, followed by zero or -// more complete data frames. If any corrupt data frames are present the resumption will fail. +// more complete data sections. If any corrupt data sections are present the resumption will fail. // Note, if set previously, the blockstore must use the same WithCarV1Padding option as before, // since this option is used to locate the CAR v1 data payload. // @@ -102,7 +88,7 @@ func WithCidDeduplication(b *ReadWrite) { // TODO should this take a bool and re // // Resuming from finalized files is allowed. However, resumption will regenerate the index // regardless by scanning every existing block in file. -func OpenReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, error) { +func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.ReadWriteOption) (*ReadWrite, error) { // TODO: enable deduplication by default now that resumption is automatically attempted. f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o666) // TODO: Should the user be able to configure FileMode permissions? if err != nil { @@ -129,13 +115,27 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...Option) (*ReadWrite, er idx: newInsertionIndex(), header: carv2.NewHeader(0), } + for _, opt := range opts { - opt(rwbs) + switch opt := opt.(type) { + case carv2.ReadOption: + opt(&rwbs.ropts) + case carv2.WriteOption: + opt(&rwbs.wopts) + } + } + if p := rwbs.wopts.CarV1Padding; p > 0 { + rwbs.header = rwbs.header.WithCarV1Padding(p) + } + if p := rwbs.wopts.IndexPadding; p > 0 { + rwbs.header = rwbs.header.WithIndexPadding(p) } rwbs.carV1Writer = internalio.NewOffsetWriter(rwbs.f, int64(rwbs.header.CarV1Offset)) v1r := internalio.NewOffsetReadSeeker(rwbs.f, int64(rwbs.header.CarV1Offset)) - rwbs.ReadOnly = ReadOnly{backing: v1r, idx: rwbs.idx, carv2Closer: rwbs.f} + rwbs.ReadOnly.backing = v1r + rwbs.ReadOnly.idx = rwbs.idx + rwbs.ReadOnly.carv2Closer = rwbs.f if resume { if err = rwbs.resumeWithRoots(roots); err != nil { @@ -237,13 +237,13 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { if err != nil { return err } - frameOffset := int64(0) - if frameOffset, err = v1r.Seek(int64(offset), io.SeekStart); err != nil { + sectionOffset := int64(0) + if sectionOffset, err = v1r.Seek(int64(offset), io.SeekStart); err != nil { return err } for { - // Grab the length of the frame. + // Grab the length of the section. // Note that ReadUvarint wants a ByteReader. length, err := varint.ReadUvarint(v1r) if err != nil { @@ -253,10 +253,13 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { return err } - // Null padding; treat zero-length frames as an EOF. - // They don't contain a CID nor block, so they're not useful. + // Null padding; by default it's an error. if length == 0 { - break // TODO This behaviour should be an option, not default. By default we should error. Hook this up to a write option + if b.ropts.ZeroLegthSectionAsEOF { + break + } else { + return fmt.Errorf("carv1 null padding not allowed by default; see WithZeroLegthSectionAsEOF") + } } // Grab the CID. @@ -264,16 +267,16 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { if err != nil { return err } - b.idx.insertNoReplace(c, uint64(frameOffset)) + b.idx.insertNoReplace(c, uint64(sectionOffset)) - // Seek to the next frame by skipping the block. - // The frame length includes the CID, so subtract it. - if frameOffset, err = v1r.Seek(int64(length)-int64(n), io.SeekCurrent); err != nil { + // Seek to the next section by skipping the block. + // The section length includes the CID, so subtract it. + if sectionOffset, err = v1r.Seek(int64(length)-int64(n), io.SeekCurrent); err != nil { return err } } // Seek to the end of last skipped block where the writer should resume writing. - _, err = b.carV1Writer.Seek(frameOffset, io.SeekStart) + _, err = b.carV1Writer.Seek(sectionOffset, io.SeekStart) return err } @@ -304,8 +307,17 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { for _, bl := range blks { c := bl.Cid() - if b.dedupCids && b.idx.hasExactCID(c) { - continue + + if !b.wopts.BlockstoreAllowDuplicatePuts { + if b.ropts.BlockstoreUseWholeCIDs && b.idx.hasExactCID(c) { + continue // deduplicated by CID + } + if !b.ropts.BlockstoreUseWholeCIDs { + _, err := b.idx.Get(c) + if err == nil { + continue // deduplicated by hash + } + } } n := uint64(b.carV1Writer.Position()) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 884edca754..3a8099fbd2 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -122,15 +122,19 @@ func TestBlockstore(t *testing.T) { func TestBlockstorePutSameHashes(t *testing.T) { tdir := t.TempDir() + + // wbs allows duplicate puts. wbs, err := blockstore.OpenReadWrite( filepath.Join(tdir, "readwrite.car"), nil, + blockstore.AllowDuplicatePuts(true), ) require.NoError(t, err) t.Cleanup(func() { wbs.Finalize() }) + // wbs deduplicates puts by CID. wbsd, err := blockstore.OpenReadWrite( filepath.Join(tdir, "readwrite-dedup.car"), nil, - blockstore.WithCidDeduplication, + blockstore.UseWholeCIDs(true), ) require.NoError(t, err) t.Cleanup(func() { wbsd.Finalize() }) @@ -276,7 +280,7 @@ func TestBlockstoreNullPadding(t *testing.T) { // A sample null-padded CARv1 file. paddedV1 = append(paddedV1, make([]byte, 2048)...) - rbs, err := blockstore.NewReadOnly(bufferReaderAt(paddedV1), nil) + rbs, err := blockstore.NewReadOnly(bufferReaderAt(paddedV1), nil, carv2.ZeroLegthSectionAsEOF) require.NoError(t, err) roots, err := rbs.Roots() @@ -483,8 +487,8 @@ func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { subject, err := blockstore.OpenReadWrite( path, WantRoots, - blockstore.WithCarV1Padding(wantCarV1Padding), - blockstore.WithIndexPadding(wantIndexPadding)) + carv2.UseCarV1Padding(wantCarV1Padding), + carv2.UseIndexPadding(wantIndexPadding)) require.NoError(t, err) t.Cleanup(func() { subject.Close() }) require.NoError(t, subject.Put(oneTestBlockWithCidV1)) @@ -544,7 +548,7 @@ func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing. subject, err := blockstore.OpenReadWrite( path, WantRoots, - blockstore.WithCarV1Padding(1413)) + carv2.UseCarV1Padding(1413)) require.NoError(t, err) t.Cleanup(func() { subject.Close() }) require.NoError(t, subject.Put(oneTestBlockWithCidV1)) @@ -553,7 +557,7 @@ func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing. resumingSubject, err := blockstore.OpenReadWrite( path, WantRoots, - blockstore.WithCarV1Padding(1314)) + carv2.UseCarV1Padding(1314)) require.EqualError(t, err, "cannot resume from file with mismatched CARv1 offset; "+ "`WithCarV1Padding` option must match the padding on file. "+ "Expected padding value of 1413 but got 1314") diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 859909cae5..4fa9ac1df2 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -18,7 +18,12 @@ import ( // GenerateIndex generates index for a given car in v1 format. // The index can be stored using index.Save into a file or serialized using index.WriteTo. -func GenerateIndex(v1r io.Reader) (index.Index, error) { +func GenerateIndex(v1r io.Reader, opts ...ReadOption) (index.Index, error) { + var o ReadOptions + for _, opt := range opts { + opt(&o) + } + reader := internalio.ToByteReadSeeker(v1r) header, err := carv1.ReadHeader(reader) if err != nil { @@ -35,20 +40,20 @@ func GenerateIndex(v1r io.Reader) (index.Index, error) { } records := make([]index.Record, 0) - // Record the start of each frame, with first frame starring from current position in the + // Record the start of each section, with first section starring from current position in the // reader, i.e. right after the header, since we have only read the header so far. - var frameOffset int64 + var sectionOffset int64 // The Seek call below is equivalent to getting the reader.offset directly. // We get it through Seek to only depend on APIs of a typical io.Seeker. // This would also reduce refactoring in case the utility reader is moved. - if frameOffset, err = reader.Seek(0, io.SeekCurrent); err != nil { + if sectionOffset, err = reader.Seek(0, io.SeekCurrent); err != nil { return nil, err } for { - // Read the frame's length. - frameLen, err := varint.ReadUvarint(reader) + // Read the section's length. + sectionLen, err := varint.ReadUvarint(reader) if err != nil { if err == io.EOF { break @@ -56,11 +61,13 @@ func GenerateIndex(v1r io.Reader) (index.Index, error) { return nil, err } - // Null padding; treat zero-length frames as an EOF. - // They don't contain a CID nor block, so they're not useful. - // TODO: Amend the CARv1 spec to explicitly allow this. - if frameLen == 0 { - break + // Null padding; by default it's an error. + if sectionLen == 0 { + if o.ZeroLegthSectionAsEOF { + break + } else { + return nil, fmt.Errorf("carv1 null padding not allowed by default; see ZeroLegthSectionAsEOF") + } } // Read the CID. @@ -68,12 +75,12 @@ func GenerateIndex(v1r io.Reader) (index.Index, error) { if err != nil { return nil, err } - records = append(records, index.Record{Cid: c, Idx: uint64(frameOffset)}) + records = append(records, index.Record{Cid: c, Idx: uint64(sectionOffset)}) - // Seek to the next frame by skipping the block. - // The frame length includes the CID, so subtract it. - remainingFrameLen := int64(frameLen) - int64(cidLen) - if frameOffset, err = reader.Seek(remainingFrameLen, io.SeekCurrent); err != nil { + // Seek to the next section by skipping the block. + // The section length includes the CID, so subtract it. + remainingSectionLen := int64(sectionLen) - int64(cidLen) + if sectionOffset, err = reader.Seek(remainingSectionLen, io.SeekCurrent); err != nil { return nil, err } } diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go new file mode 100644 index 0000000000..ad859d1a0a --- /dev/null +++ b/ipld/car/v2/options.go @@ -0,0 +1,67 @@ +package car + +// ReadOptions holds the configured options after applying a number of +// ReadOption funcs. +// +// This type should not be used directly by end users; it's only exposed as a +// side effect of ReadOption. +type ReadOptions struct { + ZeroLegthSectionAsEOF bool + + BlockstoreUseWholeCIDs bool +} + +// ReadOption describes an option which affects behavior when parsing CAR files. +type ReadOption func(*ReadOptions) + +func (ReadOption) readWriteOption() {} + +var _ ReadWriteOption = ReadOption(nil) + +// WriteOptions holds the configured options after applying a number of +// WriteOption funcs. +// +// This type should not be used directly by end users; it's only exposed as a +// side effect of WriteOption. +type WriteOptions struct { + CarV1Padding uint64 + IndexPadding uint64 + + BlockstoreAllowDuplicatePuts bool +} + +// WriteOption describes an option which affects behavior when encoding CAR files. +type WriteOption func(*WriteOptions) + +func (WriteOption) readWriteOption() {} + +var _ ReadWriteOption = WriteOption(nil) + +// ReadWriteOption is either a ReadOption or a WriteOption. +type ReadWriteOption interface { + readWriteOption() +} + +// ZeroLegthSectionAsEOF is a read option which allows a CARv1 decoder to treat +// a zero-length section as the end of the input CAR file. For example, this can +// be useful to allow "null padding" after a CARv1 without knowing where the +// padding begins. +func ZeroLegthSectionAsEOF(o *ReadOptions) { + o.ZeroLegthSectionAsEOF = true +} + +// UseCarV1Padding is a write option which sets the padding to be added between +// CAR v2 header and its data payload on Finalize. +func UseCarV1Padding(p uint64) WriteOption { + return func(o *WriteOptions) { + o.CarV1Padding = p + } +} + +// UseIndexPadding is a write option which sets the padding between data payload +// and its index on Finalize. +func UseIndexPadding(p uint64) WriteOption { + return func(o *WriteOptions) { + o.IndexPadding = p + } +} diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index ad231c0f1d..f845a81ce7 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -20,13 +20,13 @@ type Reader struct { } // OpenReader is a wrapper for NewReader which opens the file at path. -func OpenReader(path string) (*Reader, error) { +func OpenReader(path string, opts ...ReadOption) (*Reader, error) { f, err := mmap.Open(path) if err != nil { return nil, err } - r, err := NewReader(f) + r, err := NewReader(f, opts...) if err != nil { return nil, err } @@ -38,7 +38,7 @@ func OpenReader(path string) (*Reader, error) { // NewReader constructs a new reader that reads CAR v2 from the given r. // Upon instantiation, the reader inspects the payload by reading the pragma and will return // an error if the pragma does not represent a CAR v2. -func NewReader(r io.ReaderAt) (*Reader, error) { +func NewReader(r io.ReaderAt, opts ...ReadOption) (*Reader, error) { cr := &Reader{ r: r, } From 4adbd3d63414e7a501e2a21aa2a9f5b7bd0456ca Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 16 Jul 2021 11:50:48 +0100 Subject: [PATCH 125/291] Implement examples and tests for `index` package Add examples that show how to read and write an index to/from file. Test marshalling and unmarshalling index files. Run `gofumt` on repo. This commit was moved from ipld/go-car@0aefa5b13039d83b109a8cc2921cf0f41f46933a --- ipld/car/v2/blockstore/readonly_test.go | 5 +- ipld/car/v2/index/example_test.go | 93 +++++++++++ ipld/car/v2/index/index_test.go | 172 +++++++++++++++++++++ ipld/car/v2/index/indexsorted_test.go | 3 +- ipld/car/v2/testdata/sample-index.carindex | Bin 0 -> 209505 bytes 5 files changed, 270 insertions(+), 3 deletions(-) create mode 100644 ipld/car/v2/index/example_test.go create mode 100644 ipld/car/v2/index/index_test.go create mode 100644 ipld/car/v2/testdata/sample-index.carindex diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 97ed730912..10f9804cd9 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -2,13 +2,14 @@ package blockstore import ( "context" - blockstore "github.com/ipfs/go-ipfs-blockstore" - "github.com/ipfs/go-merkledag" "io" "os" "testing" "time" + blockstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipfs/go-merkledag" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" diff --git a/ipld/car/v2/index/example_test.go b/ipld/car/v2/index/example_test.go new file mode 100644 index 0000000000..cf6d56a630 --- /dev/null +++ b/ipld/car/v2/index/example_test.go @@ -0,0 +1,93 @@ +package index_test + +import ( + "fmt" + "os" + "reflect" + + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/index" +) + +// ExampleReadFrom unmarshalls an index from an indexed CARv2 file, and for each root CID prints the +// offset at which its corresponding block starts relative to the wrapped CARv1 data payload. +func ExampleReadFrom() { + // Open the CARv2 file + cr, err := carv2.OpenReader("../testdata/sample-wrapped-v2.car") + if err != nil { + panic(err) + } + defer cr.Close() + + // Get root CIDs in the CARv1 file. + roots, err := cr.Roots() + if err != nil { + panic(err) + } + + // Read and unmarshall index within CARv2 file. + idx, err := index.ReadFrom(cr.IndexReader()) + if err != nil { + panic(err) + } + + // For each root CID print the offset relative to CARv1 data payload. + for _, r := range roots { + offset, err := idx.Get(r) + if err != nil { + panic(err) + } + fmt.Printf("Frame with CID %v starts at offset %v relative to CARv1 data payload.\n", r, offset) + } + + // Output: + // Frame with CID bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy starts at offset 61 relative to CARv1 data payload. +} + +// ExampleSave unmarshalls an index from an indexed CARv2 file, and stores it as a separate +// file on disk. +func ExampleSave() { + // Open the CARv2 file + src := "../testdata/sample-wrapped-v2.car" + cr, err := carv2.OpenReader(src) + if err != nil { + panic(err) + } + defer cr.Close() + + // Read and unmarshall index within CARv2 file. + idx, err := index.ReadFrom(cr.IndexReader()) + if err != nil { + panic(err) + } + + // Store the index alone onto destination file. + dest := "../testdata/sample-index.carindex" + err = index.Save(idx, dest) + if err != nil { + panic(err) + } + + // Open the destination file that contains the index only. + f, err := os.Open(dest) + if err != nil { + panic(err) + } + defer f.Close() + + // Read and unmarshall the destination file as a separate index instance. + reReadIdx, err := index.ReadFrom(f) + if err != nil { + panic(err) + } + + // Expect indices to be equal. + if reflect.DeepEqual(idx, reReadIdx) { + fmt.Printf("Saved index file at %v matches the index embedded in CARv2 at %v.\n", dest, src) + } else { + panic("expected to get the same index as the CARv2 file") + } + + // Output: + // Saved index file at ../testdata/sample-index.carindex matches the index embedded in CARv2 at ../testdata/sample-wrapped-v2.car. +} diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go new file mode 100644 index 0000000000..945ca1bbc1 --- /dev/null +++ b/ipld/car/v2/index/index_test.go @@ -0,0 +1,172 @@ +package index + +import ( + "bytes" + "io" + "os" + "path/filepath" + "testing" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/ipld/go-car/v2/internal/carv1/util" + "github.com/multiformats/go-multicodec" + "github.com/multiformats/go-varint" + "github.com/stretchr/testify/require" +) + +func TestNew(t *testing.T) { + tests := []struct { + name string + codec multicodec.Code + want Index + wantErr bool + }{ + { + name: "CarSortedIndexCodecIsConstructed", + codec: multicodec.CarIndexSorted, + want: newSorted(), + }, + { + name: "ValidMultiCodecButUnknwonToIndexIsError", + codec: multicodec.Cidv1, + wantErr: true, + }, + { + name: "IndexSingleSortedMultiCodecIsError", + codec: multicodec.Code(indexSingleSorted), + wantErr: true, + }, + { + name: "IndexHashedMultiCodecIsError", + codec: multicodec.Code(indexHashed), + wantErr: true, + }, + { + name: "IndexGobHashedMultiCodecIsError", + codec: multicodec.Code(indexGobHashed), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := New(tt.codec) + if tt.wantErr { + require.Error(t, err) + } else { + require.Equal(t, tt.want, got) + } + }) + } +} + +func TestReadFrom(t *testing.T) { + idxf, err := os.Open("../testdata/sample-index.carindex") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, idxf.Close()) }) + + subject, err := ReadFrom(idxf) + require.NoError(t, err) + + crf, err := os.Open("../testdata/sample-v1.car") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, crf.Close()) }) + cr, err := carv1.NewCarReader(crf) + require.NoError(t, err) + + for { + wantBlock, err := cr.Next() + if err == io.EOF { + break + } + require.NoError(t, err) + + // Get offset from the index for a CID and assert it exists + gotOffset, err := subject.Get(wantBlock.Cid()) + require.NoError(t, err) + require.NotZero(t, gotOffset) + + // Seek to the offset on CARv1 file + _, err = crf.Seek(int64(gotOffset), io.SeekStart) + require.NoError(t, err) + + // Read the fame at offset and assert the frame corresponds to the expected block. + gotCid, gotData, err := util.ReadNode(crf) + require.NoError(t, err) + gotBlock, err := blocks.NewBlockWithCid(gotData, gotCid) + require.NoError(t, err) + require.Equal(t, wantBlock, gotBlock) + } +} + +func TestWriteTo(t *testing.T) { + // Read sample index on file + idxf, err := os.Open("../testdata/sample-index.carindex") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, idxf.Close()) }) + + // Unmarshall to get expected index + wantIdx, err := ReadFrom(idxf) + require.NoError(t, err) + + // Write the same index out + dest := filepath.Join(t.TempDir(), "index-write-to-test.carindex") + destF, err := os.Create(dest) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, destF.Close()) }) + require.NoError(t, WriteTo(wantIdx, destF)) + + // Seek to the beginning of the written out file. + _, err = destF.Seek(0, io.SeekStart) + require.NoError(t, err) + + // Read the written index back + gotIdx, err := ReadFrom(destF) + require.NoError(t, err) + + // Assert they are equal + require.Equal(t, wantIdx, gotIdx) +} + +func TestSave(t *testing.T) { + // Read sample index on file + idxf, err := os.Open("../testdata/sample-index.carindex") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, idxf.Close()) }) + + // Unmarshall to get expected index + wantIdx, err := ReadFrom(idxf) + require.NoError(t, err) + + // Save the same index at destination + dest := filepath.Join(t.TempDir(), "index-write-to-test.carindex") + require.NoError(t, Save(wantIdx, dest)) + + // Open the saved file + destF, err := os.Open(dest) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, destF.Close()) }) + + // Read the written index back + gotIdx, err := ReadFrom(destF) + require.NoError(t, err) + + // Assert they are equal + require.Equal(t, wantIdx, gotIdx) +} + +func TestMarshalledIndexStartsWithCodec(t *testing.T) { + // Read sample index on file + idxf, err := os.Open("../testdata/sample-index.carindex") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, idxf.Close()) }) + + // Unmarshall to get expected index + wantIdx, err := ReadFrom(idxf) + require.NoError(t, err) + + // Assert the first two bytes are the corresponding multicodec code. + buf := new(bytes.Buffer) + require.NoError(t, WriteTo(wantIdx, buf)) + require.Equal(t, varint.ToUvarint(uint64(multicodec.CarIndexSorted)), buf.Bytes()[:2]) +} diff --git a/ipld/car/v2/index/indexsorted_test.go b/ipld/car/v2/index/indexsorted_test.go index c491bedf5a..fe0ca961a0 100644 --- a/ipld/car/v2/index/indexsorted_test.go +++ b/ipld/car/v2/index/indexsorted_test.go @@ -1,10 +1,11 @@ package index import ( + "testing" + "github.com/ipfs/go-merkledag" "github.com/multiformats/go-multicodec" "github.com/stretchr/testify/require" - "testing" ) func TestSortedIndexCodec(t *testing.T) { diff --git a/ipld/car/v2/testdata/sample-index.carindex b/ipld/car/v2/testdata/sample-index.carindex new file mode 100644 index 0000000000000000000000000000000000000000..0f91e36507fc21d3a93a00f32bf330e024405af8 GIT binary patch literal 209505 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
    N*||>TJ{<{;C;+BF=Tv` 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 Date: Fri, 16 Jul 2021 12:51:13 +0100 Subject: [PATCH 126/291] Remove memory intensive writer and add warping tests Remove the memory intensive CARv2 writer implementation since it needs to be reimplemented anyway and not used. We will release writers if needed when it comes to SelectiveCar writer implementation. Just now it is not clear if we want a CARv2 writer that works with the deprecated `format.NodeGetter`. Add tests for V1 wrapping functionality. Reformat code in repo for consistency using `gofumpt`. This commit was moved from ipld/go-car@0f79c3e852cf5fab1e9b02066267a2d78d43d991 --- ipld/car/v2/writer.go | 124 ------------------------------------- ipld/car/v2/writer_test.go | 95 ++++++++++++++-------------- 2 files changed, 45 insertions(+), 174 deletions(-) diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index 3675972283..fa94913e9a 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -1,138 +1,14 @@ package car import ( - "bytes" - "context" "io" "os" internalio "github.com/ipld/go-car/v2/internal/io" - "github.com/ipfs/go-cid" - format "github.com/ipfs/go-ipld-format" "github.com/ipld/go-car/v2/index" - "github.com/ipld/go-car/v2/internal/carv1" ) -const bulkPaddingBytesSize = 1024 - -var bulkPadding = make([]byte, bulkPaddingBytesSize) - -type ( - // padding represents the number of padding bytes. - padding uint64 - // writer writes CAR v2 into a given io.Writer. - writer struct { - NodeGetter format.NodeGetter - CarV1Padding uint64 - IndexPadding uint64 - - ctx context.Context - roots []cid.Cid - encodedCarV1 *bytes.Buffer - } -) - -// WriteTo writes this padding to the given writer as default value bytes. -func (p padding) WriteTo(w io.Writer) (n int64, err error) { - var reminder int64 - if p > bulkPaddingBytesSize { - reminder = int64(p % bulkPaddingBytesSize) - iter := int(p / bulkPaddingBytesSize) - for i := 0; i < iter; i++ { - if _, err = w.Write(bulkPadding); err != nil { - return - } - n += bulkPaddingBytesSize - } - } else { - reminder = int64(p) - } - - paddingBytes := make([]byte, reminder) - _, err = w.Write(paddingBytes) - n += reminder - return -} - -// newWriter instantiates a new CAR v2 writer. -func newWriter(ctx context.Context, ng format.NodeGetter, roots []cid.Cid) *writer { - return &writer{ - NodeGetter: ng, - ctx: ctx, - roots: roots, - encodedCarV1: new(bytes.Buffer), - } -} - -// WriteTo writes the given root CIDs according to CAR v2 specification. -func (w *writer) WriteTo(writer io.Writer) (n int64, err error) { - _, err = writer.Write(Pragma) - if err != nil { - return - } - n += int64(PragmaSize) - // We read the entire car into memory because GenerateIndex takes a reader. - // TODO Future PRs will make this more efficient by exposing necessary interfaces in index pacakge so that - // this can be done in an streaming manner. - if err = carv1.WriteCar(w.ctx, w.NodeGetter, w.roots, w.encodedCarV1); err != nil { - return - } - carV1Len := w.encodedCarV1.Len() - - wn, err := w.writeHeader(writer, carV1Len) - if err != nil { - return - } - n += wn - - wn, err = padding(w.CarV1Padding).WriteTo(writer) - if err != nil { - return - } - n += wn - - carV1Bytes := w.encodedCarV1.Bytes() - wwn, err := writer.Write(carV1Bytes) - if err != nil { - return - } - n += int64(wwn) - - wn, err = padding(w.IndexPadding).WriteTo(writer) - if err != nil { - return - } - n += wn - - wn, err = w.writeIndex(writer, carV1Bytes) - if err == nil { - n += wn - } - return -} - -func (w *writer) writeHeader(writer io.Writer, carV1Len int) (int64, error) { - header := NewHeader(uint64(carV1Len)). - WithCarV1Padding(w.CarV1Padding). - WithIndexPadding(w.IndexPadding) - return header.WriteTo(writer) -} - -func (w *writer) writeIndex(writer io.Writer, carV1 []byte) (int64, error) { - // TODO avoid recopying the bytes by refactoring index once it is integrated here. - // Right now we copy the bytes since index takes a writer. - // Consider refactoring index to make this process more efficient. - // We should avoid reading the entire car into memory since it can be large. - reader := bytes.NewReader(carV1) - idx, err := GenerateIndex(reader) - if err != nil { - return 0, err - } - // FIXME refactor index to expose the number of bytes written. - return 0, index.WriteTo(idx, writer) -} - // WrapV1File is a wrapper around WrapV1 that takes filesystem paths. // The source path is assumed to exist, and the destination path is overwritten. // Note that the destination path might still be created even if an error diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index 633fc46260..ff8f3bff2c 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -1,10 +1,17 @@ package car import ( - "bytes" "context" + "io" + "io/ioutil" + "os" + "path/filepath" "testing" + "github.com/ipld/go-car/v2/index" + "github.com/ipld/go-car/v2/internal/carv1" + "github.com/stretchr/testify/require" + "github.com/ipfs/go-cid" format "github.com/ipfs/go-ipld-format" "github.com/ipfs/go-merkledag" @@ -12,56 +19,44 @@ import ( "github.com/stretchr/testify/assert" ) -func TestPadding_WriteTo(t *testing.T) { - tests := []struct { - name string - padding padding - wantBytes []byte - wantN int64 - wantErr bool - }{ - { - "ZeroPaddingIsNoBytes", - padding(0), - nil, - 0, - false, - }, - { - "NonZeroPaddingIsCorrespondingZeroValueBytes", - padding(3), - []byte{0x00, 0x00, 0x00}, - 3, - false, - }, - { - "PaddingLargerThanTheBulkPaddingSizeIsCorrespondingZeroValueBytes", - padding(1025), - make([]byte, 1025), - 1025, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - w := &bytes.Buffer{} - gotN, gotErr := tt.padding.WriteTo(w) - if tt.wantErr { - assert.Error(t, gotErr) - return - } - gotBytes := w.Bytes() - assert.Equal(t, tt.wantN, gotN) - assert.Equal(t, tt.wantBytes, gotBytes) - }) - } -} +func TestWrapV1(t *testing.T) { + // Produce a CARv1 file to test wrapping with. + dagSvc := dstest.Mock() + src := filepath.Join(t.TempDir(), "unwrapped-test-v1.car") + sf, err := os.Create(src) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, sf.Close()) }) + require.NoError(t, carv1.WriteCar(context.Background(), dagSvc, generateRootCid(t, dagSvc), sf)) + + // Wrap the test CARv1 file + dest := filepath.Join(t.TempDir(), "wrapped-test-v1.car") + df, err := os.Create(dest) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, df.Close()) }) + _, err = sf.Seek(0, io.SeekStart) + require.NoError(t, err) + require.NoError(t, WrapV1(sf, df)) + + // Assert wrapped file is valid CARv2 with CARv1 data payload matching the original CARv1 file. + subject, err := OpenReader(dest) + t.Cleanup(func() { require.NoError(t, subject.Close()) }) + require.NoError(t, err) + + // Assert CARv1 data payloads are identical. + _, err = sf.Seek(0, io.SeekStart) + require.NoError(t, err) + wantPayload, err := ioutil.ReadAll(sf) + require.NoError(t, err) + gotPayload, err := ioutil.ReadAll(subject.CarV1Reader()) + require.NoError(t, err) + require.Equal(t, wantPayload, gotPayload) -func TestNewWriter(t *testing.T) { - dagService := dstest.Mock() - wantRoots := generateRootCid(t, dagService) - writer := newWriter(context.Background(), dagService, wantRoots) - assert.Equal(t, wantRoots, writer.roots) + // Assert embedded index in CARv2 is same as index generated from the original CARv1. + wantIdx, err := GenerateIndexFromFile(src) + require.NoError(t, err) + gotIdx, err := index.ReadFrom(subject.IndexReader()) + require.NoError(t, err) + require.Equal(t, wantIdx, gotIdx) } func generateRootCid(t *testing.T, adder format.NodeAdder) []cid.Cid { From dd5c6d89a665c02ef5e3162d27b2f222ff0cf2d3 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" <301855+masih@users.noreply.github.com> Date: Fri, 16 Jul 2021 14:23:05 +0100 Subject: [PATCH 127/291] Rename `CarV1` with `Data` in API to be consistent with spec Rename terminology to match what the spec uses to describe the inner CARv1 payload, i.e. Data payload. Update docs to use CARv1 and CARv2 consistently. Fix typo in Options API. See: - https://ipld.io/specs/transport/car/carv2/ This commit was moved from ipld/go-car@aa62719de471679b027e9e0f18d420f4692aa3f9 --- ipld/car/v2/blockstore/doc.go | 10 +-- ipld/car/v2/blockstore/readonly.go | 20 +++--- ipld/car/v2/blockstore/readonly_test.go | 2 +- ipld/car/v2/blockstore/readwrite.go | 78 ++++++++++++------------ ipld/car/v2/blockstore/readwrite_test.go | 32 +++++----- ipld/car/v2/car.go | 64 +++++++++---------- ipld/car/v2/car_test.go | 26 ++++---- ipld/car/v2/doc.go | 4 +- ipld/car/v2/example_test.go | 2 +- ipld/car/v2/index/doc.go | 4 +- ipld/car/v2/index_gen.go | 18 +++--- ipld/car/v2/internal/carv1/doc.go | 2 +- ipld/car/v2/options.go | 18 +++--- ipld/car/v2/reader.go | 35 +++++------ ipld/car/v2/writer.go | 6 +- ipld/car/v2/writer_test.go | 2 +- 16 files changed, 161 insertions(+), 162 deletions(-) diff --git a/ipld/car/v2/blockstore/doc.go b/ipld/car/v2/blockstore/doc.go index 7210d742b9..180dc7292f 100644 --- a/ipld/car/v2/blockstore/doc.go +++ b/ipld/car/v2/blockstore/doc.go @@ -2,11 +2,11 @@ // This package provides two flavours of blockstore: ReadOnly and ReadWrite. // // The ReadOnly blockstore provides a read-only random access from a given data payload either in -// unindexed v1 format or indexed/unindexed v2 format: -// * ReadOnly.NewReadOnly can be used to instantiate a new read-only blockstore for a given CAR v1 -// or CAR v2 data payload with an optional index override. -// * ReadOnly.OpenReadOnly can be used to instantiate a new read-only blockstore for a given CAR v1 -// or CAR v2 file with automatic index generation if the index is not present. +// unindexed CARv1 format or indexed/unindexed v2 format: +// * ReadOnly.NewReadOnly can be used to instantiate a new read-only blockstore for a given CARv1 +// or CARv2 data payload with an optional index override. +// * ReadOnly.OpenReadOnly can be used to instantiate a new read-only blockstore for a given CARv1 +// or CARv2 file with automatic index generation if the index is not present. // // The ReadWrite blockstore allows writing and reading of the blocks concurrently. The user of this // blockstore is responsible for calling ReadWrite.Finalize when finished writing blocks. diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 5b9c8535dd..bbbba8f4e1 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -34,9 +34,9 @@ type ReadOnly struct { // For simplicity, the entirety of the blockstore methods grab the mutex. mu sync.RWMutex - // The backing containing the CAR in v1 format. + // The backing containing the data payload in CARv1 format. backing io.ReaderAt - // The CAR v1 content index. + // The CARv1 content index. idx index.Index // If we called carv2.NewReaderMmap, remember to close it too. @@ -54,8 +54,6 @@ type ReadOnly struct { // • Get, Has, and HasSize will only return a block // only if the entire CID is present in the CAR file. // -// • DeleteBlock will delete a block only when the entire CID matches. -// // • AllKeysChan will return the original whole CIDs, instead of with their // multicodec set to "raw" to just provide multihashes. // @@ -73,12 +71,12 @@ func UseWholeCIDs(enable bool) carv2.ReadOption { } // NewReadOnly creates a new ReadOnly blockstore from the backing with a optional index as idx. -// This function accepts both CAR v1 and v2 backing. +// This function accepts both CARv1 and CARv2 backing. // The blockstore is instantiated with the given index if it is not nil. // // Otherwise: -// * For a CAR v1 backing an index is generated. -// * For a CAR v2 backing an index is only generated if Header.HasIndex returns false. +// * For a CARv1 backing an index is generated. +// * For a CARv2 backing an index is only generated if Header.HasIndex returns false. // // There is no need to call ReadOnly.Close on instances returned by this function. func NewReadOnly(backing io.ReaderAt, idx index.Index, opts ...carv2.ReadOption) (*ReadOnly, error) { @@ -112,11 +110,11 @@ func NewReadOnly(backing io.ReaderAt, idx index.Index, opts ...carv2.ReadOption) if err != nil { return nil, err } - } else if idx, err = generateIndex(v2r.CarV1Reader(), opts...); err != nil { + } else if idx, err = generateIndex(v2r.DataReader(), opts...); err != nil { return nil, err } } - b.backing = v2r.CarV1Reader() + b.backing = v2r.DataReader() b.idx = idx return b, nil default: @@ -169,7 +167,7 @@ func (b *ReadOnly) readBlock(idx int64) (cid.Cid, []byte, error) { return bcid, data, err } -// DeleteBlock is unsupported and always returns an error. +// DeleteBlock is unsupported and always panics. func (b *ReadOnly) DeleteBlock(_ cid.Cid) error { panic("called write method on a read-only blockstore") } @@ -289,7 +287,7 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { // Null padding; by default it's an error. if length == 0 { - if b.ropts.ZeroLegthSectionAsEOF { + if b.ropts.ZeroLengthSectionAsEOF { break } else { return // TODO: log this error diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 10f9804cd9..3d75fd96da 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -131,7 +131,7 @@ func newReaderFromV2File(t *testing.T, carv2Path string) *carv1.CarReader { t.Cleanup(func() { f.Close() }) v2r, err := carv2.NewReader(f) require.NoError(t, err) - v1r, err := carv1.NewCarReader(v2r.CarV1Reader()) + v1r, err := carv1.NewCarReader(v2r.DataReader()) require.NoError(t, err) return v1r } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index a1c7fd84f7..693e52f913 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -23,7 +23,7 @@ import ( var _ blockstore.Blockstore = (*ReadWrite)(nil) -// ReadWrite implements a blockstore that stores blocks in CAR v2 format. +// ReadWrite implements a blockstore that stores blocks in CARv2 format. // Blocks put into the blockstore can be read back once they are successfully written. // This implementation is preferable for a write-heavy workload. // The blocks are written immediately on Put and PutAll calls, while the index is stored in memory @@ -33,8 +33,8 @@ var _ blockstore.Blockstore = (*ReadWrite)(nil) // Upon calling Finalize header is finalized and index is written out. // Once finalized, all read and write calls to this blockstore will result in panics. type ReadWrite struct { - f *os.File - carV1Writer *internalio.OffsetWriteSeeker + f *os.File + dataWriter *internalio.OffsetWriteSeeker ReadOnly idx *insertionIndex header carv2.Header @@ -57,13 +57,13 @@ func AllowDuplicatePuts(allow bool) carv2.WriteOption { // OpenReadWrite creates a new ReadWrite at the given path with a provided set of root CIDs and options. // // ReadWrite.Finalize must be called once putting and reading blocks are no longer needed. -// Upon calling ReadWrite.Finalize the CAR v2 header and index are written out onto the file and the +// Upon calling ReadWrite.Finalize the CARv2 header and index are written out onto the file and the // backing file is closed. Once finalized, all read and write calls to this blockstore will result // in panics. Note, ReadWrite.Finalize must be called on an open instance regardless of whether any // blocks were put or not. // // If a file at given path does not exist, the instantiation will write car.Pragma and data payload -// header (i.e. the inner CAR v1 header) onto the file before returning. +// header (i.e. the inner CARv1 header) onto the file before returning. // // When the given path already exists, the blockstore will attempt to resume from it. // On resumption the existing data sections in file are re-indexed, allowing the caller to continue @@ -73,15 +73,15 @@ func AllowDuplicatePuts(allow bool) carv2.WriteOption { // Resumption only works on files that were created by a previous instance of a ReadWrite // blockstore. This means a file created as a result of a successful call to OpenReadWrite can be // resumed from as long as write operations such as ReadWrite.Put, ReadWrite.PutMany returned -// successfully. On resumption the roots argument and WithCarV1Padding option must match the +// successfully. On resumption the roots argument and WithDataPadding option must match the // previous instantiation of ReadWrite blockstore that created the file. More explicitly, the file // resuming from must: -// 1. start with a complete CAR v2 car.Pragma. -// 2. contain a complete CAR v1 data header with root CIDs matching the CIDs passed to the -// constructor, starting at offset optionally padded by WithCarV1Padding, followed by zero or +// 1. start with a complete CARv2 car.Pragma. +// 2. contain a complete CARv1 data header with root CIDs matching the CIDs passed to the +// constructor, starting at offset optionally padded by WithDataPadding, followed by zero or // more complete data sections. If any corrupt data sections are present the resumption will fail. -// Note, if set previously, the blockstore must use the same WithCarV1Padding option as before, -// since this option is used to locate the CAR v1 data payload. +// Note, if set previously, the blockstore must use the same WithDataPadding option as before, +// since this option is used to locate the CARv1 data payload. // // Note, resumption should be used with WithCidDeduplication, so that blocks that are successfully // written into the file are not re-written. Unless, the user explicitly wants duplicate blocks. @@ -124,15 +124,15 @@ func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.ReadWriteOption) opt(&rwbs.wopts) } } - if p := rwbs.wopts.CarV1Padding; p > 0 { - rwbs.header = rwbs.header.WithCarV1Padding(p) + if p := rwbs.wopts.DataPadding; p > 0 { + rwbs.header = rwbs.header.WithDataPadding(p) } if p := rwbs.wopts.IndexPadding; p > 0 { rwbs.header = rwbs.header.WithIndexPadding(p) } - rwbs.carV1Writer = internalio.NewOffsetWriter(rwbs.f, int64(rwbs.header.CarV1Offset)) - v1r := internalio.NewOffsetReadSeeker(rwbs.f, int64(rwbs.header.CarV1Offset)) + rwbs.dataWriter = internalio.NewOffsetWriter(rwbs.f, int64(rwbs.header.DataOffset)) + v1r := internalio.NewOffsetReadSeeker(rwbs.f, int64(rwbs.header.DataOffset)) rwbs.ReadOnly.backing = v1r rwbs.ReadOnly.idx = rwbs.idx rwbs.ReadOnly.carv2Closer = rwbs.f @@ -154,13 +154,13 @@ func (b *ReadWrite) initWithRoots(roots []cid.Cid) error { if _, err := b.f.WriteAt(carv2.Pragma, 0); err != nil { return err } - return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, b.carV1Writer) + return carv1.WriteHeader(&carv1.CarHeader{Roots: roots, Version: 1}, b.dataWriter) } func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { - // On resumption it is expected that the CAR v2 Pragma, and the CAR v1 header is successfully written. + // On resumption it is expected that the CARv2 Pragma, and the CARv1 header is successfully written. // Otherwise we cannot resume from the file. - // Read pragma to assert if b.f is indeed a CAR v2. + // Read pragma to assert if b.f is indeed a CARv2. version, err := carv2.ReadVersion(b.f) if err != nil { // The file is not a valid CAR file and cannot resume from it. @@ -168,36 +168,36 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { return err } if version != 2 { - // The file is not a CAR v2 and we cannot resume from it. + // The file is not a CARv2 and we cannot resume from it. return fmt.Errorf("cannot resume on CAR file with version %v", version) } - // Check if file was finalized by trying to read the CAR v2 header. + // Check if file was finalized by trying to read the CARv2 header. // We check because if finalized the CARv1 reader behaviour needs to be adjusted since - // EOF will not signify end of CAR v1 payload. i.e. index is most likely present. + // EOF will not signify end of CARv1 payload. i.e. index is most likely present. var headerInFile carv2.Header _, err = headerInFile.ReadFrom(internalio.NewOffsetReadSeeker(b.f, carv2.PragmaSize)) // If reading CARv2 header succeeded, and CARv1 offset in header is not zero then the file is // most-likely finalized. Check padding and truncate the file to remove index. // Otherwise, carry on reading the v1 payload at offset determined from b.header. - if err == nil && headerInFile.CarV1Offset != 0 { - if headerInFile.CarV1Offset != b.header.CarV1Offset { - // Assert that the padding on file matches the given WithCarV1Padding option. - wantPadding := headerInFile.CarV1Offset - carv2.PragmaSize - carv2.HeaderSize - gotPadding := b.header.CarV1Offset - carv2.PragmaSize - carv2.HeaderSize + if err == nil && headerInFile.DataOffset != 0 { + if headerInFile.DataOffset != b.header.DataOffset { + // Assert that the padding on file matches the given WithDataPadding option. + wantPadding := headerInFile.DataOffset - carv2.PragmaSize - carv2.HeaderSize + gotPadding := b.header.DataOffset - carv2.PragmaSize - carv2.HeaderSize return fmt.Errorf( "cannot resume from file with mismatched CARv1 offset; "+ - "`WithCarV1Padding` option must match the padding on file. "+ + "`WithDataPadding` option must match the padding on file. "+ "Expected padding value of %v but got %v", wantPadding, gotPadding, ) - } else if headerInFile.CarV1Size != 0 { + } else if headerInFile.DataSize != 0 { // If header in file contains the size of car v1, then the index is most likely present. // Since we will need to re-generate the index, as the one in file is flattened, truncate // the file so that the Readonly.backing has the right set of bytes to deal with. // This effectively means resuming from a finalized file will wipe its index even if there // are no blocks put unless the user calls finalize. - if err := b.f.Truncate(int64(headerInFile.CarV1Offset + headerInFile.CarV1Size)); err != nil { + if err := b.f.Truncate(int64(headerInFile.DataOffset + headerInFile.DataSize)); err != nil { return err } } else { @@ -214,11 +214,11 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { } } - // Use the given CAR v1 padding to instantiate the CAR v1 reader on file. + // Use the given CARv1 padding to instantiate the CARv1 reader on file. v1r := internalio.NewOffsetReadSeeker(b.ReadOnly.backing, 0) header, err := carv1.ReadHeader(v1r) if err != nil { - // Cannot read the CAR v1 header; the file is most likely corrupt. + // Cannot read the CARv1 header; the file is most likely corrupt. return fmt.Errorf("error reading car header: %w", err) } if !header.Matches(carv1.CarHeader{Roots: roots, Version: 1}) { @@ -255,7 +255,7 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { // Null padding; by default it's an error. if length == 0 { - if b.ropts.ZeroLegthSectionAsEOF { + if b.ropts.ZeroLengthSectionAsEOF { break } else { return fmt.Errorf("carv1 null padding not allowed by default; see WithZeroLegthSectionAsEOF") @@ -276,7 +276,7 @@ func (b *ReadWrite) resumeWithRoots(roots []cid.Cid) error { } } // Seek to the end of last skipped block where the writer should resume writing. - _, err = b.carV1Writer.Seek(sectionOffset, io.SeekStart) + _, err = b.dataWriter.Seek(sectionOffset, io.SeekStart) return err } @@ -286,7 +286,7 @@ func (b *ReadWrite) unfinalize() error { } func (b *ReadWrite) panicIfFinalized() { - if b.header.CarV1Size != 0 { + if b.header.DataSize != 0 { panic("must not use a read-write blockstore after finalizing") } } @@ -320,8 +320,8 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { } } - n := uint64(b.carV1Writer.Position()) - if err := util.LdWrite(b.carV1Writer, c.Bytes(), bl.RawData()); err != nil { + n := uint64(b.dataWriter.Position()) + if err := util.LdWrite(b.dataWriter, c.Bytes(), bl.RawData()); err != nil { return err } b.idx.insertNoReplace(c, n) @@ -329,11 +329,11 @@ func (b *ReadWrite) PutMany(blks []blocks.Block) error { return nil } -// Finalize finalizes this blockstore by writing the CAR v2 header, along with flattened index +// Finalize finalizes this blockstore by writing the CARv2 header, along with flattened index // for more efficient subsequent read. // After this call, this blockstore can no longer be used for read or write. func (b *ReadWrite) Finalize() error { - if b.header.CarV1Size != 0 { + if b.header.DataSize != 0 { // Allow duplicate Finalize calls, just like Close. // Still error, just like ReadOnly.Close; it should be discarded. return fmt.Errorf("called Finalize twice") @@ -342,7 +342,7 @@ func (b *ReadWrite) Finalize() error { b.mu.Lock() defer b.mu.Unlock() // TODO check if add index option is set and don't write the index then set index offset to zero. - b.header = b.header.WithCarV1Size(uint64(b.carV1Writer.Position())) + b.header = b.header.WithDataSize(uint64(b.dataWriter.Position())) defer b.Close() // TODO if index not needed don't bother flattening it. diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 3a8099fbd2..6612268f14 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -280,7 +280,7 @@ func TestBlockstoreNullPadding(t *testing.T) { // A sample null-padded CARv1 file. paddedV1 = append(paddedV1, make([]byte, 2048)...) - rbs, err := blockstore.NewReadOnly(bufferReaderAt(paddedV1), nil, carv2.ZeroLegthSectionAsEOF) + rbs, err := blockstore.NewReadOnly(bufferReaderAt(paddedV1), nil, carv2.ZeroLengthSectionAsEOF) require.NoError(t, err) roots, err := rbs.Roots() @@ -312,7 +312,7 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, err) path := filepath.Join(t.TempDir(), "readwrite-resume.car") - // Create an incomplete CAR v2 file with no blocks put. + // Create an incomplete CARv2 file with no blocks put. subject, err := blockstore.OpenReadWrite(path, r.Header.Roots) require.NoError(t, err) @@ -330,7 +330,7 @@ func TestBlockstoreResumption(t *testing.T) { // 30% chance of subject failing; more concretely: re-instantiating blockstore with the same // file without calling Finalize. The higher this percentage the slower the test runs - // considering the number of blocks in the original CAR v1 test payload. + // considering the number of blocks in the original CARv1 test payload. resume := rng.Float32() <= 0.3 // If testing resume case, then flip a coin to decide whether to finalize before blockstore // re-instantiation or not. Note, both cases should work for resumption since we do not @@ -376,12 +376,12 @@ func TestBlockstoreResumption(t *testing.T) { } require.NoError(t, subject.Close()) - // Finalize the blockstore to complete partially written CAR v2 file. + // Finalize the blockstore to complete partially written CARv2 file. subject, err = blockstore.OpenReadWrite(path, r.Header.Roots) require.NoError(t, err) require.NoError(t, subject.Finalize()) - // Assert resumed from file is a valid CAR v2 with index. + // Assert resumed from file is a valid CARv2 with index. v2f, err := os.Open(path) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, v2f.Close()) }) @@ -389,13 +389,13 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, err) require.True(t, v2r.Header.HasIndex()) - // Assert CAR v1 payload in file matches the original CAR v1 payload. + // Assert CARv1 payload in file matches the original CARv1 payload. _, err = v1f.Seek(0, io.SeekStart) require.NoError(t, err) wantPayloadReader, err := carv1.NewCarReader(v1f) require.NoError(t, err) - gotPayloadReader, err := carv1.NewCarReader(v2r.CarV1Reader()) + gotPayloadReader, err := carv1.NewCarReader(v2r.DataReader()) require.NoError(t, err) require.Equal(t, wantPayloadReader.Header, gotPayloadReader.Header) @@ -411,7 +411,7 @@ func TestBlockstoreResumption(t *testing.T) { require.Equal(t, wantNextBlock, gotNextBlock) } - // Assert index in resumed from file is identical to index generated directly from original CAR v1 payload. + // Assert index in resumed from file is identical to index generated directly from original CARv1 payload. _, err = v1f.Seek(0, io.SeekStart) require.NoError(t, err) gotIdx, err := index.ReadFrom(v2r.IndexReader()) @@ -423,7 +423,7 @@ func TestBlockstoreResumption(t *testing.T) { func TestBlockstoreResumptionIsSupportedOnFinalizedFile(t *testing.T) { path := filepath.Join(t.TempDir(), "readwrite-resume-finalized.car") - // Create an incomplete CAR v2 file with no blocks put. + // Create an incomplete CARv2 file with no blocks put. subject, err := blockstore.OpenReadWrite(path, []cid.Cid{}) require.NoError(t, err) require.NoError(t, subject.Finalize()) @@ -487,7 +487,7 @@ func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { subject, err := blockstore.OpenReadWrite( path, WantRoots, - carv2.UseCarV1Padding(wantCarV1Padding), + carv2.UseDataPadding(wantCarV1Padding), carv2.UseIndexPadding(wantIndexPadding)) require.NoError(t, err) t.Cleanup(func() { subject.Close() }) @@ -500,8 +500,8 @@ func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { t.Cleanup(func() { gotCarV2.Close() }) require.NoError(t, err) wantCarV1Offset := carv2.PragmaSize + carv2.HeaderSize + wantCarV1Padding - wantIndexOffset := wantCarV1Offset + gotCarV2.Header.CarV1Size + wantIndexPadding - require.Equal(t, wantCarV1Offset, gotCarV2.Header.CarV1Offset) + wantIndexOffset := wantCarV1Offset + gotCarV2.Header.DataSize + wantIndexPadding + require.Equal(t, wantCarV1Offset, gotCarV2.Header.DataOffset) require.Equal(t, wantIndexOffset, gotCarV2.Header.IndexOffset) require.NoError(t, gotCarV2.Close()) @@ -510,7 +510,7 @@ func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { t.Cleanup(func() { f.Close() }) // Assert reading CARv1 directly at offset and size is as expected. - gotCarV1, err := carv1.NewCarReader(io.NewSectionReader(f, int64(wantCarV1Offset), int64(gotCarV2.Header.CarV1Size))) + gotCarV1, err := carv1.NewCarReader(io.NewSectionReader(f, int64(wantCarV1Offset), int64(gotCarV2.Header.DataSize))) require.NoError(t, err) require.Equal(t, WantRoots, gotCarV1.Header.Roots) gotOneBlock, err := gotCarV1.Next() @@ -548,7 +548,7 @@ func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing. subject, err := blockstore.OpenReadWrite( path, WantRoots, - carv2.UseCarV1Padding(1413)) + carv2.UseDataPadding(1413)) require.NoError(t, err) t.Cleanup(func() { subject.Close() }) require.NoError(t, subject.Put(oneTestBlockWithCidV1)) @@ -557,9 +557,9 @@ func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing. resumingSubject, err := blockstore.OpenReadWrite( path, WantRoots, - carv2.UseCarV1Padding(1314)) + carv2.UseDataPadding(1314)) require.EqualError(t, err, "cannot resume from file with mismatched CARv1 offset; "+ - "`WithCarV1Padding` option must match the padding on file. "+ + "`WithDataPadding` option must match the padding on file. "+ "Expected padding value of 1413 but got 1314") require.Nil(t, resumingSubject) } diff --git a/ipld/car/v2/car.go b/ipld/car/v2/car.go index 4d8c3f50a9..d12683313b 100644 --- a/ipld/car/v2/car.go +++ b/ipld/car/v2/car.go @@ -6,16 +6,16 @@ import ( ) const ( - // PragmaSize is the size of the CAR v2 pragma in bytes. + // PragmaSize is the size of the CARv2 pragma in bytes. PragmaSize = 11 - // HeaderSize is the fixed size of CAR v2 header in number of bytes. + // HeaderSize is the fixed size of CARv2 header in number of bytes. HeaderSize = 40 - // CharacteristicsSize is the fixed size of Characteristics bitfield within CAR v2 header in number of bytes. + // CharacteristicsSize is the fixed size of Characteristics bitfield within CARv2 header in number of bytes. CharacteristicsSize = 16 ) -// The pragma of a CAR v2, containing the version number.. -// This is a valid CAR v1 header, with version number set to 2. +// The pragma of a CARv2, containing the version number. +// This is a valid CARv1 header, with version number of 2 and no root CIDs. var Pragma = []byte{ 0x0a, // unit(10) 0xa1, // map(1) @@ -25,18 +25,18 @@ var Pragma = []byte{ } type ( - // Header represents the CAR v2 header/pragma. + // Header represents the CARv2 header/pragma. Header struct { - // 128-bit characteristics of this CAR v2 file, such as order, deduplication, etc. Reserved for future use. + // 128-bit characteristics of this CARv2 file, such as order, deduplication, etc. Reserved for future use. Characteristics Characteristics - // The offset from the beginning of the file at which the dump of CAR v1 starts. - CarV1Offset uint64 - // The size of CAR v1 encapsulated in this CAR v2 as bytes. - CarV1Size uint64 - // The offset from the beginning of the file at which the CAR v2 index begins. + // The byte-offset from the beginning of the CARv2 to the first byte of the CARv1 data payload. + DataOffset uint64 + // The byte-length of the CARv1 data payload. + DataSize uint64 + // The byte-offset from the beginning of the CARv2 to the first byte of the index payload. This value may be 0 to indicate the absence of index data. IndexOffset uint64 } - // Characteristics is a bitfield placeholder for capturing the characteristics of a CAR v2 such as order and determinism. + // Characteristics is a bitfield placeholder for capturing the characteristics of a CARv2 such as order and determinism. Characteristics struct { Hi uint64 Lo uint64 @@ -64,37 +64,37 @@ func (c *Characteristics) ReadFrom(r io.Reader) (int64, error) { return n, nil } -// NewHeader instantiates a new CAR v2 header, given the byte length of a CAR v1. -func NewHeader(carV1Size uint64) Header { +// NewHeader instantiates a new CARv2 header, given the data size. +func NewHeader(dataSize uint64) Header { header := Header{ - CarV1Size: carV1Size, + DataSize: dataSize, } - header.CarV1Offset = PragmaSize + HeaderSize - header.IndexOffset = header.CarV1Offset + carV1Size + header.DataOffset = PragmaSize + HeaderSize + header.IndexOffset = header.DataOffset + dataSize return header } -// WithIndexPadding sets the index offset from the beginning of the file for this header and returns the -// header for convenient chained calls. +// WithIndexPadding sets the index offset from the beginning of the file for this header and returns +// the header for convenient chained calls. // The index offset is calculated as the sum of PragmaSize, HeaderSize, -// Header.CarV1Size, and the given padding. +// Header.DataSize, and the given padding. func (h Header) WithIndexPadding(padding uint64) Header { h.IndexOffset = h.IndexOffset + padding return h } -// WithCarV1Padding sets the CAR v1 dump offset from the beginning of the file for this header and returns the -// header for convenient chained calls. -// The CAR v1 offset is calculated as the sum of PragmaSize, HeaderSize and the given padding. +// WithDataPadding sets the data payload byte-offset from the beginning of the file for this header +// and returns the header for convenient chained calls. +// The Data offset is calculated as the sum of PragmaSize, HeaderSize and the given padding. // The call to this function also shifts the Header.IndexOffset forward by the given padding. -func (h Header) WithCarV1Padding(padding uint64) Header { - h.CarV1Offset = PragmaSize + HeaderSize + padding +func (h Header) WithDataPadding(padding uint64) Header { + h.DataOffset = PragmaSize + HeaderSize + padding h.IndexOffset = h.IndexOffset + padding return h } -func (h Header) WithCarV1Size(size uint64) Header { - h.CarV1Size = size +func (h Header) WithDataSize(size uint64) Header { + h.DataSize = size h.IndexOffset = size + h.IndexOffset return h } @@ -112,8 +112,8 @@ func (h Header) WriteTo(w io.Writer) (n int64, err error) { return } buf := make([]byte, 24) - binary.LittleEndian.PutUint64(buf[:8], h.CarV1Offset) - binary.LittleEndian.PutUint64(buf[8:16], h.CarV1Size) + binary.LittleEndian.PutUint64(buf[:8], h.DataOffset) + binary.LittleEndian.PutUint64(buf[8:16], h.DataSize) binary.LittleEndian.PutUint64(buf[16:], h.IndexOffset) written, err := w.Write(buf) n += int64(written) @@ -132,8 +132,8 @@ func (h *Header) ReadFrom(r io.Reader) (int64, error) { if err != nil { return n, err } - h.CarV1Offset = binary.LittleEndian.Uint64(buf[:8]) - h.CarV1Size = binary.LittleEndian.Uint64(buf[8:16]) + h.DataOffset = binary.LittleEndian.Uint64(buf[:8]) + h.DataSize = binary.LittleEndian.Uint64(buf[8:16]) h.IndexOffset = binary.LittleEndian.Uint64(buf[16:]) return n, nil } diff --git a/ipld/car/v2/car_test.go b/ipld/car/v2/car_test.go index 83a552ba65..4223a5600e 100644 --- a/ipld/car/v2/car_test.go +++ b/ipld/car/v2/car_test.go @@ -36,11 +36,11 @@ func TestCarV2PragmaLength(t *testing.T) { func TestCarV2PragmaIsValidCarV1Header(t *testing.T) { v1h, err := carv1.ReadHeader(bytes.NewReader(carv2.Pragma)) - assert.NoError(t, err, "cannot decode pragma as CBOR with CAR v1 header structure") + assert.NoError(t, err, "cannot decode pragma as CBOR with CARv1 header structure") assert.Equal(t, &carv1.CarHeader{ Roots: nil, Version: 2, - }, v1h, "CAR v2 pragma must be a valid CAR v1 header") + }, v1h, "CARv2 pragma must be a valid CARv1 header") } func TestHeader_WriteTo(t *testing.T) { @@ -70,8 +70,8 @@ func TestHeader_WriteTo(t *testing.T) { Characteristics: carv2.Characteristics{ Hi: 1001, Lo: 1002, }, - CarV1Offset: 99, - CarV1Size: 100, + DataOffset: 99, + DataSize: 100, IndexOffset: 101, }, []byte{ @@ -94,8 +94,8 @@ func TestHeader_WriteTo(t *testing.T) { } gotWrite := buf.Bytes() assert.Equal(t, tt.wantWrite, gotWrite, "Header.WriteTo() gotWrite = %v, wantWrite %v", gotWrite, tt.wantWrite) - assert.EqualValues(t, carv2.HeaderSize, uint64(len(gotWrite)), "WriteTo() CAR v2 header length must always be %v bytes long", carv2.HeaderSize) - assert.EqualValues(t, carv2.HeaderSize, uint64(written), "WriteTo() CAR v2 header byte count must always be %v bytes long", carv2.HeaderSize) + assert.EqualValues(t, carv2.HeaderSize, uint64(len(gotWrite)), "WriteTo() CARv2 header length must always be %v bytes long", carv2.HeaderSize) + assert.EqualValues(t, carv2.HeaderSize, uint64(written), "WriteTo() CARv2 header byte count must always be %v bytes long", carv2.HeaderSize) }) } } @@ -135,8 +135,8 @@ func TestHeader_ReadFrom(t *testing.T) { Characteristics: carv2.Characteristics{ Hi: 1001, Lo: 1002, }, - CarV1Offset: 99, - CarV1Size: 100, + DataOffset: 99, + DataSize: 100, IndexOffset: 101, }, false, @@ -168,13 +168,13 @@ func TestHeader_WithPadding(t *testing.T) { }, { "WhenOnlyPaddingCarV1BothOffsetsShift", - carv2.NewHeader(123).WithCarV1Padding(3), + carv2.NewHeader(123).WithDataPadding(3), carv2.PragmaSize + carv2.HeaderSize + 3, carv2.PragmaSize + carv2.HeaderSize + 3 + 123, }, { "WhenPaddingBothCarV1AndIndexBothOffsetsShiftWithAdditionalIndexShift", - carv2.NewHeader(123).WithCarV1Padding(3).WithIndexPadding(7), + carv2.NewHeader(123).WithDataPadding(3).WithIndexPadding(7), carv2.PragmaSize + carv2.HeaderSize + 3, carv2.PragmaSize + carv2.HeaderSize + 3 + 123 + 7, }, @@ -182,7 +182,7 @@ func TestHeader_WithPadding(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.EqualValues(t, tt.wantCarV1Offset, tt.subject.CarV1Offset) + assert.EqualValues(t, tt.wantCarV1Offset, tt.subject.DataOffset) assert.EqualValues(t, tt.wantIndexOffset, tt.subject.IndexOffset) }) } @@ -192,8 +192,8 @@ func TestNewHeaderHasExpectedValues(t *testing.T) { wantCarV1Len := uint64(1413) want := carv2.Header{ Characteristics: carv2.Characteristics{}, - CarV1Offset: carv2.PragmaSize + carv2.HeaderSize, - CarV1Size: wantCarV1Len, + DataOffset: carv2.PragmaSize + carv2.HeaderSize, + DataSize: wantCarV1Len, IndexOffset: carv2.PragmaSize + carv2.HeaderSize + wantCarV1Len, } got := carv2.NewHeader(wantCarV1Len) diff --git a/ipld/car/v2/doc.go b/ipld/car/v2/doc.go index 5b210211bf..2029d7cf38 100644 --- a/ipld/car/v2/doc.go +++ b/ipld/car/v2/doc.go @@ -1,3 +1,3 @@ -// Package car represents the CAR v2 implementation. -// TODO add CAR v2 byte structure here. +// Package car represents the CARv2 implementation. +// TODO add CARv2 byte structure here. package car diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go index 4e74466289..6944f2fa7c 100644 --- a/ipld/car/v2/example_test.go +++ b/ipld/car/v2/example_test.go @@ -38,7 +38,7 @@ func ExampleWrapV1File() { if err != nil { panic(err) } - inner, err := ioutil.ReadAll(cr.CarV1Reader()) + inner, err := ioutil.ReadAll(cr.DataReader()) if err != nil { panic(err) } diff --git a/ipld/car/v2/index/doc.go b/ipld/car/v2/index/doc.go index 4c7beb1f8b..41b860216c 100644 --- a/ipld/car/v2/index/doc.go +++ b/ipld/car/v2/index/doc.go @@ -1,5 +1,5 @@ -// package index provides indexing functionality for CAR v1 data payload represented as a mapping of -// CID to offset. This can then be used to implement random access over a CAR v1. +// package index provides indexing functionality for CARv1 data payload represented as a mapping of +// CID to offset. This can then be used to implement random access over a CARv1. // // Index can be written or read using the following static functions: index.WriteTo and // index.ReadFrom. diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 4fa9ac1df2..5a60fb7111 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -63,10 +63,10 @@ func GenerateIndex(v1r io.Reader, opts ...ReadOption) (index.Index, error) { // Null padding; by default it's an error. if sectionLen == 0 { - if o.ZeroLegthSectionAsEOF { + if o.ZeroLengthSectionAsEOF { break } else { - return nil, fmt.Errorf("carv1 null padding not allowed by default; see ZeroLegthSectionAsEOF") + return nil, fmt.Errorf("carv1 null padding not allowed by default; see ZeroLengthSectionAsEOF") } } @@ -103,10 +103,10 @@ func GenerateIndexFromFile(path string) (index.Index, error) { return GenerateIndex(f) } -// ReadOrGenerateIndex accepts both CAR v1 and v2 format, and reads or generates an index for it. -// When the given reader is in CAR v1 format an index is always generated. -// For a payload in CAR v2 format, an index is only generated if Header.HasIndex returns false. -// An error is returned for all other formats, i.e. versions other than 1 or 2. +// ReadOrGenerateIndex accepts both CARv1 and CARv2 formats, and reads or generates an index for it. +// When the given reader is in CARv1 format an index is always generated. +// For a payload in CARv2 format, an index is only generated if Header.HasIndex returns false. +// An error is returned for all other formats, i.e. pragma with versions other than 1 or 2. // // Note, the returned index lives entirely in memory and will not depend on the // given reader to fulfill index lookup. @@ -126,7 +126,7 @@ func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { // Simply generate the index, since there can't be a pre-existing one. return GenerateIndex(rs) case 2: - // Read CAR v2 format + // Read CARv2 format v2r, err := NewReader(internalio.ToReaderAt(rs)) if err != nil { return nil, err @@ -135,8 +135,8 @@ func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { if v2r.Header.HasIndex() { return index.ReadFrom(v2r.IndexReader()) } - // Otherwise, generate index from CAR v1 payload wrapped within CAR v2 format. - return GenerateIndex(v2r.CarV1Reader()) + // Otherwise, generate index from CARv1 payload wrapped within CARv2 format. + return GenerateIndex(v2r.DataReader()) default: return nil, fmt.Errorf("unknown version %v", version) } diff --git a/ipld/car/v2/internal/carv1/doc.go b/ipld/car/v2/internal/carv1/doc.go index a13ffdfc2a..821ca2f0aa 100644 --- a/ipld/car/v2/internal/carv1/doc.go +++ b/ipld/car/v2/internal/carv1/doc.go @@ -1,2 +1,2 @@ -// Forked from CAR v1 to avoid dependency to ipld-prime 0.9.0 due to outstanding upgrades in filecoin. +// Forked from CARv1 to avoid dependency to ipld-prime 0.9.0 due to outstanding upgrades in filecoin. package carv1 diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index ad859d1a0a..89044a761b 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -6,7 +6,7 @@ package car // This type should not be used directly by end users; it's only exposed as a // side effect of ReadOption. type ReadOptions struct { - ZeroLegthSectionAsEOF bool + ZeroLengthSectionAsEOF bool BlockstoreUseWholeCIDs bool } @@ -24,7 +24,7 @@ var _ ReadWriteOption = ReadOption(nil) // This type should not be used directly by end users; it's only exposed as a // side effect of WriteOption. type WriteOptions struct { - CarV1Padding uint64 + DataPadding uint64 IndexPadding uint64 BlockstoreAllowDuplicatePuts bool @@ -42,19 +42,19 @@ type ReadWriteOption interface { readWriteOption() } -// ZeroLegthSectionAsEOF is a read option which allows a CARv1 decoder to treat +// ZeroLengthSectionAsEOF is a read option which allows a CARv1 decoder to treat // a zero-length section as the end of the input CAR file. For example, this can // be useful to allow "null padding" after a CARv1 without knowing where the // padding begins. -func ZeroLegthSectionAsEOF(o *ReadOptions) { - o.ZeroLegthSectionAsEOF = true +func ZeroLengthSectionAsEOF(o *ReadOptions) { + o.ZeroLengthSectionAsEOF = true } -// UseCarV1Padding is a write option which sets the padding to be added between -// CAR v2 header and its data payload on Finalize. -func UseCarV1Padding(p uint64) WriteOption { +// UseDataPadding is a write option which sets the padding to be added between +// CARv2 header and its data payload on Finalize. +func UseDataPadding(p uint64) WriteOption { return func(o *WriteOptions) { - o.CarV1Padding = p + o.DataPadding = p } } diff --git a/ipld/car/v2/reader.go b/ipld/car/v2/reader.go index f845a81ce7..98d3715d37 100644 --- a/ipld/car/v2/reader.go +++ b/ipld/car/v2/reader.go @@ -11,12 +11,12 @@ import ( "golang.org/x/exp/mmap" ) -// Reader represents a reader of CAR v2. +// Reader represents a reader of CARv2. type Reader struct { - Header Header - r io.ReaderAt - roots []cid.Cid - carv2Closer io.Closer + Header Header + r io.ReaderAt + roots []cid.Cid + closer io.Closer } // OpenReader is a wrapper for NewReader which opens the file at path. @@ -31,13 +31,13 @@ func OpenReader(path string, opts ...ReadOption) (*Reader, error) { return nil, err } - r.carv2Closer = f + r.closer = f return r, nil } -// NewReader constructs a new reader that reads CAR v2 from the given r. +// NewReader constructs a new reader that reads CARv2 from the given r. // Upon instantiation, the reader inspects the payload by reading the pragma and will return -// an error if the pragma does not represent a CAR v2. +// an error if the pragma does not represent a CARv2. func NewReader(r io.ReaderAt, opts ...ReadOption) (*Reader, error) { cr := &Reader{ r: r, @@ -63,12 +63,13 @@ func (r *Reader) requireVersion2() (err error) { return } -// Roots returns the root CIDs of this CAR +// Roots returns the root CIDs. +// The root CIDs are extracted lazily from the data payload header. func (r *Reader) Roots() ([]cid.Cid, error) { if r.roots != nil { return r.roots, nil } - header, err := carv1.ReadHeader(r.CarV1Reader()) + header, err := carv1.ReadHeader(r.DataReader()) if err != nil { return nil, err } @@ -91,26 +92,26 @@ type SectionReader interface { io.ReaderAt } -// CarV1Reader provides a reader containing the CAR v1 section encapsulated in this CAR v2. -func (r *Reader) CarV1Reader() SectionReader { - return io.NewSectionReader(r.r, int64(r.Header.CarV1Offset), int64(r.Header.CarV1Size)) +// DataReader provides a reader containing the data payload in CARv1 format. +func (r *Reader) DataReader() SectionReader { + return io.NewSectionReader(r.r, int64(r.Header.DataOffset), int64(r.Header.DataSize)) } -// IndexReader provides an io.Reader containing the index of this CAR v2. +// IndexReader provides an io.Reader containing the index for the data payload. func (r *Reader) IndexReader() io.Reader { return internalio.NewOffsetReadSeeker(r.r, int64(r.Header.IndexOffset)) } // Close closes the underlying reader if it was opened by OpenReader. func (r *Reader) Close() error { - if r.carv2Closer != nil { - return r.carv2Closer.Close() + if r.closer != nil { + return r.closer.Close() } return nil } // ReadVersion reads the version from the pragma. -// This function accepts both CAR v1 and v2 payloads. +// This function accepts both CARv1 and CARv2 payloads. func ReadVersion(r io.Reader) (version uint64, err error) { // TODO if the user provides a reader that sufficiently satisfies what carv1.ReadHeader is asking then use that instead of wrapping every time. header, err := carv1.ReadHeader(r) diff --git a/ipld/car/v2/writer.go b/ipld/car/v2/writer.go index fa94913e9a..40004648e0 100644 --- a/ipld/car/v2/writer.go +++ b/ipld/car/v2/writer.go @@ -79,11 +79,11 @@ func WrapV1(src io.ReadSeeker, dst io.Writer) error { return nil } -// AttachIndex attaches a given index to an existing car v2 file at given path and offset. +// AttachIndex attaches a given index to an existing CARv2 file at given path and offset. func AttachIndex(path string, idx index.Index, offset uint64) error { // TODO: instead of offset, maybe take padding? - // TODO: check that the given path is indeed a CAR v2. - // TODO: update CAR v2 header according to the offset at which index is written out. + // TODO: check that the given path is indeed a CARv2. + // TODO: update CARv2 header according to the offset at which index is written out. out, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) if err != nil { return err diff --git a/ipld/car/v2/writer_test.go b/ipld/car/v2/writer_test.go index ff8f3bff2c..3cf119cee0 100644 --- a/ipld/car/v2/writer_test.go +++ b/ipld/car/v2/writer_test.go @@ -47,7 +47,7 @@ func TestWrapV1(t *testing.T) { require.NoError(t, err) wantPayload, err := ioutil.ReadAll(sf) require.NoError(t, err) - gotPayload, err := ioutil.ReadAll(subject.CarV1Reader()) + gotPayload, err := ioutil.ReadAll(subject.DataReader()) require.NoError(t, err) require.Equal(t, wantPayload, gotPayload) From ece6c8bd54956e00832599191bf8338986627125 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 16 Jul 2021 16:45:26 +0100 Subject: [PATCH 128/291] Replace a file if exists on `index.Save` If appendage is needed there is already `car.AttachIndex`. This commit was moved from ipld/go-car@64bb51b2aba026731017fbb8fb62eaf79bce8ac3 --- ipld/car/v2/index/index.go | 4 ++-- ipld/car/v2/testdata/sample-index.carindex | Bin 209505 -> 41901 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 96b730f165..669728bbac 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -52,9 +52,9 @@ func New(codec multicodec.Code) (Index, error) { } } -// Save writes a generated index into the given `path`. +// Save writes a generated index into the given `path` replacing the file if it exists. func Save(idx Index, path string) error { - stream, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640) + stream, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o640) if err != nil { return err } diff --git a/ipld/car/v2/testdata/sample-index.carindex b/ipld/car/v2/testdata/sample-index.carindex index 0f91e36507fc21d3a93a00f32bf330e024405af8..5df07d379e444d8fa3a0f5b96a59874f208bd0c1 100644 GIT binary patch delta 9 RcmaF(glFw>rVVQs0{|Or1xNq@ delta 21 ScmZ2`oax~co(*dkBO?Hf?+r)* From fc756e15fe8f9450ca62edcf99d6bb01369d8688 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 16 Jul 2021 17:09:49 +0100 Subject: [PATCH 129/291] Remove `index.Save` API It's two lines of code extra and we already have read from and write to APIs for index. Remove `hydrate` cli since the assumptions it makes about appending index are no longer true. header needs updating for example. Removing to be re-added when needed as part of a propper CARv2 CLI. This commit was moved from ipld/go-car@e45d3b24a640861b95d35069e8a784250c5ca839 --- ipld/car/v2/example_test.go | 7 ++++- ipld/car/v2/index/example_test.go | 27 ++++++++++++++----- ipld/car/v2/index/index.go | 11 -------- ipld/car/v2/index/index_test.go | 27 ------------------- ipld/car/v2/internal/carbs/doc.go | 3 --- ipld/car/v2/internal/carbs/util/hydrate.go | 30 ---------------------- 6 files changed, 26 insertions(+), 79 deletions(-) delete mode 100644 ipld/car/v2/internal/carbs/doc.go delete mode 100644 ipld/car/v2/internal/carbs/util/hydrate.go diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go index 6944f2fa7c..dd3e002b20 100644 --- a/ipld/car/v2/example_test.go +++ b/ipld/car/v2/example_test.go @@ -25,7 +25,12 @@ func ExampleWrapV1File() { if err != nil { panic(err) } - defer cr.Close() + defer func() { + if err := cr.Close(); err != nil { + panic(err) + } + }() + roots, err := cr.Roots() if err != nil { panic(err) diff --git a/ipld/car/v2/index/example_test.go b/ipld/car/v2/index/example_test.go index cf6d56a630..9f8368d1aa 100644 --- a/ipld/car/v2/index/example_test.go +++ b/ipld/car/v2/index/example_test.go @@ -2,6 +2,7 @@ package index_test import ( "fmt" + "io" "os" "reflect" @@ -44,16 +45,20 @@ func ExampleReadFrom() { // Frame with CID bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy starts at offset 61 relative to CARv1 data payload. } -// ExampleSave unmarshalls an index from an indexed CARv2 file, and stores it as a separate +// ExampleWriteTo unmarshalls an index from an indexed CARv2 file, and stores it as a separate // file on disk. -func ExampleSave() { +func ExampleWriteTo() { // Open the CARv2 file src := "../testdata/sample-wrapped-v2.car" cr, err := carv2.OpenReader(src) if err != nil { panic(err) } - defer cr.Close() + defer func() { + if err := cr.Close(); err != nil { + panic(err) + } + }() // Read and unmarshall index within CARv2 file. idx, err := index.ReadFrom(cr.IndexReader()) @@ -63,17 +68,25 @@ func ExampleSave() { // Store the index alone onto destination file. dest := "../testdata/sample-index.carindex" - err = index.Save(idx, dest) + f, err := os.Create(dest) + if err != nil { + panic(err) + } + defer func() { + if err := f.Close(); err != nil { + panic(err) + } + }() + err = index.WriteTo(idx, f) if err != nil { panic(err) } - // Open the destination file that contains the index only. - f, err := os.Open(dest) + // Seek to the beginning of tile to read it back. + _, err = f.Seek(0, io.SeekStart) if err != nil { panic(err) } - defer f.Close() // Read and unmarshall the destination file as a separate index instance. reReadIdx, err := index.ReadFrom(f) diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 669728bbac..119821714e 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -4,7 +4,6 @@ import ( "encoding/binary" "fmt" "io" - "os" internalio "github.com/ipld/go-car/v2/internal/io" @@ -52,16 +51,6 @@ func New(codec multicodec.Code) (Index, error) { } } -// Save writes a generated index into the given `path` replacing the file if it exists. -func Save(idx Index, path string) error { - stream, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o640) - if err != nil { - return err - } - defer stream.Close() - return WriteTo(idx, stream) -} - // WriteTo writes the given idx into w. // The written bytes include the index encoding. // This can then be read back using index.ReadFrom diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index 945ca1bbc1..a32205dcaa 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -128,33 +128,6 @@ func TestWriteTo(t *testing.T) { require.Equal(t, wantIdx, gotIdx) } -func TestSave(t *testing.T) { - // Read sample index on file - idxf, err := os.Open("../testdata/sample-index.carindex") - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, idxf.Close()) }) - - // Unmarshall to get expected index - wantIdx, err := ReadFrom(idxf) - require.NoError(t, err) - - // Save the same index at destination - dest := filepath.Join(t.TempDir(), "index-write-to-test.carindex") - require.NoError(t, Save(wantIdx, dest)) - - // Open the saved file - destF, err := os.Open(dest) - require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, destF.Close()) }) - - // Read the written index back - gotIdx, err := ReadFrom(destF) - require.NoError(t, err) - - // Assert they are equal - require.Equal(t, wantIdx, gotIdx) -} - func TestMarshalledIndexStartsWithCodec(t *testing.T) { // Read sample index on file idxf, err := os.Open("../testdata/sample-index.carindex") diff --git a/ipld/car/v2/internal/carbs/doc.go b/ipld/car/v2/internal/carbs/doc.go deleted file mode 100644 index 099875379b..0000000000 --- a/ipld/car/v2/internal/carbs/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package carbs provides a read-only blockstore interface directly reading out of a car file. -// TODO to be refactored -package carbs diff --git a/ipld/car/v2/internal/carbs/util/hydrate.go b/ipld/car/v2/internal/carbs/util/hydrate.go deleted file mode 100644 index 96bb4c953f..0000000000 --- a/ipld/car/v2/internal/carbs/util/hydrate.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "fmt" - "os" - - carv2 "github.com/ipld/go-car/v2" - - "github.com/ipld/go-car/v2/index" -) - -func main() { - if len(os.Args) < 2 { - fmt.Printf("Usage: hydrate \n") - return - } - db := os.Args[1] - - idx, err := carv2.GenerateIndexFromFile(db) - if err != nil { - fmt.Printf("Error generating index: %v\n", err) - return - } - - fmt.Printf("Saving...\n") - - if err := index.Save(idx, db); err != nil { - fmt.Printf("Error saving : %v\n", err) - } -} From 9780e1f0f324f738c1a9a9c5274da59c83d4b1f4 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Fri, 16 Jul 2021 16:18:31 +0100 Subject: [PATCH 130/291] Add example blockstore examples Implement examples that show how to open a read-only blockstore, and a read-write blockstore along with resumption from the same file. The example surfaced a race condition where if the `AllKeysChan` is partially consumed and file is closed then the gorutie started by chan population causes the race condition. Locking on Close will make the closure hang. Better solution is needed but choices are limited due to `AllKeysChan` signature. Fixes: - https://github.com/ipld/go-car/issues/124 This commit was moved from ipld/go-car@ae4ddd418cce2f49d44a4dcb1ab4fbb989db7bba --- ipld/car/v2/blockstore/example_test.go | 148 +++++++++++++++++++++++ ipld/car/v2/testdata/sample-rw-bs-v2.car | Bin 0 -> 1875 bytes 2 files changed, 148 insertions(+) create mode 100644 ipld/car/v2/blockstore/example_test.go create mode 100644 ipld/car/v2/testdata/sample-rw-bs-v2.car diff --git a/ipld/car/v2/blockstore/example_test.go b/ipld/car/v2/blockstore/example_test.go new file mode 100644 index 0000000000..72540e5fb6 --- /dev/null +++ b/ipld/car/v2/blockstore/example_test.go @@ -0,0 +1,148 @@ +package blockstore_test + +import ( + "context" + "fmt" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-merkledag" + carv2 "github.com/ipld/go-car/v2" + "github.com/ipld/go-car/v2/blockstore" +) + +const cidPrintCount = 5 + +// ExampleOpenReadOnly opens a read-only blockstore from a CARv1 file, and prints its root CIDs +// along with CID mapping to raw data size of blocks for first five sections in the CAR file. +func ExampleOpenReadOnly() { + // Open a new ReadOnly blockstore from a CARv1 file. + // Note, `OpenReadOnly` accepts bot CARv1 and CARv2 formats and transparently generate index + // in the background if necessary. + // This instance sets ZeroLengthSectionAsEOF option to treat zero sized sections in file as EOF. + robs, err := blockstore.OpenReadOnly("../testdata/sample-v1.car", carv2.ZeroLengthSectionAsEOF) + if err != nil { + panic(err) + } + defer robs.Close() + + // Print root CIDs. + roots, err := robs.Roots() + if err != nil { + panic(err) + } + fmt.Printf("Contains %v root CID(s):\n", len(roots)) + for _, r := range roots { + fmt.Printf("\t%v\n", r) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Print the raw data size for the first 5 CIDs in the CAR file. + keysChan, err := robs.AllKeysChan(ctx) + if err != nil { + panic(err) + } + fmt.Printf("List of first %v CIDs and their raw data size:\n", cidPrintCount) + i := 1 + for k := range keysChan { + if i > cidPrintCount { + cancel() + break + } + size, err := robs.GetSize(k) + if err != nil { + panic(err) + } + fmt.Printf("\t%v -> %v bytes\n", k, size) + i++ + } + + // Output: + // Contains 1 root CID(s): + // bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy + // List of first 5 CIDs and their raw data size: + // bafy2bzaced4ueelaegfs5fqu4tzsh6ywbbpfk3cxppupmxfdhbpbhzawfw5oy -> 821 bytes + // bafy2bzaceaycv7jhaegckatnncu5yugzkrnzeqsppzegufr35lroxxnsnpspu -> 1053 bytes + // bafy2bzaceb62wdepofqu34afqhbcn4a7jziwblt2ih5hhqqm6zitd3qpzhdp4 -> 1094 bytes + // bafy2bzaceb3utcspm5jqcdqpih3ztbaztv7yunzkiyfq7up7xmokpxemwgu5u -> 1051 bytes + // bafy2bzacedjwekyjresrwjqj4n2r5bnuuu3klncgjo2r3slsp6wgqb37sz4ck -> 821 bytes +} + +// ExampleOpenReadWrite creates a read-write blockstore and puts +func ExampleOpenReadWrite() { + thisBlock := merkledag.NewRawNode([]byte("fish")).Block + thatBlock := merkledag.NewRawNode([]byte("lobster")).Block + andTheOtherBlock := merkledag.NewRawNode([]byte("barreleye")).Block + + dest := "../testdata/sample-rw-bs-v2.car" + roots := []cid.Cid{thisBlock.Cid(), thatBlock.Cid(), andTheOtherBlock.Cid()} + + rwbs, err := blockstore.OpenReadWrite(dest, roots, carv2.UseDataPadding(1413), carv2.UseIndexPadding(42)) + if err != nil { + panic(err) + } + defer rwbs.Close() + + // Put all blocks onto the blockstore. + blocks := []blocks.Block{thisBlock, thatBlock} + if err := rwbs.PutMany(blocks); err != nil { + panic(err) + } + fmt.Printf("Successfully wrote %v blocks into the blockstore.\n", len(blocks)) + + // Any blocks put can be read back using the same blockstore instance. + block, err := rwbs.Get(thatBlock.Cid()) + if err != nil { + panic(err) + } + fmt.Printf("Read back block just put with raw value of `%v`.\n", string(block.RawData())) + + // Finalize the blockstore to flush out the index and make a complete CARv2. + if err := rwbs.Finalize(); err != nil { + panic(err) + } + + // Resume from the same file to add more blocks. + // Note the UseDataPadding and roots must match the values passed to the blockstore instance + // that created the original file. Otherwise, we cannot resume from the same file. + resumedRwbos, err := blockstore.OpenReadWrite(dest, roots, carv2.UseDataPadding(1413)) + if err != nil { + panic(err) + } + defer resumedRwbos.Close() + + // Put another block, appending it to the set of blocks that are written previously. + if err := resumedRwbos.Put(andTheOtherBlock); err != nil { + panic(err) + } + + // Read back the the block put before resumption. + // Blocks previously put are present. + block, err = resumedRwbos.Get(thatBlock.Cid()) + if err != nil { + panic(err) + } + fmt.Printf("Resumed blockstore contains blocks put previously with raw value of `%v`.\n", string(block.RawData())) + + // Put an additional block to the CAR. + // Blocks put after resumption are also present. + block, err = resumedRwbos.Get(andTheOtherBlock.Cid()) + if err != nil { + panic(err) + } + fmt.Printf("It also contains the block put after resumption with raw value of `%v`.\n", string(block.RawData())) + + // Finalize the blockstore to flush out the index and make a complete CARv2. + // Note, Finalize must be called on an open ReadWrite blockstore to flush out a complete CARv2. + if err := resumedRwbos.Finalize(); err != nil { + panic(err) + } + + // Output: + // Successfully wrote 2 blocks into the blockstore. + // Read back block just put with raw value of `lobster`. + // Resumed blockstore contains blocks put previously with raw value of `lobster`. + // It also contains the block put after resumption with raw value of `barreleye`. +} diff --git a/ipld/car/v2/testdata/sample-rw-bs-v2.car b/ipld/car/v2/testdata/sample-rw-bs-v2.car new file mode 100644 index 0000000000000000000000000000000000000000..9f7b56df358e0e3a0eb3734743b13cb3d88a12f4 GIT binary patch literal 1875 zcmd;Dm|m7zRGgWg$HagJcCbPO1Q{XpNj5YEqukLD7!85Z5Eu;s`iDRd%Su5i)ABfJaO z8OQqmf3Er3H)fG_$ Date: Fri, 16 Jul 2021 17:44:54 +0100 Subject: [PATCH 131/291] make Close safe, not letting Finalize block forever either This commit was moved from ipld/go-car@c06b4f2ff65cc894d088d7d8ee55063834f12bcc --- ipld/car/v2/blockstore/readonly.go | 11 +++++++++++ ipld/car/v2/blockstore/readwrite.go | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index bbbba8f4e1..34672cad77 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -330,7 +330,18 @@ func (b *ReadOnly) Roots() ([]cid.Cid, error) { } // Close closes the underlying reader if it was opened by OpenReadOnly. +// +// Note that this call may block if any blockstore operations are currently in +// progress, including an AllKeysChan that hasn't been fully consumed or +// cancelled. func (b *ReadOnly) Close() error { + b.mu.Lock() + defer b.mu.Unlock() + + return b.closeWithoutMutex() +} + +func (b *ReadOnly) closeWithoutMutex() error { if b.carv2Closer != nil { return b.carv2Closer.Close() } diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 693e52f913..665a65384e 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -343,7 +343,12 @@ func (b *ReadWrite) Finalize() error { defer b.mu.Unlock() // TODO check if add index option is set and don't write the index then set index offset to zero. b.header = b.header.WithDataSize(uint64(b.dataWriter.Position())) - defer b.Close() + + // Note that we can't use b.Close here, as that tries to grab the same + // mutex we're holding here. + // TODO: should we check the error here? especially with OpenReadWrite, + // we should care about close errors. + defer b.closeWithoutMutex() // TODO if index not needed don't bother flattening it. fi, err := b.idx.flatten() From aa123ddcdf87e06f114c37f50f25c5218c23f2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 19 Jul 2021 18:11:09 +0100 Subject: [PATCH 132/291] blockstore: implement UseWholeCIDs Update the tests too, as a few expected whole CIDs. For now, just make them use the option. This required index.Index to gain a new method, GetAll, so that when using whole CIDs, methods like Get can return true if any of the matching indexed CIDs are an exact whole-CID match, and not just looking at the first matching indexed CID. GetAll is akin to an iteration over all matching indexed CIDs, and its callback returns a boolean to say if the iteration should continue. This allows stopping as soon as we're done. We also remove Index.Get, instead replacing it with a helper called GetFirst, which simply makes simple uses of GetAll a single line. We remove the non-specced and unused index implementations, too. They were left in place in case they were useful again, but they haven't been so far, and their code is still in git. Keeping them around just means updating more code when refactoring. While at it, make ZeroLengthSectionAsEOF take a boolean and return an option, just like the other boolean options, for consistency. Fixes #130. This commit was moved from ipld/go-car@c2e497e22825ba8d5a6329bbf9f06be08197e316 --- ipld/car/v2/blockstore/example_test.go | 5 +- ipld/car/v2/blockstore/insertionindex.go | 32 +++++- ipld/car/v2/blockstore/readonly.go | 127 ++++++++++++++++------ ipld/car/v2/blockstore/readonly_test.go | 4 +- ipld/car/v2/blockstore/readwrite.go | 1 - ipld/car/v2/blockstore/readwrite_test.go | 133 ++++++++++++++--------- ipld/car/v2/index/example_test.go | 2 +- ipld/car/v2/index/index.go | 50 +++++++-- ipld/car/v2/index/index_test.go | 17 +-- ipld/car/v2/index/indexgobhash.go | 48 -------- ipld/car/v2/index/indexhashed.go | 47 -------- ipld/car/v2/index/indexsorted.go | 45 ++++---- ipld/car/v2/index/indexsorted_test.go | 6 +- ipld/car/v2/index_gen.go | 2 +- ipld/car/v2/options.go | 6 +- 15 files changed, 281 insertions(+), 244 deletions(-) delete mode 100644 ipld/car/v2/index/indexgobhash.go delete mode 100644 ipld/car/v2/index/indexhashed.go diff --git a/ipld/car/v2/blockstore/example_test.go b/ipld/car/v2/blockstore/example_test.go index 72540e5fb6..00a81dc7f8 100644 --- a/ipld/car/v2/blockstore/example_test.go +++ b/ipld/car/v2/blockstore/example_test.go @@ -20,7 +20,10 @@ func ExampleOpenReadOnly() { // Note, `OpenReadOnly` accepts bot CARv1 and CARv2 formats and transparently generate index // in the background if necessary. // This instance sets ZeroLengthSectionAsEOF option to treat zero sized sections in file as EOF. - robs, err := blockstore.OpenReadOnly("../testdata/sample-v1.car", carv2.ZeroLengthSectionAsEOF) + robs, err := blockstore.OpenReadOnly("../testdata/sample-v1.car", + blockstore.UseWholeCIDs(true), + carv2.ZeroLengthSectionAsEOF(true), + ) if err != nil { panic(err) } diff --git a/ipld/car/v2/blockstore/insertionindex.go b/ipld/car/v2/blockstore/insertionindex.go index 8e664eac90..00d414656f 100644 --- a/ipld/car/v2/blockstore/insertionindex.go +++ b/ipld/car/v2/blockstore/insertionindex.go @@ -55,7 +55,7 @@ func newRecordFromCid(c cid.Cid, at uint64) recordDigest { panic(err) } - return recordDigest{d.Digest, index.Record{Cid: c, Idx: at}} + return recordDigest{d.Digest, index.Record{Cid: c, Offset: at}} } func (ii *insertionIndex) insertNoReplace(key cid.Cid, n uint64) { @@ -77,7 +77,31 @@ func (ii *insertionIndex) Get(c cid.Cid) (uint64, error) { return 0, errUnsupported } - return r.Record.Idx, nil + return r.Record.Offset, nil +} + +func (ii *insertionIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { + d, err := multihash.Decode(c.Hash()) + if err != nil { + return err + } + entry := recordDigest{digest: d.Digest} + + any := false + iter := func(i llrb.Item) bool { + existing := i.(recordDigest) + if !bytes.Equal(existing.digest, entry.digest) { + // We've already looked at all entries with matching digests. + return false + } + any = true + return fn(existing.Record.Offset) + } + ii.items.AscendGreaterOrEqual(entry, iter) + if !any { + return index.ErrNotFound + } + return nil } func (ii *insertionIndex) Marshal(w io.Writer) error { @@ -152,6 +176,10 @@ func (ii *insertionIndex) flatten() (index.Index, error) { return si, nil } +// note that hasExactCID is very similar to GetAll, +// but it's separate as it allows us to compare Record.Cid directly, +// whereas GetAll just provides Record.Offset. + func (ii *insertionIndex) hasExactCID(c cid.Cid) bool { d, err := multihash.Decode(c.Hash()) if err != nil { diff --git a/ipld/car/v2/blockstore/readonly.go b/ipld/car/v2/blockstore/readonly.go index 34672cad77..6c2397f991 100644 --- a/ipld/car/v2/blockstore/readonly.go +++ b/ipld/car/v2/blockstore/readonly.go @@ -65,7 +65,6 @@ type ReadOnly struct { // go-car/v2 package. func UseWholeCIDs(enable bool) carv2.ReadOption { return func(o *carv2.ReadOptions) { - // TODO: update methods like Get, Has, and AllKeysChan to obey this. o.BlockstoreUseWholeCIDs = enable } } @@ -177,22 +176,35 @@ func (b *ReadOnly) Has(key cid.Cid) (bool, error) { b.mu.RLock() defer b.mu.RUnlock() - offset, err := b.idx.Get(key) + var fnFound bool + var fnErr error + err := b.idx.GetAll(key, func(offset uint64) bool { + uar := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) + var err error + _, err = varint.ReadUvarint(uar) + if err != nil { + fnErr = err + return false + } + _, readCid, err := cid.CidFromReader(uar) + if err != nil { + fnErr = err + return false + } + if b.ropts.BlockstoreUseWholeCIDs { + fnFound = readCid.Equals(key) + return !fnFound // continue looking if we haven't found it + } else { + fnFound = bytes.Equal(readCid.Hash(), key.Hash()) + return false + } + }) if errors.Is(err, index.ErrNotFound) { return false, nil } else if err != nil { return false, err } - uar := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) - _, err = varint.ReadUvarint(uar) - if err != nil { - return false, err - } - _, c, err := cid.CidFromReader(uar) - if err != nil { - return false, err - } - return bytes.Equal(key.Hash(), c.Hash()), nil + return fnFound, fnErr } // Get gets a block corresponding to the given key. @@ -200,21 +212,39 @@ func (b *ReadOnly) Get(key cid.Cid) (blocks.Block, error) { b.mu.RLock() defer b.mu.RUnlock() - offset, err := b.idx.Get(key) - if err != nil { - if err == index.ErrNotFound { - err = blockstore.ErrNotFound + var fnData []byte + var fnErr error + err := b.idx.GetAll(key, func(offset uint64) bool { + readCid, data, err := b.readBlock(int64(offset)) + if err != nil { + fnErr = err + return false } + if b.ropts.BlockstoreUseWholeCIDs { + if readCid.Equals(key) { + fnData = data + return false + } else { + return true // continue looking + } + } else { + if bytes.Equal(readCid.Hash(), key.Hash()) { + fnData = data + } + return false + } + }) + if errors.Is(err, index.ErrNotFound) { + return nil, blockstore.ErrNotFound + } else if err != nil { return nil, err + } else if fnErr != nil { + return nil, fnErr } - entry, data, err := b.readBlock(int64(offset)) - if err != nil { - return nil, err - } - if !bytes.Equal(key.Hash(), entry.Hash()) { + if fnData == nil { return nil, blockstore.ErrNotFound } - return blocks.NewBlockWithCid(data, key) + return blocks.NewBlockWithCid(fnData, key) } // GetSize gets the size of an item corresponding to the given key. @@ -222,23 +252,45 @@ func (b *ReadOnly) GetSize(key cid.Cid) (int, error) { b.mu.RLock() defer b.mu.RUnlock() - idx, err := b.idx.Get(key) - if err != nil { - return -1, err - } - rdr := internalio.NewOffsetReadSeeker(b.backing, int64(idx)) - sectionLen, err := varint.ReadUvarint(rdr) - if err != nil { + var fnSize int = -1 + var fnErr error + err := b.idx.GetAll(key, func(offset uint64) bool { + rdr := internalio.NewOffsetReadSeeker(b.backing, int64(offset)) + sectionLen, err := varint.ReadUvarint(rdr) + if err != nil { + fnErr = err + return false + } + cidLen, readCid, err := cid.CidFromReader(rdr) + if err != nil { + fnErr = err + return false + } + if b.ropts.BlockstoreUseWholeCIDs { + if readCid.Equals(key) { + fnSize = int(sectionLen) - cidLen + return false + } else { + return true // continue looking + } + } else { + if bytes.Equal(readCid.Hash(), key.Hash()) { + fnSize = int(sectionLen) - cidLen + } + return false + } + }) + if errors.Is(err, index.ErrNotFound) { return -1, blockstore.ErrNotFound + } else if err != nil { + return -1, err + } else if fnErr != nil { + return -1, fnErr } - cidLen, readCid, err := cid.CidFromReader(rdr) - if err != nil { - return 0, err - } - if !readCid.Equals(key) { + if fnSize == -1 { return -1, blockstore.ErrNotFound } - return int(sectionLen) - cidLen, err + return fnSize, nil } // Put is not supported and always returns an error. @@ -304,6 +356,11 @@ func (b *ReadOnly) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { return // TODO: log this error } + // If we're just using multihashes, flatten to the "raw" codec. + if !b.ropts.BlockstoreUseWholeCIDs { + c = cid.NewCidV1(cid.Raw, c.Hash()) + } + select { case ch <- c: case <-ctx.Done(): diff --git a/ipld/car/v2/blockstore/readonly_test.go b/ipld/car/v2/blockstore/readonly_test.go index 3d75fd96da..4d443c3cfb 100644 --- a/ipld/car/v2/blockstore/readonly_test.go +++ b/ipld/car/v2/blockstore/readonly_test.go @@ -49,7 +49,9 @@ func TestReadOnly(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - subject, err := OpenReadOnly(tt.v1OrV2path) + subject, err := OpenReadOnly(tt.v1OrV2path, + UseWholeCIDs(true), + ) t.Cleanup(func() { subject.Close() }) require.NoError(t, err) diff --git a/ipld/car/v2/blockstore/readwrite.go b/ipld/car/v2/blockstore/readwrite.go index 665a65384e..ba87ee601c 100644 --- a/ipld/car/v2/blockstore/readwrite.go +++ b/ipld/car/v2/blockstore/readwrite.go @@ -89,7 +89,6 @@ func AllowDuplicatePuts(allow bool) carv2.WriteOption { // Resuming from finalized files is allowed. However, resumption will regenerate the index // regardless by scanning every existing block in file. func OpenReadWrite(path string, roots []cid.Cid, opts ...carv2.ReadWriteOption) (*ReadWrite, error) { - // TODO: enable deduplication by default now that resumption is automatically attempted. f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o666) // TODO: Should the user be able to configure FileMode permissions? if err != nil { return nil, fmt.Errorf("could not open read/write file: %w", err) diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 6612268f14..2494da6ab8 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -123,21 +123,29 @@ func TestBlockstore(t *testing.T) { func TestBlockstorePutSameHashes(t *testing.T) { tdir := t.TempDir() - // wbs allows duplicate puts. - wbs, err := blockstore.OpenReadWrite( - filepath.Join(tdir, "readwrite.car"), nil, + // This blockstore allows duplicate puts, + // and identifies by multihash as per the default. + wbsAllowDups, err := blockstore.OpenReadWrite( + filepath.Join(tdir, "readwrite-allowdup.car"), nil, blockstore.AllowDuplicatePuts(true), ) require.NoError(t, err) - t.Cleanup(func() { wbs.Finalize() }) + t.Cleanup(func() { wbsAllowDups.Finalize() }) - // wbs deduplicates puts by CID. - wbsd, err := blockstore.OpenReadWrite( - filepath.Join(tdir, "readwrite-dedup.car"), nil, + // This blockstore deduplicates puts by CID. + wbsByCID, err := blockstore.OpenReadWrite( + filepath.Join(tdir, "readwrite-dedup-wholecid.car"), nil, blockstore.UseWholeCIDs(true), ) require.NoError(t, err) - t.Cleanup(func() { wbsd.Finalize() }) + t.Cleanup(func() { wbsByCID.Finalize() }) + + // This blockstore deduplicates puts by multihash. + wbsByHash, err := blockstore.OpenReadWrite( + filepath.Join(tdir, "readwrite-dedup-hash.car"), nil, + ) + require.NoError(t, err) + t.Cleanup(func() { wbsByHash.Finalize() }) var blockList []blocks.Block @@ -160,15 +168,15 @@ func TestBlockstorePutSameHashes(t *testing.T) { // However, we have multiple CIDs for each multihash. // We also have two duplicate CIDs. data1 := []byte("foo bar") - appendBlock(data1, 0, cid.Raw) - appendBlock(data1, 1, cid.Raw) + appendBlock(data1, 0, cid.DagProtobuf) + appendBlock(data1, 1, cid.DagProtobuf) appendBlock(data1, 1, cid.DagCBOR) appendBlock(data1, 1, cid.DagCBOR) // duplicate CID data2 := []byte("foo bar baz") - appendBlock(data2, 0, cid.Raw) - appendBlock(data2, 1, cid.Raw) - appendBlock(data2, 1, cid.Raw) // duplicate CID + appendBlock(data2, 0, cid.DagProtobuf) + appendBlock(data2, 1, cid.DagProtobuf) + appendBlock(data2, 1, cid.DagProtobuf) // duplicate CID appendBlock(data2, 1, cid.DagCBOR) countBlocks := func(bs *blockstore.ReadWrite) int { @@ -176,52 +184,75 @@ func TestBlockstorePutSameHashes(t *testing.T) { require.NoError(t, err) n := 0 - for range ch { + for c := range ch { + if c.Prefix().Codec == cid.Raw { + if bs == wbsByCID { + t.Error("expected blockstore with UseWholeCIDs to not flatten on AllKeysChan") + } + } else { + if bs != wbsByCID { + t.Error("expected blockstore without UseWholeCIDs to flatten on AllKeysChan") + } + } n++ } return n } - for i, block := range blockList { - // Has should never error here. - // The first block should be missing. - // Others might not, given the duplicate hashes. - has, err := wbs.Has(block.Cid()) - require.NoError(t, err) - if i == 0 { - require.False(t, has) - } + putBlockList := func(bs *blockstore.ReadWrite) { + for i, block := range blockList { + // Has should never error here. + // The first block should be missing. + // Others might not, given the duplicate hashes. + has, err := bs.Has(block.Cid()) + require.NoError(t, err) + if i == 0 { + require.False(t, has) + } - err = wbs.Put(block) - require.NoError(t, err) - } + err = bs.Put(block) + require.NoError(t, err) - for _, block := range blockList { - has, err := wbs.Has(block.Cid()) - require.NoError(t, err) - require.True(t, has) + // Has, Get, and GetSize need to work right after a Put. + has, err = bs.Has(block.Cid()) + require.NoError(t, err) + require.True(t, has) - got, err := wbs.Get(block.Cid()) - require.NoError(t, err) - require.Equal(t, block.Cid(), got.Cid()) - require.Equal(t, block.RawData(), got.RawData()) + got, err := bs.Get(block.Cid()) + require.NoError(t, err) + require.Equal(t, block.Cid(), got.Cid()) + require.Equal(t, block.RawData(), got.RawData()) + + size, err := bs.GetSize(block.Cid()) + require.NoError(t, err) + require.Equal(t, len(block.RawData()), size) + } } - require.Equal(t, len(blockList), countBlocks(wbs)) + putBlockList(wbsAllowDups) + require.Equal(t, len(blockList), countBlocks(wbsAllowDups)) - err = wbs.Finalize() + err = wbsAllowDups.Finalize() require.NoError(t, err) // Put the same list of blocks to the blockstore that // deduplicates by CID. - // We should end up with two fewer blocks. - for _, block := range blockList { - err = wbsd.Put(block) - require.NoError(t, err) - } - require.Equal(t, len(blockList)-2, countBlocks(wbsd)) + // We should end up with two fewer blocks, + // as two are entire CID duplicates. + putBlockList(wbsByCID) + require.Equal(t, len(blockList)-2, countBlocks(wbsByCID)) + + err = wbsByCID.Finalize() + require.NoError(t, err) + + // Put the same list of blocks to the blockstore that + // deduplicates by CID. + // We should end up with just two blocks, + // as the original set of blocks only has two distinct multihashes. + putBlockList(wbsByHash) + require.Equal(t, 2, countBlocks(wbsByHash)) - err = wbsd.Finalize() + err = wbsByHash.Finalize() require.NoError(t, err) } @@ -280,7 +311,8 @@ func TestBlockstoreNullPadding(t *testing.T) { // A sample null-padded CARv1 file. paddedV1 = append(paddedV1, make([]byte, 2048)...) - rbs, err := blockstore.NewReadOnly(bufferReaderAt(paddedV1), nil, carv2.ZeroLengthSectionAsEOF) + rbs, err := blockstore.NewReadOnly(bufferReaderAt(paddedV1), nil, + carv2.ZeroLengthSectionAsEOF(true)) require.NoError(t, err) roots, err := rbs.Roots() @@ -313,7 +345,8 @@ func TestBlockstoreResumption(t *testing.T) { path := filepath.Join(t.TempDir(), "readwrite-resume.car") // Create an incomplete CARv2 file with no blocks put. - subject, err := blockstore.OpenReadWrite(path, r.Header.Roots) + subject, err := blockstore.OpenReadWrite(path, r.Header.Roots, + blockstore.UseWholeCIDs(true)) require.NoError(t, err) // For each block resume on the same file, putting blocks one at a time. @@ -345,7 +378,8 @@ func TestBlockstoreResumption(t *testing.T) { // We do this to avoid resource leak during testing. require.NoError(t, subject.Close()) } - subject, err = blockstore.OpenReadWrite(path, r.Header.Roots) + subject, err = blockstore.OpenReadWrite(path, r.Header.Roots, + blockstore.UseWholeCIDs(true)) require.NoError(t, err) } require.NoError(t, subject.Put(b)) @@ -377,7 +411,8 @@ func TestBlockstoreResumption(t *testing.T) { require.NoError(t, subject.Close()) // Finalize the blockstore to complete partially written CARv2 file. - subject, err = blockstore.OpenReadWrite(path, r.Header.Roots) + subject, err = blockstore.OpenReadWrite(path, r.Header.Roots, + blockstore.UseWholeCIDs(true)) require.NoError(t, err) require.NoError(t, subject.Finalize()) @@ -528,9 +563,9 @@ func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { indexSize := stat.Size() - int64(wantIndexOffset) gotIdx, err := index.ReadFrom(io.NewSectionReader(f, int64(wantIndexOffset), indexSize)) require.NoError(t, err) - _, err = gotIdx.Get(oneTestBlockCid) + _, err = index.GetFirst(gotIdx, oneTestBlockCid) require.NoError(t, err) - _, err = gotIdx.Get(anotherTestBlockCid) + _, err = index.GetFirst(gotIdx, anotherTestBlockCid) require.NoError(t, err) } diff --git a/ipld/car/v2/index/example_test.go b/ipld/car/v2/index/example_test.go index 9f8368d1aa..47347d8355 100644 --- a/ipld/car/v2/index/example_test.go +++ b/ipld/car/v2/index/example_test.go @@ -34,7 +34,7 @@ func ExampleReadFrom() { // For each root CID print the offset relative to CARv1 data payload. for _, r := range roots { - offset, err := idx.Get(r) + offset, err := index.GetFirst(idx, r) if err != nil { panic(err) } diff --git a/ipld/car/v2/index/index.go b/ipld/car/v2/index/index.go index 119821714e..3408dfbd28 100644 --- a/ipld/car/v2/index/index.go +++ b/ipld/car/v2/index/index.go @@ -14,33 +14,59 @@ import ( "github.com/ipfs/go-cid" ) -// Codec table is a first var-int in CAR indexes -const ( - indexHashed codec = 0x300000 + iota - indexSingleSorted - indexGobHashed -) - type ( - // codec is used as a multicodec identifier for CAR index files - codec int - // Record is a pre-processed record of a car item and location. Record struct { cid.Cid - Idx uint64 + Offset uint64 } // Index provides an interface for looking up byte offset of a given CID. + // + // Note that each indexing mechanism is free to match CIDs however it + // sees fit. For example, multicodec.CarIndexSorted only indexes + // multihash digests, meaning that Get and GetAll will find matching + // blocks even if the CID's encoding multicodec differs. Other index + // implementations might index the entire CID, the entire multihash, or + // just part of a multihash's digest. Index interface { + // Codec provides the multicodec code that the index implements. + // + // Note that this may return a reserved code if the index + // implementation is not defined in a spec. Codec() multicodec.Code + + // Marshal encodes the index in serial form. Marshal(w io.Writer) error + // Unmarshal decodes the index from its serial form. Unmarshal(r io.Reader) error - Get(cid.Cid) (uint64, error) + + // Load inserts a number of records into the index. Load([]Record) error + + // Get looks up all blocks matching a given CID, + // calling a function for each one of their offsets. + // + // If the function returns false, GetAll stops. + // + // If no error occurred and the CID isn't indexed, + // meaning that no callbacks happen, + // ErrNotFound is returned. + GetAll(cid.Cid, func(uint64) bool) error } ) +// GetFirst is a wrapper over Index.GetAll, returning the offset for the first +// matching indexed CID. +func GetFirst(idx Index, key cid.Cid) (uint64, error) { + var firstOffset uint64 + err := idx.GetAll(key, func(offset uint64) bool { + firstOffset = offset + return false + }) + return firstOffset, err +} + // New constructs a new index corresponding to the given CAR index codec. func New(codec multicodec.Code) (Index, error) { switch codec { diff --git a/ipld/car/v2/index/index_test.go b/ipld/car/v2/index/index_test.go index a32205dcaa..03efbc94d6 100644 --- a/ipld/car/v2/index/index_test.go +++ b/ipld/car/v2/index/index_test.go @@ -32,21 +32,6 @@ func TestNew(t *testing.T) { codec: multicodec.Cidv1, wantErr: true, }, - { - name: "IndexSingleSortedMultiCodecIsError", - codec: multicodec.Code(indexSingleSorted), - wantErr: true, - }, - { - name: "IndexHashedMultiCodecIsError", - codec: multicodec.Code(indexHashed), - wantErr: true, - }, - { - name: "IndexGobHashedMultiCodecIsError", - codec: multicodec.Code(indexGobHashed), - wantErr: true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -82,7 +67,7 @@ func TestReadFrom(t *testing.T) { require.NoError(t, err) // Get offset from the index for a CID and assert it exists - gotOffset, err := subject.Get(wantBlock.Cid()) + gotOffset, err := GetFirst(subject, wantBlock.Cid()) require.NoError(t, err) require.NotZero(t, gotOffset) diff --git a/ipld/car/v2/index/indexgobhash.go b/ipld/car/v2/index/indexgobhash.go deleted file mode 100644 index a74e8b92b9..0000000000 --- a/ipld/car/v2/index/indexgobhash.go +++ /dev/null @@ -1,48 +0,0 @@ -package index - -import ( - "encoding/gob" - "io" - - "github.com/multiformats/go-multicodec" - - "github.com/ipfs/go-cid" -) - -//lint:ignore U1000 kept for potential future use. -type mapGobIndex map[cid.Cid]uint64 - -func (m *mapGobIndex) Get(c cid.Cid) (uint64, error) { - el, ok := (*m)[c] - if !ok { - return 0, ErrNotFound - } - return el, nil -} - -func (m *mapGobIndex) Marshal(w io.Writer) error { - e := gob.NewEncoder(w) - return e.Encode(m) -} - -func (m *mapGobIndex) Unmarshal(r io.Reader) error { - d := gob.NewDecoder(r) - return d.Decode(m) -} - -func (m *mapGobIndex) Codec() multicodec.Code { - return multicodec.Code(indexHashed) -} - -func (m *mapGobIndex) Load(rs []Record) error { - for _, r := range rs { - (*m)[r.Cid] = r.Idx - } - return nil -} - -//lint:ignore U1000 kept for potential future use. -func newGobHashed() Index { - mi := make(mapGobIndex) - return &mi -} diff --git a/ipld/car/v2/index/indexhashed.go b/ipld/car/v2/index/indexhashed.go deleted file mode 100644 index 84b0ad1575..0000000000 --- a/ipld/car/v2/index/indexhashed.go +++ /dev/null @@ -1,47 +0,0 @@ -package index - -import ( - "io" - - "github.com/multiformats/go-multicodec" - - "github.com/ipfs/go-cid" - cbor "github.com/whyrusleeping/cbor/go" -) - -//lint:ignore U1000 kept for potential future use. -type mapIndex map[cid.Cid]uint64 - -func (m *mapIndex) Get(c cid.Cid) (uint64, error) { - el, ok := (*m)[c] - if !ok { - return 0, ErrNotFound - } - return el, nil -} - -func (m *mapIndex) Marshal(w io.Writer) error { - return cbor.Encode(w, m) -} - -func (m *mapIndex) Unmarshal(r io.Reader) error { - d := cbor.NewDecoder(r) - return d.Decode(m) -} - -func (m *mapIndex) Codec() multicodec.Code { - return multicodec.Code(indexHashed) -} - -func (m *mapIndex) Load(rs []Record) error { - for _, r := range rs { - (*m)[r.Cid] = r.Idx - } - return nil -} - -//lint:ignore U1000 kept for potential future use. -func newHashed() Index { - mi := make(mapIndex) - return &mi -} diff --git a/ipld/car/v2/index/indexsorted.go b/ipld/car/v2/index/indexsorted.go index 65446f6651..5f37eee446 100644 --- a/ipld/car/v2/index/indexsorted.go +++ b/ipld/car/v2/index/indexsorted.go @@ -44,10 +44,6 @@ func (r recordSet) Swap(i, j int) { r[i], r[j] = r[j], r[i] } -func (s *singleWidthIndex) Codec() multicodec.Code { - return multicodec.Code(indexSingleSorted) -} - func (s *singleWidthIndex) Marshal(w io.Writer) error { if err := binary.Write(w, binary.LittleEndian, s.width); err != nil { return err @@ -77,25 +73,34 @@ func (s *singleWidthIndex) Less(i int, digest []byte) bool { return bytes.Compare(digest[:], s.index[i*int(s.width):((i+1)*int(s.width)-8)]) <= 0 } -func (s *singleWidthIndex) Get(c cid.Cid) (uint64, error) { +func (s *singleWidthIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { d, err := multihash.Decode(c.Hash()) if err != nil { - return 0, err + return err } - return s.get(d.Digest) + return s.getAll(d.Digest, fn) } -func (s *singleWidthIndex) get(d []byte) (uint64, error) { +func (s *singleWidthIndex) getAll(d []byte, fn func(uint64) bool) error { idx := sort.Search(int(s.len), func(i int) bool { return s.Less(i, d) }) if uint64(idx) == s.len { - return 0, ErrNotFound + return ErrNotFound + } + + any := false + for bytes.Equal(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) { + any = true + offset := binary.LittleEndian.Uint64(s.index[(idx+1)*int(s.width)-8 : (idx+1)*int(s.width)]) + if !fn(offset) { + break + } } - if !bytes.Equal(d[:], s.index[idx*int(s.width):(idx+1)*int(s.width)-8]) { - return 0, ErrNotFound + if !any { + return ErrNotFound } - return binary.LittleEndian.Uint64(s.index[(idx+1)*int(s.width)-8 : (idx+1)*int(s.width)]), nil + return nil } func (s *singleWidthIndex) Load(items []Record) error { @@ -115,15 +120,15 @@ func (s *singleWidthIndex) Load(items []Record) error { return nil } -func (m *multiWidthIndex) Get(c cid.Cid) (uint64, error) { +func (m *multiWidthIndex) GetAll(c cid.Cid, fn func(uint64) bool) error { d, err := multihash.Decode(c.Hash()) if err != nil { - return 0, err + return err } if s, ok := (*m)[uint32(len(d.Digest)+8)]; ok { - return s.get(d.Digest) + return s.getAll(d.Digest, fn) } - return 0, ErrNotFound + return ErrNotFound } func (m *multiWidthIndex) Codec() multicodec.Code { @@ -184,7 +189,7 @@ func (m *multiWidthIndex) Load(items []Record) error { idxs[len(digest)] = make([]digestRecord, 0) idx = idxs[len(digest)] } - idxs[len(digest)] = append(idx, digestRecord{digest, item.Idx}) + idxs[len(digest)] = append(idx, digestRecord{digest, item.Offset}) } // Sort each list. then write to compact form. @@ -209,9 +214,3 @@ func newSorted() Index { m := make(multiWidthIndex) return &m } - -//lint:ignore U1000 kept for potential future use. -func newSingleSorted() Index { - s := singleWidthIndex{} - return &s -} diff --git a/ipld/car/v2/index/indexsorted_test.go b/ipld/car/v2/index/indexsorted_test.go index fe0ca961a0..767937b79a 100644 --- a/ipld/car/v2/index/indexsorted_test.go +++ b/ipld/car/v2/index/indexsorted_test.go @@ -18,10 +18,6 @@ func TestSortedIndex_GetReturnsNotFoundWhenCidDoesNotExist(t *testing.T) { name string subject Index }{ - { - "SingleSorted", - newSingleSorted(), - }, { "Sorted", newSorted(), @@ -29,7 +25,7 @@ func TestSortedIndex_GetReturnsNotFoundWhenCidDoesNotExist(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotOffset, err := tt.subject.Get(nonExistingKey) + gotOffset, err := GetFirst(tt.subject, nonExistingKey) require.Equal(t, ErrNotFound, err) require.Equal(t, uint64(0), gotOffset) }) diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index 5a60fb7111..ae938498ae 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -75,7 +75,7 @@ func GenerateIndex(v1r io.Reader, opts ...ReadOption) (index.Index, error) { if err != nil { return nil, err } - records = append(records, index.Record{Cid: c, Idx: uint64(sectionOffset)}) + records = append(records, index.Record{Cid: c, Offset: uint64(sectionOffset)}) // Seek to the next section by skipping the block. // The section length includes the CID, so subtract it. diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index 89044a761b..b206c8342a 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -46,8 +46,10 @@ type ReadWriteOption interface { // a zero-length section as the end of the input CAR file. For example, this can // be useful to allow "null padding" after a CARv1 without knowing where the // padding begins. -func ZeroLengthSectionAsEOF(o *ReadOptions) { - o.ZeroLengthSectionAsEOF = true +func ZeroLengthSectionAsEOF(enable bool) ReadOption { + return func(o *ReadOptions) { + o.ZeroLengthSectionAsEOF = true + } } // UseDataPadding is a write option which sets the padding to be added between From b56791a73872334f23bff0c9f1a1451b7b8a18aa Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 20 Jul 2021 16:47:59 +0100 Subject: [PATCH 133/291] Avoid writing to files in testdata Avoid writing to files in `testdata` through examples. This is because when tests that use those file are run in parallel will fail if files are modified. Instead write to a temporary file in examples, and whenever opening testdata files in `RW` mode, make a temporary copy. Thanks to @mvdan for pointing this out. Fixes #175 This commit was moved from ipld/go-car@c3a595560230886d34e35e63ce258eb79c815ccc --- ipld/car/v2/blockstore/example_test.go | 13 ++++++++++--- ipld/car/v2/blockstore/readwrite_test.go | 20 +++++++++++++++++++- ipld/car/v2/example_test.go | 8 +++++++- ipld/car/v2/index/example_test.go | 14 ++++++++++---- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/ipld/car/v2/blockstore/example_test.go b/ipld/car/v2/blockstore/example_test.go index 00a81dc7f8..3091e471e7 100644 --- a/ipld/car/v2/blockstore/example_test.go +++ b/ipld/car/v2/blockstore/example_test.go @@ -3,6 +3,9 @@ package blockstore_test import ( "context" "fmt" + "io/ioutil" + "os" + "path/filepath" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" @@ -79,10 +82,14 @@ func ExampleOpenReadWrite() { thatBlock := merkledag.NewRawNode([]byte("lobster")).Block andTheOtherBlock := merkledag.NewRawNode([]byte("barreleye")).Block - dest := "../testdata/sample-rw-bs-v2.car" + tdir, err := ioutil.TempDir(os.TempDir(), "example-*") + if err != nil { + panic(err) + } + dst := filepath.Join(tdir, "sample-rw-bs-v2.car") roots := []cid.Cid{thisBlock.Cid(), thatBlock.Cid(), andTheOtherBlock.Cid()} - rwbs, err := blockstore.OpenReadWrite(dest, roots, carv2.UseDataPadding(1413), carv2.UseIndexPadding(42)) + rwbs, err := blockstore.OpenReadWrite(dst, roots, carv2.UseDataPadding(1413), carv2.UseIndexPadding(42)) if err != nil { panic(err) } @@ -110,7 +117,7 @@ func ExampleOpenReadWrite() { // Resume from the same file to add more blocks. // Note the UseDataPadding and roots must match the values passed to the blockstore instance // that created the original file. Otherwise, we cannot resume from the same file. - resumedRwbos, err := blockstore.OpenReadWrite(dest, roots, carv2.UseDataPadding(1413)) + resumedRwbos, err := blockstore.OpenReadWrite(dst, roots, carv2.UseDataPadding(1413)) if err != nil { panic(err) } diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index 2494da6ab8..d53379fc52 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -570,11 +570,29 @@ func TestReadWriteWithPaddingWorksAsExpected(t *testing.T) { } func TestReadWriteResumptionFromNonV2FileIsError(t *testing.T) { - subject, err := blockstore.OpenReadWrite("../testdata/sample-rootless-v42.car", []cid.Cid{}) + tmpPath := requireTmpCopy(t, "../testdata/sample-rootless-v42.car") + subject, err := blockstore.OpenReadWrite(tmpPath, []cid.Cid{}) require.EqualError(t, err, "cannot resume on CAR file with version 42") require.Nil(t, subject) } +func requireTmpCopy(t *testing.T, src string) string { + srcF, err := os.Open(src) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, srcF.Close()) }) + stats, err := srcF.Stat() + require.NoError(t, err) + + dst := filepath.Join(t.TempDir(), stats.Name()) + dstF, err := os.Create(dst) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, dstF.Close()) }) + + _, err = io.Copy(dstF, srcF) + require.NoError(t, err) + return dst +} + func TestReadWriteResumptionFromFileWithDifferentCarV1PaddingIsError(t *testing.T) { oneTestBlockCid := oneTestBlockWithCidV1.Cid() WantRoots := []cid.Cid{oneTestBlockCid} diff --git a/ipld/car/v2/example_test.go b/ipld/car/v2/example_test.go index dd3e002b20..37bcc22b53 100644 --- a/ipld/car/v2/example_test.go +++ b/ipld/car/v2/example_test.go @@ -4,6 +4,8 @@ import ( "bytes" "fmt" "io/ioutil" + "os" + "path/filepath" carv2 "github.com/ipld/go-car/v2" "github.com/ipld/go-car/v2/blockstore" @@ -15,7 +17,11 @@ func ExampleWrapV1File() { // Writing the result to testdata allows reusing that file in other tests, // and also helps ensure that the result is deterministic. src := "testdata/sample-v1.car" - dst := "testdata/sample-wrapped-v2.car" + tdir, err := ioutil.TempDir(os.TempDir(), "example-*") + if err != nil { + panic(err) + } + dst := filepath.Join(tdir, "wrapped-v2.car") if err := carv2.WrapV1File(src, dst); err != nil { panic(err) } diff --git a/ipld/car/v2/index/example_test.go b/ipld/car/v2/index/example_test.go index 47347d8355..5070462074 100644 --- a/ipld/car/v2/index/example_test.go +++ b/ipld/car/v2/index/example_test.go @@ -3,7 +3,9 @@ package index_test import ( "fmt" "io" + "io/ioutil" "os" + "path/filepath" "reflect" carv2 "github.com/ipld/go-car/v2" @@ -67,8 +69,12 @@ func ExampleWriteTo() { } // Store the index alone onto destination file. - dest := "../testdata/sample-index.carindex" - f, err := os.Create(dest) + tdir, err := ioutil.TempDir(os.TempDir(), "example-*") + if err != nil { + panic(err) + } + dst := filepath.Join(tdir, "index.carindex") + f, err := os.Create(dst) if err != nil { panic(err) } @@ -96,11 +102,11 @@ func ExampleWriteTo() { // Expect indices to be equal. if reflect.DeepEqual(idx, reReadIdx) { - fmt.Printf("Saved index file at %v matches the index embedded in CARv2 at %v.\n", dest, src) + fmt.Printf("Saved index file matches the index embedded in CARv2 at %v.\n", src) } else { panic("expected to get the same index as the CARv2 file") } // Output: - // Saved index file at ../testdata/sample-index.carindex matches the index embedded in CARv2 at ../testdata/sample-wrapped-v2.car. + // Saved index file matches the index embedded in CARv2 at ../testdata/sample-wrapped-v2.car. } From 4644dbb113cdb0ea750fda7565f973597760a16e Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Tue, 20 Jul 2021 17:36:44 +0100 Subject: [PATCH 134/291] Use `ioutil.TempFile` to simplify file creation in index example Use existing SDK to create temp file in index example. This commit was moved from ipld/go-car@326783fe5b60a7e80510fb3267ca08d13343b559 --- ipld/car/v2/index/example_test.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/ipld/car/v2/index/example_test.go b/ipld/car/v2/index/example_test.go index 5070462074..ca0ac73ab9 100644 --- a/ipld/car/v2/index/example_test.go +++ b/ipld/car/v2/index/example_test.go @@ -5,7 +5,6 @@ import ( "io" "io/ioutil" "os" - "path/filepath" "reflect" carv2 "github.com/ipld/go-car/v2" @@ -69,12 +68,7 @@ func ExampleWriteTo() { } // Store the index alone onto destination file. - tdir, err := ioutil.TempDir(os.TempDir(), "example-*") - if err != nil { - panic(err) - } - dst := filepath.Join(tdir, "index.carindex") - f, err := os.Create(dst) + f, err := ioutil.TempFile(os.TempDir(), "example-index-*.carindex") if err != nil { panic(err) } From 2299fcbdc3e19b34a96cf933d96b2af5760f4d64 Mon Sep 17 00:00:00 2001 From: "Masih H. Derkani" Date: Wed, 21 Jul 2021 10:04:43 +0100 Subject: [PATCH 135/291] Allow `ReadOption`s to be set when getting or generating index Fix a bug in read options where the value of zero-len option is always set to `true` even if the flag passed is false. This meant if the option was present it would have always enabled the zero-len as EOF. Allow the user to specify read options on `ReadOrGenerateIndex`. Pass the options onto downstream reader/generators. Generate a CARv1 file with null padding and check it into `testdata`. In addition check in another such file from ignite just to have two different samples of this case. Enhance index generation tests to expect error when zero-len option is not set, and generate index when it is for files with null padding. Make RW blockstore test use the fixed test files from `testdata` instead of generating every time. This commit was moved from ipld/go-car@396cc2289891953aa5f2445d28e22a92994b938b --- ipld/car/v2/blockstore/readwrite_test.go | 5 +-- ipld/car/v2/index_gen.go | 8 ++-- ipld/car/v2/index_gen_test.go | 41 +++++++++++++++++- ipld/car/v2/options.go | 2 +- .../sample-v1-with-zero-len-section.car | Bin 0 -> 481955 bytes .../sample-v1-with-zero-len-section2.car | Bin 0 -> 32512 bytes 6 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 ipld/car/v2/testdata/sample-v1-with-zero-len-section.car create mode 100644 ipld/car/v2/testdata/sample-v1-with-zero-len-section2.car diff --git a/ipld/car/v2/blockstore/readwrite_test.go b/ipld/car/v2/blockstore/readwrite_test.go index d53379fc52..f3016e25d0 100644 --- a/ipld/car/v2/blockstore/readwrite_test.go +++ b/ipld/car/v2/blockstore/readwrite_test.go @@ -305,12 +305,9 @@ func (b bufferReaderAt) ReadAt(p []byte, off int64) (int, error) { } func TestBlockstoreNullPadding(t *testing.T) { - paddedV1, err := ioutil.ReadFile("../testdata/sample-v1.car") + paddedV1, err := ioutil.ReadFile("../testdata/sample-v1-with-zero-len-section.car") require.NoError(t, err) - // A sample null-padded CARv1 file. - paddedV1 = append(paddedV1, make([]byte, 2048)...) - rbs, err := blockstore.NewReadOnly(bufferReaderAt(paddedV1), nil, carv2.ZeroLengthSectionAsEOF(true)) require.NoError(t, err) diff --git a/ipld/car/v2/index_gen.go b/ipld/car/v2/index_gen.go index ae938498ae..11bf36beed 100644 --- a/ipld/car/v2/index_gen.go +++ b/ipld/car/v2/index_gen.go @@ -110,7 +110,7 @@ func GenerateIndexFromFile(path string) (index.Index, error) { // // Note, the returned index lives entirely in memory and will not depend on the // given reader to fulfill index lookup. -func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { +func ReadOrGenerateIndex(rs io.ReadSeeker, opts ...ReadOption) (index.Index, error) { // Read version. version, err := ReadVersion(rs) if err != nil { @@ -124,10 +124,10 @@ func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { switch version { case 1: // Simply generate the index, since there can't be a pre-existing one. - return GenerateIndex(rs) + return GenerateIndex(rs, opts...) case 2: // Read CARv2 format - v2r, err := NewReader(internalio.ToReaderAt(rs)) + v2r, err := NewReader(internalio.ToReaderAt(rs), opts...) if err != nil { return nil, err } @@ -136,7 +136,7 @@ func ReadOrGenerateIndex(rs io.ReadSeeker) (index.Index, error) { return index.ReadFrom(v2r.IndexReader()) } // Otherwise, generate index from CARv1 payload wrapped within CARv2 format. - return GenerateIndex(v2r.DataReader()) + return GenerateIndex(v2r.DataReader(), opts...) default: return nil, fmt.Errorf("unknown version %v", version) } diff --git a/ipld/car/v2/index_gen_test.go b/ipld/car/v2/index_gen_test.go index 133aaf9256..97eb244b45 100644 --- a/ipld/car/v2/index_gen_test.go +++ b/ipld/car/v2/index_gen_test.go @@ -13,12 +13,14 @@ func TestReadOrGenerateIndex(t *testing.T) { tests := []struct { name string carPath string + readOpts []ReadOption wantIndexer func(t *testing.T) index.Index wantErr bool }{ { "CarV1IsIndexedAsExpected", "testdata/sample-v1.car", + []ReadOption{}, func(t *testing.T) index.Index { v1, err := os.Open("testdata/sample-v1.car") require.NoError(t, err) @@ -32,6 +34,7 @@ func TestReadOrGenerateIndex(t *testing.T) { { "CarV2WithIndexIsReturnedAsExpected", "testdata/sample-wrapped-v2.car", + []ReadOption{}, func(t *testing.T) index.Index { v2, err := os.Open("testdata/sample-wrapped-v2.car") require.NoError(t, err) @@ -44,9 +47,45 @@ func TestReadOrGenerateIndex(t *testing.T) { }, false, }, + { + "CarV1WithZeroLenSectionIsGeneratedAsExpected", + "testdata/sample-v1-with-zero-len-section.car", + []ReadOption{ZeroLengthSectionAsEOF(true)}, + func(t *testing.T) index.Index { + v1, err := os.Open("testdata/sample-v1-with-zero-len-section.car") + require.NoError(t, err) + defer v1.Close() + want, err := GenerateIndex(v1, ZeroLengthSectionAsEOF(true)) + require.NoError(t, err) + return want + }, + false, + }, + { + "AnotherCarV1WithZeroLenSectionIsGeneratedAsExpected", + "testdata/sample-v1-with-zero-len-section2.car", + []ReadOption{ZeroLengthSectionAsEOF(true)}, + func(t *testing.T) index.Index { + v1, err := os.Open("testdata/sample-v1-with-zero-len-section2.car") + require.NoError(t, err) + defer v1.Close() + want, err := GenerateIndex(v1, ZeroLengthSectionAsEOF(true)) + require.NoError(t, err) + return want + }, + false, + }, + { + "CarV1WithZeroLenSectionWithoutOptionIsError", + "testdata/sample-v1-with-zero-len-section.car", + []ReadOption{}, + func(t *testing.T) index.Index { return nil }, + true, + }, { "CarOtherThanV1OrV2IsError", "testdata/sample-rootless-v42.car", + []ReadOption{}, func(t *testing.T) index.Index { return nil }, true, }, @@ -56,7 +95,7 @@ func TestReadOrGenerateIndex(t *testing.T) { carFile, err := os.Open(tt.carPath) require.NoError(t, err) t.Cleanup(func() { assert.NoError(t, carFile.Close()) }) - got, err := ReadOrGenerateIndex(carFile) + got, err := ReadOrGenerateIndex(carFile, tt.readOpts...) if tt.wantErr { require.Error(t, err) } diff --git a/ipld/car/v2/options.go b/ipld/car/v2/options.go index b206c8342a..86aaa9e55d 100644 --- a/ipld/car/v2/options.go +++ b/ipld/car/v2/options.go @@ -48,7 +48,7 @@ type ReadWriteOption interface { // padding begins. func ZeroLengthSectionAsEOF(enable bool) ReadOption { return func(o *ReadOptions) { - o.ZeroLengthSectionAsEOF = true + o.ZeroLengthSectionAsEOF = enable } } diff --git a/ipld/car/v2/testdata/sample-v1-with-zero-len-section.car b/ipld/car/v2/testdata/sample-v1-with-zero-len-section.car new file mode 100644 index 0000000000000000000000000000000000000000..2bb7dd2c211ebe3f990f6d2a11260db7fb215264 GIT binary patch literal 481955 zcmdSBRZtz;(yooW>%`q5XmHoy?ry=|-QC@TySuwXfZ!6`JxGAyhwQW0D%N+C|GW6B z&f>a9O{&Ly-nU1O9+NxI*xts*(ZO$tT8#n-*e+)mgz#PvS&t~k82RzAB9>MjOd+o_4OvAu(tjWzIhi2rX3qJ)6ff&J9kP7mV^XFOuvNS(Jd z{qMJ#FJ(8=_wi8BHQ}u=;<6T|Q=!iO zNagh)J~5HNCL!IMHV1)65=PfX6{g=_2mH!-7ERt++QP$L zF9`?O)_d_m#~buJ~qfQNd<{?*_Wvv1wjvydgi3JwF;2fgnvgXnOaj(8EsnL_~UKh{jg9!E! zc+84I=z?-6Rn5l;4CEwW$VA)=+TrX@(`9fOd&f&m8=xi7@U5BpuZN>~jE^d+nv==6Y~5oF#z9g3{DpJZ9+ zqM&7n$6;!v9Wh$eR%gyd5|mWF1?0UjUad|(!F~szc|(H7>(*YkZsVb5g_^aIQqtT3 z>cGB*FMJcYB@ls{NOg*`EY6~`1oxFO^caW zb9d>NuU_afcpyN=P7H`+JOPVCWllW)JZpV3eKbT#AW<(7sMAE zK>YBc_O2;aFRm35{t<7uWR;p;fkY;ifZ*EjZ&eiFk?u^mJ-f8NrxRqaYZ{?0uZ*FWo zn^7jViqYO&QbyBAS{LLQZ1W|iI(m1Vnx2`cgiq(Av18j}s$TjnE_qiMQ+b#~?rXCm zgP#zVZF#(W2tUTKZ#Jz1%C|t()+?gr1}T~=%8^=MXQNdPD%VV5#9Vk3vjC~To4a8w ztT=1gvu1Vq(a-@|4NgW5ZU7-et0g(BrkCc!`p`=dQ$|00*B$Vj_HC~%SR5x>;%sh2 z>S?S?!1XewedO68u!GBRHu3ESDGG}+NV;{%p?J3V^� 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 Tx_{@>Ah$cBb;X>)D9fct)9`U;$XV@y4$E$ z*eQTV_~wpPza$N;iF~qTt9(-U8?Ha){td4GT%5MRg*Z2duf6>MQGARCBcHJWbpUH^+{uK~fG)irF2I@#40d52k zF^uCx;189qqdV^>>5k8vtkAV&`I)V_C6xvbbYMeQLIdQ1HIAao)|W;J*~VL;vj}Yr z3BDbI4T6}j8IS+QUyKNMtbNPy}!6Jf6!jWK!#e`B3md?ulTSI1pvQVLqJ zy1?>!$rs6hNmkR#C+yOQ;1nMs2+!Li(WpM#gT~R^t&*URHe2G&zzARMh`pu4yJ)C$ zr8#R8Nu^i>m}3YEQa11np*0(Fw=uR-hn8#{x)GZV8-;By6^@d?Ud|y-@VBFCHC)oc zI%j46hO3RlpX+wR`jG34ntx=Ab>4#8VR_u+ioKt*NQ=;>l?DsypKu+)e15!5+_o#r z{kSC+FSs_&&ZI{3#AvpWpT;JaI&b|37g-Fz?j12l>%a+`3kUIO`la*}Hor1ijrqU1 z^C4_8(UHHH+&jL1i1_b=%jylTW=Gc-)3n{~A!yCbxyPNUYg1!>C&IfOC84V}2#4nI zKp#c_^&lRD1^+EEm^6S72we;?v$90->Nb9vUdRMWndUO#L!TEHCsrFnN-Hk9ASS^; zS7N>JbJ_T5MRCUK-xcJaa{m`x@y6(J0@rDR1r%Rb)U=zaTa%I4B&#S@+d#m|$2Afn z<&FKv6%0Lv-D2EIqY#Y;!C?GBUAFer44j{0@;S)}Wtr^8Y`(yl=En~f^S=z2jq@6# zV3p^{ZqsjRpX9JyzQGmd*JCEI>FCqqun^J`5n0#ySooQ<5WN-MwM3z?=xHGdJ(_Ib z=s3`VexA(*6h5rhd`9y;XEhkLWHTr`{G~Jdh}>Zg5&wyUf)GA zoQr;0SF>M<=&nzz#IA5h0J&ARs9-q=&c}Q=mC{q;o`c>yH`fv~6yI-9#K8OCN~1v3 z^y+9LM5aq%>m&S4CfJU69rZPS;wsKSrUw7~Dx^fma;)yCf(?T7iBC=PJDmi61TYj) zJat9T@GAo+t_sKxp2y<2)&o-JGnqe->rZ!xT)zv8TH$sla7^==&z$r1ftVQlAb2q_-PlLeoF8z^xwzf?>EZ9ZM*eh z$j;qF#Bo}A7kR-y!B)CUt0@Kcnpjq*^Y%<#wD-Y&My(r67;Kf`vwc=UofJ2R_q%!3 z&Rv*w1o{vy_<#t+sCf-URUYBZBQTrS^0+mh$Hkt0X)JHz%uy=vcdzwN+w>QQafqeC z>zA0q2&o&^KY`t4P}JuJmi821H8+;(kc3W^1IjS+o<`~D(?8E zVxssb@Dnq`_jI-l#S!As?}w|7OMrQ|cE+8_O!JJbkVO%0GlCS@IQX(hLF_a8392%D zm-C71YN&kZ@kbs+o0u-=VDwR`IsNKZFxL2A7BZC6wN+2Mt)+{P8G{I(%1-HqoDz9Sv z_$1LGq5T35wZ43XPPoe67>d&&2>&sK+rX)WTTPr?a{po@sGE~4@o^uT!Hn`{9!W_T zNl=YwJ5dTs`I&ReSX2?F!ghY`gF&lCR?s=>jxiQ(Z?$duS=3w=x7iwzk~-r}gG8)t zNULHRGQE6$dK-{onL5i6*DeZ@6oyX_{lOI$@WuP$fV>2v93SD8gs}Di?aAq|zqnB}uLrFLjrFw$azwR?q_;|@S z2&%1xDFa4RGKZ^SJkUzz)7?wC!cK(;bN7bE@v?3Q&p95!6TD7i)<=g;B?c%;!nW(? zEY*dM)ETLN_gY8qRPlWsMsWRhn0&{TM6(h%L`o(6gtz6WXeefKE~T*u|1*uauAtbA z_N)z18U9?V$2lj5IY7pJ%iGOSnAR!}Sce7t&t5CuqUJ$CfPhY}dX{%;O~YJ;<=H?G zGZAXrG;c$FiK+gZhztt>Y~CqDwP~Oq_!fd7Lenplo)%U#(wo;!5F&gyQsoJH?4U{B zccK425kY+u5#Mg}F+4*=2d)5RWT7749W-_R%V;nlPHbt%VcCK$zXMD=qwk@bkG7)& ztBX6;ToAgPP*MD;H{J9iiJ2hMS$GnIH-20|Cpf-6^p#AzUBkiB>gIJ+Tp3S_KTJJ~ z4}b#y+Yb6u?%zb@KdZS8&?+OpO<% z7X6OV32@SSm1g-U`^BBB+BVuVm@^%g6tq!VQg1JyA&g@3g&gKxmsU|#!!hDuh*h{{ z3B#Dzgs;`#%)FtdN)3-+*tU7|!$&;g$32=T<)@R$na-S zTqpt9DUV6ow=__opF_oc5N@na%v?Z;;J=kbwyBM5wlCRU$VV3V(G*z`k&EnXKb}+` zhib)X@PL@Lv+}In!}#Kg_k8l_z5{O8we3d|V12iC)Sa_p%ju1vQf9PzD{)%v-~3eN6t}U}i*}-&G2!gj ze9o>uAPw=+(4IH&!-;CpMXohk{|eESzJ!hNoGb-k?8~|Jcl*Vb?cbF$_8BBv)w0zr z=}%5LCPZ1~%gvBrYBN0dq+U$*%1Aibe-8SVoL2XJ%TS&(z^uHIO*Qc5_%dsFXUqB2 zLbCZX>rOB94wMwK)V$Nk9&OaC*p#8 zE8QAUASI))Qmjy;Fe;oOpQ&pINzUHC687iyfk}QB`mdvqZ2yhIC_>1V+b0q)jSBY3 z^Gi9!N71v|j2HvDmFkwe1(U{3P`jBpM@2@2548#sf%+L;!EYJ`_|F-f!ZPQ;??jMtL3+_@>LBD+d zY9<1%Sr4g8hbHsNqWm2fLy@FvS5B$q1cO%%7KD^PKihE40g>OclXFVya}LL6?$uu{ zPa~UoR*sn8z;R_@*pw9b+gHb>kbEIkq0Gq63;mdTKr`iet35>q-o6(*dLdIWv0S5O9@&P557hX$AFgQrQio!ROW z?`?^#Q5!v!>g7M=d(TfugS4?9Tij1;r(qw+I@<)f!L?jDwTlNiv$b+r{@7}lZYE!x;3oTynF7lh5D zri@5DUaa5h(n7k{hk%Lo#h?pLa$7c4+99|?`yxj;De6LTv3c6I~%u#@(wUk4~ovzJoyBE28qBn zFZ$SlTCX4-5VnKgG$DZ+2w3Y0%l#_Z21CkM=`1$lXQ*sgz8@s?k;6>H&M7~@_ih9H zx0S2oE|{eP5SI@(gG^`Omy<-g&!!85BA-DyH!Z@J8dR}x|8s3xWSV4LX(jY4R$ehe zJV|K$(3B&_|G;5rgx$LJ`rhs z6On*bo#vYvS#9dWNOTY6rQX2Q?m0Py0msx_q*|Hq(ni&Dw!;^?4hPe%eM0e*3n>M#iAy!rOcA>RUm5H9TXrk{tnY}b=&dEUq5r1q+ zVYyLjV;0(r1bM=RW40@Z-@u+DoVuNAB&6N%(6j7-Y)xLUBQ!JaAOs>Uthj3TFVpWN z|Kb4+%uZrJtJtm(e&6||AuRs(z+P{*oB#o+QK+O{5rydL04p_hjoQZwnp(S~ZLEJ& zh#e)G^wq|8e!2}-wafffmzI%@?8iQ}t#0eS6XQu1X!G0X0nqqm73w-|LVoR53a~JxB@YJ=-_$c z?Kd7Xs4=a44gx@Dz~mFEwL@pLE;I7f;m;Ln&)1v8@l#0~p&8VYJ_9l!VZQO9eh|2D z9RQ0abL7`H&i9}CNV(fty@Oifr@RS9V0%^x62b1TMg~VnruMf(|0(x>!BzG=CgWJ4 zmOft!^@(L@Mh;Q2h`7W%hlb`zOm`+F^)+RnoWHW_nB8dixjHrvNDu3m^dNHOqha=V zzNDToM{nDbJsKJSENkQ>Ku*`2hsWK-Fu*&VfPg@mFOba;gr%@Q;QV9W$9c3Us!t6o4+0jqRNMxSx`; zd=1K^x}L+iML7CZHoAdo%Je2@|Hevw4ICe-?O50@ z!>P61f|RU1S0CXM)@I{l?Asn$9>y@@Tavb7;>!A2ZV&@}AvD>eSJk;f-M;V%OkS7m+mv&>41O&7j#vY?L{}E4$lXTP%0<_ z<8=oQo2|8c;DaVt?y)r@_0K7r0y!mj(R{L|b2rkL{`<>qhGi3bi#C1BqBpI9;4;C4<-CCCu-)MX%ovUn0$fd7mhV=S!4TO6pBN`{X(u`%{GY#j@wryPZ~l>buBgdub(t8PrA1Nx+L)dv z75NUYt4H62_f13)1_2iDL}U>tF#8h}LY?c&@~vX-+RleOTy0yXm2wQeo3%`$&b@b` z|2`4XeG`#lQ9fa}AQ-rnDrkmOKRsltZE5xl=WNz*P$cg1u-in2uCODBc1~7?4wX~lS7IY~URD-kQTn#&fhuxB+H_hLI9T^A1?>X8M6#1DX0JYxA>?4u zn?w7y2f9BuIs*F#-B(+}Tx^o@Kd2vJ(GddE&{EITyI~?0yGC3o(((ELPllvI1Suuh z>lA$QG*+%#d$!m5wPOwpWOYgJvN{i6StA2cc)~ZlOUxhz`Cap<-}?GNx-=Trce;|S z0^M|m7G#mKPy+xi=NDz~IsIs8x1mG@jUbC|l#7Ray+h6ZSejm9YgbqNX^5O_YzDh$ znehB4sRhf`VX${EPZn=y7u}81x>qrdjH3NSk%oL!%3)2!5=^<8UbBTbf#u$2?``xZ zYCb4j^r8N>^No$;akQsYC?7tn_F#`k|-M+c_P%D-U-9CA{ zwsxU!#O*Y<>U6@M@)N20+xY)KMvo`(3i~lMj(umXh1$6tyob5ZWU1MCu0Pe!E6xZF zLemKYp=?zE%-<377_}krvxF^oi^0fP?deIzH$33hTug6p z`F)85XbV}Lp0UF90<6xF3#YQ&EbIr>U7tWQs8F7oIXuhiGv#81WWN6&>fR|l)9h*c zj;)SuTOHfBt&Wq9la9@fZQHh!j%}-B+nq1@AMebUd3I)d_Uq0@UHP3=YgMhP%2+IC zLPI27jEN{Q4K>Y3d`v*PWi1jEzI^k_?b`L&U)`2N4) z`ZwSI1y`Q5_E8c@N~}?f)gN$&Ibu_yU!k$fxNG5v%OX1>6K63iylQ#aW-U`jzRE=L zH}y!BGAbaNykFYQySD826DOw^Rm&RP`r&0eIrt+U>>ap@aAY3b_ds_g>Jj~_Xg!;-jK4y%v-r~rd9Lu`HV(+7!e-OKEU=ehm<@Z9Rpl zGYfF2a;YJeJo(OzLm1{&mT@#&k3Z9qt=SeLXR15!0T+3+FK?G#3VI5n`Np_$u1zfv zE%~t^y80EGzP?iS5C#SW$Uo-PVj4ggtQD!9(g_%#i2JXRJ;?vDQG0QRKi3F2Bt(xV%OB0H=qPk%&(0>{((08~(o*vRqMx|&FSndKbOo_5oMPRkKsFK!zex$!)Y9jI273L6B z!SNJ+F!@7Y8)Kvl6bk@fZijcU@;rse_SS3;*_=HnMc|+VpT^vh%vJ8bK_(2na?LP= zu>g>AnKpb;t#PHoSoECPuC@&smK?3&ul-S%cOtPbdY9}B9nD5j0(5eRwXc52l_834 z`dF*7ww}I`{YY;mDPfLZB@!dkk6iGbQ0 zrA@KV*D9lBulHbs(DnNJ&{vlRih#~U#H|@PA8XU1|2M6II%z5gdA|6KzeMXS@|B0R za$CEcCjR*yw&(ywsXU+VKZiR*yGur6rI-vf6vw9O6AFufuXE)<^bbG)o($|Z zY5nN51)cs#f5UhLS3)DfiuZvyXHEyFQBE5j`&(l=N?Fqy7g8_I-$cJqHvZGADfM}r;FT>N31&N1E8{phTMYh|)-5ppVO;VdvxZAla z6Z|bA|K|I@M5LKwiP7=KnTX=cW|vB#kWvUYip(Jij||F;vu|sD=z_xnAG>A4)GlY% zCM-V&?kf`XZ^@{tz>7zdTHW!NO-EqWCG@#;jalkmrrVpJDV{VwuC8qa!YE@;Hg}xx zV*(!{;_pd)0fCnPkZR-WaLaM%ztJh8$UR6^dDG(fn0pek=pG`3FKm-q)s$|ST)Wgs z+ZfaRd+y2BE^X>z&FOwF%oSo5^&Bp#W(EITo@-9QGiD6)wQ ziW+0Fu!dG`lSH_!XmnS;E#s=0xlHxrqnIG#OL@!FLjUS4bky-jv8>F%GF}y@=OMio zlu{Mh?y9q{Vo*%%N?On^oS=!~9~3PGKQKmMGOp?L2xuQC>EgIhC|j(!y~==Wy@-Zi zIil`hjC6=*x3Q+froT3JeoreDs>SUSQrqp62M(SfeL!p54A8_uyR)tpA~J`B59=c% z7c!WP#L*7HdGa)*Yet&Bx<-5x!d%^k0dk$&MDgoElO~Nw8ec&D?o^qXRheaJzqOpM zo5KqsdA|rOQO{RjmKBBG(@3C3_#q;u2G4vUI4GfA5`)r@J6hkhOy#QT5}qXpl!fw*)d{pCNFMLMC-XuMInmKuqgXtJl}jJ6bz7mJ`SRe0g0VJ z2Hk5;QcL&m{9)>E%}X46f8Y&H8O{N^=PUazA{zSWCZCjn0n5>OSr1J#J)mD-1>=jl zg@#gBB-*i93LQ;5+iFG9C)59)h?KsINWk|*WK@CsE*we5`~t*NnEfrIGZvVbVtiZS zHK^v$RX#sWnYZp9EUEzjrstM{H*{x#Y@NkNol|jn0=LQStVhtfNld`h|)O zdBRpUOl^Bz^AN$!3Ate!_%XY^NCI5XV*9X4ScsG+`;Q|O2o|sB?3`vyuD{Y%^|n|d z1w5rI=#TkuirOuLxS<5j#=7@3KX^yODV<22^M@cBxjFYJ+y-oN2?2OGR>ZT@pE2+B zdx89fMZ3k)W%A{389=dJU9=6! zt0d7mY5D3Z@M_Gn)#xiY=c{Aukmv1R6WK7Te>Pt;w(?ZvQvaa@#_d|0Ah(%SGLDt> z)bQlRzC69m8EaVZ5uhb9m~@;=EHD<%Qxi&EQv3DT7_>@Am)*TeRf10XJ^)h%kNf8h zh&%vd?x32Uca6+i>1w8bf6qP@By!6n&JQ+m4>JOPpDn`tig=4`t_ z9%FSNDZVTWN`9D@gLbF@@dLNq;yYY4V(7-7h=IEr5fIn=iDm0lfxz>0q6WiVgVuh@ z;>)XW7(?eR^Z3d1zXzA+J6wT-7&W1#$Fbn|QP|k{iN^b@H>8b77r+2$OGRfyPTIF> zmcL5=yq+~xT0&Qir02u?A#LzVr|WE}vM`obQbs5Ax}=_-g@D-_#=e3K*^3P0`TBy8 z1Mb-<8r-%r5a0hDq5sYIf5Fw{2;b-}HS)F|SSp$^cX#;0$Ipkw4?r=FZh)gVJxt+r z4xF3zu!Zkag!bj>O+R;;Oq5g?(`x_P`LO-w{)1OP?mDWhARbC(XUIfq%kj4cG#)(6<@X%_}iL6{N?W&)-`Sc#q{713Jx( zXgs0Y6dL2KIEfmKTT`g~$HlH8mU)QPO3aN67_!JzFDnn(9a#~Wde9OS2nF{F;-bVa zNA7CSlw!6sWXVX_p2(?@D{gU`kjX=o98BlE>~fSR$36V>!f{f1A98%ZLE6CSq|rL_ zYcXqSFmg)5it+@3+Z=+!u+>co^fh7ob(`1IboI`JwConHb`Xuu;iHbZDeuUa7fA_=Qhs!Xe_ z-0T#^R^XP7I{Qdosfw2Enmee7OU*`6;is9P00Fll`B_jkRQ3S0?Ao&R)p`H@pk$&# z&mcsZQ%N-3+~n$R#;c(MCBwq2J$J%=+4S*PojyQBhj2B$`kykB%Mh)+EZ-}J!R6l0 z^ld|sCc9i!%t2AFhM1o9a8>FmVH{^K&XFlc2n$%{zvvr9d}kRl;tr2!o5XP3# z^Dk6;^XsI&?tzQ*-`YqsUY0d3Rgp^gD+`oKY8QZ&yo(3`9s0LV%0TkEg2$rWXarTz zF~L{Pq%Ab)BlAL!Z;4lJ#>mOuQvJ#Fzb7K{?;?`tEUw}!UJ`y*H2Cs^>*uxb9j$lb z1Bt5Ck#Mu-GY1Mb)^$}>F49UvQ+z#Y_&f!$ZzdEU^+=Upxs;DDEH|~%$$~X{8?wn- zV)5H#e(iQ)gvs}MR)em~l|nG2N|E8eMdaUn|CfkFbfq%O)dDuUT*M}lM<*RDoOATU zjdN=Bj_vGc83IThn{Uq%)-OW@oD#cse@0GJJmV0<~-^^TyUq}!8)`C+R8e+ zTa#sJjjb3$rR)SygYfcnoV$&u(LVC|5Rr;pYTF4xxe^BXR~u{0jSl(J)(CjORsYgs z%Pikklzk%|+d$tSP%LD-Ev-UU%Evh7NRc0CGb)=y+O|_B_^;FyArR82Tem3h!0_ECsR}z2dAg zdDr^glPWlsY9I2Q{vJu>6?$M%a&=$?{u1jZ&Lzw<1_z*7^^G^(KZSI?Hw0z@o*&`X zOm>V51csCb2v#vc7YaGpO=E#To*I1?DCNt`$jbGV0U4Mylkj zDoq8A`qQNZd^2p^+ZPue!L-tkbut!&@Z207n^Z6~Y9>Xzv(C-ja2lPdrrVjh{HKWfn7&7`TAy%?T~N44AW7c%88o<`t?Oxbie4mFO@a# zjG7{ML(9za5fMuvs5)H)Yc~1RdjQ;fILW<8eg;rtUTa zxC3_TV$GmN*;JA2t&90j#rL~%&1sYTzh;)M(LSZh$Y6a?g7|b{<`D5w9@)75?&aQA z+GRIuVQIGiJEcd#N$I{=Bd#NjVaLl%8iE*v&~{Fs$56`EVK-UR5vE88;L8juy3r?P z=-FUpa3R|XXS3Lie48tR^kn!hiWv7To*%#Qqg5E`lj(m?M2O!-q^yyLKU?2`={0rU z16mq|#|dZw|EAykvdXoddC+tGS^Q1L#J6s4sSUk5xC!T+83>!+(C>w6u%w|@l zA?+?wF-A33y%?7U{?vjPo_x|w)CI!C$0?Up3%9ceQmOVe?SeJTrx-<8;gkc3%xsQ! z54@E~LSOgTHfFk=ZquZ3I3-oDkHrf}b1f9|Qs&FSpZE}wcu7$dN zBO?|V6$^j)$?~~4h%F1V);aI|h=*_aEO}%Mz)of$fZbzKOCb~`t z5?LqIU#A)bj?#mK4VM|GveG4)lE0*l$6MXo;DK^pb;{f77Hd7SjNVq5?W4VQfp!?3 z8Wj^e8{fxONp~VbIW^g)c4`uo9kFEF`q`)PR?o-!0z5)`CjhECmj-P;r1zmNcq*Hy zr9U^u6UkSqD9s_{$xAS3+xB9S_|VEGM>pb{gR#%rElP=)R~e_Xe~L@`WNJK6)pdCJmJgBw#_P7 zswO6#J2k!1$^`2fUxgfO&lbNW$eFr6Pmfs(v9lRbfZyRgR=nOC5O)=i6hJ|c6Zkc~ z^eO#cpm{);IFLtg{br>KzoHv{Qig}bC$pJxY*&aEG^F2AI3f?(rs<9|lg%xw#+csEh6|re|8pWj zbpI|Q)n*yBIV88T7&on$E?G8s?yN8o5cq>JjWvIm(ALX&-hx}}xyY^9Ja+a5h@w#! z!H)e$gI@^&>gc7?7^67ZnPSRaQO3zB>~4Ej?&Yaqy)QgXUKPVtrOkiJ7Mo}W|NTOeTis!&Hai z6GH`tZTNC$$xHye0GW06yf1tlAOwE_{gnoKo?xBIe}MY+{FR`QQq}m341nBwT;Tn# zAcp#o71~SC`<0>WA9I z_)YE9m2YTKYs)&2%&ZIsEKLgU&>JzmsfMcYD+FR#Aa%H8!V1q1ucebCXTX6$&2R(U z^!dTC`Mvcl@zx#U=7|x;Z3*YueEtV4%k5zMSZN02w@s;;HN zH0AaI)z_N5ILY=7U~x9f_|_Jf+ihVlu5b}BsIOr-YC}`*58C8H;=<+ ztCF={znC3Z(5xI|wkh%MsI2{`qov0f*r30PE5!-b^G_7-M@XNcMkve$fU4MDdbcWOGPAV*;%Cb z;s@QB`BVS?`O$it*!E7W1cp#*F5N`?&bmemT`)LFi0b5?T^xY6-gcrxRNANvH!em+&O;N&UZtl zHOtV={c7Vy^z+N6P2#~XtN?OdLPu+VZrB-ov-anfMe<^VUlX92M98FzO|>=Xkh!1 z7r4Sg7K@QmMXFF2JYf!EfGYthWz!)*!KBk3Uc^`q@0lBQribN9Al&BlwLRCLP|0X( z;|rajm?17PQx=%r!GN2asOiNTNnyVb>fLx%j>q&5hkmpp3e?Ks?3ARR2GGo?Jk%A$Mt^su&3Wyt8Z5 zB=uJ1O~Q&;tBMz=Yfi}gz3BVftdIZnam-)yOcq zT-!jr`(2vsE4yQC^J>`)^8Opt27WUb#y|~n$-r-4`&19-|i<>U#PZu zeH0!?#A=qTh7h&Sn)~=AipbHlEo|jEQmh}2ku$a@k&65owcUHq zYCnag@+d&>?Z#%lDt<6zfC+aZ%1-(nuJ{YBx0?O23aS{Gq47G3B*6}Ir6el)n;d+Vbt3Scy2vd(J`uZ~jigYE4Ah~TplMNlQ$bSmfk7g3%DgXAf?^a_x}`X)L~fdVi(C}gRg6nDgc-c1Gc7o|0?dbm_QaIVmHxIw z>1r~U9XetF;eHGfg$7Ai&>}JA4g^^;P|bXfhyl;|)mTQW0-fuS4Mc3*MoT$+rmVM^ z`X-#_1FjZt?z780>uK>V20F9z{qXP8vi@-)-f^3F#L#{~ z4pp}=W2Gw3@053SKq=)uw`{Sx5QZ)0{0wYWK~E6G(BcMJ8lay)(9;7hFH{#TAJnxdCqM`TcZD%KMZ-`_>qf;~$|;&9r^< z&41VfpE?=bW8uKxsU|k*5sQPnpFJV=I{M)PBzGIGEB31-*LBX`~d1 z5;X53rR8oFa@eixOj`5~SAZGcK_n>p{UtOEgmy2%D`q*+!XyC2zJG@sxzzf+5^$W| zu7#8)W`u^$+^AvSls{VUA;a^FBKo90ognO-i8?5MX}a<&WGygriKC2 zw#=Q0Ih9>T^3tFf)Kmxn;nWLV_Y*NJxqDy1%wp)()6j(FGJfhQ{JF`5ctQ~UCB)vG z{i{yQyb(#KH2e&>P{nFsOFfB;x# z-H_UO%Y;`Yhw?*AYGR&34CPnkE-L}nFnAwtrlbpnIR{@@O|1QvE!AQoX5<$&vV;v-_z$^Qh>e^4U-V|WFftRa@+ljYX zYxc)$@xsDi6ML4!Z^T|Y@CGZ=d?84Plp+fz5rx4ii0yoqhfW`G4Vy!ytQU<2|9JdtWp3kI1Du`2&vtFCI-ULV2n?vmyEFXJtUJGaY;o z4S;&WU~ShsTwTWKI-iJvj=VQI5hgIWbS`(_oC&buD{<3h454z~g&RQ_#qs{!m-XL+ z3-ukYfK)r;#0VPn!qK101yU^#l-l`2>3Kyb?2P42q z18V-B*zrdqMH!sCQH$n-cv@V+;;r>BSKXmmQ9xX0iriRIa0o}nXbFs^y1Fx0`5Uf( z^Zj3N`TAkdGzzcy5nYAIns1ZB>+_(HnI zq8aPLZJZ}Wk3{mP*&CuK%!;`fVn@B&ZdYm26DehG-VrSN@EO|v*DEq}kU@q*Q_vDM zZO)>B)|L*N_0z{YnshV*f~ss?ATd)AynSC2h73Oo2_pr&BS)LDY#%($1g?SUhYhw= z{c|Rt=S}sSjQja+2-|rt~96g?zZ|KwMnQ1*3 zdmc;Kh9!1Y@x9uX1|xP1x3{?Q(OF&TR^uHQE`4be1yR9!%eZdfY!JRWO&61-ic*#^ zi+C6Dcaj(m@dmMzUpRazS|Ccj3LJfzj<=MbJtlt5D1&or{Ix(;_yEQ7vbSYS~wp* zN$;aRkPs+|aw{{ehRf=h9Bo@-NKQMnOF*p`IDh+2vA=+|EBZAV?<+9~K}J2NSZbe^CJ>c6WK|K|I@;EL+B z*#@mkz5wisaNZT;5Ni$E6hw2Z(VowfRv{$EUKEODpL&Zwvyv4M;6@rsnM2faB|>w@ z>(lk}u>~mpvx)Fsg`5fqZ1h_4+La_IUuyzgw51d}Nrk0_d0l?T4gI4!Q8^MYc0cD4 zq~7LdHdUvuK>icP4L>enRop}-SD^m*w)@9U1vhadB#@O}2X#ahay+D|)`&DqIlQFr zkBsHAX;UT#&wYEp%!C??bPEXZ7Z1PhQw@q8VlLxsN>{hJ#-kL+W`(DC_H z&+snMXf**bJH94!%Y-(dRPCRur<(2|mMW(UM!v}oNEG8`p7A0Tbp3`odvMG0Z5!)x_Ym#t7GiAY{|L8d;Xqc z(D}Mx<0Fwb?r=NIt#H-Bt! zr-jb#NB4Yr$pliS4##t>x3PYy)MtQRpkUTv{XLJZ$2aPA%brvCg7BjNZ*<^A$wQw7 z`9KD1&{Y+uGY-t1%KY18ekEzWDt=zsnx9o{6`Xczr)f`OG|w0ZZNL4kprtDeb0~(&0`SC4gVPdF zR6D1N5IEykgC0R#fZ%p^EQ}eh8uxB;gSVT$^!OU*z#BiMUkkmFLZ!pe?42}Ty$Ogy z=+WW;A2>2Jpnp^+h)J|NDMJH=;b_E`29?Kz?q`}B>kmGD?zl(@5GrHC%W!V3@b&`B+4XX_20H(M;VSH=Ykz;?&!2OxK9C6NO^C zF8*3A2em9&OGu!JHn{jDOhXP6qfr7{J%L14n(dCF!o(Wi_}EK&sN7CqCGz>OpWyjC zY^pz1931|R(EsN9zeFUO80_j~^-2ZMR14FfJSVFh6X4-h=tRa3RrbMc>^?uyI4~QN zpIApNIIjpM^g$DK;yWZ!S<-LfGl~+LMF*-h{inQT(Wz0NDDuvy9ko}a>PHnv3_x#D zruV-gr``7+p#xi-1WRap4~Q#+*9L}N>UL^5H(A9yszL4_J6zqI5Q~k&p==*^pMvyd z6EDk`+oNK`EH3BBARkq5ld);*#oVL=&&s~&Hu{^;BQK7?rNYuixH+lC1Ln!uT6;qP z62jLsO%;VsRhGcX=y`K7LA^1Fvur@vQqc3!cNSSU*@#wG(OiDR1Q7owF{xg&!Pw-w zzwCAq=t#PQ8Q~bfJw#7+VorGH`G5lV(%mzvB$+i*?D|JGh!dWv8tt{wG{Vtd;r`m zjeW{9YV_F2s|f0;l7$eeTpGFfu&H~E7NHhc64Hu$h-rw?y-^N6BDA-3caPeVhP?2w zWan%1`?~eYo_XKi0!SOF;#XWKGF=Q};D4mQp5YR9KtGQ_Fyi6naet6=AP(W0XJ=h| z>>h8LhNa_+yu-zgg|7JtE;l28Ls9-vFN{>)sjLfN!UFhx-LTMT4M5Q}Sj4Ezl~1Ps zJ-8y@;R8!a4?Xpv)GE1D)up!f2?2V5wp%#Mge z5e+0n5y?L~3kj$@WCwW1s#K$NME{JeTGRpXzSZ8>y;i!M9q;vbM?<=T+$zP_fPlZ@ z`ZwSI1y^xd4&t}83!45|Q?djzNQqS87aEC^BOAGJ3X2;8>-G}6?PO4x1CP9_)(blctc#5~Sn3$kpYgzM5- zKU6=Hll26&RGG>QJ$haI<&#J4o6>W*zTP7eJNdg_@vewNOf|iN)3KI4nvQ8q3Dz00 zcmUN=i#COeThquzU}bUM_C}zVl;*?A)kwTaXT9RyE0cQgp^c+`c{TiYptR#knX9+1 zYz&gY`5{QvfW#fX+~wbzJFmm#@lt2Gi-MTm5*NQd;F6bzYaDsKp}m!9N=G+qm8L~1 z{qjKX*jTOG0Cd^)gn&W(&-Ax+O&<3y5?9~EXAHo9Fh2VzGfUg43=hK&8n$y+uW9}r zE*KzmjZefZRAH9$2r9Wn%d(cr5VXkRhHq(nKSxg8te2?2chDgGWcuHO%itZZDm1@j zkBzL{ZZ3A(CSn$a$>0H0rzQL&AO6QMt6h*uBnvVHM+3xLO$o7Hqk)=xhbtkNY+69C z_(ge&+w;Y1gv46LS+7%72mlR#}sdR{O_|@!e`!{p?pH+*T zP_yN>(w>AqD!NPH351X+JmvrjTj1orV@3xFrsp)m90SQEG|qwq4~M>$IJ+JC^ijHj z0L>}+1G4=btt?SU>CCGUbS%9@(6R(ZgAYZsj3a7#(UAeu@_mPffBaiJcfyH2`Ef$kh%?ox+s#m6HQ%N z@0#425O4b`)DJap1BWaE1Wm%z0F$y6e!wN5Lw?gmTO+dv>Vq z->03og{5$%nYT(L?#BY-!~Fal1pW>mY;zAQ`^ogb z2kh5(VDs*8f5YE4pB%2TyfVA94S=W8->{R5$=saQ%oyP`6d8e~1s#*^Pj4Igvjt-s zcp)zzW6m?(Av6$=g6}fp#IYOxg5A(I*#IglrZo0<&9;y^)@ZS-o-@_lk-Z-j=KcG; z`fp$KFJSYJ&=!)AcjZWL0-CYp#8185>YKp|fz*2wd#FsUY>nu6esaKCLrhfrjW1g2 zuHGY}Mq<{1a(XaebQ!hTdtBh}iy{@cei^)jNwRCOvAaP)#wu<}GBcHgbB&#Xy#L?g z`ljv-Tb<}cSZY8uPm5Q(tB$uZj+iA;`uN@H#Dlzt$mSE;kz&4==?BjV6v!ij?Fv@_ zRih;mB*!m4>594G@4a+<;ARKsoQfd)RYBm>T71ZrPLi#j$0JS>xCE1Rn8-;zyw^ca zZCVobLCf*qGUsk3rD!%!C8=4_)|t9C*oC~Ra~@w^E-F33kr?q|l6{UNMv@erwotth zAZn^$sMoQlf^Wsh~875rEO3mK_qEGDkN5czo);IeV{6v0X#W^*jX$s+! zYhj1mnG~UmM!M=oYIV}Mnf!&Ter;PK*C>7=%JTEdh^fTkwNzo1POk5SH?`QH9GQe) zuao^s!Z-T>e50hfjud0~5qr2)ZIYZqe2|u4iw0ioVhrIf2T3RO>!mw{+^QR9gb}72 zYFS(&JIXy{?eV-wgFMx#&=GI@)$-~*H+HsJXSo;c?Tcxz=QDlwHyD$kwB{{yXC+i| zX^v+>2~~o)#Q`Dqy?MAfW`A3w9ac3xA45FEH8F6)iL)Oezh6jGHSAY3>**NQgL069 zeh-qt1chfCmTb_K>^nT=$rQ~*1NY~JNx5bs`!M#D?EgGRrzDt|`9=1U5{Wj3%V@F` z&{&z2{q(Kv$L17hY^Bz(r;qch(7~AJVGc}0(=|R*)>oUcOE9W?eZ7h z|F@P)Sr;gHNNqV9wxc=EFV%6D)`BiR;CLN_8yrY(tUjOjW2f)u^D6587z8fA6{Cm7 z_8<&5a+Jw_uu~f^P>|bf!*47PwXJs%DNaXM`=kud^7FTxW#kFW_=opg4+uvH zp)A-eZIyyaEsp9uMVH%T2#^X4PGaFHmwiNjfI_cNYCGa}VSHN;{yhYjG{dQYwJsZz zipF7#LX=|CJ>(n;m{4VkTLez@*M#Qo3ncCF;u4?-$|IpkdR?!*mJaL^g`EeeQ$LjH z-+0@#b`ZU%702TZsqW68Dl?F1j3LZHKu^VWaSli0(=lr=C9f-I(vgYP_`liu(L{8{ z0t-d6-n)48g)HPGcws>MJsU(QQ+IP(eAmT+1b#8+(Rs*SH?_DbM`&HQeD6`3@2qDs z!2*P+%)ROP{k)UO+f|d7W?{n&%}F&%=VSAr1wMprL@T9rOro9Wbs->Iz2iv~a_{gF z^^-#zSFM~d+dao)B9WAj+rx`rSjwY@6>En>#mKkpBtNFteBK9_zf)D}3SIoi0-ei&{*VmMy^VtcylFqu0{us7 z^HrGCD`f0NgB!2JJx5{V-4s4)5Ok-lofy3(cAid#zb#Mx&*RQ-0LX9qXn!AV|Lrrq zbNIjICUu1IWi7|W6<7X3id%=QQ~b4aF}$|j+bxedLxxJYvC#yr5(n+s`GS=p#19K% zWk7u3QMHz8X2fU)Eq`uhu<*QK`*sTb*y`R`&q}yK^ZLVHeadJ9W(b(}RZF8QVd{>e@h#O&IL&C&cIrR z{8e)5={3Yu1;BcKKtGRc`?jAP%uNmSm1>oVh4Q#6X%Nnj5Tr~yk`Ch>U|ucR!2r8W zL_M8+HKcSTi8a|W{tErE@i70$S{TAlXI%*Z5yzI)K z4jv5VX2YkkIki-b-#g3y5#-u^L+xN$bK_nigxW!gU6g9io=h#JV;KOSfeOEp$@#v+ zB`J@t@(C`ZYrZ<_)&lI^Mue{l&Nf~H&f@^9fhtRLP6~j%Jc*wp=zkBcig&p3`zxdc z+WC({xtKJis3TS~fh&fm^DCEuo|V)@5f$_VEhsB+H?TLb7nNs|>tytx=v{%8^>ZOa zMSl|+mg|T~BbMnX;AZ}5qWdv;#!%)PfHPL0N^~k{NZ!Kn1h!om>jsI}+nw9ACRlCI{s?lfD3yx-rESON zNZmn9`=We8osE(s*HTtvM+c{>_R}s=I?&)P8sMJG-&S$Gyf!Oplp24I_E23uaJoyl zql?6s!)hp)L9o=yjQ$3{^XnRD%W=L%QEwDtk^I>Lxo!qgyz?g<#5(2H_JxoTyG$S= zftZXb-o03EX0=%5RYu>G)5wfd4{Iz_U#=IJE|4X(%}6iw8?3hJiCJBoMg7CN5x@W{&jUku_cZSlqcltoZ$9A4IggmVQI5~zF5ebdOei`5{^Q}Mj+7v;EE{t= zv&;^Nf&5Rn2J{lMke7062oC4&s)bZDVeecN3UkgxXCiz$D!)Zzzr%&ghOYbxE^0h$ zdn{_jn=+6d9c1c1t&-SWCz{bY;?3+8o#Uee;?(CTVbq0Br2pi0+}_L zdqHzeE`G4K4A52RX3YFUc6jA|&qj&Z0DMw>FBZTF1fXr!Zy@}C^Zj3NmDAl5Mu@_C zrQ}Uq>XY$0iT-%nY0g)nS!o*&#O(?;9f~+S_Lp3-+o58Be#J(a0Iee{Kg6|SvZ;EB zi%B5|h^IOVDv!WbN?DV^)ONa6kpO7n0BcA`i%!?6>GB|b_$Zqet+i|~tD7KwLjtr9 z5R#DQ*)N{e^)_@#Ph)x9;p>F3^Z&CHm}#ejDGdUy_j~MFECb1L0&0M? zb|cYu3SNqza#gPdN-H9&Z#Pog2BTjJ*sY)q;5s3V>U%yzQ9;~F)uEPO__LrUeSX9> zc89pSPmt_R!jHR-#V6jn{1RawGoshFGG1XJ+kOJ~N4J;_)PNtVC46%a!Y+SaUyBZ% zog?Jp6$gN>c$ky8Ep+I*~G^nxIraj7s72?>v;epMDc- zG;JAyP>0o&Ro5JDKo3d%3-}{W4V!Xwg;wp>dV$~-*jRSo^IvddQK|HqEhAT91xbWE zK)K0XJ;D}EnUSpP-k+2^LBvnm%~Qq^;|E)X+TIpUn4rZr*y5}58X|s|`kKXz3}W>Z z&PDSNH~o+AFFfj%M+BkS z7fe_rog@EA5YAjqvk4mWMPI?#|2|ehnrO4TycIE z$ExK{uubSC_q6WDXnRH^%c=OI0*DaDCKTg_u_~YBzc0Q1?KAzQHi;A&_#pVW)iG|R zMNmqu62BQn3OY#6+bK4)10SW+fiXO2Pz0fPd{EP6J z?(92SCq(?cWmqXOvTnJ~*Z#TB`&Sblhyc7jV$Mu7Z4=4W(M=s9WHM$f(@%u- zt~Xup9d(%W2kf%6ON8ah5M}`1?=`oRx`a!+ho+P+&tV3d^- z;UuO}tM7}}CMH6D_~FAu!s^7t>R8~PbO~?a=^D>uW$xz~c|m3CGb<$+_03YCW$P7{ z^rC(R4pZ?c;j3qmx0j`CTET_Se)a9+1MY1PYGAh$#CwDa3Eyq4amA+|=b0x$2vvgp z-=$Yk7SkV1eG2Y|xEMt+`7A3`r-bO#^%bFkVbVU#B0PKH%fm1_K0NYtbEU(z^$xh(EO(7 zJ;<5^^)4cvC+LcwL`1amvR?#p29yj_fo5heAjXst82nk@HgC!{6R51H?(@>o|DK4D zy^BadC(vDfeTg65W2=A~PmTGgfv-yY%Ui;}^uo6?+tta`l;NP`Zunnk>#%gU3$y57 z>V$vp!JOZdPzW07pS;4VvG^+ZcWF=HEY{6c3AmmiA-`6VsY9q|;K)i*pN?Yxed+aY zzJC{y|Gln>^`kY5?G-Iajbin-28wfI(k3#f^I!Aa#itpQ8wsddg~!~M{6oKt(L75i z5sO!cX3}w&gn_kt`ppcX-yH8@+DNw~H&XJbQ~DRvUI(r6J{h(m^@*MjS+2KZgG+bk z`+1-$nJq`{Si4ZNVD33_Imq^xN|%9MQFQj>M#pQ4bzHz>*sTyjoBi32jF^or6)RS_^(hR$__o!C`mi`=Mj8^+v3!jxe@04hxI^_J;V=RhfRDM<4eT5vz{EqfE)BrWX;*cot)Vfw6rI zMw2$%c>jWxva<1~qLAEdJ1_f~V3qj{yq-K)X#mG7SEkNf*%tIW`Th>vsz6S6z|-#R zeeXBxLemOHKWAeT-R$U?<1Xqh2>c?Dt-4%q)?~(nT&s`sfU3;mROTe+QaIm23Om1q zxf2vBq>o&zD{?W(40U18dypf3fio`rT17p z7l>nhq6&+&a{3(D&C*N9?2vV$Izp(h*gY5p#s-pIDFmfY0`Z;0m>G11Prs;nd#Yk{ zGXvuI|EN2s@I1F@ZO69Vn2pmUjcwaTW2dnj+qP}nZfvV@8aw^J?tia!W$lw5_k6r3 z+wK8e;4}KaY*>|n?nx}mMR0$dmUK* zwHbtNsfSev9p`#Ze$Y%MT^+)4X@Q$rz%l-iQ^f$cBi0Vv9K@=jKhlDL)>Yv zP^5~IDPaD=c{KW?tM;YP+(OWymmA*Cxx`8p$58}c-hWN6KmDeEig{21Q$p_&9>?65 z29FmpNPVd5{Gh({&HIt~dZ|P%=McYhdM__$4)yS#!N3OMY(GFxHju}HPK)Pn`|9Y^ zGaiZHeyph+d#ZS=ehFr5L)baK-0uY&y0ycG!DY-(azVi`pjW+9j5xWXQTW z5VJ#Ai781S4kS(r)0*DbG^Y%VY@&X2xCd!79sBzJ=x!`8`o;xJ`!_S21KF536fy?# z=Pp}U#Q}o$+h8$rQ?J?I9IEi8Q~YA6YhSzdSIxXzDB47VJQ}#SFkp=X)*EQ7WBh$# z@x3qQQQcq_1TQKgw(gZx>J|?}C}Cq%X4RMy=85F5gn8ZzfJ;9c>;2l=#zSRHgD@hw z{K(}jU>|0?5w^6brIrq@Dr2%xU!!~NuZC*^09QcinD%x{YJ2a)bL7)x{u`~+dnlFgn~o?T*o$RU9bVTsPe88lWB zKyAD2=O14xGGmhYU!V9A(bi>J5>0{GYaJfmxNnYARiAA1>7{yZt9KSOmIj-L`L;DR zs`0N{LT%jY*az?|aH{E*TOqgAK0U)NlKwK5=FWcEy|!G$Z02<9+%>%5_2Ndswl}}) zxWIUj?iFUnV+oB$!6i!9P|XGArV^(;stYLX#@ebQK*}*sr&&h0*f6{q+G0iYcBA;w z>78={`5c(?8!pFl0<`KRK>@h$0fnV79w8W}_`}$9cBhYb_Ojciq2m7?mfejJs{d79 zE}ydjF66!fREulyJJ4DSavNF6Sd|$W+dTj+mLM#-cf53Q$9lT(@hcZ=7;G%VcE{z7 ziafOI`Xc0UN}!jfE$zJv{rl=xHUO<+oQ;0bDIE(hcSXNvp~**1!#1;P)Gyi({+QVj zx<)dLenBD0b8xFKI5LnXD>KnZiKQrBFV51O{8mwmki7fR)kKtJ8p+@l(^p2PUFaMi zHPX3~&}8;f6pc^TcOixTty_P}{Xb|`j*Z=i8yVOe`6Ah&ZsqkM9!6t%i3}_#XWpC| zWq`-GJiK6_;-QLKz>lic@!{JeGG^FfUOVQD@U2cVN7vRy*1#Vr1)|?OyCgbnC=zlF zaQoDpdrU(ynJg!6@WRvp(8~P<6yB}BMV?}mR55>usqJHHT|Tb z=-D52rW+5LOZ;$}BjAqwg%Pt=n*9+5a=on8t$*i%`ol+SUTq*c?m=K{%!GdPqn}OR z?hrTtb<2dkXGb5cDxN@OK*dl?NeHcIFv);7sQ^B;Bms+TTZl3r;*{mwLS{$&RoG9e z0;)~dEEcG@p;NP9*~$Hu2$l$>cejrZat$()6z45Dzlk9c&cUxIC9}r*O$%`|{x@3R zo)i)fqM!V>*J2lEBoh=PHBQrHg*4q;AUuUlm|ul|Hza&tw}g3G@A3j`k%6Y>6E5f_ zhBUp_O;_|3c|+}*2J60lk_W&AX^r*%u2NVU*Jo^jI>tVFqef(=nLQq~H^I z3N`#A3Wv!NRnrTpg#61SBxGu=SxbF&eC$Vs5?}veP`Zy0e?;p2opl6sw#g_ltGT`+}+skY&}Vx!#?n>q)H^{vTDiVGFyI zB>3BE(NY9pw9EWqyQD)rA1pED!W*gsIN8<@qfgZAP8AlY{VMORcUnh#@;s?T$Ld&g zjILq7u5zvXpvtX_(5=kl;TJFEoJ9|$!?Dq*aZbd>o7A$4@gL}as$hC)l+uM53L`D| ztb%RjfZM1^$(I%H5s-u8Fqb8S1hqH-qP6z=RrDzxjN^P!1$}&NK>-00!m{jHYcOo+ zhLWKb_))erf+9`+B@?=B5~3;2HJAKg$Sfq!Rv`TsV`O^FBaN+f_ChHmz=Rmzz}U=MnogHtvdI4E!EH~@cH}Xkv?;y4 zrbq=T>K+^HsA3EnLWy_$T8t8zvrH&NCebex$!zL_`205_~<&p+T}Fe_fZ0T0$1!P8;@3cs3J8yzfv?LZb)HN_07+0*J>j}*)iExBW zEOqW5Y(ZmE0MOguXvk4jegP9uY#X)T^Uks;3Q1GSP$3x*uSiD*3t z&rFk#e{}$!0SYlIgKKi^1o`s$^^AmMmChHH*|H@+WjRIhE}(|nA9w7oh=~I^4)83I zCEdb%rd6tBQbOf2E_pXyODDN-X!kGh9~tR84%-!RH&B*%?xJ)QR!sEZCD~oDi5F!>2I639bEv>U*n6G8aaFZ)l8o`bk}C2oZa`W$xus+ z@l6F3Y^47#^9>w~2}Q0YrShu@-OZ1O0%m)u%McnV=Tbhk%k|+^rSn;OIdV>1qUg~Ubl^5PFRXSRCp@)uFtt?L)@B4s3F5Rt;VP8G1@EJShOjrH4@0Gi_Ycz>&6p z@T?K>SQdBmdBgqOPknHP<{RE=`~yx^0Zb|sWBa-1XLZ2_R}&tbE4~*0ioyqzoP_9d z+p*1dP6#E~&d6_>R~c6}>fCSsst}+t1Cl`|r?-AD#Smssz{btRgz9F;YLch?v8@zg z*dC>CU13TIeEtQp zTgaws?9S1T?%AEXUAvbOgjCWiKZVdwfx{zcJTpM|(UN!9rVRK3c85vA(+6y}MVL6L zxFE6$!iGGGkw|MxPV3878V6m)aeU{v;`#pMnvS?yG}f(~f3#se<)X51L?+WxDIT`i4kLSADQ@+ zUe~o?L~+-UoS95M;i(`Pz@ZNbmejj10-e?$y*#7YB(Te-WypAJz5+uSePq%OKwV^Y zZMKHGe;4}KarogBz+vbjP24pZsaK+(tMW$;59AIZh5EFNB#^C7A$H?Mj=NLVH)g*O zqr_Eow&M$5CvPx2mp^KtNb*{*h7^#s1n{( zq?roL0Hcg`+4ApV9#BFg@@N$lmKd>{xj2lICZ2qbI0ALWzn7rEc)%Ij)7?*MfMokd z(yr8Wj_jZ=7QueK@4QwzX(kkn4s*=NzIRt5aDgKgfPoJ;^tcdwe}!c_?#H?)%z1Fv z{B#jA3+56mZovJ_<{|%)KF-UHktL?F5z&J{-(;J@J&|tH6L$(IfatEXJ2GY3p>5}F z)&J~mq;|&OrQ5yRk_!pAVW!swb9idM?yIv6KKK^YRT{BOqiGfg(2XH&Kf%WoU7vYA zUKA)U8}qqJ`A6ms9A@P0{)!ok-q_Khv*e_6^&yWdK3G>9w}3poQ-eFC_NeJwp7c?` z_B37i-gm#f*+p$GvHS?n9rk47lf2Q~#cjpP)1X zvn2(4RS0S&VPkowi5%B6}Q(OP;$kAJL@8v$%*~p zEKelUMZ-uDh!;#R%icvRH)3xXhLq-C$L>$L2S~{OT(7R6WTlTiPrUoiT4K0aKrP#P zF$@@xsYtVj#!pv%Dy4dV-ww3{Kl~=)8iRV9EsG5Pnb0Ee;aDPm{#Fn-Hww5nF3|TC zd}Pg)1%6~(UM(vh!qrSJd^NO+l>=-n{RL2@y2DwRd5rZy32_#d#i*X6B= zeqr5Pz}s0|8}{n0tJkZ>=hr|i5#fFcAu1ha0Dtpx6X1I(7HRNn=vFVM2ZaF2q#8wJ z_SED;dnmz)SXh)iEWpbKOgGW_S_+GADHki?52LE53YO&yonaWxJn~WpvdZixrRt#h z;%lNw3WUV`G3@{-G~o6goi7ACM8fkfE_D_bG=i|wR2qn(-RW&y{yLoo$OBOs3{B^XsKM$XWQQ4mCUI0q zA%~Q74G|SgewT6@JJUJTn6Fz`c2evtpg;$BgwLB@(B`yOOp7GFjpc^_ce9z^@tmM&vxsv6%PC;xqL#RA}}d8)72GCu7(gBOYN!1SlV_NEg~IU$e|rM0o( z8<5?j2?r6OJl(`RHR;;~YeBHXoa5Aj+QOKqto(9>Y6=}V=tzEDIEH+}d5`|&NGzz} z;8Xp0aXLj!5%Wj09GBLU#~@HVzl1BRvqdrH$BkFn&#A=$Y#`Q3dnMRtxLucn+z$q0|& z7O!%Br(#eNN;`;XRWNx>4Y;Is6DO$Bnsi8gFJ}Zi!qR>2m?^(JY~VWlC(bU+d@Y4q zNp=$zsGibP&`-O7)qU0?txp(Qy0*7f+xK70*5y?b)sL2a&K|Adl?0Y~5ju9jFCucj zSSMb~?w{#Dj=11jP8M2vX65#xGkK(PdV|?xN(Yv59X*cSziEBSy zW4E;5x4J=sV|iz41VD%_lL+cDm&IHQS>Bk1GUz?eUE*LKqhb2RUVY@l9e-M{`3=`< z0G_lYWLYWA{S)0|LqWmfG$&mu*f(V$#gfXJQcG6A_3HoF;SpU_yHw%aQ0|6ty{Y_y52Z@-Y?9E$X!!Gq*xl3ykBl5d*8GIje8T=b1?b*>Ls?k53?< zqWOp1J!iEfWr?q>F{aj!>W)17)4?*3iB9}F3_dFq@Bn$)Z(^~J^0XBgg;gvBv93NtN|Wo(C@GANv~=k`FbsaBaU|g1;B0K~`xzf{ z5wQf8<%P!4(voM>$IWvQu?7O+V_DCmPqeXw&WO&erchsf*1g)FVcdi2qDK)Q!*(_{ z)S;6K`ojZJ?e48+vMtB3){JZDcsPoXS#)JFuhMC&^u=)J7E|JF=m*87e=08>sB#`_ z?Mm7x3awJJ?IJ|V3@W8~k!{+@_ESvbaf_&ck=u+zW&|mj?zEa-H2IEfgI+FF)DyAb zM7*TZmvu>0k_saKN-43$(Iw;nCOUe#tB&c^C^uXtIvU;NjKy#n32yq}`qNsp)cjO| zgV((V&O~sy%7K0Zo$|T`V)-%sht#<5G@9Piz>y0rKR1D{@y$nh!M!?!5(+U^VQh6n zMSf8bEGaWN2iysNBWSqz=&tIP3vPMvmDX#M>y%idvqGUvJz`??+u+dnqt4QB*vD$E zLf!VBEXgKm4{OXWf`Wh$v~K(K_QV~VJxBISt;w9e41gVOgTm(HcnxSGnUHnBNxT&% zo5{k3_Qh<_tS=tc=M;niS#^EI7bV;p`%+|zu&o=ul@gik#mF4U4SW9v(Y29q{4sk| z|LsHE%V)Q*f~{vj+mmx2WOGD}?U)q;l4c}+FMe@EtnE>}Sp?i`#FJUw;MZ&R@<-j= zy`Hb1gWL$6AL0LZ@JyKX$lH z!_TZvgxb2`g->K8JUVR~=!2QIcd=}QwPM%S98}f-5g97L5__i%?oKtkC{vo>wH$)P z4N)2Qr65CEXz{Cx9|H&_j#@7p--Z5tBEk+3k(|b*%L_a{**=S&vPVhugZx{e|CIZGL?k|oHP#;bn1*)7!se4`umrw7DHi?t z17B9X(&rlLuH2cNKnS6?p>67)+onYQmcV1;Vti=jbO* zf#ik_6K)hE1JKBdN%u2VO%_Q_w1x7P(SS41ao zgP%jF$TJ93qINRLRbYrX_I7FH2h|AD&$hRXnRMTnvbHs-Vd(S)W7gf^E>7`vJ55Qx z4==YEVsaGk_fzeD`^8BigN!I4v;R1)Z~E$$cfXDlAfxjWR{rATLs3Z&$V7=!@pxD_ z7t@DsoA}tNxiGWu=_w6g`h&_%KVDOOAiWS!pPdQv3^?1XJ=GM7#2wYKt<`>^q!H#h z**wJGEg1$At$k6`%d^UglhI&3v)tu31M@$p=mc^ER<8Gtg&hC17ZzYC9$V!1K>xay zhru^|Eab3N0Yv@fQXpF-&%r?PxS8Y=j!qmKzXJNRO3%hx-JD7^1PXyO4HyXrv-EJ6 z$=o`J)b9$t!9qxqJg9Xd)20=#RyKgw@wMirwh;;6a{g!E616V#|9wKQ3S&=3NzsPN zE)Bhb%pfPVrTE$Q;j1QE*0izSfcNUxwtbR@(FiG1EmyxYQ3rJfU%L6 zQ6eupg4;n5unH|{c^CTk!37I|s{*NlV!?rJZ5WZGy4cMJBXDF%l&^RQSRLMT==;DT z1(3LY8bOr3J#ss6yh43Y6AY?|Q)L#bp*#H5JR9iUn@QF&jX{o&YASSDlt86Km?cL7 z`lsDPGz)oYSd7qcmVa&HKjr=(xSEo3ZnM|IOIKV_5{`RfWTo z1leX;ECa}-$+x|7bzXN8Q?N|EIF+Vtn0l8ztbE3|S7@(j3gW-T11(OFl-*30tM9ji zLPBwSm!z5Af^2YJ*!gK4%dgi_HrJrh7NOX4Nb)n#g19*D zRaLu%Q%Vc1?#SJ?rpL)lIEO5{fz5@o+oQPBscYhcL5u!vq`{*TnxQBe`uxw<{Rk9nmDlar$h zurqyl(Z4#n8V}`sY9+)*E5z5Uq+96kMN*D$s(o)4909^otne8p4br9IwVd?(#!vwT z;B{g1Gw1oIWdtQvVmIn};EoKL%u+l@VY}km_IgnW9}p&u+5{aTJ*x4G7!zEC>M{^Q zISrZbFcN8|xr&Pf!mY!D3pHJU~{&gG@YydcnRP)3f zCVbT5KfH?9hHHF@Or8s!#tSJzmlq5vsWlfZQOfH+YekS0a&V`+EPeikYVAmk2rQl! zoamC5t{I@R6dbc$XJ$bRt*HbNq0=bpa@ns9Tw62?}f)d{U!j1|I^CC z|MO37C98Nx2FC1k3(T>q1w^!V>EaxQY`(^?Xf?U5$E7yt>rPN0_Q9DMa=B3=*vV%& z1pIkLbRq}%IRV<_Y-*aUW0*`_)0o{59;tIqJz1o8-t~P2M$swu#Me2By*cK@Z#NSkD%0>Td4f8COzw`9=?gwd7Q*K^F8Fdff9R zy{1ZsloRjC*f^;%f|Llp6$Y$0^-Rn+E8_gkFZ>kXvU)#36nbzD`@Tjz1bwptl3Q+Y zBv(8ho!aC{DZR_yuQ(-GmP`Ys)F#o$=7%R4Lja~uvuaIh)&*4^^tq%UW|D#TxkXIy zfcvPepEIDR+NV3+Jlh+zK=}M!VyvIN>oo>mrI-N6h zUyZ+&V-rI8?;BrF7JY`FWVgL1!|KE;B}TCL%0Z_Q*j%_=m9|_?B>RB7#Bf}ESiyisL3E7BY40`Hj^2(V9NGLS~E zH^f_5liIb%iczZEJ}}Zz#>n#L5n91DFA&EFB-x3|@qgEkf6DzoB`yr2L*08A2v{b$ z860aN-u)+^hu~Zs<;y8S7-WHiTR-Y{%Gq-?7?32eib1fz+BvFB)q_DAzCl@SftB|i z{1tcV-3NkWjB%$E;2CP<8yRSCE$Jo3qE^e#{nu_^#su@eI~B8#+0%Pt8K8}(nB z(g;U{XlN*^Dv)7on(`K6E9uS`%Ck;#k|jQuRGuu@MIwn&q8wLl%@$u5bBuwG7b zw8J=L6w(}jH=s&~=7I)`ag%~U#Z;A`zAO}XFzie@SF z5Me%p*wyBGx(`W4r4y_QuxOGnV0dahE!zc7&&mN2w%XEd=8dvE+fL$U&dLc@E;O(1|$3T*M>Rirrr;0b>VE ziEyk~ziAxml6AAemW#OlMoTdi*cjJSJnADgzUlICdwF^Xp)`F4x`$<+vdiLTEyn-7 zs9(FP``LWz(|PZkKZabq3WY#>rA<-YqXes?eA_Xy>ni{*L<%gScf^$SxI4zjc0*X| zosSN>ZsvUb&`P-7>xp7ud1jwN1IP0&^zVZ!0037@bOVU~k~^w4Fk$2((hqy>5I9MvfYw z0whLQa{5yjIfiI1NNURZUvF`bec+e=b$I@i`+wl_Yd+)vanweH{6RP9)H%MPMLIrN zd#7v>kvNb(dh7x&%k9;h@EyK=%qxl{c34jisa+%r>iko}Gyjl=?2!~Hr5riL9 zha)b}GUOIftO^cob% z+u2)@YS}nz=^p5`H@v*~L4rU;^dMk1*v*w&I5NUf*`!pp0%( zR(eQRH#yJm+r^DV=6Z>n*s;2Q-fvFtpX8$Ou>KZerV@+XNDiLvZa(!$*3!DySio(z z7rCAplGS5Ml`lZ|OBNL*f4jCWL#Y)C(2vnysi}n_ zo}j=B=Hk^lH$OsvNjGB(_q>v_Hu*CjlE3<|7N1oT^O-4?-gRVTa}Q^e!fe4Kzyo^! zn!DL7qio(sp&JAfBe5<$d|z&@IO+K&;Aya0GfJ;lOj9`7h2FLCut2yrM>EW4Kb*S^ zBIi+~tF3JkC9)4s$)H9)PRw70zp1GJO?L1YSh1yM)N#a~{_0Ld@EZ8-@Bo8Xi)Lzo ziMmqm1;nbANO2n))Q4-ns>!6S98kGXK>Y9V=2q-W28VJ{Vfs8)sygx_s!-Vqgxa7@ zdAEX&XK}PlYTYqKYuDxw`(t&bop7PKcqK4wBdusaA? z@0*F+aZ)Si1T>Hv9}nzlhM{Xpu*Ut=0V8-J7og)C!l)?zt3^Z=AR>k1)b>$)GR>Ln zFrFza-|_Ok%wsfjN4isufaC%bUCFGy1aiuLhb)ujpbwbXRuO`CVg%-bHa7YC!VUxu z3sQoDXBwzMZK`O=V%Fn9Quy^jFsenNHK5uy;Rko`3JcS}o6tYy9v~wB^Q5($D!-}Y z#a~jSkLtF{M>KyToZsRi1+}iKYw84Rt2BkF!;Blf-}KBIvu6oc2I35LyxAP*(FZc1p@+l{NVX?7jQY>?sY z0ckjP4oLS>BrGlWEyun!)hha19c*^~@J=(mK^}+ZIR~LD#CY1^8EPmYFv8PaH!61i zd|JDjAFr3HgcSj;%X&Ya*~h3z=7{`~ay7ktu3$pYOIP*Rjmc|yU@R{m{S8dOx!ZFEO$gqk*`b+aR9TJvVQoXqWZExdmQrB>jMuN zJ7|D!OsUwTjZ5)V(<~jPHTlNrFu) zsT9i1bE2iR%)^4i^uH_RKmDeEig}|%KPM2U&%(q{jtOoeedp}_+ex_qjvZ9_+&a$^ z?`0PCA;7}7M0V7u(QCpneHSKs_LJv@gZ2 z9h?&N36}iE6ZQ~*Uck?6?H=lmwzu7|2tw3!;G|_wf2-_D2*K$!&rq=|hHn;A>G>WJE z{?eTbo_~wnpK||Ckt>X%YpOd2hl*E`;hXk5;em8({gms^M|q|p;qZYUj5eQl`1MZ6 zVxZ1i<&fU5zZr`73x^CxfDSg3k)uxi*a@jzaXjh34oezIE5@$Q)e5Wn@_>tC8NZI| z*B|;;0zLq^f^+w{!BO0XgX~=S>~Lv>L19g_OoPf z3;UuUQVH|sVo;P>{K7J5O%oar+z!{vHEpQ}UPl>koYel^o5 znDtCRP;+-1+o?E*sIPNNY&*j&C>&ZhX)T)oBfJYgcx_pzZ}h+}&KGkQz*N-OLd6cz zd$%N8u&pd7QVSG>$#dYlJaR)c-E8fuK9|MyrMqvE`xZx}mX~Rw9htQ7eP|hD(ZyYTd->gvzsT zR#(npKI6rrvr-MtLcGV_8roWfgZejI?=RZm!ym`xKlSS=OWxc~UYO(T4BwCn7{sI} zb|*|rJYBzU0nGfJ+t&+-L~+fKPNrs3;wvgH^{?uIIRuGWH*OoY-p>FIRZ6h<-obT< zcIP8pzPGK!D2H|BzUeFGTZPn4pf7&T(%NbV)b+l_!{5i@6oA8IG;Pn&Pr$Erf$^4{ z;Sj%`;CR_m5NC`mzKF$0I7dyM4)4Hi_`~}32$sULI4P5Qk*8o5c85;4KHPpQy7)fUnKjII>8erB?bmsN z#1*;EyW!d~l&pCEh*!hLedDgY4x?*?yg3@=+#3OV(7t40iMnL8T#^ir4jLOyX~AK! z+AZtE;<bF=*h6JS&;!#?~)O zn@>kc<{~4U2b*w<2T+(bu@;9h0j^-sHKz9cbVAvSL)+hy{ z2W?Ky6sz@eoLmIT%C$TORF;C1)Hhkc#`^5M{djfexnqwJp4>>kXEpZ^r z8Wu%l;OAnKMlaQQZ$sFS8T^R^7CX|hYk|00jR!gwXbq9G;jKJ5y<=^;H{y$*Jf`MB z=#Sn~eJ8H{zHg%in;J+)4zS02jhyBC`q_I3JXWv?m3Rvq2t|`b@nojujd61-5tbYs zA{`~qI;Y*L=@rWbw>qGUs^*n)$`0m+m3;hK5!;!!T96o08g7c%Ye-rx)Q-xyqem=( zo=4ue5DZph$8Er-C2J(o?^`mBsJ4wgCEUhsY5PR_&B+PIYaOb1#no`vy*b~p@R=Cm za_RSuH8;@=lFw`B<%}_&5@cJVzoHwQC|XUsdJps^7jvf?=6~MSIJRRx($>xxnlo`W4F%I;aC{EeAbR zAQUpwDr3CazuD#-sJP*$wKa*{oD{}!`&0Enq_VOiD4k1orjo0?MwS0oY2)8j@}F}5 zkAOtWDzRLQAu1h<%E(~k0ufWV^L$|t!SJyH8nWD*T9h*Z-50{(OkWCaj^PvI2Li14z$s`{dVVf0bolA5o zyco5aO{xN`)NQ z>2UwX`=I}8dI*Zszf#viyu4*u#BSu?7twisIT8jBas?Wf&AKAkLR9BaxT#2O;8&;k z6)E|Y(dH2BH{a6u8fs$6AbQ$B@jQ&?XYO?~HVwUJ4m!Y{AW5_~g$1H*vzbsk)`>G; z*BR8;s31WH8X*<=e@{Nz`jPObu>K|om=CjWvi z&Q&Gr1)gNfzSbUKTFV&Vu4m_`b$`n+qzcA2kjxnw5Dz`yhE+?lK*d7tWRysQ&^yU- zUXkA(?)&v-k__KqWmBpk#$_$*3L|S6#e&v@>`d6Xib70O6I=YdIu|h@7;vuOIk@%0c?47>`Hv zq=D-__ag>IJ5Oiew6c92?A{MGm(}Lq?CSz$A#f3$%UC{OB3hWNiVz96Q{>zTZOPD| zuq5)Fyv4W;)r4G&D-dm5r=3OoWcroVbwHDaf0%`8L#KqV0%|@C=<+u-WbT(P0iV}RG1UhF8b|>ODXK8D3>hK!dDrRqy~De;up!R zG#@OCn-f;n?2SS-y((aBa(boDH=wu97nvKFIG-uqy-@WYk;bg3e(g?9^QXYf+&xNG zBj%agf#R2Zuf_z|M5PA0`>rIOxFz`(1vhJq@$9|kewu5`Mn&1zvv;ZF!B*%}$32S! zE9AM<+4Z|RhXev`MA&}f*KG=?d%mCAYz<=gn&#nev5)$zSJT^H9~p}5pE1B-0J}>T3bM>YM?U{JQgaEcA0hg`Ez{lSs!>WDWY>>su?b~uFss400FVdN#@7xQ-ZB;GN6 z7y9>!h&Vt*;-*HbH2e6bWv#{eApHjSHC7b1E6bZaZP^STb|wSoL>dY{<|xvNBJ>FN zriQ<5z+U)(0ShQTXxOBoZ!4PZQyO04-UW>PX@BYM_FcS-cW`FkB4ol>oJJ@?ez z|0^PY%Kbkg5+S2HBIXhK-1F7ws~>w4*F)I;EBol)Wlwkz2J4j5IZQZmQNg(WJxDJp zRFwKw0y1yE`mEe9WqW__OC}8I-3H!$f_$yzX$Bqi5haJ>rn?(5(}y2zHDk8!9|^-^ zp?-@WPw;iuJc3Z+>s?fuwE*%E=aYknovB22EEG%Kuyc~RzB(;G#rE<%LlSXM)?5kcn zJ;edbZ!y(N5Fhxi4mV!$6dTx}5dB^EJ?R$(j$MpUQ8{b$)Sz2UK2Syz<)CPrRx#-& zw3Ed%ePcC}ESKzQz@p{<$js0|DgAlm<_?2|Us9?sdO||4HBLh4Yguhj`(QscT!5a4 z}ZxA+9$;i8Zq4VSuUK*osch3_aLaY z4JBk$^qkCcAG#-l!05Bd&TI6%*RI{vWI`8lOd5i{u)*>1gYLxfR9UwmJQDgEl+kPA zw-Q_=sG#e~l2iSDoiP^S;6J0m2X3V`iU>I$$c5Lq&Z6=j)za*L`GI`6QA@#W2ZrFI zz_zxmF`lWls1CuAk8N!rF(`A{Zc|h2f(>}QYsP!b?z;Is$v9tZ&sc`1w+$?xC%Y;d zq-r?q68vF3JLf^#V@Z1SMWsdGPO7g0ZcXuO{=}G5! z+71smZTkJ@Zt2Gu)1Z_uUh$WRbhK)G(QMe*C+#o$0qDqIu(mQ#2>Zo zeVWrlg9B_Km021WJ*~vVjYxsi)YOJ>T8GV~Cwn8KWw1cp5wMdFRn_GZNe)Fq9E9@Y z3}U*&%!ricnDG)k(#$?0`V-Chjswv7|M~=f%Kbkg5)7DYmZNk=TTdrz)_{+;nu-~I zsES3)+sGJEeMlD0PA~Cf>z_3i4}~fY0#?lE4@VE%eg=C_##jN0#zum?8-&zq|0Qp= zETnJlt;1uEDyfWY2Pw{v@5f6G1g9_*$!`%!Wne2A)^G!uE*%xM`ZZU5T<`!sdb!zw9FJVdB@p1>5dHX1ms)ASO}oIJ!bP z@YbO|{YEJx1cH)Qe^j`s2JKfHVBrXdun79x@(;x`E88;ila`&yhd6CYAB4zN;=S%yZThGn_V^BnoMMp};f zX-=&MUenuqHHNgX$}4(Hw)2WPkj3_R*f_K_In_q;AZan}#EMfVOXmoU`@gC3G2 zh_3gv(EEjaF4dJEYNPQJIii>`z`|2Jd>GpFJ8q`sN7;T(f7us$yHV568BU$YfPG4- z1KB!0)-sr=+U_Eq@v5DX_V}klHiK={hsW$I-q9JcMV{B^N;%BUU6kBV7@a2Uj%I_i zYY0OZND`t%7H(()v^TvF?1h!EVxG4yVcGBao~-A)`o@}jhDtG0czv4S6RC zMM0pJqyFCZX+{O<-E)}ecHb0V#5dp88V2gag)P&Y0eWMHg2nmn3#Pc#^}e)D^h8bN zO&Z~e^!oOEUE`pfB^OAy204`snu^m(ki}6sEJM_E3=)MJV z^e~LBxa=+N{iFL&fs!qhFv8RwAD@-}wH5yKn*e(AKZoGKG%qDgt?aPb>=d#c{#P2` zyu%vo9?h?~uX}m@XWTOJ!Ny9JoRF0)U=RG{U)}kFULlSu-Fg8GeS?Chv z0fV>j161ZVRg!ON)HUGDK~bqDbqb1=94#1EL*aV@NbzSO{_M#bvxQszoo#Dp^U~V6 z!fNqErY4BtR!5Gu$df04tpKJlvl5FLwPmY8_5y0Sml2MC%#1(_-sBG>jgGyM5xg2_ zvfAdOw>sdec--?S1o9!EHB<9>B^nzYvCemMrqJ%luj46!R|fB_d888usGydvL5izh zY_tq+$anjv+HYII?nfH?VxqRrIcu&Q0)E4W$#k9voz}9zxB+?s?m-PNHrRhXJ5F=Q z#lhW6+%bD~Gw5);6rI$A+lt-Lng2)KImPF-Z4W!PZJUi6v$3tlb{gBZokopq+cp}z zaT*)n+w=eS{^gvTeZB9;do$PjJY%e}<{We0i>LCF8QL8HF8)m{t`EdO)io@@a8K~U zqCJbrDgFNZ6740BM@`rBS-vVIQ)7zcL+HN`uHHXzwZOwRSoVA5j9+^ezWi!=gT>q~ z-KB{|iB$0NT|+DVwXS82!r^4O+h_uCL>Ep_Ccqkrl}+IZbdo{@g9 z+9g%^{v0Ve+{XjdcZbVC`C_>E^~uEIZ@B(x?f**Vl}3xGqEw$nflq6s^mE&)>Si8| zPbY&b63?v4SQL=3xABP0^R^}(>_#U|OmaFoq3MvSZJPvtTE6#&yjch#w;y738{S;i zdS3WYeiiX>!Ehhzhv^5*JC_h5O%6QgO3XpJHmuD4R>X6A zOu^S97@tZ4xX*Q14y^wLw&3%3eH7its*D&bR%F%bEPjm!)`f0KUzpwAy$7o^De|wPjdsp_ zXYF~%N`K%AbuC!r+O5|l3tYtYnT+hOQVtG4uQ$(C!mBFr&QRt3-%5EL#JPNQvvD4g zh3v-jHvzFQhHu6F&<2FZbb3XVUq~GQE|ftm&JV;a*Mx|)KT|=*?U_K??P||Fl>h!| zAfI4$BW<{L;{EN{htPi?TuA`90x&eoCo~n&u0lrJXUMhQL+FpCCh<<`PSLO2xY+gY zDzH#XeVxK~e!Vp{O0tMBLhMP5m!R>Zc61gvugHTtuOCO~a*VCshh#aFfDfPgh9IC|yociq(8}dzu~t7LibD zneeQanaI2*JDNyn+iZpoCu{%$`h9TS`&wP1HU{$~fE3J~c+XY}oVIs3XV*6+0U3(M zzR5IEb#(r8!3!&lQcqz;C4wAOX|1$9+{L2$_D;WR?{Gq(*`2tbmfWxGz%hq!S{tpF zA}p1U#*DZe8=9(@H9z^ZXWIPY+0$^Jh@DVlN9&ZM*2`-cm@1#A<=O74@uq@0z(TEr z6-sUu&Ck6x2t6?wk{Ot-OYDRcE4=TxpE6%mS4egj{~(C2WLKwyv1O2rq>DWHvw5YV&*BKAnY zt=KYrcgdiuY*TUt96a=nE~{=KLR$4oxo5E79lIt~;JKNtUOpil&&Tb^i!S%XiblDGcH$J*DZq&6r!VuhP;m(lufsnpkQeTL@4C?Xy*Bz?Jm?roPYP!uJ6>zQMk}E=FOi<`6yFs&?fm^5eh@ksxBpE zEH&uuL1k5ajRK9)1$d-a3vdYPPozd^@9R5V86NYayI z?Joz|rUo4Ffpf1{YJ_9R(^&L2E*%2fa<~I43SssVEXw0slUs}QW1kP92HaxbnHI5A zTRr;TX+#_*zTyc4i#BZ=tifAvaI)q_eh59q#VSxb^9{A`D52AxT{-8n8;S?|#E%zz zjWPQUn}{V01ifE@#qr@6Cz4|sJrh59VoM#|j)@Q~k#}+s=X-+-_}c{Y5lNa(K7{`J z)MN%wQ}bA5;Z?(F5P`oU(TFOz9UH%2GsLo>f!9SO($1s8WoHMHya`h}8a3YF&kg%` zMv!$_Vj~y=tKDlnUN8j)+F$zolxy9(tdJITU%SBF1Or0bgJr7&k!*Ph1rUI|cK_}K z{?ilvOHHYb`I)gQxR?E- zrn*{H+x)jO8-7F?qdJB+i&~Fu!o4rHc5KhcVF?{QmKD|}U`?B=%SY7R1K{4smN`4% z`kE%j6&X}esyiaY5N@XO(H*n?>OZqzx%c<&$%62<>v8 zVGdQl&dOa*Q7RB_VbVEv&L|D??Mr`q-X5f@@}WGezBu(h5H|i?{i$d#I1j<^djK!Q zAd4YjDycPr9EDU6*L;z?zT8*-#wiqkg~PI&o>V-f>;cX?+0=yb3w->hwXU%XZ<*u0 zA1%I0wne=CEe+(3#vQFmg|DA_anCk=dd%eyZ9$36ux|>>y24(w3@b%5(Os582z@oDig zbWcl+oBh?VmNe^_P!8If7e1UMvQEm8Y_%I<$A=#yu=+v{U61bRXfN~Na)xghkh;k^ zl_Fyyhd)17b6m~xl+3P4*az-H>kIgtP`gN@>Xogng00&w=vQOD;-gdy0*21@^vF`9 zB*}vEl7DL3GT6!2r^iTgg1WX-fY~gKF&plrVBjsqkUrL`hOw=vr0y)LS`(|Jdfqv< z2iH%{{yi2^ioZtROY1jc4QbCGRay+&%bD@mALmg}-H1l8PhDTXEN*fC z|IZ29u8qQKer|?2+#1-R?aw`ZN}qe5h7xp}V}>{1&rEp#+=TpbsCPo7LNtoYJy2pA zEzSW%KJ#^OCjMdvBq^>eT&iws+!R0})%sZMAIzZ8D|gi&M^@9cnb(lZzPlfgbx_Be z`U+A_xHN<4fCT;_^xr2DZh%B`T;OS$m-XFrWpcv@qOFX&yYUEy!RMD zr0_hD6ra{fBMIEJV)pC>x~?i#L|z;W#b7zS1I<^9}Vf&Yn$H|tWLn_ZIJS! zwlxeTbP7dh1$BdyoP|Iqlr2F;0=zsVXo7x{Wm{#K+sd<(lTllD)~6bK`J(V#_`Y*g z?6Op5&OmAE^x~CXhb*kHpT(?P;!Pd$U8U`IRK4f zWGA$vd4w&(t3S)kjlqT85r6Lq8gl}#^whY}ksvM9*m-&EbdPoK1bLizR+DFyd2?ET zjsIu}`$Bx=yeson%%A(W?TPib1@=upZ--Z20{t7Ol29u#55+7fQohRpf0{mQkmS~L zY%1}(iLZT+R_c0*CE*L8j?DAmVDAk}gTg`_E1;oxIkm=h-%Wm)Ok+r4vt^S<5$X;a zkRmWDWiDyM=bOC+Eu=~Y%3W+#NeOYX;F_|ApFqi_lBw3xHYAty&--4N-)d*N@Jhr3 zb4C8=Z01GN{$-`S2ExK(5%F%()^bkAA>ISbD|96msj|SJ4>dLtppo|B|G%++k&l4J zz|$^}MaAPuC9_XA&@g&zp2kgycLJsm(DNa*DgimEv0*j)Yqwk520FK;fHq;`qR77vV@U0uMDC<1P59-m-ibw&V0C}tqPjvBpg46{`-(c03ciS-aO9+(E!?0`$Tl( zkAjF5!URX*|N045Q|BAlBk_P{Jz5d9VE+2sHj|xYNQnT*vr>-_%sUrbo#?gt{&^0J zS#Fr~62x4%8-a}NSCpaj+~e+2vrr&!U0zL-2)*#X^Zq|QQ2?_4X{PF`uA3bmmfuisw~?&^=f>-x-jX76bzlK9x|(g*0Q+b9^cI6aogk$#Za`UL5AU{2_;MXp$w1DubbJ?v+BcRKEd4U3y&SBf{5yFPA@wT zhKzOV+_glkcXY_xjuUwBoaF|PoKXd`ug7W2v*Qh5!?`&a}lIfeR2BR6avKgPz#9bLx48g<(;&@%$7j>+W7<)@Bb66rk z{^b3zuLw@mz10Q05uQC%p|`X$=1{M$(C{oc(tZM#8a@)(SpV5Zkno%o^>&r*X+9<;rN7@+rOhO!rhad2Y$xlb#0DWpt&x0|8eV_0l^j{|t z+--nFVvnI9IX|zUgA(660!G@HtcVS%5Vh@dbHtZ?w}xHuFSgYmITKY$7RJZJile_@ z1CwME!Id0wW{x(W0^=R@L0i_})-jD^H90xa8h279o`w6WuO<96;zRoRYOfu4|L>;Z zKdt>=5{c3g4CoB2Wfknp>lKe-ygI#7#fq7M@!=}8dvKO=+Q+7VI*jM7c!I*;I^P2^ zIYlq|Hlh2vqRT2XCla=$sA~M%lIT{fPYvqTgcKWL`@4`pNcH8g#tKh9ltTpeh$F`GS`ReQXc<}9l1kS-1OQSAR z%zPoJwZrzcinBtf(>@hCMYw&nm;xXq15m%N!0Iu>#d2&|n&~SiBvbwoO5` zjs-fxc6vYvEG;HORyx>t|Gm+Vp_Rz2ah1Rq4oH#1<=4$n$oKNFO(o#3?vRh@gNIF9 z0Jw;Du~MPMJemWRZECFa+;jJL%k)JE@gHyMw`KBBWdR{ui~ zkd^=QT_-plFU)dLu1rD++r3OOwoBk|35t%NW#F}(yd65{b_!R~fq!S{e_H#$;7S^- z;w5(KNXecu#hG3&g5=o8#Xp;ZLAAZCuI0YBaLW**u)4k7&Na(b6z2a4;ea6SwtePh z*I|lL!D+)x>#F?4ZCObxGC%wC>K+@FT4m98?1Orq0_O=EM6$kM#L}M(U7c+UM01^> z-RWWXFn?7X*{IRBDV#^;-EZNyn{m7ASwpwzTf88JvoFn@>9WIoy+L z|3Y&Y*i3pn(ZbD@Z9#i>2cCn$sAZ%C2P>{(k=Dc;)1Y*EABeo~w*7q7y5}7a?5DW0 z2?$c3q3p1b61jzO#V?ja44OHArbhW`*)FXF=5#ZbOatqW7e%SNEfg^Q5!Qygk&_{6<#BVd zQ~BN5xk*O{&yPW6XTC!UA{e95h-)^)juaFYQQOD7m^dgtOXYK%#*NTYc-A{66+TrA z!JRptlEDgpTIRJcO|<)67mx6^xrVd>BkB%ER{*OtgCl#xBxs>Pq=SA3qwNRVnY~cR zf@?QUrVVO&UF0#oKz{GKRoc)_;4JLpBC9je0*lf$B;wcRnx1@hFtwL)-d^kV4#Lvs zPc0uwZ&TAmY4>E(oT0AQJbJ{gzXt3-487lz@%MRq? znu%qO`u?kd@|$&fsEzSi!nPMyIKE=J?>sDwAU58A9~@_7qi_4>Vqdzv_h$5p{9?B(0hOzwC6uPz3LJy&g^yQtL9sf|cg z8#v5oc$NoP=p-?0as4X^ve3CA^44W&v4Ne{GmmwD9IS<76AjC=;bV%iGX*Y}{A4yO zB%RSo!=?(>HM$>a)=yT)Z51xJQm+~nDg8-*v)W4F|>mw^7sCpfXU0dc@=8gc_5zhQ0B4QU%WshjpShhwGj zI=R`cA0}#76C_Eb!wH4JBk^8YeoA>mB1cX?uxSMP$c(L7_oz9nYSK0&{63$B(s$8f zPqr z(*0>={`8<`2SX`cfT2O(%Ie$Eeuh~q*%1`X?_N-VL326{BQoFk1$S(1 zv_1a3IN3}7WHO~Q0&+p!)Xp%%htPkYL^J^sX+h{~mm5euup6}HQ;%G{vTLIF-8vqY zk=gtc(Ji5Z=T3W&{}>GiN~D3yOfC+Q4%vJ@09-K&W|qG;FS2ms6G>dkyl3?e>ojPs zQxdFqT}D(>@`^X`8szkRcwy19``=~$Kdt>=5-Fl}^aXn_tDO~zrKxfaX=RK9N?tWURzs|b%H^MiSaqdYAH;G`|9@F&$L0rru0U)UanrT zt}`da?px+g77^V-vGi?dqs1Hsp)H`ykGra8)gvtL3}K22Q0*l5lVfDJEQ7z`)&JCCa)&H)Khl9*~P6A1EZ1 zMSOMsQ|2=Rjx?C>wQT;lOX3o~l}&dZ=vFuHJq2^%C%WC^TXgp25@SS5aHQ#FUEhf` z09#CQ9+1KWs`uG>pnSo&??e?@x4{ztYOxo4-P{jfA1YBu2I`iFxGvG#A+AU&O~tn2 zR4eWek%8!_9K7c92KxK@n}1sSzu*d*-0cGnv|B36Xb;SGNh=6hb{lNE@0}BteMDH` zqHYj^J=>|I-D&))V{s1oDaaIP0e_iM7Y5V8#BW7N-mW#~=o7c`cV?G<1P7K8RT&uI z(IP6im&v1Nn1#yKejWcm89J$eMfDB^kAp30bRz!|SKZU5m>5x_I}%bo7Io<-6=`_b z&CisG&Wrt|zN{3aW{6bn+RG*91k}uxnugu2`^0hao^-g$i`Y3#+F(N(s=}3LY*~Ni zVkJ)reD@Ve;zD#8LKdWb7+2{2#!v?KNBYZq{_-AN-*xRUUwC5^LXiDDo^gV-89K?Y zFBM6wHMiGM_ZVa9^Zjw3}dXuc4*^FKMHUf(T_|;Kunl zAv-bzvnT5_zp4=(F(}FDtUlj$wgbIO&3h&_Q6}{ZS=>@jUcGJLrn_a^9+V>^j4Od% zz`coM>LacnSK7H`QosDZLkUPZU zWQ&bfl)q}jHhOfI4XYb_8N($qNk00_htPkYLVy4YDKifxBc0HPf=)ggM=rFEMR#jH zJLd9o<)>0nvi5|=k46fKw<1*t@p0g|kWkDB1-@{}VPyUNY$X%})vJdb2t*NN%`Q`8 zIE#SWdu6rKQhfG2`qjv6atAFw(PCq@>A2Q8N4PUK>SfiUGP9g>Z>b(D(1ptSgyJiqqS(p9R=`r zP8xi0&XRf<<6qyUHE8wu{U&)4HJTqHN{QdKwW7I<%w(^CHE}s1ocdjf;F5`|rf#u& zTPDg$g4e%nBxEAFpA5^kjvQQKz%uF-=$#mK1%OR(ts9#!kqA|8GJWG`*V)ZQ^!N5r zE>ED0(vtQ_rL~?5L-vP8&jCTz%J}uA8tHp_{v=m44t>oTNE&q%eLd)$B81PvuDC9H zQQTB5q@&jl#b*upk;c6HY4D43%ah=gl-$9!33H7j2i!Ue&P=O9+#|d|t)cix-B%Iy z-*AuPi$TipFmXu~Hfs*wOuVC;vj#>LohPtkYh-aHS@-dY=-j!RrAWmd12!Y)*e^bwp zicY1CpQpOMlbHh+A$Nl*t$;+_>iF{HR`|%G&WOw=q8<4kg=pvEyFgP4 zZfC({r)R0gUS+D9{bps8W)mUrlEnf!Rl|n+k3^)9KdBr68K}v{$=RT;o?BCtWnxIP zLy2gRUl?-gKVV=({dcDz)A+&wY`;Z`6ahI{*G8a~5WXnk`1iHl(a}KUMA7;vKqCAm zSd1Ucz;jHMOv2{qxhO~oJw+2~gU-kVy9)%J05n(mb0P{M<3s4bP9ivS0EyHtH$*eE zPHSQ+Dl=c0%wsI8lzgAMb*AmL8u7o6w)q`sy5f0Fwm5>LV0pnhp7jeYph1z_7nPEe zseBV5OS@acR4sNvi~YK0>%P@;o-NYvTJ7SDodI#YT0_YZ61(K@BLAP({x69Hz?w7& zuYSHj_R!JZi8{h$<^aCxZ&>Vr4xA~>ghZicJuhLEvMeC~K1R^5a2L=HQI&97)(LYe zb04sypS9MU5&)uV%KRn;J(laU9D=7l)Th#bpgsE;-@Q}kK1J>`pvW(lPF7J05XG=l z%?;63*%$!799p6)W>(E?34^70-a4GOLMRchDAgsMla9bhn3iek^+d_{vD>BvcV4RV zyYsTZnh?U^hP{da48|SXU>KYFy%4kG35$7(8*}`D-&sd>jcM81G%797I7*~q){AuPFoNPkY+WEK9n5>4`D=VXMVNEu{J;9bYMep8}71TlY!#d|U zeJkvI<{(^aKMDPI_FOg}$LGsVr<@3!9V!dmoc|te^-pULfa^aSWRe`Se@n)hi;^71 z!3iDSi*F&i!-S41a|cU+3M;$oT;2C-uXz7rKXIm0)E3Uo}7UsOG#kcZwJ8D#P5qjfO6lM(&?6nL5z3K z%|iE3^V5>jt%)lghPh>M6AluZ0!8cK$`~69neHdCTe}ri>8eVlc#&Z znZQxkYH&w{#~Vra2_&2xYlx^E#cr-TF&(`rFgK`R*o$q15oODUjZ|S-9oY?s-1z3W zY_Dmsgg_m-@LpdkzxT?z8PEakDM#qU#+lN{LLTVRig9OeH*6eRwzPoHx3E0KP_({W zY|#aV#aaUc+;&FY^(#cVoQsGhfccGtPfEREcZ6++H-u_utlwO>FA#hn5@Tc7-c9$68h zWR!z=A$UvUNJL6gBPjmBmGgzB5U&ER%PS{7hb=pX+Mc936J!Xt|Moa^On?N1?$7S} zk4MNpCuh+?hBYE8z-YbIR>3=I7lDUCg+c3LM^^*%mi)2qH8(nk{{(i79$|Ub^vEy2 zV^=82?rz!|u;9NS@$HgzPZ$VtpB0P#1BZHptdD+sL^oSIx86BQKPP1&j@Pr4_(HQ| z8zi?9YvDeG{`-=g8-T-lK1VfA8{~*aiwK>wBM;+Vq~tL@)=-)2PTAHW1tKSc*+XYP z;r&iB+EC#F5vNR)tfwb4R-0UuDAmaq=Af{#@ybCYV!ln5{&k3(!>mubs85aVIy5uf zOtH4%z)Ii#u9W}jG5wX~qF%qmekYI_r|StjPzBy%#P%;;c3Lh-p=n}IEu-$j+v5c* z49x*1NlKdco*Vv&162n0#X^bBZ1u=Uw<2RLOYC!d6ngF^{S#U}Sc>gb^(*Y-mA`;u z`k9cI&nRV){+~)Y6|do|09maNyEN1Qp=nLl;I>Yiz80|NXD`^Bg-IB&+@H?Cnpd(N zIHIGIf_#hC@xJ576YPpjfGe+^{Dl`{bT}9mMtW?`Z3@U$mflpHASl+t!i}q0u-`P+ zdP>5O;Q1(Z)woDJ&sC>j;G+xG5iO0dEb|q2H5$~iOE|3r^E z80c$F%JF3^TA&HX;2r5m-j~06!^E%Z9BAu=8qa-o`faDE7==GWtqhmJ8~aA(a}^W# zKua0YaGp5L>SJGcx1K08RlOL^6Aqm)#85J@U%iu5a#dswuwRYD@QZ6Hc1wv8C8vAz zx77Dr<iOd6)Ko!R=`hg0Ie*ZD&SjRyP!wwKmxgA z{;V~7mm#LS5Z|0*)0cwILez0GinhHB`mW`gst6eT^RQw>_WCd6?we%v`EgEb&Uujg zSz#ct0X#9Py=!f+BHaFD%D%FKQ4~KZxr+Db9k9?PmDKerq_N^d2`p^DQOmA%v9)4U z#1iL9#oI++1|58X_GZq^i)1jxF-fvdC%Q~!D!ze3Er;787Hr^UpQ}`=Ar?2dgt~Og zP#=F?4G~f~Z&5mz1p(7vThfH*Vz#f6bUfPoQz;vKhZ79PTc;%IME7ZPP2-O{+>Z1+ z@rw`Y2`L)I%uy< z!z-&2>1`NRo3zvszaKwo-*X46w)|Zw|I^z4C6Q!2yHE#;(s&->gAJ*jj&idy5Y@1k zHdgCJtkrY)9W(GRzs7AlDnMX<;Rs438La{ZC)4?-KzLRZ{W7!>R|=j9r-DK1RveK_ z-?{+5O7Sp>P^|06M@==+jW_g~^!l^gwRQ;9$y!87s{J|mtx7lkZU+UjQuYBTN7hur z+%@SdMe$<@7`L)tM(PYwTkOfMRMTpg<_LxUig!%+^sPmwn^u-py}qt@zhujuhi;o) z_7vT`)?&Zmw9Rfy!`9UvA~G$i*ERa9k>_Ts^Wh$<9r>dVPls*J}5j zcneNLkZRL??=%CJ*!ZpKlTT!OhjiEVTfqFyBPR$q?cSj#MG+8O8uU7A1{XTX=*dVJtv-qkc<=3JDj4zQ^4+ZU3U&ddH z@HepBzYBYmA>`GQUV@?(;-dQgaTfXf6XwtbQbb7j1j!E*VRQ?I3>5! zQ_VB2$ZvD{M^3?Oo64o-WX7}9jG?up{l>kFNHKZ#&*Hp?J%m#O;EHo(oG*13&L}*A zfbYGfn++Eu$i9AYlM{#fAR(Jtmh*CPQYh8mwG(N?%Y_1P5XKmK^QGC`#G#bwYBxYx zTl(rTx6AC&gBd?Bq9Z*`<@?@rwg*t|wbcnfJcpVX2`|KNFjOrbf+dNSxt9EaYtO#l zJcVjRH@Ey zn8Kp@@QcU|{83Cj5A>sF_8;%ogc`CNu{dVB!@!pv<%2M^;UDM4{_8lzz5;NVN0SQX z7e2Fp{gsV?NqJH0o;o6?g6%4#OlVQG8z+ta>L%hbh~$^Xw#nB9X1!lq2rQO|%KJB( zSic6>p45DWVwIek$I;xaf-n>(4#TN$VP-uUtE66e8-=b#mF(wy{;rh&=`sDqVek;A zS0b$;7!1=Knux;Ep|3EONqc*)uNcd#x@%ac$;>nZC_0@0`15nSB^Lv+7f8lZTER2C z&EZti@o(=>as*jnNIYfWf_&(*r~$@55cMz1(EW{|A6)bB?!AV6_x@DM^&;vkoFY?v z#&39>o+-?;L{X1+A*P>gWAf{_;`761v{`8BCz!8dh-#67+Mb?|8f}Z$JQI|s@!#Lg z_z=Tk5DSGhE9}yhNm2TyF-I>XbiUNcr(8PxTn+Y0iW%6uUU`ZtGg0VJeERYI4F1}Q*=xUc#&9A$zrgS8P6YY*=9RRezBtZUPLk~e z6uNO50h|}8mNj7n63om`=y;P8W|jdeNnvu z-Ec*W%9YZ6c#;3C?bs8Xg?igCvB=W^^RCJ6q85{z3~Ys)zp$D|Cr zj_C7~8u5rsk-Xt?suqFDXe1mgMns@*5psGZ)lUTDCb69fpQX%Zo^#m0`k~xG&Brbv z(a&f1cW+8X`mIE{A1R6sBE%p0a7nQxjG zT%vori&&y~e=23TWf3`rmC%)=LbK^czGf*fUCEBgrK=}Aa2L`RWfrafKOxGT9bQvH zHiu{~{UTJicjt82_`W#YS~VN=Z0pkAN}J$+he~Ws?6H*^evi@NEJ7Ju5;jkEm7pzO z4{8h60TOr@HDdrsgent@`h!G*DV44#Ph~aO5PWB#2rIEsUlTJxl?>+d_o&h;g%(vuUCXOx{5Qy-~5j%4i?XGz5^t?Nbax>x*n zrTkB850J=z+FngB3&QF4(=r9Gjw4W8sPnp z3a&{6KtZ5iV`)29C4yE~rW-HKmf4bTqatAH_~P>wnt|otuXkA=dTT9f#h0Tc8O2#D z3I6nyQ?XBB1Qep@iQF2+-RDS`vA0)$DJoDi*IIo>M|b;#M-3ldmjXwZ&)jG5voz0vjP@O zIGOD%4l6wM6U$Qp84E#WP&?HGYqf5FT-{VSP-uk19>UZ(|N2|Yj=Ek;kKx|FEH>rX z*DZEsQp9wbaK0}qu15-wOE`fZ;5$f+aXt@ljouPfX3bfF`1*D5?53Hguk~KXq$g0u zoMNb3^y^RC{!DiN@^(nn%EYlDY1!LWcW|YRj4)yG6Vc zQ94IE>l-k6S(&*k@53BoIR!S+e_tKHJQSIc%Ma($60NdPI(n~5V-buGerM1ms8$PI zhD&N73;y$j&bHgF*oi%DCbC-(^1^ADl_z6_lT zfU7`-i=WcI)hyQFt~3EL{?`T~-{AsT!8<%C$bz%ZtEg)b@F@~}5!+7GowR|jR4YWR`tjcx`k&VR zFSuIlb#h!O3Si#l$KUfybeV!4%~s1ePJfh%lbP!(F8sPHr@^I&(mfTE@23waJzB{ z$9X#b@yCMr)va)6z>gR(h4hJPy+>Q4K$)g;-^Ojy}G$l?@6(&P4>mr8}hw=B}uRL`Ceoe&!aE+)7NXu(+0TU3lOO` zgkDbB@F%50Xsz3X5yA`*e@S({ym|j8UyK;}4aMMNVH7Wa>PYc(cZUqbk*+1U0ixVQ~Xaa z3t7><2|4Ip3_3nWO}fz^eqDxsrtS6KW4~_&QKVLy{eMqz*|evmM~Fk`i=}wACfy^% z!wrAaz3|ae=HwWq$yd_upMkb-OH!*Xy0VIb9YKwFaNm;&hz4wd@tan_% zXg=Wsr-IG%-wz^#2s?&iO_}GZhpdWKeN#=4Y8q?^Gu0s7!bo0fpHMbgYp zg^>()HH_F@qOE@Q(p{*93_7cbG7}8ZOB~z7ptc;;4;Jk?2k+i5h93P|SU}u+(jGH8A|+_D&_LqR&!EXSpOH zO~REUU!!Y`7MB3N)#EYtluj4(p%~F@LTm`6Ch_NIr_s=NwWTH*c#Qx*()l{ohko{G zu;&%_3EyFE+(irHFSk6FoZHIY7P+$8@=s=Sid}+2>#Is6FQw~P>Plb4H2rcsZm>lR zd$-s4$UTy+a@|e%QJ#EzBeW4U*}Fx%{8~8nMh}ykw%;UzF@J{EJ=fY{w8#uaP#5YV zg4z<6`i*SJT9W*FzHd+7!H1|F10^Jgen+@3_jDam*K24Cg^s^wdhh~W*S68eCk+AZ z{3H@*fEw?>Cn)}CG?LAT0-s)l#`t`2D-4N2{26usvIA9{aVxhy*x6}OoZ(VL=VG}? zqUH4z@+Ac|M~_K{uN2%I)ELD~KthmbHsOy%WJgv=ue9`Bp{40PlOFi-mC z1p@EHFvq1Ku)+Q_nTDWi4~8qGDRYSL6}@gK3kT%K4XLEp3W$PPNZ?h8C%Cd&4$zOA zX?2j=h-Ix!U4nG@ARmXv2n%SBy?kHeaGRxxG(Cr{#0H`Tf z0gK{;Z_+(DlUs-{&RF5YKp&tv?h;R^E`sXzp59cf9)a%DK2C=G_o;~wpr%j|xt+)| z1?^azu$o^nxP%#CI5>|4TUs!$T8ZBC22~s9CS*DsL|{C*7PHXE5_RzIo1a7A^>K}j zou>0U^btZ1xjFSJbRq`+L2r)UQij00+=NJeL+ibX)ZtkR0`z9)$RczH;6gxR3$X(%BR ze-`j1Q*@==%CP(q$InX5$~8*WfY)xD-`Hc?%i97UAYWRTp=MoRFl4{*V~8}vA#)17 zQ)#z6m&+8YfhNN!C_(m|2=6S~XUhqm0P;|4gfqfd2iXmdV<6aPU9ZoEBgRU2jSCA} z-7;mLEmNDTY-zMMv>j@63mZun@b}zOJTT~U=Fh0I`4JQsI8=P7o2ddpUum{TN1M*L-+Xpz{UDXPtJ{gg8E|`0W;c_83aZ$$+$iBYL zx&7mT)MGLoGu)TlVt7nVgwPwi7fSGn5=Qq3d{`Wkk^ERjJ@$0>u0bLCZ5j9-BF3-B z`zJLah1QRQv3~wvXBifBoncP&Dt}%c+~l$h%uYIpj)F{GNWL90vKIe}4-y+f#=IUy z$Rh|ebh1#1rgR}f$8owGcjRajjb$O{He z(4%&R+#a~q!)V_HDzMTR0j2k7dPg!Fs3^t{QCT2T7fdi>kAq)D#oXq*X49V|7{hi+ z8Z>0Y1H_%m6ex=!*lolFQ(t@aq9P9~=X1FA6#jr9HL9A9R1-M9My7Tz|z?@+f2;tSyF|M}k3AGG4rO)66i-ES-<3 z{Di9n{v^xCP4U`u9%6te?`2Bh_ZgCa0{hFlCe#m)jk|UMU;@>MmO;7_au4v?sX5#W zpLsj;0Y=LC@+AAv?y^g}UzLv@z}56LS&g4ww3om_2VC01H&7acW@4MWpMhPp?=4QFe?E4Ya>ESBUP9ofLkQ@za``D1Y4 zHMAlY()=>432inn!wm`dLm zx)D8-zZ$2ze0n#MRne09N!S>6I(Z=GI@{`mNvkn4r8@0(Awc|h9Se;mAwKlG;v}5X z7=GYBZ*Ap`rvQ*Uh(O1iRCESd+Vj^p`pHn4BwPqJ2~Cl4yptlC*^KG3&@R5=FUg0- zkQ1(0wa*hOMFX=)tucE^qEWGAhqZSJntz?E1j>lSye13>#!vv^+DrWgWFQFK&8V65lsr^*KQgbLy z`SpGIWx#WtH3tldS72sUox^Snu?*&nai>v{GmWq`ldVDSLNuOA**u?PCD_z};THJ> z$U5f*S^f`Pp%5uoivmU$Nrv4<+BQv=nIjQV#NR;-5-}h1taJM?v5EeBpK_=x?!FY{ z@@b_{_*;ZfZ6!S2Jjl{3jw>c=tW-z*<~9H>yb?wr$&PoW{0on~l++v2CldZU4LP_n$NJzBlLkTxN{5b20aLp0(#(Yt0GF6nQj# zH%7e*Asp)wz3RPL1QLGbJ_H-Jr~R*nD+~Zvlh#_&WNb~TN;K2skfRY|Iny2tP#x)g z4b6F$Jx&iwm)$7rIXWd>$&Ej`F-6ucIu!rCbc~YD!sxpO?&gjKWb6xtx-aeijv<3= zVKSkYF^_tTg$!mwnEhY~c^i_;-(|0Viv3@3m1(ntV+1-a&Wdvm+C_A!=X6#s7nYTd zl)H{%XH>J;x*v>z>Q!BViU47!nv-wJ!pH0J@b;F_$^}K?Ng>E<)+nkI(Dsr*$3Sd| zG`SdPoRjVC6EoT-9QH2K)~N7}{>fNl!acm*`Xmqo$?2C*^Wr-m4ZE75$Wp5zVT;`Y z-Uxrk80O!pW;n^E6U7k4`@y`|C?}QQhQxo$6p5oWFd4UXRoQdBPLhs_e7LIvzkEQb z?;sPex-6A70Zy~*iAjdWj^Pbjam6jk?7)xwUJQZR)|I;Qy>ta%M%a6WufPpMM^x}c zSakZk5@V#Ogm{YI zwe+Y!KI?qRWUOfZt_l{GE@VT?<1I^~UZPnTIS?E=L(O1cl~gcPh^NN0eZa7!Pw$JO z*PCuJwO8ZD9MW!y;!>CmRw!kWCw2U((5?MZH1|v)-(_J4Q>r`rpWmjY;=%1CdE>Y0Fa^yS2Q2eT=HN^}b1!!D!n5#Tc zLW&rM>!WQb+hxEtP@@7KGDE+~#HkYb>IM+Q16sKhrDGil5Md64Zont`o)oWUS&NWya~Q$BK&u zpy9QOQwpF$>w>%-#QgU?g735M1eE{3EdpE*f>7Korv8O=DM@V2p%O(pPWDzMb8j88 z&5LYKhQa?nx{UEqrfCVaHjq@WTbPj8zrfuJP=_Hr5~pI@E>i!f=8pqW1pQQl5kl0p4e9}g7(b2* zC;j8Rqg;zsys{cvetV>xb;1c3NzwoFdMA?+QboY#S)Uh z*B+2$g~j9u7xfk>p*x;uAPHM=_sjgv>BF0q#PTo0<({m5#W0rp<0-Y$tgwNA-*&w5 z!Bsgaq48Bb1|x1!#%qO>WzBHQ+a19TBHNr9f|D^7eM)wu)TpxX zQ?)9p#p|lCZ(Dj-p9`_iUuqx7enfRC=%KR&!a2*nQ4m%CCNcz`fwjfB-9cMMp6Q~^ zf{C6bTT?raOvA!af!T#{h>HW)98(kLL>~hP9lDTvrt8+7|1-uX7V~_4ebsFYo76d0 zva>cB%1(t+%g=?nj}nZ@C{<)D$Ofi4@wIf@MIAw~StdYG;+&)JSf$uRU7Bg{Jutc3 z_~Mm97P$k)g$pD8J7!(1Dx7Lp<;QRhcw?Wt)qJQf{lS>bq2?xz?%cQH)}o( z%!be(apJ3D8$CLSpsZ`MewRPgEvtfH)b`EJ^gY)Amcez*z1s<;?$#Ua(6{PXs-r4! zfW-D1=O+m^4~Z6__qv`kb;lNs!E@M;+PV>juj3%Q8h}z%!O}gFalWgqi?b1);pBxW zd{&`e1|&IbaSL@IGVhB#17ZrS_w8Km&LR0vpNph!{7x_fgTxoA{Y((il#s_q0C{Zd zq#CgBJV|nzy>cfdKXS$oWMinujjq8d0$lp?OPyapcQu%dKoqjcB`}Z=GIiq<60mr)c$8#mI%W+`F0wQ>+<&)42>J?SJod8t2I+-N!x&4TjHwcBi#ahGM0c%1 z9j0LbINb*SnTtjE!3?-@yGN+dy%`5EF;ejr#doL`Tjnr^7L7e1F-r&_8vGxY{`(|C z50FSQSV{0ki-!J=ueh%$k|LGDLW@T?u&av^u|zO06|sVd-xwxgwx=D(S0uRjTSs8{gr&T1p2PvmmF_n2C69ULjc3T9=X#DWJP8;%9_qvSvJ54E&p-O7 z*#9Mw24*mHU$Jw};=8)O{tCV+`eEhT(aXh18%*h%-;W;MY8arS*6fEhv=;&wHWLH^ z*mxFu>`Zpb{t00ZTqhS&iYiizZfU9bs~jD|$p$wXJh;E;=c=<>7iH$L$7v`~)=Q4vE$Mo1?*t5h98l?aQ(fjEXwKHtbL|<-w*g zZx*jpw<(#JnscI5TF0%<{TMLAajSVV366S^@=B@99Hi;kvE!wl=22EKyvybE@upa4wOsLBI;JV^Jo=I z=uZwdlWBQLr;}-vz6#VKzVBGhWd4>MwFN^Pu^vAlt2!wrI*7QaEr8a%?*Z$YQC5;d zdndZ1?jR?pAZN*#veoMF&I}i;rk5lYYgN#1lE$7n7s_d4J#GB^CrE(4?eToXv!!Nz z`hy}>C&8n9otet2iv{Ln@E)evQaIsK3ZZzn;O`L{HU4^*l05UAURNiPiRv7&I#^yj zA`f^?&~EsV<|>wWdhWPR>&0`=_uLzx7g=3z9_4A+F`Nu1H%Is8FcRozOgNa?z z4~Y<4!6-{b;3FQ2NlM8BvhS#2YC8&zQ!9k`~G){NGWMk3h zyQl$t6BdF+@IfMP>PMXXq<$PPD6=6~qbb1sUC}$+49qC<_#vw|$^jpT#Qyty0}Aj> z#OGJcUH0}#nwScge8fT2)ac=>V-m5~xXhvxxr6$8Iwr~3tp&K{^gYxns2OWjXX>Ix>e*v32wck0zdJ?z)7SKu zZ~V|9b=3#?&G$#8*5Vqpx$!lM+5O|2pTtk} zB8@TLLAUbD!VkIvZb)nERhz=K8I5JDS?aUu_)a(QSQNkI!#2W3&66Ed{ZNQ|>2)L1 zotuj&86KlGPN79EY|qMAf`~H_V!&5@$>*Pz%TQJB*UZgG1Jxmt+g~>s)LcKN}5f1hP!wQ~Y+C;E|f>e*Ur3nRk>gN~DmUG|wx6=*=9o20v z-K_eW+f^^O#KS^Wb%3Voi z%XOTHE{4_*sXz7ZBF{Nd_to_L^Y&zvVZ|<(fJP240auluJ%zzaYF$UN1}tS+k6E=5 zTRrz6^KQ*y<)yuOJlq_>5x9K~;qAr}I);Ec;^N~P7a^I}@n^~c9l>Fj_%@8nqWYRx zNgH0T+l#!3A`PPc_$KutDe2;!QmuIfA0l3wpU2mj`WlmEG*W>%w_5Htj079*Kfals zA`#2+-=ey3mU5ippBh{Iv}%5!RRLebg!ciznXid8@Q~-`J;3Pxn%bgD3`l zf0o)QBEHbOe9i2K)Q4L+{y7n5Eon(p}-P zXEhW)EdAFh1alssklGqcgg%Wp10dDWKG@1YM`D!}O<)(Y-7CkE%2d{|CAtlC0po6N zwY<8q?+l&aEwGm}XsDmVM&3>x`}uK>9Bq!TJ`r{HNFh6!M?0&z0zh(vVEnIl+#(?04Y<5B5sv2*TFqHymVV_u&v_8D4Hd zt}c?|pEgKKcqQ4;l@L2+XCgh;>E@lo2U?0c;qw`<yaZGxD^_#SIWuV$ubCP5IxCoibCxZ#N;en{?uqH^=h!p^?qEQ+)Yv~2 zJS455-)9^rm6!6fuDUXxOI%e;+tM#46BiDB%Z)BBKdodp4xhYSqZP3jMafQ@I_;?S zCNpri#z08KDo~BSJJA^_CJ*4Wk>vN^mEEK#>Y2}ee9^6NEDDEd3~+Y$ygrjl-tZLN zC=ELcNN6`ONGoi!Li%E!g&iLDfyxHzGO8H%5<~jt~3((2Q z`Xokw)ffqE=oglxRVkF_DyQrEGeS9F63*c1C)Zi0=WjEId;F-!7i9RhRjJ8fCOGY@ z+6u5GTy$gnJw$s5j`O63p5H2pf)OQ;m1ZF;@|XC&y}AM8n^Tu6^%G^>gf7P^_X!0b*C~+F# zjg8?TiyYX7@Z$k=asvOtg@yNl7%V(jYYdQh0WXNEk zN~9$;q*+JA@+AZMxnN?3pU}@7n@`q(PcdUPRCYGC@RQF1Zgi7H4%WcQf*!s(G&eaL zf5Y`pvHuIMoCJ1dSy@BDPiWs#t_5q@mU&0pJ_B2qGTSU=xsVN^e%^JQZ+y{r84n^) zeA?;g$K-q~j6GF{f0|mbq>pJJ+d@bjZn=^-QlaQ@!{61yeWOGae4|2?uIu1@NO8n6 z0KgU9T5aW_*aI}Mza8L*6L7I3!*%xhRW7eA0*Q_gv6Cj=R*>n5BKTRpq!2ZAxF|nf z$A?kndZ{=TB}2^>V%BaRUMKKd>A@d^s0b zKAGfZfN0jg*fivAX}1uqOjl;M23g-}?1xv++C>pule!v8ais>0HK{C$d1(Q2AzO*D zJ%b}sl4?4t3%<9r;<#@cdhk+th@pzZC~u@vJA8ore9F*JHEuTvxa}IcPD?(WE!=