contacts, modem: implement direct QRTR QMI UIM SIM phonebook import and export for SIM 1 and SIM 2
This commit is contained in:
@@ -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