94 lines
3.3 KiB
Python
94 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Utilities for data encoding/decoding (PSM timers, SIM BCD, PLMN lists).
|
|
"""
|
|
|
|
from typing import List, Union, Optional
|
|
|
|
def encode_psm_timer(seconds: int, is_t3324: bool = False) -> str:
|
|
"""Encodes seconds into 3GPP binary string."""
|
|
if is_t3324:
|
|
if seconds <= 2 * 31: return f"000{seconds // 2:05b}"
|
|
if seconds <= 60 * 31: return f"001{seconds // 60:05b}"
|
|
if seconds <= 360 * 31: return f"010{seconds // 360:05b}"
|
|
return "11100000"
|
|
else:
|
|
if seconds <= 30 * 31: return f"100{seconds // 30:05b}"
|
|
if seconds <= 60 * 31: return f"101{seconds // 60:05b}"
|
|
if seconds <= 3600 * 31: return f"001{seconds // 3600:05b}"
|
|
return f"010{min(31, seconds // 36000):05b}"
|
|
|
|
def decode_plmn(bcd_data: bytes) -> Optional[str]:
|
|
"""Decodes a 3-byte BCD PLMN (MCC/MNC) from SIM storage."""
|
|
if len(bcd_data) < 3 or bcd_data == b'\xff\xff\xff':
|
|
return None
|
|
|
|
mcc = f"{(bcd_data[0] & 0x0F)}{(bcd_data[0] >> 4)}{(bcd_data[1] & 0x0F)}"
|
|
mnc_digit3 = (bcd_data[1] >> 4)
|
|
mnc = f"{(bcd_data[2] & 0x0F)}{(bcd_data[2] >> 4)}"
|
|
if mnc_digit3 != 0xF:
|
|
mnc = f"{(bcd_data[2] & 0x0F)}{(bcd_data[2] >> 4)}{mnc_digit3}"
|
|
return f"{mcc}-{mnc}"
|
|
|
|
def decode_act(act_bytes: bytes) -> str:
|
|
"""Decodes Access Technology bitmask (2 bytes)."""
|
|
if len(act_bytes) < 2: return "Unknown"
|
|
techs = []
|
|
b1, b2 = act_bytes[0], act_bytes[1]
|
|
if b1 & 0x80: techs.append("UTRAN")
|
|
if b1 & 0x40: techs.append("E-UTRAN")
|
|
if b1 & 0x20: techs.append("NG-RAN")
|
|
if b2 & 0x80: techs.append("GSM")
|
|
if b2 & 0x40: techs.append("cdma2000 HRPD")
|
|
if b2 & 0x20: techs.append("cdma2000 1xRTT")
|
|
return "/".join(techs) if techs else "None"
|
|
|
|
def decode_plmn_list(data: bytes, has_act: bool = False) -> List[str]:
|
|
"""Decodes a list of PLMNs, optionally with Access Technology."""
|
|
stride = 5 if has_act else 3
|
|
results = []
|
|
for i in range(0, len(data), stride):
|
|
chunk = data[i:i+stride]
|
|
plmn = decode_plmn(chunk[:3])
|
|
if plmn:
|
|
if has_act:
|
|
act = decode_act(chunk[3:5])
|
|
results.append(f"{plmn} [{act}]")
|
|
else:
|
|
results.append(plmn)
|
|
return results
|
|
|
|
def decode_iccid(bcd_data: bytes) -> str:
|
|
"""Decodes raw ICCID bytes (swapped nibbles)."""
|
|
return "".join([f"{(b & 0x0F)}{(b >> 4):X}" for b in bcd_data]).replace("F", "")
|
|
|
|
def bands_to_hex_mask(bands: List[int]) -> str:
|
|
"""Converts a list of band numbers into a hex bitmask string."""
|
|
mask = 0
|
|
for b in bands:
|
|
if b > 0:
|
|
mask |= (1 << (b - 1))
|
|
return f"{mask:X}"
|
|
|
|
def hex_mask_to_bands(hex_mask: str) -> List[int]:
|
|
"""Converts a hex bitmask string back into a list of band numbers."""
|
|
try:
|
|
mask = int(hex_mask, 16)
|
|
except (ValueError, TypeError):
|
|
return []
|
|
|
|
bands = []
|
|
for i in range(128): # Support up to 128 bands
|
|
if (mask >> i) & 1:
|
|
bands.append(i + 1)
|
|
return bands
|
|
|
|
def fmt_val(val: Union[str, List, None]) -> str:
|
|
"""Helper to format values for display in reports and shells."""
|
|
if val is None or val == "" or val == []:
|
|
return "None (Not set on SIM)"
|
|
if isinstance(val, list):
|
|
return ", ".join(val)
|
|
return str(val)
|