diff --git a/routing/http/client/client.go b/routing/http/client/client.go index 88e23739a..173c20909 100644 --- a/routing/http/client/client.go +++ b/routing/http/client/client.go @@ -12,7 +12,9 @@ import ( "github.com/benbjohnson/clock" "github.com/ipfs/go-cid" ipns "github.com/ipfs/go-ipns" - delegatedrouting "github.com/ipfs/go-libipfs/routing/http" + "github.com/ipfs/go-libipfs/routing/http/internal/drjson" + "github.com/ipfs/go-libipfs/routing/http/server" + "github.com/ipfs/go-libipfs/routing/http/types" logging "github.com/ipfs/go-log/v2" record "github.com/libp2p/go-libp2p-record" "github.com/libp2p/go-libp2p/core/crypto" @@ -29,12 +31,12 @@ type client struct { clock clock.Clock peerID peer.ID - addrs []delegatedrouting.Multiaddr + addrs []types.Multiaddr identity crypto.PrivKey // called immeidately after signing a provide req // used for testing, e.g. testing the server with a mangled signature - afterSignCallback func(req *delegatedrouting.BitswapWriteProviderRequest) + afterSignCallback func(req *types.WriteBitswapProviderRecord) } type httpClient interface { @@ -59,7 +61,7 @@ func WithProviderInfo(peerID peer.ID, addrs []multiaddr.Multiaddr) option { return func(c *client) { c.peerID = peerID for _, a := range addrs { - c.addrs = append(c.addrs, delegatedrouting.Multiaddr{Multiaddr: a}) + c.addrs = append(c.addrs, types.Multiaddr{Multiaddr: a}) } } } @@ -85,8 +87,8 @@ func New(baseURL string, opts ...option) (*client, error) { return client, nil } -func (c *client) FindProviders(ctx context.Context, key cid.Cid) ([]delegatedrouting.Provider, error) { - url := c.baseURL + "/v1/providers/" + key.String() +func (c *client) FindProviders(ctx context.Context, key cid.Cid) ([]types.ProviderResponse, error) { + url := c.baseURL + server.ProvidePath + key.String() req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err @@ -102,7 +104,7 @@ func (c *client) FindProviders(ctx context.Context, key cid.Cid) ([]delegatedrou return nil, httpError(resp.StatusCode, resp.Body) } - parsedResp := &delegatedrouting.FindProvidersResponse{} + parsedResp := &types.ReadProvidersResponse{} err = json.NewDecoder(resp.Body).Decode(parsedResp) return parsedResp.Providers, err } @@ -115,19 +117,19 @@ func (c *client) ProvideBitswap(ctx context.Context, keys []cid.Cid, ttl time.Du return 0, errors.New("cannot provide Bitswap records without a peer ID") } - ks := make([]delegatedrouting.CID, len(keys)) + ks := make([]types.CID, len(keys)) for i, c := range keys { - ks[i] = delegatedrouting.CID{Cid: c} + ks[i] = types.CID{Cid: c} } now := c.clock.Now() - req := delegatedrouting.BitswapWriteProviderRequest{ - Protocol: "bitswap", - Payload: delegatedrouting.BitswapWriteProviderRequestPayload{ + req := types.WriteBitswapProviderRecord{ + Protocol: types.BitswapProviderID, + Payload: types.BitswapPayload{ Keys: ks, - AdvisoryTTL: &delegatedrouting.Duration{Duration: ttl}, - Timestamp: &delegatedrouting.Time{Time: now}, + AdvisoryTTL: &types.Duration{Duration: ttl}, + Timestamp: &types.Time{Time: now}, ID: &c.peerID, Addrs: c.addrs, }, @@ -150,12 +152,12 @@ func (c *client) ProvideBitswap(ctx context.Context, keys []cid.Cid, ttl time.Du } // ProvideAsync makes a provide request to a delegated router -func (c *client) provideSignedBitswapRecord(ctx context.Context, bswp *delegatedrouting.BitswapWriteProviderRequest) (time.Duration, error) { - req := delegatedrouting.WriteProvidersRequest{Providers: []delegatedrouting.Provider{bswp}} +func (c *client) provideSignedBitswapRecord(ctx context.Context, bswp *types.WriteBitswapProviderRecord) (time.Duration, error) { + req := types.WriteProvidersRequest{Providers: []types.WriteProviderRecord{bswp}} - url := c.baseURL + "/v1/providers" + url := c.baseURL + server.ProvidePath - b, err := json.Marshal(req) + b, err := drjson.MarshalJSONBytes(req) if err != nil { return 0, err } @@ -174,7 +176,7 @@ func (c *client) provideSignedBitswapRecord(ctx context.Context, bswp *delegated if resp.StatusCode != http.StatusOK { return 0, httpError(resp.StatusCode, resp.Body) } - provideResult := delegatedrouting.WriteProvidersResponse{Protocols: []string{"bitswap"}} + var provideResult types.WriteProvidersResponse err = json.NewDecoder(resp.Body).Decode(&provideResult) if err != nil { return 0, err @@ -183,5 +185,14 @@ func (c *client) provideSignedBitswapRecord(ctx context.Context, bswp *delegated return 0, fmt.Errorf("expected 1 result but got %d", len(provideResult.ProvideResults)) } - return provideResult.ProvideResults[0].(*delegatedrouting.BitswapWriteProviderResponse).AdvisoryTTL, nil + v, ok := provideResult.ProvideResults[0].(*types.WriteBitswapProviderRecordResponse) + if !ok { + return 0, fmt.Errorf("expected AdvisoryTTL field") + } + + if v.AdvisoryTTL != nil { + return v.AdvisoryTTL.Duration, nil + } + + return 0, nil } diff --git a/routing/http/client/client_test.go b/routing/http/client/client_test.go index aef55e06f..6e87542d7 100644 --- a/routing/http/client/client_test.go +++ b/routing/http/client/client_test.go @@ -10,8 +10,8 @@ import ( "github.com/benbjohnson/clock" "github.com/ipfs/go-cid" - delegatedrouting "github.com/ipfs/go-libipfs/routing/http" "github.com/ipfs/go-libipfs/routing/http/server" + "github.com/ipfs/go-libipfs/routing/http/types" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" @@ -24,17 +24,18 @@ import ( type mockContentRouter struct{ mock.Mock } -func (m *mockContentRouter) FindProviders(ctx context.Context, key cid.Cid) ([]delegatedrouting.Provider, error) { +func (m *mockContentRouter) FindProviders(ctx context.Context, key cid.Cid) ([]types.ProviderResponse, error) { args := m.Called(ctx, key) - return args.Get(0).([]delegatedrouting.Provider), args.Error(1) + return args.Get(0).([]types.ProviderResponse), args.Error(1) } -func (m *mockContentRouter) Provide(ctx context.Context, req server.ProvideRequest) (time.Duration, error) { +func (m *mockContentRouter) ProvideBitswap(ctx context.Context, req *server.BitswapWriteProvideRequest) (time.Duration, error) { args := m.Called(ctx, req) return args.Get(0).(time.Duration), args.Error(1) } -func (m *mockContentRouter) Ready() bool { - args := m.Called() - return args.Bool(0) + +func (m *mockContentRouter) Provide(ctx context.Context, req *server.WriteProvideRequest) (types.ProviderResponse, error) { + args := m.Called(ctx, req) + return args.Get(0).(types.ProviderResponse), args.Error(1) } type testDeps struct { @@ -78,24 +79,24 @@ func makeCID() cid.Cid { return c } -func addrsToDRAddrs(addrs []multiaddr.Multiaddr) (drmas []delegatedrouting.Multiaddr) { +func addrsToDRAddrs(addrs []multiaddr.Multiaddr) (drmas []types.Multiaddr) { for _, a := range addrs { - drmas = append(drmas, delegatedrouting.Multiaddr{Multiaddr: a}) + drmas = append(drmas, types.Multiaddr{Multiaddr: a}) } return } -func drAddrsToAddrs(drmas []delegatedrouting.Multiaddr) (addrs []multiaddr.Multiaddr) { +func drAddrsToAddrs(drmas []types.Multiaddr) (addrs []multiaddr.Multiaddr) { for _, a := range drmas { addrs = append(addrs, a.Multiaddr) } return } -func makeBSReadProviderResp() delegatedrouting.BitswapReadProviderResponse { +func makeBSReadProviderResp() types.ReadBitswapProviderRecord { peerID, addrs, _ := makeProviderAndIdentity() - return delegatedrouting.BitswapReadProviderResponse{ - Protocol: "bitswap", + return types.ReadBitswapProviderRecord{ + Protocol: types.BitswapProviderID, ID: &peerID, Addrs: addrsToDRAddrs(addrs), } @@ -125,16 +126,16 @@ func makeProviderAndIdentity() (peer.ID, []multiaddr.Multiaddr, crypto.PrivKey) func TestClient_FindProviders(t *testing.T) { bsReadProvResp := makeBSReadProviderResp() - bitswapProvs := []delegatedrouting.Provider{&bsReadProvResp} + bitswapProvs := []types.ProviderResponse{&bsReadProvResp} cases := []struct { name string manglePath bool stopServer bool - routerProvs []delegatedrouting.Provider + routerProvs []types.ProviderResponse routerErr error - expProvs []delegatedrouting.Provider + expProvs []types.ProviderResponse expErrContains []string expWinErrContains []string }{ @@ -278,7 +279,7 @@ func TestClient_Provide(t *testing.T) { deps.server.Close() } if c.mangleSignature { - client.afterSignCallback = func(req *delegatedrouting.BitswapWriteProviderRequest) { + client.afterSignCallback = func(req *types.WriteBitswapProviderRecord) { mh, err := multihash.Encode([]byte("boom"), multihash.SHA2_256) require.NoError(t, err) mb, err := multibase.Encode(multibase.Base64, mh) @@ -288,7 +289,7 @@ func TestClient_Provide(t *testing.T) { } } - expectedProvReq := server.ProvideRequest{ + expectedProvReq := &server.BitswapWriteProvideRequest{ Keys: c.cids, Timestamp: clock.Now().Truncate(time.Millisecond), AdvisoryTTL: c.ttl, @@ -296,7 +297,7 @@ func TestClient_Provide(t *testing.T) { ID: client.peerID, } - router.On("Provide", mock.Anything, expectedProvReq). + router.On("ProvideBitswap", mock.Anything, expectedProvReq). Return(c.routerAdvisoryTTL, c.routerErr) advisoryTTL, err := client.ProvideBitswap(ctx, c.cids, c.ttl) diff --git a/routing/http/internal/drjson/json.go b/routing/http/internal/drjson/json.go new file mode 100644 index 000000000..3bc3ab942 --- /dev/null +++ b/routing/http/internal/drjson/json.go @@ -0,0 +1,26 @@ +package drjson + +import ( + "bytes" + "encoding/json" +) + +func marshalJSON(val any) (*bytes.Buffer, error) { + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + err := enc.Encode(val) + return buf, err +} + +// MarshalJSONBytes is needed to avoid changes +// on the original bytes due to HTML escapes. +func MarshalJSONBytes(val any) ([]byte, error) { + buf, err := marshalJSON(val) + if err != nil { + return nil, err + } + + // remove last \n added by Encode + return buf.Bytes()[:buf.Len()-1], nil +} diff --git a/routing/http/internal/drjson/json_test.go b/routing/http/internal/drjson/json_test.go new file mode 100644 index 000000000..0e3bae81b --- /dev/null +++ b/routing/http/internal/drjson/json_test.go @@ -0,0 +1,16 @@ +package drjson + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMarshalJSON(t *testing.T) { + // ensure that < is not escaped, which is the default Go behavior + bytes, err := MarshalJSONBytes(map[string]string{"<": "<"}) + if err != nil { + panic(err) + } + require.Equal(t, "{\"<\":\"<\"}", string(bytes)) +} diff --git a/routing/http/server/server.go b/routing/http/server/server.go index 1e13216fb..015ff3d68 100644 --- a/routing/http/server/server.go +++ b/routing/http/server/server.go @@ -12,7 +12,8 @@ import ( "github.com/gorilla/mux" "github.com/ipfs/go-cid" - delegatedrouting "github.com/ipfs/go-libipfs/routing/http" + "github.com/ipfs/go-libipfs/routing/http/internal/drjson" + "github.com/ipfs/go-libipfs/routing/http/types" "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" @@ -21,7 +22,16 @@ import ( var logger = logging.Logger("service/server/delegatedrouting") -type ProvideRequest struct { +const ProvidePath = "/routing/v1/providers/" +const FindProvidersPath = "/routing/v1/providers/{cid}" + +type ContentRouter interface { + FindProviders(ctx context.Context, key cid.Cid) ([]types.ProviderResponse, error) + ProvideBitswap(ctx context.Context, req *BitswapWriteProvideRequest) (time.Duration, error) + Provide(ctx context.Context, req *WriteProvideRequest) (types.ProviderResponse, error) +} + +type BitswapWriteProvideRequest struct { Keys []cid.Cid Timestamp time.Time AdvisoryTTL time.Duration @@ -29,9 +39,9 @@ type ProvideRequest struct { Addrs []multiaddr.Multiaddr } -type ContentRouter interface { - FindProviders(ctx context.Context, key cid.Cid) ([]delegatedrouting.Provider, error) - Provide(ctx context.Context, req ProvideRequest) (time.Duration, error) +type WriteProvideRequest struct { + Protocol string + Bytes []byte } type serverOption func(s *server) @@ -46,8 +56,8 @@ func Handler(svc ContentRouter, opts ...serverOption) http.Handler { } r := mux.NewRouter() - r.HandleFunc("/v1/providers", server.provide).Methods("POST") - r.HandleFunc("/v1/providers/{cid}", server.findProviders).Methods("GET") + r.HandleFunc(ProvidePath, server.provide).Methods("POST") + r.HandleFunc(FindProvidersPath, server.findProviders).Methods("GET") return r } @@ -57,18 +67,18 @@ type server struct { } func (s *server) provide(w http.ResponseWriter, httpReq *http.Request) { - req := delegatedrouting.WriteProvidersRequest{} + req := types.WriteProvidersRequest{} err := json.NewDecoder(httpReq.Body).Decode(&req) if err != nil { writeErr(w, "Provide", http.StatusBadRequest, fmt.Errorf("invalid request: %w", err)) return } - resp := delegatedrouting.WriteProvidersResponse{} + resp := types.WriteProvidersResponse{} for i, prov := range req.Providers { switch v := prov.(type) { - case *delegatedrouting.BitswapWriteProviderRequest: + case *types.WriteBitswapProviderRecord: err := v.Verify() if err != nil { logErr("Provide", "signature verification failed", err) @@ -85,7 +95,7 @@ func (s *server) provide(w http.ResponseWriter, httpReq *http.Request) { for i, a := range v.Payload.Addrs { addrs[i] = a.Multiaddr } - advisoryTTL, err := s.svc.Provide(httpReq.Context(), ProvideRequest{ + advisoryTTL, err := s.svc.ProvideBitswap(httpReq.Context(), &BitswapWriteProvideRequest{ Keys: keys, Timestamp: v.Payload.Timestamp.Time, AdvisoryTTL: v.Payload.AdvisoryTTL.Duration, @@ -96,11 +106,22 @@ func (s *server) provide(w http.ResponseWriter, httpReq *http.Request) { writeErr(w, "Provide", http.StatusInternalServerError, fmt.Errorf("delegate error: %w", err)) return } - resp.Protocols = append(resp.Protocols, v.Protocol) - resp.ProvideResults = append(resp.ProvideResults, &delegatedrouting.BitswapWriteProviderResponse{AdvisoryTTL: advisoryTTL}) - case *delegatedrouting.UnknownProvider: - resp.Protocols = append(resp.Protocols, v.Protocol) - resp.ProvideResults = append(resp.ProvideResults, v) + resp.ProvideResults = append(resp.ProvideResults, + &types.WriteBitswapProviderRecordResponse{ + Protocol: v.Protocol, + AdvisoryTTL: &types.Duration{Duration: advisoryTTL}, + }, + ) + case *types.UnknownProviderRecord: + provResp, err := s.svc.Provide(httpReq.Context(), &WriteProvideRequest{ + Protocol: v.Protocol, + Bytes: v.Bytes, + }) + if err != nil { + writeErr(w, "Provide", http.StatusInternalServerError, fmt.Errorf("delegate error: %w", err)) + return + } + resp.ProvideResults = append(resp.ProvideResults, provResp) default: writeErr(w, "Provide", http.StatusBadRequest, fmt.Errorf("provider record %d does not contain a protocol", i)) return @@ -122,14 +143,14 @@ func (s *server) findProviders(w http.ResponseWriter, httpReq *http.Request) { writeErr(w, "FindProviders", http.StatusInternalServerError, fmt.Errorf("delegate error: %w", err)) return } - response := delegatedrouting.FindProvidersResponse{Providers: providers} + response := types.ReadProvidersResponse{Providers: providers} writeResult(w, "FindProviders", response) } func writeResult(w http.ResponseWriter, method string, val any) { // keep the marshaling separate from the writing, so we can distinguish bugs (which surface as 500) // from transient network issues (which surface as transport errors) - b, err := json.Marshal(val) + b, err := drjson.MarshalJSONBytes(val) if err != nil { writeErr(w, method, http.StatusInternalServerError, fmt.Errorf("marshaling response: %w", err)) return diff --git a/routing/http/types.go b/routing/http/types.go deleted file mode 100644 index cc9d85f22..000000000 --- a/routing/http/types.go +++ /dev/null @@ -1,271 +0,0 @@ -package http - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/ipfs/go-cid" - "github.com/multiformats/go-multiaddr" -) - -type Time struct{ time.Time } - -func (t *Time) MarshalJSON() ([]byte, error) { - return json.Marshal(t.Time.UnixMilli()) -} -func (t *Time) UnmarshalJSON(b []byte) error { - var timestamp int64 - err := json.Unmarshal(b, ×tamp) - if err != nil { - return err - } - t.Time = time.UnixMilli(timestamp) - return nil -} - -type Duration struct{ time.Duration } - -func (d *Duration) MarshalJSON() ([]byte, error) { return json.Marshal(d.Duration) } -func (d *Duration) UnmarshalJSON(b []byte) error { - var dur int64 - err := json.Unmarshal(b, &dur) - if err != nil { - return err - } - d.Duration = time.Duration(dur) - return nil -} - -type CID struct{ cid.Cid } - -func (c *CID) MarshalJSON() ([]byte, error) { return json.Marshal(c.String()) } -func (c *CID) UnmarshalJSON(b []byte) error { - var s string - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - decodedCID, err := cid.Decode(s) - if err != nil { - return err - } - c.Cid = decodedCID - return nil -} - -type Multiaddr struct{ multiaddr.Multiaddr } - -func (m *Multiaddr) UnmarshalJSON(b []byte) error { - var s string - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - ma, err := multiaddr.NewMultiaddr(s) - if err != nil { - return err - } - m.Multiaddr = ma - return nil -} - -type Provider interface{} -type WriteProviderResponse interface{} - -type UnknownWriteProviderResponse struct { - Bytes []byte -} - -func (r *UnknownWriteProviderResponse) UnmarshalJSON(b []byte) error { - r.Bytes = b - return nil -} - -func (r UnknownWriteProviderResponse) MarshalJSON() ([]byte, error) { - // the response type must be an object - m := map[string]interface{}{} - err := json.Unmarshal(r.Bytes, &m) - if err != nil { - return nil, err - } - return json.Marshal(m) -} - -type WriteProvidersRequest struct { - Providers []Provider -} - -func (r *WriteProvidersRequest) UnmarshalJSON(b []byte) error { - type wpr struct { - Providers []json.RawMessage - } - var tempWPR wpr - err := json.Unmarshal(b, &tempWPR) - if err != nil { - return err - } - - for _, provBytes := range tempWPR.Providers { - var rawProv RawProvider - err := json.Unmarshal(provBytes, &rawProv) - if err != nil { - return err - } - - switch rawProv.Protocol { - case "bitswap": - var prov BitswapWriteProviderRequest - err := json.Unmarshal(rawProv.bytes, &prov) - if err != nil { - return err - } - r.Providers = append(r.Providers, &prov) - default: - var prov UnknownProvider - err := json.Unmarshal(b, &prov) - if err != nil { - return err - } - r.Providers = append(r.Providers, &prov) - } - } - return nil -} - -type WriteProvidersResponse struct { - // Protocols is the list of protocols expected for each result. - // This is required to unmarshal the result types. - // It can be derived from the request that was sent. - // If this is nil, then each WriteProviderResponse will contain a map[string]interface{}. - Protocols []string `json:"-"` - - ProvideResults []WriteProviderResponse -} - -func (r *WriteProvidersResponse) UnmarshalJSON(b []byte) error { - type wpr struct { - ProvideResults []json.RawMessage - } - var tempWPR wpr - err := json.Unmarshal(b, &tempWPR) - if err != nil { - return err - } - if r.Protocols != nil && len(r.Protocols) != len(tempWPR.ProvideResults) { - return fmt.Errorf("got %d results but only have protocol information for %d", len(tempWPR.ProvideResults), len(r.Protocols)) - } - - r.ProvideResults = make([]WriteProviderResponse, len(r.Protocols)) - - for i, provResBytes := range tempWPR.ProvideResults { - if r.Protocols == nil { - m := map[string]interface{}{} - err := json.Unmarshal(provResBytes, &m) - if err != nil { - return fmt.Errorf("error unmarshaling element %d of response: %w", len(tempWPR.ProvideResults), err) - } - r.ProvideResults[i] = m - continue - } - - var val any - switch r.Protocols[i] { - case "bitswap": - var resp BitswapWriteProviderResponse - err := json.Unmarshal(provResBytes, &resp) - if err != nil { - return err - } - val = &resp - default: - val = &UnknownWriteProviderResponse{Bytes: provResBytes} - } - - r.ProvideResults[i] = val - } - return nil -} - -type RawProvider struct { - Protocol string - bytes []byte -} - -func (p *RawProvider) UnmarshalJSON(b []byte) error { - v := struct{ Protocol string }{} - err := json.Unmarshal(b, &v) - if err != nil { - return err - } - p.bytes = b - p.Protocol = v.Protocol - return nil -} - -func (p *RawProvider) MarshalJSON() ([]byte, error) { - return p.bytes, nil -} - -type UnknownProvider struct { - Protocol string - Bytes []byte -} - -type FindProvidersResponse struct { - Providers []Provider -} - -func (r *FindProvidersResponse) UnmarshalJSON(b []byte) error { - type fpr struct { - Providers []json.RawMessage - } - var tempFPR fpr - err := json.Unmarshal(b, &tempFPR) - if err != nil { - return err - } - - for _, provBytes := range tempFPR.Providers { - var readProv RawProvider - err := json.Unmarshal(provBytes, &readProv) - if err != nil { - return err - } - - switch readProv.Protocol { - case "bitswap": - var prov BitswapReadProviderResponse - err := json.Unmarshal(readProv.bytes, &prov) - if err != nil { - return err - } - r.Providers = append(r.Providers, &prov) - default: - var prov UnknownProvider - err := json.Unmarshal(b, &prov) - if err != nil { - return err - } - r.Providers = append(r.Providers, &prov) - } - - } - return nil -} - -func (u *UnknownProvider) UnmarshalJSON(b []byte) error { - u.Bytes = b - return nil -} - -func (u UnknownProvider) MarshalJSON() ([]byte, error) { - m := map[string]interface{}{} - err := json.Unmarshal(u.Bytes, &m) - if err != nil { - return nil, err - } - m["Protocol"] = u.Protocol - - return json.Marshal(m) -} diff --git a/routing/http/types/ipfs.go b/routing/http/types/ipfs.go new file mode 100644 index 000000000..0b13134ef --- /dev/null +++ b/routing/http/types/ipfs.go @@ -0,0 +1,42 @@ +package types + +import ( + "encoding/json" + + "github.com/ipfs/go-cid" + "github.com/ipfs/go-libipfs/routing/http/internal/drjson" + "github.com/multiformats/go-multiaddr" +) + +type CID struct{ cid.Cid } + +func (c *CID) MarshalJSON() ([]byte, error) { return drjson.MarshalJSONBytes(c.String()) } +func (c *CID) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + decodedCID, err := cid.Decode(s) + if err != nil { + return err + } + c.Cid = decodedCID + return nil +} + +type Multiaddr struct{ multiaddr.Multiaddr } + +func (m *Multiaddr) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + ma, err := multiaddr.NewMultiaddr(s) + if err != nil { + return err + } + m.Multiaddr = ma + return nil +} diff --git a/routing/http/types/provider.go b/routing/http/types/provider.go new file mode 100644 index 000000000..d0f998822 --- /dev/null +++ b/routing/http/types/provider.go @@ -0,0 +1,146 @@ +package types + +import ( + "encoding/json" +) + +// WriteProviderRecord is a type that enforces structs to imlement it to avoid confusion +type WriteProviderRecord interface { + IsWriteProviderRecord() +} + +// ReadProviderRecord is a type that enforces structs to imlement it to avoid confusion +type ReadProviderRecord interface { + IsReadProviderRecord() +} + +type WriteProvidersRequest struct { + Providers []WriteProviderRecord +} + +func (r *WriteProvidersRequest) UnmarshalJSON(b []byte) error { + type wpr struct { + Providers []json.RawMessage + } + var tempWPR wpr + err := json.Unmarshal(b, &tempWPR) + if err != nil { + return err + } + + for _, provBytes := range tempWPR.Providers { + var rawProv UnknownProviderRecord + err := json.Unmarshal(provBytes, &rawProv) + if err != nil { + return err + } + + switch rawProv.Protocol { + case BitswapProviderID: + var prov WriteBitswapProviderRecord + err := json.Unmarshal(rawProv.Bytes, &prov) + if err != nil { + return err + } + r.Providers = append(r.Providers, &prov) + default: + var prov UnknownProviderRecord + err := json.Unmarshal(b, &prov) + if err != nil { + return err + } + r.Providers = append(r.Providers, &prov) + } + } + return nil +} + +// ProviderResponse is implemented for any ProviderResponse. It needs to have a Protocol field. +type ProviderResponse interface { + GetProtocol() string +} + +// WriteProvidersResponse is the result of a Provide operation +type WriteProvidersResponse struct { + ProvideResults []ProviderResponse +} + +// rawWriteProvidersResponse is a helper struct to make possible to parse WriteProvidersResponse's +type rawWriteProvidersResponse struct { + ProvideResults []json.RawMessage +} + +func (r *WriteProvidersResponse) UnmarshalJSON(b []byte) error { + var tempWPR rawWriteProvidersResponse + err := json.Unmarshal(b, &tempWPR) + if err != nil { + return err + } + + for _, provBytes := range tempWPR.ProvideResults { + var rawProv UnknownProviderRecord + err := json.Unmarshal(provBytes, &rawProv) + if err != nil { + return err + } + + switch rawProv.GetProtocol() { + case BitswapProviderID: + var prov WriteBitswapProviderRecordResponse + err := json.Unmarshal(rawProv.Bytes, &prov) + if err != nil { + return err + } + r.ProvideResults = append(r.ProvideResults, &prov) + default: + r.ProvideResults = append(r.ProvideResults, &rawProv) + } + } + + return nil +} + +// ReadProvidersResponse is the result of a Provide request +type ReadProvidersResponse struct { + Providers []ProviderResponse +} + +// rawReadProvidersResponse is a helper struct to make possible to parse ReadProvidersResponse's +type rawReadProvidersResponse struct { + Providers []json.RawMessage +} + +func (r *ReadProvidersResponse) UnmarshalJSON(b []byte) error { + var tempFPR rawReadProvidersResponse + err := json.Unmarshal(b, &tempFPR) + if err != nil { + return err + } + + for _, provBytes := range tempFPR.Providers { + var readProv UnknownProviderRecord + err := json.Unmarshal(provBytes, &readProv) + if err != nil { + return err + } + + switch readProv.Protocol { + case BitswapProviderID: + var prov ReadBitswapProviderRecord + err := json.Unmarshal(readProv.Bytes, &prov) + if err != nil { + return err + } + r.Providers = append(r.Providers, &prov) + default: + var prov UnknownProviderRecord + err := json.Unmarshal(b, &prov) + if err != nil { + return err + } + r.Providers = append(r.Providers, &prov) + } + + } + return nil +} diff --git a/routing/http/types_bitswap.go b/routing/http/types/provider_bitswap.go similarity index 59% rename from routing/http/types_bitswap.go rename to routing/http/types/provider_bitswap.go index 5a5c8ed2f..95c85d0e9 100644 --- a/routing/http/types_bitswap.go +++ b/routing/http/types/provider_bitswap.go @@ -1,33 +1,32 @@ -package http +package types import ( "crypto/sha256" "encoding/json" "errors" "fmt" - "time" + "github.com/ipfs/go-libipfs/routing/http/internal/drjson" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multibase" ) -type BitswapReadProviderResponse struct { - Protocol string - ID *peer.ID - Addrs []Multiaddr -} +const BitswapProviderID = "bitswap" + +var _ WriteProviderRecord = &WriteBitswapProviderRecord{} -type BitswapWriteProviderRequest struct { +// WriteBitswapProviderRecord is used when we want to add a new provider record that is using bitswap. +type WriteBitswapProviderRecord struct { Protocol string Signature string // this content must be untouched because it is signed and we need to verify it - RawPayload json.RawMessage `json:"Payload"` - Payload BitswapWriteProviderRequestPayload `json:"-"` + RawPayload json.RawMessage `json:"Payload"` + Payload BitswapPayload `json:"-"` } -type BitswapWriteProviderRequestPayload struct { +type BitswapPayload struct { Keys []CID Timestamp *Time AdvisoryTTL *Duration @@ -35,9 +34,11 @@ type BitswapWriteProviderRequestPayload struct { Addrs []Multiaddr } -type tmpBWPR BitswapWriteProviderRequest +func (*WriteBitswapProviderRecord) IsWriteProviderRecord() {} -func (p *BitswapWriteProviderRequest) UnmarshalJSON(b []byte) error { +type tmpBWPR WriteBitswapProviderRecord + +func (p *WriteBitswapProviderRecord) UnmarshalJSON(b []byte) error { var bwp tmpBWPR err := json.Unmarshal(b, &bwp) if err != nil { @@ -51,12 +52,12 @@ func (p *BitswapWriteProviderRequest) UnmarshalJSON(b []byte) error { return json.Unmarshal(bwp.RawPayload, &p.Payload) } -func (p *BitswapWriteProviderRequest) IsSigned() bool { +func (p *WriteBitswapProviderRecord) IsSigned() bool { return p.Signature != "" } -func (p *BitswapWriteProviderRequest) setRawPayload() error { - payloadBytes, err := json.Marshal(p.Payload) +func (p *WriteBitswapProviderRecord) setRawPayload() error { + payloadBytes, err := drjson.MarshalJSONBytes(p.Payload) if err != nil { return fmt.Errorf("marshaling bitswap write provider payload: %w", err) } @@ -66,7 +67,7 @@ func (p *BitswapWriteProviderRequest) setRawPayload() error { return nil } -func (p *BitswapWriteProviderRequest) Sign(peerID peer.ID, key crypto.PrivKey) error { +func (p *WriteBitswapProviderRecord) Sign(peerID peer.ID, key crypto.PrivKey) error { if p.IsSigned() { return errors.New("already signed") } @@ -102,7 +103,7 @@ func (p *BitswapWriteProviderRequest) Sign(peerID peer.ID, key crypto.PrivKey) e return nil } -func (p *BitswapWriteProviderRequest) Verify() error { +func (p *WriteBitswapProviderRecord) Verify() error { if !p.IsSigned() { return errors.New("not signed") } @@ -142,6 +143,30 @@ func (p *BitswapWriteProviderRequest) Verify() error { return nil } -type BitswapWriteProviderResponse struct { - AdvisoryTTL time.Duration +var _ ProviderResponse = &WriteBitswapProviderRecordResponse{} + +// WriteBitswapProviderRecordResponse will be returned as a result of WriteBitswapProviderRecord +type WriteBitswapProviderRecordResponse struct { + Protocol string + AdvisoryTTL *Duration +} + +func (wbprr *WriteBitswapProviderRecordResponse) GetProtocol() string { + return wbprr.Protocol } + +var _ ReadProviderRecord = &ReadBitswapProviderRecord{} +var _ ProviderResponse = &ReadBitswapProviderRecord{} + +// ReadBitswapProviderRecord is a provider result with parameters for bitswap providers +type ReadBitswapProviderRecord struct { + Protocol string + ID *peer.ID + Addrs []Multiaddr +} + +func (rbpr *ReadBitswapProviderRecord) GetProtocol() string { + return rbpr.Protocol +} + +func (*ReadBitswapProviderRecord) IsReadProviderRecord() {} diff --git a/routing/http/types/provider_unknown.go b/routing/http/types/provider_unknown.go new file mode 100644 index 000000000..190b8300a --- /dev/null +++ b/routing/http/types/provider_unknown.go @@ -0,0 +1,51 @@ +package types + +import ( + "encoding/json" + + "github.com/ipfs/go-libipfs/routing/http/internal/drjson" +) + +var _ ReadProviderRecord = &UnknownProviderRecord{} +var _ WriteProviderRecord = &UnknownProviderRecord{} +var _ ProviderResponse = &UnknownProviderRecord{} + +// UnknownProviderRecord is used when we cannot parse the provider record using `GetProtocol` +type UnknownProviderRecord struct { + Protocol string + Bytes []byte +} + +func (u *UnknownProviderRecord) GetProtocol() string { + return u.Protocol +} + +func (u *UnknownProviderRecord) IsReadProviderRecord() {} +func (u UnknownProviderRecord) IsWriteProviderRecord() {} + +func (u *UnknownProviderRecord) UnmarshalJSON(b []byte) error { + m := map[string]interface{}{} + if err := json.Unmarshal(b, &m); err != nil { + return err + } + + ps, ok := m["Protocol"].(string) + if ok { + u.Protocol = ps + } + + u.Bytes = b + + return nil +} + +func (u UnknownProviderRecord) MarshalJSON() ([]byte, error) { + m := map[string]interface{}{} + err := json.Unmarshal(u.Bytes, &m) + if err != nil { + return nil, err + } + m["Protocol"] = u.Protocol + + return drjson.MarshalJSONBytes(m) +} diff --git a/routing/http/types/time.go b/routing/http/types/time.go new file mode 100644 index 000000000..6ef0f8889 --- /dev/null +++ b/routing/http/types/time.go @@ -0,0 +1,36 @@ +package types + +import ( + "encoding/json" + "time" + + "github.com/ipfs/go-libipfs/routing/http/internal/drjson" +) + +type Time struct{ time.Time } + +func (t *Time) MarshalJSON() ([]byte, error) { + return drjson.MarshalJSONBytes(t.Time.UnixMilli()) +} +func (t *Time) UnmarshalJSON(b []byte) error { + var timestamp int64 + err := json.Unmarshal(b, ×tamp) + if err != nil { + return err + } + t.Time = time.UnixMilli(timestamp) + return nil +} + +type Duration struct{ time.Duration } + +func (d *Duration) MarshalJSON() ([]byte, error) { return drjson.MarshalJSONBytes(d.Duration) } +func (d *Duration) UnmarshalJSON(b []byte) error { + var dur int64 + err := json.Unmarshal(b, &dur) + if err != nil { + return err + } + d.Duration = time.Duration(dur) + return nil +}