Files
libicsneo/device/extensions/bootloader/communication/application.cpp
T

408 lines
15 KiB
C++

#include "icsneo/device/extensions/bootloader/communication/application.h"
#include "icsneo/device/extensions/bootloader/parser/fwrepo.h"
#include "icsneo/device/extensions/bootloader/platform/logging.h"
#include "icsneo/device/extensions/bootloader/api.h"
#include "icsneo/communication/message/extendedresponsemessage.h"
#include "icsneo/communication/message/main51message.h"
#include <algorithm>
#include <condition_variable>
#include <cstring>
#include <limits>
#include <mutex>
#include <optional>
#include <thread>
#include <icsbl/icsbl.h>
namespace icsneo {
namespace Bootloader {
bool Application::enterApplication() {
// Always reboot
Log::Info(__func__, "sending reboot command");
bool ok = device.com->sendCommand(icsneo::ExtendedCommand::Reboot);
if(!ok) {
Log::Error(__func__, "reboot command failed");
}
return ok;
}
bool Application::finalize() {
Log::Debug(__func__, "sending finalize command");
bool ok = sendCommand(Update::Finalize, [](double) {});
if(!ok) {
Log::Error(__func__, "finalize command failed");
}
return ok;
}
bool Application::flash(const FWRepo& fw, const icsneo::Bootloader::StatusHandler& handler) {
Log::Info(__func__, "starting flash for " + targetChip.getName());
const auto& srcVer = fw.getVersion();
std::vector<uint8_t> bin;
if(!fw.getFW(targetChip.getIEFName(), bin)) {
Log::Error(__func__, "failed to get firmware for " + targetChip.getIEFName());
return false;
}
IEF ief;
if(!ief.load(std::move(bin))) {
Log::Error(__func__, "failed to load IEF for " + targetChip.getName());
return false;
}
auto currentString = "Preparing to update " + targetChip.getName() + "...";
handler(StatusType::Progress, currentString, std::nullopt);
// The supplied IEF is not for this product
if(ief.getVersion().prodId != static_cast<uint8_t>(device.getProductID())) {
Log::Error(__func__, "IEF product ID mismatch for " + targetChip.getName());
return false;
}
// The supplied IEF is for this product, but not for this chip
if(ief.getVersion().chipId != static_cast<uint8_t>(targetChip.getID())) {
Log::Error(__func__, "IEF chip ID mismatch for " + targetChip.getName());
return false;
}
if(doAuthChecks) {
if(!authenticate()) {
Log::Error(__func__, "authentication failed for " + targetChip.getName());
return false;
}
// Tell the application software what version of Vehicle Spy 3 this firmware is from.
// We check to make sure it approves, but also, the device will not accept new firmware
// if we have not sent this.
// Sometimes new hardware revisions can not accept firmware which is too old.
if(!sendCommand(Update::CheckVersion, [](double) {}, 0, {srcVer.major, srcVer.minor, srcVer.release, srcVer.build})) {
Log::Error(__func__, "version check failed for " + targetChip.getName());
return false;
}
}
bool ok = sendSoftware(ief.getSectors(), ief.getBlockSize(), handler);
if(!ok) {
Log::Error(__func__, "sendSoftware failed for " + targetChip.getName());
}
return ok;
}
bool Application::authenticate() {
icsbl_seed_v1 seed = {};
if(!icsbl_generate_seed_v1(device.getSerialNumber(), &seed)) {
Log::Error(__func__, "libicsbl could not generate an authentication challenge");
return false;
}
// Send the challenge (seed.a), the device replies with seed.b and one trailing byte
std::vector<uint8_t> cmdArgs(sizeof(seed.a));
writeLE32(cmdArgs.data(), seed.a[0]);
writeLE32(cmdArgs.data() + 4, seed.a[1]);
auto filter = std::make_shared<icsneo::MessageFilter>(icsneo::Message::Type::Main51);
auto msg = device.com->waitForMessageSync([&] {
return device.com->sendCommand(icsneo::Command::ValidateDevice, cmdArgs);
}, filter, std::chrono::milliseconds(200));
if(!msg) {
Log::Error(__func__, "No response from the device");
return false;
}
auto main51Msg = std::static_pointer_cast<icsneo::Main51Message>(msg);
if(main51Msg->data.size() != sizeof(seed.b) + 1) {
Log::Error(__func__, "Unexpected authentication reply size from the device");
return false;
}
const bool authenticated = readLE32(main51Msg->data.data()) == seed.b[0] && readLE32(main51Msg->data.data() + 4) == seed.b[1];
if(!authenticated) {
Log::Error(__func__, "The authentication reply does not match, the device did not authenticate");
}
return authenticated;
}
bool Application::validateFirmware(const DeviceAppVersion& chipSrcVer) {
return sendCommand(Update::CheckComponentVersion, [](double) {}, 0, {chipSrcVer.major, chipSrcVer.minor});
}
bool Application::sendSoftware(const std::vector<uint8_t>& bin, const icsneo::Bootloader::StatusHandler& handler) {
Log::Debug(__func__, "starting for " + targetChip.getName() + " (" + std::to_string(bin.size()) + " bytes)");
auto currentString = "Preparing to update " + targetChip.getName() + "...";
handler(StatusType::Progress, currentString, std::nullopt);
if(bin.empty() || bin.size() > std::numeric_limits<uint32_t>::max()) {
Log::Error(__func__, "invalid firmware size for " + targetChip.getName());
return false;
}
const uint32_t totalSize = static_cast<uint32_t>(bin.size());
Log::Debug(__func__, "erasing " + targetChip.getName());
if(!sendCommand(Update::Erase, 0, totalSize, [&](double progress) {
handler(StatusType::Progress, currentString, progress);
}, std::chrono::milliseconds(45000))) {
Log::Error(__func__, "erase failed for " + targetChip.getName());
return false;
}
currentString = "Updating " + targetChip.getName() + "...";
handler(StatusType::Progress, currentString, std::nullopt);
const uint32_t stepSize = std::min<uint32_t>(totalSize, 2048);
uint32_t bytesTransferred = 0;
while(bytesTransferred < totalSize) {
const uint32_t bytesToSend = std::min(stepSize, totalSize - bytesTransferred);
if(!sendCommand(Update::Write, [](double) {}, bytesTransferred, { bin.begin() + bytesTransferred, bin.begin() + bytesTransferred + bytesToSend})) {
Log::Error(__func__, "write failed at offset " + std::to_string(bytesTransferred) + " for " + targetChip.getName());
return false;
}
bytesTransferred += bytesToSend;
handler(StatusType::Progress, currentString, double(bytesTransferred) / totalSize);
}
Log::Debug(__func__, "write complete, validating " + targetChip.getName());
if(!validateComponent()) {
Log::Error(__func__, "validation failed for " + targetChip.getName());
return false;
}
Log::Debug(__func__, "completed successfully for " + targetChip.getName());
return true;
}
bool Application::sendSoftware(const std::vector<IEF::Sector>& sectorPairs, uint32_t blockSize, const icsneo::Bootloader::StatusHandler& handler) {
if(sectorPairs.empty()) {
Log::Error(__func__, "no sectors to flash for " + targetChip.getName());
return false;
}
Log::Debug(__func__, "starting for " + targetChip.getName() + " (" + std::to_string(sectorPairs.size()) + " sectors)");
auto currentString = "Preparing to update " + targetChip.getName() + "...";
handler(StatusType::Progress, currentString, std::nullopt);
uint64_t totalSize = 0;
for(const auto& sector : sectorPairs) {
totalSize += sector.second.size();
}
if(totalSize > std::numeric_limits<uint32_t>::max()) {
Log::Error(__func__, "total size too large for " + targetChip.getName());
return false;
}
if(totalSize == 0) {
Log::Error(__func__, "total size is 0 for " + targetChip.getName());
return false;
}
Log::Debug(__func__, "erasing " + targetChip.getName() + " (" + std::to_string(totalSize) + " bytes)");
if(!sendCommand(Update::Erase, 0, static_cast<uint32_t>(totalSize), [&](double progress) {
handler(StatusType::Progress, currentString, progress);
}, std::chrono::milliseconds(45000))) {
Log::Error(__func__, "erase failed for " + targetChip.getName());
return false;
}
if(isSFPChip(targetChip)) {
// SFPs are not ready to write until 4 seconds after finishing erasing (for some reason)
Log::Trace(__func__, "SFP chip detected, waiting 4000ms after erase");
std::this_thread::sleep_for(std::chrono::milliseconds(4000));
}
currentString = "Updating " + targetChip.getName() + "...";
handler(StatusType::Progress, currentString, std::nullopt);
uint32_t bytesTransferred = 0;
for(const auto& pair : sectorPairs) {
const auto& rawData = pair.second;
const uint32_t offset = pair.first * blockSize;
std::vector<uint8_t> processedData(rawData.begin(), rawData.end());
if(encrypted) {
if(rawData.size() % 8 != 0) {
Log::Error(__func__, "raw data size not aligned");
return false;
}
icsbl_encrypt_v1(device.getSerialNumber(), rawData.data(), rawData.size(), processedData.data());
}
if(!sendCommand(Update::Write, [](double) {}, offset, processedData)) {
Log::Error(__func__, "write failed at offset " + std::to_string(offset) + " for " + targetChip.getName());
return false;
}
bytesTransferred += static_cast<uint32_t>(rawData.size());
handler(StatusType::Progress, currentString, double(bytesTransferred) / double(totalSize));
}
Log::Debug(__func__, "write complete, validating " + targetChip.getName());
bool ok = validateComponent();
if(!ok) {
Log::Error(__func__, "validation failed for " + targetChip.getName());
}
else {
Log::Debug(__func__, "completed successfully for " + targetChip.getName());
}
return ok;
}
bool Application::validateComponent() {
Log::Debug(__func__, "validating " + targetChip.getName());
bool ok = sendCommand(Update::ValidateComponent, [](double) {}, 0, {}, std::chrono::milliseconds(45000));
if(!ok) {
Log::Error(__func__, "validation failed for " + targetChip.getName());
}
return ok;
}
/**
* Send one SoftwareUpdate sub-command and wait for it to complete.
*
* The device answers the request with OK when it is already done, or OperationPending when it will
* finish later. For a pending command we poll Update::GetProgress (except for Write and
* ValidateComponent, which do not report progress) until the device sends a final OK or the timeout
* passes. Progress replies are OperationPending with a SoftwareUpdateCommandHeader whose
* commandSizeOrProgress holds the progress so far, or a GenericReturn with no progress.
*/
bool Application::sendCommand(Update command, uint32_t offset, uint32_t length, const CommandStatusHandler& handler, std::chrono::milliseconds timeout, std::vector<uint8_t> args) {
bool finishedResponse = false;
std::condition_variable finishedResponseCV;
std::mutex finishedResponseMutex;
std::optional<uint32_t> progress;
std::condition_variable progressCV;
std::mutex progressMutex;
bool firstResponse = true;
// Register the callback before sending the command, so that if it goes from pending to finished quickly we will still catch it
const auto callback = std::make_shared<icsneo::MessageCallback>(std::make_shared<icsneo::MessageFilter>(icsneo::Message::Type::ExtendedResponse), [&](std::shared_ptr<icsneo::Message> msg) {
if(firstResponse) {
firstResponse = false;
return true; // let waitForMessageSync handle the first message
}
auto extMsg = std::static_pointer_cast<ExtendedResponseMessage>(msg);
if(extMsg->command != ExtendedCommand::SoftwareUpdate) {
Log::Error(__func__, "Got an extended response for a command other than SoftwareUpdate");
return false;
}
if(extMsg->response == ExtendedResponse::OK) {
{
std::scoped_lock lk(finishedResponseMutex);
finishedResponse = true;
}
finishedResponseCV.notify_one();
} else if(extMsg->response == ExtendedResponse::OperationPending) {
if(extMsg->data.size() < sizeof(ExtendedResponseMessage::ResponseHeader)) {
Log::Error(__func__, "The pending response is too short to contain a header");
return false;
}
ExtendedResponseMessage::ResponseHeader respHeader;
std::memcpy(&respHeader, extMsg->data.data(), sizeof(respHeader));
if(respHeader.command == ExtendedCommand::SoftwareUpdate) {
if(respHeader.length != sizeof(SoftwareUpdateCommandHeader) ||
extMsg->data.size() < sizeof(ExtendedResponseMessage::ResponseHeader) + sizeof(SoftwareUpdateCommandHeader)) {
Log::Error(__func__, "The progress response does not contain a SoftwareUpdate header");
return false;
}
SoftwareUpdateCommandHeader resp;
std::memcpy(&resp, extMsg->data.data() + sizeof(ExtendedResponseMessage::ResponseHeader), sizeof(resp));
{
std::scoped_lock lk(progressMutex);
progress.emplace(resp.commandSizeOrProgress);
}
progressCV.notify_one();
} else if(respHeader.command == ExtendedCommand::GenericReturn) {
// No progress information, the final OK comes later
{
std::scoped_lock lk(progressMutex);
progress.emplace(0);
}
progressCV.notify_one();
} else {
Log::Error(__func__, "The pending response is for an unexpected command");
return false;
}
} else {
Log::Error(__func__, "The device reported an error for the SoftwareUpdate command");
return false;
}
return true;
});
const auto finishedCallbackHandle = device.com->addMessageCallback(callback);
Lifetime statusCallbackCleanup([&] {
device.com->removeMessageCallback(finishedCallbackHandle);
});
SoftwareUpdateCommandHeader header = {};
header.componentIdentifier = static_cast<uint32_t>(targetChip.getID());
header.commandType = command;
header.offset = offset;
header.commandSizeOrProgress = length;
header.expansionSlot = targetChip.expansionSlot;
const auto headerBytes = reinterpret_cast<const uint8_t*>(&header);
args.insert(args.begin(), headerBytes, headerBytes + sizeof(header));
{
auto msg = device.com->waitForMessageSync([&] {
return device.com->sendCommand(ExtendedCommand::SoftwareUpdate, args);
}, std::make_shared<icsneo::MessageFilter>(icsneo::Message::Type::ExtendedResponse), timeout);
if(!msg) {
Log::Error(__func__, "No response to the SoftwareUpdate command");
return false;
}
auto extMsg = std::static_pointer_cast<ExtendedResponseMessage>(msg);
if(extMsg->command != ExtendedCommand::SoftwareUpdate) {
Log::Error(__func__, "Got an extended response for a command other than SoftwareUpdate");
return false;
}
if(extMsg->response == ExtendedResponse::OK) {
return true;
}
if(extMsg->response != ExtendedResponse::OperationPending) {
Log::Error(__func__, "The device rejected the SoftwareUpdate command");
return false;
}
}
SoftwareUpdateCommandHeader progressHeader = {};
progressHeader.componentIdentifier = static_cast<uint32_t>(targetChip.getID());
progressHeader.commandType = Update::GetProgress;
progressHeader.expansionSlot = targetChip.expansionSlot;
const auto progressHeaderBytes = reinterpret_cast<const uint8_t*>(&progressHeader);
const std::vector<uint8_t> progressArgs(progressHeaderBytes, progressHeaderBytes + sizeof(progressHeader));
const auto deadline = std::chrono::steady_clock::now() + timeout;
// Write and ValidateComponent do not report progress, just wait for the final OK
if(command != Update::Write && command != Update::ValidateComponent) {
while(std::chrono::steady_clock::now() < deadline) {
{
std::unique_lock lk(progressMutex);
progress.reset();
if(!device.com->sendCommand(ExtendedCommand::SoftwareUpdate, progressArgs)) {
Log::Error(__func__, "Failed to send progress request to the device");
return false;
}
if(!progressCV.wait_for(lk, std::chrono::milliseconds(5000), [&]{ return progress.has_value(); })) {
Log::Error(__func__, "No progress response from device");
return false;
}
handler(*progress / double(length));
}
{
std::scoped_lock lk(finishedResponseMutex);
if(finishedResponse) {
break;
}
}
}
}
std::unique_lock lk(finishedResponseMutex);
return finishedResponseCV.wait_until(lk, deadline, [&]{ return finishedResponse; });
}
} // namespace Bootloader
} // namespace icsneo