From 608bb38874cda5985a936cca7e44c770671f172c Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Thu, 30 Nov 2023 18:10:59 +0100 Subject: [PATCH] chore(@desktop/keycard): improvement in terms of avoiding overlapping keycard library flows --- .../keycard_popup/controller.nim | 8 +++ .../keycard_popup/io_interface.nim | 3 + .../shared_modules/keycard_popup/module.nim | 33 +++++++++ src/app_service/common/async_tasks.nim | 3 +- src/app_service/service/keycard/service.nim | 67 ++++++++++++++++--- 5 files changed, 104 insertions(+), 10 deletions(-) diff --git a/src/app/modules/shared_modules/keycard_popup/controller.nim b/src/app/modules/shared_modules/keycard_popup/controller.nim index 0d5ea3e8e2..b8522965fa 100644 --- a/src/app/modules/shared_modules/keycard_popup/controller.nim +++ b/src/app/modules/shared_modules/keycard_popup/controller.nim @@ -170,6 +170,14 @@ proc disconnectAll*(self: Controller) = proc delete*(self: Controller) = self.disconnectAll() +proc checkKeycardAvailability*(self: Controller) = + if self.keycardService.isBusy(): + self.keycardService.registerForKeycardAvailability(proc () = + self.delegate.keycardReady() + ) + else: + self.delegate.keycardReady() + proc init*(self: Controller, fullConnect = true) = self.connectKeycardReponseSignal() diff --git a/src/app/modules/shared_modules/keycard_popup/io_interface.nim b/src/app/modules/shared_modules/keycard_popup/io_interface.nim index a15f3da2f2..c328f5ed6e 100644 --- a/src/app/modules/shared_modules/keycard_popup/io_interface.nim +++ b/src/app/modules/shared_modules/keycard_popup/io_interface.nim @@ -106,6 +106,9 @@ type method delete*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") +method keycardReady*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/shared_modules/keycard_popup/module.nim b/src/app/modules/shared_modules/keycard_popup/module.nim index 7d3069f315..5792e7dc8c 100644 --- a/src/app/modules/shared_modules/keycard_popup/module.nim +++ b/src/app/modules/shared_modules/keycard_popup/module.nim @@ -35,6 +35,15 @@ type authenticationPopupIsAlreadyRunning: bool runningFlow: FlowType # in general used to mark the global shared flow that is being running (`Authentication` or `Sign`) + # temporary variables used to store data while we're wiating for keycard lib to get ready + tmpFlowToRun: FlowType + tmpKeyUid: string + tmpBip44Paths: seq[string] + tmpTxHash: string + tmpForceFlow: bool + tmpReturnToFlow: FlowType + tmpPin: string + proc newModule*[T](delegate: T, uniqueIdentifier: string, events: EventEmitter, @@ -58,6 +67,10 @@ proc newModule*[T](delegate: T, {.push warning[Deprecated]: off.} +## Forward declarations +proc proceedWithSyncKeycardBasedOnAppState[T](self: Module[T], keyUid: string, pin: string) +proc proceedWithRunFlow[T](self: Module[T], flowToRun: FlowType, keyUid: string, bip44Paths: seq[string], txHash: string, forceFlow: bool, returnToFlow: FlowType) + method delete*[T](self: Module[T]) = self.view.delete self.viewVariant.delete @@ -69,6 +82,12 @@ proc init[T](self: Module[T], fullConnect = true) = self.controller.cleanReceivedKeycardData() self.controller.init(fullConnect) +method keycardReady*[T](self: Module[T]) = + if self.tmpPin.len > 0: + self.proceedWithSyncKeycardBasedOnAppState(self.tmpKeyUid, self.tmpPin) + else: + self.proceedWithRunFlow(self.tmpFlowToRun, self.tmpKeyUid, self.tmpBip44Paths, self.tmpTxHash, self.tmpForceFlow, self.tmpReturnToFlow) + method getModuleAsVariant*[T](self: Module[T]): QVariant = return self.viewVariant @@ -318,6 +337,11 @@ method syncKeycardBasedOnAppState*[T](self: Module[T], keyUid: string, pin: stri if keyUid.len == 0: debug "cannot sync with the empty keyUid" return + self.tmpKeyUid = keyUid + self.tmpPin = pin + self.controller.checkKeycardAvailability() + +proc proceedWithSyncKeycardBasedOnAppState[T](self: Module[T], keyUid: string, pin: string) = self.init(fullConnect = false) self.controller.setKeyUidWhichIsBeingSyncing(keyUid) self.controller.setPin(pin) @@ -478,6 +502,15 @@ method runFlow*[T](self: Module[T], flowToRun: FlowType, keyUid = "", bip44Paths self.controller.terminateCurrentFlow(lastStepInTheCurrentFlow = false) error "sm_cannot run an general flow" return + self.tmpFlowToRun = flowToRun + self.tmpKeyUid = keyUid + self.tmpBip44Paths = bip44Paths + self.tmpTxHash = txHash + self.tmpForceFlow = forceFlow + self.tmpReturnToFlow = returnToFlow + self.controller.checkKeycardAvailability() + +proc proceedWithRunFlow[T](self: Module[T], flowToRun: FlowType, keyUid: string, bip44Paths: seq[string], txHash: string, forceFlow: bool, returnToFlow: FlowType) = self.init() self.view.setForceFlow(forceFlow) self.view.setReturnToFlow(returnToFlow) diff --git a/src/app_service/common/async_tasks.nim b/src/app_service/common/async_tasks.nim index 556a7b19d2..ca89da91ad 100644 --- a/src/app_service/common/async_tasks.nim +++ b/src/app_service/common/async_tasks.nim @@ -5,8 +5,9 @@ type TimerTaskArg = ref object of QObjectTaskArg timeoutInMilliseconds: int + reason: string const timerTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[TimerTaskArg](argEncoded) sleep(arg.timeoutInMilliseconds) - arg.finish("") \ No newline at end of file + arg.finish(arg.reason) \ No newline at end of file diff --git a/src/app_service/service/keycard/service.nim b/src/app_service/service/keycard/service.nim index 1407a8c379..d654e24cc7 100644 --- a/src/app_service/service/keycard/service.nim +++ b/src/app_service/service/keycard/service.nim @@ -37,10 +37,15 @@ const SupportedMnemonicLength18* = 18 const SupportedMnemonicLength24* = 24 const MnemonicLengthForStatusApp = SupportedMnemonicLength12 -const TimerIntervalInMilliseconds = 3 * 1000 # 3 seconds +const ReRunCurrentFlowInterval = 3 * 1000 # 3 seconds +const CheckKeycardAvailabilityInterval = 1000 # 1 seconds const SIGNAL_KEYCARD_RESPONSE* = "keycardResponse" +type TimerReason {.pure.} = enum + ReRunCurrentFlowLater = "ReRunCurrentFlowLater" + WaitForKeycardAvailability = "WaitForKeycardAvailability" + logScope: topics = "keycard-service" @@ -63,6 +68,16 @@ QtObject: lastReceivedKeycardData: tuple[flowType: string, flowEvent: KeycardEvent] setPayloadForCurrentFlow: JsonNode doLogging: bool + busy: bool + waitingFlows: seq[tuple[flow: KCSFlowType, payload: JsonNode]] + registeredCallback: proc () + + ## Forward declaration + proc startFlow(self: Service, payload: JsonNode) + proc runTimer(self: Service, timeoutInMilliseconds: int, reason: string) + + proc isBusy*(self: Service): bool = + return self.busy proc delete*(self: Service) = self.closingApp = true @@ -108,7 +123,13 @@ QtObject: self.events.emit(SIGNAL_KEYCARD_RESPONSE, KeycardLibArgs(flowType: flowType, flowEvent: flowEvent)) proc receiveKeycardSignal(self: Service, signal: string) {.slot.} = + self.busy = false self.processSignal(signal) + if self.waitingFlows.len > 0: + let (flow, payload) = self.waitingFlows[0] + self.waitingFlows.delete(0) + self.currentFlow = flow + self.startFlow(payload) proc getLastReceivedKeycardData*(self: Service): tuple[flowType: string, flowEvent: KeycardEvent] = return self.lastReceivedKeycardData @@ -132,18 +153,28 @@ QtObject: return self.currentFlow proc startFlow(self: Service, payload: JsonNode) = + if self.busy: + self.waitingFlows.add((flow: self.currentFlow, payload: payload)) + return + self.busy = true self.updateLocalPayloadForCurrentFlow(payload, cleanBefore = true) let response = keycard_go.keycardStartFlow(self.currentFlow.int, $payload) if self.doLogging: debug "keycardStartFlow", kcServiceCurrFlow=($self.currentFlow), payload=payload, response=response proc resumeFlow(self: Service, payload: JsonNode) = + if self.busy: + return + self.busy = true self.updateLocalPayloadForCurrentFlow(payload) let response = keycard_go.keycardResumeFlow($payload) if self.doLogging: debug "keycardResumeFlow", kcServiceCurrFlow=($self.currentFlow), payload=payload, response=response proc cancelCurrentFlow*(self: Service) = + if self.busy: + return + writeStackTrace() let response = keycard_go.keycardCancelFlow() self.currentFlow = KCSFlowType.NoFlow if self.doLogging: @@ -199,13 +230,23 @@ QtObject: result = result & $rand(0 .. 9) proc onTimeout(self: Service, response: string) {.slot.} = - if(self.closingApp or self.currentFlow == KCSFlowType.NoFlow): - return - if self.doLogging: - debug "onTimeout, about to start flow: ", kcServiceCurrFlow=($self.currentFlow) - self.startFlow(self.setPayloadForCurrentFlow) + if response == $TimerReason.ReRunCurrentFlowLater: + if(self.closingApp or self.currentFlow == KCSFlowType.NoFlow): + return + if self.doLogging: + debug "onTimeout, about to start flow: ", kcServiceCurrFlow=($self.currentFlow) + self.startFlow(self.setPayloadForCurrentFlow) + elif response == $TimerReason.WaitForKeycardAvailability: + if self.busy: + self.runTimer(CheckKeycardAvailabilityInterval, $TimerReason.WaitForKeycardAvailability) + return + if self.registeredCallback != nil: + self.registeredCallback() + self.registeredCallback = nil + else: + error "unknown timer reason", reason = response - proc runTimer(self: Service) = + proc runTimer(self: Service, timeoutInMilliseconds: int, reason: string) = if(self.closingApp or self.currentFlow == KCSFlowType.NoFlow): return @@ -213,7 +254,8 @@ QtObject: tptr: cast[ByteAddress](timerTask), vptr: cast[ByteAddress](self.vptr), slot: "onTimeout", - timeoutInMilliseconds: TimerIntervalInMilliseconds + timeoutInMilliseconds: timeoutInMilliseconds, + reason: reason ) self.threadpool.start(arg) @@ -440,4 +482,11 @@ QtObject: let tmpFlow = self.currentFlow self.cancelCurrentFlow() self.currentFlow = tmpFlow - self.runTimer() \ No newline at end of file + self.runTimer(ReRunCurrentFlowInterval, "reRunCurrentFlowLater") + + proc registerForKeycardAvailability*(self: Service, p: proc()) = + if not self.busy: + error "registerForKeycardAvailability can be called only when keycard is busy" + return + self.registeredCallback = p + self.runTimer(CheckKeycardAvailabilityInterval, $TimerReason.WaitForKeycardAvailability) \ No newline at end of file