28a59165创建于 2025年7月23日历史提交
/*

 * Copyright (c) 2025 Huawei Device Co., Ltd.

 * 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 "event_replayer.h"



#include <charconv>

#include <chrono>

#include <fstream>

#include <thread>

#include <unordered_map>

#include <vector>



#include "common.h"

#include "device_manager.h"



namespace OHOS {

namespace MMI {

namespace {

constexpr int32_t MICROSECONDS_PER_SECOND = 1000000;

const char* DEVICES_PREFIX = "DEVICES:";

const char* EVENTS_BEGIN = "EVENTS_BEGIN";

const char* EVENTS_END = "EVENTS_END";

constexpr char FIELD_COMMA = ',';

constexpr char COMMENT_CHAR = '#';

constexpr char BRACKET_START = '[';

constexpr char BRACKET_END = ']';

}



EventReplayer::EventReplayer(const std::string& inputPath, const std::map<uint16_t, uint16_t>& deviceMapping)

    : inputPath_(inputPath), lastSec_(0), lastUsec_(0), firstEvent_(true)

{

    for (const auto &[sourceId, targetId] : deviceMapping) {

        deviceMapping_[sourceId] = "/dev/input/event" + std::to_string(targetId);

    }

    DeviceManager deviceManager;

    auto devices = deviceManager.DiscoverDevices();

    for (const InputDevice& device : devices) {

        hashToDevicePath_[device.GetHash()] = device.GetPath();

    }

    if (devices.size() != hashToDevicePath_.size()) {

        PrintWarning("Device hashVal is not unique!");

    }

}



bool EventReplayer::SeekToDevicesSection(std::ifstream& inputFile)

{

    if (!inputFile.good()) {

        PrintError("File stream is not in good state");

        return false;

    }

    inputFile.seekg(0, std::ios::end);

    std::streampos fileSize = inputFile.tellg();

    constexpr std::streamoff SEARCH_OFFSET = 2048;

    if (fileSize <= SEARCH_OFFSET) {

        inputFile.seekg(0, std::ios::beg);

    } else {

        inputFile.seekg(-SEARCH_OFFSET, std::ios::end);

        std::string discardLine;

        std::getline(inputFile, discardLine);

        if (inputFile.eof()) {

            inputFile.clear();

            inputFile.seekg(0, std::ios::beg);

        }

    }

    std::string line;

    while (std::getline(inputFile, line)) {

        if (line.find(DEVICES_PREFIX) == 0) {

            inputFile.seekg(-static_cast<std::streamoff>(line.length() + 1), std::ios::cur);

            if (inputFile.peek() == '\r') {

                inputFile.seekg(-1, std::ios::cur);

            }

            return true;

        }

    }

    PrintWarning("DEVICES section not found in file");

    return false;

}



bool EventReplayer::SeekToEventsSection(std::ifstream& inputFile)

{

    if (!inputFile.good()) {

        PrintError("File stream is not in good state");

        return false;

    }

    inputFile.seekg(0, std::ios::beg);

    std::string line;

    while (std::getline(inputFile, line)) {

        if (line == EVENTS_BEGIN) {

            return true;

        }

    }

    return false;

}



bool EventReplayer::Replay()

{

    // ensure the out coming path safe

    char resolvedPath[PATH_MAX] = {};

    if (realpath(inputPath_.c_str(), resolvedPath) == nullptr) {

        PrintError("Realpath failed. path:%{private}s", inputPath_.c_str());

        return false;

    }

    std::ifstream inputFile(resolvedPath);

    if (!inputFile.is_open()) {

        PrintError("Failed to open file, error:%{private}s", strerror(errno));

        return false;

    }

    if (!SeekToDevicesSection(inputFile)) {

        PrintError("seek to DEVICES_PREFIX tag error");

        return false;

    }

    std::map<uint32_t, std::unique_ptr<InputDevice>> outputDevices;

    if (!InitializeOutputDevices(inputFile, outputDevices)) {

        return false;

    }

    if (outputDevices.empty()) {

        PrintError("No output devices available for replay");

        return false;

    }

    PrintInfo("Starting replay...");

    firstEvent_ = true;

    bool result = ReplayEvents(inputFile, outputDevices);

    PrintInfo(result ? "Replay completed" : "Replay interrupted");

    return true;

}



bool EventReplayer::ProcessDeviceLines(std::ifstream& inputFile,

    std::map<uint32_t, std::unique_ptr<InputDevice>>& outputDevices, uint32_t deviceCount)

{

    std::string line;

    for (uint32_t i = 0; i < deviceCount; i++) {

        line.clear();

        if (!std::getline(inputFile, line)) {

            PrintWarning("Reached end of file after reading %u of %u devices", i, deviceCount);

            break;

        }

        if (line.empty() || line[0] == COMMENT_CHAR) {

            --i;

            continue;

        }

        auto device = std::make_unique<InputDevice>();

        if (!device) {

            PrintError("Failed to allocate device object");

            return false;

        }

        if (!device->InitFromTextLine(line)) {

            PrintWarning("Failed to parse device line: %s", line.c_str());

            continue;

        }

        uint16_t deviceId = static_cast<uint16_t>(device->GetId());

        auto mappingIt = deviceMapping_.find(deviceId);

        if (mappingIt != deviceMapping_.end()) {

            PrintInfo("Mapping device %u to %s", deviceId, mappingIt->second.c_str());

            device->SetPath(mappingIt->second);

        }

        if (deviceMapping_.empty()) {

            std::string path = device->GetPath();

            std::string hash = device->GetHash();

            auto hashIt = hashToDevicePath_.find(hash);

            if (hashIt != hashToDevicePath_.end() && hashToDevicePath_[hash] != path) {

                PrintInfo("Validate and correct path using hash verification");

                device->SetPath(hashToDevicePath_[hash]);

            }

        }

        if (device->OpenForWriting()) {

            PrintInfo("Using device %u: %s", device->GetId(), device->GetName().c_str());

            outputDevices[device->GetId()] = std::move(device);

        } else {

            PrintWarning("Failed to open device for replay: %s", device->GetPath().c_str());

        }

    }

    return true;

}



bool EventReplayer::InitializeOutputDevices(std::ifstream& inputFile,

    std::map<uint32_t, std::unique_ptr<InputDevice>>& outputDevices)

{

    outputDevices.clear();

    std::string line;

    if (!std::getline(inputFile, line)) {

        PrintError("Failed to read device count line");

        return false;

    }

    std::string countStr = line;

    if (!RemovePrefix(countStr, DEVICES_PREFIX)) {

        PrintError("Invalid device count line format");

        return false;

    }

    TrimString(countStr);

    uint32_t deviceCount = 0;

    auto result = std::from_chars(countStr.data(), countStr.data() + countStr.size(), deviceCount);

    if (result.ec != std::errc()) {

        PrintError("Failed to parse device count: %s", countStr.c_str());

        return false;

    }

    PrintInfo("Found %u devices", deviceCount);

    return ProcessDeviceLines(inputFile, outputDevices, deviceCount);

}



void EventReplayer::ApplyEventDelay(const struct input_event& currentEvent)

{

    if (!firstEvent_) {

        long nowSec = currentEvent.input_event_sec;

        long nowUsec = currentEvent.input_event_usec;

        long diffSec = nowSec - lastSec_;

        long diffUsec = nowUsec - lastUsec_;

        if (diffUsec < 0) {

            diffSec--;

            diffUsec += MICROSECONDS_PER_SECOND;

        }

        if (diffSec > 0 || diffUsec > 0) {

            std::this_thread::sleep_for(

                std::chrono::seconds(diffSec) + std::chrono::microseconds(diffUsec));

        }

    }

    firstEvent_ = false;

    lastSec_ = currentEvent.input_event_sec;

    lastUsec_ = currentEvent.input_event_usec;

}



bool EventReplayer::ReplayEvents(std::ifstream& inputFile,

    const std::map<uint32_t, std::unique_ptr<InputDevice>>& outputDevices)

{

    if (!SeekToEventsSection(inputFile)) {

        PrintError("Failed to locate events section");

        return false;

    }

    deviceEventBuffers_.clear();

    std::string line;

    while (std::getline(inputFile, line)) {

        if (!inputFile || inputFile.eof()) {

            PrintWarning("Error reading event record");

            return false;

        }

        if (line.empty()) {

            continue;

        }

        if (line == EVENTS_END) {

            PrintDebug("Reached end of events section");

            break;

        }

        uint32_t deviceId;

        input_event event;

        if (!ParseInputLine(line, deviceId, event)) {

            PrintError("Failed to parse event line: %s", line.c_str());

            return false;

        }

        auto deviceIt = outputDevices.find(deviceId);

        if (deviceIt == outputDevices.end()) {

            continue;

        }

        ApplyEventDelay(event);

        auto& currentDeviceBuffer = deviceEventBuffers_[deviceId];

        currentDeviceBuffer.push_back(event);

        if (event.type == EV_SYN && event.code == SYN_REPORT) {

            if (!deviceIt->second->WriteEvents(currentDeviceBuffer)) {

                PrintError("Failed to write events for device %u", deviceId);

                return false;

            }

            currentDeviceBuffer.clear();

        }

        if (g_shutdown.load()) {

            return false;

        }

        line.clear();

    }

    return true;

}



template<typename T>

static bool ParseField(const char*& ptr, const char* endPtr, T& value)

{

    while (ptr < endPtr && (*ptr == ' ' || *ptr == '\t')) {

        ptr++;

    }

    auto result = std::from_chars(ptr, endPtr, value);

    if (result.ec != std::errc()) {

        return false;

    }

    ptr = result.ptr;

    if (ptr >= endPtr) {

        return false;

    }

    if (*ptr++ != FIELD_COMMA) {

        return false;

    }

    return true;

}



bool EventReplayer::ParseInputLine(const std::string& line, uint32_t& deviceId, struct input_event& evt)

{

    size_t commentPos = line.find(COMMENT_CHAR);

    std::string content = (commentPos != std::string::npos) ? line.substr(0, commentPos) : line;

    size_t startPos = content.find(BRACKET_START);

    size_t endPos = content.find(BRACKET_END);

    if (startPos == std::string::npos || endPos == std::string::npos || startPos >= endPos) {

        return false;

    }

    std::string data = content.substr(startPos + 1, endPos - startPos - 1);

    const char* ptr = data.c_str();

    const char* endPtr = ptr + data.length();

    if (!ParseField(ptr, endPtr, deviceId)) {

        return false;

    }

    uint16_t type;

    if (!ParseField(ptr, endPtr, type)) {

        return false;

    }

    evt.type = type;

    uint16_t code;

    if (!ParseField(ptr, endPtr, code)) {

        return false;

    }

    evt.code = code;

    int32_t value;

    if (!ParseField(ptr, endPtr, value)) {

        return false;

    }

    evt.value = value;

    long s;

    if (!ParseField(ptr, endPtr, s)) {

        return false;

    }

    evt.input_event_sec = s;

    long us;

    while (ptr < endPtr && (*ptr == ' ' || *ptr == '\t')) {

        ptr++;

    }

    auto result = std::from_chars(ptr, endPtr, us);

    if (result.ec != std::errc()) {

        return false;

    }

    evt.input_event_usec = us;

    return true;

}

} // namespace MMI

} // namespace OHOS