contacts, modem: implement direct QRTR QMI UIM SIM phonebook import and export for SIM 1 and SIM 2
This commit is contained in:
Binary file not shown.
@@ -77,6 +77,9 @@ detect_qmicli_modem() {
|
|||||||
if timeout 3 qmicli --silent -pd qrtr://0 --uim-noop >/dev/null 2>&1; then
|
if timeout 3 qmicli --silent -pd qrtr://0 --uim-noop >/dev/null 2>&1; then
|
||||||
QMICLI_MODEM="timeout 4 qmicli --silent -pd qrtr://0"
|
QMICLI_MODEM="timeout 4 qmicli --silent -pd qrtr://0"
|
||||||
return 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
|
elif [ -e "/dev/modem" ]; then
|
||||||
if timeout 3 qmicli --silent -pd /dev/modem --uim-noop >/dev/null 2>&1; 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"
|
QMICLI_MODEM="timeout 4 qmicli --silent -pd /dev/modem"
|
||||||
@@ -1181,6 +1184,151 @@ case "$cmd" in
|
|||||||
fi
|
fi
|
||||||
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_slot="$PRIMARY_GW_SLOT"
|
||||||
orig_gw_aid=""
|
orig_gw_aid=""
|
||||||
if [ "$orig_gw_slot" = "1" ]; then
|
if [ "$orig_gw_slot" = "1" ]; then
|
||||||
@@ -1210,57 +1358,7 @@ case "$cmd" in
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tmp_contacts="/tmp/sim_import_$$.tsv"
|
# Method 2: Direct qmicli UIM EF_ADN (3F00-7F10-6F3A) record fallback
|
||||||
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
|
|
||||||
if [ ! -s "$tmp_contacts" ]; then
|
if [ ! -s "$tmp_contacts" ]; then
|
||||||
adn_attr="$($QMICLI_MODEM --uim-get-file-attributes=0x3F00,0x7F10,0x6F3A 2>/dev/null || true)"
|
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}')"
|
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
|
fi
|
||||||
rec=1
|
rec=1
|
||||||
while [ "$rec" -le "$reccount" ]; do
|
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
|
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) {
|
function hex2val(h, i, c, v, res) {
|
||||||
res = 0
|
res = 0
|
||||||
h = toupper(h)
|
h = toupper(h)
|
||||||
@@ -1291,15 +1389,21 @@ case "$cmd" in
|
|||||||
if (n == 11) return "#"
|
if (n == 11) return "#"
|
||||||
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
|
line = $0
|
||||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
|
||||||
n = split(line, hex, ":")
|
sub(/:$/, "", line)
|
||||||
if (n < 14) next
|
full = (full == "") ? line : (full ":" line)
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (full == "") exit
|
||||||
|
n = split(full, hex, ":")
|
||||||
|
if (n < 14) exit
|
||||||
alpha_len = n - 14
|
alpha_len = n - 14
|
||||||
bcd_len = hex2val(hex[alpha_len + 1])
|
bcd_len = hex2val(hex[alpha_len + 1])
|
||||||
ton_npi = hex2val(hex[alpha_len + 2])
|
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 = ""
|
num = ""
|
||||||
if (int(ton_npi / 16) % 8 == 1) {
|
if (int(ton_npi / 16) % 8 == 1) {
|
||||||
num = "+"
|
num = "+"
|
||||||
@@ -1318,7 +1422,7 @@ case "$cmd" in
|
|||||||
if (d2 == "") break
|
if (d2 == "") break
|
||||||
num = num d2
|
num = num d2
|
||||||
}
|
}
|
||||||
if (num == "" || num == "+") next
|
if (num == "" || num == "+") exit
|
||||||
name = ""
|
name = ""
|
||||||
for (i = 1; i <= alpha_len; i++) {
|
for (i = 1; i <= alpha_len; i++) {
|
||||||
b = hex2val(hex[i])
|
b = hex2val(hex[i])
|
||||||
@@ -1396,64 +1500,234 @@ case "$cmd" in
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
orig_gw_slot="$PRIMARY_GW_SLOT"
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
orig_gw_aid=""
|
exp_out="$(python3 - "$target_slot" "$src_tsv" << 'PY'
|
||||||
if [ "$orig_gw_slot" = "1" ]; then
|
import socket, struct, select, sys
|
||||||
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 [ "$PRIMARY_GW_SLOT" != "$target_slot" ] && [ -n "$target_aid" ]; then
|
slot = int(sys.argv[1]) if len(sys.argv) > 1 and sys.argv[1].isdigit() else 1
|
||||||
if [ "$PRIMARY_GW_SLOT" != "0" ]; then
|
tsv_path = sys.argv[2]
|
||||||
$QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true
|
|
||||||
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
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
at_info="$("$0" at auto 'AT+CMEE=2;+CSCS="GSM";+CPBS="SM";+CPBR=?' 2>/dev/null || true)"
|
rows = []
|
||||||
if printf "%s\n" "$at_info" | grep -qE "ERROR"; then
|
with open(tsv_path, "r", encoding="utf-8", errors="replace") as f:
|
||||||
echo "ERROR: SIM $target_slot phonebook not ready"
|
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 printf "%s\n" "$exp_out" | grep -q "^ERROR:"; then
|
||||||
|
printf "%s\n" "$exp_out"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
fi
|
||||||
|
|
||||||
exported=0
|
echo "ERROR: SIM $target_slot phonebook not ready"
|
||||||
while IFS="$(printf '\t')" read -r raw_num raw_name; do
|
exit 1
|
||||||
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
|
|
||||||
;;
|
;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
|
|||||||
Binary file not shown.
@@ -908,6 +908,11 @@ int runContactsApp(int argc, char** argv) {
|
|||||||
setStatus("Reading SIM " + std::to_string(slotNum) + " contacts...",
|
setStatus("Reading SIM " + std::to_string(slotNum) + " contacts...",
|
||||||
false, 0);
|
false, 0);
|
||||||
render();
|
render();
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>> rows;
|
||||||
|
std::string qErr;
|
||||||
|
bool ok = qrtrReadSimContacts(slotNum, rows, qErr);
|
||||||
|
if (!ok) {
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
std::string out = runCaptureCmd(
|
std::string out = runCaptureCmd(
|
||||||
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-import " +
|
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-import " +
|
||||||
@@ -919,11 +924,12 @@ int runContactsApp(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
if (out.rfind("ERROR:", 0) == 0) {
|
if (out.rfind("ERROR:", 0) == 0) {
|
||||||
std::string err = trimStr(out.substr(6));
|
std::string err = trimStr(out.substr(6));
|
||||||
setStatus(err.empty() ? "SIM import failed" : err, true);
|
setStatus(err.empty() ? (qErr.empty() ? "SIM import failed" : qErr)
|
||||||
|
: err,
|
||||||
|
true);
|
||||||
render();
|
render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::vector<std::pair<std::string, std::string>> rows;
|
|
||||||
std::stringstream ss(out);
|
std::stringstream ss(out);
|
||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(ss, line)) {
|
while (std::getline(ss, line)) {
|
||||||
@@ -938,6 +944,7 @@ int runContactsApp(int argc, char** argv) {
|
|||||||
rows.push_back({num, name.empty() ? num : name});
|
rows.push_back({num, name.empty() ? num : name});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (rows.empty()) {
|
if (rows.empty()) {
|
||||||
setStatus("No contacts on SIM " + std::to_string(slotNum), false);
|
setStatus("No contacts on SIM " + std::to_string(slotNum), false);
|
||||||
} else {
|
} else {
|
||||||
@@ -954,25 +961,39 @@ int runContactsApp(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
render();
|
render();
|
||||||
} else {
|
} else {
|
||||||
std::string srcFile = getContactsFilePath();
|
std::vector<std::pair<std::string, std::string>> toExport;
|
||||||
std::string tmpSingle;
|
|
||||||
if (pendingSimOp == SimOp::ExportSingle) {
|
if (pendingSimOp == SimOp::ExportSingle) {
|
||||||
if (selected < 0 || selected >= static_cast<int>(filtered.size())) {
|
if (selected < 0 || selected >= static_cast<int>(filtered.size())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tmpSingle = "/tmp/sim_single_" + std::to_string(::getpid()) + ".tsv";
|
toExport.push_back({filtered[selected].number, filtered[selected].name});
|
||||||
std::ofstream tf(tmpSingle, std::ios::trunc);
|
} else {
|
||||||
tf << filtered[selected].number << "\t" << filtered[selected].name
|
if (book.savedContacts().empty()) {
|
||||||
<< "\n";
|
|
||||||
tf.close();
|
|
||||||
srcFile = tmpSingle;
|
|
||||||
} else if (book.savedContacts().empty()) {
|
|
||||||
setStatus("Phonebook is empty", true);
|
setStatus("Phonebook is empty", true);
|
||||||
render();
|
render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
for (const auto& c : book.savedContacts()) {
|
||||||
|
toExport.push_back({c.number, c.name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setStatus("Writing to SIM " + std::to_string(slotNum) + "...", false, 0);
|
setStatus("Writing to SIM " + std::to_string(slotNum) + "...", false, 0);
|
||||||
render();
|
render();
|
||||||
|
|
||||||
|
int exported = 0;
|
||||||
|
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;
|
int rc = 0;
|
||||||
std::string out = runCaptureCmd(
|
std::string out = runCaptureCmd(
|
||||||
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-export " +
|
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-export " +
|
||||||
@@ -987,11 +1008,12 @@ int runContactsApp(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
if (out.rfind("ERROR:", 0) == 0) {
|
if (out.rfind("ERROR:", 0) == 0) {
|
||||||
std::string err = trimStr(out.substr(6));
|
std::string err = trimStr(out.substr(6));
|
||||||
setStatus(err.empty() ? "SIM export failed" : err, true);
|
setStatus(err.empty() ? (qErr.empty() ? "SIM export failed" : qErr)
|
||||||
|
: err,
|
||||||
|
true);
|
||||||
render();
|
render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int exported = 0;
|
|
||||||
std::stringstream ss(out);
|
std::stringstream ss(out);
|
||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(ss, line)) {
|
while (std::getline(ss, line)) {
|
||||||
@@ -999,6 +1021,7 @@ int runContactsApp(int argc, char** argv) {
|
|||||||
exported = std::atoi(line.c_str() + 9);
|
exported = std::atoi(line.c_str() + 9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (exported > 0) {
|
if (exported > 0) {
|
||||||
if (pendingSimOp == SimOp::ExportSingle) {
|
if (pendingSimOp == SimOp::ExportSingle) {
|
||||||
setStatus("Copied contact to SIM " + std::to_string(slotNum), false);
|
setStatus("Copied contact to SIM " + std::to_string(slotNum), false);
|
||||||
@@ -1008,7 +1031,10 @@ int runContactsApp(int argc, char** argv) {
|
|||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setStatus("SIM " + std::to_string(slotNum) + " write failed", true);
|
setStatus(qErr.empty()
|
||||||
|
? ("SIM " + std::to_string(slotNum) + " write failed")
|
||||||
|
: qErr,
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1762,6 +1762,628 @@ inline void cancelModemUssd() {
|
|||||||
"|| mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1");
|
"|| 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
|
} // namespace bananarchy
|
||||||
|
|
||||||
#endif // DWM_PLUGINS_PHONE_DATA_H
|
#endif // DWM_PLUGINS_PHONE_DATA_H
|
||||||
|
|||||||
Reference in New Issue
Block a user