package blockstore import ( "bytes" "context" "encoding/binary" "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" block "github.com/ipfs/go-libipfs/blocks" "github.com/libp2p/go-msgio" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" ) type NetworkStoreHandler struct { msgStream msgio.ReadWriteCloser bs Blockstore } // NOTE: This code isn't yet hardened to accept untrusted input. See TODOs here and in net.go func HandleNetBstoreStream(ctx context.Context, bs Blockstore, mss msgio.ReadWriteCloser) *NetworkStoreHandler { ns := &NetworkStoreHandler{ msgStream: mss, bs: bs, } go ns.handle(ctx) return ns } func (h *NetworkStoreHandler) handle(ctx context.Context) { defer func() { if err := h.msgStream.Close(); err != nil { log.Errorw("error closing blockstore stream", "error", err) } }() for { var req NetRpcReq ms, err := h.msgStream.ReadMsg() if err != nil { log.Warnw("bstore stream err", "error", err) return } if err := req.UnmarshalCBOR(bytes.NewReader(ms)); err != nil { return } h.msgStream.ReleaseMsg(ms) switch req.Type { case NRpcHas: if len(req.Cid) != 1 { if err := h.respondError(req.ID, xerrors.New("expected request for 1 cid"), cid.Undef); err != nil { log.Warnw("writing error response", "error", err) return } continue } res, err := h.bs.Has(ctx, req.Cid[0]) if err != nil { if err := h.respondError(req.ID, err, req.Cid[0]); err != nil { log.Warnw("writing error response", "error", err) return } continue } var resData [1]byte if res { resData[0] = cbg.CborBoolTrue[0] } else { resData[0] = cbg.CborBoolFalse[0] } if err := h.respond(req.ID, NRpcOK, resData[:]); err != nil { log.Warnw("writing response", "error", err) return } case NRpcGet: if len(req.Cid) != 1 { if err := h.respondError(req.ID, xerrors.New("expected request for 1 cid"), cid.Undef); err != nil { log.Warnw("writing error response", "error", err) return } continue } err := h.bs.View(ctx, req.Cid[0], func(bdata []byte) error { return h.respond(req.ID, NRpcOK, bdata) }) if err != nil { if err := h.respondError(req.ID, err, req.Cid[0]); err != nil { log.Warnw("writing error response", "error", err) return } continue } case NRpcGetSize: if len(req.Cid) != 1 { if err := h.respondError(req.ID, xerrors.New("expected request for 1 cid"), cid.Undef); err != nil { log.Warnw("writing error response", "error", err) return } continue } sz, err := h.bs.GetSize(ctx, req.Cid[0]) if err != nil { if err := h.respondError(req.ID, err, req.Cid[0]); err != nil { log.Warnw("writing error response", "error", err) return } continue } var resData [4]byte binary.LittleEndian.PutUint32(resData[:], uint32(sz)) if err := h.respond(req.ID, NRpcOK, resData[:]); err != nil { log.Warnw("writing response", "error", err) return } case NRpcPut: blocks := make([]block.Block, len(req.Cid)) if len(req.Cid) != len(req.Data) { if err := h.respondError(req.ID, xerrors.New("cid count didn't match data count"), cid.Undef); err != nil { log.Warnw("writing error response", "error", err) } return } for i := range req.Cid { blocks[i], err = block.NewBlockWithCid(req.Data[i], req.Cid[i]) if err != nil { log.Warnw("make block", "error", err) return } } err := h.bs.PutMany(ctx, blocks) if err != nil { if err := h.respondError(req.ID, err, cid.Undef); err != nil { log.Warnw("writing error response", "error", err) return } continue } if err := h.respond(req.ID, NRpcOK, []byte{}); err != nil { log.Warnw("writing response", "error", err) return } case NRpcDelete: err := h.bs.DeleteMany(ctx, req.Cid) if err != nil { if err := h.respondError(req.ID, err, cid.Undef); err != nil { log.Warnw("writing error response", "error", err) return } continue } if err := h.respond(req.ID, NRpcOK, []byte{}); err != nil { log.Warnw("writing response", "error", err) return } default: if err := h.respondError(req.ID, xerrors.New("unsupported request type"), cid.Undef); err != nil { log.Warnw("writing error response", "error", err) return } continue } } } func (h *NetworkStoreHandler) respondError(req uint64, uerr error, c cid.Cid) error { var resp NetRpcResp resp.ID = req resp.Type = NRpcErr nerr := NetRpcErr{ Type: NRpcErrGeneric, Msg: uerr.Error(), } if ipld.IsNotFound(uerr) { nerr.Type = NRpcErrNotFound nerr.Cid = &c } var edata bytes.Buffer if err := nerr.MarshalCBOR(&edata); err != nil { return xerrors.Errorf("marshaling error data: %w", err) } resp.Data = edata.Bytes() var msg bytes.Buffer if err := resp.MarshalCBOR(&msg); err != nil { return xerrors.Errorf("marshaling error response: %w", err) } if err := h.msgStream.WriteMsg(msg.Bytes()); err != nil { return xerrors.Errorf("write error response: %w", err) } return nil } func (h *NetworkStoreHandler) respond(req uint64, rt NetRPCRespType, data []byte) error { var resp NetRpcResp resp.ID = req resp.Type = rt resp.Data = data var msg bytes.Buffer if err := resp.MarshalCBOR(&msg); err != nil { return xerrors.Errorf("marshaling response: %w", err) } if err := h.msgStream.WriteMsg(msg.Bytes()); err != nil { return xerrors.Errorf("write response: %w", err) } return nil }