github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/service/gregor.go (about) 1 package service 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "net/url" 8 "sync" 9 "time" 10 11 "golang.org/x/net/context" 12 13 "github.com/keybase/backoff" 14 "github.com/keybase/client/go/badges" 15 "github.com/keybase/client/go/chat" 16 "github.com/keybase/client/go/chat/globals" 17 chatstorage "github.com/keybase/client/go/chat/storage" 18 "github.com/keybase/client/go/chat/utils" 19 "github.com/keybase/client/go/engine" 20 "github.com/keybase/client/go/gregor" 21 grclient "github.com/keybase/client/go/gregor/client" 22 "github.com/keybase/client/go/gregor/storage" 23 grutils "github.com/keybase/client/go/gregor/utils" 24 "github.com/keybase/client/go/libkb" 25 "github.com/keybase/client/go/logger" 26 "github.com/keybase/client/go/protocol/chat1" 27 "github.com/keybase/client/go/protocol/gregor1" 28 "github.com/keybase/client/go/protocol/keybase1" 29 "github.com/keybase/clockwork" 30 "github.com/keybase/go-framed-msgpack-rpc/rpc" 31 jsonw "github.com/keybase/go-jsonw" 32 ) 33 34 const GregorRequestTimeout time.Duration = 30 * time.Second 35 const GregorConnectionRetryInterval time.Duration = 2 * time.Second 36 const GregorGetClientTimeout time.Duration = 4 * time.Second 37 const slowConnSleepTime = 1 * time.Second 38 39 type IdentifyUIHandler struct { 40 libkb.Contextified 41 connID libkb.ConnectionID 42 alwaysAlive bool 43 } 44 45 var _ libkb.GregorInBandMessageHandler = (*IdentifyUIHandler)(nil) 46 47 func NewIdentifyUIHandler(g *libkb.GlobalContext, connID libkb.ConnectionID) IdentifyUIHandler { 48 return IdentifyUIHandler{ 49 Contextified: libkb.NewContextified(g), 50 connID: connID, 51 alwaysAlive: false, 52 } 53 } 54 55 func (h IdentifyUIHandler) IsAlive() bool { 56 return (h.alwaysAlive || h.G().ConnectionManager.LookupConnection(h.connID) != nil) 57 } 58 59 func (h IdentifyUIHandler) Name() string { 60 return "IdentifyUIHandler" 61 } 62 63 func (h *IdentifyUIHandler) toggleAlwaysAlive(alive bool) { 64 h.alwaysAlive = alive 65 } 66 67 type oobmSystemSubscriptions map[string]bool 68 69 func newOOBMSystemSubscriptions(oobmSystems []string) oobmSystemSubscriptions { 70 if oobmSystems == nil { 71 return nil 72 } 73 ret := make(oobmSystemSubscriptions) 74 for _, system := range oobmSystems { 75 ret[system] = true 76 } 77 return ret 78 } 79 80 type gregorFirehoseHandler struct { 81 libkb.Contextified 82 connID libkb.ConnectionID 83 cli keybase1.GregorUIClient 84 oobmFilter oobmSystemSubscriptions 85 } 86 87 func newGregorFirehoseHandler(g *libkb.GlobalContext, connID libkb.ConnectionID, xp rpc.Transporter, oobmSystems []string) *gregorFirehoseHandler { 88 return &gregorFirehoseHandler{ 89 Contextified: libkb.NewContextified(g), 90 connID: connID, 91 cli: keybase1.GregorUIClient{Cli: rpc.NewClient(xp, libkb.NewContextifiedErrorUnwrapper(g), nil)}, 92 oobmFilter: newOOBMSystemSubscriptions(oobmSystems), 93 } 94 } 95 96 func (h *gregorFirehoseHandler) IsAlive() bool { 97 return h.G().ConnectionManager.LookupConnection(h.connID) != nil 98 } 99 100 func (h *gregorFirehoseHandler) PushState(s gregor1.State, r keybase1.PushReason) { 101 defer h.G().Trace("gregorFirehoseHandler#PushState", nil)() 102 err := h.cli.PushState(context.Background(), keybase1.PushStateArg{State: s, Reason: r}) 103 if err != nil { 104 h.G().Log.Error(fmt.Sprintf("Error in firehose push state: %s", err)) 105 } 106 } 107 108 func (h *gregorFirehoseHandler) filterOOBMs(v []gregor1.OutOfBandMessage) []gregor1.OutOfBandMessage { 109 // Filter OOBMs down to wanted systems if we have a filter installed 110 if h.oobmFilter == nil { 111 return v 112 } 113 114 var tmp []gregor1.OutOfBandMessage 115 for _, m := range v { 116 if h.oobmFilter[m.System().String()] { 117 tmp = append(tmp, m) 118 } 119 } 120 return tmp 121 } 122 123 func (h *gregorFirehoseHandler) PushOutOfBandMessages(v []gregor1.OutOfBandMessage) { 124 defer h.G().Trace("gregorFirehoseHandler#PushOutOfBandMessages", nil)() 125 nOrig := len(v) 126 127 // Filter OOBMs down to wanted systems if we have a filter installed 128 v = h.filterOOBMs(v) 129 h.G().Log.Debug("gregorFirehoseHandler#PushOutOfBandMessages: %d message(s) (%d before filter)", len(v), nOrig) 130 131 if len(v) == 0 { 132 return 133 } 134 135 err := h.cli.PushOutOfBandMessages(context.Background(), v) 136 if err != nil { 137 h.G().Log.Error(fmt.Sprintf("Error in firehose push out-of-band messages: %s", err)) 138 } 139 } 140 141 type testingReplayRes struct { 142 replayed []gregor.InBandMessage 143 err error 144 } 145 146 type testingEvents struct { 147 broadcastSentCh chan error 148 replayThreadCh chan testingReplayRes 149 } 150 151 func newTestingEvents() *testingEvents { 152 return &testingEvents{ 153 broadcastSentCh: make(chan error), 154 replayThreadCh: make(chan testingReplayRes, 10), 155 } 156 } 157 158 type connectionAuthError struct { 159 msg string 160 shouldRetry bool 161 } 162 163 func newConnectionAuthError(msg string, shouldRetry bool) connectionAuthError { 164 return connectionAuthError{ 165 msg: msg, 166 shouldRetry: shouldRetry, 167 } 168 } 169 170 func (c connectionAuthError) ShouldRetry() bool { 171 return c.shouldRetry 172 } 173 174 func (c connectionAuthError) Error() string { 175 return fmt.Sprintf("connection auth error: msg: %s shouldRetry: %v", c.msg, c.shouldRetry) 176 } 177 178 type replayThreadArg struct { 179 cli gregor1.IncomingInterface 180 t time.Time 181 ctx context.Context 182 } 183 184 type gregorHandler struct { 185 globals.Contextified 186 187 // This lock is to protect ibmHandlers and gregorCli and firehoseHandlers. Only public methods 188 // should grab it. 189 sync.Mutex 190 ibmHandlers []libkb.GregorInBandMessageHandler 191 192 // gregorCliMu just protecs the gregorCli pointer, since it can be swapped out 193 // in one goroutine and accessed in another. 194 gregorCliMu sync.Mutex 195 gregorCli *grclient.Client 196 197 firehoseHandlers []libkb.GregorFirehoseHandler 198 badger *badges.Badger 199 reachability *reachability 200 chatLog utils.DebugLabeler 201 202 // This mutex protects the con object 203 connMutex sync.Mutex 204 conn *rpc.Connection 205 uri *rpc.FMPURI 206 207 // connectHappened will be closed after gregor connection established 208 connectHappened chan struct{} 209 210 cli rpc.GenericClient 211 pingCli rpc.GenericClient 212 sessionID gregor1.SessionID 213 firstConnectMu sync.Mutex 214 firstConnect bool 215 forceSessionCheck bool 216 217 // Function for determining if a new BroadcastMessage should trigger 218 // a pushState call to firehose handlers 219 pushStateFilter func(m gregor.Message) bool 220 221 shutdownCh chan struct{} 222 broadcastCh chan gregor1.Message 223 replayCh chan replayThreadArg 224 pushStateCh chan struct{} 225 forcePingCh chan struct{} 226 227 // Testing 228 testingEvents *testingEvents 229 transportForTesting *connTransport 230 } 231 232 var _ libkb.GregorState = (*gregorHandler)(nil) 233 var _ libkb.GregorListener = (*gregorHandler)(nil) 234 235 func newGregorHandler(g *globals.Context) *gregorHandler { 236 gh := &gregorHandler{ 237 Contextified: globals.NewContextified(g), 238 chatLog: utils.NewDebugLabeler(g.ExternalG(), "PushHandler", false), 239 firstConnect: true, 240 pushStateFilter: func(m gregor.Message) bool { return true }, 241 badger: nil, 242 broadcastCh: make(chan gregor1.Message, 10000), 243 forceSessionCheck: false, 244 connectHappened: make(chan struct{}), 245 replayCh: make(chan replayThreadArg, 10), 246 pushStateCh: make(chan struct{}, 100), 247 forcePingCh: make(chan struct{}, 5), 248 } 249 return gh 250 } 251 252 // Init starts all the background services for managing connection to Gregor 253 func (g *gregorHandler) Init() { 254 // Start broadcast handler goroutine 255 go g.broadcastMessageHandler() 256 // Start the app state monitor thread 257 go g.monitorAppState() 258 // Start replay thread 259 go g.syncReplayThread() 260 } 261 262 const ( 263 monitorConnect int = iota 264 monitorDisconnect 265 monitorNoop 266 ) 267 268 func (g *gregorHandler) monitorAppState() { 269 // Wait for state updates and react accordingly 270 state := keybase1.MobileAppState_FOREGROUND 271 suspended := false 272 for { 273 monitorAction := monitorNoop 274 select { 275 case state = <-g.G().MobileAppState.NextUpdate(&state): 276 switch state { 277 case keybase1.MobileAppState_FOREGROUND: 278 g.forcePing(context.Background()) 279 monitorAction = monitorConnect 280 case keybase1.MobileAppState_BACKGROUNDACTIVE: 281 monitorAction = monitorConnect 282 case keybase1.MobileAppState_BACKGROUND, keybase1.MobileAppState_INACTIVE: 283 monitorAction = monitorDisconnect 284 } 285 case suspended = <-g.G().DesktopAppState.NextSuspendUpdate(&suspended): 286 if !suspended { 287 monitorAction = monitorConnect 288 g.chatLog.Debug(context.Background(), "resumed, connecting") 289 } else { 290 g.chatLog.Debug(context.Background(), "suspended, disconnecting") 291 monitorAction = monitorDisconnect 292 } 293 } 294 switch monitorAction { 295 case monitorConnect: 296 // Make sure the URI is set before attempting this (possible it isn't in a race) 297 if g.uri != nil { 298 g.chatLog.Debug(context.Background(), "foregrounded, reconnecting") 299 if err := g.Connect(g.uri); err != nil { 300 g.chatLog.Debug(context.Background(), "error reconnecting: %s", err) 301 } 302 } 303 case monitorDisconnect: 304 g.chatLog.Debug(context.Background(), "backgrounded, shutting down connection") 305 g.Shutdown() 306 } 307 } 308 } 309 310 func (g *gregorHandler) GetURI() *rpc.FMPURI { 311 return g.uri 312 } 313 314 func (g *gregorHandler) GetIncomingClient() gregor1.IncomingInterface { 315 cli := g.getRPCCli() 316 if g.IsShutdown() || cli == nil { 317 return gregor1.IncomingClient{Cli: chat.OfflineClient{}} 318 } 319 return gregor1.IncomingClient{Cli: cli} 320 } 321 322 func (g *gregorHandler) GetClient() chat1.RemoteInterface { 323 cli := g.getRPCCli() 324 if g.IsShutdown() || cli == nil { 325 select { 326 case <-g.connectHappened: 327 cli = g.getRPCCli() 328 if g.IsShutdown() || cli == nil { 329 g.chatLog.Debug(context.Background(), "GetClient: connectHappened, but still shutdown, using OfflineClient for chat1.RemoteClient") 330 return chat1.RemoteClient{Cli: chat.OfflineClient{}} 331 332 } 333 g.chatLog.Debug(context.Background(), "GetClient: successfully waited for connection") 334 return chat1.RemoteClient{Cli: chat.NewRemoteClient(g.G(), cli)} 335 case <-time.After(GregorGetClientTimeout): 336 g.chatLog.Debug(context.Background(), "GetClient: shutdown, using OfflineClient for chat1.RemoteClient (waited %s for connectHappened)", GregorGetClientTimeout) 337 return chat1.RemoteClient{Cli: chat.OfflineClient{}} 338 } 339 } 340 g.chatLog.Debug(context.Background(), "GetClient: not shutdown, making new remote client") 341 return chat1.RemoteClient{Cli: chat.NewRemoteClient(g.G(), cli)} 342 } 343 344 func (g *gregorHandler) isFirstConnect() bool { 345 g.firstConnectMu.Lock() 346 defer g.firstConnectMu.Unlock() 347 return g.firstConnect 348 } 349 350 func (g *gregorHandler) setFirstConnect(val bool) { 351 g.firstConnectMu.Lock() 352 defer g.firstConnectMu.Unlock() 353 g.firstConnect = val 354 } 355 356 func (g *gregorHandler) shutdownGregorClient(ctx context.Context) { 357 g.gregorCliMu.Lock() 358 gcliOld := g.gregorCli 359 g.gregorCli = nil 360 g.gregorCliMu.Unlock() 361 if gcliOld != nil { 362 gcliOld.Stop() 363 } 364 } 365 366 func (g *gregorHandler) resetGregorClient(ctx context.Context, uid gregor1.UID, deviceID gregor1.DeviceID) (gcli *grclient.Client, err error) { 367 defer g.G().Trace("gregorHandler#newGregorClient", &err)() 368 // Create client object if we are logged in 369 if uid != nil && deviceID != nil { 370 gcli = grclient.NewClient(uid, deviceID, func() gregor.StateMachine { 371 return storage.NewMemEngine(gregor1.ObjFactory{}, clockwork.NewRealClock(), g.G().Log) 372 }, storage.NewLocalDB(g.G().ExternalG()), g.GetIncomingClient, g.G().Log, clockwork.NewRealClock()) 373 374 // Bring up local state 375 g.Debug(ctx, "restoring state from leveldb") 376 if err = gcli.Restore(ctx); err != nil { 377 // If this fails, we'll keep trying since the server can bail us out 378 g.Debug(ctx, "restore local state failed: %s", err) 379 } 380 } 381 g.gregorCliMu.Lock() 382 gcliOld := g.gregorCli 383 g.gregorCli = gcli 384 g.gregorCliMu.Unlock() 385 if gcliOld != nil { 386 gcliOld.Stop() 387 } 388 return gcli, nil 389 } 390 391 func (g *gregorHandler) getGregorCli() (*grclient.Client, error) { 392 393 if g == nil { 394 return nil, errors.New("gregorHandler client unset") 395 } 396 397 g.gregorCliMu.Lock() 398 ret := g.gregorCli 399 g.gregorCliMu.Unlock() 400 401 if ret == nil { 402 return nil, errors.New("client unset") 403 } 404 return ret, nil 405 } 406 407 func (g *gregorHandler) getRPCCli() rpc.GenericClient { 408 g.connMutex.Lock() 409 defer g.connMutex.Unlock() 410 return g.cli 411 } 412 413 func (g *gregorHandler) Debug(ctx context.Context, s string, args ...interface{}) { 414 g.G().Log.CloneWithAddedDepth(1).CDebugf(ctx, "PushHandler: "+s, args...) 415 } 416 417 func (g *gregorHandler) Warning(ctx context.Context, s string, args ...interface{}) { 418 g.G().Log.CloneWithAddedDepth(1).CWarningf(ctx, "PushHandler: "+s, args...) 419 } 420 421 func (g *gregorHandler) Errorf(ctx context.Context, s string, args ...interface{}) { 422 g.G().Log.CloneWithAddedDepth(1).CErrorf(ctx, "PushHandler: "+s, args...) 423 } 424 425 func (g *gregorHandler) SetPushStateFilter(f func(m gregor.Message) bool) { 426 g.pushStateFilter = f 427 } 428 429 func (g *gregorHandler) setReachability(r *reachability) { 430 g.reachability = r 431 } 432 433 func (g *gregorHandler) Connect(uri *rpc.FMPURI) (err error) { 434 435 defer g.G().Trace("gregorHandler#Connect", &err)() 436 437 g.connMutex.Lock() 438 defer g.connMutex.Unlock() 439 if g.conn != nil { 440 g.chatLog.Debug(context.Background(), "skipping connect, conn is not nil") 441 return nil 442 } 443 defer func() { 444 close(g.connectHappened) 445 g.connectHappened = make(chan struct{}) 446 }() 447 448 // In case we need to interrupt auth'ing or the ping loop, 449 // set up this channel. 450 g.shutdownCh = make(chan struct{}) 451 g.uri = uri 452 go g.pushStateNewDataDebouncer(g.shutdownCh) 453 if uri.UseTLS() { 454 err = g.connectTLS() 455 } else { 456 err = g.connectNoTLS() 457 } 458 459 return err 460 } 461 462 func (g *gregorHandler) HandlerName() string { 463 return "gregor" 464 } 465 466 // PushHandler adds a new ibm handler to our list. This is usually triggered 467 // when an external entity (like Electron) connects to the service, and we can 468 // safely send Gregor information to it 469 func (g *gregorHandler) PushHandler(handler libkb.GregorInBandMessageHandler) { 470 defer g.chatLog.Trace(context.Background(), nil, "PushHandler")() 471 472 g.G().Log.Debug("pushing inband handler %s to position %d", handler.Name(), len(g.ibmHandlers)) 473 474 g.Lock() 475 g.ibmHandlers = append(g.ibmHandlers, handler) 476 g.Unlock() 477 478 // Only try replaying if we are logged in, it's possible that a handler can 479 // attach before that is true (like if we start the service logged out and 480 // Electron connects) 481 cli := g.getRPCCli() 482 if g.IsConnected() && cli != nil { 483 if _, err := g.replayInBandMessages(context.TODO(), gregor1.IncomingClient{Cli: cli}, 484 time.Time{}, handler); err != nil { 485 g.Errorf(context.Background(), "replayInBandMessages on PushHandler failed: %s", err) 486 } 487 488 if g.badger != nil { 489 s, err := g.getState(context.Background()) 490 if err != nil { 491 g.Warning(context.Background(), "Cannot get state in PushHandler: %s", err) 492 return 493 } 494 g.badger.PushState(context.Background(), s) 495 } 496 } 497 } 498 499 // PushFirehoseHandler pushes a new firehose handler onto the list of currently 500 // active firehose handles. We can have several of these active at once. All 501 // get the "firehose" of gregor events. They're removed lazily as their underlying 502 // connections die. 503 func (g *gregorHandler) PushFirehoseHandler(handler libkb.GregorFirehoseHandler) { 504 defer g.chatLog.Trace(context.Background(), nil, "PushFirehoseHandler")() 505 g.Lock() 506 g.firehoseHandlers = append(g.firehoseHandlers, handler) 507 g.Unlock() 508 509 s, err := g.getState(context.Background()) 510 if err != nil { 511 g.Warning(context.Background(), "Cannot push state in firehose handler: %s", err) 512 return 513 } 514 g.Debug(context.Background(), "PushFirehoseHandler: pushing state with %d items", len(s.Items_)) 515 handler.PushState(s, keybase1.PushReason_RECONNECTED) 516 } 517 518 // iterateOverFirehoseHandlers applies the function f to all live firehose handlers 519 // and then resets the list to only include the live ones. 520 func (g *gregorHandler) iterateOverFirehoseHandlers(f func(h libkb.GregorFirehoseHandler)) { 521 var freshHandlers []libkb.GregorFirehoseHandler 522 for _, h := range g.firehoseHandlers { 523 if h.IsAlive() { 524 f(h) 525 freshHandlers = append(freshHandlers, h) 526 } 527 } 528 g.firehoseHandlers = freshHandlers 529 } 530 531 func (g *gregorHandler) pushStateNewDataDebouncer(shutdownCh chan struct{}) { 532 shouldSend := false 533 var lastTime time.Time 534 dur := time.Second 535 trigger := func() { 536 if shouldSend { 537 go g.pushStateOnce(keybase1.PushReason_NEW_DATA) 538 shouldSend = false 539 lastTime = time.Now() 540 } 541 } 542 for { 543 select { 544 case <-g.pushStateCh: 545 shouldSend = true 546 if time.Since(lastTime) > dur { 547 trigger() 548 } 549 case <-time.After(dur): 550 trigger() 551 case <-shutdownCh: 552 return 553 } 554 } 555 } 556 557 func (g *gregorHandler) pushStateOnce(r keybase1.PushReason) { 558 s, err := g.getState(context.Background()) 559 if err != nil { 560 g.Warning(context.Background(), "Cannot push state in firehose handler: %s", err) 561 return 562 } 563 g.iterateOverFirehoseHandlers(func(h libkb.GregorFirehoseHandler) { 564 g.Debug(context.Background(), "pushState: pushing state with %d items", len(s.Items_)) 565 h.PushState(s, r) 566 }) 567 // Only send this state update on reception of new data, not a reconnect since we will 568 // be sending that on a different code path altogether (see OnConnect). 569 if g.badger != nil && r != keybase1.PushReason_RECONNECTED { 570 g.badger.PushState(context.Background(), s) 571 } 572 } 573 574 func (g *gregorHandler) pushState(r keybase1.PushReason) { 575 switch r { 576 case keybase1.PushReason_RECONNECTED, keybase1.PushReason_NONE: 577 g.pushStateOnce(r) 578 default: 579 g.pushStateCh <- struct{}{} 580 } 581 } 582 583 func (g *gregorHandler) pushOutOfBandMessages(m []gregor1.OutOfBandMessage) { 584 g.iterateOverFirehoseHandlers(func(h libkb.GregorFirehoseHandler) { h.PushOutOfBandMessages(m) }) 585 } 586 587 // replayInBandMessages will replay all the messages in the current state from 588 // the given time. If a handler is specified, it will only replay using it, 589 // otherwise it will try all of them. gregorHandler needs to be locked when calling 590 // this function. 591 func (g *gregorHandler) replayInBandMessages(ctx context.Context, cli gregor1.IncomingInterface, 592 t time.Time, handler libkb.GregorInBandMessageHandler) ([]gregor.InBandMessage, error) { 593 594 var msgs []gregor.InBandMessage 595 var err error 596 597 gcli, err := g.getGregorCli() 598 if err != nil { 599 return nil, err 600 } 601 602 if t.IsZero() { 603 g.Debug(ctx, "replayInBandMessages: fresh replay: using state items") 604 state, err := gcli.StateMachineState(ctx, nil, true) 605 if err != nil { 606 g.Debug(ctx, "replayInBandMessages: unable to fetch state for replay: %s", err) 607 return nil, err 608 } 609 if msgs, err = gcli.InBandMessagesFromState(state); err != nil { 610 g.Debug(ctx, "replayInBandMessages: unable to fetch messages from state for replay: %s", err) 611 return nil, err 612 } 613 } else { 614 g.Debug(ctx, "replayInBandMessages: incremental replay: using ibms since") 615 if msgs, err = gcli.StateMachineInBandMessagesSince(ctx, t, true); err != nil { 616 g.Debug(ctx, "replayInBandMessages: unable to fetch messages for replay: %s", err) 617 return nil, err 618 } 619 } 620 621 g.Debug(ctx, "replayInBandMessages: replaying %d messages", len(msgs)) 622 for _, msg := range msgs { 623 g.Debug(ctx, "replayInBandMessages: replaying: %s", msg.Metadata().MsgID()) 624 // If we have a handler, just run it on that, otherwise run it against 625 // all of the handlers we know about 626 if handler == nil { 627 err = g.handleInBandMessage(ctx, cli, msg) 628 } else { 629 _, err = g.handleInBandMessageWithHandler(ctx, cli, msg, handler) 630 } 631 632 // If an error happens when replaying, don't kill everything else that 633 // follows, just make a warning. 634 if err != nil { 635 g.Debug(ctx, "replayInBandMessages: failure in message replay: %s", err.Error()) 636 err = nil 637 } 638 } 639 640 return msgs, nil 641 } 642 643 func (g *gregorHandler) IsShutdown() bool { 644 g.connMutex.Lock() 645 defer g.connMutex.Unlock() 646 return g.conn == nil 647 } 648 649 func (g *gregorHandler) IsConnected() bool { 650 g.connMutex.Lock() 651 defer g.connMutex.Unlock() 652 return g.conn != nil && g.conn.IsConnected() 653 } 654 655 func (g *gregorHandler) syncReplayThread() { 656 for rarg := range g.replayCh { 657 var trr testingReplayRes 658 now := time.Now() 659 g.Debug(rarg.ctx, "serverSync: starting replay thread") 660 replayedMsgs, err := g.replayInBandMessages(rarg.ctx, rarg.cli, rarg.t, nil) 661 if err != nil { 662 g.Debug(rarg.ctx, "serverSync: replayThread: replay messages failed: %s", err) 663 trr.err = err 664 } else { 665 g.Debug(rarg.ctx, "serverSync: replayThread: replayed %d messages", len(replayedMsgs)) 666 trr.replayed = replayedMsgs 667 } 668 if g.testingEvents != nil { 669 g.testingEvents.replayThreadCh <- trr 670 } 671 g.Debug(rarg.ctx, "serverSync: syncReplayThread complete: %v", time.Since(now)) 672 } 673 } 674 675 // serverSync is called from 676 // gregord. This can happen either on initial startup, or after a reconnect. Needs 677 // to be called with gregorHandler locked. 678 func (g *gregorHandler) serverSync(ctx context.Context, 679 cli gregor1.IncomingInterface, gcli *grclient.Client, syncRes *chat1.SyncAllNotificationRes) (res []gregor.InBandMessage, err error) { 680 defer g.chatLog.Trace(ctx, &err, "serverSync")() 681 682 // Get time of the last message we synced (unless this is our first time syncing) 683 var t time.Time 684 if !g.isFirstConnect() { 685 pt := gcli.StateMachineLatestCTime(ctx) 686 if pt != nil { 687 t = *pt 688 } 689 g.Debug(ctx, "serverSync: starting replay from: %s", t) 690 } else { 691 g.Debug(ctx, "serverSync: performing a fresh replay") 692 } 693 694 // Sync down everything from the server 695 consumedMsgs, err := gcli.Sync(ctx, cli, syncRes) 696 if err != nil { 697 g.Debug(ctx, "serverSync: error syncing from the server, reason: %s", err) 698 return nil, err 699 } 700 g.Debug(ctx, "serverSync: consumed %d messages", len(consumedMsgs)) 701 702 // Schedule replay of in-band messages 703 g.replayCh <- replayThreadArg{ 704 cli: cli, 705 t: t, 706 ctx: globals.BackgroundChatCtx(ctx, g.G()), 707 } 708 709 g.pushState(keybase1.PushReason_RECONNECTED) 710 return consumedMsgs, nil 711 } 712 713 func (g *gregorHandler) makeReconnectOobm() gregor1.Message { 714 return gregor1.Message{ 715 Oobm_: &gregor1.OutOfBandMessage{ 716 System_: "internal.reconnect", 717 }, 718 } 719 } 720 721 func (g *gregorHandler) authParams(ctx context.Context) (uid gregor1.UID, deviceID gregor1.DeviceID, 722 token gregor1.SessionToken, nist *libkb.NIST, err error) { 723 var res loggedInRes 724 var stoken string 725 var kuid keybase1.UID 726 var kdid keybase1.DeviceID 727 if kuid, kdid, stoken, nist, res = g.loggedIn(ctx); res != loggedInYes { 728 return uid, deviceID, token, nil, 729 newConnectionAuthError("failed to check logged in status", res == loggedInMaybe) 730 } 731 deviceID = make([]byte, libkb.DeviceIDLen) 732 if err := kdid.ToBytes(deviceID); err != nil { 733 return uid, deviceID, token, nil, err 734 } 735 g.chatLog.Debug(ctx, "generated NIST for UID %s", kuid) 736 return kuid.ToBytes(), deviceID, gregor1.SessionToken(stoken), nist, nil 737 } 738 739 func (g *gregorHandler) inboxParams(ctx context.Context, uid gregor1.UID) chat1.InboxVers { 740 // Grab current on disk version 741 ibox := chatstorage.NewInbox(g.G()) 742 vers, err := ibox.Version(ctx, uid) 743 if err != nil { 744 g.chatLog.Debug(ctx, "inboxParams: failed to get current inbox version (using 0): %s", 745 err.Error()) 746 vers = chat1.InboxVers(0) 747 } 748 return vers 749 } 750 751 func (g *gregorHandler) notificationParams(ctx context.Context, gcli *grclient.Client) (t gregor1.Time) { 752 pt := gcli.StateMachineLatestCTime(ctx) 753 if pt != nil { 754 t = gregor1.ToTime(*pt) 755 } 756 g.chatLog.Debug(ctx, "notificationParams: latest ctime: %v", t.Time()) 757 return t 758 } 759 760 // OnConnect is called by the rpc library to indicate we have connected to 761 // gregord 762 func (g *gregorHandler) OnConnect(ctx context.Context, conn *rpc.Connection, 763 cli rpc.GenericClient, srv *rpc.Server) (err error) { 764 765 ctx = libkb.WithLogTag(ctx, "GRGRONCONN") 766 767 defer g.chatLog.Trace(ctx, &err, "OnConnect")() 768 769 // If we get a random OnConnect on some other connection that is not g.conn, then 770 // just reject it. 771 g.connMutex.Lock() 772 if conn != g.conn { 773 g.connMutex.Unlock() 774 g.chatLog.Debug(ctx, "aborting on dup connection") 775 return chat.ErrDuplicateConnection 776 } 777 g.connMutex.Unlock() 778 779 g.chatLog.Debug(ctx, "connected") 780 timeoutCli := WrapGenericClientWithTimeout(cli, GregorRequestTimeout, chat.ErrChatServerTimeout) 781 chatCli := chat1.RemoteClient{Cli: chat.NewRemoteClient(g.G(), cli)} 782 if err := srv.Register(gregor1.OutgoingProtocol(g)); err != nil { 783 return fmt.Errorf("error registering protocol: %s", err) 784 } 785 786 uid, deviceID, token, nist, err := g.authParams(ctx) 787 if err != nil { 788 return err 789 } 790 gcli, err := g.resetGregorClient(ctx, uid, deviceID) 791 if err != nil { 792 return fmt.Errorf("failed to get gregor client: %s", err) 793 } 794 iboxVers := g.inboxParams(ctx, uid) 795 latestCtime := g.notificationParams(ctx, gcli) 796 797 // Run SyncAll to both authenticate, and grab all the data we will need to run the 798 // various resync procedures for chat and notifications 799 var identBreaks []keybase1.TLFIdentifyFailure 800 ctx = globals.ChatCtx(ctx, g.G(), keybase1.TLFIdentifyBehavior_CHAT_GUI, &identBreaks, 801 chat.NewCachingIdentifyNotifier(g.G())) 802 g.chatLog.Debug(ctx, "OnConnect begin") 803 syncAllRes, err := chatCli.SyncAll(ctx, chat1.SyncAllArg{ 804 Uid: uid, 805 DeviceID: deviceID, 806 Session: token, 807 InboxVers: iboxVers, 808 Ctime: latestCtime, 809 Fresh: g.isFirstConnect(), 810 ProtVers: chat1.SyncAllProtVers_V1, 811 HostName: g.GetURI().Host, 812 SummarizeMaxMsgs: true, 813 ParticipantsMode: chat1.InboxParticipantsMode_SKIP_TEAMS, 814 }) 815 if err != nil { 816 // This will cause us to try and refresh session on the next attempt 817 if _, ok := err.(libkb.BadSessionError); ok { 818 g.chatLog.Debug(ctx, "bad session from SyncAll(): forcing session check on next attempt") 819 g.forceSessionCheck = true 820 nist.MarkFailure() 821 } 822 return fmt.Errorf("error running SyncAll: %s", err) 823 } 824 825 // Use the client parameter instead of conn.GetClient(), since we can get stuck 826 // in a recursive loop if we keep retrying on reconnect. 827 if err := g.auth(ctx, timeoutCli, &syncAllRes.Auth); err != nil { 828 return fmt.Errorf("error authenticating: %s", err) 829 } 830 831 // Update badging for chat. 832 // This happens before Syncer.Connected for a reason. 833 // If the new inbox version (e.g. 8) were committed to disk and then the 834 // app lost connection and bailed out of OnConnect before applying the 835 // badging update (7->8) then on reconnect an incomplete chat badge update (8->9) 836 // could be received. 837 // See: https://github.com/keybase/client/pull/12651 838 if g.badger != nil { 839 g.badger.PushChatFullUpdate(ctx, syncAllRes.Badge) 840 } 841 842 // Sync chat data using a Syncer object 843 // This commits the new inbox version to persistent storage. 844 if err := g.G().Syncer.Connected(ctx, chatCli, uid, &syncAllRes.Chat); err != nil { 845 return fmt.Errorf("error running chat sync: %s", err) 846 } 847 848 // Sync down events since we have been dead 849 if _, err := g.serverSync(ctx, gregor1.IncomingClient{Cli: timeoutCli}, gcli, 850 &syncAllRes.Notification); err != nil { 851 g.chatLog.Debug(ctx, "serverSync: failure: %s", err) 852 return fmt.Errorf("error running state sync: %s", err) 853 } 854 855 // Update badging from gregor. 856 if g.badger != nil { 857 state, err := gcli.StateMachineState(ctx, nil, false) 858 if err != nil { 859 g.chatLog.Debug(ctx, "unable to get gregor state for badging: %v", err) 860 g.badger.PushState(ctx, gregor1.State{}) 861 } else { 862 g.badger.PushState(ctx, state) 863 } 864 } 865 866 // Call out to reachability module if we have one 867 if g.reachability != nil { 868 g.chatLog.Debug(ctx, "setting reachability") 869 g.reachability.setReachability(keybase1.Reachability{ 870 Reachable: keybase1.Reachable_YES, 871 }) 872 } 873 874 // Broadcast reconnect oobm. Spawn this off into a goroutine so that we don't delay 875 // reconnection any longer than we have to. 876 g.chatLog.Debug(ctx, "broadcasting reconnect oobm") 877 go func(m gregor1.Message) { 878 ctx := context.Background() 879 err := g.BroadcastMessage(ctx, m) 880 if err != nil { 881 g.chatLog.Debug(ctx, "Gregor broadcast error: %+v", err) 882 } 883 }(g.makeReconnectOobm()) 884 885 // No longer first connect if we are now connected 886 g.chatLog.Debug(ctx, "setting first connect to false") 887 g.setFirstConnect(false) 888 // On successful login we can reset this guy to not force a check 889 g.forceSessionCheck = false 890 g.chatLog.Debug(ctx, "OnConnect complete") 891 892 return nil 893 } 894 895 func (g *gregorHandler) OnConnectError(err error, reconnectThrottleDuration time.Duration) { 896 defer g.chatLog.Trace(context.Background(), nil, "OnConnectError")() 897 g.chatLog.Debug(context.Background(), "OnConnectError: err: %s, reconnect throttle duration: %s", err, 898 reconnectThrottleDuration) 899 900 // Check reachability here to see the nature of our offline status 901 go func() { 902 if g.reachability != nil && !g.isReachable() { 903 g.reachability.setReachability(keybase1.Reachability{ 904 Reachable: keybase1.Reachable_NO, 905 }) 906 } 907 }() 908 } 909 910 func (g *gregorHandler) OnDisconnected(ctx context.Context, status rpc.DisconnectStatus) { 911 g.chatLog.Debug(context.Background(), "disconnected: %v", status) 912 913 // Alert chat syncer that we are now disconnected 914 g.G().Syncer.Disconnected(ctx) 915 916 // Call out to reachability module if we have one (and we are currently connected) 917 go func() { 918 if g.reachability != nil && status != rpc.StartingFirstConnection && !g.isReachable() { 919 g.reachability.setReachability(keybase1.Reachability{ 920 Reachable: keybase1.Reachable_NO, 921 }) 922 } 923 }() 924 } 925 926 func (g *gregorHandler) OnDoCommandError(err error, nextTime time.Duration) { 927 g.chatLog.Debug(context.Background(), "do command error: %s, nextTime: %s", err, nextTime) 928 } 929 930 func (g *gregorHandler) ShouldRetry(name string, err error) bool { 931 g.chatLog.Debug(context.Background(), "should retry: name %s, err %v (returning false)", name, err) 932 return false 933 } 934 935 func (g *gregorHandler) ShouldRetryOnConnect(err error) bool { 936 if err == nil { 937 return false 938 } 939 940 ctx := context.Background() 941 g.chatLog.Debug(ctx, "should retry on connect, err %v", err) 942 if err == chat.ErrDuplicateConnection { 943 g.chatLog.Debug(ctx, "duplicate connection error, not retrying") 944 return false 945 } 946 if _, ok := err.(libkb.BadSessionError); ok { 947 g.chatLog.Debug(ctx, "bad session error, not retrying") 948 return false 949 } 950 if cerr, ok := err.(connectionAuthError); ok && !cerr.ShouldRetry() { 951 g.chatLog.Debug(ctx, "should retry on connect, non-retry error, ending: %s", err.Error()) 952 return false 953 } 954 955 return true 956 } 957 958 func (g *gregorHandler) broadcastMessageOnce(ctx context.Context, m gregor1.Message) (err error) { 959 defer g.chatLog.Trace(ctx, &err, "broadcastMessageOnce")() 960 961 // Handle the message 962 var obm gregor.OutOfBandMessage 963 ibm := m.ToInBandMessage() 964 if ibm != nil { 965 gcli, err := g.getGregorCli() 966 if err != nil { 967 g.Debug(ctx, "BroadcastMessage: failed to get Gregor client: %s", err.Error()) 968 return err 969 } 970 // Check to see if this is already in our state 971 msgID := ibm.Metadata().MsgID() 972 state, err := gcli.StateMachineState(ctx, nil, false) 973 if err != nil { 974 g.Debug(ctx, "BroadcastMessage: no state machine available: %s", err.Error()) 975 return err 976 } 977 if _, ok := state.GetItem(msgID); ok { 978 g.Debug(ctx, "BroadcastMessage: msgID: %s already in state, ignoring", msgID) 979 return errors.New("ignored repeat message") 980 } 981 982 g.Debug(ctx, "broadcast: in-band message: msgID: %s Ctime: %s", msgID, ibm.Metadata().CTime()) 983 err = g.handleInBandMessage(ctx, g.GetIncomingClient(), ibm) 984 985 // Send message to local state machine 986 consumeErr := gcli.StateMachineConsumeMessage(ctx, m) 987 if consumeErr != nil { 988 g.Debug(ctx, "broadcast: error consuming message: %+v", consumeErr) 989 } 990 991 // Forward to electron or whichever UI is listening for the new gregor state 992 if g.pushStateFilter(m) { 993 g.pushState(keybase1.PushReason_NEW_DATA) 994 } 995 996 return err 997 } 998 999 obm = m.ToOutOfBandMessage() 1000 if obm != nil { 1001 g.Debug(ctx, "broadcast: out-of-band message: uid: %s", 1002 m.ToOutOfBandMessage().UID()) 1003 if err := g.handleOutOfBandMessage(ctx, obm); err != nil { 1004 g.Debug(ctx, "BroadcastMessage: error handling oobm: %s", err.Error()) 1005 return err 1006 } 1007 return nil 1008 } 1009 1010 g.Debug(ctx, "BroadcastMessage: both in-band and out-of-band message nil") 1011 return errors.New("invalid message, no ibm or oobm") 1012 } 1013 1014 func (g *gregorHandler) broadcastMessageHandler() { 1015 ctx := context.Background() 1016 for { 1017 m := <-g.broadcastCh 1018 if g.G().GetEnv().GetSlowGregorConn() { 1019 g.Debug(ctx, "[slow conn]: sleeping") 1020 time.Sleep(slowConnSleepTime) 1021 g.Debug(ctx, "[slow conn]: awake") 1022 } 1023 err := g.broadcastMessageOnce(ctx, m) 1024 if err != nil { 1025 g.Debug(context.Background(), "broadcast error: %v", err) 1026 } 1027 1028 // Testing alerts 1029 if g.testingEvents != nil { 1030 g.testingEvents.broadcastSentCh <- err 1031 } 1032 } 1033 } 1034 1035 // BroadcastMessage is called when we receive a new message from gregord. Grabs 1036 // the lock protect the state machine and handleInBandMessage 1037 func (g *gregorHandler) BroadcastMessage(ctx context.Context, m gregor1.Message) error { 1038 // Send the message on a channel so we can return to Gregor as fast as possible. Note 1039 // that this can block, but broadcastCh has a large buffer to try and mitigate 1040 g.broadcastCh <- m 1041 return nil 1042 } 1043 1044 // handleInBandMessage runs a message on all the alive handlers. gregorHandler 1045 // must be locked when calling this function. 1046 func (g *gregorHandler) handleInBandMessage(ctx context.Context, cli gregor1.IncomingInterface, 1047 ibm gregor.InBandMessage) (err error) { 1048 1049 defer g.G().Trace(fmt.Sprintf("gregorHandler#handleInBandMessage with %d handlers", len(g.ibmHandlers)), &err)() 1050 ctx = libkb.WithLogTag(ctx, "GRGIBM") 1051 1052 var freshHandlers []libkb.GregorInBandMessageHandler 1053 1054 // Loop over all handlers and run the messages against any that are alive 1055 // If the handler is not alive, we prune it from our list 1056 for i, handler := range g.ibmHandlers { 1057 g.Debug(ctx, "trying handler %s at position %d", handler.Name(), i) 1058 if handler.IsAlive() { 1059 if handled, err := g.handleInBandMessageWithHandler(ctx, cli, ibm, handler); err != nil { 1060 if handled { 1061 // Don't stop handling errors on a first failure. 1062 g.Errorf(ctx, "failed to run %s handler: %s", handler.Name(), err) 1063 } else { 1064 g.Debug(ctx, "handleInBandMessage() failed to run %s handler: %s", handler.Name(), err) 1065 } 1066 } 1067 freshHandlers = append(freshHandlers, handler) 1068 } else { 1069 g.Debug(ctx, "skipping handler as it's marked dead: %s", handler.Name()) 1070 } 1071 } 1072 1073 if len(g.ibmHandlers) != len(freshHandlers) { 1074 g.Debug(ctx, "Change # of live handlers from %d to %d", len(g.ibmHandlers), len(freshHandlers)) 1075 g.ibmHandlers = freshHandlers 1076 } 1077 return nil 1078 } 1079 1080 // handleInBandMessageWithHandler runs a message against the specified handler 1081 func (g *gregorHandler) handleInBandMessageWithHandler(ctx context.Context, cli gregor1.IncomingInterface, 1082 ibm gregor.InBandMessage, handler libkb.GregorInBandMessageHandler) (bool, error) { 1083 g.Debug(ctx, "handleInBand: %+v", ibm) 1084 1085 gcli, err := g.getGregorCli() 1086 if err != nil { 1087 return false, err 1088 } 1089 state, err := gcli.StateMachineState(ctx, nil, false) 1090 if err != nil { 1091 return false, err 1092 } 1093 1094 sync := ibm.ToStateSyncMessage() 1095 if sync != nil { 1096 g.Debug(ctx, "state sync message") 1097 return false, nil 1098 } 1099 1100 update := ibm.ToStateUpdateMessage() 1101 if update != nil { 1102 g.Debug(ctx, "state update message") 1103 1104 item := update.Creation() 1105 if item != nil { 1106 id := item.Metadata().MsgID().String() 1107 g.Debug(ctx, "msg ID %s created ctime: %s", id, 1108 item.Metadata().CTime()) 1109 1110 category := "" 1111 if item.Category() != nil { 1112 category = item.Category().String() 1113 g.Debug(ctx, "item %s has category %s", id, category) 1114 } 1115 1116 if handled, err := handler.Create(ctx, cli, category, item); err != nil { 1117 return handled, err 1118 } 1119 } 1120 1121 dismissal := update.Dismissal() 1122 if dismissal != nil { 1123 g.Debug(ctx, "received dismissal") 1124 for _, id := range dismissal.MsgIDsToDismiss() { 1125 item, present := state.GetItem(id) 1126 if !present { 1127 g.Debug(ctx, "tried to dismiss item %s, not present", id.String()) 1128 continue 1129 } 1130 g.Debug(ctx, "dismissing item %s", id.String()) 1131 1132 category := "" 1133 if item.Category() != nil { 1134 category = item.Category().String() 1135 g.Debug(ctx, "dismissal %s has category %s", id, category) 1136 } 1137 1138 if handled, err := handler.Dismiss(ctx, cli, category, item); handled && err != nil { 1139 return handled, err 1140 } 1141 } 1142 if len(dismissal.RangesToDismiss()) > 0 { 1143 g.Debug(ctx, "message range dismissing not implemented") 1144 } 1145 } 1146 1147 return true, nil 1148 } 1149 1150 return false, nil 1151 } 1152 1153 func (h IdentifyUIHandler) Create(ctx context.Context, cli gregor1.IncomingInterface, category string, 1154 item gregor.Item) (bool, error) { 1155 1156 switch category { 1157 case "show_tracker_popup": 1158 return true, h.handleShowTrackerPopupCreate(ctx, cli, item) 1159 default: 1160 return false, nil 1161 } 1162 } 1163 1164 func (h IdentifyUIHandler) Dismiss(ctx context.Context, cli gregor1.IncomingInterface, category string, 1165 item gregor.Item) (bool, error) { 1166 1167 switch category { 1168 case "show_tracker_popup": 1169 return true, h.handleShowTrackerPopupDismiss(ctx, cli, item) 1170 default: 1171 return false, nil 1172 } 1173 } 1174 1175 func (h IdentifyUIHandler) handleShowTrackerPopupCreate(ctx context.Context, cli gregor1.IncomingInterface, 1176 item gregor.Item) error { 1177 1178 h.G().Log.Debug("handleShowTrackerPopupCreate: %+v", item) 1179 if item.Body() == nil { 1180 return errors.New("gregor handler for show_tracker_popup: nil message body") 1181 } 1182 body, err := jsonw.Unmarshal(item.Body().Bytes()) 1183 if err != nil { 1184 h.G().Log.Debug("body failed to unmarshal", err) 1185 return err 1186 } 1187 uidString, err := body.AtPath("uid").GetString() 1188 if err != nil { 1189 h.G().Log.Debug("failed to extract uid", err) 1190 return err 1191 } 1192 uid, err := keybase1.UIDFromString(uidString) 1193 if err != nil { 1194 h.G().Log.Debug("failed to convert UID from string", err) 1195 return err 1196 } 1197 1198 identifyUI, err := h.G().UIRouter.GetIdentifyUI() 1199 if err != nil { 1200 h.G().Log.Debug("failed to get IdentifyUI", err) 1201 return err 1202 } 1203 if identifyUI == nil { 1204 h.G().Log.Debug("got nil IdentifyUI") 1205 return errors.New("got nil IdentifyUI") 1206 } 1207 secretUI, err := h.G().UIRouter.GetSecretUI(0) 1208 if err != nil { 1209 h.G().Log.Debug("failed to get SecretUI", err) 1210 return err 1211 } 1212 if secretUI == nil { 1213 h.G().Log.Debug("got nil SecretUI") 1214 return errors.New("got nil SecretUI") 1215 } 1216 uis := libkb.UIs{ 1217 IdentifyUI: identifyUI, 1218 SecretUI: secretUI, 1219 } 1220 1221 identifyReason := keybase1.IdentifyReason{ 1222 Type: keybase1.IdentifyReasonType_TRACK, 1223 // TODO: text here? 1224 } 1225 identifyArg := keybase1.Identify2Arg{Uid: uid, Reason: identifyReason} 1226 m := libkb.NewMetaContext(ctx, h.G()).WithUIs(uis) 1227 identifyEng := engine.NewIdentify2WithUID(h.G(), &identifyArg) 1228 identifyEng.SetResponsibleGregorItem(item) 1229 return identifyEng.Run(m) 1230 } 1231 1232 func (h IdentifyUIHandler) handleShowTrackerPopupDismiss(ctx context.Context, cli gregor1.IncomingInterface, 1233 item gregor.Item) error { 1234 mctx := libkb.NewMetaContext(ctx, h.G()) 1235 1236 mctx.Debug("handleShowTrackerPopupDismiss: %+v", item) 1237 if item.Body() == nil { 1238 return errors.New("gregor dismissal for show_tracker_popup: nil message body") 1239 } 1240 body, err := jsonw.Unmarshal(item.Body().Bytes()) 1241 if err != nil { 1242 mctx.Debug("body failed to unmarshal", err) 1243 return err 1244 } 1245 uidString, err := body.AtPath("uid").GetString() 1246 if err != nil { 1247 mctx.Debug("failed to extract uid", err) 1248 return err 1249 } 1250 uid, err := keybase1.UIDFromString(uidString) 1251 if err != nil { 1252 mctx.Debug("failed to convert UID from string", err) 1253 return err 1254 } 1255 user, err := libkb.LoadUser(libkb.NewLoadUserByUIDArg(ctx, h.G(), uid)) 1256 if err != nil { 1257 mctx.Debug("failed to load user from UID", err) 1258 return err 1259 } 1260 1261 identifyUI, err := h.G().UIRouter.GetIdentifyUI() 1262 if err != nil { 1263 mctx.Debug("failed to get IdentifyUI", err) 1264 return err 1265 } 1266 if identifyUI == nil { 1267 mctx.Debug("got nil IdentifyUI") 1268 return errors.New("got nil IdentifyUI") 1269 } 1270 1271 reason := keybase1.DismissReason{ 1272 Type: keybase1.DismissReasonType_HANDLED_ELSEWHERE, 1273 } 1274 _ = identifyUI.Dismiss(mctx, user.GetName(), reason) 1275 1276 return nil 1277 } 1278 1279 func (g *gregorHandler) handleOutOfBandMessage(ctx context.Context, obm gregor.OutOfBandMessage) error { 1280 if obm.System() == nil { 1281 return errors.New("nil system in out of band message") 1282 } 1283 1284 if tmp, ok := obm.(gregor1.OutOfBandMessage); ok { 1285 g.pushOutOfBandMessages([]gregor1.OutOfBandMessage{tmp}) 1286 } else { 1287 g.G().Log.Warning("Got non-exportable out-of-band message") 1288 } 1289 1290 // Send the oobm to the chat system so that it can potentially handle it 1291 if g.G().PushHandler != nil { 1292 handled, err := g.G().PushHandler.HandleOobm(ctx, obm) 1293 if err != nil { 1294 return err 1295 } 1296 if handled { 1297 return nil 1298 } 1299 } 1300 1301 // Send the oobm to the wallet system so that it can potentially handle it 1302 if g.G().StellarPushHandler != nil { 1303 handled, err := g.G().StellarPushHandler.HandleOobm(ctx, obm) 1304 if err != nil { 1305 return err 1306 } 1307 if handled { 1308 return nil 1309 } 1310 } 1311 1312 switch obm.System().String() { 1313 case "internal.reconnect": 1314 g.G().Log.Debug("reconnected to push server") 1315 return nil 1316 default: 1317 return fmt.Errorf("unhandled system: %s", obm.System()) 1318 } 1319 } 1320 1321 func (g *gregorHandler) Shutdown() { 1322 defer g.chatLog.Trace(context.Background(), nil, "Shutdown")() 1323 g.connMutex.Lock() 1324 defer g.connMutex.Unlock() 1325 1326 if g.conn == nil { 1327 return 1328 } 1329 1330 // Alert chat syncer that we are now disconnected 1331 g.G().Syncer.Disconnected(context.Background()) 1332 1333 close(g.shutdownCh) 1334 g.conn.Shutdown() 1335 g.conn = nil 1336 g.cli = nil 1337 } 1338 1339 func (g *gregorHandler) Reset() error { 1340 g.Shutdown() 1341 g.setFirstConnect(true) 1342 g.shutdownGregorClient(context.TODO()) 1343 return nil 1344 } 1345 1346 type loggedInRes int 1347 1348 const ( 1349 loggedInYes loggedInRes = iota 1350 loggedInNo 1351 loggedInMaybe 1352 ) 1353 1354 func (g *gregorHandler) loggedIn(ctx context.Context) (uid keybase1.UID, did keybase1.DeviceID, token string, nist *libkb.NIST, res loggedInRes) { 1355 1356 // Check to see if we have been shut down, 1357 select { 1358 case <-g.shutdownCh: 1359 return uid, did, token, nil, loggedInMaybe 1360 default: 1361 // if we were going to block, then that means we are still alive 1362 } 1363 1364 var err error 1365 1366 nist, uid, did, err = g.G().ActiveDevice.NISTAndUIDDeviceID(ctx) 1367 if nist == nil { 1368 g.G().Log.CDebugf(ctx, "gregorHandler: no NIST for login; user isn't logged in") 1369 return uid, did, token, nil, loggedInNo 1370 } 1371 if err != nil { 1372 g.G().Log.CDebugf(ctx, "gregorHandler: error in generating NIST: %s", err.Error()) 1373 return uid, did, token, nil, loggedInMaybe 1374 } 1375 1376 return uid, did, nist.Token().String(), nist, loggedInYes 1377 } 1378 1379 func (g *gregorHandler) auth(ctx context.Context, cli rpc.GenericClient, auth *gregor1.AuthResult) (err error) { 1380 var token string 1381 var res loggedInRes 1382 var uid keybase1.UID 1383 var nist *libkb.NIST 1384 1385 if uid, _, token, nist, res = g.loggedIn(ctx); res != loggedInYes { 1386 return newConnectionAuthError("not logged in for auth", res == loggedInMaybe) 1387 } 1388 1389 if auth == nil { 1390 g.chatLog.Debug(ctx, "logged in: authenticating") 1391 ac := gregor1.AuthClient{Cli: cli} 1392 auth = new(gregor1.AuthResult) 1393 *auth, err = ac.AuthenticateSessionToken(ctx, gregor1.SessionToken(token)) 1394 if err != nil { 1395 g.chatLog.Debug(ctx, "auth error: %s", err) 1396 g.forceSessionCheck = true 1397 nist.DidFail() 1398 return err 1399 } 1400 } else { 1401 g.Debug(ctx, "using previously obtained auth result") 1402 } 1403 1404 g.chatLog.Debug(ctx, "auth result: %+v", *auth) 1405 if !bytes.Equal(auth.Uid, uid.ToBytes()) { 1406 msg := fmt.Sprintf("auth result uid %x doesn't match session uid %q", auth.Uid, uid) 1407 return newConnectionAuthError(msg, false) 1408 } 1409 g.sessionID = auth.Sid 1410 1411 return nil 1412 } 1413 1414 func (g *gregorHandler) isReachable() bool { 1415 ctx := context.Background() 1416 timeout := g.G().Env.GetGregorPingTimeout() 1417 url, err := url.Parse(g.G().Env.GetGregorURI()) 1418 if err != nil { 1419 g.chatLog.Debug(ctx, "isReachable: failed to parse server uri, exiting: %s", err.Error()) 1420 return false 1421 } 1422 1423 // If we currently think we are online, then make sure 1424 conn, err := libkb.ProxyDialTimeout(g.G().Env, "tcp", url.Host, timeout) 1425 if conn != nil { 1426 conn.Close() 1427 return true 1428 } 1429 if err != nil { 1430 g.chatLog.Debug(ctx, "isReachable: error: terminating connection: %s", err.Error()) 1431 if _, err := g.Reconnect(ctx); err != nil { 1432 g.chatLog.Debug(ctx, "isReachable: error reconnecting: %s", err.Error()) 1433 } 1434 return false 1435 } 1436 1437 return true 1438 } 1439 1440 func (g *gregorHandler) Reconnect(ctx context.Context) (didShutdown bool, err error) { 1441 if g.IsConnected() { 1442 didShutdown = true 1443 g.chatLog.Debug(ctx, "Reconnect: reconnecting to server") 1444 g.Shutdown() 1445 return didShutdown, g.Connect(g.uri) 1446 } 1447 1448 didShutdown = false 1449 g.chatLog.Debug(ctx, "Reconnect: skipping reconnect, already disconnected") 1450 return didShutdown, nil 1451 } 1452 1453 func (g *gregorHandler) forcePing(ctx context.Context) { 1454 select { 1455 case g.forcePingCh <- struct{}{}: 1456 default: 1457 g.Debug(ctx, "forcePing: failed to write to channel, its full") 1458 } 1459 } 1460 1461 func (g *gregorHandler) pingOnce(ctx context.Context, id []byte, shutdownCancel context.CancelFunc) { 1462 var err error 1463 doneCh := make(chan error) 1464 timeout := g.G().Env.GetGregorPingTimeout() 1465 go func(ctx context.Context) { 1466 if g.IsConnected() { 1467 // If we are connected, subject the ping call to a fairly 1468 // aggressive timeout so our chat stuff can be responsive 1469 // to changes in connectivity 1470 var timeoutCancel context.CancelFunc 1471 var timeoutCtx context.Context 1472 timeoutCtx, timeoutCancel = context.WithTimeout(ctx, timeout) 1473 _, err = gregor1.IncomingClient{Cli: g.pingCli}.Ping(timeoutCtx) 1474 timeoutCancel() 1475 } else { 1476 // If we are not connected, we don't want to timeout anything 1477 // Just hook into the normal reconnect chan stuff in the RPC 1478 // library 1479 g.chatLog.Debug(ctx, "ping loop: id: %x normal ping, not connected", id) 1480 _, err = gregor1.IncomingClient{Cli: g.pingCli}.Ping(ctx) 1481 g.chatLog.Debug(ctx, "ping loop: id: %x normal ping success", id) 1482 } 1483 select { 1484 case <-ctx.Done(): 1485 g.chatLog.Debug(ctx, "ping loop: id: %x context cancelled, so not sending err", id) 1486 default: 1487 doneCh <- err 1488 } 1489 }(ctx) 1490 1491 select { 1492 case err = <-doneCh: 1493 case <-g.shutdownCh: 1494 g.chatLog.Debug(ctx, "ping loop: id: %x shutdown received", id) 1495 shutdownCancel() 1496 return 1497 } 1498 if err != nil { 1499 g.Debug(ctx, "ping loop: id: %x error: %s", id, err) 1500 if err == context.DeadlineExceeded { 1501 g.chatLog.Debug(ctx, "ping loop: timeout: terminating connection") 1502 var didShutdown bool 1503 var err error 1504 if didShutdown, err = g.Reconnect(ctx); err != nil { 1505 g.chatLog.Debug(ctx, "ping loop: id: %x error reconnecting: %s", id, err) 1506 } 1507 // It is possible that we have already reconnected by the time we call Reconnect 1508 // above. If that is the case, we don't want to terminate the ping loop. Only 1509 // if Reconnect has actually reset the connection do we stop this ping loop. 1510 if didShutdown { 1511 shutdownCancel() 1512 return 1513 } 1514 } 1515 } 1516 } 1517 1518 func (g *gregorHandler) pingLoop() { 1519 ctx := context.Background() 1520 id, _ := libkb.RandBytes(4) 1521 duration := g.G().Env.GetGregorPingInterval() 1522 timeout := g.G().Env.GetGregorPingTimeout() 1523 url, err := url.Parse(g.G().Env.GetGregorURI()) 1524 if err != nil { 1525 g.chatLog.Debug(ctx, "ping loop: failed to parse server uri, exiting: %s", err.Error()) 1526 return 1527 } 1528 g.chatLog.Debug(ctx, "ping loop: starting up: id: %x duration: %v timeout: %v url: %s", 1529 id, duration, timeout, url.Host) 1530 defer g.chatLog.Debug(ctx, "ping loop: id: %x terminating", id) 1531 ticker := time.NewTicker(duration) 1532 for { 1533 ctx, shutdownCancel := context.WithCancel(context.Background()) 1534 select { 1535 case <-g.forcePingCh: 1536 g.chatLog.Debug(ctx, "ping loop: forced attempt") 1537 g.pingOnce(ctx, id, shutdownCancel) 1538 case <-ticker.C: 1539 g.pingOnce(ctx, id, shutdownCancel) 1540 case <-g.shutdownCh: 1541 g.chatLog.Debug(ctx, "ping loop: id: %x shutdown received", id) 1542 shutdownCancel() 1543 return 1544 } 1545 shutdownCancel() 1546 } 1547 } 1548 1549 // connMutex must be locked before calling this 1550 func (g *gregorHandler) connectTLS() error { 1551 ctx := context.Background() 1552 if g.conn != nil { 1553 g.chatLog.Debug(ctx, "skipping connect, conn is not nil") 1554 return nil 1555 } 1556 1557 uri := g.uri 1558 g.chatLog.Debug(ctx, "connecting to gregord via TLS at %s", uri) 1559 rawCA := g.G().Env.GetBundledCA(uri.Host) 1560 if len(rawCA) == 0 { 1561 return fmt.Errorf("No bundled CA for %s", uri.Host) 1562 } 1563 g.chatLog.Debug(ctx, "Using CA for gregor: %s", libkb.ShortCA(rawCA)) 1564 // Let people know we are trying to sync 1565 g.G().NotifyRouter.HandleChatInboxSyncStarted(ctx, g.G().Env.GetUID()) 1566 1567 opts := rpc.ConnectionOpts{ 1568 TagsFunc: logger.LogTagsFromContextRPC, 1569 WrapErrorFunc: libkb.MakeWrapError(g.G().ExternalG()), 1570 ReconnectBackoff: func() backoff.BackOff { 1571 return backoff.NewConstantBackOff(GregorConnectionRetryInterval) 1572 }, 1573 DialerTimeout: 10 * time.Second, 1574 HandshakeTimeout: 10 * time.Second, 1575 // We deliberately avoid ForceInitialBackoff here, becuase we don't 1576 // want to penalize mobile, which tears down its connection frequently. 1577 } 1578 g.conn = rpc.NewTLSConnectionWithDialable(rpc.NewFixedRemote(uri.HostPort), 1579 []byte(rawCA), libkb.NewContextifiedErrorUnwrapper(g.G().ExternalG()), 1580 g, libkb.NewRPCLogFactory(g.G().ExternalG()), 1581 g.G().ExternalG().RemoteNetworkInstrumenterStorage, 1582 logger.LogOutputWithDepthAdder{Logger: g.G().Log}, 1583 rpc.DefaultMaxFrameLength, opts, 1584 libkb.NewProxyDialable(g.G().Env)) 1585 1586 // The client we get here will reconnect to gregord on disconnect if necessary. 1587 // We should grab it here instead of in OnConnect, since the connection is not 1588 // fully established in OnConnect. Anything that wants to make calls outside 1589 // of OnConnect should use g.cli, everything else should the client that is 1590 // a parameter to OnConnect 1591 g.cli = WrapGenericClientWithTimeout(g.conn.GetClient(), GregorRequestTimeout, 1592 chat.ErrChatServerTimeout) 1593 g.pingCli = g.conn.GetClient() // Don't want this to have a timeout from here 1594 1595 // Start up ping loop to keep the connection to gregord alive, and to kick 1596 // off the reconnect logic in the RPC library 1597 go g.pingLoop() 1598 1599 return nil 1600 } 1601 1602 // connMutex must be locked before calling this 1603 func (g *gregorHandler) connectNoTLS() error { 1604 ctx := context.Background() 1605 if g.conn != nil { 1606 g.chatLog.Debug(ctx, "skipping connect, conn is not nil") 1607 return nil 1608 } 1609 uri := g.uri 1610 g.chatLog.Debug(ctx, "connecting to gregord without TLS at %s", uri) 1611 t := newConnTransport(g.G().ExternalG(), uri.HostPort) 1612 g.transportForTesting = t 1613 1614 opts := rpc.ConnectionOpts{ 1615 TagsFunc: logger.LogTagsFromContextRPC, 1616 WrapErrorFunc: libkb.MakeWrapError(g.G().ExternalG()), 1617 ReconnectBackoff: func() backoff.BackOff { 1618 return backoff.NewConstantBackOff(GregorConnectionRetryInterval) 1619 }, 1620 } 1621 g.conn = rpc.NewConnectionWithTransport(g, t, 1622 libkb.NewContextifiedErrorUnwrapper(g.G().ExternalG()), 1623 logger.LogOutputWithDepthAdder{Logger: g.G().Log}, opts) 1624 1625 g.cli = WrapGenericClientWithTimeout(g.conn.GetClient(), GregorRequestTimeout, 1626 chat.ErrChatServerTimeout) 1627 g.pingCli = g.conn.GetClient() 1628 1629 // Start up ping loop to keep the connection to gregord alive, and to kick 1630 // off the reconnect logic in the RPC library 1631 go g.pingLoop() 1632 1633 return nil 1634 } 1635 1636 func (g *gregorHandler) currentUID() gregor1.UID { 1637 return gregor1.UID(g.G().ActiveDevice.UID().ToBytes()) 1638 } 1639 1640 // `cli` is the interface used to talk to gregor. 1641 // If nil then the global cli will be used. 1642 // Be sure to pass a cli when called from within OnConnect, as the global cli would deadlock. 1643 func (g *gregorHandler) DismissItem(ctx context.Context, cli gregor1.IncomingInterface, id gregor.MsgID) error { 1644 if id == nil { 1645 return nil 1646 } 1647 var err error 1648 defer g.G().CTrace(ctx, fmt.Sprintf("gregorHandler.dismissItem(%s)", id.String()), 1649 &err, 1650 )() 1651 defer g.pushState(keybase1.PushReason_NEW_DATA) 1652 dismissal, err := grutils.FormMessageForDismissItem(ctx, g.currentUID(), id) 1653 if err != nil { 1654 return err 1655 } 1656 gcli, err := g.getGregorCli() 1657 if err != nil { 1658 return err 1659 } 1660 return gcli.ConsumeMessage(ctx, dismissal) 1661 } 1662 1663 func (g *gregorHandler) LocalDismissItem(ctx context.Context, id gregor.MsgID) (err error) { 1664 if id == nil { 1665 return nil 1666 } 1667 defer g.G().CTrace(ctx, fmt.Sprintf("gregorHandler.localDismissItem(%s)", id.String()), 1668 &err, 1669 )() 1670 defer g.pushState(keybase1.PushReason_NEW_DATA) 1671 1672 cli, err := g.getGregorCli() 1673 if err != nil { 1674 return err 1675 } 1676 return cli.StateMachineConsumeLocalDismissal(ctx, id) 1677 } 1678 1679 func (g *gregorHandler) DismissCategory(ctx context.Context, category gregor1.Category) error { 1680 var err error 1681 defer g.G().CTrace(ctx, fmt.Sprintf("gregorHandler.DismissCategory(%s)", category.String()), 1682 &err, 1683 )() 1684 defer g.pushState(keybase1.PushReason_NEW_DATA) 1685 1686 dismissal, err := grutils.FormMessageForDismissCategory(ctx, g.currentUID(), category) 1687 if err != nil { 1688 return err 1689 } 1690 1691 gcli, err := g.getGregorCli() 1692 if err != nil { 1693 return err 1694 } 1695 return gcli.ConsumeMessage(ctx, dismissal) 1696 } 1697 1698 func (g *gregorHandler) InjectItem(ctx context.Context, cat string, body []byte, dtime gregor1.TimeOrOffset) (gregor1.MsgID, error) { 1699 var err error 1700 defer g.G().CTrace(ctx, fmt.Sprintf("gregorHandler.InjectItem(%s)", cat), 1701 &err, 1702 )() 1703 defer g.pushState(keybase1.PushReason_NEW_DATA) 1704 1705 creation, err := grutils.FormMessageForInjectItem(ctx, g.currentUID(), cat, body, dtime) 1706 if err != nil { 1707 return nil, err 1708 } 1709 1710 gcli, err := g.getGregorCli() 1711 if err != nil { 1712 return nil, err 1713 } 1714 retMsgID := gregor1.MsgID(creation.ToInBandMessage().Metadata().MsgID().Bytes()) 1715 return retMsgID, gcli.ConsumeMessage(ctx, creation) 1716 } 1717 1718 func (g *gregorHandler) UpdateItem(ctx context.Context, msgID gregor1.MsgID, cat string, body []byte, dtime gregor1.TimeOrOffset) (gregor1.MsgID, error) { 1719 var err error 1720 defer g.G().CTrace(ctx, fmt.Sprintf("gregorHandler.UpdateItem(%s,%s)", msgID.String(), cat), 1721 &err, 1722 )() 1723 defer g.pushState(keybase1.PushReason_NEW_DATA) 1724 1725 msg, err := grutils.TemplateMessage(g.currentUID()) 1726 if err != nil { 1727 return nil, err 1728 } 1729 msg.Ibm_.StateUpdate_.Creation_ = &gregor1.Item{ 1730 Category_: gregor1.Category(cat), 1731 Body_: gregor1.Body(body), 1732 Dtime_: dtime, 1733 } 1734 msg.Ibm_.StateUpdate_.Dismissal_ = &gregor1.Dismissal{ 1735 MsgIDs_: []gregor1.MsgID{msgID}, 1736 } 1737 1738 gcli, err := g.getGregorCli() 1739 if err != nil { 1740 return nil, err 1741 } 1742 return msg.Ibm_.StateUpdate_.Md_.MsgID_, gcli.ConsumeMessage(ctx, msg) 1743 } 1744 1745 func (g *gregorHandler) UpdateCategory(ctx context.Context, cat string, body []byte, 1746 dtime gregor1.TimeOrOffset) (res gregor1.MsgID, err error) { 1747 defer g.G().CTrace(ctx, fmt.Sprintf("gregorHandler.UpdateCategory(%s)", cat), 1748 &err, 1749 )() 1750 defer g.pushState(keybase1.PushReason_NEW_DATA) 1751 1752 msg, err := grutils.TemplateMessage(g.currentUID()) 1753 if err != nil { 1754 return nil, err 1755 } 1756 msgID := msg.Ibm_.StateUpdate_.Md_.MsgID_ 1757 msg.Ibm_.StateUpdate_.Creation_ = &gregor1.Item{ 1758 Category_: gregor1.Category(cat), 1759 Body_: gregor1.Body(body), 1760 Dtime_: dtime, 1761 } 1762 msg.Ibm_.StateUpdate_.Dismissal_ = &gregor1.Dismissal{ 1763 Ranges_: []gregor1.MsgRange{ 1764 { 1765 Category_: gregor1.Category(cat), 1766 SkipMsgIDs_: []gregor1.MsgID{msgID}, 1767 }}, 1768 } 1769 1770 gcli, err := g.getGregorCli() 1771 if err != nil { 1772 return nil, err 1773 } 1774 return msgID, gcli.ConsumeMessage(ctx, msg) 1775 } 1776 1777 func (g *gregorHandler) InjectOutOfBandMessage(ctx context.Context, system string, body []byte) error { 1778 var err error 1779 defer g.G().CTrace(ctx, fmt.Sprintf("gregorHandler.InjectOutOfBandMessage(%s)", system), 1780 &err, 1781 )() 1782 1783 uid := g.G().Env.GetUID() 1784 if uid.IsNil() { 1785 return libkb.LoggedInError{} 1786 } 1787 gregorUID := gregor1.UID(uid.ToBytes()) 1788 1789 msg := gregor1.Message{ 1790 Oobm_: &gregor1.OutOfBandMessage{ 1791 Uid_: gregorUID, 1792 System_: gregor1.System(system), 1793 Body_: gregor1.Body(body), 1794 }, 1795 } 1796 1797 gcli, err := g.getGregorCli() 1798 if err != nil { 1799 return err 1800 } 1801 return gcli.ConsumeMessage(ctx, msg) 1802 } 1803 1804 func (g *gregorHandler) simulateCrashForTesting() { 1805 g.transportForTesting.Reset() 1806 _, _ = gregor1.IncomingClient{Cli: g.cli}.Ping(context.Background()) 1807 } 1808 1809 type gregorRPCHandler struct { 1810 libkb.Contextified 1811 xp rpc.Transporter 1812 gh *gregorHandler 1813 } 1814 1815 func newGregorRPCHandler(xp rpc.Transporter, g *libkb.GlobalContext, gh *gregorHandler) *gregorRPCHandler { 1816 return &gregorRPCHandler{ 1817 Contextified: libkb.NewContextified(g), 1818 xp: xp, 1819 gh: gh, 1820 } 1821 } 1822 1823 func (g *gregorHandler) getState(ctx context.Context) (res gregor1.State, err error) { 1824 var s gregor.State 1825 1826 gcli, err := g.getGregorCli() 1827 if err != nil { 1828 return res, err 1829 } 1830 1831 s, err = gcli.StateMachineState(ctx, nil, true) 1832 if err != nil { 1833 return res, err 1834 } 1835 1836 ps, err := s.Export() 1837 if err != nil { 1838 return res, err 1839 } 1840 1841 var ok bool 1842 if res, ok = ps.(gregor1.State); !ok { 1843 return res, errors.New("failed to convert state to exportable format") 1844 } 1845 1846 return res, nil 1847 } 1848 1849 func (g *gregorHandler) State(ctx context.Context) (res gregor.State, err error) { 1850 defer g.G().CTrace(ctx, "gregorHandler#State", &err)() 1851 gcli, err := g.getGregorCli() 1852 if err != nil { 1853 return res, err 1854 } 1855 return gcli.StateMachineState(ctx, nil, true) 1856 } 1857 1858 func (g *gregorRPCHandler) GetState(ctx context.Context) (res gregor1.State, err error) { 1859 defer g.G().CTrace(ctx, "gregorRPCHandler#GetState", &err)() 1860 if res, err = g.gh.getState(ctx); err != nil { 1861 return res, err 1862 } 1863 g.G().Log.CDebugf(ctx, "GetState: returning %d items", len(res.Items_)) 1864 return res, nil 1865 } 1866 1867 func (g *gregorRPCHandler) InjectItem(ctx context.Context, arg keybase1.InjectItemArg) (res gregor1.MsgID, err error) { 1868 defer g.G().CTrace(ctx, "gregorRPCHandler#InjectItem", &err)() 1869 return g.gh.InjectItem(ctx, arg.Cat, []byte(arg.Body), arg.Dtime) 1870 } 1871 1872 func (g *gregorRPCHandler) UpdateItem(ctx context.Context, arg keybase1.UpdateItemArg) (res gregor1.MsgID, err error) { 1873 defer g.G().CTrace(ctx, "gregorRPCHandler#UpdateItem", &err)() 1874 return g.gh.UpdateItem(ctx, arg.MsgID, arg.Cat, []byte(arg.Body), arg.Dtime) 1875 } 1876 1877 func (g *gregorRPCHandler) UpdateCategory(ctx context.Context, arg keybase1.UpdateCategoryArg) (res gregor1.MsgID, err error) { 1878 defer g.G().CTrace(ctx, "gregorRPCHandler#UpdateCategory", &err)() 1879 return g.gh.UpdateCategory(ctx, arg.Category, []byte(arg.Body), arg.Dtime) 1880 } 1881 1882 func (g *gregorRPCHandler) DismissCategory(ctx context.Context, category gregor1.Category) (err error) { 1883 defer g.G().CTrace(ctx, "gregorRPCHandler#DismissCategory", &err)() 1884 return g.gh.DismissCategory(ctx, category) 1885 } 1886 1887 func (g *gregorRPCHandler) DismissItem(ctx context.Context, id gregor1.MsgID) (err error) { 1888 defer g.G().CTrace(ctx, "gregorRPCHandler#DismissItem", &err)() 1889 return g.gh.DismissItem(ctx, nil, id) 1890 } 1891 1892 func WrapGenericClientWithTimeout(client rpc.GenericClient, timeout time.Duration, timeoutErr error) rpc.GenericClient { 1893 return &timeoutClient{client, timeout, timeoutErr} 1894 } 1895 1896 type timeoutClient struct { 1897 inner rpc.GenericClient 1898 timeout time.Duration 1899 timeoutErr error 1900 } 1901 1902 var _ rpc.GenericClient = (*timeoutClient)(nil) 1903 1904 func (t *timeoutClient) Call(ctx context.Context, method string, arg interface{}, 1905 res interface{}, timeout time.Duration) error { 1906 if timeout == 0 { 1907 timeout = t.timeout 1908 } 1909 err := t.inner.Call(ctx, method, arg, res, timeout) 1910 if err == context.DeadlineExceeded { 1911 return t.timeoutErr 1912 } 1913 return err 1914 } 1915 1916 func (t *timeoutClient) CallCompressed(ctx context.Context, method string, arg interface{}, 1917 res interface{}, ctype rpc.CompressionType, timeout time.Duration) error { 1918 if timeout == 0 { 1919 timeout = t.timeout 1920 } 1921 err := t.inner.CallCompressed(ctx, method, arg, res, ctype, timeout) 1922 if err == context.DeadlineExceeded { 1923 return t.timeoutErr 1924 } 1925 return err 1926 } 1927 1928 func (t *timeoutClient) Notify(ctx context.Context, method string, arg interface{}, timeout time.Duration) error { 1929 if timeout == 0 { 1930 timeout = t.timeout 1931 } 1932 err := t.inner.Notify(ctx, method, arg, timeout) 1933 if err == context.DeadlineExceeded { 1934 return t.timeoutErr 1935 } 1936 return err 1937 }