diff --git a/client.go b/client.go index 4c4fc4e..098d9b3 100644 --- a/client.go +++ b/client.go @@ -5,23 +5,43 @@ import ( "encoding/json" "errors" "fmt" + "io" "os" "time" "github.com/ipfs/boxo/ipns" "github.com/ipfs/boxo/routing/http/client" "github.com/ipfs/boxo/routing/http/types" + "github.com/ipfs/boxo/routing/http/types/iter" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/peer" ) -func findProviders(ctx context.Context, key cid.Cid, endpoint string, prettyOutput bool) error { +type askClient struct { + drc *client.Client + out io.Writer + pretty bool +} + +func newAskClient(endpoint string, prettyOutput bool, out io.Writer) (*askClient, error) { drc, err := client.New(endpoint) if err != nil { - return err + return nil, err } - recordsIter, err := drc.FindProviders(ctx, key) + if out == nil { + out = os.Stdout + } + + return &askClient{ + drc: drc, + pretty: prettyOutput, + out: out, + }, nil +} + +func (a *askClient) findProviders(ctx context.Context, key cid.Cid) error { + recordsIter, err := a.drc.FindProviders(ctx, key) if err != nil { return err } @@ -40,24 +60,24 @@ func findProviders(ctx context.Context, key cid.Cid, endpoint string, prettyOutp return nil } - if prettyOutput { + if a.pretty { switch res.Val.GetSchema() { case types.SchemaPeer: record := res.Val.(*types.PeerRecord) - fmt.Fprintln(os.Stdout, record.ID) - fmt.Fprintln(os.Stdout, "\tProtocols:", record.Protocols) - fmt.Fprintln(os.Stdout, "\tAddresses:", record.Addrs) + fmt.Fprintln(a.out, record.ID) + fmt.Fprintln(a.out, "\tProtocols:", record.Protocols) + fmt.Fprintln(a.out, "\tAddresses:", record.Addrs) default: // This is an unknown schema. Let's just print it raw. - err := json.NewEncoder(os.Stdout).Encode(res.Val) + err := json.NewEncoder(a.out).Encode(res.Val) if err != nil { return err } } - fmt.Fprintln(os.Stdout) + fmt.Fprintln(a.out) } else { - err := json.NewEncoder(os.Stdout).Encode(res.Val) + err := json.NewEncoder(a.out).Encode(res.Val) if err != nil { return err } @@ -67,13 +87,56 @@ func findProviders(ctx context.Context, key cid.Cid, endpoint string, prettyOutp return nil } -func findPeers(ctx context.Context, pid peer.ID, endpoint string, prettyOutput bool) error { - drc, err := client.New(endpoint) +func (a *askClient) provide(ctx context.Context, records ...*types.AnnouncementRecord) error { + for _, rec := range records { + err := rec.Verify() + if err != nil { + return err + } + } + + recordsIter, err := a.drc.ProvideRecords(ctx, records...) if err != nil { return err } + defer recordsIter.Close() + return a.printProvideResult(recordsIter) +} + +func (a *askClient) printProvideResult(recordsIter iter.ResultIter[*types.AnnouncementResponseRecord]) error { + for recordsIter.Next() { + res := recordsIter.Val() - recordsIter, err := drc.FindPeers(ctx, pid) + // Check for error, but do not complain if we exceeded the timeout. We are + // expecting that to happen: we explicitly defined a timeout. + if res.Err != nil { + if !errors.Is(res.Err, context.DeadlineExceeded) { + return res.Err + } + + return nil + } + + if a.pretty { + if res.Val.Error != "" { + fmt.Fprintf(a.out, "Error: %s", res.Val.Error) + } else { + fmt.Fprintf(a.out, "TTL: %s", res.Val.TTL) + } + fmt.Fprintln(a.out) + } else { + err := json.NewEncoder(a.out).Encode(res.Val) + if err != nil { + return err + } + } + } + + return nil +} + +func (a *askClient) findPeers(ctx context.Context, pid peer.ID) error { + recordsIter, err := a.drc.FindPeers(ctx, pid) if err != nil { return err } @@ -92,13 +155,13 @@ func findPeers(ctx context.Context, pid peer.ID, endpoint string, prettyOutput b return nil } - if prettyOutput { - fmt.Fprintln(os.Stdout, res.Val.ID) - fmt.Fprintln(os.Stdout, "\tProtocols:", res.Val.Protocols) - fmt.Fprintln(os.Stdout, "\tAddresses:", res.Val.Addrs) - fmt.Fprintln(os.Stdout) + if a.pretty { + fmt.Fprintln(a.out, res.Val.ID) + fmt.Fprintln(a.out, "\tProtocols:", res.Val.Protocols) + fmt.Fprintln(a.out, "\tAddresses:", res.Val.Addrs) + fmt.Fprintln(a.out) } else { - err := json.NewEncoder(os.Stdout).Encode(res.Val) + err := json.NewEncoder(a.out).Encode(res.Val) if err != nil { return err } @@ -108,18 +171,30 @@ func findPeers(ctx context.Context, pid peer.ID, endpoint string, prettyOutput b return nil } -func getIPNS(ctx context.Context, name ipns.Name, endpoint string, prettyOutput bool) error { - drc, err := client.New(endpoint) +func (a *askClient) providePeer(ctx context.Context, records ...*types.AnnouncementRecord) error { + for _, rec := range records { + err := rec.Verify() + if err != nil { + return err + } + } + + recordsIter, err := a.drc.ProvidePeerRecords(ctx, records...) if err != nil { return err } + defer recordsIter.Close() - rec, err := drc.GetIPNS(ctx, name) + return a.printProvideResult(recordsIter) +} + +func (a *askClient) getIPNS(ctx context.Context, name ipns.Name) error { + rec, err := a.drc.GetIPNS(ctx, name) if err != nil { return err } - if prettyOutput { + if a.pretty { v, err := rec.Value() if err != nil { return err @@ -135,19 +210,19 @@ func getIPNS(ctx context.Context, name ipns.Name, endpoint string, prettyOutput return err } - fmt.Printf("/ipns/%s\n", name) + fmt.Fprintf(a.out, "/ipns/%s\n", name) // Since [client.Client.GetIPNS] verifies if the retrieved record is valid, we // do not need to verify it again. However, if you were not using this specific // client, but using some other tool, you should always validate the IPNS Record // using the [ipns.Validate] or [ipns.ValidateWithName] functions. - fmt.Println("\tSignature Validated") - fmt.Println("\tValue:", v.String()) - fmt.Println("\tSequence:", seq) - fmt.Println("\tValidityType : EOL/End-of-Life") - fmt.Println("\tValidity:", eol.Format(time.RFC3339)) + fmt.Fprintln(a.out, "\tSignature Validated") + fmt.Fprintln(a.out, "\tValue:", v.String()) + fmt.Fprintln(a.out, "\tSequence:", seq) + fmt.Fprintln(a.out, "\tValidityType : EOL/End-of-Life") + fmt.Fprintln(a.out, "\tValidity:", eol.Format(time.RFC3339)) if ttl, err := rec.TTL(); err == nil { - fmt.Println("\tTTL:", ttl.String()) + fmt.Fprintln(a.out, "\tTTL:", ttl.String()) } return nil @@ -158,20 +233,15 @@ func getIPNS(ctx context.Context, name ipns.Name, endpoint string, prettyOutput return err } - _, err = os.Stdout.Write(raw) + _, err = a.out.Write(raw) return err } -func putIPNS(ctx context.Context, name ipns.Name, record []byte, endpoint string) error { - drc, err := client.New(endpoint) - if err != nil { - return err - } - +func (a *askClient) putIPNS(ctx context.Context, name ipns.Name, record []byte) error { rec, err := ipns.UnmarshalRecord(record) if err != nil { return err } - return drc.PutIPNS(ctx, name, rec) + return a.drc.PutIPNS(ctx, name, rec) } diff --git a/go.mod b/go.mod index b47d6df..1c0fb4e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/CAFxX/httpcompression v0.0.9 github.com/felixge/httpsnoop v1.0.4 - github.com/ipfs/boxo v0.18.1-0.20240220135834-45d50f539dc4 + github.com/ipfs/boxo v0.18.1-0.20240306111355-f261eac52f4c github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-log/v2 v2.5.1 github.com/libp2p/go-libp2p v0.33.0 diff --git a/go.sum b/go.sum index 13bd799..3f9f998 100644 --- a/go.sum +++ b/go.sum @@ -186,8 +186,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/ipfs/boxo v0.18.1-0.20240220135834-45d50f539dc4 h1:2okDVJqJ8Zqnud+JX4rItjtV/7mHzGXJ27BAvI2GwW0= -github.com/ipfs/boxo v0.18.1-0.20240220135834-45d50f539dc4/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= +github.com/ipfs/boxo v0.18.1-0.20240306111355-f261eac52f4c h1:e0PwRwv2XN6K1PBFxaUJfvCTEtXXW4A7d3d/bcoxwRk= +github.com/ipfs/boxo v0.18.1-0.20240306111355-f261eac52f4c/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= diff --git a/main.go b/main.go index 626c008..8ea63ef 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,13 @@ package main import ( + "encoding/json" "errors" "log" "os" "github.com/ipfs/boxo/ipns" + "github.com/ipfs/boxo/routing/http/types" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multibase" @@ -85,7 +87,39 @@ func main() { if err != nil { return err } - return findProviders(ctx.Context, c, ctx.String("endpoint"), ctx.Bool("pretty")) + cl, err := newAskClient(ctx.String("endpoint"), ctx.Bool("pretty"), os.Stdout) + if err != nil { + return err + } + return cl.findProviders(ctx.Context, c) + }, + }, + { + Name: "provide", + Usage: "provide ", + UsageText: "Provide a one or multiple announcement records to the network", + Action: func(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("invalid command, see help") + } + records := []*types.AnnouncementRecord{} + for _, recordPath := range ctx.Args().Slice() { + recordData, err := os.ReadFile(recordPath) + if err != nil { + return err + } + var record *types.AnnouncementRecord + err = json.Unmarshal(recordData, &record) + if err != nil { + return err + } + records = append(records, record) + } + cl, err := newAskClient(ctx.String("endpoint"), ctx.Bool("pretty"), os.Stdout) + if err != nil { + return err + } + return cl.provide(ctx.Context, records...) }, }, { @@ -101,7 +135,39 @@ func main() { if err != nil { return err } - return findPeers(ctx.Context, pid, ctx.String("endpoint"), ctx.Bool("pretty")) + cl, err := newAskClient(ctx.String("endpoint"), ctx.Bool("pretty"), os.Stdout) + if err != nil { + return err + } + return cl.findPeers(ctx.Context, pid) + }, + }, + { + Name: "providepeers", + Usage: "providepeers ", + UsageText: "Provide a one or multiple peer announcement records to the network", + Action: func(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("invalid command, see help") + } + records := []*types.AnnouncementRecord{} + for _, recordPath := range ctx.Args().Slice() { + recordData, err := os.ReadFile(recordPath) + if err != nil { + return err + } + var record *types.AnnouncementRecord + err = json.Unmarshal(recordData, &record) + if err != nil { + return err + } + records = append(records, record) + } + cl, err := newAskClient(ctx.String("endpoint"), ctx.Bool("pretty"), os.Stdout) + if err != nil { + return err + } + return cl.providePeer(ctx.Context, records...) }, }, { @@ -117,7 +183,11 @@ func main() { if err != nil { return err } - return getIPNS(ctx.Context, name, ctx.String("endpoint"), ctx.Bool("pretty")) + cl, err := newAskClient(ctx.String("endpoint"), ctx.Bool("pretty"), os.Stdout) + if err != nil { + return err + } + return cl.getIPNS(ctx.Context, name) }, }, { @@ -139,7 +209,11 @@ func main() { if err != nil { return err } - return putIPNS(ctx.Context, name, recBytes, ctx.String("endpoint")) + cl, err := newAskClient(ctx.String("endpoint"), ctx.Bool("pretty"), os.Stdout) + if err != nil { + return err + } + return cl.putIPNS(ctx.Context, name, recBytes) }, }, }, diff --git a/server_routers.go b/server_routers.go index 1dc9036..b7c5745 100644 --- a/server_routers.go +++ b/server_routers.go @@ -478,7 +478,7 @@ func (d clientRouter) FindProviders(ctx context.Context, cid cid.Cid, limit int) } func (d clientRouter) Provide(ctx context.Context, req *types.AnnouncementRecord) (time.Duration, error) { - return d.provide(func() (iter.ResultIter[*types.AnnouncementRecord], error) { + return d.provide(func() (iter.ResultIter[*types.AnnouncementResponseRecord], error) { return d.Client.ProvideRecords(ctx, req) }) } @@ -488,18 +488,19 @@ func (d clientRouter) FindPeers(ctx context.Context, pid peer.ID, limit int) (it } func (d clientRouter) ProvidePeer(ctx context.Context, req *types.AnnouncementRecord) (time.Duration, error) { - return d.provide(func() (iter.ResultIter[*types.AnnouncementRecord], error) { + return d.provide(func() (iter.ResultIter[*types.AnnouncementResponseRecord], error) { return d.Client.ProvidePeerRecords(ctx, req) }) } -func (d clientRouter) provide(do func() (iter.ResultIter[*types.AnnouncementRecord], error)) (time.Duration, error) { - resIter, err := do() +func (d clientRouter) provide(do func() (iter.ResultIter[*types.AnnouncementResponseRecord], error)) (time.Duration, error) { + resultsIter, err := do() if err != nil { return 0, err } + defer resultsIter.Close() - records, err := iter.ReadAllResults(resIter) + records, err := iter.ReadAllResults(resultsIter) if err != nil { return 0, err } @@ -508,5 +509,5 @@ func (d clientRouter) provide(do func() (iter.ResultIter[*types.AnnouncementReco return 0, errors.New("invalid number of records returned") } - return records[0].Payload.TTL, nil + return records[0].TTL, nil }