11import logging
22from contextlib import contextmanager
3+ from dataclasses import dataclass
4+ from enum import Enum
35from time import sleep
46from typing import (
57 TYPE_CHECKING ,
2931T = TypeVar ("T" , bound = TrussedBase )
3032
3133
34+ class UpdateStatus (Enum ):
35+ SUCCESS = "success"
36+ ERROR = "error"
37+ ABORTED = "aborted"
38+
39+
40+ @dataclass
41+ class UpdateResult :
42+ status : UpdateStatus
43+ message : str | None = None
44+
45+
46+ @dataclass
47+ class UpdateException (Exception ):
48+ def __init__ (self , status : UpdateStatus , * msgs : Any ) -> None :
49+ super ().__init__ (* msgs )
50+ self .status = status
51+
52+
3253class UpdateGUI (UpdateUi ):
3354 def __init__ (self , common_ui : "CommonUi" , is_qubesos : bool ) -> None :
3455 super ().__init__ ()
3556
36- self ._version_printed = False
3757 self .common_ui = common_ui
3858 self .is_qubesos = is_qubesos
3959
4060 # blocking wait, set by parent during confirm-prompt
4161 self .await_confirmation : Optional [bool ] = None
4262
4363 def error (self , * msgs : Any ) -> Exception :
44- return Exception (* msgs )
64+ logger .error (f"Error during firmware update: { msgs } " )
65+ return UpdateException (UpdateStatus .ERROR , * msgs )
4566
4667 def abort (self , * msgs : Any ) -> Exception :
47- return Exception (* msgs )
68+ logger .warning (f"Firmware update aborted: { msgs } " )
69+ return UpdateException (UpdateStatus .ABORTED , * msgs )
4870
4971 def raise_warning (self , warning : Warning ) -> Exception :
50- return Exception ( warning .message )
72+ return UpdateException ( UpdateStatus . ERROR , warning .message )
5173
5274 def show_warning (self , warning : Warning ) -> None :
5375 res = self .run_confirm_dialog (
@@ -60,8 +82,9 @@ def show_warning(self, warning: Warning) -> None:
6082 logger .info ("OK clicked (warning dialog)" )
6183
6284 def abort_downgrade (self , current : Version , image : Version ) -> Exception :
63- self ._print_firmware_versions (current , image )
64- return self .abort ("Abort: firmware older as on device" )
85+ return self .abort (
86+ f"firmware { image } is older than the firmware on the device ({ current } )"
87+ )
6588
6689 def run_confirm_dialog (self , title : str , desc : str ) -> bool :
6790 self .common_ui .prompt .confirm .emit (title , desc )
@@ -106,7 +129,7 @@ def confirm_update(self, current: Optional[Version], new: Version) -> None:
106129 )
107130 if not res :
108131 logger .info ("Cancel clicked (confirm update)" )
109- raise self .abort ("Abort: canceled by user (confirm update)" )
132+ raise self .abort ("canceled by user (confirm update)" )
110133
111134 logger .info ("OK clicked (confirm update)" )
112135 self .common_ui .touch .start .emit ()
@@ -122,30 +145,30 @@ def confirm_update_same_version(self, version: Version) -> None:
122145 )
123146 if not res :
124147 logger .info ("Cancel clicked (confirm same version)" )
125- raise self .abort ("Abort: canceled by user (confirm same version)" )
148+ raise self .abort ("canceled by user (confirm same version)" )
126149
127150 logger .info ("OK clicked (confirm same version)" )
128151
129152 def confirm_extra_information (self , txt : List [str ]) -> None :
130153 res = self .run_confirm_dialog ("Confirm extra information" , " " .join (txt ))
131154 if not res :
132155 logger .info ("Cancel clicked (confirm extra info)" )
133- raise self .abort ("Abort: canceled by user (confirm extra info)" )
156+ raise self .abort ("canceled by user (confirm extra info)" )
134157
135158 logger .info ("OK clicked (confirm same version)" )
136159
137160 def abort_pynitrokey_version (
138161 self , current : Version , required : Version
139162 ) -> Exception :
140- raise self .abort (f"Abort: pynitrokey { required } too old, need: { current } " )
163+ raise self .abort (f"pynitrokey { required } too old, need: { current } " )
141164
142165 def confirm_pynitrokey_version (self , current : Version , required : Version ) -> None :
143166 # TODO: implement
144- raise self .abort (f"Abort: pynitrokey { required } too old, need: { current } " )
167+ raise self .abort (f"pynitrokey { required } too old, need: { current } " )
145168
146169 def request_repeated_update (self ) -> Exception :
147170 logger .info ("Bootloader mode enabled. Repeat to update" )
148- return self .abort ("Abort: bootloader enabled" )
171+ return self .abort ("bootloader enabled" )
149172
150173 def request_bootloader_confirmation (self ) -> None :
151174 logger .info ("requesting bootloader confirmation" )
@@ -173,15 +196,6 @@ def finalization_progress_bar(
173196 self .common_ui .progress .start .emit ("Finalization" )
174197 yield self .common_ui .progress .progress .emit
175198
176- def _print_firmware_versions (
177- self , current : Optional [Version ], new : Optional [Version ]
178- ) -> None :
179- if not self ._version_printed :
180- current_str = str (current ) if current else "[unknown]"
181- logger .info (f"Current firmware version: { current_str } " )
182- logger .info (f"Updated firmware version: { new } " )
183- self ._version_printed = True
184-
185199
186200class Nk3Context (DeviceHandler ):
187201 def __init__ (self , path : str ) -> None :
@@ -245,21 +259,31 @@ def update(
245259 self ,
246260 ui : UpdateGUI ,
247261 image : Optional [str ] = None ,
248- version : Optional [str ] = None ,
249- ignore_pynitrokey_version : bool = False ,
250- ) -> None :
251- with self .connect () as device :
252- updater = Updater (ui , self )
253- _ , status = updater .update (
254- device , image , version , ignore_pynitrokey_version
255- )
262+ ) -> UpdateResult :
263+ try :
264+ with self .connect () as device :
265+ updater = Updater (ui , self )
266+ _ , status = updater .update (
267+ device = device , image = image , update_version = None
268+ )
269+ except UpdateException as e :
270+ return UpdateResult (status = e .status , message = str (e ))
271+ except Exception as e :
272+ return UpdateResult (status = UpdateStatus .ERROR , message = str (e ))
273+
256274 if status .init_status is not None :
257275 if status .init_status & InitStatus .EXT_FLASH_NEED_REFORMAT :
258- raise Exception (
259- "External filesystem needs to be reformatted."
260- " Please contact support@nitrokey.com for more information on how to solve this issue."
276+ logger .error (
277+ f"Problematic init status after update: { status .init_status } "
278+ )
279+ return UpdateResult (
280+ status = UpdateStatus .ERROR ,
281+ message = "External filesystem needs to be reformatted."
282+ " Please contact support@nitrokey.com for more information on how to solve this issue." ,
261283 )
262284
285+ return UpdateResult (status = UpdateStatus .SUCCESS )
286+
263287
264288class Try :
265289 """Utility class for an execution of a repeated action with Retries."""
0 commit comments