dht.go 16.7 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

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

		closestPeers, err := dht.GetClosestPeers(ctx, string(id))
Jeromy's avatar
Jeromy committed
85
		if err != nil {
86
			cancel()
Overbool's avatar
Overbool committed
87
			return err
Jeromy's avatar
Jeromy committed
88
		}
89 90

		go func() {
91
			defer cancel()
92
			for p := range closestPeers {
Raúl Kripalani's avatar
Raúl Kripalani committed
93
				routing.PublishQueryEvent(ctx, &routing.QueryEvent{
94
					ID:   p,
Raúl Kripalani's avatar
Raúl Kripalani committed
95
					Type: routing.FinalPeer,
96
				})
97 98 99
			}
		}()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

438 439 440 441
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).
442
Different key types can specify other 'best' rules.
Jeromy's avatar
Jeromy committed
443 444 445
`,
	},

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

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

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

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

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

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

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

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

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

527 528 529
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).
530

531 532
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
533
go-ipfs routing internals, you likely want to be using 'ipfs name publish' instead
534
of this.
535

536 537 538
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
539 540 541
`,
	},

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

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

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

564 565 566 567 568 569 570 571 572 573
		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
574

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

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

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

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

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

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

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

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

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

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

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