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

This commit is contained in:
Luxferre
2026-10-25 15:30:24 +02:00
parent ef18a33c9b
commit 7c707844cc
5 changed files with 1095 additions and 173 deletions
+88 -62
View File
@@ -908,34 +908,41 @@ int runContactsApp(int argc, char** argv) {
setStatus("Reading SIM " + std::to_string(slotNum) + " contacts...",
false, 0);
render();
int rc = 0;
std::string out = runCaptureCmd(
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-import " +
std::to_string(slotNum) + " 2>&1",
&rc);
if (visible) {
XRaiseWindow(dpy, win);
grabPluginKeyboard(dpy, win);
}
if (out.rfind("ERROR:", 0) == 0) {
std::string err = trimStr(out.substr(6));
setStatus(err.empty() ? "SIM import failed" : err, true);
render();
return;
}
std::vector<std::pair<std::string, std::string>> rows;
std::stringstream ss(out);
std::string line;
while (std::getline(ss, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
std::string qErr;
bool ok = qrtrReadSimContacts(slotNum, rows, qErr);
if (!ok) {
int rc = 0;
std::string out = runCaptureCmd(
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-import " +
std::to_string(slotNum) + " 2>&1",
&rc);
if (visible) {
XRaiseWindow(dpy, win);
grabPluginKeyboard(dpy, win);
}
size_t tab = line.find('\t');
if (tab == std::string::npos) continue;
std::string num = trimStr(line.substr(0, tab));
std::string name = trimStr(line.substr(tab + 1));
if (!num.empty()) {
rows.push_back({num, name.empty() ? num : name});
if (out.rfind("ERROR:", 0) == 0) {
std::string err = trimStr(out.substr(6));
setStatus(err.empty() ? (qErr.empty() ? "SIM import failed" : qErr)
: err,
true);
render();
return;
}
std::stringstream ss(out);
std::string line;
while (std::getline(ss, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
size_t tab = line.find('\t');
if (tab == std::string::npos) continue;
std::string num = trimStr(line.substr(0, tab));
std::string name = trimStr(line.substr(tab + 1));
if (!num.empty()) {
rows.push_back({num, name.empty() ? num : name});
}
}
}
if (rows.empty()) {
@@ -954,49 +961,65 @@ int runContactsApp(int argc, char** argv) {
}
render();
} else {
std::string srcFile = getContactsFilePath();
std::string tmpSingle;
std::vector<std::pair<std::string, std::string>> toExport;
if (pendingSimOp == SimOp::ExportSingle) {
if (selected < 0 || selected >= static_cast<int>(filtered.size())) {
return;
}
tmpSingle = "/tmp/sim_single_" + std::to_string(::getpid()) + ".tsv";
std::ofstream tf(tmpSingle, std::ios::trunc);
tf << filtered[selected].number << "\t" << filtered[selected].name
<< "\n";
tf.close();
srcFile = tmpSingle;
} else if (book.savedContacts().empty()) {
setStatus("Phonebook is empty", true);
render();
return;
toExport.push_back({filtered[selected].number, filtered[selected].name});
} else {
if (book.savedContacts().empty()) {
setStatus("Phonebook is empty", true);
render();
return;
}
for (const auto& c : book.savedContacts()) {
toExport.push_back({c.number, c.name});
}
}
setStatus("Writing to SIM " + std::to_string(slotNum) + "...", false, 0);
render();
int rc = 0;
std::string out = runCaptureCmd(
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-export " +
std::to_string(slotNum) + " " + shellQuote(srcFile) + " 2>&1",
&rc);
if (visible) {
XRaiseWindow(dpy, win);
grabPluginKeyboard(dpy, win);
}
if (!tmpSingle.empty()) {
::unlink(tmpSingle.c_str());
}
if (out.rfind("ERROR:", 0) == 0) {
std::string err = trimStr(out.substr(6));
setStatus(err.empty() ? "SIM export failed" : err, true);
render();
return;
}
int exported = 0;
std::stringstream ss(out);
std::string line;
while (std::getline(ss, line)) {
if (line.rfind("EXPORTED=", 0) == 0) {
exported = std::atoi(line.c_str() + 9);
std::string qErr;
bool ok = qrtrWriteSimContacts(slotNum, toExport, exported, qErr);
if (!ok && exported <= 0) {
std::string srcFile = getContactsFilePath();
std::string tmpSingle;
if (pendingSimOp == SimOp::ExportSingle) {
tmpSingle = "/tmp/sim_single_" + std::to_string(::getpid()) + ".tsv";
std::ofstream tf(tmpSingle, std::ios::trunc);
tf << toExport[0].first << "\t" << toExport[0].second << "\n";
tf.close();
srcFile = tmpSingle;
}
int rc = 0;
std::string out = runCaptureCmd(
"doas -n /usr/libexec/msm-modem-uim-selection sim-contacts-export " +
std::to_string(slotNum) + " " + shellQuote(srcFile) + " 2>&1",
&rc);
if (visible) {
XRaiseWindow(dpy, win);
grabPluginKeyboard(dpy, win);
}
if (!tmpSingle.empty()) {
::unlink(tmpSingle.c_str());
}
if (out.rfind("ERROR:", 0) == 0) {
std::string err = trimStr(out.substr(6));
setStatus(err.empty() ? (qErr.empty() ? "SIM export failed" : qErr)
: err,
true);
render();
return;
}
std::stringstream ss(out);
std::string line;
while (std::getline(ss, line)) {
if (line.rfind("EXPORTED=", 0) == 0) {
exported = std::atoi(line.c_str() + 9);
}
}
}
if (exported > 0) {
@@ -1008,7 +1031,10 @@ int runContactsApp(int argc, char** argv) {
false);
}
} else {
setStatus("SIM " + std::to_string(slotNum) + " write failed", true);
setStatus(qErr.empty()
? ("SIM " + std::to_string(slotNum) + " write failed")
: qErr,
true);
}
render();
}
+622
View File
@@ -1762,6 +1762,628 @@ inline void cancelModemUssd() {
"|| mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1");
}
inline bool decodeEfAdnRecord(const uint8_t* raw, size_t len,
std::string& outNum, std::string& outName) {
if (!raw || len < 14) return false;
size_t alphaLen = len - 14;
uint8_t bcdLen = raw[alphaLen];
uint8_t tonNpi = raw[alphaLen + 1];
if (bcdLen == 0 || bcdLen > 11 || bcdLen == 0xFF) return false;
outNum.clear();
if (((tonNpi >> 4) & 0x07) == 1) {
outNum.push_back('+');
}
auto nibToChar = [](uint8_t nib) -> char {
if (nib <= 9) return static_cast<char>('0' + nib);
if (nib == 0x0A) return '*';
if (nib == 0x0B) return '#';
if (nib == 0x0C) return 'p';
return '\0';
};
size_t bcdBytes = std::min<size_t>(10, bcdLen - 1);
for (size_t i = 0; i < bcdBytes; ++i) {
uint8_t b = raw[alphaLen + 2 + i];
if (b == 0xFF) break;
char d1 = nibToChar(b & 0x0F);
if (!d1) break;
outNum.push_back(d1);
uint8_t hi = (b >> 4) & 0x0F;
if (hi == 0x0F) break;
char d2 = nibToChar(hi);
if (!d2) break;
outNum.push_back(d2);
}
if (outNum.empty() || outNum == "+") return false;
size_t aEnd = alphaLen;
while (aEnd > 0 && raw[aEnd - 1] == 0xFF) {
--aEnd;
}
outName.clear();
if (aEnd > 0 && raw[0] == 0x80) {
for (size_t i = 1; i + 1 < aEnd; i += 2) {
uint16_t cp = (static_cast<uint16_t>(raw[i]) << 8) |
static_cast<uint16_t>(raw[i + 1]);
if (cp == 0xFFFF || cp == 0) break;
if (cp < 0x80) {
if (cp >= 32 && cp != '\t') outName.push_back(static_cast<char>(cp));
} else if (cp < 0x800) {
outName.push_back(static_cast<char>(0xC0 | (cp >> 6)));
outName.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
} else {
outName.push_back(static_cast<char>(0xE0 | (cp >> 12)));
outName.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
outName.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
}
}
} else {
for (size_t i = 0; i < aEnd; ++i) {
uint8_t b = raw[i];
if (b == 0xFF) break;
if (b >= 32 && b < 127 && b != '\t') {
outName.push_back(static_cast<char>(b));
}
}
}
outName = trimStr(outName);
if (outName.empty()) outName = outNum;
return true;
}
inline bool encodeEfAdnRecord(const std::string& rawNum,
const std::string& rawName, size_t recSize,
std::vector<uint8_t>& outRec) {
if (recSize < 14) return false;
outRec.assign(recSize, 0xFF);
size_t alphaLen = recSize - 14;
std::string cleanNum = normalizePhone(rawNum);
if (cleanNum.empty()) return false;
bool isIntl = (cleanNum.front() == '+');
uint8_t tonNpi = isIntl ? 0x91 : 0x81;
std::vector<uint8_t> nibs;
for (char c : cleanNum) {
if (c == '+') continue;
if (c >= '0' && c <= '9') nibs.push_back(static_cast<uint8_t>(c - '0'));
else if (c == '*') nibs.push_back(0x0A);
else if (c == '#') nibs.push_back(0x0B);
if (nibs.size() >= 20) break;
}
if (nibs.empty()) return false;
std::string name = trimStr(rawName);
if (name.empty()) name = cleanNum;
bool pureAscii = true;
for (unsigned char uc : name) {
if (uc >= 0x80) {
pureAscii = false;
break;
}
}
if (pureAscii) {
size_t n = std::min(name.size(), alphaLen);
for (size_t i = 0; i < n; ++i) {
unsigned char uc = static_cast<unsigned char>(name[i]);
outRec[i] = (uc >= 32 && uc < 127) ? uc : static_cast<uint8_t>(' ');
}
} else if (alphaLen >= 3) {
outRec[0] = 0x80;
size_t outPos = 1;
size_t i = 0;
while (i < name.size() && outPos + 1 < alphaLen) {
uint32_t cp = 0;
unsigned char c0 = static_cast<unsigned char>(name[i]);
if (c0 < 0x80) {
cp = c0;
i += 1;
} else if ((c0 & 0xE0) == 0xC0 && i + 1 < name.size()) {
cp = ((c0 & 0x1F) << 6) |
(static_cast<unsigned char>(name[i + 1]) & 0x3F);
i += 2;
} else if ((c0 & 0xF0) == 0xE0 && i + 2 < name.size()) {
cp = ((c0 & 0x0F) << 12) |
((static_cast<unsigned char>(name[i + 1]) & 0x3F) << 6) |
(static_cast<unsigned char>(name[i + 2]) & 0x3F);
i += 3;
} else {
i += 1;
continue;
}
if (cp <= 0xFFFF) {
outRec[outPos++] = static_cast<uint8_t>((cp >> 8) & 0xFF);
outRec[outPos++] = static_cast<uint8_t>(cp & 0xFF);
}
}
}
size_t bcdBytes = (nibs.size() + 1) / 2;
outRec[alphaLen] = static_cast<uint8_t>(1 + bcdBytes);
outRec[alphaLen + 1] = tonNpi;
for (size_t i = 0; i < nibs.size(); ++i) {
size_t bIdx = alphaLen + 2 + (i / 2);
if ((i % 2) == 0) {
outRec[bIdx] = (outRec[bIdx] & 0xF0) | (nibs[i] & 0x0F);
} else {
outRec[bIdx] = (outRec[bIdx] & 0x0F) | ((nibs[i] & 0x0F) << 4);
}
}
return true;
}
inline bool openQrtrUimSocket(int& outSock, uint32_t& outNode,
uint32_t& outPort) {
constexpr int kAfQipcrtr = 42;
constexpr uint32_t kQrtrPortCtrl = 0xfffffffeu;
constexpr uint32_t kQrtrTypeNewServer = 4;
constexpr uint32_t kQrtrTypeNewLookup = 10;
constexpr uint32_t kQmiServiceUim = 11;
struct SockAddrQrtr {
unsigned short sq_family;
uint32_t sq_node;
uint32_t sq_port;
};
struct QrtrCtrlPkt {
uint32_t cmd;
uint32_t service;
uint32_t instance;
uint32_t node;
uint32_t port;
} __attribute__((packed));
int lfd = ::socket(kAfQipcrtr, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (lfd < 0) return false;
SockAddrQrtr localSq {};
socklen_t sl = sizeof(localSq);
if (::getsockname(lfd, reinterpret_cast<struct sockaddr*>(&localSq), &sl) < 0) {
::close(lfd);
return false;
}
SockAddrQrtr ctrlSq {};
ctrlSq.sq_family = kAfQipcrtr;
ctrlSq.sq_node = localSq.sq_node;
ctrlSq.sq_port = kQrtrPortCtrl;
QrtrCtrlPkt lookup {};
lookup.cmd = kQrtrTypeNewLookup;
lookup.service = kQmiServiceUim;
lookup.instance = 0;
if (::sendto(lfd, &lookup, sizeof(lookup), 0,
reinterpret_cast<const struct sockaddr*>(&ctrlSq),
sizeof(ctrlSq)) < 0) {
::close(lfd);
return false;
}
bool found = false;
for (int i = 0; i < 16; ++i) {
struct pollfd pfd { lfd, POLLIN, 0 };
if (::poll(&pfd, 1, 80) <= 0) break;
QrtrCtrlPkt pkt {};
ssize_t n = ::recv(lfd, &pkt, sizeof(pkt), 0);
if (n >= static_cast<ssize_t>(sizeof(pkt)) &&
pkt.cmd == kQrtrTypeNewServer) {
if (pkt.service == kQmiServiceUim && pkt.port != 0) {
outNode = pkt.node;
outPort = pkt.port;
found = true;
}
if (pkt.service == 0 && pkt.node == 0 && pkt.port == 0) {
break;
}
}
}
::close(lfd);
if (!found) return false;
outSock = ::socket(kAfQipcrtr, SOCK_DGRAM | SOCK_CLOEXEC, 0);
return outSock >= 0;
}
inline bool queryQrtrUimAdnGeometry(int sfd, uint32_t uimNode, uint32_t uimPort,
int slotNum, uint8_t& outSessionType,
std::vector<uint8_t>& outFileTlvVal,
uint16_t& outRecSize,
uint16_t& outRecCount) {
struct SockAddrQrtr {
unsigned short sq_family;
uint32_t sq_node;
uint32_t sq_port;
} uimSq { 42, uimNode, uimPort };
uint8_t sessionCandidates[2] = {
static_cast<uint8_t>((slotNum == 2) ? 7 : 6),
0
};
struct FilePathSpec {
uint16_t fileId;
uint8_t pathLen;
uint8_t pathBytes[6];
} fileSpecs[2] = {
{ 0x6F3A, 4, { 0x00, 0x3F, 0x10, 0x7F, 0x00, 0x00 } },
{ 0x4F3A, 6, { 0x00, 0x3F, 0x10, 0x7F, 0x3A, 0x5F } }
};
uint16_t txnId = 1;
for (uint8_t stype : sessionCandidates) {
for (const auto& fs : fileSpecs) {
std::vector<uint8_t> fval;
fval.push_back(static_cast<uint8_t>(fs.fileId & 0xFF));
fval.push_back(static_cast<uint8_t>((fs.fileId >> 8) & 0xFF));
fval.push_back(fs.pathLen);
for (uint8_t k = 0; k < fs.pathLen; ++k) {
fval.push_back(fs.pathBytes[k]);
}
std::vector<uint8_t> req(7);
req[0] = 0x00;
req[1] = static_cast<uint8_t>(txnId & 0xFF);
req[2] = static_cast<uint8_t>((txnId >> 8) & 0xFF);
++txnId;
req[3] = 0x24;
req[4] = 0x00;
// TLV 0x01: Session
req.push_back(0x01);
req.push_back(0x02);
req.push_back(0x00);
req.push_back(stype);
req.push_back(0x00);
// TLV 0x02: File
req.push_back(0x02);
req.push_back(static_cast<uint8_t>(fval.size() & 0xFF));
req.push_back(static_cast<uint8_t>((fval.size() >> 8) & 0xFF));
req.insert(req.end(), fval.begin(), fval.end());
uint16_t tlvTotal = static_cast<uint16_t>(req.size() - 7);
req[5] = static_cast<uint8_t>(tlvTotal & 0xFF);
req[6] = static_cast<uint8_t>((tlvTotal >> 8) & 0xFF);
if (::sendto(sfd, req.data(), req.size(), 0,
reinterpret_cast<const struct sockaddr*>(&uimSq),
sizeof(uimSq)) < 0) {
continue;
}
struct pollfd pfd { sfd, POLLIN, 0 };
if (::poll(&pfd, 1, 120) <= 0) continue;
uint8_t resp[512];
ssize_t rlen = ::recv(sfd, resp, sizeof(resp), 0);
if (rlen < 14 || resp[0] != 0x02 || resp[3] != 0x24) continue;
bool okResult = false;
uint16_t rSize = 0;
uint16_t rCount = 0;
size_t pos = 7;
while (pos + 3 <= static_cast<size_t>(rlen)) {
uint8_t tt = resp[pos];
uint16_t tl = static_cast<uint16_t>(resp[pos + 1]) |
(static_cast<uint16_t>(resp[pos + 2]) << 8);
pos += 3;
if (pos + tl > static_cast<size_t>(rlen)) break;
if (tt == 0x02 && tl >= 4) {
uint16_t res = static_cast<uint16_t>(resp[pos]) |
(static_cast<uint16_t>(resp[pos + 1]) << 8);
uint16_t err = static_cast<uint16_t>(resp[pos + 2]) |
(static_cast<uint16_t>(resp[pos + 3]) << 8);
if (res == 0 && err == 0) okResult = true;
} else if (tt == 0x11 && tl >= 9) {
rSize = static_cast<uint16_t>(resp[pos + 5]) |
(static_cast<uint16_t>(resp[pos + 6]) << 8);
rCount = static_cast<uint16_t>(resp[pos + 7]) |
(static_cast<uint16_t>(resp[pos + 8]) << 8);
}
pos += tl;
}
if (okResult && rSize >= 14 && rCount >= 1) {
outSessionType = stype;
outFileTlvVal = fval;
outRecSize = rSize;
outRecCount = std::min<uint16_t>(rCount, 500);
return true;
}
}
}
return false;
}
inline bool qrtrReadSimRecord(int sfd, uint32_t uimNode, uint32_t uimPort,
uint16_t txnId, uint8_t sessionType,
const std::vector<uint8_t>& fileTlvVal,
uint16_t recNum, uint16_t recSize,
std::vector<uint8_t>& outData) {
struct SockAddrQrtr {
unsigned short sq_family;
uint32_t sq_node;
uint32_t sq_port;
} uimSq { 42, uimNode, uimPort };
std::vector<uint8_t> req(7);
req[0] = 0x00;
req[1] = static_cast<uint8_t>(txnId & 0xFF);
req[2] = static_cast<uint8_t>((txnId >> 8) & 0xFF);
req[3] = 0x21; // QMI_UIM_READ_RECORD
req[4] = 0x00;
// TLV 0x01: Session
req.push_back(0x01);
req.push_back(0x02);
req.push_back(0x00);
req.push_back(sessionType);
req.push_back(0x00);
// TLV 0x02: File
req.push_back(0x02);
req.push_back(static_cast<uint8_t>(fileTlvVal.size() & 0xFF));
req.push_back(static_cast<uint8_t>((fileTlvVal.size() >> 8) & 0xFF));
req.insert(req.end(), fileTlvVal.begin(), fileTlvVal.end());
// TLV 0x03: Record (record_number u16 LE, record_length u16 LE)
req.push_back(0x03);
req.push_back(0x04);
req.push_back(0x00);
req.push_back(static_cast<uint8_t>(recNum & 0xFF));
req.push_back(static_cast<uint8_t>((recNum >> 8) & 0xFF));
req.push_back(static_cast<uint8_t>(recSize & 0xFF));
req.push_back(static_cast<uint8_t>((recSize >> 8) & 0xFF));
uint16_t tlvTotal = static_cast<uint16_t>(req.size() - 7);
req[5] = static_cast<uint8_t>(tlvTotal & 0xFF);
req[6] = static_cast<uint8_t>((tlvTotal >> 8) & 0xFF);
if (::sendto(sfd, req.data(), req.size(), 0,
reinterpret_cast<const struct sockaddr*>(&uimSq),
sizeof(uimSq)) < 0) {
return false;
}
struct pollfd pfd { sfd, POLLIN, 0 };
if (::poll(&pfd, 1, 120) <= 0) return false;
uint8_t resp[512];
ssize_t rlen = ::recv(sfd, resp, sizeof(resp), 0);
if (rlen < 14 || resp[0] != 0x02 || resp[3] != 0x21) return false;
bool okResult = false;
outData.clear();
size_t pos = 7;
while (pos + 3 <= static_cast<size_t>(rlen)) {
uint8_t tt = resp[pos];
uint16_t tl = static_cast<uint16_t>(resp[pos + 1]) |
(static_cast<uint16_t>(resp[pos + 2]) << 8);
pos += 3;
if (pos + tl > static_cast<size_t>(rlen)) break;
if (tt == 0x02 && tl >= 4) {
uint16_t res = static_cast<uint16_t>(resp[pos]) |
(static_cast<uint16_t>(resp[pos + 1]) << 8);
uint16_t err = static_cast<uint16_t>(resp[pos + 2]) |
(static_cast<uint16_t>(resp[pos + 3]) << 8);
if (res == 0 && err == 0) okResult = true;
} else if (tt == 0x11 && tl >= 2) {
uint16_t clen = static_cast<uint16_t>(resp[pos]) |
(static_cast<uint16_t>(resp[pos + 1]) << 8);
if (2 + clen <= tl) {
outData.assign(resp + pos + 2, resp + pos + 2 + clen);
}
}
pos += tl;
}
return okResult && !outData.empty();
}
inline bool qrtrWriteSimRecord(int sfd, uint32_t uimNode, uint32_t uimPort,
uint16_t txnId, uint8_t sessionType,
const std::vector<uint8_t>& fileTlvVal,
uint16_t recNum,
const std::vector<uint8_t>& recData) {
struct SockAddrQrtr {
unsigned short sq_family;
uint32_t sq_node;
uint32_t sq_port;
} uimSq { 42, uimNode, uimPort };
std::vector<uint8_t> req(7);
req[0] = 0x00;
req[1] = static_cast<uint8_t>(txnId & 0xFF);
req[2] = static_cast<uint8_t>((txnId >> 8) & 0xFF);
req[3] = 0x23; // QMI_UIM_WRITE_RECORD
req[4] = 0x00;
// TLV 0x01: Session
req.push_back(0x01);
req.push_back(0x02);
req.push_back(0x00);
req.push_back(sessionType);
req.push_back(0x00);
// TLV 0x02: File
req.push_back(0x02);
req.push_back(static_cast<uint8_t>(fileTlvVal.size() & 0xFF));
req.push_back(static_cast<uint8_t>((fileTlvVal.size() >> 8) & 0xFF));
req.insert(req.end(), fileTlvVal.begin(), fileTlvVal.end());
// TLV 0x03: Write Record (record_number u16 LE, data_len u16 LE, data[])
uint16_t wTlvLen = static_cast<uint16_t>(4 + recData.size());
req.push_back(0x03);
req.push_back(static_cast<uint8_t>(wTlvLen & 0xFF));
req.push_back(static_cast<uint8_t>((wTlvLen >> 8) & 0xFF));
req.push_back(static_cast<uint8_t>(recNum & 0xFF));
req.push_back(static_cast<uint8_t>((recNum >> 8) & 0xFF));
uint16_t dLen = static_cast<uint16_t>(recData.size());
req.push_back(static_cast<uint8_t>(dLen & 0xFF));
req.push_back(static_cast<uint8_t>((dLen >> 8) & 0xFF));
req.insert(req.end(), recData.begin(), recData.end());
uint16_t tlvTotal = static_cast<uint16_t>(req.size() - 7);
req[5] = static_cast<uint8_t>(tlvTotal & 0xFF);
req[6] = static_cast<uint8_t>((tlvTotal >> 8) & 0xFF);
if (::sendto(sfd, req.data(), req.size(), 0,
reinterpret_cast<const struct sockaddr*>(&uimSq),
sizeof(uimSq)) < 0) {
return false;
}
struct pollfd pfd { sfd, POLLIN, 0 };
if (::poll(&pfd, 1, 200) <= 0) return false;
uint8_t resp[512];
ssize_t rlen = ::recv(sfd, resp, sizeof(resp), 0);
if (rlen < 14 || resp[0] != 0x02 || resp[3] != 0x23) return false;
bool okResult = false;
size_t pos = 7;
while (pos + 3 <= static_cast<size_t>(rlen)) {
uint8_t tt = resp[pos];
uint16_t tl = static_cast<uint16_t>(resp[pos + 1]) |
(static_cast<uint16_t>(resp[pos + 2]) << 8);
pos += 3;
if (pos + tl > static_cast<size_t>(rlen)) break;
if (tt == 0x02 && tl >= 4) {
uint16_t res = static_cast<uint16_t>(resp[pos]) |
(static_cast<uint16_t>(resp[pos + 1]) << 8);
uint16_t err = static_cast<uint16_t>(resp[pos + 2]) |
(static_cast<uint16_t>(resp[pos + 3]) << 8);
if (res == 0 && err == 0) okResult = true;
}
pos += tl;
}
return okResult;
}
inline bool qrtrReadSimContacts(
int slotNum, std::vector<std::pair<std::string, std::string>>& outContacts,
std::string& outErr) {
outContacts.clear();
outErr.clear();
int sfd = -1;
uint32_t uimNode = 0, uimPort = 0;
if (!openQrtrUimSocket(sfd, uimNode, uimPort)) {
outErr = "Modem UIM service unavailable";
return false;
}
uint8_t sessionType = 0;
std::vector<uint8_t> fileTlvVal;
uint16_t recSize = 0, recCount = 0;
if (!queryQrtrUimAdnGeometry(sfd, uimNode, uimPort, slotNum, sessionType,
fileTlvVal, recSize, recCount)) {
::close(sfd);
outErr = "SIM " + std::to_string(slotNum) + " phonebook not ready";
return false;
}
std::vector<uint8_t> raw;
for (uint16_t rnum = 1; rnum <= recCount; ++rnum) {
if (qrtrReadSimRecord(sfd, uimNode, uimPort, static_cast<uint16_t>(10 + rnum),
sessionType, fileTlvVal, rnum, recSize, raw)) {
std::string num, name;
if (decodeEfAdnRecord(raw.data(), raw.size(), num, name)) {
outContacts.push_back({num, name});
}
}
}
::close(sfd);
return true;
}
inline bool qrtrWriteSimContacts(
int slotNum,
const std::vector<std::pair<std::string, std::string>>& contactsToWrite,
int& outExported, std::string& outErr) {
outExported = 0;
outErr.clear();
if (contactsToWrite.empty()) {
outErr = "No contacts to export";
return false;
}
int sfd = -1;
uint32_t uimNode = 0, uimPort = 0;
if (!openQrtrUimSocket(sfd, uimNode, uimPort)) {
outErr = "Modem UIM service unavailable";
return false;
}
uint8_t sessionType = 0;
std::vector<uint8_t> fileTlvVal;
uint16_t recSize = 0, recCount = 0;
if (!queryQrtrUimAdnGeometry(sfd, uimNode, uimPort, slotNum, sessionType,
fileTlvVal, recSize, recCount)) {
::close(sfd);
outErr = "SIM " + std::to_string(slotNum) + " phonebook not ready";
return false;
}
std::unordered_map<std::string, uint16_t> existingByNorm;
std::unordered_map<std::string, uint16_t> existingBySuffix;
std::vector<uint16_t> emptySlots;
std::vector<uint8_t> raw;
for (uint16_t rnum = 1; rnum <= recCount; ++rnum) {
if (qrtrReadSimRecord(sfd, uimNode, uimPort, static_cast<uint16_t>(10 + rnum),
sessionType, fileTlvVal, rnum, recSize, raw)) {
std::string num, name;
if (decodeEfAdnRecord(raw.data(), raw.size(), num, name)) {
std::string norm = normalizePhone(num);
if (!norm.empty()) {
existingByNorm[norm] = rnum;
std::string sfx = phoneSuffixKey(norm);
if (!sfx.empty()) existingBySuffix[sfx] = rnum;
}
} else {
emptySlots.push_back(rnum);
}
}
}
size_t emptyIdx = 0;
uint16_t txnId = static_cast<uint16_t>(1000);
bool simFull = false;
for (const auto& kv : contactsToWrite) {
std::string norm = normalizePhone(kv.first);
if (norm.empty()) continue;
uint16_t targetRec = 0;
auto it = existingByNorm.find(norm);
if (it != existingByNorm.end()) {
targetRec = it->second;
} else {
std::string sfx = phoneSuffixKey(norm);
auto sit = sfx.empty() ? existingBySuffix.end()
: existingBySuffix.find(sfx);
if (sit != existingBySuffix.end()) {
targetRec = sit->second;
} else if (emptyIdx < emptySlots.size()) {
targetRec = emptySlots[emptyIdx++];
} else {
simFull = true;
break;
}
}
std::vector<uint8_t> recData;
if (!encodeEfAdnRecord(kv.first, kv.second, recSize, recData)) {
continue;
}
if (qrtrWriteSimRecord(sfd, uimNode, uimPort, txnId++, sessionType,
fileTlvVal, targetRec, recData)) {
++outExported;
existingByNorm[norm] = targetRec;
std::string sfx = phoneSuffixKey(norm);
if (!sfx.empty()) existingBySuffix[sfx] = targetRec;
}
}
::close(sfd);
if (outExported == 0 && simFull) {
outErr = "SIM " + std::to_string(slotNum) + " phonebook is full";
return false;
}
return outExported > 0;
}
} // namespace bananarchy
#endif // DWM_PLUGINS_PHONE_DATA_H