contacts, modem: implement direct QRTR QMI UIM SIM phonebook import and export for SIM 1 and SIM 2

This commit is contained in:
Luxferre
2026-10-25 15:30:24 +02:00
parent ef18a33c9b
commit 7c707844cc
5 changed files with 1095 additions and 173 deletions
Binary file not shown.
+385 -111
View File
@@ -77,6 +77,9 @@ detect_qmicli_modem() {
if timeout 3 qmicli --silent -pd qrtr://0 --uim-noop >/dev/null 2>&1; then
QMICLI_MODEM="timeout 4 qmicli --silent -pd qrtr://0"
return 0
elif timeout 3 qmicli --silent -d qrtr://0 --uim-noop >/dev/null 2>&1; then
QMICLI_MODEM="timeout 4 qmicli --silent -d qrtr://0"
return 0
elif [ -e "/dev/modem" ]; then
if timeout 3 qmicli --silent -pd /dev/modem --uim-noop >/dev/null 2>&1; then
QMICLI_MODEM="timeout 4 qmicli --silent -pd /dev/modem"
@@ -1181,6 +1184,151 @@ case "$cmd" in
fi
fi
tmp_contacts="/tmp/sim_import_$$.tsv"
rm -f "$tmp_contacts"
: > "$tmp_contacts"
# Method 1: Fast direct QRTR QMI UIM EF_ADN read (supports SIM 1 & SIM 2 without switching GW session)
if command -v python3 >/dev/null 2>&1; then
python3 - "$target_slot" > "$tmp_contacts" 2>/dev/null << 'PY' || true
import socket, struct, select, sys
slot = int(sys.argv[1]) if len(sys.argv) > 1 and sys.argv[1].isdigit() else 1
AF_QIPCRTR = 42
QRTR_PORT_CTRL = 0xfffffffe
QRTR_TYPE_NEW_SERVER = 4
QRTR_TYPE_NEW_LOOKUP = 10
QMI_SERVICE_UIM = 11
ls = socket.socket(AF_QIPCRTR, socket.SOCK_DGRAM)
node, _ = ls.getsockname()
ls.sendto(struct.pack("<IIIII", QRTR_TYPE_NEW_LOOKUP, QMI_SERVICE_UIM, 0, 0, 0), (node, QRTR_PORT_CTRL))
uim_addr = None
while True:
r, _, _ = select.select([ls], [], [], 0.15)
if not r: break
data, _ = ls.recvfrom(256)
if len(data) >= 20:
cmd, srv, inst, srv_node, srv_port = struct.unpack("<IIIII", data[:20])
if cmd == QRTR_TYPE_NEW_SERVER:
if srv == QMI_SERVICE_UIM and srv_port != 0:
uim_addr = (srv_node, srv_port)
if srv == 0 and srv_node == 0 and srv_port == 0:
break
ls.close()
if not uim_addr:
sys.exit(1)
s = socket.socket(AF_QIPCRTR, socket.SOCK_DGRAM)
session_type = 7 if slot == 2 else 6
tlv_f = None
rec_size = 0
rec_count = 0
txn = 1
for stype in [session_type, 0]:
for fid, path in [(0x6F3A, [0x3F00, 0x7F10]), (0x4F3A, [0x3F00, 0x7F10, 0x5F3A])]:
pbytes = b"".join(struct.pack("<H", x) for x in path)
fval = struct.pack("<HB", fid, len(pbytes)) + pbytes
tf = struct.pack("<BH", 0x02, len(fval)) + fval
ts = struct.pack("<BHB", 0x01, 2, stype) + b"\x00"
tlvs = ts + tf
s.sendto(struct.pack("<BHHH", 0x00, txn, 0x0024, len(tlvs)) + tlvs, uim_addr)
txn += 1
r, _, _ = select.select([s], [], [], 0.15)
if not r: continue
resp, _ = s.recvfrom(512)
if len(resp) < 14 or resp[0] != 0x02 or resp[3] != 0x24: continue
pos = 7
ok = False
rs = rc = 0
while pos + 3 <= len(resp):
tt, tl = struct.unpack("<BH", resp[pos:pos+3])
pos += 3
if tt == 0x02 and tl >= 4:
res, err = struct.unpack("<HH", resp[pos:pos+4])
if res == 0 and err == 0: ok = True
elif tt == 0x11 and tl >= 9:
_, _, _, rs, rc = struct.unpack("<HHBHH", resp[pos:pos+9])
pos += tl
if ok and rs >= 14 and rc >= 1:
session_type = stype
tlv_f = tf
rec_size = rs
rec_count = min(rc, 500)
break
if tlv_f:
break
if not tlv_f:
s.close()
sys.exit(1)
tlv_session = struct.pack("<BHB", 0x01, 2, session_type) + b"\x00"
bcd_map = "0123456789*#p"
for rnum in range(1, rec_count + 1):
tlv_rec = struct.pack("<BHHH", 0x03, 4, rnum, rec_size)
tlvs = tlv_session + tlv_f + tlv_rec
s.sendto(struct.pack("<BHHH", 0x00, txn, 0x0021, len(tlvs)) + tlvs, uim_addr)
txn += 1
r, _, _ = select.select([s], [], [], 0.15)
if not r: continue
resp, _ = s.recvfrom(512)
pos = 7
raw = None
ok = False
while pos + 3 <= len(resp):
tt, tl = struct.unpack("<BH", resp[pos:pos+3])
pos += 3
if tt == 0x02 and tl >= 4:
res, err = struct.unpack("<HH", resp[pos:pos+4])
if res == 0 and err == 0: ok = True
elif tt == 0x11 and tl >= 2:
clen = struct.unpack("<H", resp[pos:pos+2])[0]
raw = resp[pos+2:pos+2+clen]
pos += tl
if not (ok and raw and len(raw) >= 14):
continue
alpha_len = len(raw) - 14
alpha = raw[:alpha_len]
bcd_len = raw[alpha_len]
ton_npi = raw[alpha_len + 1]
if bcd_len == 0 or bcd_len > 11 or bcd_len == 0xFF:
continue
num = "+" if ((ton_npi >> 4) & 0x07) == 1 else ""
for b in raw[alpha_len + 2 : alpha_len + 2 + min(10, bcd_len - 1)]:
if b == 0xFF: break
lo = b & 0x0F
hi = (b >> 4) & 0x0F
if lo < len(bcd_map): num += bcd_map[lo]
else: break
if hi == 0x0F: break
if hi < len(bcd_map): num += bcd_map[hi]
else: break
if not num or num == "+":
continue
while alpha and alpha[-1] == 0xFF:
alpha = alpha[:-1]
if not alpha:
name = num
elif alpha[0] == 0x80:
u16 = alpha[1:]
if len(u16) % 2 == 1: u16 = u16[:-1]
name = u16.decode("utf-16-be", errors="replace").replace("\t", " ").replace("\n", " ").strip()
else:
name = "".join(chr(b) for b in alpha if 32 <= b < 127 and b != 9).strip()
print(f"{num}\t{name or num}")
s.close()
PY
fi
if [ -s "$tmp_contacts" ]; then
cat "$tmp_contacts"
rm -f "$tmp_contacts"
exit 0
fi
orig_gw_slot="$PRIMARY_GW_SLOT"
orig_gw_aid=""
if [ "$orig_gw_slot" = "1" ]; then
@@ -1210,57 +1358,7 @@ case "$cmd" in
fi
fi
tmp_contacts="/tmp/sim_import_$$.tsv"
rm -f "$tmp_contacts"
: > "$tmp_contacts"
# Method 1: Standard 3GPP AT+CPBS="SM" / AT+CPBR
at_info="$("$0" at auto 'AT+CMEE=2;+CSCS="GSM";+CPBS="SM";+CPBR=?' 2>/dev/null || true)"
max_idx="$(printf "%s\n" "$at_info" | sed -n 's/.*+CPBR:[[:space:]]*([0-9]*-\([0-9]*\)).*/\1/p' | head -n1)"
if [ -n "$max_idx" ] && [ "$max_idx" -gt 0 ] 2>/dev/null; then
if [ "$max_idx" -gt 500 ]; then
max_idx=500
fi
start_idx=1
while [ "$start_idx" -le "$max_idx" ]; do
end_idx=$((start_idx + 49))
if [ "$end_idx" -gt "$max_idx" ]; then
end_idx="$max_idx"
fi
chunk="$("$0" at auto "AT+CPBR=${start_idx},${end_idx}" 2>/dev/null || true)"
if [ -n "$chunk" ]; then
printf "%s\n" "$chunk" | awk '
/^\+CPBR:[[:space:]]*[0-9]+,/ {
line = $0
sub(/^\+CPBR:[[:space:]]*[0-9]+,[[:space:]]*"/, "", line)
q1 = index(line, "\"")
if (q1 <= 0) next
num = substr(line, 1, q1 - 1)
rest = substr(line, q1 + 1)
sub(/^[[:space:]]*,[[:space:]]*/, "", rest)
c2 = index(rest, ",")
if (c2 <= 0) next
ton = substr(rest, 1, c2 - 1) + 0
name = substr(rest, c2 + 1)
sub(/^[[:space:]]*"/, "", name)
sub(/"[[:space:]]*$/, "", name)
gsub(/[\t\r\n]/, " ", name)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", name)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", num)
if (num == "") next
if (ton == 145 && substr(num, 1, 1) != "+") {
num = "+" num
}
if (name == "") name = num
printf "%s\t%s\n", num, name
}
' >> "$tmp_contacts"
fi
start_idx=$((end_idx + 1))
done
fi
# Method 2: Direct QMI UIM EF_ADN (3F00:7F10:6F3A) record fallback
# Method 2: Direct qmicli UIM EF_ADN (3F00-7F10-6F3A) record fallback
if [ ! -s "$tmp_contacts" ]; then
adn_attr="$($QMICLI_MODEM --uim-get-file-attributes=0x3F00,0x7F10,0x6F3A 2>/dev/null || true)"
reclen="$(printf "%s\n" "$adn_attr" | awk '/Record (size|length):/ {gsub(/[^0-9]/, "", $NF); print $NF + 0; exit}')"
@@ -1271,9 +1369,9 @@ case "$cmd" in
fi
rec=1
while [ "$rec" -le "$reccount" ]; do
raw_rec="$($QMICLI_MODEM --uim-read-record="file=0x3F00,0x7F10,0x6F3A,record-number=${rec},record-length=${reclen}" 2>/dev/null || true)"
raw_rec="$($QMICLI_MODEM --uim-read-record="file=0x3F00-0x7F10-0x6F3A,record-number=${rec},record-length=${reclen}" 2>/dev/null || true)"
if [ -n "$raw_rec" ]; then
printf "%s\n" "$raw_rec" | awk -v rlen="$reclen" '
printf "%s\n" "$raw_rec" | awk '
function hex2val(h, i, c, v, res) {
res = 0
h = toupper(h)
@@ -1291,15 +1389,21 @@ case "$cmd" in
if (n == 11) return "#"
return ""
}
/^[[:space:]]*([0-9A-Fa-f]{2}:)+[0-9A-Fa-f]{2}[[:space:]]*$/ {
/Read result:/ { in_res = 1; full = ""; next }
in_res && /^[[:space:]]*[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2})*:?[[:space:]]*$/ {
line = $0
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
n = split(line, hex, ":")
if (n < 14) next
sub(/:$/, "", line)
full = (full == "") ? line : (full ":" line)
}
END {
if (full == "") exit
n = split(full, hex, ":")
if (n < 14) exit
alpha_len = n - 14
bcd_len = hex2val(hex[alpha_len + 1])
ton_npi = hex2val(hex[alpha_len + 2])
if (bcd_len <= 0 || bcd_len > 11 || bcd_len == 255) next
if (bcd_len <= 0 || bcd_len > 11 || bcd_len == 255) exit
num = ""
if (int(ton_npi / 16) % 8 == 1) {
num = "+"
@@ -1318,7 +1422,7 @@ case "$cmd" in
if (d2 == "") break
num = num d2
}
if (num == "" || num == "+") next
if (num == "" || num == "+") exit
name = ""
for (i = 1; i <= alpha_len; i++) {
b = hex2val(hex[i])
@@ -1396,64 +1500,234 @@ case "$cmd" in
fi
fi
orig_gw_slot="$PRIMARY_GW_SLOT"
orig_gw_aid=""
if [ "$orig_gw_slot" = "1" ]; then
orig_gw_aid="$SLOT1_AID"
elif [ "$orig_gw_slot" = "2" ]; then
orig_gw_aid="$SLOT2_AID"
fi
switched_gw=0
restore_gw_slot_exp() {
if [ "$switched_gw" = "1" ]; then
$QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true
if [ -n "$orig_gw_slot" ] && [ "$orig_gw_slot" != "0" ] && [ -n "$orig_gw_aid" ]; then
$QMICLI_MODEM --uim-change-provisioning-session="slot=$orig_gw_slot,activate=yes,session-type=primary-gw-provisioning,aid=$orig_gw_aid" >/dev/null 2>&1 || true
fi
switched_gw=0
fi
}
trap restore_gw_slot_exp EXIT INT TERM
if command -v python3 >/dev/null 2>&1; then
exp_out="$(python3 - "$target_slot" "$src_tsv" << 'PY'
import socket, struct, select, sys
if [ "$PRIMARY_GW_SLOT" != "$target_slot" ] && [ -n "$target_aid" ]; then
if [ "$PRIMARY_GW_SLOT" != "0" ]; then
$QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true
slot = int(sys.argv[1]) if len(sys.argv) > 1 and sys.argv[1].isdigit() else 1
tsv_path = sys.argv[2]
rows = []
with open(tsv_path, "r", encoding="utf-8", errors="replace") as f:
for line in f:
line = line.rstrip("\r\n")
if "\t" not in line: continue
num, name = line.split("\t", 1)
num = "".join(c for c in num if c in "+0123456789*#")
name = name.strip()
if num:
rows.append((num, name or num))
if not rows:
print("ERROR: No contacts to export")
sys.exit(1)
AF_QIPCRTR = 42
QRTR_PORT_CTRL = 0xfffffffe
QRTR_TYPE_NEW_SERVER = 4
QRTR_TYPE_NEW_LOOKUP = 10
QMI_SERVICE_UIM = 11
ls = socket.socket(AF_QIPCRTR, socket.SOCK_DGRAM)
node, _ = ls.getsockname()
ls.sendto(struct.pack("<IIIII", QRTR_TYPE_NEW_LOOKUP, QMI_SERVICE_UIM, 0, 0, 0), (node, QRTR_PORT_CTRL))
uim_addr = None
while True:
r, _, _ = select.select([ls], [], [], 0.15)
if not r: break
data, _ = ls.recvfrom(256)
if len(data) >= 20:
cmd, srv, inst, srv_node, srv_port = struct.unpack("<IIIII", data[:20])
if cmd == QRTR_TYPE_NEW_SERVER:
if srv == QMI_SERVICE_UIM and srv_port != 0:
uim_addr = (srv_node, srv_port)
if srv == 0 and srv_node == 0 and srv_port == 0:
break
ls.close()
if not uim_addr:
print("ERROR: Modem UIM service unavailable")
sys.exit(1)
s = socket.socket(AF_QIPCRTR, socket.SOCK_DGRAM)
session_type = 7 if slot == 2 else 6
tlv_f = None
rec_size = 0
rec_count = 0
txn = 1
for stype in [session_type, 0]:
for fid, path in [(0x6F3A, [0x3F00, 0x7F10]), (0x4F3A, [0x3F00, 0x7F10, 0x5F3A])]:
pbytes = b"".join(struct.pack("<H", x) for x in path)
fval = struct.pack("<HB", fid, len(pbytes)) + pbytes
tf = struct.pack("<BH", 0x02, len(fval)) + fval
ts = struct.pack("<BHB", 0x01, 2, stype) + b"\x00"
tlvs = ts + tf
s.sendto(struct.pack("<BHHH", 0x00, txn, 0x0024, len(tlvs)) + tlvs, uim_addr)
txn += 1
r, _, _ = select.select([s], [], [], 0.15)
if not r: continue
resp, _ = s.recvfrom(512)
if len(resp) < 14 or resp[0] != 0x02 or resp[3] != 0x24: continue
pos = 7
ok = False
rs = rc = 0
while pos + 3 <= len(resp):
tt, tl = struct.unpack("<BH", resp[pos:pos+3])
pos += 3
if tt == 0x02 and tl >= 4:
res, err = struct.unpack("<HH", resp[pos:pos+4])
if res == 0 and err == 0: ok = True
elif tt == 0x11 and tl >= 9:
_, _, _, rs, rc = struct.unpack("<HHBHH", resp[pos:pos+9])
pos += tl
if ok and rs >= 14 and rc >= 1:
session_type = stype
tlv_f = tf
rec_size = rs
rec_count = min(rc, 500)
break
if tlv_f:
break
if not tlv_f:
s.close()
print(f"ERROR: SIM {slot} phonebook not ready")
sys.exit(1)
tlv_session = struct.pack("<BHB", 0x01, 2, session_type) + b"\x00"
bcd_map_str = "0123456789*#p"
existing_by_norm = {}
existing_by_sfx = {}
empty_slots = []
def sfx_key(norm):
d = "".join(c for c in norm if c.isdigit())
return d[-9:] if len(d) >= 9 else norm
for rnum in range(1, rec_count + 1):
tlv_rec = struct.pack("<BHHH", 0x03, 4, rnum, rec_size)
tlvs = tlv_session + tlv_f + tlv_rec
s.sendto(struct.pack("<BHHH", 0x00, txn, 0x0021, len(tlvs)) + tlvs, uim_addr)
txn += 1
r, _, _ = select.select([s], [], [], 0.15)
if not r: continue
resp, _ = s.recvfrom(512)
pos = 7
raw = None
ok = False
while pos + 3 <= len(resp):
tt, tl = struct.unpack("<BH", resp[pos:pos+3])
pos += 3
if tt == 0x02 and tl >= 4:
res, err = struct.unpack("<HH", resp[pos:pos+4])
if res == 0 and err == 0: ok = True
elif tt == 0x11 and tl >= 2:
clen = struct.unpack("<H", resp[pos:pos+2])[0]
raw = resp[pos+2:pos+2+clen]
pos += tl
if not (ok and raw and len(raw) >= 14):
continue
alpha_len = len(raw) - 14
bcd_len = raw[alpha_len]
ton_npi = raw[alpha_len + 1]
if bcd_len == 0 or bcd_len > 11 or bcd_len == 0xFF:
empty_slots.append(rnum)
continue
num = "+" if ((ton_npi >> 4) & 0x07) == 1 else ""
for b in raw[alpha_len + 2 : alpha_len + 2 + min(10, bcd_len - 1)]:
if b == 0xFF: break
lo = b & 0x0F
hi = (b >> 4) & 0x0F
if lo < len(bcd_map_str): num += bcd_map_str[lo]
else: break
if hi == 0x0F: break
if hi < len(bcd_map_str): num += bcd_map_str[hi]
else: break
if not num or num == "+":
empty_slots.append(rnum)
continue
existing_by_norm[num] = rnum
existing_by_sfx[sfx_key(num)] = rnum
def encode_adn(name, num, rsize):
alpha_len = rsize - 14
try:
nb = name.encode("ascii")[:alpha_len]
alpha = nb + b"\xFF" * (alpha_len - len(nb))
except UnicodeEncodeError:
u16 = name.encode("utf-16-be")[:max(0, (alpha_len - 1) // 2 * 2)]
alpha = b"\x80" + u16 + b"\xFF" * (alpha_len - 1 - len(u16))
ton_npi = 0x91 if num.startswith("+") else 0x81
digits = [c for c in num if c != "+"][:20]
if not digits: return None
bcd = bytearray([0xFF] * 10)
bmap = {str(i): i for i in range(10)}
bmap["*"] = 0x0A
bmap["#"] = 0x0B
for idx, ch in enumerate(digits):
nib = bmap.get(ch, 0x0F)
bi = idx // 2
if idx % 2 == 0:
bcd[bi] = (bcd[bi] & 0xF0) | nib
else:
bcd[bi] = (bcd[bi] & 0x0F) | (nib << 4)
bcd_len = 1 + (len(digits) + 1) // 2
return alpha + bytes([bcd_len, ton_npi]) + bytes(bcd) + b"\xFF\xFF"
exported = 0
empty_idx = 0
for num, name in rows:
trec = existing_by_norm.get(num) or existing_by_sfx.get(sfx_key(num))
if not trec:
if empty_idx < len(empty_slots):
trec = empty_slots[empty_idx]
empty_idx += 1
else:
break
rdata = encode_adn(name, num, rec_size)
if not rdata: continue
wval = struct.pack("<HH", trec, len(rdata)) + rdata
tlv_w = struct.pack("<BH", 0x03, len(wval)) + wval
tlvs = tlv_session + tlv_f + tlv_w
s.sendto(struct.pack("<BHHH", 0x00, txn, 0x0023, len(tlvs)) + tlvs, uim_addr)
txn += 1
r, _, _ = select.select([s], [], [], 0.2)
if not r: continue
resp, _ = s.recvfrom(512)
pos = 7
ok = False
while pos + 3 <= len(resp):
tt, tl = struct.unpack("<BH", resp[pos:pos+3])
pos += 3
if tt == 0x02 and tl >= 4:
res, err = struct.unpack("<HH", resp[pos:pos+4])
if res == 0 and err == 0: ok = True
pos += tl
if ok:
exported += 1
existing_by_norm[num] = trec
existing_by_sfx[sfx_key(num)] = trec
s.close()
if exported == 0 and empty_idx >= len(empty_slots):
print(f"ERROR: SIM {slot} phonebook is full")
sys.exit(1)
print(f"EXPORTED={exported}")
print(f"SLOT={slot}")
PY
)"
if printf "%s\n" "$exp_out" | grep -q "^EXPORTED="; then
printf "%s\n" "$exp_out"
exit 0
fi
if $QMICLI_MODEM --uim-change-provisioning-session="slot=$target_slot,activate=yes,session-type=primary-gw-provisioning,aid=$target_aid" >/dev/null 2>&1; then
switched_gw=1
sleep 1
if printf "%s\n" "$exp_out" | grep -q "^ERROR:"; then
printf "%s\n" "$exp_out"
exit 1
fi
fi
at_info="$("$0" at auto 'AT+CMEE=2;+CSCS="GSM";+CPBS="SM";+CPBR=?' 2>/dev/null || true)"
if printf "%s\n" "$at_info" | grep -qE "ERROR"; then
echo "ERROR: SIM $target_slot phonebook not ready"
exit 1
fi
tlen="$(printf "%s\n" "$at_info" | sed -n 's/.*+CPBR:[[:space:]]*([0-9]*-[0-9]*),[0-9]*,\([0-9]*\).*/\1/p' | head -n1)"
if [ -z "$tlen" ] || [ "$tlen" -le 0 ] 2>/dev/null; then
tlen=14
fi
exported=0
while IFS="$(printf '\t')" read -r raw_num raw_name; do
num="$(printf "%s" "$raw_num" | tr -cd '+0-9*#')"
name="$(printf "%s" "$raw_name" | tr -d '"\r\n\t\\' | cut -c1-"$tlen")"
[ -n "$num" ] || continue
[ -n "$name" ] || name="$(printf "%s" "$num" | cut -c1-"$tlen")"
ton=129
case "$num" in
+*) ton=145 ;;
esac
res="$("$0" at auto "AT+CPBW=,\"${num}\",${ton},\"${name}\"" 2>/dev/null || true)"
if printf "%s\n" "$res" | grep -q "OK"; then
exported=$((exported + 1))
fi
done < "$src_tsv"
echo "EXPORTED=${exported}"
echo "SLOT=${target_slot}"
exit 0
echo "ERROR: SIM $target_slot phonebook not ready"
exit 1
;;
*)
Binary file not shown.
+88 -62
View File
@@ -908,34 +908,41 @@ int runContactsApp(int argc, char** argv) {
setStatus("Reading SIM " + std::to_string(slotNum) + " contacts...",
false, 0);
render();
int rc = 0;
std::string out = runCaptureCmd(
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-import " +
std::to_string(slotNum) + " 2>&1",
&rc);
if (visible) {
XRaiseWindow(dpy, win);
grabPluginKeyboard(dpy, win);
}
if (out.rfind("ERROR:", 0) == 0) {
std::string err = trimStr(out.substr(6));
setStatus(err.empty() ? "SIM import failed" : err, true);
render();
return;
}
std::vector<std::pair<std::string, std::string>> rows;
std::stringstream ss(out);
std::string line;
while (std::getline(ss, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
std::string qErr;
bool ok = qrtrReadSimContacts(slotNum, rows, qErr);
if (!ok) {
int rc = 0;
std::string out = runCaptureCmd(
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-import " +
std::to_string(slotNum) + " 2>&1",
&rc);
if (visible) {
XRaiseWindow(dpy, win);
grabPluginKeyboard(dpy, win);
}
size_t tab = line.find('\t');
if (tab == std::string::npos) continue;
std::string num = trimStr(line.substr(0, tab));
std::string name = trimStr(line.substr(tab + 1));
if (!num.empty()) {
rows.push_back({num, name.empty() ? num : name});
if (out.rfind("ERROR:", 0) == 0) {
std::string err = trimStr(out.substr(6));
setStatus(err.empty() ? (qErr.empty() ? "SIM import failed" : qErr)
: err,
true);
render();
return;
}
std::stringstream ss(out);
std::string line;
while (std::getline(ss, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
size_t tab = line.find('\t');
if (tab == std::string::npos) continue;
std::string num = trimStr(line.substr(0, tab));
std::string name = trimStr(line.substr(tab + 1));
if (!num.empty()) {
rows.push_back({num, name.empty() ? num : name});
}
}
}
if (rows.empty()) {
@@ -954,49 +961,65 @@ int runContactsApp(int argc, char** argv) {
}
render();
} else {
std::string srcFile = getContactsFilePath();
std::string tmpSingle;
std::vector<std::pair<std::string, std::string>> toExport;
if (pendingSimOp == SimOp::ExportSingle) {
if (selected < 0 || selected >= static_cast<int>(filtered.size())) {
return;
}
tmpSingle = "/tmp/sim_single_" + std::to_string(::getpid()) + ".tsv";
std::ofstream tf(tmpSingle, std::ios::trunc);
tf << filtered[selected].number << "\t" << filtered[selected].name
<< "\n";
tf.close();
srcFile = tmpSingle;
} else if (book.savedContacts().empty()) {
setStatus("Phonebook is empty", true);
render();
return;
toExport.push_back({filtered[selected].number, filtered[selected].name});
} else {
if (book.savedContacts().empty()) {
setStatus("Phonebook is empty", true);
render();
return;
}
for (const auto& c : book.savedContacts()) {
toExport.push_back({c.number, c.name});
}
}
setStatus("Writing to SIM " + std::to_string(slotNum) + "...", false, 0);
render();
int rc = 0;
std::string out = runCaptureCmd(
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-export " +
std::to_string(slotNum) + " " + shellQuote(srcFile) + " 2>&1",
&rc);
if (visible) {
XRaiseWindow(dpy, win);
grabPluginKeyboard(dpy, win);
}
if (!tmpSingle.empty()) {
::unlink(tmpSingle.c_str());
}
if (out.rfind("ERROR:", 0) == 0) {
std::string err = trimStr(out.substr(6));
setStatus(err.empty() ? "SIM export failed" : err, true);
render();
return;
}
int exported = 0;
std::stringstream ss(out);
std::string line;
while (std::getline(ss, line)) {
if (line.rfind("EXPORTED=", 0) == 0) {
exported = std::atoi(line.c_str() + 9);
std::string qErr;
bool ok = qrtrWriteSimContacts(slotNum, toExport, exported, qErr);
if (!ok && exported <= 0) {
std::string srcFile = getContactsFilePath();
std::string tmpSingle;
if (pendingSimOp == SimOp::ExportSingle) {
tmpSingle = "/tmp/sim_single_" + std::to_string(::getpid()) + ".tsv";
std::ofstream tf(tmpSingle, std::ios::trunc);
tf << toExport[0].first << "\t" << toExport[0].second << "\n";
tf.close();
srcFile = tmpSingle;
}
int rc = 0;
std::string out = runCaptureCmd(
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-export " +
std::to_string(slotNum) + " " + shellQuote(srcFile) + " 2>&1",
&rc);
if (visible) {
XRaiseWindow(dpy, win);
grabPluginKeyboard(dpy, win);
}
if (!tmpSingle.empty()) {
::unlink(tmpSingle.c_str());
}
if (out.rfind("ERROR:", 0) == 0) {
std::string err = trimStr(out.substr(6));
setStatus(err.empty() ? (qErr.empty() ? "SIM export failed" : qErr)
: err,
true);
render();
return;
}
std::stringstream ss(out);
std::string line;
while (std::getline(ss, line)) {
if (line.rfind("EXPORTED=", 0) == 0) {
exported = std::atoi(line.c_str() + 9);
}
}
}
if (exported > 0) {
@@ -1008,7 +1031,10 @@ int runContactsApp(int argc, char** argv) {
false);
}
} else {
setStatus("SIM " + std::to_string(slotNum) + " write failed", true);
setStatus(qErr.empty()
? ("SIM " + std::to_string(slotNum) + " write failed")
: qErr,
true);
}
render();
}
+622
View File
@@ -1762,6 +1762,628 @@ inline void cancelModemUssd() {
"|| mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1");
}
inline bool decodeEfAdnRecord(const uint8_t* raw, size_t len,
std::string& outNum, std::string& outName) {
if (!raw || len < 14) return false;
size_t alphaLen = len - 14;
uint8_t bcdLen = raw[alphaLen];
uint8_t tonNpi = raw[alphaLen + 1];
if (bcdLen == 0 || bcdLen > 11 || bcdLen == 0xFF) return false;
outNum.clear();
if (((tonNpi >> 4) & 0x07) == 1) {
outNum.push_back('+');
}
auto nibToChar = [](uint8_t nib) -> char {
if (nib <= 9) return static_cast<char>('0' + nib);
if (nib == 0x0A) return '*';
if (nib == 0x0B) return '#';
if (nib == 0x0C) return 'p';
return '\0';
};
size_t bcdBytes = std::min<size_t>(10, bcdLen - 1);
for (size_t i = 0; i < bcdBytes; ++i) {
uint8_t b = raw[alphaLen + 2 + i];
if (b == 0xFF) break;
char d1 = nibToChar(b & 0x0F);
if (!d1) break;
outNum.push_back(d1);
uint8_t hi = (b >> 4) & 0x0F;
if (hi == 0x0F) break;
char d2 = nibToChar(hi);
if (!d2) break;
outNum.push_back(d2);
}
if (outNum.empty() || outNum == "+") return false;
size_t aEnd = alphaLen;
while (aEnd > 0 && raw[aEnd - 1] == 0xFF) {
--aEnd;
}
outName.clear();
if (aEnd > 0 && raw[0] == 0x80) {
for (size_t i = 1; i + 1 < aEnd; i += 2) {
uint16_t cp = (static_cast<uint16_t>(raw[i]) << 8) |
static_cast<uint16_t>(raw[i + 1]);
if (cp == 0xFFFF || cp == 0) break;
if (cp < 0x80) {
if (cp >= 32 && cp != '\t') outName.push_back(static_cast<char>(cp));
} else if (cp < 0x800) {
outName.push_back(static_cast<char>(0xC0 | (cp >> 6)));
outName.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
} else {
outName.push_back(static_cast<char>(0xE0 | (cp >> 12)));
outName.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
outName.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
}
}
} else {
for (size_t i = 0; i < aEnd; ++i) {
uint8_t b = raw[i];
if (b == 0xFF) break;
if (b >= 32 && b < 127 && b != '\t') {
outName.push_back(static_cast<char>(b));
}
}
}
outName = trimStr(outName);
if (outName.empty()) outName = outNum;
return true;
}
inline bool encodeEfAdnRecord(const std::string& rawNum,
const std::string& rawName, size_t recSize,
std::vector<uint8_t>& outRec) {
if (recSize < 14) return false;
outRec.assign(recSize, 0xFF);
size_t alphaLen = recSize - 14;
std::string cleanNum = normalizePhone(rawNum);
if (cleanNum.empty()) return false;
bool isIntl = (cleanNum.front() == '+');
uint8_t tonNpi = isIntl ? 0x91 : 0x81;
std::vector<uint8_t> nibs;
for (char c : cleanNum) {
if (c == '+') continue;
if (c >= '0' && c <= '9') nibs.push_back(static_cast<uint8_t>(c - '0'));
else if (c == '*') nibs.push_back(0x0A);
else if (c == '#') nibs.push_back(0x0B);
if (nibs.size() >= 20) break;
}
if (nibs.empty()) return false;
std::string name = trimStr(rawName);
if (name.empty()) name = cleanNum;
bool pureAscii = true;
for (unsigned char uc : name) {
if (uc >= 0x80) {
pureAscii = false;
break;
}
}
if (pureAscii) {
size_t n = std::min(name.size(), alphaLen);
for (size_t i = 0; i < n; ++i) {
unsigned char uc = static_cast<unsigned char>(name[i]);
outRec[i] = (uc >= 32 && uc < 127) ? uc : static_cast<uint8_t>(' ');
}
} else if (alphaLen >= 3) {
outRec[0] = 0x80;
size_t outPos = 1;
size_t i = 0;
while (i < name.size() && outPos + 1 < alphaLen) {
uint32_t cp = 0;
unsigned char c0 = static_cast<unsigned char>(name[i]);
if (c0 < 0x80) {
cp = c0;
i += 1;
} else if ((c0 & 0xE0) == 0xC0 && i + 1 < name.size()) {
cp = ((c0 & 0x1F) << 6) |
(static_cast<unsigned char>(name[i + 1]) & 0x3F);
i += 2;
} else if ((c0 & 0xF0) == 0xE0 && i + 2 < name.size()) {
cp = ((c0 & 0x0F) << 12) |
((static_cast<unsigned char>(name[i + 1]) & 0x3F) << 6) |
(static_cast<unsigned char>(name[i + 2]) & 0x3F);
i += 3;
} else {
i += 1;
continue;
}
if (cp <= 0xFFFF) {
outRec[outPos++] = static_cast<uint8_t>((cp >> 8) & 0xFF);
outRec[outPos++] = static_cast<uint8_t>(cp & 0xFF);
}
}
}
size_t bcdBytes = (nibs.size() + 1) / 2;
outRec[alphaLen] = static_cast<uint8_t>(1 + bcdBytes);
outRec[alphaLen + 1] = tonNpi;
for (size_t i = 0; i < nibs.size(); ++i) {
size_t bIdx = alphaLen + 2 + (i / 2);
if ((i % 2) == 0) {
outRec[bIdx] = (outRec[bIdx] & 0xF0) | (nibs[i] & 0x0F);
} else {
outRec[bIdx] = (outRec[bIdx] & 0x0F) | ((nibs[i] & 0x0F) << 4);
}
}
return true;
}
inline bool openQrtrUimSocket(int& outSock, uint32_t& outNode,
uint32_t& outPort) {
constexpr int kAfQipcrtr = 42;
constexpr uint32_t kQrtrPortCtrl = 0xfffffffeu;
constexpr uint32_t kQrtrTypeNewServer = 4;
constexpr uint32_t kQrtrTypeNewLookup = 10;
constexpr uint32_t kQmiServiceUim = 11;
struct SockAddrQrtr {
unsigned short sq_family;
uint32_t sq_node;
uint32_t sq_port;
};
struct QrtrCtrlPkt {
uint32_t cmd;
uint32_t service;
uint32_t instance;
uint32_t node;
uint32_t port;
} __attribute__((packed));
int lfd = ::socket(kAfQipcrtr, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (lfd < 0) return false;
SockAddrQrtr localSq {};
socklen_t sl = sizeof(localSq);
if (::getsockname(lfd, reinterpret_cast<struct sockaddr*>(&localSq), &sl) < 0) {
::close(lfd);
return false;
}
SockAddrQrtr ctrlSq {};
ctrlSq.sq_family = kAfQipcrtr;
ctrlSq.sq_node = localSq.sq_node;
ctrlSq.sq_port = kQrtrPortCtrl;
QrtrCtrlPkt lookup {};
lookup.cmd = kQrtrTypeNewLookup;
lookup.service = kQmiServiceUim;
lookup.instance = 0;
if (::sendto(lfd, &lookup, sizeof(lookup), 0,
reinterpret_cast<const struct sockaddr*>(&ctrlSq),
sizeof(ctrlSq)) < 0) {
::close(lfd);
return false;
}
bool found = false;
for (int i = 0; i < 16; ++i) {
struct pollfd pfd { lfd, POLLIN, 0 };
if (::poll(&pfd, 1, 80) <= 0) break;
QrtrCtrlPkt pkt {};
ssize_t n = ::recv(lfd, &pkt, sizeof(pkt), 0);
if (n >= static_cast<ssize_t>(sizeof(pkt)) &&
pkt.cmd == kQrtrTypeNewServer) {
if (pkt.service == kQmiServiceUim && pkt.port != 0) {
outNode = pkt.node;
outPort = pkt.port;
found = true;
}
if (pkt.service == 0 && pkt.node == 0 && pkt.port == 0) {
break;
}
}
}
::close(lfd);
if (!found) return false;
outSock = ::socket(kAfQipcrtr, SOCK_DGRAM | SOCK_CLOEXEC, 0);
return outSock >= 0;
}
inline bool queryQrtrUimAdnGeometry(int sfd, uint32_t uimNode, uint32_t uimPort,
int slotNum, uint8_t& outSessionType,
std::vector<uint8_t>& outFileTlvVal,
uint16_t& outRecSize,
uint16_t& outRecCount) {
struct SockAddrQrtr {
unsigned short sq_family;
uint32_t sq_node;
uint32_t sq_port;
} uimSq { 42, uimNode, uimPort };
uint8_t sessionCandidates[2] = {
static_cast<uint8_t>((slotNum == 2) ? 7 : 6),
0
};
struct FilePathSpec {
uint16_t fileId;
uint8_t pathLen;
uint8_t pathBytes[6];
} fileSpecs[2] = {
{ 0x6F3A, 4, { 0x00, 0x3F, 0x10, 0x7F, 0x00, 0x00 } },
{ 0x4F3A, 6, { 0x00, 0x3F, 0x10, 0x7F, 0x3A, 0x5F } }
};
uint16_t txnId = 1;
for (uint8_t stype : sessionCandidates) {
for (const auto& fs : fileSpecs) {
std::vector<uint8_t> fval;
fval.push_back(static_cast<uint8_t>(fs.fileId & 0xFF));
fval.push_back(static_cast<uint8_t>((fs.fileId >> 8) & 0xFF));
fval.push_back(fs.pathLen);
for (uint8_t k = 0; k < fs.pathLen; ++k) {
fval.push_back(fs.pathBytes[k]);
}
std::vector<uint8_t> req(7);
req[0] = 0x00;
req[1] = static_cast<uint8_t>(txnId & 0xFF);
req[2] = static_cast<uint8_t>((txnId >> 8) & 0xFF);
++txnId;
req[3] = 0x24;
req[4] = 0x00;
// TLV 0x01: Session
req.push_back(0x01);
req.push_back(0x02);
req.push_back(0x00);
req.push_back(stype);
req.push_back(0x00);
// TLV 0x02: File
req.push_back(0x02);
req.push_back(static_cast<uint8_t>(fval.size() & 0xFF));
req.push_back(static_cast<uint8_t>((fval.size() >> 8) & 0xFF));
req.insert(req.end(), fval.begin(), fval.end());
uint16_t tlvTotal = static_cast<uint16_t>(req.size() - 7);
req[5] = static_cast<uint8_t>(tlvTotal & 0xFF);
req[6] = static_cast<uint8_t>((tlvTotal >> 8) & 0xFF);
if (::sendto(sfd, req.data(), req.size(), 0,
reinterpret_cast<const struct sockaddr*>(&uimSq),
sizeof(uimSq)) < 0) {
continue;
}
struct pollfd pfd { sfd, POLLIN, 0 };
if (::poll(&pfd, 1, 120) <= 0) continue;
uint8_t resp[512];
ssize_t rlen = ::recv(sfd, resp, sizeof(resp), 0);
if (rlen < 14 || resp[0] != 0x02 || resp[3] != 0x24) continue;
bool okResult = false;
uint16_t rSize = 0;
uint16_t rCount = 0;
size_t pos = 7;
while (pos + 3 <= static_cast<size_t>(rlen)) {
uint8_t tt = resp[pos];
uint16_t tl = static_cast<uint16_t>(resp[pos + 1]) |
(static_cast<uint16_t>(resp[pos + 2]) << 8);
pos += 3;
if (pos + tl > static_cast<size_t>(rlen)) break;
if (tt == 0x02 && tl >= 4) {
uint16_t res = static_cast<uint16_t>(resp[pos]) |
(static_cast<uint16_t>(resp[pos + 1]) << 8);
uint16_t err = static_cast<uint16_t>(resp[pos + 2]) |
(static_cast<uint16_t>(resp[pos + 3]) << 8);
if (res == 0 && err == 0) okResult = true;
} else if (tt == 0x11 && tl >= 9) {
rSize = static_cast<uint16_t>(resp[pos + 5]) |
(static_cast<uint16_t>(resp[pos + 6]) << 8);
rCount = static_cast<uint16_t>(resp[pos + 7]) |
(static_cast<uint16_t>(resp[pos + 8]) << 8);
}
pos += tl;
}
if (okResult && rSize >= 14 && rCount >= 1) {
outSessionType = stype;
outFileTlvVal = fval;
outRecSize = rSize;
outRecCount = std::min<uint16_t>(rCount, 500);
return true;
}
}
}
return false;
}
inline bool qrtrReadSimRecord(int sfd, uint32_t uimNode, uint32_t uimPort,
uint16_t txnId, uint8_t sessionType,
const std::vector<uint8_t>& fileTlvVal,
uint16_t recNum, uint16_t recSize,
std::vector<uint8_t>& outData) {
struct SockAddrQrtr {
unsigned short sq_family;
uint32_t sq_node;
uint32_t sq_port;
} uimSq { 42, uimNode, uimPort };
std::vector<uint8_t> req(7);
req[0] = 0x00;
req[1] = static_cast<uint8_t>(txnId & 0xFF);
req[2] = static_cast<uint8_t>((txnId >> 8) & 0xFF);
req[3] = 0x21; // QMI_UIM_READ_RECORD
req[4] = 0x00;
// TLV 0x01: Session
req.push_back(0x01);
req.push_back(0x02);
req.push_back(0x00);
req.push_back(sessionType);
req.push_back(0x00);
// TLV 0x02: File
req.push_back(0x02);
req.push_back(static_cast<uint8_t>(fileTlvVal.size() & 0xFF));
req.push_back(static_cast<uint8_t>((fileTlvVal.size() >> 8) & 0xFF));
req.insert(req.end(), fileTlvVal.begin(), fileTlvVal.end());
// TLV 0x03: Record (record_number u16 LE, record_length u16 LE)
req.push_back(0x03);
req.push_back(0x04);
req.push_back(0x00);
req.push_back(static_cast<uint8_t>(recNum & 0xFF));
req.push_back(static_cast<uint8_t>((recNum >> 8) & 0xFF));
req.push_back(static_cast<uint8_t>(recSize & 0xFF));
req.push_back(static_cast<uint8_t>((recSize >> 8) & 0xFF));
uint16_t tlvTotal = static_cast<uint16_t>(req.size() - 7);
req[5] = static_cast<uint8_t>(tlvTotal & 0xFF);
req[6] = static_cast<uint8_t>((tlvTotal >> 8) & 0xFF);
if (::sendto(sfd, req.data(), req.size(), 0,
reinterpret_cast<const struct sockaddr*>(&uimSq),
sizeof(uimSq)) < 0) {
return false;
}
struct pollfd pfd { sfd, POLLIN, 0 };
if (::poll(&pfd, 1, 120) <= 0) return false;
uint8_t resp[512];
ssize_t rlen = ::recv(sfd, resp, sizeof(resp), 0);
if (rlen < 14 || resp[0] != 0x02 || resp[3] != 0x21) return false;
bool okResult = false;
outData.clear();
size_t pos = 7;
while (pos + 3 <= static_cast<size_t>(rlen)) {
uint8_t tt = resp[pos];
uint16_t tl = static_cast<uint16_t>(resp[pos + 1]) |
(static_cast<uint16_t>(resp[pos + 2]) << 8);
pos += 3;
if (pos + tl > static_cast<size_t>(rlen)) break;
if (tt == 0x02 && tl >= 4) {
uint16_t res = static_cast<uint16_t>(resp[pos]) |
(static_cast<uint16_t>(resp[pos + 1]) << 8);
uint16_t err = static_cast<uint16_t>(resp[pos + 2]) |
(static_cast<uint16_t>(resp[pos + 3]) << 8);
if (res == 0 && err == 0) okResult = true;
} else if (tt == 0x11 && tl >= 2) {
uint16_t clen = static_cast<uint16_t>(resp[pos]) |
(static_cast<uint16_t>(resp[pos + 1]) << 8);
if (2 + clen <= tl) {
outData.assign(resp + pos + 2, resp + pos + 2 + clen);
}
}
pos += tl;
}
return okResult && !outData.empty();
}
inline bool qrtrWriteSimRecord(int sfd, uint32_t uimNode, uint32_t uimPort,
uint16_t txnId, uint8_t sessionType,
const std::vector<uint8_t>& fileTlvVal,
uint16_t recNum,
const std::vector<uint8_t>& recData) {
struct SockAddrQrtr {
unsigned short sq_family;
uint32_t sq_node;
uint32_t sq_port;
} uimSq { 42, uimNode, uimPort };
std::vector<uint8_t> req(7);
req[0] = 0x00;
req[1] = static_cast<uint8_t>(txnId & 0xFF);
req[2] = static_cast<uint8_t>((txnId >> 8) & 0xFF);
req[3] = 0x23; // QMI_UIM_WRITE_RECORD
req[4] = 0x00;
// TLV 0x01: Session
req.push_back(0x01);
req.push_back(0x02);
req.push_back(0x00);
req.push_back(sessionType);
req.push_back(0x00);
// TLV 0x02: File
req.push_back(0x02);
req.push_back(static_cast<uint8_t>(fileTlvVal.size() & 0xFF));
req.push_back(static_cast<uint8_t>((fileTlvVal.size() >> 8) & 0xFF));
req.insert(req.end(), fileTlvVal.begin(), fileTlvVal.end());
// TLV 0x03: Write Record (record_number u16 LE, data_len u16 LE, data[])
uint16_t wTlvLen = static_cast<uint16_t>(4 + recData.size());
req.push_back(0x03);
req.push_back(static_cast<uint8_t>(wTlvLen & 0xFF));
req.push_back(static_cast<uint8_t>((wTlvLen >> 8) & 0xFF));
req.push_back(static_cast<uint8_t>(recNum & 0xFF));
req.push_back(static_cast<uint8_t>((recNum >> 8) & 0xFF));
uint16_t dLen = static_cast<uint16_t>(recData.size());
req.push_back(static_cast<uint8_t>(dLen & 0xFF));
req.push_back(static_cast<uint8_t>((dLen >> 8) & 0xFF));
req.insert(req.end(), recData.begin(), recData.end());
uint16_t tlvTotal = static_cast<uint16_t>(req.size() - 7);
req[5] = static_cast<uint8_t>(tlvTotal & 0xFF);
req[6] = static_cast<uint8_t>((tlvTotal >> 8) & 0xFF);
if (::sendto(sfd, req.data(), req.size(), 0,
reinterpret_cast<const struct sockaddr*>(&uimSq),
sizeof(uimSq)) < 0) {
return false;
}
struct pollfd pfd { sfd, POLLIN, 0 };
if (::poll(&pfd, 1, 200) <= 0) return false;
uint8_t resp[512];
ssize_t rlen = ::recv(sfd, resp, sizeof(resp), 0);
if (rlen < 14 || resp[0] != 0x02 || resp[3] != 0x23) return false;
bool okResult = false;
size_t pos = 7;
while (pos + 3 <= static_cast<size_t>(rlen)) {
uint8_t tt = resp[pos];
uint16_t tl = static_cast<uint16_t>(resp[pos + 1]) |
(static_cast<uint16_t>(resp[pos + 2]) << 8);
pos += 3;
if (pos + tl > static_cast<size_t>(rlen)) break;
if (tt == 0x02 && tl >= 4) {
uint16_t res = static_cast<uint16_t>(resp[pos]) |
(static_cast<uint16_t>(resp[pos + 1]) << 8);
uint16_t err = static_cast<uint16_t>(resp[pos + 2]) |
(static_cast<uint16_t>(resp[pos + 3]) << 8);
if (res == 0 && err == 0) okResult = true;
}
pos += tl;
}
return okResult;
}
inline bool qrtrReadSimContacts(
int slotNum, std::vector<std::pair<std::string, std::string>>& outContacts,
std::string& outErr) {
outContacts.clear();
outErr.clear();
int sfd = -1;
uint32_t uimNode = 0, uimPort = 0;
if (!openQrtrUimSocket(sfd, uimNode, uimPort)) {
outErr = "Modem UIM service unavailable";
return false;
}
uint8_t sessionType = 0;
std::vector<uint8_t> fileTlvVal;
uint16_t recSize = 0, recCount = 0;
if (!queryQrtrUimAdnGeometry(sfd, uimNode, uimPort, slotNum, sessionType,
fileTlvVal, recSize, recCount)) {
::close(sfd);
outErr = "SIM " + std::to_string(slotNum) + " phonebook not ready";
return false;
}
std::vector<uint8_t> raw;
for (uint16_t rnum = 1; rnum <= recCount; ++rnum) {
if (qrtrReadSimRecord(sfd, uimNode, uimPort, static_cast<uint16_t>(10 + rnum),
sessionType, fileTlvVal, rnum, recSize, raw)) {
std::string num, name;
if (decodeEfAdnRecord(raw.data(), raw.size(), num, name)) {
outContacts.push_back({num, name});
}
}
}
::close(sfd);
return true;
}
inline bool qrtrWriteSimContacts(
int slotNum,
const std::vector<std::pair<std::string, std::string>>& contactsToWrite,
int& outExported, std::string& outErr) {
outExported = 0;
outErr.clear();
if (contactsToWrite.empty()) {
outErr = "No contacts to export";
return false;
}
int sfd = -1;
uint32_t uimNode = 0, uimPort = 0;
if (!openQrtrUimSocket(sfd, uimNode, uimPort)) {
outErr = "Modem UIM service unavailable";
return false;
}
uint8_t sessionType = 0;
std::vector<uint8_t> fileTlvVal;
uint16_t recSize = 0, recCount = 0;
if (!queryQrtrUimAdnGeometry(sfd, uimNode, uimPort, slotNum, sessionType,
fileTlvVal, recSize, recCount)) {
::close(sfd);
outErr = "SIM " + std::to_string(slotNum) + " phonebook not ready";
return false;
}
std::unordered_map<std::string, uint16_t> existingByNorm;
std::unordered_map<std::string, uint16_t> existingBySuffix;
std::vector<uint16_t> emptySlots;
std::vector<uint8_t> raw;
for (uint16_t rnum = 1; rnum <= recCount; ++rnum) {
if (qrtrReadSimRecord(sfd, uimNode, uimPort, static_cast<uint16_t>(10 + rnum),
sessionType, fileTlvVal, rnum, recSize, raw)) {
std::string num, name;
if (decodeEfAdnRecord(raw.data(), raw.size(), num, name)) {
std::string norm = normalizePhone(num);
if (!norm.empty()) {
existingByNorm[norm] = rnum;
std::string sfx = phoneSuffixKey(norm);
if (!sfx.empty()) existingBySuffix[sfx] = rnum;
}
} else {
emptySlots.push_back(rnum);
}
}
}
size_t emptyIdx = 0;
uint16_t txnId = static_cast<uint16_t>(1000);
bool simFull = false;
for (const auto& kv : contactsToWrite) {
std::string norm = normalizePhone(kv.first);
if (norm.empty()) continue;
uint16_t targetRec = 0;
auto it = existingByNorm.find(norm);
if (it != existingByNorm.end()) {
targetRec = it->second;
} else {
std::string sfx = phoneSuffixKey(norm);
auto sit = sfx.empty() ? existingBySuffix.end()
: existingBySuffix.find(sfx);
if (sit != existingBySuffix.end()) {
targetRec = sit->second;
} else if (emptyIdx < emptySlots.size()) {
targetRec = emptySlots[emptyIdx++];
} else {
simFull = true;
break;
}
}
std::vector<uint8_t> recData;
if (!encodeEfAdnRecord(kv.first, kv.second, recSize, recData)) {
continue;
}
if (qrtrWriteSimRecord(sfd, uimNode, uimPort, txnId++, sessionType,
fileTlvVal, targetRec, recData)) {
++outExported;
existingByNorm[norm] = targetRec;
std::string sfx = phoneSuffixKey(norm);
if (!sfx.empty()) existingBySuffix[sfx] = targetRec;
}
}
::close(sfd);
if (outExported == 0 && simFull) {
outErr = "SIM " + std::to_string(slotNum) + " phonebook is full";
return false;
}
return outExported > 0;
}
} // namespace bananarchy
#endif // DWM_PLUGINS_PHONE_DATA_H