Files
ANSCORE/modules/ANSUtilities/ANSUtilities.cpp

1285 lines
57 KiB
C++
Raw Normal View History

2026-03-28 16:54:11 +11:00
#include "ANSUtilities.h"
2026-03-31 14:10:21 +11:00
#ifdef _WIN32
#include <windows.h>
#endif
2026-03-28 16:54:11 +11:00
#include <iostream>
#include <CkFileAccess.h>
#include <CkAuthGoogle.h>
#include <CkSocket.h>
#include <CkGlobal.h>
#include <CkMailMan.h>
#include <CkEmail.h>
#include <CkMht.h>
#include <vector>
static bool ansutilityLicenceValid = false;
static std::once_flag ansutilityLicenseOnceFlag;
std::timed_mutex timeImageMutex;
namespace ANSCENTER
{
static void VerifyGlobalANSUltilityLicense(const std::string& licenseKey) {
try {
static const std::vector<std::pair<int, std::string>> licenseChecks = {
{1000, "ANNHUB-LV"},
{1001, "DLHUB-LV"},
{1002, "ODHUB-LV"},
{1003, "ANSVIS"},
{1004, "ANSFR"},
{1005, "ANSOCR"},
{1006, "ANSALPR"},
{1007, "ANSCV"},
{1008, "ANSSRT"}
};
ansutilityLicenceValid = false;
for (const auto& [productId, productName] : licenseChecks) {
if (ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, productId, productName)) {
ansutilityLicenceValid = true;
break; // Stop at the first valid license
}
}
}
catch (const std::exception& e) {
ansutilityLicenceValid = false;
}
}
ANSUtilities::ANSUtilities() {
_unlockCode = "ANSDRC.CB1122026_MEQCIFwO1IFQCG0BhZwsXFO68QUU6mDB5uge4duOsqOJanEyAiAB67ahqnXin4SRy0vIegISgbFlpldmbuS5gbU21GYVqA==";// "ANSDRC.CB1082025_Ax6P3M7F8B3d";//
_fireBaseSessionToken = "";
_proxyHost = "";
_proxyPort = 0;
_proxyUsername = "";
_proxyPassword = "";
_bProxy = false;
}
void ANSUtilities::CheckLicense() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try {
// Check once globally
std::call_once(ansutilityLicenseOnceFlag, [this]() {
VerifyGlobalANSUltilityLicense(_licenseKey);
});
// Update this instance's local license flag
_isLicenseValid = ansutilityLicenceValid;
}
catch (const std::exception& e) {
this->_logger.LogFatal("ANSUtilities::CheckLicense. Error:", e.what(), __FILE__, __LINE__);
}
}
void ANSUtilities::CheckUnlockCode() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try {
CkGlobal glob;
_unlockCode = "ANSDRC.CB1122026_MEQCIFwO1IFQCG0BhZwsXFO68QUU6mDB5uge4duOsqOJanEyAiAB67ahqnXin4SRy0vIegISgbFlpldmbuS5gbU21GYVqA==";// "ANSDRC.CB1082025_Ax6P3M7F8B3d";
_isUnlockCodeValid = glob.UnlockBundle(_unlockCode.c_str());
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::CheckUnlockCode", glob.lastErrorText(), __FILE__, __LINE__);
return;
}
int status = glob.get_UnlockStatus();
if (status != 2) {
_logger.LogDebug("ANSUtilities::CheckUnlockCode", "Unlocked in trial mode.", __FILE__, __LINE__);
}
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::CheckUnlockCode", e.what(), __FILE__, __LINE__);
}
}
ANSUtilities::~ANSUtilities()
{
_fireBaseSessionToken = "";
}
bool ANSUtilities::Initialize(const std::string& licenseKey) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try {
_licenseKey = licenseKey;
CheckLicense();
CheckUnlockCode();
return _isLicenseValid && _isUnlockCodeValid;
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::Initialize", e.what(), __FILE__, __LINE__);
return false;
}
}
bool ANSUtilities::SetServerProxy(const std::string& proxyHost, int proxyPort, const std::string& proxyUsername, const std::string& proxyPassword) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try {
_proxyHost = proxyHost;
_proxyPort = proxyPort;
_proxyUsername = proxyUsername;
_proxyPassword = proxyPassword;
_bProxy = false;
if (!_proxyHost.empty() && (_proxyPort > 0)) {
_bProxy = true;
}
else {
_bProxy = false;
}
return true;
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::SetServerProxy", e.what(), __FILE__, __LINE__);
return false;
}
}
std::string ANSUtilities::GetFirebaseCloudMessageAcccessToken(std::string privateKey)
{
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isLicenseValid) {
this->_logger.LogFatal("ANSUtilities::GetFirebaseCloudMessageAcccessToken()", "Error: Invalid license key", __FILE__, __LINE__);
return "Error: Invalid license key";
}
if (!_isUnlockCodeValid) {
this->_logger.LogFatal("ANSUtilities::GetFirebaseCloudMessageAcccessToken()", "Error: Invalid unlock code", __FILE__, __LINE__);
return "Error: Invalid unlock code";
}
try {
auto currentTime = std::chrono::system_clock::now();
// Check if we need to generate a new token
bool needNewToken = false;
if (!tokenInitialized) {
std::cout << "First time calling getToken(), generating initial token..." << std::endl;
needNewToken = true;
}
else {
// Calculate time difference in seconds since token was generated
auto timeDiff = std::chrono::duration_cast<std::chrono::seconds>(currentTime - tokenGeneratedTime);
long secondsElapsed = timeDiff.count();
std::cout << "Time since token generated: " << secondsElapsed << " seconds" << std::endl;
if (secondsElapsed >= TOKEN_EXPIRY_SECONDS) {
std::cout << "Token expired (" << secondsElapsed << "s >= " << TOKEN_EXPIRY_SECONDS << "s), regenerating..." << std::endl;
needNewToken = true;
}
else {
long remainingTime = TOKEN_EXPIRY_SECONDS - secondsElapsed;
std::cout << "Token still valid, " << remainingTime << " seconds remaining" << std::endl;
}
}
if (needNewToken) {
CkFileAccess fac;
const char* jsonKey = "{"
"\"type\": \"service_account\","
"\"project_id\": \"ansaiboxpushnotifications\","
"\"private_key_id\": \"62f7485c308b9ea613866c24c98736fec498d47d\","
"\"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDSwXb/N4vPkANa\\nA7nwOTxS9NVCup/9mIdyR7FRYJNV8uHY7699I8LCCvG5rIhWGZ/Rb4Z9ocJKuuj9\\nRelsjTDEWeBU/o8ip41Ru8K3KlOO25HIVDckyhbmfDzzlf8Ham1HhOUZmfDUfQ2d\\n5YoUXflqoCyvOi8aqUgfcP9uZuIpEhyQvjqDfY0d0B36NoXpilFE8IuIlBsnpfZn\\nYkeHs/Cl6CDaKxGeUZmrqinyMH5f7m4Nz30V5s8Xdl4WZdoYhVatn1or57Zjy/zl\\ng+4rWeHRbYrBUJlMb8/7IaaZ7KuDkh0awEFIDvLuNvjXkaGKJT33vxKSsaH4yrhg\\nYiR5OVY3AgMBAAECggEADiUS5f0l4ofhWbS/UXqd7FlnSMO6wivvB0H9ih8ntFCJ\\nTOSFTCpOw3Q1lgcY3WJ54fYQujTVk+togLsk9/af68W2cy3kkGhbaT1nS6DJG+Dr\\nr1zLmKoBkHWNJ7IM/EPt0qt+LtIwoipEdDD4K/bEqx3V8eq/R5RN9WJBmnjIPAZO\\nXYGtTBu66yTjMVX9AFqYGJwaW96mHAi3lI5Mso5wTmFA8tSBiWmiYgpDDUNzu+4w\\nPnbDJabMbv4xnC6UtiqLIvv+P3BWWyLz0bzZ7W9P2fGK9Rz04leTXZlNcfC/IIAr\\nXDPVZfF7E5Wici53Cwc/QnxoCVH9n9hqsyTTUrKYbQKBgQD9WuCLVZRwKnAsW2Wx\\nZjh8Z92aGjIoc/lJTYaFsYGT7Hx496izhmg18kUillUDe/L9gHuDJPIMv89irjV1\\n07jN+z+tANyFT+h/NHCSxreG8FDbo8F88snekMEsBym3CicE/P6aWVx+lIig8rPS\\nHAqA1WOwdFzto/rG4zFKIiajYwKBgQDU9LxgERHwoy8mt2U5h/hg4oiKlycFJCFV\\nsgEqhVFN7Cs8jlov4tJh9863tVeYYGSKrP1HS9dVUE+7VY2qgPbyAtR5QZeHIV+b\\nzMx8o4HVLN4ffmld+/mYiAzIQiQeZOoC0mczakQpRDE8sghQQoYLwEcKXcTkLr75\\nk1XIpT4cHQKBgQCmMSzGeZbrlQsMLdAhdHptMPzuj2yDmL/X0+EAZhYn4KMt/tdN\\nHEfTy16Kd67AoFge7l8XAe89ab0ycDBlYEMD62IzrDL7yBUtDEskHPJas912lo7f\\n1auSMcZliTVV+nTqEsM4oJHJ/sk5Oru2gepp5JCGOW6T/FMOkA3PIWPTHQKBgQCG\\n456Om0Fp03OCaphLoLzLYbJrVuL4drJGvcHPVTLy0K1yZhjqTBpGw9jEtLEPa79D\\nt9+W0YtMFtrqJn7diWLiWLiNNebtSU5uOYMtT8Rla04nVMMZLQoke8jc8EhAmFtB\\n/lQwVRdnrDIj6AEsFXci6mAVSN/2SUXegFzOAx0cYQKBgBYCGGKWd/EFvszO5VS6\\nnegR3fpx4GRShA4iWfbQhPcjPunKthUAUguN4m+FfQuSA6xaBsz+om9hRiSCHe2l\\nGio/U7z2QYznnl/furGXEhS6yE+u3cSN82N0PV/c4XY0DUqKycE6cx9GVTOmx7NP\\n897fh7f103RrGgIRivtM1g3g\\n-----END PRIVATE KEY-----\\n\","
"\"client_email\": \"firebase-adminsdk-tcw8f@ansaiboxpushnotifications.iam.gserviceaccount.com\","
"\"client_id\": \"108921284506387607836\","
"\"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\","
"\"token_uri\": \"https://oauth2.googleapis.com/token\","
"\"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\","
"\"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-tcw8f%40ansaiboxpushnotifications.iam.gserviceaccount.com\","
"\"universe_domain\": \"googleapis.com\""
"}";
if (!_isLicenseValid) {
this->_logger.LogFatal("ANSUtilities::GetFirebaseCloudMessageAcccessToken()", "Error: Invalid license key", __FILE__, __LINE__);
_fireBaseSessionToken = "";
return "Error: Invalid license key";
}
if (!_isUnlockCodeValid) {
this->_logger.LogFatal("ANSUtilities::GetFirebaseCloudMessageAcccessToken()", "Error: Invalid unlock code", __FILE__, __LINE__);
_fireBaseSessionToken = "";
return "Error: Invalid unlock code";
}
if (privateKey != "62f7485c308b9ea613866c24c98736fec498d47d") {
this->_logger.LogFatal("ANSUtilities::GetFirebaseCloudMessageAcccessToken()", "Error: Invalid private key", __FILE__, __LINE__);
_fireBaseSessionToken = "";
return "Error: Invalid private key";
}
CkAuthGoogle gAuth;
gAuth.put_JsonKey(jsonKey);
gAuth.put_Scope("https://www.googleapis.com/auth/cloud-platform");
gAuth.put_ExpireNumSeconds(TOKEN_EXPIRY_SECONDS);
gAuth.put_SubEmailAddress("");
CkSocket tlsSock;
bool success = tlsSock.Connect("www.googleapis.com", 443, true, 5000);
if (success != true) {
this->_logger.LogFatal("ANSUtilities::GetFirebaseCloudMessageAcccessToken()", tlsSock.lastErrorText(), __FILE__, __LINE__);
_fireBaseSessionToken = "";
return std::string("Error:") + tlsSock.lastErrorText();
}
success = gAuth.ObtainAccessToken(tlsSock);
if (success != true) {
this->_logger.LogFatal("ANSUtilities::GetFirebaseCloudMessageAcccessToken()", gAuth.lastErrorText(), __FILE__, __LINE__);
_fireBaseSessionToken = "";
return std::string("Error:") + tlsSock.lastErrorText();
}
_fireBaseSessionToken = gAuth.accessToken();
tokenGeneratedTime = currentTime; // Reset the time when token is generated
tokenInitialized = true;
}
return _fireBaseSessionToken;
}
catch (std::exception& e) {
this->_logger.LogFatal("ANSUtilities::GetFirebaseCloudMessageAcccessToken()", e.what(), __FILE__, __LINE__);
_fireBaseSessionToken = "";
return "Error: " + std::string(e.what());
}
}
std::string ANSUtilities::CreateAWSSNSTopic(const std::string& topicName) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::CreateAWSSNSTopic", "Error: Invalid unlock code", __FILE__, __LINE__);
return "Error: Invalid unlock code";
}
if (!_isLicenseValid) {
_logger.LogFatal("ANSUtilities::CreateAWSSNSTopic", "Error: Invalid license key", __FILE__, __LINE__);
return "Error: Invalid license key";
}
try {
CkRest _rest;
_rest.AddQueryParam("Action", "CreateTopic");
_rest.AddQueryParam("Name", topicName.c_str());
const char* responseXml = _rest.fullRequestNoBody("GET", "/");
if (!_rest.get_LastMethodSuccess()) {
_logger.LogFatal("ANSUtilities::CreateAWSSNSTopic", _rest.lastErrorText(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.lastErrorText());
}
if (_rest.get_ResponseStatusCode() != 200) {
_logger.LogError("ANSUtilities::CreateAWSSNSTopic", _rest.responseHeader(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.responseHeader());
}
CkXml xml;
xml.LoadXml(responseXml);
return xml.chilkatPath("CreateTopicResult|TopicArn|*");
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::CreateAWSSNSTopic", e.what(), __FILE__, __LINE__);
return "Error: " + std::string(e.what());
}
}
bool ANSUtilities::DeleteAWSSNSTopic(const std::string& topicARN) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::DeleteAWSSNSTopic", "Error: Invalid unlock code", __FILE__, __LINE__);
return false;
}
if (!_isLicenseValid) {
_logger.LogFatal("ANSUtilities::DeleteAWSSNSTopic", "Error: Invalid license key", __FILE__, __LINE__);
return false;
}
try {
CkRest _rest;
_rest.AddQueryParam("Action", "DeleteTopic");
_rest.AddQueryParam("TopicArn", topicARN.c_str());
const char* responseXml = _rest.fullRequestNoBody("GET", "/");
if (!_rest.get_LastMethodSuccess()) {
_logger.LogFatal("ANSUtilities::DeleteAWSSNSTopic", _rest.lastErrorText(), __FILE__, __LINE__);
return false;
}
if (_rest.get_ResponseStatusCode() != 200) {
_logger.LogError("ANSUtilities::DeleteAWSSNSTopic", _rest.responseHeader(), __FILE__, __LINE__);
return false;
}
return true;
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::DeleteAWSSNSTopic", e.what(), __FILE__, __LINE__);
return false;
}
}
std::string ANSUtilities::SubcribeSMSPhoneNumber(const std::string& topicARN, const std::string& phoneNumber) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::SubscribeSMSPhoneNumber", "Error: Invalid unlock code", __FILE__, __LINE__);
return "Error: Invalid unlock code";
}
if (!_isLicenseValid) {
_logger.LogFatal("ANSUtilities::SubscribeSMSPhoneNumber", "Error: Invalid license key", __FILE__, __LINE__);
return "Error: Invalid license key";
}
try {
CkRest _rest;
_rest.AddQueryParam("Action", "Subscribe");
_rest.AddQueryParam("Endpoint", phoneNumber.c_str()); // Phone number in E.164 format
_rest.AddQueryParam("Protocol", "sms");
_rest.AddQueryParam("TopicArn", topicARN.c_str());
const char* responseXml = _rest.fullRequestNoBody("GET", "/");
if (!_rest.get_LastMethodSuccess()) {
_logger.LogError("ANSUtilities::SubscribeSMSPhoneNumber", _rest.lastErrorText(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.lastErrorText());
}
if (_rest.get_ResponseStatusCode() != 200) {
_logger.LogError("ANSUtilities::SubscribeSMSPhoneNumber", _rest.responseHeader(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.responseHeader());
}
CkXml xml;
xml.LoadXml(responseXml);
return xml.chilkatPath("SubscribeResult|SubscriptionArn|*");
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::SubscribeSMSPhoneNumber", e.what(), __FILE__, __LINE__);
return "Error: " + std::string(e.what());
}
}
std::string ANSUtilities::SubscribeEmailAddress(const std::string& topicARN, const std::string& emailAddress) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::SubscribeEmailAddress", "Error: Invalid unlock code", __FILE__, __LINE__);
return "Error: Invalid unlock code";
}
if (!_isLicenseValid) {
_logger.LogFatal("ANSUtilities::SubscribeEmailAddress", "Error: Invalid license key", __FILE__, __LINE__);
return "Error: Invalid license key";
}
try {
CkRest _rest;
_rest.AddQueryParam("Action", "Subscribe");
_rest.AddQueryParam("Endpoint", emailAddress.c_str());
_rest.AddQueryParam("Protocol", "email");
_rest.AddQueryParam("TopicArn", topicARN.c_str());
const char* responseXml = _rest.fullRequestNoBody("GET", "/");
if (!_rest.get_LastMethodSuccess()) {
_logger.LogError("ANSUtilities::SubscribeEmailAddress", _rest.lastErrorText(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.lastErrorText());
}
if (_rest.get_ResponseStatusCode() != 200) {
_logger.LogError("ANSUtilities::SubscribeEmailAddress", _rest.responseHeader(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.responseHeader());
}
CkXml xml;
xml.LoadXml(responseXml);
return xml.chilkatPath("SubscribeResult|SubscriptionArn|*"); // Usually "pending confirmation"
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::SubscribeEmailAddress", e.what(), __FILE__, __LINE__);
return "Error: " + std::string(e.what());
}
}
std::string ANSUtilities::SendMessageToTopic(const std::string& topicARN, const std::string& subjectContent, const std::string& messageContent) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::SendMessageToTopic", "Error: Invalid unlock code", __FILE__, __LINE__);
return "Error: Invalid unlock code";
}
if (!_isLicenseValid) {
_logger.LogFatal("ANSUtilities::SendMessageToTopic", "Error: Invalid license key", __FILE__, __LINE__);
return "Error: Invalid license key";
}
try {
CkRest _rest;
_rest.AddQueryParam("Action", "Publish");
_rest.AddQueryParam("TopicArn", topicARN.c_str());
_rest.AddQueryParam("Subject", subjectContent.c_str()); // Subject line for emails
_rest.AddQueryParam("Message", messageContent.c_str()); // The message body
const char* responseXml = _rest.fullRequestNoBody("GET", "/");
if (!_rest.get_LastMethodSuccess()) {
_logger.LogError("ANSUtilities::SendMessageToTopic", _rest.lastErrorText(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.lastErrorText());
}
if (_rest.get_ResponseStatusCode() != 200) {
_logger.LogError("ANSUtilities::SendMessageToTopic", _rest.responseHeader(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.responseHeader());
}
CkXml xml;
xml.LoadXml(responseXml);
return xml.chilkatPath("PublishResult|MessageId|*"); // Return the MessageId
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::SendMessageToTopic", e.what(), __FILE__, __LINE__);
return "Error: " + std::string(e.what());
}
}
std::string ANSUtilities::SendMessageToPhoneNumber(const std::string& phoneNumber, const std::string& messageContent) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::SendMessageToPhoneNumber", "Error: Invalid unlock code", __FILE__, __LINE__);
return "Error: Invalid unlock code";
}
if (!_isLicenseValid) {
_logger.LogFatal("ANSUtilities::SendMessageToPhoneNumber", "Error: Invalid license key", __FILE__, __LINE__);
return "Error: Invalid license key";
}
try {
CkRest _rest;
_rest.AddQueryParam("Action", "Publish");
_rest.AddQueryParam("PhoneNumber", phoneNumber.c_str());
_rest.AddQueryParam("Message", messageContent.c_str());
const char* responseXml = _rest.fullRequestNoBody("GET", "/");
if (!_rest.get_LastMethodSuccess()) {
_logger.LogError("ANSUtilities::SendMessageToPhoneNumber", _rest.lastErrorText(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.lastErrorText());
}
if (_rest.get_ResponseStatusCode() != 200) {
_logger.LogError("ANSUtilities::SendMessageToPhoneNumber", _rest.responseHeader(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.responseHeader());
}
CkXml xml;
xml.LoadXml(responseXml);
return xml.chilkatPath("PublishResult|MessageId|*"); // Return the MessageId
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::SendMessageToPhoneNumber", e.what(), __FILE__, __LINE__);
return "Error: " + std::string(e.what());
}
}
std::string ANSUtilities::ListAWSSNSTopic() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::ListAWSSNSTopic", "Error: Invalid unlock code", __FILE__, __LINE__);
return "Error: Invalid unlock code";
}
if (!_isLicenseValid) {
_logger.LogFatal("ANSUtilities::ListAWSSNSTopic", "Error: Invalid license key", __FILE__, __LINE__);
return "Error: Invalid license key";
}
try {
CkRest _rest;
_rest.AddQueryParam("Action", "ListTopics");
const char* responseXml = _rest.fullRequestNoBody("GET", "/");
if (!_rest.get_LastMethodSuccess()) {
_logger.LogError("ANSUtilities::ListAWSSNSTopic", _rest.lastErrorText(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.lastErrorText());
}
if (_rest.get_ResponseStatusCode() != 200) {
_logger.LogError("ANSUtilities::ListAWSSNSTopic", _rest.responseHeader(), __FILE__, __LINE__);
return "Error: " + std::string(_rest.responseHeader());
}
CkXml xml;
xml.LoadXml(responseXml);
// Move to Topics list
xml.chilkatPath("ListTopicsResult|Topics|$");
int numTopics = xml.get_NumChildren();
std::vector<std::string> topics;
topics.reserve(numTopics); // Slight optimization
for (int i = 0; i < numTopics; ++i) {
xml.GetChild2(i);
topics.emplace_back(xml.getChildContent("TopicArn"));
xml.GetParent2();
}
if (topics.empty()) {
_logger.LogError("ANSUtilities::ListAWSSNSTopic", "No topics found", __FILE__, __LINE__);
return "";
}
// Concatenate topics with ';'
std::string topicsStr;
for (size_t i = 0; i < topics.size(); ++i) {
topicsStr += topics[i];
if (i + 1 != topics.size()) {
topicsStr += ";";
}
}
return topicsStr;
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::ListAWSSNSTopic", e.what(), __FILE__, __LINE__);
return "Error: " + std::string(e.what());
}
}
bool ANSUtilities::AuthenticateGCS(const std::string& jsonKeyFile) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::AuthenticateGCS", "Error: Invalid unlock code", __FILE__, __LINE__);
return false;
}
if (_isAuthenticated) {
return true;
}
try {
_authGoogle.put_JsonKey(jsonKeyFile.c_str());
_authGoogle.put_Scope("https://www.googleapis.com/auth/cloud-platform");
_authGoogle.put_ExpireNumSeconds(3600); // 1 hour token
_authGoogle.put_SubEmailAddress("");
CkSocket tlsSock;
bool success = tlsSock.Connect("www.googleapis.com", 443, true, 5000);
if (!success) {
_logger.LogError("ANSUtilities::AuthenticateGCS", tlsSock.lastErrorText(), __FILE__, __LINE__);
return false;
}
success = _authGoogle.ObtainAccessToken(tlsSock);
if (!success) {
_logger.LogError("ANSUtilities::AuthenticateGCS", _authGoogle.lastErrorText(), __FILE__, __LINE__);
return false;
}
if (!_authGoogle.accessToken()) {
_logger.LogError("ANSUtilities::AuthenticateGCS", _authGoogle.lastErrorText(), __FILE__, __LINE__);
return false;
}
_isAuthenticated = true;
return true;
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::AuthenticateGCS", e.what(), __FILE__, __LINE__);
return false;
}
}
bool ANSUtilities::UploadMatToGCS(const std::string& bucketName, const std::string& objectName, const cv::Mat& image) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::UploadMatToGCS", "Error: Invalid unlock code", __FILE__, __LINE__);
return false;
}
if (!_isAuthenticated) {
_logger.LogError("ANSUtilities::UploadMatToGCS", "Not authenticated. Call AuthenticateGCS() first.", __FILE__, __LINE__);
return false;
}
try {
CkRest rest;
CkSocket _gcsSocket;
std::string gcsHost = "www.googleapis.com";
bool bTls = true;
bool autoReconnect = true;
int port = 443;
int maxWaitMs = 15000;
if (_bProxy) {
if (!_proxyHost.empty() && (_proxyPort > 0)) {
_gcsSocket.put_HttpProxyHostname(_proxyHost.c_str());
_gcsSocket.put_HttpProxyPort(_proxyPort);
_gcsSocket.put_HttpProxyUsername(_proxyUsername.c_str());
_gcsSocket.put_HttpProxyPassword(_proxyPassword.c_str());
_gcsSocket.put_HttpProxyForHttp(true);
if (!_gcsSocket.Connect(gcsHost.c_str(), port, bTls, maxWaitMs)) {
_logger.LogFatal("ANSUtilities::UploadMatToGCS", _gcsSocket.lastErrorText(), __FILE__, __LINE__);
return false;
}
// Bind socket to rest
if (!rest.UseConnection(_gcsSocket, autoReconnect)) {
_logger.LogFatal("ANSUtilities::UploadMatToGCS", rest.lastErrorText(), __FILE__, __LINE__);
return false;
}
rest.SetAuthGoogle(_authGoogle);
}
else {
_logger.LogFatal("ANSUtilities::UploadMatToGCS", "Invalid proxy hostname and port", __FILE__, __LINE__);
return false;
}
}
else { // No proxy
rest.SetAuthGoogle(_authGoogle);
// Connect to Google Cloud Storage REST API
if (!rest.Connect(gcsHost.c_str(), port, bTls, autoReconnect)) {
_logger.LogError("ANSUtilities::UploadMatToGCS", rest.lastErrorText(), __FILE__, __LINE__);
return false;
}
}
// Convert cv::Mat to JPEG binary
std::vector<uchar> jpegBuffer;
std::vector<int> compressionParams = { cv::IMWRITE_JPEG_QUALITY, 90 };
if (!cv::imencode(".jpg", image, jpegBuffer, compressionParams)) {
_logger.LogError("ANSUtilities::UploadMatToGCS", "Failed to encode cv::Mat as JPEG.", __FILE__, __LINE__);
return false;
}
// Convert std::vector<uchar> to CkByteData
CkByteData jpegBytes;
if (!jpegBuffer.empty()) {
jpegBytes.append2(jpegBuffer.data(), static_cast<unsigned long>(jpegBuffer.size()));
}
// Prepare the HTTP request to upload the file
std::string urlPath = "/upload/storage/v1/b/" + bucketName + "/o?uploadType=media&name=" + objectName;
rest.AddHeader("Content-Type", "image/jpeg");
// Upload the JPEG data to GCS
std::string jsonResponse = rest.fullRequestBinary("POST", urlPath.c_str(), jpegBytes);
if (!rest.get_LastMethodSuccess()) {
_logger.LogError("ANSUtilities::UploadMatToGCS", rest.lastErrorText(), __FILE__, __LINE__);
return false;
}
int statusCode = rest.get_ResponseStatusCode();
if (statusCode != 200) {
_logger.LogError("ANSUtilities::UploadMatToGCS", rest.responseHeader(), __FILE__, __LINE__);
return false;
}
_logger.LogDebug("ANSUtilities::UploadMatToGCS", "Upload successful. Status Code: " + std::to_string(statusCode), __FILE__, __LINE__);
return true;
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::UploadMatToGCS", e.what(), __FILE__, __LINE__);
return false;
}
}
bool ANSUtilities::UploadMatToGCS(const std::string& bucketName, const std::string& objectName, unsigned char* jpeg_string, int32 bufferLength) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isUnlockCodeValid) {
_logger.LogFatal("ANSUtilities::UploadMatToGCS", "Error: Invalid unlock code", __FILE__, __LINE__);
return false;
}
if (!_isAuthenticated) {
_logger.LogError("ANSUtilities::UploadMatToGCS", "Not authenticated. Call AuthenticateGCS() first.", __FILE__, __LINE__);
return false;
}
if(bufferLength<=0) {
_logger.LogError("ANSUtilities::UploadMatToGCS", "Invalid buffer length", __FILE__, __LINE__);
return false;
}
try {
CkRest rest;
CkSocket _gcsSocket;
std::string gcsHost = "www.googleapis.com";
bool bTls = true;
bool autoReconnect = true;
int port = 443;
int maxWaitMs = 15000;
if (_bProxy) {
if (!_proxyHost.empty() && (_proxyPort > 0)) {
_gcsSocket.put_HttpProxyHostname(_proxyHost.c_str());
_gcsSocket.put_HttpProxyPort(_proxyPort);
_gcsSocket.put_HttpProxyUsername(_proxyUsername.c_str());
_gcsSocket.put_HttpProxyPassword(_proxyPassword.c_str());
_gcsSocket.put_HttpProxyForHttp(true);
if (!_gcsSocket.Connect(gcsHost.c_str(), port, bTls, maxWaitMs)) {
_logger.LogFatal("ANSUtilities::UploadMatToGCS", _gcsSocket.lastErrorText(), __FILE__, __LINE__);
return false;
}
// Bind socket to rest
if (!rest.UseConnection(_gcsSocket, autoReconnect)) {
_logger.LogFatal("ANSUtilities::UploadMatToGCS", rest.lastErrorText(), __FILE__, __LINE__);
return false;
}
rest.SetAuthGoogle(_authGoogle);
}
else {
_logger.LogFatal("ANSUtilities::UploadMatToGCS", "Invalid proxy hostname and port", __FILE__, __LINE__);
return false;
}
}
else { // No proxy
rest.SetAuthGoogle(_authGoogle);
// Connect to Google Cloud Storage REST API
if (!rest.Connect(gcsHost.c_str(), port, bTls, autoReconnect)) {
_logger.LogError("ANSUtilities::UploadMatToGCS", rest.lastErrorText(), __FILE__, __LINE__);
return false;
}
}
// Convert std::vector<uchar> to CkByteData
CkByteData jpegBytes;
jpegBytes.append2(jpeg_string, static_cast<unsigned long>(bufferLength));
// Prepare the HTTP request to upload the file
std::string urlPath = "/upload/storage/v1/b/" + bucketName + "/o?uploadType=media&name=" + objectName;
rest.AddHeader("Content-Type", "image/jpeg");
// Upload the JPEG data to GCS
std::string jsonResponse = rest.fullRequestBinary("POST", urlPath.c_str(), jpegBytes);
if (!rest.get_LastMethodSuccess()) {
_logger.LogError("ANSUtilities::UploadMatToGCS", rest.lastErrorText(), __FILE__, __LINE__);
return false;
}
int statusCode = rest.get_ResponseStatusCode();
if (statusCode != 200) {
_logger.LogError("ANSUtilities::UploadMatToGCS", rest.responseHeader(), __FILE__, __LINE__);
return false;
}
_logger.LogDebug("ANSUtilities::UploadMatToGCS", "Upload successful. Status Code: " + std::to_string(statusCode), __FILE__, __LINE__);
return true;
}
catch (const std::exception& e) {
_logger.LogFatal("ANSUtilities::UploadMatToGCS", e.what(), __FILE__, __LINE__);
return false;
}
}
bool ANSUtilities::SendEmail(const std::string& smtpServer, int port,
const std::string& userName, const std::string& password,
const std::string& subjectContent,
const std::string& bodyHTMLContent,
const std::string& bodyTextContent,
const std::string& fromEmailSender,
const std::vector<std::string>& toEmails,
const std::vector<std::string>& ccEmails,
const std::vector<std::string>& bccEmails)
{
std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
if (!lock.try_lock_for(std::chrono::milliseconds(45000))) {
std::cerr << "Error: Mutex timeout in ANSUtilities::SendEmail!" << std::endl;
return false;
}
if (!CheckUnlockCode_S()) {
return false;
}
try {
// The mailman object is used for sending and receiving email.
CkMailMan mailman;
mailman.put_SmtpHost(smtpServer.c_str());
mailman.put_SmtpPort(port);
mailman.put_StartTLS(true);//STARTTLS
// Set the SMTP login/password
mailman.put_SmtpUsername(userName.c_str());
mailman.put_SmtpPassword(password.c_str());
CkEmail email;
if (!bodyHTMLContent.empty()) {
CkMht mht;
mht.put_UseCids(true);
const char* mime = 0;
mime = mht.htmlToEML(bodyHTMLContent.c_str());
bool setmime = email.SetFromMimeText(mime);
email.ConvertInlineImages();
}
email.put_Subject(subjectContent.c_str());
if (!bodyTextContent.empty())
email.put_Body(bodyTextContent.c_str());
email.put_From(fromEmailSender.c_str());
for (const auto& toEmail : toEmails) {
email.AddTo("", toEmail.c_str());
}
for (const auto& ccEmail : ccEmails) {
email.AddCC("", ccEmail.c_str());
}
for (const auto& bccEmail : bccEmails) {
email.AddBcc("", bccEmail.c_str());
}
bool success = mailman.SendEmail(email);
if (success != true) {
return false;
}
success = mailman.CloseSmtpConnection();
if (success != true) {
}
return success;
}
catch (const std::exception& e) {
return false;
}
}
bool ANSUtilities::CheckUnlockCode_S() {
static std::once_flag unlockOnceFlag;
static bool isUnlockValid = false;
try {
std::call_once(unlockOnceFlag, []() {
CkGlobal glob;
const std::string unlockCode = "ANSDRC.CB1122026_MEQCIFwO1IFQCG0BhZwsXFO68QUU6mDB5uge4duOsqOJanEyAiAB67ahqnXin4SRy0vIegISgbFlpldmbuS5gbU21GYVqA==";// "ANSDRC.CB1082025_Ax6P3M7F8B3d";
if (glob.UnlockBundle(unlockCode.c_str()) && glob.get_UnlockStatus() == 2) {
isUnlockValid = true;
}
});
return isUnlockValid;
}
catch (...) {
return false;
}
}
std::string ANSUtilities::AESEncryption(const std::string& inputString, const std::string& inputKey) {
if (!CheckUnlockCode_S()) {
return "";
}
try {
CkCrypt2 crypt;
// AES is also known as Rijndael.
crypt.put_CryptAlgorithm("aes");
// CipherMode may be "ecb", "cbc", "ofb", "cfb", "gcm", etc.
crypt.put_CipherMode("cbc");
// KeyLength may be 128, 192, 256
crypt.put_KeyLength(256);
// The padding scheme determines the contents of the bytes
// that are added to pad the result to a multiple of the
// encryption algorithm's block size. AES has a block
// size of 16 bytes, so encrypted output is always
// a multiple of 16.
crypt.put_PaddingScheme(0); // 0 = RFC 1423 padding scheme
// EncodingMode specifies the encoding of the output for
// encryption, and the input for decryption.
// It may be "hex", "url", "base64", or "quoted-printable".
crypt.put_EncodingMode("base64");
// Ensure inputKey is exactly 16 characters for IV.
std::string ivKey = inputKey.substr(0, std::min<size_t>(16, inputKey.size()));
if (ivKey.size() < 16) {
ivKey.append(16 - ivKey.size(), 'A'); // Pad with spaces if less than 16 characters
}
crypt.SetEncodedIV(ivKey.c_str(), "ascii");
// The secret key must equal the size of the key. For
// 256-bit encryption, the binary secret key is 32 bytes.
// For 128-bit encryption, the binary secret key is 16 bytes.
std::string secreteKey = inputKey.substr(0, std::min<size_t>(32, inputKey.size()));
if (secreteKey.size() < 32) {
secreteKey.append(32 - secreteKey.size(), 'A'); // Pad with spaces if less than 32 characters
}
crypt.SetEncodedKey(secreteKey.c_str(), "ascii");
const char* encStr = crypt.encryptStringENC(inputString.c_str());
return encStr ? std::string(encStr) : std::string();
}
catch (const std::exception& e) {
// Optional: If you want logging, consider static logging in static functions
return "";
}
}
std::string ANSUtilities::AESDecryption(const std::string& encryptString, const std::string& inputKey) {
if (!CheckUnlockCode_S()) {
return "";
}
try {
CkCrypt2 crypt;
// AES is also known as Rijndael.
crypt.put_CryptAlgorithm("aes");
// CipherMode may be "ecb", "cbc", "ofb", "cfb", "gcm", etc.
crypt.put_CipherMode("cbc");
// KeyLength may be 128, 192, 256
crypt.put_KeyLength(256);
// The padding scheme determines the contents of the bytes
// that are added to pad the result to a multiple of the
// encryption algorithm's block size. AES has a block
// size of 16 bytes, so encrypted output is always
// a multiple of 16.
crypt.put_PaddingScheme(0); // 0 = RFC 1423 padding scheme
// EncodingMode specifies the encoding of the output for
// encryption, and the input for decryption.
// It may be "hex", "url", "base64", or "quoted-printable".
crypt.put_EncodingMode("base64");
// Ensure inputKey is exactly 16 characters for IV.
std::string ivKey = inputKey.substr(0, std::min<size_t>(16, inputKey.size()));
if (ivKey.size() < 16) {
ivKey.append(16 - ivKey.size(), 'A'); // Pad with spaces if less than 16 characters
}
crypt.SetEncodedIV(ivKey.c_str(), "ascii");
// The secret key must equal the size of the key. For
// 256-bit encryption, the binary secret key is 32 bytes.
// For 128-bit encryption, the binary secret key is 16 bytes.
std::string secreteKey = inputKey.substr(0, std::min<size_t>(32, inputKey.size()));
if (secreteKey.size() < 32) {
secreteKey.append(32 - secreteKey.size(), 'A'); // Pad with spaces if less than 32 characters
}
crypt.SetEncodedKey(secreteKey.c_str(), "ascii");
const char* decStr = crypt.decryptStringENC(encryptString.c_str());
return decStr ? std::string(decStr) : std::string();
}
catch (const std::exception& e) {
// Optional: If you want logging, consider static logging in static functions
return "";
}
}
std::string ANSUtilities::MD5HashFile(const std::string& inputFilePath) {
if (!CheckUnlockCode_S()) {
return "";
}
try {
CkCrypt2 crypt;
// Set the name of the hash algorithm.
// Other choices include "sha1", "sha256", "sha384", "sha512", "md2", "md5", and "haval".
crypt.put_HashAlgorithm("md5");
// EncodingMode specifies the encoding of the hash output.
// It may be "hex", "url", "base64", or "quoted-printable".
crypt.put_EncodingMode("hex");
// Files of any type may be hashed -- it doesn't matter
// if the file is binary or text...
const char* hashStr = nullptr;
hashStr = crypt.hashFileENC(inputFilePath.c_str());
return hashStr ? std::string(hashStr) : std::string();
}
catch (const std::exception& e) {
// Optional: If you want logging, consider static logging in static functions
return "";
}
}
bool ANSUtilities::RestartPC() {
try {
return system("shutdown /r /t 0") == 0;
}
catch (...) {
return false;
}
}
2026-03-31 14:10:21 +11:00
std::string ANSUtilities::DecodeJsonUnicodeToUTF16LE(const std::string& escapedStr) {
std::string result;
result.reserve(escapedStr.size() * 2);
size_t i = 0;
while (i < escapedStr.size()) {
if (i + 5 < escapedStr.size() && escapedStr[i] == '\\' && escapedStr[i + 1] == 'u') {
char hex[5] = { escapedStr[i + 2], escapedStr[i + 3], escapedStr[i + 4], escapedStr[i + 5], 0 };
uint16_t codepoint = (uint16_t)strtoul(hex, nullptr, 16);
result += static_cast<char>(codepoint & 0xFF);
result += static_cast<char>((codepoint >> 8) & 0xFF);
i += 6;
} else {
result += escapedStr[i];
result += '\0';
i++;
}
}
return result;
}
std::string ANSUtilities::ConvertUTF16LEToUTF8(const char* utf16leBytes, int byteLen) {
#ifdef _WIN32
if (!utf16leBytes || byteLen <= 0) return "";
int wideLen = byteLen / (int)sizeof(wchar_t);
const wchar_t* wideStr = reinterpret_cast<const wchar_t*>(utf16leBytes);
// First call: get required UTF-8 buffer size
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wideStr, wideLen, nullptr, 0, nullptr, nullptr);
if (utf8Len <= 0) return "";
std::string utf8Str(utf8Len, 0);
WideCharToMultiByte(CP_UTF8, 0, wideStr, wideLen, &utf8Str[0], utf8Len, nullptr, nullptr);
return utf8Str;
#else
return std::string(utf16leBytes, byteLen);
#endif
}
std::string ANSUtilities::ConvertUTF8ToUTF16LE(const std::string& utf8Str) {
#ifdef _WIN32
if (utf8Str.empty()) return "";
int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), (int)utf8Str.size(), nullptr, 0);
if (wideLen <= 0) return "";
std::wstring wideStr(wideLen, 0);
MultiByteToWideChar(CP_UTF8, 0, utf8Str.c_str(), (int)utf8Str.size(), &wideStr[0], wideLen);
const char* rawBytes = reinterpret_cast<const char*>(wideStr.data());
return std::string(rawBytes, wideLen * sizeof(wchar_t));
#else
return utf8Str;
#endif
}
2026-04-06 14:20:43 +10:00
std::vector<unsigned char> ANSUtilities::RepairLabVIEWUTF16LE(const unsigned char* data, int len) {
std::vector<unsigned char> repaired;
if (!data || len <= 0) return repaired;
repaired.reserve(len + 32);
// Helper: emit a BMP codepoint as UTF-16LE pair
auto emitU16 = [&](uint16_t cp) {
repaired.push_back(static_cast<unsigned char>(cp & 0xFF));
repaired.push_back(static_cast<unsigned char>((cp >> 8) & 0xFF));
};
for (int i = 0; i < len; ) {
unsigned char b = data[i];
// --- 1. Detect embedded UTF-8 multi-byte sequences ---
// LabVIEW text controls may mix UTF-8 encoded characters into a
// UTF-16LE stream. UTF-8 lead bytes (C2-F4) followed by valid
// continuation bytes (80-BF) are a strong signal.
// We decode the UTF-8 codepoint and re-encode as UTF-16LE.
// 2-byte UTF-8: 110xxxxx 10xxxxxx (U+0080 .. U+07FF)
if (b >= 0xC2 && b <= 0xDF && i + 1 < len) {
unsigned char b1 = data[i + 1];
if ((b1 & 0xC0) == 0x80) {
uint32_t cp = ((b & 0x1F) << 6) | (b1 & 0x3F);
emitU16(static_cast<uint16_t>(cp));
i += 2;
continue;
}
}
// 3-byte UTF-8: 1110xxxx 10xxxxxx 10xxxxxx (U+0800 .. U+FFFF)
if (b >= 0xE0 && b <= 0xEF && i + 2 < len) {
unsigned char b1 = data[i + 1];
unsigned char b2 = data[i + 2];
if ((b1 & 0xC0) == 0x80 && (b2 & 0xC0) == 0x80) {
uint32_t cp = ((b & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F);
// Reject overlong encodings and surrogates
if (cp >= 0x0800 && (cp < 0xD800 || cp > 0xDFFF)) {
emitU16(static_cast<uint16_t>(cp));
i += 3;
continue;
}
}
}
// 4-byte UTF-8: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (U+10000 .. U+10FFFF)
if (b >= 0xF0 && b <= 0xF4 && i + 3 < len) {
unsigned char b1 = data[i + 1];
unsigned char b2 = data[i + 2];
unsigned char b3 = data[i + 3];
if ((b1 & 0xC0) == 0x80 && (b2 & 0xC0) == 0x80 && (b3 & 0xC0) == 0x80) {
uint32_t cp = ((b & 0x07) << 18) | ((b1 & 0x3F) << 12)
| ((b2 & 0x3F) << 6) | (b3 & 0x3F);
if (cp >= 0x10000 && cp <= 0x10FFFF) {
// Surrogate pair
cp -= 0x10000;
emitU16(static_cast<uint16_t>(0xD800 + (cp >> 10)));
emitU16(static_cast<uint16_t>(0xDC00 + (cp & 0x3FF)));
i += 4;
continue;
}
}
}
// --- 2. Normal UTF-16LE pair (low byte + 0x00 high byte) ---
if (i + 1 < len && data[i + 1] == 0x00) {
repaired.push_back(data[i]);
repaired.push_back(0x00);
i += 2;
}
// --- 3. Lone space byte — LabVIEW dropped the 0x00 high byte ---
else if (b == 0x20 && (i + 1 >= len || data[i + 1] != 0x00)) {
repaired.push_back(0x20);
repaired.push_back(0x00);
i += 1;
}
// --- 4. Non-ASCII UTF-16LE pair (e.g. ễ = C5 1E) ---
else if (i + 1 < len) {
repaired.push_back(data[i]);
repaired.push_back(data[i + 1]);
i += 2;
}
// --- 5. Trailing odd byte — skip ---
else {
i++;
}
}
return repaired;
}
bool ANSUtilities::IsValidUTF8(const unsigned char* data, int len) {
if (!data || len <= 0) return false;
bool hasMultiByte = false;
for (int i = 0; i < len; ) {
unsigned char b = data[i];
if (b <= 0x7F) {
// ASCII — valid, but alone doesn't prove UTF-8
i++;
} else if (b >= 0xC2 && b <= 0xDF) {
// 2-byte sequence
if (i + 1 >= len || (data[i + 1] & 0xC0) != 0x80) return false;
hasMultiByte = true;
i += 2;
} else if (b >= 0xE0 && b <= 0xEF) {
// 3-byte sequence
if (i + 2 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80) return false;
uint32_t cp = ((b & 0x0F) << 12) | ((data[i + 1] & 0x3F) << 6) | (data[i + 2] & 0x3F);
if (cp < 0x0800 || (cp >= 0xD800 && cp <= 0xDFFF)) return false; // overlong or surrogate
hasMultiByte = true;
i += 3;
} else if (b >= 0xF0 && b <= 0xF4) {
// 4-byte sequence
if (i + 3 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80 || (data[i + 3] & 0xC0) != 0x80) return false;
uint32_t cp = ((b & 0x07) << 18) | ((data[i + 1] & 0x3F) << 12) | ((data[i + 2] & 0x3F) << 6) | (data[i + 3] & 0x3F);
if (cp < 0x10000 || cp > 0x10FFFF) return false;
hasMultiByte = true;
i += 4;
} else {
return false; // invalid lead byte (C0, C1, F5-FF)
}
}
// Only confirm UTF-8 if we found at least one multi-byte sequence.
// Pure ASCII is ambiguous — let the caller decide.
return hasMultiByte;
}
2026-04-06 14:20:43 +10:00
std::string ANSUtilities::ConvertUTF16LEToUnicodeEscapes(const char* utf16leBytes, int byteLen) {
if (!utf16leBytes || byteLen <= 0) return "";
int offset = 0;
// Strip BOM (FF FE) if present
if (byteLen >= 2 &&
static_cast<unsigned char>(utf16leBytes[0]) == 0xFF &&
static_cast<unsigned char>(utf16leBytes[1]) == 0xFE) {
offset = 2;
}
int remaining = byteLen - offset;
if (remaining <= 0) return "";
// Drop trailing odd byte if present (e.g. null terminator appended by LabVIEW)
if (remaining % 2 != 0) remaining--;
2026-04-06 14:20:43 +10:00
int endPos = offset + remaining; // safe end position (even-aligned)
2026-04-06 14:20:43 +10:00
std::string result;
result.reserve(remaining * 3);
for (int i = offset; i + 1 < endPos; i += 2) {
2026-04-06 14:20:43 +10:00
uint16_t codepoint = static_cast<unsigned char>(utf16leBytes[i])
| (static_cast<unsigned char>(utf16leBytes[i + 1]) << 8);
// Pass through printable ASCII including space (0x20-0x7E)
// Escape control characters and non-ASCII as \uXXXX
2026-04-06 14:20:43 +10:00
if (codepoint >= 0x20 && codepoint <= 0x7E) {
result += static_cast<char>(codepoint);
} else {
char buf[7];
snprintf(buf, sizeof(buf), "\\u%04X", codepoint);
result += buf;
}
}
return result;
}
2026-04-07 07:59:46 +10:00
std::string ANSUtilities::ConvertUTF8ToUnicodeEscapes(const std::string& utf8Str) {
if (utf8Str.empty()) return "";
std::string result;
result.reserve(utf8Str.size() * 2);
size_t i = 0;
while (i < utf8Str.size()) {
unsigned char c = static_cast<unsigned char>(utf8Str[i]);
if (c >= 0x20 && c <= 0x7E) {
// Printable ASCII including space (0x20-0x7E) — pass through as-is
2026-04-07 07:59:46 +10:00
result += utf8Str[i];
i++;
} else if (c <= 0x7F) {
// Control chars (0x00-0x1F), DEL (0x7F) — escape as \uXXXX
char buf[7];
snprintf(buf, sizeof(buf), "\\u%04x", c);
result += buf;
i++;
2026-04-07 07:59:46 +10:00
} else {
// Multi-byte UTF-8 sequence -- decode to Unicode codepoint
uint32_t codepoint = 0;
int seqLen = 0;
if ((c & 0xE0) == 0xC0) { codepoint = c & 0x1F; seqLen = 2; }
else if ((c & 0xF0) == 0xE0) { codepoint = c & 0x0F; seqLen = 3; }
else if ((c & 0xF8) == 0xF0) { codepoint = c & 0x07; seqLen = 4; }
else { result += utf8Str[i]; i++; continue; } // invalid lead byte
bool valid = true;
for (int j = 1; j < seqLen && i + j < utf8Str.size(); j++) {
unsigned char b = static_cast<unsigned char>(utf8Str[i + j]);
if ((b & 0xC0) != 0x80) { valid = false; break; }
codepoint = (codepoint << 6) | (b & 0x3F);
}
if (!valid || i + seqLen > utf8Str.size()) {
result += utf8Str[i]; i++; continue; // skip invalid
}
if (codepoint <= 0xFFFF) {
char buf[7];
snprintf(buf, sizeof(buf), "\\u%04x", codepoint);
result += buf;
} else {
// Surrogate pair for codepoints above U+FFFF
codepoint -= 0x10000;
uint16_t high = 0xD800 + (uint16_t)(codepoint >> 10);
uint16_t low = 0xDC00 + (uint16_t)(codepoint & 0x3FF);
char buf[14];
snprintf(buf, sizeof(buf), "\\u%04x\\u%04x", high, low);
result += buf;
}
i += seqLen;
}
}
return result;
}
2026-04-06 14:20:43 +10:00
std::string ANSUtilities::ConvertUnicodeEscapesToUTF8(const std::string& escapedStr) {
if (escapedStr.empty()) return "";
// First decode \uXXXX to UTF-16LE, then convert to UTF-8
std::string utf16le;
utf16le.reserve(escapedStr.size() * 2);
size_t i = 0;
while (i < escapedStr.size()) {
if (i + 5 < escapedStr.size() && escapedStr[i] == '\\' && escapedStr[i + 1] == 'u') {
char hex[5] = { escapedStr[i + 2], escapedStr[i + 3], escapedStr[i + 4], escapedStr[i + 5], 0 };
uint16_t codepoint = (uint16_t)strtoul(hex, nullptr, 16);
utf16le += static_cast<char>(codepoint & 0xFF);
utf16le += static_cast<char>((codepoint >> 8) & 0xFF);
i += 6;
} else {
utf16le += escapedStr[i];
utf16le += '\0';
i++;
}
}
return ConvertUTF16LEToUTF8(utf16le.data(), (int)utf16le.size());
}
2026-04-07 07:59:46 +10:00
std::string ANSUtilities::DoubleEscapeUnicode(const std::string& str) {
if (str.empty()) return "";
std::string result;
result.reserve(str.size() + str.size() / 4);
for (size_t i = 0; i < str.size(); i++) {
if (str[i] == '\\' && i + 1 < str.size() && str[i + 1] == 'u') {
result += "\\\\u";
i++; // skip 'u', loop advances past '\'
} else {
result += str[i];
}
}
return result;
}
std::string ANSUtilities::ConvertUTF8ToDoubleEscapedUnicode(const std::string& utf8Str) {
return DoubleEscapeUnicode(ConvertUTF8ToUnicodeEscapes(utf8Str));
}
std::string ANSUtilities::UnescapeDoubleEscapedUnicode(const std::string& str) {
if (str.empty()) return "";
2026-04-07 08:53:53 +10:00
// Quick scan: if no \\u pattern exists, return original string unchanged
bool found = false;
for (size_t i = 0; i + 2 < str.size(); i++) {
if (str[i] == '\\' && str[i + 1] == '\\' && str[i + 2] == 'u') {
found = true;
break;
}
}
if (!found) return str;
2026-04-07 07:59:46 +10:00
std::string result;
result.reserve(str.size());
for (size_t i = 0; i < str.size(); i++) {
if (str[i] == '\\' && i + 2 < str.size() && str[i + 1] == '\\' && str[i + 2] == 'u') {
result += "\\u";
i += 2; // skip past '\\' and 'u', loop will advance past first '\'
} else {
result += str[i];
}
}
return result;
}
2026-03-28 16:54:11 +11:00
}