github.com/storacha/go-ucanto@v0.7.2/core/car/car.go (about)

     1  package car
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"iter"
     8  
     9  	"github.com/ipfs/go-cid"
    10  	cbor "github.com/ipfs/go-ipld-cbor"
    11  	ipldcar "github.com/ipld/go-car"
    12  	"github.com/ipld/go-car/util"
    13  	cidlink "github.com/ipld/go-ipld-prime/linking/cid"
    14  	"github.com/multiformats/go-varint"
    15  	"github.com/storacha/go-ucanto/core/ipld"
    16  	"github.com/storacha/go-ucanto/core/ipld/block"
    17  )
    18  
    19  // ContentType is the value the HTTP Content-Type header should have for CARs.
    20  // See https://www.iana.org/assignments/media-types/application/vnd.ipld.car
    21  const ContentType = "application/vnd.ipld.car"
    22  
    23  func Encode(roots []ipld.Link, blocks iter.Seq2[ipld.Block, error]) io.ReadCloser {
    24  	reader, writer := io.Pipe()
    25  	go func() {
    26  		cids := []cid.Cid{}
    27  		for _, r := range roots {
    28  			_, cid, err := cid.CidFromBytes([]byte(r.Binary()))
    29  			if err != nil {
    30  				writer.CloseWithError(fmt.Errorf("decoding CAR root: %s: %w", r, err))
    31  				return
    32  			}
    33  			cids = append(cids, cid)
    34  		}
    35  		h := ipldcar.CarHeader{
    36  			Roots:   cids,
    37  			Version: 1,
    38  		}
    39  		hb, err := cbor.DumpObject(h)
    40  		if err != nil {
    41  			writer.CloseWithError(fmt.Errorf("writing CAR header: %w", err))
    42  			return
    43  		}
    44  		util.LdWrite(writer, hb)
    45  		for block, err := range blocks {
    46  			if err != nil {
    47  				writer.CloseWithError(fmt.Errorf("writing CAR blocks: %w", err))
    48  				return
    49  			}
    50  			err = util.LdWrite(writer, []byte(block.Link().Binary()), block.Bytes())
    51  			if err != nil {
    52  				writer.CloseWithError(fmt.Errorf("writing CAR blocks: %w", err))
    53  				return
    54  			}
    55  		}
    56  		writer.Close()
    57  	}()
    58  	return reader
    59  }
    60  
    61  type CarBlock interface {
    62  	ipld.Block
    63  	Offset() uint64
    64  	Length() uint64
    65  }
    66  
    67  type carBlock struct {
    68  	ipld.Block
    69  	offset uint64
    70  	length uint64
    71  }
    72  
    73  func (cb carBlock) Offset() uint64 {
    74  	return cb.offset
    75  }
    76  
    77  func (cb carBlock) Length() uint64 {
    78  	return cb.length
    79  }
    80  
    81  func Decode(reader io.Reader) ([]ipld.Link, iter.Seq2[ipld.Block, error], error) {
    82  	br := bufio.NewReader(reader)
    83  
    84  	h, err := ipldcar.ReadHeader(br)
    85  	if err != nil {
    86  		return nil, nil, err
    87  	}
    88  
    89  	if h.Version != 1 {
    90  		return nil, nil, fmt.Errorf("invalid car version: %d", h.Version)
    91  	}
    92  
    93  	offset, err := ipldcar.HeaderSize(h)
    94  	if err != nil {
    95  		return nil, nil, err
    96  	}
    97  
    98  	var roots []ipld.Link
    99  	for _, r := range h.Roots {
   100  		roots = append(roots, cidlink.Link{Cid: r})
   101  	}
   102  
   103  	r := &blkReader{br, offset}
   104  	return roots, func(yield func(ipld.Block, error) bool) {
   105  		for {
   106  			blk, err := r.next()
   107  			if err == io.EOF {
   108  				return
   109  			}
   110  			if !yield(blk, err) {
   111  				return
   112  			}
   113  		}
   114  	}, nil
   115  }
   116  
   117  type blkReader struct {
   118  	br     *bufio.Reader
   119  	offset uint64
   120  }
   121  
   122  func (r *blkReader) next() (CarBlock, error) {
   123  	cid, bytes, err := util.ReadNode(r.br)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	hashed, err := cid.Prefix().Sum(bytes)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	if !hashed.Equals(cid) {
   134  		return nil, fmt.Errorf("mismatch in content integrity, name: %s, data: %s", cid, hashed)
   135  	}
   136  
   137  	ss := uint64(cid.ByteLen()) + uint64(len(bytes))
   138  	r.offset += uint64(varint.UvarintSize(ss)) + ss
   139  
   140  	return carBlock{block.NewBlock(cidlink.Link{Cid: cid}, bytes), r.offset - uint64(len(bytes)), uint64(len(bytes))}, nil
   141  }