programmer-core 1.0.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1822 @@
1
+ import argparse
2
+ from os.path import (
3
+ isabs,
4
+ abspath,
5
+ join,
6
+ dirname,
7
+ basename,
8
+ dirname,
9
+ join,
10
+ isdir,
11
+ isfile,
12
+ normcase,
13
+ normpath,
14
+ exists,
15
+ )
16
+ from os import getcwd, makedirs, remove
17
+ import re
18
+ from sys import executable
19
+ from construct import Int16ul, Int32ul, Int8ul, Padding, Struct, GreedyBytes
20
+ import sys
21
+ import logging
22
+ import pathlib
23
+ import json
24
+ from tabulate import tabulate
25
+ import shutil
26
+
27
+ from communicator_core import (
28
+ CC35XXE,
29
+ CC37XXR,
30
+ CC33XX,
31
+ CC35XX,
32
+ CC36XXR,
33
+ LifeCycleStateReg,
34
+ CC35xxELifeCycleStateReg,
35
+ CC3xxxRLifeCycleStateReg,
36
+ )
37
+
38
+ from flash_images_builder_core import (
39
+ ProgInstImgBuilder,
40
+ GPEBuilder,
41
+ BootSectorFromImgBuilder,
42
+ VendorImageBuilder,
43
+ ActionReqBuilder,
44
+ TBLImageBuilder,
45
+ BL3ImageBuilder,
46
+ CleanImageBuilder,
47
+ WSOCImageBuilder,
48
+ SUPPORTED_FLASH_TYPES,
49
+ PsramOption,
50
+ FIBUtility,
51
+ KEY_INFO_MAGIC_DEFAULT,
52
+ KEY_INFO_VERSION_DEFAULT,
53
+ KEY_INFO_SVN_DEFAULT,
54
+ KEY_INFO_SVN_POLICY_DEFAULT,
55
+ )
56
+
57
+ CC33XX = "CC33XX"
58
+ CC35X1 = "CC35X1"
59
+ CC35X1E = "CC35X1E"
60
+ CC360XR = "CC360XR"
61
+ CC370XR = "CC370XR"
62
+
63
+
64
+ PROGRAMMER_DIR = "programmer"
65
+ PROGRAMMER_TBL_DIR = "bl2"
66
+ PROGRAMMER_WSOC_DIR = "ti_wireless_firmware"
67
+ VENDOR_APP_FOLDER = "pre_complied_application"
68
+ PROGRAMMING_FILES_FOLDER_NAME = "programming"
69
+ ACTIVATION_FILES_FOLDER_NAME = "activation_n_initial_programming"
70
+ SDK_EXAMPLE_KEY_FILES_FOLDER_NAME = "sdk_example_key"
71
+ AUTHENTICATION_BYPASS_FILES_FOLDER_NAME = "authentication_bypass"
72
+ VENDOR_KEY_FILES_FOLDER_NAME = "vendor_key"
73
+ CONFIG_FOLDER_NAME = "configuration_files"
74
+ TOOL_SETTINGS_TEMPLATE = "tool_settings.template.json"
75
+ TOOL_SETTINGS = "tool_settings.json"
76
+ CONNECTION_INFO_FILE_NAME = "connection_info.json"
77
+ ACTION_PARAMS_TEMPLATE = "action_params.template.json"
78
+ ACTION_PARAMS = "action_params.json"
79
+ BINARIES_FOLDER_NAME = "binaries"
80
+
81
+ # files that are inside jsons folder
82
+ EXTMEM_JSON_NAME = "flash_disc_param_ext_mem.json"
83
+ OTFDE_JSON_NAME = "flash_disc_param_otfde.json"
84
+ XSPI_JSON_NAME = "flash_disc_param_xspi.json"
85
+
86
+ # files that are inside jsons folder
87
+ EXTMEM_JSON_MOD_NAME = "flash_disc_param_ext_mem.mod.json"
88
+ OTFDE_JSON_MOD_NAME = "flash_disc_param_otfde.mod.json"
89
+ XSPI_JSON_MOD_NAME = "flash_disc_param_xspi.mod.json"
90
+
91
+ # files that are inside images folder
92
+ VENDOR_OUT_NAME_CC35XX = "vendor_app.CC35XX.out"
93
+ VENDOR_OUT_NAME_CC35XXE = "vendor_app.CC35XXE.out"
94
+ VENDOR_OUT_NAME_CC37XXR = "vendor_app.CC37XXR.out"
95
+
96
+ # files that are inside ti_wireless_firmware folder
97
+ WSOC_NAME_CC35XX = "ti_wireless_firmware.CC35XX.bin"
98
+ WSOC_NAME_CC35XXE = "ti_wireless_firmware.CC35XXE.bin"
99
+ WSOC_NAME_CC37XXR = "ti_wireless_firmware.CC37XXR.bin"
100
+ CONF_BIN_NAME_CC35XXE = "ti_wireless_firmware.CC35XXE.conf.bin"
101
+ CONF_BIN_NAME_CC37XXR = "ti_wireless_firmware.CC37XXR.conf.bin"
102
+
103
+ # files that in fuze folder
104
+ FUSE_PROG_NAME_CC35XX = "fuse_prog_inst_param.CC35XX.json"
105
+ FUSE_PROG_NAME_CC35XXE = "fuse_prog_inst_param.CC35XXE.json"
106
+ FUSE_PROG_NAME_CC37XXR = "fuse_prog_inst_param.CC37XXR.json"
107
+ FUSE_PROG_NAME_CC35XXE_TEMPLATE = "fuse_prog_inst_param.template.json"
108
+
109
+ # CC35XX BL2
110
+ TI_BOOTLOADER_CC35XX = "ti_bootloader.CC35XX.sign.bin"
111
+ TI_PROGRAMMER_CC35XX = "ti_programmer.CC35XX.sign.bin"
112
+ TI_DEBUG_CC35XX = "ti_programmer.CC35XX.sign.bin"
113
+ TI_RMA_CC35XX = "ti_programmer.CC35XX.sign.bin"
114
+ TI_ACTIVATION_CC35XX = "ti_programmer.CC35XX.sign.bin"
115
+
116
+ # CC35XXE BL2
117
+ TI_BOOTLOADER_CC35XXE = "ti_bootloader.CC35XXE.sign.bin"
118
+ TI_PROGRAMMER_CC35XXE = "ti_programmer.CC35XXE.sign.bin"
119
+ TI_DEBUG_CC35XXE = "ti_debug.CC35XXE.sign.bin"
120
+ TI_RMA_CC35XXE = "ti_rma.CC35XXE.sign.bin"
121
+ TI_ACTIVATION_CC35XXE = "ti_activation.CC35XXE.sign.bin"
122
+
123
+ # CC37XXR BL2
124
+ TI_BOOTLOADER_CC37XXR = "ti_bootloader.CC37XXR.sign.bin"
125
+ TI_PROGRAMMER_CC37XXR = "ti_programmer.CC37XXR.sign.bin"
126
+ TI_ACTIVATION_CC37XXR = "ti_activation.CC37XXR.sign.bin"
127
+ TI_DEBUG_CC37XXR = "ti_debug.CC37XXR.sign.bin"
128
+
129
+ # Short names
130
+ # TBL = TI BootLoader
131
+ # TPR = TI PRogrammer
132
+ # TAC = TI ACtivation
133
+ # TDB = TI DeBug
134
+ # TRM = TI RMA
135
+
136
+ # Supported country codes (ISO 3166-1 alpha-2)
137
+ VALID_COUNTRY_CODES = {
138
+ "WW",
139
+ "US",
140
+ "JP",
141
+ "AF",
142
+ "AL",
143
+ "DZ",
144
+ "AS",
145
+ "AD",
146
+ "AI",
147
+ "AR",
148
+ "AM",
149
+ "AW",
150
+ "AU",
151
+ "AT",
152
+ "AZ",
153
+ "BS",
154
+ "BH",
155
+ "BD",
156
+ "BB",
157
+ "BY",
158
+ "BE",
159
+ "BZ",
160
+ "BM",
161
+ "BT",
162
+ "BO",
163
+ "BA",
164
+ "BR",
165
+ "BN",
166
+ "BG",
167
+ "BF",
168
+ "KH",
169
+ "CA",
170
+ "KY",
171
+ "CF",
172
+ "TD",
173
+ "CL",
174
+ "CN",
175
+ "CX",
176
+ "CO",
177
+ "CR",
178
+ "CI",
179
+ "HR",
180
+ "CU",
181
+ "CY",
182
+ "CZ",
183
+ "DK",
184
+ "DM",
185
+ "DO",
186
+ "EC",
187
+ "EG",
188
+ "SV",
189
+ "EE",
190
+ "ET",
191
+ "FI",
192
+ "FR",
193
+ "GF",
194
+ "PF",
195
+ "GE",
196
+ "DE",
197
+ "GH",
198
+ "GR",
199
+ "GL",
200
+ "GD",
201
+ "GP",
202
+ "GU",
203
+ "GT",
204
+ "GY",
205
+ "HT",
206
+ "HN",
207
+ "HK",
208
+ "HU",
209
+ "IS",
210
+ "IN",
211
+ "ID",
212
+ "IR",
213
+ "IE",
214
+ "IL",
215
+ "IT",
216
+ "JM",
217
+ "JO",
218
+ "KZ",
219
+ "KE",
220
+ "KP",
221
+ "KR",
222
+ "KW",
223
+ "LV",
224
+ "LB",
225
+ "LS",
226
+ "LI",
227
+ "LT",
228
+ "LU",
229
+ "MO",
230
+ "MK",
231
+ "MG",
232
+ "MW",
233
+ "MY",
234
+ "MV",
235
+ "MT",
236
+ "MH",
237
+ "MQ",
238
+ "MR",
239
+ "MU",
240
+ "YT",
241
+ "MX",
242
+ "FM",
243
+ "MD",
244
+ "MC",
245
+ "MN",
246
+ "ME",
247
+ "MA",
248
+ "NP",
249
+ "NL",
250
+ "AN",
251
+ "NZ",
252
+ "NI",
253
+ "NG",
254
+ "MP",
255
+ "NO",
256
+ "OM",
257
+ "PK",
258
+ "PW",
259
+ "PA",
260
+ "PG",
261
+ "PY",
262
+ "PE",
263
+ "PH",
264
+ "PL",
265
+ "PT",
266
+ "PR",
267
+ "QA",
268
+ "RE",
269
+ "RO",
270
+ "RU",
271
+ "RW",
272
+ "BL",
273
+ "KN",
274
+ "LC",
275
+ "PM",
276
+ "VC",
277
+ "WS",
278
+ "SA",
279
+ "SN",
280
+ "RS",
281
+ "SG",
282
+ "SK",
283
+ "SI",
284
+ "ZA",
285
+ "ES",
286
+ "LK",
287
+ "SR",
288
+ "SE",
289
+ "CH",
290
+ "SY",
291
+ "TW",
292
+ "TZ",
293
+ "TH",
294
+ "TG",
295
+ "TT",
296
+ "TN",
297
+ "TR",
298
+ "TC",
299
+ "UG",
300
+ "UA",
301
+ "AE",
302
+ "GB",
303
+ "UY",
304
+ "UZ",
305
+ "VU",
306
+ "VE",
307
+ "VN",
308
+ "VI",
309
+ "WF",
310
+ "YE",
311
+ "ZW",
312
+ }
313
+
314
+ # For display purposes (sorted list)
315
+ COUNTRY_CODES_LIST = sorted(VALID_COUNTRY_CODES)
316
+
317
+ PROGRAMMER_UTILITY_LOGGER = logging.getLogger("programmer-utility")
318
+
319
+
320
+ def get_lifecycle_state_reg_class(device_type):
321
+ """
322
+ Get the appropriate LifeCycleStateReg class based on device type.
323
+
324
+ This function selects the correct CFGAP register implementation for each
325
+ device variant (CC35XX, CC35XXE, CC37XXR, etc.), ensuring correct field
326
+ parsing for device-specific register layouts.
327
+
328
+ Args:
329
+ device_type (str): Device type constant (CC35XX, CC35XXE, CC37XXR, CC36XXR)
330
+
331
+ Returns:
332
+ class: Device-appropriate LifeCycleStateReg class
333
+ """
334
+ if device_type == CC35XXE:
335
+ return CC35xxELifeCycleStateReg
336
+ elif device_type in [CC37XXR, CC36XXR]:
337
+ return CC3xxxRLifeCycleStateReg
338
+ else:
339
+ # Default to CC35XX for CC35XX and any other devices
340
+ return LifeCycleStateReg
341
+
342
+
343
+ class ProgrammerUtility:
344
+ def __init__(
345
+ self, programmer_artifacts_path, fib_artifacts_path, is_internal_release=True
346
+ ):
347
+ # Convert to Path object if it's not already
348
+ self.is_internal_release = is_internal_release
349
+
350
+ self.programmer_path = pathlib.Path(programmer_artifacts_path)
351
+ self.fib_utility = FIBUtility(
352
+ fib_artifacts_path, is_internal_release=self.is_internal_release
353
+ )
354
+
355
+ def get_device_type_from_family(self, device_family):
356
+ """Map device family string to device type constant.
357
+
358
+ Args:
359
+ device_family: Device family string (e.g., "CC35X1E", "CC35X1", "CC370XR", "CC360XR", "CC33XX")
360
+
361
+ Returns:
362
+ Device type constant (CC35XXE, CC35XX, CC37XXR, CC36XXR or CC33XX)
363
+ """
364
+ device_family_upper = device_family.upper()
365
+ if device_family_upper in [CC35X1E, CC35XXE]:
366
+ return CC35XXE
367
+ elif device_family_upper in [CC370XR, CC37XXR]:
368
+ return CC37XXR
369
+ elif device_family_upper in [CC360XR, CC36XXR]:
370
+ # TODO: temporary - CC360XR currently not supported
371
+ return CC36XXR
372
+ elif device_family_upper in [CC33XX]:
373
+ return CC33XX
374
+ elif device_family_upper in [CC35X1, CC35XX]:
375
+ return CC35XX
376
+ else:
377
+ return None
378
+
379
+ def get_programmer_dir_path(self):
380
+ return str(self.programmer_path)
381
+
382
+ def get_tbl_dir_path(self):
383
+ binaries_path = self.get_binaries_folder()
384
+ return join(binaries_path, PROGRAMMER_TBL_DIR)
385
+
386
+ def get_wsoc_dir_path(self):
387
+ binaries_path = self.get_binaries_folder()
388
+ return join(binaries_path, PROGRAMMER_WSOC_DIR)
389
+
390
+ def get_programming_tmp_folder(self):
391
+ tmp_dir = self.get_tmp_folder()
392
+ prog_tmp_folder = join(tmp_dir, PROGRAMMING_FILES_FOLDER_NAME)
393
+ makedirs(prog_tmp_folder, exist_ok=True)
394
+
395
+ return prog_tmp_folder
396
+
397
+ def get_tmp_folder(self):
398
+ programmer_dir = self.get_programmer_dir_path()
399
+ tmp_folder = join(programmer_dir, "tmp")
400
+ makedirs(tmp_folder, exist_ok=True)
401
+
402
+ return tmp_folder
403
+
404
+ def get_prog_tools_settings(self):
405
+ prog_tmp_folder = self.get_programming_tmp_folder()
406
+ return join(prog_tmp_folder, TOOL_SETTINGS)
407
+
408
+ def get_tool_settings_template(self):
409
+ return join(self.get_config_folder(), TOOL_SETTINGS_TEMPLATE)
410
+
411
+ def move_flash_json_files(
412
+ self,
413
+ flash_type_files_folder,
414
+ flash_device,
415
+ enable_ota=False,
416
+ ):
417
+ tmp_dir = self.get_programming_tmp_folder()
418
+
419
+ if flash_device in SUPPORTED_FLASH_TYPES:
420
+ jsons_flash_type_files_folder = join(flash_type_files_folder, flash_device)
421
+
422
+ xspi_json = self.copy_file(
423
+ jsons_flash_type_files_folder, tmp_dir, XSPI_JSON_NAME
424
+ )
425
+ otfde_json = self.copy_file(
426
+ jsons_flash_type_files_folder, tmp_dir, OTFDE_JSON_NAME
427
+ )
428
+ ext_mem_json = self.copy_file(
429
+ jsons_flash_type_files_folder, tmp_dir, EXTMEM_JSON_NAME
430
+ )
431
+
432
+ return xspi_json, otfde_json, ext_mem_json
433
+
434
+ def get_mod_flash_jsons(self):
435
+ tmp_dir = self.get_programming_tmp_folder()
436
+
437
+ xspi_json = join(tmp_dir, XSPI_JSON_MOD_NAME)
438
+ otfde_json = join(tmp_dir, OTFDE_JSON_MOD_NAME)
439
+ ext_mem_json = join(tmp_dir, EXTMEM_JSON_MOD_NAME)
440
+
441
+ return xspi_json, otfde_json, ext_mem_json
442
+
443
+ def get_act_n_init_tmp_folder(self):
444
+ tmp_folder = self.get_tmp_folder()
445
+ act_tmp_folder = join(tmp_folder, ACTIVATION_FILES_FOLDER_NAME)
446
+ makedirs(act_tmp_folder, exist_ok=True)
447
+
448
+ return act_tmp_folder
449
+
450
+ def get_flash_jsons(
451
+ self,
452
+ flash_type_files_folder,
453
+ flash_device,
454
+ tmp_dir_path,
455
+ psram_type=PsramOption.NO_PSRAM.name,
456
+ enable_ota=False,
457
+ ):
458
+ if flash_device in SUPPORTED_FLASH_TYPES:
459
+ jsons_flash_type_files_folder = join(flash_type_files_folder, flash_device)
460
+
461
+ xspi_json = join(jsons_flash_type_files_folder, XSPI_JSON_NAME)
462
+ otfde_json = join(jsons_flash_type_files_folder, OTFDE_JSON_NAME)
463
+ ext_mem_json = join(jsons_flash_type_files_folder, EXTMEM_JSON_NAME)
464
+
465
+ updated_xspi = self.update_xspi_to_psram_type(
466
+ xspi_json_path=xspi_json,
467
+ psram_type=psram_type,
468
+ output_dir_path=tmp_dir_path,
469
+ )
470
+
471
+ return updated_xspi, otfde_json, ext_mem_json
472
+
473
+ def get_default_tbl_img(self, device_type):
474
+ tbl_folder_name = self.get_tbl_dir_path()
475
+ if device_type == CC35XXE:
476
+ path_to_tbl = join(tbl_folder_name, TI_BOOTLOADER_CC35XXE)
477
+ elif device_type == CC37XXR:
478
+ path_to_tbl = join(tbl_folder_name, TI_BOOTLOADER_CC37XXR)
479
+ else:
480
+ path_to_tbl = join(tbl_folder_name, TI_BOOTLOADER_CC35XX)
481
+ return path_to_tbl
482
+
483
+ def get_default_vendor_app(self, device_type, enable_ota=False):
484
+ vendor_folder = join(self.get_binaries_folder(), VENDOR_APP_FOLDER)
485
+
486
+ # Map device type to vendor app filename
487
+ vendor_app_map = {
488
+ CC35XX: VENDOR_OUT_NAME_CC35XX,
489
+ CC35XXE: VENDOR_OUT_NAME_CC35XXE,
490
+ CC37XXR: VENDOR_OUT_NAME_CC37XXR,
491
+ }
492
+
493
+ vendor_app_name = vendor_app_map.get(device_type)
494
+
495
+ # For advanced devices (CC35XXE and CC37XXR), use OTA-specific folders
496
+ if device_type in (CC35XXE, CC37XXR):
497
+ source_dir = "vendor_app_ota" if enable_ota else "vendor_app_no_ota"
498
+ return join(vendor_folder, source_dir, vendor_app_name)
499
+
500
+ # For CC35XX, use flat structure
501
+ return join(vendor_folder, vendor_app_name)
502
+
503
+ def get_default_conf_bin(self, device_type):
504
+ wsoc_folder = self.get_wsoc_dir_path()
505
+
506
+ conf_map = {
507
+ CC35XXE: CONF_BIN_NAME_CC35XXE,
508
+ CC37XXR: CONF_BIN_NAME_CC37XXR,
509
+ }
510
+
511
+ conf_name = conf_map.get(device_type, None)
512
+ if conf_name is None:
513
+ return None
514
+ return join(wsoc_folder, conf_name)
515
+
516
+ return path_to_conf_bin
517
+
518
+ def get_default_prog(self, device_type):
519
+ tbl_folder_name = self.get_tbl_dir_path()
520
+ if device_type == CC35XXE:
521
+ path_to_ti_prog = join(tbl_folder_name, TI_PROGRAMMER_CC35XXE)
522
+ elif device_type == CC37XXR:
523
+ path_to_ti_prog = join(tbl_folder_name, TI_PROGRAMMER_CC37XXR)
524
+ else:
525
+ path_to_ti_prog = join(tbl_folder_name, TI_PROGRAMMER_CC35XX)
526
+ return path_to_ti_prog
527
+
528
+ def get_default_activator(self, device_type):
529
+ tbl_folder_name = self.get_tbl_dir_path()
530
+ if device_type == CC35XXE:
531
+ path_to_tac = join(tbl_folder_name, TI_ACTIVATION_CC35XXE)
532
+ elif device_type == CC37XXR:
533
+ path_to_tac = join(tbl_folder_name, TI_ACTIVATION_CC37XXR)
534
+ else:
535
+ path_to_tac = join(tbl_folder_name, TI_ACTIVATION_CC35XX)
536
+ return path_to_tac
537
+
538
+ def get_default_wsoc(self, device_type):
539
+ wsoc_folder = self.get_wsoc_dir_path()
540
+
541
+ # Map device type to WSOC firmware filename
542
+ wsoc_map = {
543
+ CC35XX: WSOC_NAME_CC35XX,
544
+ CC35XXE: WSOC_NAME_CC35XXE,
545
+ CC37XXR: WSOC_NAME_CC37XXR,
546
+ }
547
+
548
+ wsoc_name = wsoc_map.get(device_type)
549
+ if wsoc_name is None:
550
+ return None
551
+ return join(wsoc_folder, wsoc_name)
552
+
553
+ def get_action_params_template(self):
554
+ jsons_folder = self.get_config_folder()
555
+ action_params_template = join(jsons_folder, ACTION_PARAMS_TEMPLATE)
556
+
557
+ return action_params_template
558
+
559
+ def get_binaries_folder(self):
560
+ programmer_dir = self.get_programmer_dir_path()
561
+ binaries_folder = join(programmer_dir, BINARIES_FOLDER_NAME)
562
+
563
+ return binaries_folder
564
+
565
+ def get_config_folder(self):
566
+ programmer_dir = self.get_programmer_dir_path()
567
+ jsons_folder = join(programmer_dir, CONFIG_FOLDER_NAME)
568
+
569
+ return jsons_folder
570
+
571
+ def get_fuses_params_template(self):
572
+ fuses_param_json_template_path = (
573
+ pathlib.Path(self.get_config_folder()) / FUSE_PROG_NAME_CC35XXE_TEMPLATE
574
+ )
575
+ with open(fuses_param_json_template_path, "r") as json_file:
576
+ fuses_dict = json.loads(json_file.read())
577
+
578
+ return fuses_dict
579
+
580
+ def get_fuse_json_name(self, device_type):
581
+
582
+ fuses_param_map = {
583
+ CC35XX: FUSE_PROG_NAME_CC35XX,
584
+ CC35XXE: FUSE_PROG_NAME_CC35XXE,
585
+ CC37XXR: FUSE_PROG_NAME_CC37XXR,
586
+ }
587
+
588
+ fuses_param_name = fuses_param_map.get(device_type, None)
589
+ if fuses_param_name is None:
590
+ return None
591
+
592
+ return fuses_param_name
593
+
594
+ def create_new_fuses_json_file(self, dict_updated_data, full_path_to_save):
595
+ """
596
+ Creates a JSON file from the provided dictionary data.
597
+
598
+ Args:
599
+ dict_updated_data (dict): The dictionary containing the updated fuse parameters.
600
+ full_path_to_save (str): The full path to save the JSON file.
601
+ """
602
+ fuses_dict = self.get_fuses_params_template()
603
+
604
+ # Iterate through the updated dictionary and update existing keys in the template
605
+ for key, value in dict_updated_data.items():
606
+ if key in fuses_dict:
607
+ fuses_dict[key] = value
608
+
609
+ self.dict_to_json(fuses_dict, full_path_to_save)
610
+
611
+ def copy_file(self, source_path, destination_folder, filename):
612
+ """Copy PS image to destination folder"""
613
+ try:
614
+ # Ensure destination folder exists
615
+ makedirs(destination_folder, exist_ok=True)
616
+
617
+ # Build full destination path
618
+ destination_path = join(destination_folder, filename)
619
+ source_path = join(source_path, filename)
620
+
621
+ # Copy file
622
+ shutil.copy(source_path, destination_path)
623
+
624
+ return destination_path
625
+ except Exception as e:
626
+ print(f"Error copying file: {e}")
627
+ return None
628
+
629
+ def get_container_version(self, path_to_container):
630
+ container_struct = Struct(
631
+ # header
632
+ "magic_number" / Int32ul,
633
+ "record_type" / Int16ul,
634
+ "reserved" / Padding(2, b"\xff"),
635
+ "h_len" / Int32ul,
636
+ # payload
637
+ "container_format_version" / Int8ul,
638
+ "container_type" / Int16ul,
639
+ "container_type_format" / Int8ul,
640
+ "major_version" / Int8ul,
641
+ "minor_version" / Int8ul,
642
+ "rest" / GreedyBytes,
643
+ )
644
+
645
+ with open(path_to_container, "rb") as f:
646
+ container_cont = f.read()
647
+
648
+ parsed_container = container_struct.parse(container_cont)
649
+
650
+ return (parsed_container.major_version, parsed_container.minor_version)
651
+
652
+ def file_path_exist(self, path, image_name):
653
+ if path is None or path == "":
654
+ error_message = f"Path to {image_name} not exist"
655
+ elif not isfile(path):
656
+ error_message = f"Path to {image_name} is invalid"
657
+ else:
658
+ error_message = None
659
+
660
+ return error_message
661
+
662
+ @classmethod
663
+ def fix_path(cls, path, alt_base_path=None):
664
+ fixed_path = path
665
+
666
+ if not isabs(path):
667
+ cwd = getcwd()
668
+ fixed_path = normpath(normcase(join(cwd, path)))
669
+ if exists(fixed_path):
670
+ return fixed_path
671
+ PROGRAMMER_UTILITY_LOGGER.debug(
672
+ f"Unable to locate '{path}' relative to CWD ({cwd}). Continuing search..."
673
+ )
674
+
675
+ if alt_base_path is not None:
676
+ fixed_path = normpath(normcase(join(alt_base_path, path)))
677
+ if exists(fixed_path):
678
+ return fixed_path
679
+ PROGRAMMER_UTILITY_LOGGER.debug(
680
+ f"Unable to locate '{path}' relative to tool_settings.json ({alt_base_path}). Continuing search..."
681
+ )
682
+
683
+ if getattr(sys, "frozen", False):
684
+ if isabs(executable):
685
+ fixed_path = join(dirname(executable), path)
686
+ else:
687
+ fixed_path = abspath(path)
688
+
689
+ return fixed_path
690
+
691
+ def build_programming_image(
692
+ self,
693
+ version,
694
+ pub_key_path,
695
+ activation_type,
696
+ dir_out_path,
697
+ fuses_programming_instructions,
698
+ flash_discovery_config_xspi,
699
+ flash_discovery_config_otfde,
700
+ flash_discovery_config_ext_mem,
701
+ device,
702
+ signing_module=None,
703
+ logging_level=logging.DEBUG,
704
+ signing_mode="classic_rot",
705
+ ):
706
+ # combin xspi json and otfde json to one file
707
+ flash_discovery_config_path = None
708
+ dir_out_path = pathlib.Path(dir_out_path)
709
+ if flash_discovery_config_xspi:
710
+ flash_discovery_config_path = (
711
+ dir_out_path / "flash_discovery_configuration.json"
712
+ )
713
+
714
+ with open(flash_discovery_config_xspi, "r") as f:
715
+ xspi_data = json.load(f)
716
+
717
+ with open(flash_discovery_config_otfde, "r") as f:
718
+ otfde_data = json.load(f)
719
+
720
+ with open(flash_discovery_config_ext_mem, "r") as f:
721
+ ext_mem_data = json.load(f)
722
+
723
+ with open(flash_discovery_config_path, "w") as f:
724
+ xspi_data.update(otfde_data)
725
+ xspi_data.update({"otfde_external_memory_map": ext_mem_data})
726
+ f.write(json.dumps(xspi_data, indent=4))
727
+
728
+ tmp_fuses_programming_instructions = fuses_programming_instructions
729
+ if activation_type is not None:
730
+ tmp_fuses_programming_instructions = (
731
+ self.fib_utility.update_rot_in_fuses_for_programming_instructions(
732
+ fuses_programming_instructions,
733
+ dir_out_path,
734
+ activation_type,
735
+ signing_module,
736
+ signing_mode,
737
+ )
738
+ )
739
+
740
+ # build image
741
+ prog_inst_img_builder = ProgInstImgBuilder(
742
+ flash_fuse_programming_inst_json=tmp_fuses_programming_instructions,
743
+ flash_discovery_conf_file=flash_discovery_config_path,
744
+ version=version,
745
+ abs_dir_out_path=dir_out_path,
746
+ pub_key_path=pub_key_path,
747
+ device=device,
748
+ verbose=False,
749
+ signing_mode=signing_mode,
750
+ )
751
+ if not prog_inst_img_builder.build():
752
+ return None, None
753
+
754
+ # save image with dummy sign
755
+ programming_unsign_image = (
756
+ prog_inst_img_builder.abs_dir_out_path
757
+ / "programming_instructions_image.unsign.bin"
758
+ )
759
+ with open(programming_unsign_image, "wb") as fb:
760
+ fb.write(prog_inst_img_builder.programming_image_gpe_content_raw)
761
+ PROGRAMMER_UTILITY_LOGGER.log(
762
+ logging_level,
763
+ f"Saved programming instructions image with dummy signature bin file at {programming_unsign_image}",
764
+ )
765
+
766
+ # save image to sign
767
+ programming_image_to_sign_out_path = (
768
+ prog_inst_img_builder.abs_dir_out_path
769
+ / "programming_instructions_image.forsign.bin"
770
+ )
771
+ with open(programming_image_to_sign_out_path, "wb") as fb:
772
+ fb.write(prog_inst_img_builder.programming_image_to_sign_gpe_content_raw)
773
+ PROGRAMMER_UTILITY_LOGGER.log(
774
+ logging_level,
775
+ f"Saved part of programming instructions image bin file to sign at {programming_image_to_sign_out_path}",
776
+ )
777
+
778
+ if isfile(tmp_fuses_programming_instructions):
779
+ remove(tmp_fuses_programming_instructions)
780
+
781
+ return programming_unsign_image, programming_image_to_sign_out_path
782
+
783
+ # use always classic_rot when calling this function - multi key chaining not relevant to programming image
784
+ def sign_programming_image(
785
+ self,
786
+ programming_unsign_image,
787
+ programming_forsign_image,
788
+ dir_out_path,
789
+ private_key=None,
790
+ public_key=None,
791
+ activation_type=None,
792
+ signing_module=None,
793
+ signature_bin=None,
794
+ logging_level=logging.DEBUG,
795
+ signing_mode="classic_rot",
796
+ magic=KEY_INFO_MAGIC_DEFAULT,
797
+ key_info_version=KEY_INFO_VERSION_DEFAULT,
798
+ svn=KEY_INFO_SVN_DEFAULT,
799
+ svn_policy=KEY_INFO_SVN_POLICY_DEFAULT,
800
+ ):
801
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
802
+ basename_programming_unsign_image = str(
803
+ programming_unsign_image.stem.split(".")[0]
804
+ )
805
+
806
+ if activation_type is not None:
807
+ private_key = None
808
+ public_key, signature_bin = self.fib_utility.get_public_key_and_signature(
809
+ activation_type,
810
+ signing_module,
811
+ programming_forsign_image,
812
+ dir_out_path,
813
+ basename_programming_unsign_image,
814
+ signing_mode,
815
+ )
816
+
817
+ # Build KEY_INFO_TLV for multi-level mode (before calling sign_gpe)
818
+ key_info_tlv_bin = None
819
+ if signing_mode == "multi_level" and signing_module:
820
+ # Build KEY_INFO_TLV on-the-fly or get pre-built from signing module
821
+ key_info_tlv_bin = self.fib_utility.get_key_info_tlv(
822
+ signing_module_path=signing_module,
823
+ magic=magic,
824
+ key_info_version=key_info_version,
825
+ svn_policy_str=svn_policy,
826
+ svn=svn,
827
+ verbose=False,
828
+ )
829
+ if key_info_tlv_bin is None:
830
+ PROGRAMMER_UTILITY_LOGGER.log(
831
+ logging_level,
832
+ "Failed to build/get KEY_INFO_TLV for multi-level signing mode",
833
+ )
834
+ return None
835
+
836
+ # sign programming image
837
+ prog_image = GPEBuilder.sign_gpe(
838
+ unsign_image=programming_unsign_image,
839
+ private_key=private_key,
840
+ signature_bin=signature_bin,
841
+ public_key=public_key,
842
+ log_level=logging.DEBUG,
843
+ signing_mode=signing_mode,
844
+ key_info_tlv_bin=key_info_tlv_bin,
845
+ )
846
+
847
+ # save sign programming image file
848
+ programming_sign_image = dir_out_path / (
849
+ basename_programming_unsign_image + ".sign.bin"
850
+ )
851
+ with open(programming_sign_image, "wb") as f:
852
+ f.write(prog_image)
853
+
854
+ PROGRAMMER_UTILITY_LOGGER.log(
855
+ logging_level,
856
+ f"Saved sign programming image bin file at {programming_sign_image}",
857
+ )
858
+
859
+ return programming_sign_image
860
+
861
+ def build_vendor_image(
862
+ self,
863
+ vendor_out_file,
864
+ conf_bin_file,
865
+ dir_out_path,
866
+ device,
867
+ pub_key_path=None,
868
+ version="0.0.1.0",
869
+ logging_level=logging.DEBUG,
870
+ slot=None,
871
+ signing_mode="classic_rot",
872
+ ):
873
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
874
+ vendor_image_builder = VendorImageBuilder(
875
+ vendor_out_file_path=vendor_out_file,
876
+ conf_bin_file=conf_bin_file,
877
+ abs_dir_out_path=dir_out_path,
878
+ version=version,
879
+ pub_key_path=pub_key_path,
880
+ device=device,
881
+ verbose=False,
882
+ signing_mode=signing_mode,
883
+ )
884
+
885
+ if vendor_image_builder.build():
886
+ base_file_name = "vendor_image"
887
+ if slot is not None:
888
+ base_file_name += f"_slot{slot}"
889
+
890
+ # save image with dummy sign
891
+ vendor_image_out_path_unsign = (
892
+ vendor_image_builder.abs_dir_out_path / f"{base_file_name}.unsign.bin"
893
+ )
894
+ with open(vendor_image_out_path_unsign, "wb") as fb:
895
+ fb.write(vendor_image_builder.vendor_image_gpe_content_raw)
896
+ PROGRAMMER_UTILITY_LOGGER.log(
897
+ logging_level,
898
+ f"Saved vendor image with dummy signature bin file at {vendor_image_out_path_unsign}",
899
+ )
900
+
901
+ # save image to sign
902
+ vendor_image_out_path_for_sign = (
903
+ vendor_image_builder.abs_dir_out_path / f"{base_file_name}.forsign.bin"
904
+ )
905
+ with open(vendor_image_out_path_for_sign, "wb") as fb:
906
+ fb.write(vendor_image_builder.vendor_image_to_sign_gpe_content_raw)
907
+ PROGRAMMER_UTILITY_LOGGER.log(
908
+ logging_level,
909
+ f"Saved part of vendor image bin file to sign at {vendor_image_out_path_for_sign}",
910
+ )
911
+
912
+ return vendor_image_out_path_unsign, vendor_image_out_path_for_sign
913
+
914
+ def sign_vendor_image(
915
+ self,
916
+ vendor_unsign_image,
917
+ vendor_forsign_image,
918
+ dir_out_path,
919
+ device,
920
+ private_key=None,
921
+ public_key=None,
922
+ activation_type=None,
923
+ signing_module=None,
924
+ signature_bin=None,
925
+ logging_level=logging.DEBUG,
926
+ signing_mode="classic_rot",
927
+ magic=KEY_INFO_MAGIC_DEFAULT,
928
+ key_info_version=KEY_INFO_VERSION_DEFAULT,
929
+ svn=KEY_INFO_SVN_DEFAULT,
930
+ svn_policy=KEY_INFO_SVN_POLICY_DEFAULT,
931
+ ):
932
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
933
+ basename_vendor_unsign_image = str(
934
+ pathlib.Path(vendor_unsign_image).stem.split(".")[0]
935
+ )
936
+
937
+ if activation_type is not None:
938
+ private_key = None
939
+ public_key, signature_bin = self.fib_utility.get_public_key_and_signature(
940
+ activation_type,
941
+ signing_module,
942
+ vendor_forsign_image,
943
+ dir_out_path,
944
+ basename_vendor_unsign_image,
945
+ signing_mode,
946
+ )
947
+
948
+ # Build KEY_INFO_TLV for multi-level mode (before calling sign_gpe)
949
+ key_info_tlv_bin = None
950
+ if signing_mode == "multi_level" and signing_module:
951
+ # Build KEY_INFO_TLV on-the-fly or get pre-built from signing module
952
+ key_info_tlv_bin = self.fib_utility.get_key_info_tlv(
953
+ signing_module_path=signing_module,
954
+ magic=magic,
955
+ key_info_version=key_info_version,
956
+ svn_policy_str=svn_policy,
957
+ svn=svn,
958
+ verbose=False,
959
+ )
960
+ if key_info_tlv_bin is None:
961
+ PROGRAMMER_UTILITY_LOGGER.log(
962
+ logging_level,
963
+ "Failed to build/get KEY_INFO_TLV for multi-level signing mode",
964
+ )
965
+ return None
966
+
967
+ # sign vendor image
968
+ vendor_image = GPEBuilder.sign_gpe(
969
+ unsign_image=vendor_unsign_image,
970
+ private_key=private_key,
971
+ signature_bin=signature_bin,
972
+ public_key=public_key,
973
+ log_level=False,
974
+ device=device,
975
+ signing_mode=signing_mode,
976
+ key_info_tlv_bin=key_info_tlv_bin,
977
+ )
978
+
979
+ # save sign vendor image file
980
+ vendor_image_out_path = dir_out_path / (
981
+ basename_vendor_unsign_image + ".sign.bin"
982
+ )
983
+ with open(vendor_image_out_path, "wb") as f:
984
+ f.write(vendor_image)
985
+ PROGRAMMER_UTILITY_LOGGER.log(
986
+ logging_level,
987
+ f"Saved sign vendor image bin file at {vendor_image_out_path}",
988
+ )
989
+
990
+ return vendor_image_out_path
991
+
992
+ def build_action_request_image(
993
+ self,
994
+ params_json,
995
+ type,
996
+ dir_out_path,
997
+ device,
998
+ logging_level=logging.DEBUG,
999
+ ):
1000
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
1001
+ # extract the neccesery data by type
1002
+ with open(params_json, "r") as f:
1003
+ action_req_param = json.load(f)
1004
+
1005
+ action_req_param = action_req_param[type]
1006
+ # make sure public key in action params is ""
1007
+ action_req_param["pub_key"] = ""
1008
+
1009
+ # build action request
1010
+ action_req_builder = ActionReqBuilder.from_dict(
1011
+ action_req_dict=action_req_param,
1012
+ type=type,
1013
+ device_type=device,
1014
+ verbose=False,
1015
+ )
1016
+ action_req_builder.build()
1017
+
1018
+ # save action req file with dummy sig
1019
+ action_req_out_path = dir_out_path / f"{type}_action_request.unsign.bin"
1020
+ with open(action_req_out_path, "wb") as f:
1021
+ f.write(action_req_builder.action_req_content_raw)
1022
+ PROGRAMMER_UTILITY_LOGGER.log(
1023
+ logging_level, f"Saved action request bin file at {action_req_out_path}"
1024
+ )
1025
+
1026
+ # save action req to sign
1027
+ action_req_to_sign_out_path = (
1028
+ dir_out_path / f"{type}_action_request.forsign.bin"
1029
+ )
1030
+ with open(action_req_to_sign_out_path, "wb") as f:
1031
+ f.write(action_req_builder.action_req_wo_sign_content_raw)
1032
+ PROGRAMMER_UTILITY_LOGGER.log(
1033
+ logging_level,
1034
+ f"Saved action request to sign bin file at {action_req_to_sign_out_path}",
1035
+ )
1036
+
1037
+ return action_req_out_path, action_req_to_sign_out_path
1038
+
1039
+ def sign_action_request_image(
1040
+ self,
1041
+ unsign_request,
1042
+ forsign_request,
1043
+ dir_out_path,
1044
+ private_key=None,
1045
+ public_key=None,
1046
+ activation_type=None,
1047
+ signing_module=None,
1048
+ udid="0000-0000-0000-0000",
1049
+ signature_bin=None,
1050
+ logging_level=logging.DEBUG,
1051
+ signing_mode="classic_rot",
1052
+ ):
1053
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
1054
+ basename_unsign_request = str(pathlib.Path(unsign_request).stem.split(".")[0])
1055
+
1056
+ if activation_type is not None:
1057
+ private_key = None
1058
+ public_key, signature_bin = self.fib_utility.get_public_key_and_signature(
1059
+ activation_type,
1060
+ signing_module,
1061
+ forsign_request,
1062
+ dir_out_path,
1063
+ basename_unsign_request,
1064
+ signing_mode,
1065
+ )
1066
+
1067
+ # sign action request
1068
+ sign_action_request, type = ActionReqBuilder.sign_action_req(
1069
+ unsign_request=unsign_request,
1070
+ private_key=private_key,
1071
+ signature_bin=signature_bin,
1072
+ public_key=public_key,
1073
+ udid=udid,
1074
+ log_level=False,
1075
+ )
1076
+
1077
+ # save sign action req file
1078
+ action_req_out_path = dir_out_path / (basename_unsign_request + ".sign.bin")
1079
+ with open(action_req_out_path, "wb") as f:
1080
+ f.write(sign_action_request)
1081
+ PROGRAMMER_UTILITY_LOGGER.log(
1082
+ logging_level,
1083
+ f"Saved sign action request bin file at {action_req_out_path}",
1084
+ )
1085
+
1086
+ return action_req_out_path
1087
+
1088
+ def get_invalid_size_flags(
1089
+ self,
1090
+ image_paths,
1091
+ bl2_size_kb,
1092
+ wsoc_size_kb,
1093
+ vendor_size_kb,
1094
+ boot_sector_size_kb,
1095
+ vendor_bl3_size_kb=None,
1096
+ protected_storage_size_kb=None,
1097
+ ):
1098
+ """Returns a dict mapping each slot key whose built image exceeds its
1099
+ allocated size to the corresponding size-validation error message
1100
+ (via fib_utility.validate_images_sizes)."""
1101
+ _, failed_images = self.fib_utility.validate_images_sizes(
1102
+ primary_tbl_path=image_paths.get("primary_tbl"),
1103
+ secondary_tbl_path=image_paths.get("secondary_tbl"),
1104
+ tbl_size_kb=bl2_size_kb,
1105
+ primary_wsoc_path=image_paths.get("primary_ti_wsoc"),
1106
+ secondary_wsoc_path=image_paths.get("secondary_ti_wsoc"),
1107
+ wsoc_size_kb=wsoc_size_kb,
1108
+ primary_vendor_path=image_paths.get("primary_vendor_image"),
1109
+ secondary_vendor_path=image_paths.get("secondary_vendor_image"),
1110
+ vendor_size_kb=vendor_size_kb,
1111
+ primary_vendor_bl3_path=image_paths.get("primary_vendor_sbl"),
1112
+ secondary_vendor_bl3_path=image_paths.get("secondary_vendor_sbl"),
1113
+ vendor_bl3_size_kb=vendor_bl3_size_kb,
1114
+ boot_sector_path=image_paths.get("boot_sector"),
1115
+ boot_sector_size_kb=boot_sector_size_kb,
1116
+ protected_storage_path=image_paths.get("protected_storage"),
1117
+ protected_storage_size_kb=protected_storage_size_kb,
1118
+ )
1119
+
1120
+ failed_messages = {
1121
+ image_path: self.fib_utility.get_size_validation_error_msg(actual_kb, limit_kb)
1122
+ for _, image_path, actual_kb, limit_kb in failed_images
1123
+ }
1124
+ return {
1125
+ slot: failed_messages[path]
1126
+ for slot, path in image_paths.items()
1127
+ if path in failed_messages
1128
+ }
1129
+
1130
+ def get_connection_info(self):
1131
+ """Returns the last-saved connection info dict, or None if none was saved yet."""
1132
+ connection_info_file = join(self.get_tmp_folder(), CONNECTION_INFO_FILE_NAME)
1133
+ if not isfile(connection_info_file):
1134
+ return None
1135
+ with open(connection_info_file, "r") as f:
1136
+ return json.load(f)
1137
+
1138
+ def update_connection_info(self, connection_info_data):
1139
+ connection_info_file = join(self.get_tmp_folder(), CONNECTION_INFO_FILE_NAME)
1140
+ with open(connection_info_file, "w") as f:
1141
+ json.dump(connection_info_data, f, indent=4)
1142
+ return connection_info_data
1143
+
1144
+ def build_tbl_image(
1145
+ self,
1146
+ ti_sign_container,
1147
+ dir_out_path,
1148
+ device,
1149
+ logging_level=logging.DEBUG,
1150
+ slot=None,
1151
+ ):
1152
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
1153
+
1154
+ tbl_image_builder = TBLImageBuilder(
1155
+ ti_container_file_path=ti_sign_container,
1156
+ abs_dir_out_path=dir_out_path,
1157
+ device=device,
1158
+ verbose=False,
1159
+ )
1160
+ tbl_image_builder.build()
1161
+
1162
+ base_file_name = "tbl_image"
1163
+ if slot is not None:
1164
+ base_file_name += f"_slot{slot}"
1165
+
1166
+ # save image
1167
+ tbl_image_out_path = (
1168
+ pathlib.Path(tbl_image_builder.abs_dir_out_path) / f"{base_file_name}.bin"
1169
+ )
1170
+ with open(tbl_image_out_path, "wb") as fb:
1171
+ fb.write(tbl_image_builder.ti_image_gpe_content_raw)
1172
+ PROGRAMMER_UTILITY_LOGGER.log(
1173
+ logging_level,
1174
+ f"Saved ti bootloader image GPE bin file at {tbl_image_out_path}",
1175
+ )
1176
+
1177
+ return tbl_image_out_path
1178
+
1179
+ def build_vendor_bl3_image(
1180
+ self,
1181
+ bl3_bin_path,
1182
+ dir_out_path,
1183
+ device,
1184
+ version,
1185
+ pub_key_path=None,
1186
+ slot=None,
1187
+ signing_mode="classic_rot",
1188
+ logging_level=logging.DEBUG,
1189
+ ):
1190
+ """Build vendor BL3 image from binary file
1191
+
1192
+ Args:
1193
+ bl3_bin_path: Path to vendor BL3 binary file
1194
+ dir_out_path: Output directory path
1195
+ device: Device type (CC35XX, CC35XXE, CC37XXR)
1196
+ version: Image version (e.g., "1.0.0.0")
1197
+ pub_key_path: Path to public key for VA TLV (optional)
1198
+ slot: Target slot number (0 or 1), None for auto-naming
1199
+ signing_mode: Signing mode ("classic_rot" or "multi_level")
1200
+ logging_level: Logging level
1201
+
1202
+ Returns:
1203
+ Path to built vendor BL3 image
1204
+ """
1205
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
1206
+
1207
+ # Build vendor BL3 image
1208
+ bl3_builder = BL3ImageBuilder(
1209
+ bl3_out_file_path=bl3_bin_path,
1210
+ abs_dir_out_path=str(dir_out_path),
1211
+ version=version,
1212
+ pub_key_path=pub_key_path,
1213
+ device=device,
1214
+ verbose=False,
1215
+ signing_mode=signing_mode,
1216
+ )
1217
+
1218
+ if not bl3_builder.build():
1219
+ raise Exception("Failed to build vendor BL3 image")
1220
+
1221
+ base_file_name = "vendor_bl3_image"
1222
+ if slot is not None:
1223
+ base_file_name += f"_slot{slot}"
1224
+
1225
+ # save image with dummy sign
1226
+ vendor_bl3_out_path_unsign = dir_out_path / f"{base_file_name}.unsign.bin"
1227
+ with open(vendor_bl3_out_path_unsign, "wb") as fb:
1228
+ fb.write(bl3_builder.bl3_image_gpe_content_raw)
1229
+ PROGRAMMER_UTILITY_LOGGER.log(
1230
+ logging_level,
1231
+ f"Saved vendor BL3 image with dummy signature bin file at {vendor_bl3_out_path_unsign}",
1232
+ )
1233
+
1234
+ # save image to sign
1235
+ vendor_bl3_out_path_for_sign = dir_out_path / f"{base_file_name}.forsign.bin"
1236
+ with open(vendor_bl3_out_path_for_sign, "wb") as fb:
1237
+ fb.write(bl3_builder.bl3_image_to_sign_gpe_content_raw)
1238
+ PROGRAMMER_UTILITY_LOGGER.log(
1239
+ logging_level,
1240
+ f"Saved part of vendor BL3 image bin file to sign at {vendor_bl3_out_path_for_sign}",
1241
+ )
1242
+
1243
+ return vendor_bl3_out_path_unsign, vendor_bl3_out_path_for_sign
1244
+
1245
+ def sign_vendor_bl3_image(
1246
+ self,
1247
+ vendor_bl3_unsign_image,
1248
+ vendor_bl3_forsign_image,
1249
+ dir_out_path,
1250
+ device,
1251
+ private_key=None,
1252
+ public_key=None,
1253
+ activation_type=None,
1254
+ signing_module=None,
1255
+ signature_bin=None,
1256
+ logging_level=logging.DEBUG,
1257
+ signing_mode="classic_rot",
1258
+ magic=KEY_INFO_MAGIC_DEFAULT,
1259
+ key_info_version=KEY_INFO_VERSION_DEFAULT,
1260
+ svn=KEY_INFO_SVN_DEFAULT,
1261
+ svn_policy=KEY_INFO_SVN_POLICY_DEFAULT,
1262
+ ):
1263
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
1264
+ basename_vendor_unsign_image = str(
1265
+ pathlib.Path(vendor_bl3_unsign_image).stem.split(".")[0]
1266
+ )
1267
+
1268
+ if activation_type is not None:
1269
+ private_key = None
1270
+ public_key, signature_bin = self.fib_utility.get_public_key_and_signature(
1271
+ activation_type,
1272
+ signing_module,
1273
+ vendor_bl3_forsign_image,
1274
+ dir_out_path,
1275
+ basename_vendor_unsign_image,
1276
+ signing_mode,
1277
+ )
1278
+
1279
+ # Build KEY_INFO_TLV for multi-level mode (before calling sign_gpe)
1280
+ key_info_tlv_bin = None
1281
+ if signing_mode == "multi_level" and signing_module:
1282
+ key_info_tlv_bin = self.fib_utility.get_key_info_tlv(
1283
+ signing_module_path=signing_module,
1284
+ magic=magic,
1285
+ key_info_version=key_info_version,
1286
+ svn_policy_str=svn_policy,
1287
+ svn=svn,
1288
+ verbose=False,
1289
+ )
1290
+ if key_info_tlv_bin is None:
1291
+ PROGRAMMER_UTILITY_LOGGER.log(
1292
+ logging_level,
1293
+ "Failed to build/get KEY_INFO_TLV for multi-level signing mode",
1294
+ )
1295
+ return None
1296
+
1297
+ # sign vendor bl3 image
1298
+ vendor_bl3_image = GPEBuilder.sign_gpe(
1299
+ unsign_image=vendor_bl3_unsign_image,
1300
+ private_key=private_key,
1301
+ signature_bin=signature_bin,
1302
+ public_key=public_key,
1303
+ log_level=logging.DEBUG,
1304
+ device=device,
1305
+ signing_mode=signing_mode,
1306
+ key_info_tlv_bin=key_info_tlv_bin,
1307
+ )
1308
+
1309
+ # save sign vendor image file
1310
+ vendor_bl3_image_out_path = dir_out_path / (
1311
+ basename_vendor_unsign_image + ".sign.bin"
1312
+ )
1313
+ with open(vendor_bl3_image_out_path, "wb") as f:
1314
+ f.write(vendor_bl3_image)
1315
+ PROGRAMMER_UTILITY_LOGGER.log(
1316
+ logging_level,
1317
+ f"Saved sign Vendor BL3 image bin file at {vendor_bl3_image_out_path}",
1318
+ )
1319
+
1320
+ return vendor_bl3_image_out_path
1321
+
1322
+ def get_vendor_bl3_version(self, vendor_bl3_image_path, device_type):
1323
+ """Extract version from vendor BL3 image
1324
+
1325
+ Args:
1326
+ vendor_bl3_image_path: Path to vendor BL3 image
1327
+ device_type: Device type enum
1328
+
1329
+ Returns:
1330
+ Version string (e.g., "1.0.0.0") or "N/A" if cannot extract
1331
+ """
1332
+ try:
1333
+ version = BL3ImageBuilder.get_image_version(
1334
+ vendor_bl3_image_path, device=device_type
1335
+ )
1336
+ return version if version != "-1.-1.-1.-1" else "N/A"
1337
+ except Exception as e:
1338
+ PROGRAMMER_UTILITY_LOGGER.error(
1339
+ f"Failed to extract version from vendor BL3 image: {e}"
1340
+ )
1341
+ return "N/A"
1342
+
1343
+ def build_boot_sector(
1344
+ self,
1345
+ programming_sign_image_path,
1346
+ dir_out_path,
1347
+ device_type,
1348
+ version="0.0.1.0",
1349
+ logging_level=logging.DEBUG,
1350
+ ):
1351
+ # build boot sector for initial programming
1352
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
1353
+
1354
+ boot_sector_builder = BootSectorFromImgBuilder(
1355
+ image_file_path=programming_sign_image_path,
1356
+ abs_dir_out_path=dir_out_path,
1357
+ version=version,
1358
+ device=device_type,
1359
+ verbose=False,
1360
+ )
1361
+ boot_sector_builder.build()
1362
+ boot_sector_path = boot_sector_builder.boot_sector_image_out_path
1363
+ PROGRAMMER_UTILITY_LOGGER.log(
1364
+ logging_level, f"Saved boot sector image file at {boot_sector_path}"
1365
+ )
1366
+
1367
+ return boot_sector_path
1368
+
1369
+ def get_supported_devices_family_list(self, supported_devices):
1370
+ ret = []
1371
+ if any(
1372
+ [device in ["CC350x", "CC355x", "CC356x"] for device in supported_devices]
1373
+ ):
1374
+ ret.append(CC35X1)
1375
+
1376
+ if any(
1377
+ [
1378
+ device in ["CC350xE", "CC355xE", "CC356xE"]
1379
+ for device in supported_devices
1380
+ ]
1381
+ ):
1382
+ ret.append(CC35X1E)
1383
+
1384
+ # if any([device in ["CC360xR"] for device in supported_devices]):
1385
+ # ret.append(CC360XR)
1386
+
1387
+ if any([device in ["CC370xR"] for device in supported_devices]):
1388
+ ret.append(CC370XR)
1389
+
1390
+ if any(
1391
+ [device in ["CC330x", "CC335x", "CC336x"] for device in supported_devices]
1392
+ ):
1393
+ ret.append(CC33XX)
1394
+
1395
+ return ret
1396
+
1397
+ def format_nested(self, obj):
1398
+ if obj is None:
1399
+ return "None"
1400
+ elif isinstance(obj, dict):
1401
+ if not obj:
1402
+ return "None"
1403
+ else:
1404
+ return tabulate(
1405
+ [
1406
+ [k, self.format_nested(v)]
1407
+ for k, v in obj.items()
1408
+ if not k.startswith("_")
1409
+ ],
1410
+ tablefmt="grid",
1411
+ )
1412
+ else:
1413
+ return str(obj)
1414
+
1415
+ def create_table_from_json(self, json_obj):
1416
+ if not isinstance(json_obj, dict):
1417
+ return self.format_nested(json_obj)
1418
+ else:
1419
+ table_data = [
1420
+ [k, self.format_nested(v)]
1421
+ for k, v in json_obj.items()
1422
+ if not k.startswith("_")
1423
+ ]
1424
+ return tabulate(
1425
+ table_data,
1426
+ tablefmt="grid",
1427
+ headers=["Key", "Value"],
1428
+ )
1429
+
1430
+ def build_clean_image(
1431
+ self, image_file_name, image_size, dir_out_path, logging_level=logging.DEBUG
1432
+ ):
1433
+ # build boot sector for initial programming
1434
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
1435
+
1436
+ clean_image_builder = CleanImageBuilder(
1437
+ image_size=image_size,
1438
+ image_file_name=image_file_name,
1439
+ abs_dir_out_path=dir_out_path,
1440
+ verbose=False if logging_level == logging.DEBUG else True,
1441
+ )
1442
+ clean_image_builder.build()
1443
+ clean_image_path = clean_image_builder.clean_image_path
1444
+
1445
+ PROGRAMMER_UTILITY_LOGGER.log(
1446
+ logging_level, f"Clean image file created at {clean_image_path}"
1447
+ )
1448
+
1449
+ return clean_image_path
1450
+
1451
+ def build_wsoc_image(
1452
+ self,
1453
+ ti_sign_container,
1454
+ dir_out_path,
1455
+ device,
1456
+ logging_level=logging.DEBUG,
1457
+ slot=None,
1458
+ ):
1459
+ dir_out_path = pathlib.Path(dir_out_path).absolute()
1460
+
1461
+ wsoc_image_builder = WSOCImageBuilder(
1462
+ ti_container_file_path=ti_sign_container,
1463
+ abs_dir_out_path=dir_out_path,
1464
+ device=device,
1465
+ verbose=False,
1466
+ )
1467
+
1468
+ wsoc_image_builder.build()
1469
+
1470
+ base_file_name = "ti_wireless_fw_image"
1471
+ if slot is not None:
1472
+ base_file_name += f"_slot{slot}"
1473
+
1474
+ # save image
1475
+ wsoc_image_out_path = (
1476
+ pathlib.Path(wsoc_image_builder.abs_dir_out_path) / f"{base_file_name}.bin"
1477
+ )
1478
+ with open(wsoc_image_out_path, "wb") as fb:
1479
+ fb.write(wsoc_image_builder.ti_image_gpe_content_raw)
1480
+ PROGRAMMER_UTILITY_LOGGER.log(
1481
+ logging_level,
1482
+ f"Saved TI Wireless FW image GPE bin file at {wsoc_image_out_path}",
1483
+ )
1484
+
1485
+ return wsoc_image_out_path
1486
+
1487
+ def update_xspi_to_psram_type(self, xspi_json_path, psram_type, output_dir_path):
1488
+ # create updated temp jsons depends on the psram type
1489
+ with open(xspi_json_path, "r") as f:
1490
+ xspi_data = json.load(f)
1491
+
1492
+ xspi_data["xspi_header"]["ram_name"] = psram_type
1493
+
1494
+ # save updated jsons
1495
+ updated_xspi = join(output_dir_path, basename(xspi_json_path))
1496
+
1497
+ with open(updated_xspi, "w") as f:
1498
+ json.dump(xspi_data, f, indent=4)
1499
+
1500
+ return updated_xspi
1501
+
1502
+ def check_if_file_exist_else_exit(self, key, path):
1503
+ if not isfile(path):
1504
+ PROGRAMMER_UTILITY_LOGGER.error(
1505
+ f"ERROR: Path to {key} after path correction couldn't be found at {path}"
1506
+ )
1507
+ sys.exit(1)
1508
+
1509
+ def fix_paths_in_tool_settings(self, tool_settings_path):
1510
+ tool_settings_path_obj = pathlib.Path(tool_settings_path)
1511
+
1512
+ if tool_settings_path_obj.is_absolute():
1513
+ base_path = tool_settings_path_obj.parent
1514
+ else:
1515
+ base_path = pathlib.Path(getcwd())
1516
+
1517
+ mod_tool_settings = tool_settings_path.replace(".json", ".mod.json")
1518
+
1519
+ mod_tool_settings_content = json.load(open(tool_settings_path, "r"))
1520
+
1521
+ ## Fix path in programming_debug_and_ota_signed_components
1522
+ if (
1523
+ "programming_debug_and_ota_signed_components"
1524
+ not in mod_tool_settings_content
1525
+ ):
1526
+ PROGRAMMER_UTILITY_LOGGER.error(
1527
+ "ERROR: Provided tool_settings.json is incorrect format, 'programming_debug_and_ota_signed_components' is missing."
1528
+ )
1529
+ sys.exit(1)
1530
+
1531
+ programming_debug_and_ota_signed_components = mod_tool_settings_content[
1532
+ "programming_debug_and_ota_signed_components"
1533
+ ]
1534
+
1535
+ for key, value in programming_debug_and_ota_signed_components.items():
1536
+ if type(value) is str and value != "":
1537
+ p = pathlib.Path(value)
1538
+ if not p.is_absolute():
1539
+ p = self.fix_path(str(p), alt_base_path=str(base_path))
1540
+ programming_debug_and_ota_signed_components[key] = p
1541
+
1542
+ self.check_if_file_exist_else_exit(key, str(p))
1543
+
1544
+ elif type(value) is dict:
1545
+ for value_key, value_value in value.items():
1546
+ if type(value_value) is str and value_value != "":
1547
+ p = pathlib.Path(value_value)
1548
+ if not p.is_absolute():
1549
+ p = self.fix_path(str(p), alt_base_path=str(base_path))
1550
+ value[value_key] = p
1551
+
1552
+ self.check_if_file_exist_else_exit(key, str(p))
1553
+
1554
+ else:
1555
+ pass
1556
+
1557
+ else:
1558
+ pass
1559
+
1560
+ json.dump(mod_tool_settings_content, open(mod_tool_settings, "w"), indent=4)
1561
+
1562
+ return mod_tool_settings
1563
+
1564
+ def dict_to_json(self, dict_data, json_file_path, indent=4):
1565
+ with open(json_file_path, "w") as f:
1566
+ json.dump(dict_data, f, indent=indent)
1567
+
1568
+ def enable_ota_supported(self, enable_ota):
1569
+ """
1570
+ Validates and converts the enable_ota parameter (0/1, "yes"/"no", True/False)
1571
+ to a standardized integer flag (0 or 1).
1572
+ """
1573
+ VALID_OPTIONS = {
1574
+ "yes": 1,
1575
+ "no": 0,
1576
+ "true": 1,
1577
+ "false": 0,
1578
+ 1: 1,
1579
+ 0: 0,
1580
+ True: 1,
1581
+ False: 0,
1582
+ }
1583
+
1584
+ # Handle string input (case-insensitive)
1585
+ if isinstance(enable_ota, str):
1586
+ input_lower = enable_ota.lower()
1587
+ if input_lower in VALID_OPTIONS:
1588
+ return VALID_OPTIONS[input_lower]
1589
+
1590
+ # Handle integer or boolean input
1591
+ elif isinstance(enable_ota, (int, bool)):
1592
+ if enable_ota in VALID_OPTIONS:
1593
+ return VALID_OPTIONS[enable_ota]
1594
+
1595
+ # If none of the above match, raise an error
1596
+ raise ValueError(
1597
+ f"Invalid value for 'enable_ota': '{enable_ota}'. "
1598
+ f"Expected 0, 1, True, False, 'yes', 'no', 'true', or 'false'."
1599
+ )
1600
+
1601
+ def normalize_restrict_ini_hex_to_int(self, restrict_ini):
1602
+ """
1603
+ Validates that the input for 'restrict_ini' is a hex value (string or int),
1604
+ ensures it fits within a 0 to 0xFFFFFFFF, and converts it to an integer.
1605
+ """
1606
+ if restrict_ini == "" or restrict_ini is None:
1607
+ return 0
1608
+
1609
+ if isinstance(restrict_ini, int):
1610
+ if 0 <= restrict_ini <= 0xFFFFFFFF:
1611
+ return restrict_ini
1612
+ else:
1613
+ raise argparse.ArgumentTypeError( # Changed from ValueError
1614
+ f"Integer value for 'restrict_ini' out of range (0 to 0xFFFFFFFF): {restrict_ini}"
1615
+ )
1616
+
1617
+ if not isinstance(restrict_ini, str):
1618
+ raise argparse.ArgumentTypeError( # Changed from TypeError
1619
+ f"Invalid input type for 'restrict_ini': {type(restrict_ini)}. Expected string or int."
1620
+ )
1621
+
1622
+ restrict_ini = restrict_ini.upper()
1623
+ restrict_ini_normalized_hex = restrict_ini.replace("0X", "")
1624
+
1625
+ if not re.fullmatch(r"^[0-9A-F]+$", restrict_ini_normalized_hex):
1626
+ raise argparse.ArgumentTypeError( # Changed from ValueError
1627
+ f"Invalid hexadecimal characters in 'restrict_ini': '{restrict_ini}'"
1628
+ )
1629
+
1630
+ try:
1631
+ restrict_ini_int_value = int(restrict_ini_normalized_hex, 16)
1632
+ except ValueError:
1633
+ raise argparse.ArgumentTypeError( # Changed from ValueError
1634
+ f"Failed to convert 'restrict_ini' value '{restrict_ini}' to integer."
1635
+ )
1636
+
1637
+ MAX_32BIT_VALUE = 0xFFFFFFFF
1638
+ if restrict_ini_int_value > MAX_32BIT_VALUE or restrict_ini_int_value < 0:
1639
+ raise argparse.ArgumentTypeError( # Changed from ValueError
1640
+ f"'restrict_ini' value out of range (0 to 0xFFFFFFFF). Value: 0x{restrict_ini_normalized_hex} (Decimal: {restrict_ini_int_value})."
1641
+ )
1642
+
1643
+ return restrict_ini_int_value
1644
+
1645
+ def validate_hex(self, value, max_length):
1646
+ """
1647
+ Validate hex value.
1648
+ - Must be hexadecimal characters only
1649
+ - Validate max length of max_length hex characters
1650
+ - Accepts with or without 0x prefix
1651
+ - Returns normalized value with 0x prefix, or None if empty
1652
+ """
1653
+ if value is None or value == "":
1654
+ return None
1655
+
1656
+ value_str = str(value)
1657
+
1658
+ # Check if it starts with 0x prefix
1659
+ if value_str.lower().startswith("0x"):
1660
+ hex_part = value_str[2:]
1661
+ else:
1662
+ hex_part = value_str
1663
+
1664
+ # Validate hexadecimal characters only
1665
+ if not re.match(r"^[0-9a-fA-F]*$", hex_part):
1666
+ raise argparse.ArgumentTypeError(
1667
+ f"Invalid hexadecimal value '{value}'. Must contain only hexadecimal characters (0-9, a-f, A-F)."
1668
+ )
1669
+
1670
+ # Check max length
1671
+ if len(hex_part) > max_length:
1672
+ raise argparse.ArgumentTypeError(
1673
+ f"Invalid hexadecimal value '{value}'. Maximum length is {max_length} hexadecimal characters ({max_length * 4} bits), got {len(hex_part)} characters."
1674
+ )
1675
+
1676
+ # Return normalized with 0x prefix
1677
+ return "0x" + hex_part if hex_part else None
1678
+
1679
+ def convert_mac_to_int(self, mac_address):
1680
+ """
1681
+ Converts a string MAC address (e.g., "00:1A:2B:3C:4D:5E") into a 64-bit integer.
1682
+ Accepts formats: XX:XX:XX:XX:XX:XX, XX-XX-XX-XX-XX-XX, or XXXXXXXXXXXX
1683
+
1684
+ Used by both CLI and REST API.
1685
+ """
1686
+
1687
+ if mac_address == "":
1688
+ return 0
1689
+
1690
+ # Remove any separators (':', '-', or '.') from the string
1691
+ mac_address_clean = re.sub(r"[:\-.]", "", str(mac_address)).upper()
1692
+
1693
+ # Ensure the string is exactly 12 hexadecimal characters long
1694
+ if len(mac_address_clean) != 12:
1695
+ raise argparse.ArgumentTypeError(
1696
+ f"Invalid MAC address length: '{mac_address}'. Must be 12 hexadecimal digits."
1697
+ )
1698
+
1699
+ # Validate hex characters
1700
+ if not re.match(r"^[0-9A-F]{12}$", mac_address_clean):
1701
+ raise argparse.ArgumentTypeError(
1702
+ f"Invalid MAC address: '{mac_address}'. Must contain only hexadecimal digits (0-9, A-F)."
1703
+ )
1704
+
1705
+ try:
1706
+ # Convert the hex string to an integer
1707
+ mac_address_int = int(mac_address_clean, 16)
1708
+ return mac_address_int
1709
+ except ValueError:
1710
+ raise argparse.ArgumentTypeError(
1711
+ f"Failed to convert MAC address to integer: '{mac_address}'"
1712
+ )
1713
+
1714
+ def normalize_wifi6_disable(self, wifi6_disable):
1715
+ """
1716
+ Validates and converts the wifi6_disable parameter (0/1 or "yes"/"no")
1717
+ to a standardized integer flag (0 or 1).
1718
+ """
1719
+ VALID_OPTIONS = {"yes": 1, "no": 0, "1": 1, "0": 0, 1: 1, 0: 0}
1720
+
1721
+ if wifi6_disable == "":
1722
+ return 0
1723
+
1724
+ if isinstance(wifi6_disable, str):
1725
+ input_lower = wifi6_disable.lower()
1726
+ if input_lower in VALID_OPTIONS:
1727
+ return VALID_OPTIONS[input_lower]
1728
+ elif isinstance(wifi6_disable, int):
1729
+ if wifi6_disable in VALID_OPTIONS:
1730
+ return VALID_OPTIONS[wifi6_disable]
1731
+
1732
+ raise ValueError(
1733
+ f"Invalid value for 'wifi6_disable': '{wifi6_disable}'. "
1734
+ f"Expected 0, 1, 'yes', or 'no'."
1735
+ )
1736
+
1737
+ def is_valid_country_code(self, code):
1738
+ """Check if country code is valid"""
1739
+ if not code:
1740
+ return False
1741
+ return code.upper() in VALID_COUNTRY_CODES
1742
+
1743
+ def get_country_codes_list(self):
1744
+ return COUNTRY_CODES_LIST
1745
+
1746
+ def calculate_device_country_code_int(self, country_code):
1747
+ """
1748
+ Converts a 2-char country code string to a 16-bit integer.
1749
+ Validates against predefined list of supported country codes.
1750
+ Args:
1751
+ country_code: 2-letter country code (e.g., 'US', 'GB') or 0
1752
+ Returns:
1753
+ int: 16-bit integer representation (big-endian ASCII)
1754
+ Examples:
1755
+ 'US' -> 21845 (0x5553)
1756
+ 'GB' -> 18242 (0x4742)
1757
+ 0 or '0' -> 0
1758
+ """
1759
+ # Handle zero/empty cases
1760
+ if country_code in (0, -1, "0", "-1", "", None):
1761
+ return 0
1762
+
1763
+ # Reject integer input (except 0)
1764
+ if isinstance(country_code, int):
1765
+ if country_code == 0:
1766
+ return 0
1767
+ raise argparse.ArgumentTypeError(
1768
+ f"Country code must be a 2-letter string (e.g., US, GB), not integer: {country_code}"
1769
+ )
1770
+
1771
+ # Validate type
1772
+ if not isinstance(country_code, str):
1773
+ raise argparse.ArgumentTypeError(
1774
+ f"Invalid type: {type(country_code).__name__}. Expected 2-letter country code string."
1775
+ )
1776
+
1777
+ # Validate length
1778
+ if len(country_code) != 2:
1779
+ raise argparse.ArgumentTypeError(
1780
+ f"Invalid country code length: '{country_code}'. Must be exactly 2 letters.\n"
1781
+ f"Examples: US, GB, FR, DE, JP, CN"
1782
+ )
1783
+
1784
+ # Validate characters (only letters)
1785
+ if not country_code.isalpha():
1786
+ raise argparse.ArgumentTypeError(
1787
+ f"Invalid characters in '{country_code}'. Only letters (A-Z) allowed."
1788
+ )
1789
+
1790
+ # Convert to uppercase
1791
+ country_code_upper = country_code.upper()
1792
+
1793
+ # Validate against supported country codes
1794
+ if country_code_upper not in VALID_COUNTRY_CODES:
1795
+ raise argparse.ArgumentTypeError(
1796
+ f"Unsupported country code: '{country_code}'. "
1797
+ f"Must be one of the supported codes (e.g., US, GB, FR, DE, JP, CN).\n"
1798
+ )
1799
+
1800
+ if country_code_upper == "WW":
1801
+ country_code_upper = "00"
1802
+
1803
+ # Calculate 16-bit integer (big-endian)
1804
+ char1_ascii = ord(country_code_upper[0])
1805
+ char2_ascii = ord(country_code_upper[1])
1806
+ result = (char1_ascii << 8) | char2_ascii # Changed to big-endian
1807
+
1808
+ return result
1809
+
1810
+ def split_mac_address(self, mac_address):
1811
+ if isinstance(mac_address, str):
1812
+ clean_mac = mac_address.replace(":", "").upper()
1813
+ first_part = clean_mac[:8]
1814
+ second_part = clean_mac[8:]
1815
+
1816
+ return int(first_part, 16), int(second_part, 16)
1817
+
1818
+ else:
1819
+ mac_address_31_0 = mac_address & 0xFFFFFFFF
1820
+ mac_address_47_32 = (mac_address >> 32) & 0xFFFF
1821
+
1822
+ return mac_address_31_0, mac_address_47_32