dht.go 16.6 KB
Newer Older
1 2 3
package commands

import (
4
	"context"
5
	"encoding/base64"
6 7 8
	"errors"
	"fmt"
	"io"
9
	"io/ioutil"
10 11
	"time"

Overbool's avatar
Overbool committed
12
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
Jeromy's avatar
Jeromy committed
13

Jakub Sztandera's avatar
Jakub Sztandera committed
14 15 16 17 18
	cid "github.com/ipfs/go-cid"
	cmds "github.com/ipfs/go-ipfs-cmds"
	ipld "github.com/ipfs/go-ipld-format"
	dag "github.com/ipfs/go-merkledag"
	path "github.com/ipfs/go-path"
Raúl Kripalani's avatar
Raúl Kripalani committed
19 20
	peer "github.com/libp2p/go-libp2p-core/peer"
	routing "github.com/libp2p/go-libp2p-core/routing"
21 22
)

23 24
var ErrNotDHT = errors.New("routing service is not a DHT")

25 26 27
// TODO: Factor into `ipfs dht` and `ipfs routing`.
// Everything *except `query` goes into `ipfs routing`.

28
var DhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
29
	Helptext: cmds.HelpText{
rht's avatar
rht committed
30
		Tagline:          "Issue commands directly through the DHT.",
31 32 33 34
		ShortDescription: ``,
	},

	Subcommands: map[string]*cmds.Command{
35 36
		"query":     queryDhtCmd,
		"findprovs": findProvidersDhtCmd,
Jeromy's avatar
Jeromy committed
37
		"findpeer":  findPeerDhtCmd,
Jeromy's avatar
Jeromy committed
38 39
		"get":       getValueDhtCmd,
		"put":       putValueDhtCmd,
40
		"provide":   provideRefDhtCmd,
41 42 43
	},
}

Kejie Zhang's avatar
Kejie Zhang committed
44
const (
Steven Allen's avatar
Steven Allen committed
45
	dhtVerboseOptionName = "verbose"
Kejie Zhang's avatar
Kejie Zhang committed
46 47
)

48
var queryDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
49
	Helptext: cmds.HelpText{
50 51
		Tagline:          "Find the closest Peer IDs to a given Peer ID by querying the DHT.",
		ShortDescription: "Outputs a list of newline-delimited Peer IDs.",
52 53
	},

Steven Allen's avatar
Steven Allen committed
54 55
	Arguments: []cmds.Argument{
		cmds.StringArg("peerID", true, true, "The peerID to run the query against."),
56
	},
Steven Allen's avatar
Steven Allen committed
57 58
	Options: []cmds.Option{
		cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
59
	},
Overbool's avatar
Overbool committed
60 61
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
62
		if err != nil {
Overbool's avatar
Overbool committed
63
			return err
64 65
		}

Overbool's avatar
Overbool committed
66 67
		if nd.DHT == nil {
			return ErrNotDHT
68 69
		}

70
		id, err := peer.Decode(req.Arguments[0])
Steven Allen's avatar
Steven Allen committed
71
		if err != nil {
Overbool's avatar
Overbool committed
72
			return cmds.ClientError("invalid peer ID")
Steven Allen's avatar
Steven Allen committed
73
		}
74

Overbool's avatar
Overbool committed
75
		ctx, cancel := context.WithCancel(req.Context)
Raúl Kripalani's avatar
Raúl Kripalani committed
76
		ctx, events := routing.RegisterForQueryEvents(ctx)
77

Steven Allen's avatar
Steven Allen committed
78 79 80 81 82
		dht := nd.DHT.WAN
		if !nd.DHT.WANActive() {
			dht = nd.DHT.LAN
		}

83
		errCh := make(chan error, 1)
84
		go func() {
85
			defer close(errCh)
86
			defer cancel()
87 88 89 90 91 92 93 94 95 96 97 98 99
			closestPeers, err := dht.GetClosestPeers(ctx, string(id))
			if closestPeers != nil {
				for p := range closestPeers {
					routing.PublishQueryEvent(ctx, &routing.QueryEvent{
						ID:   p,
						Type: routing.FinalPeer,
					})
				}
			}

			if err != nil {
				errCh <- err
				return
100 101 102
			}
		}()

Overbool's avatar
Overbool committed
103 104 105
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
106
			}
Overbool's avatar
Overbool committed
107
		}
Overbool's avatar
Overbool committed
108

109
		return <-errCh
110
	},
Overbool's avatar
Overbool committed
111
	Encoders: cmds.EncoderMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
112
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
113
			pfm := pfuncMap{
114 115
				routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
					fmt.Fprintf(out, "%s\n", obj.ID)
116
					return nil
117 118
				},
			}
Overbool's avatar
Overbool committed
119
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
120
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
121
		}),
122
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
123
	Type: routing.QueryEvent{},
124
}
125

Kejie Zhang's avatar
Kejie Zhang committed
126 127 128 129
const (
	numProvidersOptionName = "num-providers"
)

130
var findProvidersDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
131
	Helptext: cmds.HelpText{
132
		Tagline:          "Find peers that can provide a specific value, given a key.",
133
		ShortDescription: "Outputs a list of newline-delimited provider Peer IDs.",
134 135
	},

Steven Allen's avatar
Steven Allen committed
136 137
	Arguments: []cmds.Argument{
		cmds.StringArg("key", true, true, "The key to find providers for."),
138
	},
Steven Allen's avatar
Steven Allen committed
139 140 141
	Options: []cmds.Option{
		cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
		cmds.IntOption(numProvidersOptionName, "n", "The number of providers to find.").WithDefault(20),
142
	},
Overbool's avatar
Overbool committed
143 144
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		n, err := cmdenv.GetNode(env)
145
		if err != nil {
Overbool's avatar
Overbool committed
146
			return err
147 148
		}

149
		if !n.IsOnline {
Overbool's avatar
Overbool committed
150
			return ErrNotOnline
151 152
		}

Overbool's avatar
Overbool committed
153
		numProviders, _ := req.Options[numProvidersOptionName].(int)
154
		if numProviders < 1 {
Overbool's avatar
Overbool committed
155
			return fmt.Errorf("number of providers must be greater than 0")
156
		}
157

Overbool's avatar
Overbool committed
158
		c, err := cid.Parse(req.Arguments[0])
159

160
		if err != nil {
Overbool's avatar
Overbool committed
161
			return err
162 163
		}

Overbool's avatar
Overbool committed
164
		ctx, cancel := context.WithCancel(req.Context)
Raúl Kripalani's avatar
Raúl Kripalani committed
165
		ctx, events := routing.RegisterForQueryEvents(ctx)
166

167
		pchan := n.Routing.FindProvidersAsync(ctx, c, numProviders)
168 169

		go func() {
170
			defer cancel()
171 172
			for p := range pchan {
				np := p
Raúl Kripalani's avatar
Raúl Kripalani committed
173 174 175
				routing.PublishQueryEvent(ctx, &routing.QueryEvent{
					Type:      routing.Provider,
					Responses: []*peer.AddrInfo{&np},
176
				})
177 178
			}
		}()
Overbool's avatar
Overbool committed
179 180 181 182 183
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
			}
		}
Overbool's avatar
Overbool committed
184

Overbool's avatar
Overbool committed
185
		return nil
186
	},
Overbool's avatar
Overbool committed
187
	Encoders: cmds.EncoderMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
188
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
189
			pfm := pfuncMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
190
				routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
191
					if verbose {
192
						fmt.Fprintf(out, "* closest peer %s\n", obj.ID)
193
					}
194
					return nil
195
				},
Raúl Kripalani's avatar
Raúl Kripalani committed
196
				routing.Provider: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
197 198
					prov := obj.Responses[0]
					if verbose {
199
						fmt.Fprintf(out, "provider: ")
200
					}
201
					fmt.Fprintf(out, "%s\n", prov.ID.Pretty())
202 203
					if verbose {
						for _, a := range prov.Addrs {
204
							fmt.Fprintf(out, "\t%s\n", a)
205 206
						}
					}
207
					return nil
208 209 210
				},
			}

Overbool's avatar
Overbool committed
211
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
212
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
213
		}),
214
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
215
	Type: routing.QueryEvent{},
216
}
Jeromy's avatar
Jeromy committed
217

Kejie Zhang's avatar
Kejie Zhang committed
218 219 220 221
const (
	recursiveOptionName = "recursive"
)

222
var provideRefDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
223
	Helptext: cmds.HelpText{
224 225 226
		Tagline: "Announce to the network that you are providing given values.",
	},

Steven Allen's avatar
Steven Allen committed
227 228
	Arguments: []cmds.Argument{
		cmds.StringArg("key", true, true, "The key[s] to send provide records for.").EnableStdin(),
229
	},
Steven Allen's avatar
Steven Allen committed
230 231 232
	Options: []cmds.Option{
		cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
		cmds.BoolOption(recursiveOptionName, "r", "Recursively provide entire graph."),
233
	},
Overbool's avatar
Overbool committed
234 235
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
236
		if err != nil {
Overbool's avatar
Overbool committed
237
			return err
238 239
		}

240
		if !nd.IsOnline {
Overbool's avatar
Overbool committed
241
			return ErrNotOnline
242 243
		}

Overbool's avatar
Overbool committed
244 245
		if len(nd.PeerHost.Network().Conns()) == 0 {
			return errors.New("cannot provide, no connected peers")
246 247
		}

248 249 250 251 252 253 254
		// Needed to parse stdin args.
		// TODO: Lazy Load
		err = req.ParseBodyArgs()
		if err != nil {
			return err
		}

Overbool's avatar
Overbool committed
255
		rec, _ := req.Options[recursiveOptionName].(bool)
Jeromy's avatar
Jeromy committed
256

257
		var cids []cid.Cid
Overbool's avatar
Overbool committed
258
		for _, arg := range req.Arguments {
Jeromy's avatar
Jeromy committed
259 260
			c, err := cid.Decode(arg)
			if err != nil {
Overbool's avatar
Overbool committed
261
				return err
262 263
			}

Overbool's avatar
Overbool committed
264
			has, err := nd.Blockstore.Has(c)
265
			if err != nil {
Overbool's avatar
Overbool committed
266
				return err
267 268 269
			}

			if !has {
Overbool's avatar
Overbool committed
270
				return fmt.Errorf("block %s not found locally, cannot provide", c)
271 272
			}

Jeromy's avatar
Jeromy committed
273
			cids = append(cids, c)
274 275
		}

Overbool's avatar
Overbool committed
276
		ctx, cancel := context.WithCancel(req.Context)
Raúl Kripalani's avatar
Raúl Kripalani committed
277
		ctx, events := routing.RegisterForQueryEvents(ctx)
278

279
		var provideErr error
280
		go func() {
281
			defer cancel()
Jeromy's avatar
Jeromy committed
282
			if rec {
283
				provideErr = provideKeysRec(ctx, nd.Routing, nd.DAG, cids)
Jeromy's avatar
Jeromy committed
284
			} else {
285
				provideErr = provideKeys(ctx, nd.Routing, cids)
Jeromy's avatar
Jeromy committed
286
			}
287
			if provideErr != nil {
Raúl Kripalani's avatar
Raúl Kripalani committed
288 289
				routing.PublishQueryEvent(ctx, &routing.QueryEvent{
					Type:  routing.QueryError,
290
					Extra: provideErr.Error(),
Jeromy's avatar
Jeromy committed
291
				})
292 293
			}
		}()
Overbool's avatar
Overbool committed
294

Overbool's avatar
Overbool committed
295 296 297 298 299 300
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
			}
		}

301
		return provideErr
302
	},
Overbool's avatar
Overbool committed
303
	Encoders: cmds.EncoderMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
304
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
305
			pfm := pfuncMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
306
				routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
307 308 309
					if verbose {
						fmt.Fprintf(out, "sending provider record to peer %s\n", obj.ID)
					}
310
					return nil
311 312 313
				},
			}

Overbool's avatar
Overbool committed
314
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
315
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
316
		}),
317
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
318
	Type: routing.QueryEvent{},
319 320
}

Raúl Kripalani's avatar
Raúl Kripalani committed
321
func provideKeys(ctx context.Context, r routing.Routing, cids []cid.Cid) error {
Jeromy's avatar
Jeromy committed
322
	for _, c := range cids {
323
		err := r.Provide(ctx, c, true)
Jeromy's avatar
Jeromy committed
324
		if err != nil {
Jeromy's avatar
Jeromy committed
325
			return err
Jeromy's avatar
Jeromy committed
326 327
		}
	}
Jeromy's avatar
Jeromy committed
328
	return nil
Jeromy's avatar
Jeromy committed
329 330
}

Raúl Kripalani's avatar
Raúl Kripalani committed
331
func provideKeysRec(ctx context.Context, r routing.Routing, dserv ipld.DAGService, cids []cid.Cid) error {
332
	provided := cid.NewSet()
Jeromy's avatar
Jeromy committed
333
	for _, c := range cids {
334
		kset := cid.NewSet()
Jeromy's avatar
Jeromy committed
335

336
		err := dag.Walk(ctx, dag.GetLinksDirect(dserv), c, kset.Visit)
Jeromy's avatar
Jeromy committed
337
		if err != nil {
Jeromy's avatar
Jeromy committed
338
			return err
Jeromy's avatar
Jeromy committed
339 340 341
		}

		for _, k := range kset.Keys() {
342
			if provided.Has(k) {
343 344 345
				continue
			}

346
			err = r.Provide(ctx, k, true)
Jeromy's avatar
Jeromy committed
347
			if err != nil {
Jeromy's avatar
Jeromy committed
348
				return err
Jeromy's avatar
Jeromy committed
349
			}
350
			provided.Add(k)
Jeromy's avatar
Jeromy committed
351 352 353
		}
	}

Jeromy's avatar
Jeromy committed
354
	return nil
Jeromy's avatar
Jeromy committed
355 356
}

Jeromy's avatar
Jeromy committed
357
var findPeerDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
358
	Helptext: cmds.HelpText{
359
		Tagline:          "Find the multiaddresses associated with a Peer ID.",
360
		ShortDescription: "Outputs a list of newline-delimited multiaddresses.",
Jeromy's avatar
Jeromy committed
361 362
	},

Steven Allen's avatar
Steven Allen committed
363 364
	Arguments: []cmds.Argument{
		cmds.StringArg("peerID", true, true, "The ID of the peer to search for."),
365
	},
Steven Allen's avatar
Steven Allen committed
366 367
	Options: []cmds.Option{
		cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
Jeromy's avatar
Jeromy committed
368
	},
Overbool's avatar
Overbool committed
369 370
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
371
		if err != nil {
Overbool's avatar
Overbool committed
372
			return err
Jeromy's avatar
Jeromy committed
373 374
		}

375
		if !nd.IsOnline {
Overbool's avatar
Overbool committed
376
			return ErrNotOnline
Jeromy's avatar
Jeromy committed
377 378
		}

379
		pid, err := peer.Decode(req.Arguments[0])
Jeromy's avatar
Jeromy committed
380
		if err != nil {
Overbool's avatar
Overbool committed
381
			return err
Jeromy's avatar
Jeromy committed
382 383
		}

Overbool's avatar
Overbool committed
384
		ctx, cancel := context.WithCancel(req.Context)
Raúl Kripalani's avatar
Raúl Kripalani committed
385
		ctx, events := routing.RegisterForQueryEvents(ctx)
Jeromy's avatar
Jeromy committed
386

387
		var findPeerErr error
388
		go func() {
389
			defer cancel()
Raúl Kripalani's avatar
Raúl Kripalani committed
390
			var pi peer.AddrInfo
391 392
			pi, findPeerErr = nd.Routing.FindPeer(ctx, pid)
			if findPeerErr != nil {
Raúl Kripalani's avatar
Raúl Kripalani committed
393 394
				routing.PublishQueryEvent(ctx, &routing.QueryEvent{
					Type:  routing.QueryError,
395
					Extra: findPeerErr.Error(),
396 397 398 399
				})
				return
			}

Raúl Kripalani's avatar
Raúl Kripalani committed
400 401 402
			routing.PublishQueryEvent(ctx, &routing.QueryEvent{
				Type:      routing.FinalPeer,
				Responses: []*peer.AddrInfo{&pi},
403 404
			})
		}()
Overbool's avatar
Overbool committed
405

Overbool's avatar
Overbool committed
406 407 408 409 410 411
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
			}
		}

412
		return findPeerErr
Jeromy's avatar
Jeromy committed
413
	},
Overbool's avatar
Overbool committed
414
	Encoders: cmds.EncoderMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
415
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
416
			pfm := pfuncMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
417
				routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
418 419
					pi := obj.Responses[0]
					for _, a := range pi.Addrs {
420
						fmt.Fprintf(out, "%s\n", a)
421
					}
422
					return nil
423 424
				},
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
425

Overbool's avatar
Overbool committed
426
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
427
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
428
		}),
Jeromy's avatar
Jeromy committed
429
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
430
	Type: routing.QueryEvent{},
Jeromy's avatar
Jeromy committed
431
}
Jeromy's avatar
Jeromy committed
432 433

var getValueDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
434
	Helptext: cmds.HelpText{
435
		Tagline: "Given a key, query the routing system for its best value.",
Jeromy's avatar
Jeromy committed
436
		ShortDescription: `
437 438
Outputs the best value for the given key.

439 440 441 442
There may be several different values for a given key stored in the routing
system; in this context 'best' means the record that is most desirable. There is
no one metric for 'best': it depends entirely on the key type. For IPNS, 'best'
is the record that is both valid and has the highest sequence number (freshest).
443
Different key types can specify other 'best' rules.
Jeromy's avatar
Jeromy committed
444 445 446
`,
	},

Steven Allen's avatar
Steven Allen committed
447 448
	Arguments: []cmds.Argument{
		cmds.StringArg("key", true, true, "The key to find a value for."),
Jeromy's avatar
Jeromy committed
449
	},
Steven Allen's avatar
Steven Allen committed
450 451
	Options: []cmds.Option{
		cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
Jeromy's avatar
Jeromy committed
452
	},
Overbool's avatar
Overbool committed
453 454
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
455
		if err != nil {
Overbool's avatar
Overbool committed
456
			return err
Jeromy's avatar
Jeromy committed
457 458
		}

459
		if !nd.IsOnline {
Overbool's avatar
Overbool committed
460
			return ErrNotOnline
Jeromy's avatar
Jeromy committed
461 462
		}

Overbool's avatar
Overbool committed
463
		dhtkey, err := escapeDhtKey(req.Arguments[0])
464
		if err != nil {
Overbool's avatar
Overbool committed
465
			return err
466 467
		}

Overbool's avatar
Overbool committed
468
		ctx, cancel := context.WithCancel(req.Context)
Raúl Kripalani's avatar
Raúl Kripalani committed
469
		ctx, events := routing.RegisterForQueryEvents(ctx)
470

471
		var getErr error
Jeromy's avatar
Jeromy committed
472
		go func() {
473
			defer cancel()
474 475 476
			var val []byte
			val, getErr = nd.Routing.GetValue(ctx, dhtkey)
			if getErr != nil {
Raúl Kripalani's avatar
Raúl Kripalani committed
477 478
				routing.PublishQueryEvent(ctx, &routing.QueryEvent{
					Type:  routing.QueryError,
479
					Extra: getErr.Error(),
Jeromy's avatar
Jeromy committed
480 481
				})
			} else {
Raúl Kripalani's avatar
Raúl Kripalani committed
482 483
				routing.PublishQueryEvent(ctx, &routing.QueryEvent{
					Type:  routing.Value,
484
					Extra: base64.StdEncoding.EncodeToString(val),
Jeromy's avatar
Jeromy committed
485 486 487
				})
			}
		}()
Overbool's avatar
Overbool committed
488

Overbool's avatar
Overbool committed
489 490 491 492 493 494
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
			}
		}

495
		return getErr
Jeromy's avatar
Jeromy committed
496
	},
Overbool's avatar
Overbool committed
497
	Encoders: cmds.EncoderMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
498
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
499
			pfm := pfuncMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
500
				routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
501
					if verbose {
502 503
						_, err := fmt.Fprintf(out, "got value: '%s'\n", obj.Extra)
						return err
504
					}
505 506 507 508 509 510
					res, err := base64.StdEncoding.DecodeString(obj.Extra)
					if err != nil {
						return err
					}
					_, err = out.Write(res)
					return err
511 512
				},
			}
Jan Winkelmann's avatar
Jan Winkelmann committed
513

Overbool's avatar
Overbool committed
514
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
515
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
516
		}),
Jeromy's avatar
Jeromy committed
517
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
518
	Type: routing.QueryEvent{},
Jeromy's avatar
Jeromy committed
519 520 521
}

var putValueDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
522
	Helptext: cmds.HelpText{
523
		Tagline: "Write a key/value pair to the routing system.",
Jeromy's avatar
Jeromy committed
524
		ShortDescription: `
525 526
Given a key of the form /foo/bar and a valid value for that key, this will write
that value to the routing system with that key.
527

528 529 530
Keys have two parts: a keytype (foo) and the key name (bar). IPNS uses the
/ipns keytype, and expects the key name to be a Peer ID. IPNS entries are
specifically formatted (protocol buffer).
531

532 533
You may only use keytypes that are supported in your ipfs binary: currently
this is only /ipns. Unless you have a relatively deep understanding of the
534
go-ipfs routing internals, you likely want to be using 'ipfs name publish' instead
535
of this.
536

537 538 539
The value must be a valid value for the given key type. For example, if the key
is /ipns/QmFoo, the value must be IPNS record (protobuf) signed with the key
identified by QmFoo.
Jeromy's avatar
Jeromy committed
540 541 542
`,
	},

Steven Allen's avatar
Steven Allen committed
543 544
	Arguments: []cmds.Argument{
		cmds.StringArg("key", true, false, "The key to store the value at."),
545
		cmds.FileArg("value-file", true, false, "A path to a file containing the value to store.").EnableStdin(),
Jeromy's avatar
Jeromy committed
546
	},
Steven Allen's avatar
Steven Allen committed
547 548
	Options: []cmds.Option{
		cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
Jeromy's avatar
Jeromy committed
549
	},
Overbool's avatar
Overbool committed
550 551
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
552
		if err != nil {
Overbool's avatar
Overbool committed
553
			return err
Jeromy's avatar
Jeromy committed
554 555
		}

556
		if !nd.IsOnline {
Overbool's avatar
Overbool committed
557
			return ErrNotOnline
Jeromy's avatar
Jeromy committed
558 559
		}

Overbool's avatar
Overbool committed
560
		key, err := escapeDhtKey(req.Arguments[0])
561
		if err != nil {
Overbool's avatar
Overbool committed
562
			return err
563 564
		}

565 566 567 568 569 570 571 572 573 574
		file, err := cmdenv.GetFileArg(req.Files.Entries())
		if err != nil {
			return err
		}
		defer file.Close()

		data, err := ioutil.ReadAll(file)
		if err != nil {
			return err
		}
Jeromy's avatar
Jeromy committed
575

Overbool's avatar
Overbool committed
576
		ctx, cancel := context.WithCancel(req.Context)
Raúl Kripalani's avatar
Raúl Kripalani committed
577
		ctx, events := routing.RegisterForQueryEvents(ctx)
578

579
		var putErr error
Jeromy's avatar
Jeromy committed
580
		go func() {
581
			defer cancel()
582 583
			putErr = nd.Routing.PutValue(ctx, key, []byte(data))
			if putErr != nil {
Raúl Kripalani's avatar
Raúl Kripalani committed
584 585
				routing.PublishQueryEvent(ctx, &routing.QueryEvent{
					Type:  routing.QueryError,
586
					Extra: putErr.Error(),
Jeromy's avatar
Jeromy committed
587 588 589
				})
			}
		}()
Overbool's avatar
Overbool committed
590

Overbool's avatar
Overbool committed
591 592 593 594 595 596
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
			}
		}

597
		return putErr
Jeromy's avatar
Jeromy committed
598
	},
Overbool's avatar
Overbool committed
599
	Encoders: cmds.EncoderMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
600
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
601
			pfm := pfuncMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
602
				routing.FinalPeer: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
603 604 605
					if verbose {
						fmt.Fprintf(out, "* closest peer %s\n", obj.ID)
					}
606
					return nil
607
				},
Raúl Kripalani's avatar
Raúl Kripalani committed
608
				routing.Value: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
609
					fmt.Fprintf(out, "%s\n", obj.ID.Pretty())
610
					return nil
611 612
				},
			}
Jeromy's avatar
Jeromy committed
613

Overbool's avatar
Overbool committed
614
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
Jeromy's avatar
Jeromy committed
615

616
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
617
		}),
Jeromy's avatar
Jeromy committed
618
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
619
	Type: routing.QueryEvent{},
Jeromy's avatar
Jeromy committed
620
}
621

Raúl Kripalani's avatar
Raúl Kripalani committed
622 623
type printFunc func(obj *routing.QueryEvent, out io.Writer, verbose bool) error
type pfuncMap map[routing.QueryEventType]printFunc
624

Raúl Kripalani's avatar
Raúl Kripalani committed
625
func printEvent(obj *routing.QueryEvent, out io.Writer, verbose bool, override pfuncMap) error {
626 627 628 629 630 631
	if verbose {
		fmt.Fprintf(out, "%s: ", time.Now().Format("15:04:05.000"))
	}

	if override != nil {
		if pf, ok := override[obj.Type]; ok {
632
			return pf(obj, out, verbose)
633 634 635 636
		}
	}

	switch obj.Type {
Raúl Kripalani's avatar
Raúl Kripalani committed
637
	case routing.SendingQuery:
638 639 640
		if verbose {
			fmt.Fprintf(out, "* querying %s\n", obj.ID)
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
641
	case routing.Value:
642 643 644 645 646
		if verbose {
			fmt.Fprintf(out, "got value: '%s'\n", obj.Extra)
		} else {
			fmt.Fprint(out, obj.Extra)
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
647
	case routing.PeerResponse:
648 649 650 651 652 653
		if verbose {
			fmt.Fprintf(out, "* %s says use ", obj.ID)
			for _, p := range obj.Responses {
				fmt.Fprintf(out, "%s ", p.ID)
			}
			fmt.Fprintln(out)
654
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
655
	case routing.QueryError:
656 657 658
		if verbose {
			fmt.Fprintf(out, "error: %s\n", obj.Extra)
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
659
	case routing.DialingPeer:
660 661 662
		if verbose {
			fmt.Fprintf(out, "dialing peer: %s\n", obj.ID)
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
663
	case routing.AddingPeer:
664 665 666
		if verbose {
			fmt.Fprintf(out, "adding peer to query: %s\n", obj.ID)
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
667
	case routing.FinalPeer:
668
	default:
669 670 671
		if verbose {
			fmt.Fprintf(out, "unrecognized event type: %d\n", obj.Type)
		}
672
	}
673
	return nil
674 675
}

676
func escapeDhtKey(s string) (string, error) {
rht's avatar
rht committed
677
	parts := path.SplitList(s)
678 679 680
	if len(parts) != 3 ||
		parts[0] != "" ||
		!(parts[1] == "ipns" || parts[1] == "pk") {
681 682
		return "", errors.New("invalid key")
	}
683 684 685 686 687 688

	k, err := peer.Decode(parts[2])
	if err != nil {
		return "", err
	}
	return path.Join(append(parts[:2], string(k))), nil
689
}