/****************************************************************************** * * Copyright (C) 2011-2012 Broadcom Corporation * Copyright 2018-2019, 2023-2024 NXP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ //#define LOG_NDEBUG 0 #define LOG_TAG "NxpUwbConf" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "phNxpConfig.h" #include "phNxpUciHal.h" #include "phNxpUciHal_ext.h" #include "phNxpUciHal_utils.h" #include "phNxpLog.h" namespace { static const char default_nxp_config_path[] = "/vendor/etc/libuwb-nxp.conf"; static const char country_code_config_name[] = "libuwb-countrycode.conf"; static const char nxp_uci_config_file[] = "libuwb-uci.conf"; static const char default_uci_config_path[] = "/vendor/etc/"; static const char country_code_specifier[] = ""; static const char sku_specifier[] = ""; static const char extid_specifier[] = ""; static const char revision_specifier[] = ""; static const char extid_config_name[] = "cal.extid"; static const char extid_default_value[] = "defaultextid"; static const char prop_name_calsku[] = "persist.vendor.uwb.cal.sku"; static const char prop_default_calsku[] = "defaultsku"; static const char prop_name_revision[] = "persist.vendor.uwb.cal.revision"; static const char prop_default_revision[] = "defaultrevision"; using namespace::std; class uwbParam { public: enum class type { STRING, NUMBER, BYTEARRAY, STRINGARRAY }; uwbParam(); uwbParam(const uwbParam& param); uwbParam(uwbParam&& param); uwbParam(const string& value); uwbParam(vector&& value); uwbParam(unsigned long value); uwbParam(vector&& value); virtual ~uwbParam(); type getType() const { return m_type; } unsigned long numValue() const {return m_numValue;} const char* str_value() const {return m_str_value.c_str();} size_t str_len() const {return m_str_value.length();} const uint8_t* arr_value() const { return m_arrValue.data(); } size_t arr_len() const { return m_arrValue.size(); } size_t str_arr_len() const { return m_arrStrValue.size(); } const char* str_arr_elem(const int index) const { return m_arrStrValue[index].c_str(); } size_t str_arr_elem_len(const int index) const { return m_arrStrValue[index].length(); } void dump(const string &tag) const; private: // TODO: use uint64_t or uint32_t instead of unsigned long. unsigned long m_numValue; string m_str_value; vector m_arrValue; vector m_arrStrValue; type m_type; }; class CUwbNxpConfig { public: CUwbNxpConfig(); CUwbNxpConfig(CUwbNxpConfig&& config); CUwbNxpConfig(const char *filepath); virtual ~CUwbNxpConfig(); CUwbNxpConfig& operator=(CUwbNxpConfig&& config); bool open(const char *filepath); bool isValid() const { return mValidFile; } void reset() { m_map.clear(); mValidFile = false; } const uwbParam* find(const char* p_name) const; void setCountry(const string& strCountry); const char* getFilePath() const { return mFilePath.c_str(); } void dump() const; const unordered_map& get_data() const { return m_map; } private: bool readConfig(); unordered_map m_map; bool mValidFile; string mFilePath; }; /******************************************************************************* ** ** Function: isPrintable() ** ** Description: determine if 'c' is printable ** ** Returns: 1, if printable, otherwise 0 ** *******************************************************************************/ static inline bool isPrintable(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '/' || c == '_' || c == '-' || c == '.' || c == ','; } /******************************************************************************* ** ** Function: isDigit() ** ** Description: determine if 'c' is numeral digit ** ** Returns: true, if numerical digit ** *******************************************************************************/ static inline bool isDigit(char c, int base) { if (base == 10) { return isdigit(c); } else if (base == 16) { return isxdigit(c); } else { return false; } } static inline bool isArrayDelimeter(char c) { return (isspace(c) || c== ',' || c == ':' || c == '-' || c == '}'); } /******************************************************************************* ** ** Function: getDigitValue() ** ** Description: return numerical value of a decimal or hex char ** ** Returns: numerical value if decimal or hex char, otherwise 0 ** *******************************************************************************/ inline int getDigitValue(char c, int base) { if ('0' <= c && c <= '9') return c - '0'; if (base == 16) { if ('A' <= c && c <= 'F') return c - 'A' + 10; else if ('a' <= c && c <= 'f') return c - 'a' + 10; } return 0; } /******************************************************************************* ** ** Function: CUwbNxpConfig::readConfig() ** ** Description: read Config settings and parse them into a linked list ** move the element from linked list to a array at the end ** ** Returns: 1, if there are any config data, 0 otherwise ** *******************************************************************************/ bool CUwbNxpConfig::readConfig() { enum { BEGIN_LINE = 1, TOKEN, STR_VALUE, NUM_VALUE, ARR_SPACE, ARR_STR, ARR_STR_SPACE, ARR_NUM, BEGIN_HEX, BEGIN_QUOTE, END_LINE }; FILE* fd; string token; string strValue; unsigned long numValue = 0; vector arrValue; vector arrStr; int base = 0; int c; const char *name = mFilePath.c_str(); unsigned long state = BEGIN_LINE; mValidFile = false; m_map.clear(); /* open config file, read it into a buffer */ if ((fd = fopen(name, "r")) == NULL) { ALOGV("Extra calibration file %s failed to open.", name); return false; } ALOGV("%s Opened config %s\n", __func__, name); for (;;) { c = fgetc(fd); switch (state) { case BEGIN_LINE: if (isPrintable(c)) { token.clear(); numValue = 0; strValue.clear(); arrValue.clear(); arrStr.clear(); state = TOKEN; token.push_back(c); } else { state = END_LINE; } break; case TOKEN: if (c == '=') { state = BEGIN_QUOTE; } else if (isPrintable(c)) { token.push_back(c); } else { state = END_LINE; } break; case BEGIN_QUOTE: if (c == '"') { state = STR_VALUE; base = 0; } else if (c == '0') { state = BEGIN_HEX; } else if (isDigit(c, 10)) { state = NUM_VALUE; base = 10; numValue = getDigitValue(c, base); } else if (c == '{') { state = ARR_SPACE; base = 16; } else { state = END_LINE; } break; case BEGIN_HEX: if (c == 'x' || c == 'X') { state = NUM_VALUE; base = 16; numValue = 0; } else if (isDigit(c, 10)) { state = NUM_VALUE; base = 10; numValue = getDigitValue(c, base); } else { m_map.try_emplace(token, move(uwbParam(numValue))); state = END_LINE; } break; case NUM_VALUE: if (isDigit(c, base)) { numValue *= base; numValue += getDigitValue(c, base); } else {m_map.try_emplace(token, move(uwbParam(numValue))); state = END_LINE; } break; case ARR_SPACE: if (isDigit(c, 16)) { numValue = getDigitValue(c, base); state = ARR_NUM; } else if (c == '}') { m_map.try_emplace(token, move(uwbParam(move(arrValue)))); state = END_LINE; } else if (c == '"') { state = ARR_STR; } else if (c == EOF) { state = END_LINE; } break; case ARR_STR: if (c == '"') { arrStr.emplace_back(move(strValue)); strValue.clear(); state = ARR_STR_SPACE; } else { strValue.push_back(c); } break; case ARR_STR_SPACE: if (c == '}') { m_map.try_emplace(token, move(uwbParam(move(arrStr)))); state = END_LINE; } else if (c == '"') { state = ARR_STR; } break; case ARR_NUM: if (isDigit(c, 16)) { numValue *= 16; numValue += getDigitValue(c, base); } else if (isArrayDelimeter(c)) { arrValue.push_back(numValue & 0xff); state = ARR_SPACE; } else { state = END_LINE; } if (c == '}') { m_map.try_emplace(token, move(uwbParam(move(arrValue)))); state = END_LINE; } break; case STR_VALUE: if (c == '"') { state = END_LINE; m_map.try_emplace(token, move(uwbParam(strValue))); } else { strValue.push_back(c); } break; case END_LINE: // do nothing default: break; } if (c == EOF) break; else if (state == END_LINE && (c == '\n' || c == '\r')) state = BEGIN_LINE; else if (c == '#') state = END_LINE; } if (fclose(fd) != 0) { ALOGE("[%s] fclose failed", __func__); } if (m_map.size() > 0) { mValidFile = true; ALOGI("Extra calibration file %s opened.", name); } return mValidFile; } /******************************************************************************* ** ** Function: CUwbNxpConfig::CUwbNxpConfig() ** ** Description: class constructor ** ** Returns: none ** *******************************************************************************/ CUwbNxpConfig::CUwbNxpConfig() : mValidFile(false) { } /******************************************************************************* ** ** Function: CUwbNxpConfig::~CUwbNxpConfig() ** ** Description: class destructor ** ** Returns: none ** *******************************************************************************/ CUwbNxpConfig::~CUwbNxpConfig() { } CUwbNxpConfig::CUwbNxpConfig(const char *filepath) { open(filepath); } CUwbNxpConfig::CUwbNxpConfig(CUwbNxpConfig&& config) { m_map = move(config.m_map); mValidFile = config.mValidFile; mFilePath = move(config.mFilePath); config.mValidFile = false; } CUwbNxpConfig& CUwbNxpConfig::operator=(CUwbNxpConfig&& config) { m_map = move(config.m_map); mValidFile = config.mValidFile; mFilePath = move(config.mFilePath); config.mValidFile = false; return *this; } bool CUwbNxpConfig::open(const char *filepath) { mValidFile = false; mFilePath = filepath; return readConfig(); } /******************************************************************************* ** ** Function: CUwbNxpConfig::find() ** ** Description: search if a setting exist in the setting array ** ** Returns: pointer to the setting object ** *******************************************************************************/ const uwbParam* CUwbNxpConfig::find(const char* p_name) const { const auto it = m_map.find(p_name); if (it == m_map.cend()) { return NULL; } return &it->second; } /******************************************************************************* ** ** Function: CUwbNxpConfig::dump() ** ** Description: prints all elements in the list ** ** Returns: none ** *******************************************************************************/ void CUwbNxpConfig::dump() const { ALOGV("Dump configuration file %s : %s, %zu entries", mFilePath.c_str(), mValidFile ? "valid" : "invalid", m_map.size()); for (auto &it : m_map) { auto &key = it.first; auto ¶m = it.second; param.dump(key); } } /*******************************************************************************/ uwbParam::uwbParam() : m_numValue(0), m_type(type::NUMBER) { } uwbParam::~uwbParam() { } uwbParam::uwbParam(const uwbParam ¶m) : m_numValue(param.m_numValue), m_str_value(param.m_str_value), m_arrValue(param.m_arrValue), m_arrStrValue(param.m_arrStrValue), m_type(param.m_type) { } uwbParam::uwbParam(uwbParam &¶m) : m_numValue(param.m_numValue), m_str_value(move(param.m_str_value)), m_arrValue(move(param.m_arrValue)), m_arrStrValue(move(param.m_arrStrValue)), m_type(param.m_type) { } uwbParam::uwbParam(const string& value) : m_numValue(0), m_str_value(value), m_type(type::STRING) { } uwbParam::uwbParam(unsigned long value) : m_numValue(value), m_type(type::NUMBER) { } uwbParam::uwbParam(vector &&value) : m_arrValue(move(value)), m_type(type::BYTEARRAY) { } uwbParam::uwbParam(vector &&value) : m_arrStrValue(move(value)), m_type(type::STRINGARRAY) { } void uwbParam::dump(const string &tag) const { if (m_type == type::NUMBER) { ALOGV(" - %s = 0x%lx", tag.c_str(), m_numValue); } else if (m_type == type::STRING) { ALOGV(" - %s = %s", tag.c_str(), m_str_value.c_str()); } else if (m_type == type::BYTEARRAY) { stringstream ss_hex; ss_hex.fill('0'); for (auto b : m_arrValue) { ss_hex << setw(2) << hex << (int)b << " "; } ALOGV(" - %s = { %s}", tag.c_str(), ss_hex.str().c_str()); } else if (m_type == type::STRINGARRAY) { stringstream ss; for (auto s : m_arrStrValue) { ss << "\"" << s << "\", "; } ALOGV(" - %s = { %s}", tag.c_str(), ss.str().c_str()); } } /*******************************************************************************/ class RegionCodeMap { public: void loadMapping(const char *filepath) { CUwbNxpConfig config(filepath); if (!config.isValid()) { ALOGW("Region mapping was not provided."); return; } ALOGI("Region mapping was provided by %s", filepath); auto &all_params = config.get_data(); for (auto &it : all_params) { const auto ®ion_str = it.first; const uwbParam *param = &it.second; // split space-separated strings into set stringstream ss(param->str_value()); string cc; unordered_set cc_set; while (ss >> cc) { if (cc.length() == 2 && isupper(cc[0]) && isupper(cc[1])) { cc_set.emplace(move(cc)); } } auto result = m_map.try_emplace(region_str, move(cc_set)); if (!result.second) { // region conlifct : merge result.first->second.merge(move(cc_set)); } } m_config = move(config); } string xlateCountryCode(const char country_code[2]) { string code{country_code[0], country_code[1]}; if (m_config.isValid()) { for (auto &it : m_map) { const auto ®ion_str = it.first; const auto &cc_set = it.second; if (cc_set.find(code) != cc_set.end()) { ALOGV("map country code %c%c --> %s", country_code[0], country_code[1], region_str.c_str()); return region_str; } } } return code; } void reset() { m_config.reset(); m_map.clear(); } void dump() { ALOGV("Region mapping dump:"); for (auto &entry : m_map) { const auto ®ion_str = entry.first; const auto &cc_set = entry.second; stringstream ss; for (const auto s : cc_set) { ss << "\"" << s << "\", "; } ALOGV("- %s = { %s}", region_str.c_str(), ss.str().c_str()); } } private: CUwbNxpConfig m_config; unordered_map> m_map; }; /*******************************************************************************/ class CascadeConfig { public: CascadeConfig(); void init(const char *main_config); void deinit(); bool setCountryCode(const char country_code[2]); const uwbParam* find(const char *name) const; bool getValue(const char* name, char* pValue, size_t len) const; bool getValue(const char* name, unsigned long& rValue) const; bool getValue(const char* name, uint8_t* pValue, size_t len, size_t* readlen) const; private: // default_nxp_config_path CUwbNxpConfig mMainConfig; // uci config CUwbNxpConfig mUciConfig; // EXTRA_CONF_PATH[N] std::vector> mExtraConfig; // [COUNTRY_CODE_CAP_FILE_LOCATION]/country_code_config_name CUwbNxpConfig mCapsConfig; // Region Code mapping RegionCodeMap mRegionMap; // current set of specifiers for EXTRA_CONF_PATH[] struct ExtraConfPathSpecifiers { string mCurSku; string mCurExtid; string mCurRegionCode; string mCurRevision; void reset() { mCurSku.clear(); mCurExtid.clear(); mCurRegionCode.clear(); mCurRevision.clear(); } }; ExtraConfPathSpecifiers mExtraConfSpecifiers; // Re-evaluate filepaths of mExtraConfig with mExtraConfSpecifiers, and re-load them. // returns true if any of entries were updated. bool evaluateExtraConfPaths(); void dump() { mMainConfig.dump(); mUciConfig.dump(); for (const auto &[filename, config] : mExtraConfig) config.dump(); mCapsConfig.dump(); mRegionMap.dump(); } }; CascadeConfig::CascadeConfig() { } bool CascadeConfig::evaluateExtraConfPaths() { bool updated = false; for (auto& [filename, config] : mExtraConfig) { std::string new_filename(filename); auto posSku = filename.find(sku_specifier); if (posSku != std::string::npos && !mExtraConfSpecifiers.mCurSku.empty()) { new_filename.replace(posSku, strlen(sku_specifier), mExtraConfSpecifiers.mCurSku); } auto posExtid = filename.find(extid_specifier); if (posExtid != std::string::npos && !mExtraConfSpecifiers.mCurExtid.empty()) { new_filename.replace(posExtid, strlen(extid_specifier), mExtraConfSpecifiers.mCurExtid); } auto posCountry = filename.find(country_code_specifier); if (posCountry != std::string::npos && !mExtraConfSpecifiers.mCurRegionCode.empty()) { new_filename.replace(posCountry, strlen(country_code_specifier), mExtraConfSpecifiers.mCurRegionCode); } auto posRevision = filename.find(revision_specifier); if (posRevision != std::string::npos && !mExtraConfSpecifiers.mCurRevision.empty()) { new_filename.replace(posRevision, strlen(revision_specifier), mExtraConfSpecifiers.mCurRevision); } // re-open the file if filepath got re-evaluated. if (new_filename != config.getFilePath()) { config.open(new_filename.c_str()); updated = true; } } return updated; } void CascadeConfig::init(const char *main_config) { ALOGV("CascadeConfig initialize with %s", main_config); // Main config file CUwbNxpConfig config(main_config); if (!config.isValid()) { ALOGW("Failed to load main config file"); return; } mMainConfig = move(config); { // UCI config file std::string uciConfigFilePath = default_uci_config_path; uciConfigFilePath += nxp_uci_config_file; CUwbNxpConfig config(uciConfigFilePath.c_str()); if (!config.isValid()) { ALOGW("Failed to load uci config file:%s", uciConfigFilePath.c_str()); } else { mUciConfig = move(config); } } char sku_value[PROPERTY_VALUE_MAX]; char revision_value[PROPERTY_VALUE_MAX]; property_get(prop_name_calsku, sku_value, prop_default_calsku); property_get(prop_name_revision, revision_value, prop_default_revision); // Read EXTRA_CONF_PATH[N] for (int i = 1; i <= 10; i++) { char key[32]; snprintf(key, sizeof(key), "EXTRA_CONF_PATH_%d", i); const uwbParam *param = mMainConfig.find(key); if (!param) continue; std::string filename(param->str_value()); auto entry = std::make_pair(param->str_value(), CUwbNxpConfig(filename.c_str())); mExtraConfig.emplace_back(std::move(entry)); } // evaluate and mExtraConfSpecifiers.mCurSku = sku_value; mExtraConfSpecifiers.mCurRevision = revision_value; evaluateExtraConfPaths(); // re-evaluate with "" char extid_value[PROPERTY_VALUE_MAX]; if (!NxpConfig_GetStr(extid_config_name, extid_value, sizeof(extid_value))) { strcpy(extid_value, extid_default_value); } mExtraConfSpecifiers.mCurExtid = extid_value; evaluateExtraConfPaths(); ALOGI("Provided specifiers: sku=[%s] revision=[%s] extid=[%s]", sku_value, revision_value, extid_value); // Pick one libuwb-countrycode.conf with the highest VERSION number // from multiple directories specified by COUNTRY_CODE_CAP_FILE_LOCATION size_t arrLen = 0; if (NxpConfig_GetStrArrayLen(NAME_COUNTRY_CODE_CAP_FILE_LOCATION, &arrLen) && arrLen > 0) { constexpr size_t loc_max_len = 260; auto loc = make_unique(loc_max_len); int version, max_version = -1; string strPickedPath; bool foundCapFile = false; CUwbNxpConfig pickedConfig; for (int i = 0; i < arrLen; i++) { if (!NxpConfig_GetStrArrayVal(NAME_COUNTRY_CODE_CAP_FILE_LOCATION, i, loc.get(), loc_max_len)) { continue; } string strPath(loc.get()); strPath += country_code_config_name; ALOGV("Try to load %s", strPath.c_str()); CUwbNxpConfig config(strPath.c_str()); const uwbParam *param = config.find(NAME_NXP_COUNTRY_CODE_VERSION); version = param ? atoi(param->str_value()) : -2; if (version > max_version) { foundCapFile = true; pickedConfig = move(config); strPickedPath = move(strPath); max_version = version; } } if (foundCapFile) { mCapsConfig = move(pickedConfig); ALOGI("CountryCodeCaps file %s loaded with VERSION=%d", strPickedPath.c_str(), max_version); } else { ALOGI("No CountryCodeCaps specified"); } } else { ALOGI(NAME_COUNTRY_CODE_CAP_FILE_LOCATION " was not specified, skip loading CountryCodeCaps"); } // Load region mapping const uwbParam *param = find(NAME_REGION_MAP_PATH); if (param) { mRegionMap.loadMapping(param->str_value()); } ALOGD("CascadeConfig initialized"); dump(); } void CascadeConfig::deinit() { mMainConfig.reset(); mExtraConfig.clear(); mCapsConfig.reset(); mRegionMap.reset(); mUciConfig.reset(); mExtraConfSpecifiers.reset(); } bool CascadeConfig::setCountryCode(const char country_code[2]) { string strRegion = mRegionMap.xlateCountryCode(country_code); if (strRegion == mExtraConfSpecifiers.mCurRegionCode) { ALOGI("Same region code(%c%c --> %s), per-country configuration not updated.", country_code[0], country_code[1], strRegion.c_str()); return false; } ALOGI("Apply country code %c%c --> %s\n", country_code[0], country_code[1], strRegion.c_str()); mExtraConfSpecifiers.mCurRegionCode = strRegion; return evaluateExtraConfPaths(); } const uwbParam* CascadeConfig::find(const char *name) const { const uwbParam* param = NULL; param = mCapsConfig.find(name); if (param) return param; for (auto it = mExtraConfig.rbegin(); it != mExtraConfig.rend(); it++) { auto &config = it->second; param = config.find(name); if (param) break; } if (!param) { param = mMainConfig.find(name); } if (!param) { param = mUciConfig.find(name); } return param; } // TODO: move these getValue() helpers out of the class bool CascadeConfig::getValue(const char* name, char* pValue, size_t len) const { const uwbParam *param = find(name); if (!param) return false; if (param->getType() != uwbParam::type::STRING) return false; if (len < (param->str_len() + 1)) return false; strncpy(pValue, param->str_value(), len); return true; } bool CascadeConfig::getValue(const char* name, uint8_t* pValue, size_t len, size_t* readlen) const { const uwbParam *param = find(name); if (!param) return false; if (param->getType() != uwbParam::type::BYTEARRAY) return false; if (len < param->arr_len()) return false; memcpy(pValue, param->arr_value(), param->arr_len()); if (readlen) *readlen = param->arr_len(); return true; } bool CascadeConfig::getValue(const char* name, unsigned long& rValue) const { const uwbParam *param = find(name); if (!param) return false; if (param->getType() != uwbParam::type::NUMBER) return false; rValue = param->numValue(); return true; } } // namespace static CascadeConfig gConfig; void NxpConfig_Init(void) { gConfig.init(default_nxp_config_path); } void NxpConfig_Deinit(void) { gConfig.deinit(); } // return true if new per-country configuration file was load. // false if it can stay at the current configuration. bool NxpConfig_SetCountryCode(const char country_code[2]) { return gConfig.setCountryCode(country_code); } /******************************************************************************* ** ** Function: NxpConfig_GetStr ** ** Description: API function for getting a string value of a setting ** ** Returns: True if found, otherwise False. ** *******************************************************************************/ bool NxpConfig_GetStr(const char* name, char* pValue, size_t len) { return gConfig.getValue(name, pValue, len); } /******************************************************************************* ** ** Function: NxpConfig_GetByteArray() ** ** Description: Read byte array value from the config file. ** ** Parameters: ** name - name of the config param to read. ** pValue - pointer to input buffer. ** bufflen - input buffer length. ** len - out parameter to return the number of bytes read from config file, ** return -1 in case bufflen is not enough. ** ** Returns: TRUE[1] if config param name is found in the config file, else FALSE[0] ** *******************************************************************************/ bool NxpConfig_GetByteArray(const char* name, uint8_t* pValue, size_t bufflen, size_t* len) { return gConfig.getValue(name, pValue, bufflen, len); } /******************************************************************************* ** ** Function: NxpConfig_GetNum ** ** Description: API function for getting a numerical value of a setting ** ** Returns: true, if successful ** *******************************************************************************/ bool NxpConfig_GetNum(const char* name, void* pValue, size_t len) { if ((name == nullptr) || (pValue == nullptr)){ ALOGE("[%s] Invalid arguments", __func__); return false; } const uwbParam* pParam = gConfig.find(name); if ((pParam == nullptr) || (pParam->getType() != uwbParam::type::NUMBER)) { ALOGE("Config:%s not found in the config file", name); return false; } unsigned long v = pParam->numValue(); switch (len) { case sizeof(unsigned long): *(static_cast(pValue)) = (unsigned long)v; break; case sizeof(unsigned short): *(static_cast(pValue)) = (unsigned short)v; break; case sizeof(unsigned char): *(static_cast (pValue)) = (unsigned char)v; break; default: ALOGE("[%s] unsupported length:%zu", __func__, len); return false; } return true; } // Get the length of a 'string-array' type parameter bool NxpConfig_GetStrArrayLen(const char* name, size_t* pLen) { const uwbParam* param = gConfig.find(name); if (!param || param->getType() != uwbParam::type::STRINGARRAY) return false; *pLen = param->str_arr_len(); return true; } // Get a string value from 'string-array' type parameters, index zero-based bool NxpConfig_GetStrArrayVal(const char* name, int index, char* pValue, size_t len) { const uwbParam* param = gConfig.find(name); if (!param || param->getType() != uwbParam::type::STRINGARRAY) return false; if (index < 0 || index >= param->str_arr_len()) return false; if (len < param->str_arr_elem_len(index) + 1) return false; strncpy(pValue, param->str_arr_elem(index), len); return true; }