contacts, modem: implement direct QRTR QMI UIM SIM phonebook import and export for SIM 1 and SIM 2
This commit is contained in:
@@ -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
|
||||
;;
|
||||
|
||||
*)
|
||||
|
||||
Reference in New Issue
Block a user