* Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved.
* 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.
*/
#include "hi_audit.h"
#include <chrono>
#include <ctime>
#include <dirent.h>
#include <fcntl.h>
#include <iomanip>
#include <sys/time.h>
#include <unistd.h>
#include <algorithm>
#include <locale>
#include <codecvt>
#include <regex>
#include "hilog_wrapper.h"
#include "zip_util.h"
namespace OHOS {
struct HiAuditConfig {
std::string logPath;
std::string logName;
uint32_t logSize;
uint32_t fileSize;
uint32_t fileCount;
};
const HiAuditConfig HIAUDIT_CONFIG = {
"/data/storage/el2/log/audit/", "contacts", 2 * 1024, 3 * 1024 * 1024, 10};
constexpr int8_t MILLISECONDS_LENGTH = 3;
constexpr int64_t SEC_TO_MILLISEC = 1000;
constexpr int MAX_TIME_BUFF = 64;
const std::string HIAUDIT_LOG_NAME = HIAUDIT_CONFIG.logPath + HIAUDIT_CONFIG.logName + "_audit.csv";
const std::unordered_map<OperationType, std::string> HiAudit::operationType{
{OperationType::OPERATION_ADD, "ADD"},
{OperationType::OPERATION_MOD, "MOD"},
{OperationType::OPERATION_DELETE, "DEL"}
};
constexpr int32_t CONTACT_INFO_TRUNCATE_BYTE = 15;
constexpr int32_t PHONE_NUMBER_SUFFIX_LENGTH = 6;
HiAudit::HiAudit()
{
Init();
}
HiAudit::~HiAudit()
{
if (writeFd_ >= 0) {
close(writeFd_);
}
}
HiAudit& HiAudit::GetInstance()
{
static HiAudit hiAudit;
return hiAudit;
}
void HiAudit::Init()
{
if (access(HIAUDIT_CONFIG.logPath.c_str(), F_OK) != 0) {
int ret = mkdir(HIAUDIT_CONFIG.logPath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
if (ret != 0) {
HILOG_ERROR("Failed to create directory %{public}s.", HIAUDIT_CONFIG.logPath.c_str());
}
}
std::lock_guard<std::mutex> lock(mutex_);
writeFd_ = open(HIAUDIT_LOG_NAME.c_str(), O_CREAT | O_APPEND | O_RDWR,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
if (writeFd_ < 0) {
HILOG_ERROR("writeFd_ open error errno: %{public}d", errno);
}
struct stat st;
writeLogSize_ = stat(HIAUDIT_LOG_NAME.c_str(), &st) ? 0 : static_cast<uint64_t>(st.st_size);
HILOG_INFO("writeLogSize: %{public}u", writeLogSize_.load());
}
uint64_t HiAudit::GetMilliseconds()
{
auto now = std::chrono::system_clock::now();
auto millisecs = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
return millisecs.count();
}
std::string HiAudit::GetFormattedTimestamp(time_t timeStamp, const std::string& format)
{
auto seconds = timeStamp / SEC_TO_MILLISEC;
char date[MAX_TIME_BUFF] = {0};
struct tm result {};
if (localtime_r(&seconds, &result) != nullptr) {
strftime(date, MAX_TIME_BUFF, format.c_str(), &result);
}
return std::string(date);
}
std::string HiAudit::GetFormattedTimestampEndWithMilli()
{
uint64_t milliSeconds = GetMilliseconds();
std::string formattedTimeStamp = GetFormattedTimestamp(milliSeconds, "%Y%m%d%H%M%S");
std::stringstream ss;
ss << formattedTimeStamp;
milliSeconds = milliSeconds % SEC_TO_MILLISEC;
ss << std::setfill('0') << std::setw(MILLISECONDS_LENGTH) << milliSeconds;
return ss.str();
}
void HiAudit::Write(const AuditLog& auditLog)
{
HILOG_INFO("write");
std::lock_guard<std::mutex> lock(mutex_);
if (writeLogSize_ == 0) {
WriteToFile(auditLog.TitleString() + "\n");
}
std::string writeLog = GetFormattedTimestampEndWithMilli() + ", " +
+ "com.ohos.contactsdataability, false, " + auditLog.ToString();
if (writeLog.length() > HIAUDIT_CONFIG.logSize) {
writeLog = writeLog.substr(0, HIAUDIT_CONFIG.logSize);
}
writeLog = writeLog + "\n";
WriteToFile(writeLog);
}
void HiAudit::WriteLog(const std::string &request, const OperationType &operationType,
const std::vector<OHOS::NativeRdb::ValuesBucket> &contactVector, const size_t &contactsCount)
{
std::string operationTypeStr;
if (auto iter = HiAudit::operationType.find(operationType); iter != HiAudit::operationType.end()) {
operationTypeStr = iter->second;
}
auto contactVectorSize = contactVector.size();
HILOG_INFO("WriteLog, request: %{public}s, operationType: %{public}s, contactVector: %{public}zu",
request.c_str(), operationTypeStr.c_str(), contactVectorSize);
auto preContactCount = contactsCount;
if (operationType == OperationType::OPERATION_ADD) {
preContactCount = Minus(contactsCount, contactVectorSize);
}
for (size_t i = 0; i < contactVectorSize; i++) {
auto contact = contactVector[i];
OHOS::DetailedAuditLog auditLog;
auditLog.operationScenario = request;
auditLog.operationType = operationTypeStr;
auditLog.operationStatus = operationTypeStr + " success";
auditLog.operationCount = 1;
switch (operationType) {
case OperationType::OPERATION_ADD:
auditLog.preContactCount = preContactCount + i;
auditLog.postContactCount = auditLog.preContactCount + auditLog.operationCount;
break;
case OperationType::OPERATION_DELETE:
auditLog.preContactCount = Minus(preContactCount, i);
auditLog.postContactCount = Minus(auditLog.preContactCount, auditLog.operationCount);
break;
default:
auditLog.postContactCount = auditLog.preContactCount;
break;
}
AssembleAuditLog(auditLog, contact);
Write(auditLog);
}
}
size_t HiAudit::Minus(const size_t &a, const size_t &b)
{
if (a >= b) {
return a - b;
}
return 0;
}
void HiAudit::AssembleAuditLog(OHOS::DetailedAuditLog &auditLog, const NativeRdb::ValuesBucket &contact)
{
auditLog.isUserBehavior = true;
auditLog.cause = "USER BEHAVIOR";
NativeRdb::ValueObject valueObj;
std::string displayName;
auto getObjectSuccess = contact.GetObject("display_name", valueObj);
if (getObjectSuccess) {
valueObj.GetString(displayName);
}
std::string phoneNumbers;
getObjectSuccess = contact.GetObject("phoneNumbers", valueObj);
if (getObjectSuccess) {
valueObj.GetString(phoneNumbers);
}
int64_t contactId = -1;
getObjectSuccess = contact.GetObject("contact_id", valueObj);
if (getObjectSuccess) {
valueObj.GetLong(contactId);
}
int64_t accountId = 1;
getObjectSuccess = contact.GetObject("account_id", valueObj);
if (getObjectSuccess) {
valueObj.GetLong(accountId);
}
auditLog.contactId = contactId;
auditLog.affectedAccount = std::to_string(accountId);
auditLog.key1 = GenerateKey1(displayName);
auditLog.key2 = GenerateKey2(displayName, phoneNumbers);
}
void HiAudit::GetWriteFilePath()
{
if (writeLogSize_ < HIAUDIT_CONFIG.fileSize) {
return;
}
close(writeFd_);
ZipAuditLog();
CleanOldAuditFile();
writeFd_ = open(HIAUDIT_LOG_NAME.c_str(), O_CREAT | O_TRUNC | O_RDWR,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
if (writeFd_ < 0) {
HILOG_ERROR("fd open error errno: %{public}d", errno);
}
writeLogSize_ = 0;
}
void HiAudit::CleanOldAuditFile()
{
DIR* dir = opendir(HIAUDIT_CONFIG.logPath.c_str());
if (dir == nullptr) {
HILOG_ERROR("failed open dir, errno: %{public}d.", errno);
return;
}
uint32_t zipFileSize = 0;
std::string oldestAuditFile;
while (true) {
struct dirent* ptr = readdir(dir);
if (ptr == nullptr) {
break;
}
if (std::string(ptr->d_name).find(HIAUDIT_CONFIG.logName) != std::string::npos &&
std::string(ptr->d_name).find("zip") != std::string::npos) {
zipFileSize = zipFileSize + 1;
if (oldestAuditFile.empty()) {
oldestAuditFile = HIAUDIT_CONFIG.logPath + std::string(ptr->d_name);
continue;
}
struct stat st;
stat((HIAUDIT_CONFIG.logPath + std::string(ptr->d_name)).c_str(), &st);
struct stat oldestSt;
stat(oldestAuditFile.c_str(), &oldestSt);
if (st.st_mtime < oldestSt.st_mtime) {
oldestAuditFile = HIAUDIT_CONFIG.logPath + std::string(ptr->d_name);
}
}
}
closedir(dir);
if (zipFileSize > HIAUDIT_CONFIG.fileCount) {
remove(oldestAuditFile.c_str());
}
}
void HiAudit::WriteToFile(const std::string& content)
{
GetWriteFilePath();
if (writeFd_ < 0) {
HILOG_ERROR("fd invalid.");
return;
}
write(writeFd_, content.c_str(), content.length());
writeLogSize_ = writeLogSize_ + content.length();
}
void HiAudit::ZipAuditLog()
{
std::string zipFileName = HIAUDIT_CONFIG.logPath + HIAUDIT_CONFIG.logName + "_audit_" +
GetFormattedTimestamp(GetMilliseconds(), "%Y%m%d%H%M%S");
std::rename(HIAUDIT_LOG_NAME.c_str(), (zipFileName + ".csv").c_str());
zipFile compressZip = HiviewDFX::ZipUtil::CreateZipFile(zipFileName + ".zip");
if (compressZip == nullptr) {
HILOG_WARN("open zip file failed.");
return;
}
if (HiviewDFX::ZipUtil::AddFileInZip(compressZip, zipFileName + ".csv", HiviewDFX::KEEP_NONE_PARENT_PATH) == 0) {
remove((zipFileName + ".csv").c_str());
}
HiviewDFX::ZipUtil::CloseZipFile(compressZip);
}
std::string HiAudit::GenerateKey(const std::string &data)
{
if (data.empty()) {
return "";
}
auto sha256 = HiAudit::SHA256(data);
auto result = Base64Encode(sha256.substr(0, CONTACT_INFO_TRUNCATE_BYTE));
return result;
}
std::string HiAudit::GenerateKey1(const std::string &displayName)
{
if (displayName.empty()) {
HILOG_WARN("GenerateKey1, displayName is null");
return "";
}
try {
std::string key1 = GenerateKey(displayName);
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::wstring wideDisplayName = converter.from_bytes(displayName);
std::wstring wideKey1Prefix(wideDisplayName.size(), '*');
if (wideDisplayName.size() > 1) {
wideKey1Prefix[0] = wideDisplayName[0];
}
std::stringstream ss;
ss << converter.to_bytes(wideKey1Prefix) << "|" << key1;
return ss.str();
} catch (std::exception &e) {
HILOG_ERROR("GenerateKey1, convert string error: %{public}s", e.what());
}
return "";
}
std::string HiAudit::GenerateKey2(const std::string &displayName, const std::string &phoneNums)
{
if (displayName.empty() && phoneNums.empty()) {
HILOG_WARN("GenerateKey2, displayName and phone is null");
return "";
}
std::string data;
size_t index = 0;
size_t pos = phoneNums.find(',', index);
while (pos != std::string::npos) {
auto phoneNum = phoneNums.substr(index, pos - index);
index = pos + 1;
auto key = ProcessPhoneNumber(displayName, phoneNum);
if (!data.empty()) {
data.append("|");
}
data.append(key);
pos = phoneNums.find(',', index);
}
std::string phoneNum;
if (index < phoneNums.length()) {
phoneNum = phoneNums.substr(index);
}
auto key = ProcessPhoneNumber(displayName, phoneNum);
if (!data.empty()) {
data.append("|");
}
data.append(key);
return data;
}
std::string HiAudit::ProcessPhoneNumber(const std::string &displayName, const std::string &phoneNum)
{
auto phoneNumSize = phoneNum.size();
auto processedPhoneNum = phoneNum;
if (phoneNumSize > PHONE_NUMBER_SUFFIX_LENGTH) {
auto suffixLen = phoneNumSize - PHONE_NUMBER_SUFFIX_LENGTH;
processedPhoneNum = phoneNum.substr(suffixLen, PHONE_NUMBER_SUFFIX_LENGTH);
}
std::string key2 = GenerateKey(displayName + processedPhoneNum);
std::string key2Prefix;
key2Prefix.resize(phoneNumSize);
if (phoneNumSize < 3) {
key2Prefix.assign(phoneNumSize, '*');
} else if (phoneNumSize <= 7) {
thread_local std::regex pattern(R"(^(.)(\d{1,5})(.))");
std::string mask(phoneNumSize - 2, '*');
std::string replacement = "$1" + mask + "$3";
key2Prefix = std::regex_replace(phoneNum, pattern, replacement);
} else {
thread_local std::regex pattern(R"(^(.{3})(\d{1,})(.{4}))");
std::string mask(phoneNumSize - 7, '*');
std::string replacement = "$1" + mask + "$3";
key2Prefix = std::regex_replace(phoneNum, pattern, replacement);
}
std::stringstream ss;
ss << key2Prefix << "|" << key2;
return ss.str();
}
std::string HiAudit::SHA256(const std::string &data)
{
uint8_t hash[SHA256_DIGEST_LENGTH] = {0};
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, data.c_str(), data.size());
SHA256_Final(hash, &sha256);
std::stringstream ss;
const int len = 2;
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
ss << std::hex << std::setw(len) << std::setfill('0') << static_cast<int32_t>(hash[i]);
}
return ss.str();
}
std::string HiAudit::Base64Encode(const std::string &data)
{
size_t size = data.size();
size_t base64_len = (size + 2) / 3 * 4;
if (base64_len == 0) {
return "";
}
std::string ret;
ret.resize(base64_len);
EVP_EncodeBlock(reinterpret_cast<uint8_t *>(ret.data()), reinterpret_cast<const uint8_t *>(data.c_str()), size);
return ret;
}
}