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"
Jakub Sztandera's avatar
Jakub Sztandera committed
21
	b58 "github.com/mr-tron/base58/base58"
22 23
)

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

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

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

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

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

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

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

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

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

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

Overbool's avatar
Overbool committed
79
		closestPeers, err := nd.DHT.GetClosestPeers(ctx, string(id))
Jeromy's avatar
Jeromy committed
80
		if err != nil {
81
			cancel()
Overbool's avatar
Overbool committed
82
			return err
Jeromy's avatar
Jeromy committed
83
		}
84 85

		go func() {
86
			defer cancel()
87
			for p := range closestPeers {
Raúl Kripalani's avatar
Raúl Kripalani committed
88
				routing.PublishQueryEvent(ctx, &routing.QueryEvent{
89
					ID:   p,
Raúl Kripalani's avatar
Raúl Kripalani committed
90
					Type: routing.FinalPeer,
91
				})
92 93 94
			}
		}()

Overbool's avatar
Overbool committed
95 96 97
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
98
			}
Overbool's avatar
Overbool committed
99
		}
Overbool's avatar
Overbool committed
100

Overbool's avatar
Overbool committed
101
		return nil
102
	},
Overbool's avatar
Overbool committed
103
	Encoders: cmds.EncoderMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
104
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *routing.QueryEvent) error {
105
			pfm := pfuncMap{
Raúl Kripalani's avatar
Raúl Kripalani committed
106
				routing.PeerResponse: func(obj *routing.QueryEvent, out io.Writer, verbose bool) error {
107 108 109
					for _, p := range obj.Responses {
						fmt.Fprintf(out, "%s\n", p.ID.Pretty())
					}
110
					return nil
111 112
				},
			}
Overbool's avatar
Overbool committed
113
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
114
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
115
		}),
116
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
117
	Type: routing.QueryEvent{},
118
}
119

Kejie Zhang's avatar
Kejie Zhang committed
120 121 122 123
const (
	numProvidersOptionName = "num-providers"
)

124
var findProvidersDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
125
	Helptext: cmds.HelpText{
126
		Tagline:          "Find peers that can provide a specific value, given a key.",
127
		ShortDescription: "Outputs a list of newline-delimited provider Peer IDs.",
128 129
	},

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

143
		if !n.IsOnline {
Overbool's avatar
Overbool committed
144
			return ErrNotOnline
145 146
		}

Overbool's avatar
Overbool committed
147
		numProviders, _ := req.Options[numProvidersOptionName].(int)
148
		if numProviders < 1 {
Overbool's avatar
Overbool committed
149
			return fmt.Errorf("number of providers must be greater than 0")
150
		}
151

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

154
		if err != nil {
Overbool's avatar
Overbool committed
155
			return err
156 157
		}

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

161
		pchan := n.Routing.FindProvidersAsync(ctx, c, numProviders)
162 163

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

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

Overbool's avatar
Overbool committed
205
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
206
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
207
		}),
208
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
209
	Type: routing.QueryEvent{},
210
}
Jeromy's avatar
Jeromy committed
211

Kejie Zhang's avatar
Kejie Zhang committed
212 213 214 215
const (
	recursiveOptionName = "recursive"
)

216
var provideRefDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
217
	Helptext: cmds.HelpText{
218 219 220
		Tagline: "Announce to the network that you are providing given values.",
	},

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

234
		if !nd.IsOnline {
Overbool's avatar
Overbool committed
235
			return ErrNotOnline
236 237
		}

Overbool's avatar
Overbool committed
238 239
		if len(nd.PeerHost.Network().Conns()) == 0 {
			return errors.New("cannot provide, no connected peers")
240 241
		}

242 243 244 245 246 247 248
		// Needed to parse stdin args.
		// TODO: Lazy Load
		err = req.ParseBodyArgs()
		if err != nil {
			return err
		}

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

251
		var cids []cid.Cid
Overbool's avatar
Overbool committed
252
		for _, arg := range req.Arguments {
Jeromy's avatar
Jeromy committed
253 254
			c, err := cid.Decode(arg)
			if err != nil {
Overbool's avatar
Overbool committed
255
				return err
256 257
			}

Overbool's avatar
Overbool committed
258
			has, err := nd.Blockstore.Has(c)
259
			if err != nil {
Overbool's avatar
Overbool committed
260
				return err
261 262 263
			}

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

Jeromy's avatar
Jeromy committed
267
			cids = append(cids, c)
268 269
		}

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

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

Overbool's avatar
Overbool committed
289 290 291 292 293 294
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
			}
		}

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

Overbool's avatar
Overbool committed
308
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
309
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
310
		}),
311
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
312
	Type: routing.QueryEvent{},
313 314
}

Raúl Kripalani's avatar
Raúl Kripalani committed
315
func provideKeys(ctx context.Context, r routing.Routing, cids []cid.Cid) error {
Jeromy's avatar
Jeromy committed
316
	for _, c := range cids {
317
		err := r.Provide(ctx, c, true)
Jeromy's avatar
Jeromy committed
318
		if err != nil {
Jeromy's avatar
Jeromy committed
319
			return err
Jeromy's avatar
Jeromy committed
320 321
		}
	}
Jeromy's avatar
Jeromy committed
322
	return nil
Jeromy's avatar
Jeromy committed
323 324
}

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

330
		err := dag.Walk(ctx, dag.GetLinksDirect(dserv), c, kset.Visit)
Jeromy's avatar
Jeromy committed
331
		if err != nil {
Jeromy's avatar
Jeromy committed
332
			return err
Jeromy's avatar
Jeromy committed
333 334 335
		}

		for _, k := range kset.Keys() {
336
			if provided.Has(k) {
337 338 339
				continue
			}

340
			err = r.Provide(ctx, k, true)
Jeromy's avatar
Jeromy committed
341
			if err != nil {
Jeromy's avatar
Jeromy committed
342
				return err
Jeromy's avatar
Jeromy committed
343
			}
344
			provided.Add(k)
Jeromy's avatar
Jeromy committed
345 346 347
		}
	}

Jeromy's avatar
Jeromy committed
348
	return nil
Jeromy's avatar
Jeromy committed
349 350
}

Jeromy's avatar
Jeromy committed
351
var findPeerDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
352
	Helptext: cmds.HelpText{
353
		Tagline:          "Find the multiaddresses associated with a Peer ID.",
354
		ShortDescription: "Outputs a list of newline-delimited multiaddresses.",
Jeromy's avatar
Jeromy committed
355 356
	},

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

369
		if !nd.IsOnline {
Overbool's avatar
Overbool committed
370
			return ErrNotOnline
Jeromy's avatar
Jeromy committed
371 372
		}

373
		pid, err := peer.Decode(req.Arguments[0])
Jeromy's avatar
Jeromy committed
374
		if err != nil {
Overbool's avatar
Overbool committed
375
			return err
Jeromy's avatar
Jeromy committed
376 377
		}

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

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

Raúl Kripalani's avatar
Raúl Kripalani committed
394 395 396
			routing.PublishQueryEvent(ctx, &routing.QueryEvent{
				Type:      routing.FinalPeer,
				Responses: []*peer.AddrInfo{&pi},
397 398
			})
		}()
Overbool's avatar
Overbool committed
399

Overbool's avatar
Overbool committed
400 401 402 403 404 405
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
			}
		}

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

Overbool's avatar
Overbool committed
420
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
421
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
422
		}),
Jeromy's avatar
Jeromy committed
423
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
424
	Type: routing.QueryEvent{},
Jeromy's avatar
Jeromy committed
425
}
Jeromy's avatar
Jeromy committed
426 427

var getValueDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
428
	Helptext: cmds.HelpText{
429
		Tagline: "Given a key, query the routing system for its best value.",
Jeromy's avatar
Jeromy committed
430
		ShortDescription: `
431 432
Outputs the best value for the given key.

433 434 435 436
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).
437
Different key types can specify other 'best' rules.
Jeromy's avatar
Jeromy committed
438 439 440
`,
	},

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

453
		if !nd.IsOnline {
Overbool's avatar
Overbool committed
454
			return ErrNotOnline
Jeromy's avatar
Jeromy committed
455 456
		}

Overbool's avatar
Overbool committed
457
		dhtkey, err := escapeDhtKey(req.Arguments[0])
458
		if err != nil {
Overbool's avatar
Overbool committed
459
			return err
460 461
		}

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

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

Overbool's avatar
Overbool committed
483 484 485 486 487 488
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
			}
		}

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

Overbool's avatar
Overbool committed
508
			verbose, _ := req.Options[dhtVerboseOptionName].(bool)
509
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
510
		}),
Jeromy's avatar
Jeromy committed
511
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
512
	Type: routing.QueryEvent{},
Jeromy's avatar
Jeromy committed
513 514 515
}

var putValueDhtCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
516
	Helptext: cmds.HelpText{
517
		Tagline: "Write a key/value pair to the routing system.",
Jeromy's avatar
Jeromy committed
518
		ShortDescription: `
519 520
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.
521

522 523 524
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).
525

526 527
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
528
go-ipfs routing internals, you likely want to be using 'ipfs name publish' instead
529
of this.
530

531 532 533
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
534 535 536
`,
	},

Steven Allen's avatar
Steven Allen committed
537 538
	Arguments: []cmds.Argument{
		cmds.StringArg("key", true, false, "The key to store the value at."),
539
		cmds.FileArg("value", true, false, "The value to store.").EnableStdin(),
Jeromy's avatar
Jeromy committed
540
	},
Steven Allen's avatar
Steven Allen committed
541 542
	Options: []cmds.Option{
		cmds.BoolOption(dhtVerboseOptionName, "v", "Print extra information."),
Jeromy's avatar
Jeromy committed
543
	},
Overbool's avatar
Overbool committed
544 545
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		nd, err := cmdenv.GetNode(env)
Jeromy's avatar
Jeromy committed
546
		if err != nil {
Overbool's avatar
Overbool committed
547
			return err
Jeromy's avatar
Jeromy committed
548 549
		}

550
		if !nd.IsOnline {
Overbool's avatar
Overbool committed
551
			return ErrNotOnline
Jeromy's avatar
Jeromy committed
552 553
		}

Overbool's avatar
Overbool committed
554
		key, err := escapeDhtKey(req.Arguments[0])
555
		if err != nil {
Overbool's avatar
Overbool committed
556
			return err
557 558
		}

559 560 561 562 563 564 565 566 567 568
		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
569

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

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

Overbool's avatar
Overbool committed
585 586 587 588 589 590
		for e := range events {
			if err := res.Emit(e); err != nil {
				return err
			}
		}

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

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

610
			return printEvent(out, w, verbose, pfm)
Overbool's avatar
Overbool committed
611
		}),
Jeromy's avatar
Jeromy committed
612
	},
Raúl Kripalani's avatar
Raúl Kripalani committed
613
	Type: routing.QueryEvent{},
Jeromy's avatar
Jeromy committed
614
}
615

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

Raúl Kripalani's avatar
Raúl Kripalani committed
619
func printEvent(obj *routing.QueryEvent, out io.Writer, verbose bool, override pfuncMap) error {
620 621 622 623 624 625
	if verbose {
		fmt.Fprintf(out, "%s: ", time.Now().Format("15:04:05.000"))
	}

	if override != nil {
		if pf, ok := override[obj.Type]; ok {
626
			return pf(obj, out, verbose)
627 628 629 630
		}
	}

	switch obj.Type {
Raúl Kripalani's avatar
Raúl Kripalani committed
631
	case routing.SendingQuery:
632 633 634
		if verbose {
			fmt.Fprintf(out, "* querying %s\n", obj.ID)
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
635
	case routing.Value:
636 637 638 639 640
		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
641
	case routing.PeerResponse:
642 643 644 645 646 647
		if verbose {
			fmt.Fprintf(out, "* %s says use ", obj.ID)
			for _, p := range obj.Responses {
				fmt.Fprintf(out, "%s ", p.ID)
			}
			fmt.Fprintln(out)
648
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
649
	case routing.QueryError:
650 651 652
		if verbose {
			fmt.Fprintf(out, "error: %s\n", obj.Extra)
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
653
	case routing.DialingPeer:
654 655 656
		if verbose {
			fmt.Fprintf(out, "dialing peer: %s\n", obj.ID)
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
657
	case routing.AddingPeer:
658 659 660
		if verbose {
			fmt.Fprintf(out, "adding peer to query: %s\n", obj.ID)
		}
Raúl Kripalani's avatar
Raúl Kripalani committed
661
	case routing.FinalPeer:
662
	default:
663 664 665
		if verbose {
			fmt.Fprintf(out, "unrecognized event type: %d\n", obj.Type)
		}
666
	}
667
	return nil
668 669
}

670
func escapeDhtKey(s string) (string, error) {
rht's avatar
rht committed
671
	parts := path.SplitList(s)
672 673
	switch len(parts) {
	case 1:
Steven Allen's avatar
Steven Allen committed
674 675 676 677 678
		k, err := b58.Decode(s)
		if err != nil {
			return "", err
		}
		return string(k), nil
679
	case 3:
Steven Allen's avatar
Steven Allen committed
680 681 682 683
		k, err := b58.Decode(parts[2])
		if err != nil {
			return "", err
		}
684
		return path.Join(append(parts[:2], string(k))), nil
685 686 687 688
	default:
		return "", errors.New("invalid key")
	}
}