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
|
||||
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.
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user