// C++ 17
+
+#include "PackageItem.h"
+#include "SignRsa.h"
+
+/*!
+ * \brief Utility container for UI
+ */
+struct PackageInfo {
+ std::string fullPath; ///< Full path and name.
+ std::string fileName; ///< Just the file name.
+ std::string packageName; ///< The release name.
+ std::string packageVersionInfo; ///< Release version information.
+ std::filesystem::file_time_type lastWritten; ///< Time it was last written.
+ std::string reserved; ///< Reserved for UI.
+
+ /*!
+ * \brief Equals operator.
+ *
+ * \return True if all the strings are equal.
+ */
+ bool operator == (const PackageInfo& rhs) const
+ {
+ return (
+ (fullPath == rhs.fullPath) &&
+ (fileName == rhs.fileName) &&
+ (packageName == rhs.packageName) &&
+ (packageVersionInfo == rhs.packageVersionInfo) &&
+ (lastWritten == rhs.lastWritten) &&
+ (reserved == rhs.reserved));
+ }
+
+ /*!
+ * \brief Not equals operator.
+ *
+ * \return True if any of the strings are not equal.
+ */
+ bool operator !=(const PackageInfo& rhs) const
+ {
+ return !(*this == rhs);
+ }
+
+ /*! Default constructor. */
+ PackageInfo() = default;
+
+ /*!
+ * \brief Copy constructor.
+ * \param rhs Right hand side.
+ */
+ PackageInfo(const PackageInfo& rhs) {
+ fullPath = rhs.fullPath;
+ fileName = rhs.fileName;
+ packageName = rhs.packageName;
+ packageVersionInfo = rhs.packageVersionInfo;
+ lastWritten = rhs.lastWritten;
+ reserved = rhs.reserved;
+ }
+
+ /*!
+ * \brief Full constructor.
+ * \param fullPathStr Full path string.
+ * \param fileNameStr Just the name string.
+ * \param packageNameStr The package description string.
+ * \param packageVersionInfoStr The version information string.
+ * \param lastWrittenTime Last time written file value.
+ * \param reservedStr Reserved for UI use.
+ */
+ PackageInfo(
+ const std::string& fullPathStr,
+ const std::string& fileNameStr,
+ const std::string& packageNameStr,
+ const std::string& packageVersionInfoStr,
+ const std::filesystem::file_time_type& lastWrittenTime,
+ const std::string& reservedStr) {
+
+ fullPath = fullPathStr;
+ fileName = fileNameStr;
+ packageName = packageNameStr;
+ packageVersionInfo = packageVersionInfoStr;
+ lastWritten = lastWrittenTime;
+ reserved = reservedStr;
+ }
+
+ /*!
+ * \brief Assignment operator.
+ * \param rhs Right hand side.
+ * \return Reference to this.
+ */
+ PackageInfo& operator= (const PackageInfo& rhs)
+ {
+ fullPath = rhs.fullPath;
+ fileName = rhs.fileName;
+ packageName = rhs.packageName;
+ packageVersionInfo = rhs.packageVersionInfo;
+ lastWritten = rhs.lastWritten;
+ reserved = rhs.reserved;
+ return *this;
+ }
+};
+
+/*!
+ * \brief Class to manage a package file.
+ *
+ *
+ * The key things are:
+ * get_available(...) Given a directory get a list of updates
+ * that pass validation.
+ *
+ * start() Given file name (optional key file) do a partial
+ * or complete update.
+ *
+ * UI workflow would typically be:
+ * 1. set_public(key in base 64 as a string)
+ * 2. get_available(directory) -> Show user their options.
+ * 2b. Use parse to scan a specific file as an option.
+ * 2c. Might use get_available against more than one directory
+ * Old existing + USB.
+ * 3. start_hasKey(filename)
+ * 3a. progress() array of structs of progress.
+ * 3b. abort() If user decides to stop.
+ * 4. If it completes successfully, on the next
+ * boot/powercycle U-Boot will update the UI processor (SOM).
+ *
+ * The rest of the calls are mainly software development / FPGA
+ * development / manufacturing support, the update software
+ * can be run piecewise in different ways.
+ *
+ * The proof of concept UI allows a user to select the encryption
+ * file, individually validate a file, update pieces of the system,
+ * etc. This allows firmware developers to test just the DG or HD
+ * update mechanisms without fully updating the UI or slowing things
+ * down. It can also flash a test image of a DG, HD or one of the
+ * FPGAs.
+ *
+ * Files are always:
+ * Signature (base64 text)
+ * XML (mildly encrypted) - decoded as stream till finding
+ * Contains a catalog of PackageItems that map to different data blobs.
+ * data - binary blobs of bytes, 1 blob per XML -
+ *
+ */
+class Package {
+public:
+ Package();
+ virtual ~Package();
+
+ bool parse(
+ const std::string& filename,
+ const std::string & key_location = "");
+
+ /*!
+ * \brief Get the XML after the read if any.
+ *
+ * \return String with the recovered XML.
+ */
+ std::string get_xml() {
+ return _xml;
+ }
+
+ /*!
+ * \brief Get the information about the read.
+ *
+ * \return String describing read info.
+ */
+ std::string get_info() const {
+ return _info;
+ }
+
+ /*!
+ * \brief Get the items.
+ *
+ * \return Vector of package item objects.
+ */
+ std::vector get_items() {
+ return _items;
+ }
+
+ /*!
+ * \brief Get the name of the last file parsed.
+ *
+ * \return Last parsed filename.
+ */
+ std::string lastFileParsed()
+ {
+ return _lastFileName;
+ }
+
+ /*!
+ * \brief Set the public key from a string.
+ *
+ * \param key The key, base 64.
+ *
+ * \return True on success.
+ */
+ bool set_public(const std::string& key)
+ {
+ return _rsa.set_public(key);
+ }
+
+ std::vector targetsAvailable();
+
+ bool start_hasKey(
+ const std::string &fileName,
+ const std::vector& update);
+
+ bool start(
+ const std::string& fileName,
+ const std::string& key_location,
+ const std::vector& update);
+
+ bool completedAll();
+
+ void abort();
+
+ std::vector progress();
+
+ std::vector get_available(const std::string& directory);
+
+ std::vector get_available(const std::vector& directories);
+
+ // Copy / cache it (if not already in there).
+ bool copyUpdate(const std::string& fullNamePath, const std::string& cacheDirectory, uint32 maxCacheSize);
+
+ std::string getScriptIfAny();
+protected:
+
+ bool parseInternal(FILE *fp);
+
+ void clear();
+
+ std::string get_signature(FILE *pFile);
+
+ void updateCatalog(FILE* pFile);
+
+ void recurseDirectory(
+ const std::string& directory,
+ int depth,
+ std::vector& values);
+
+ int findPackageVector(
+ const std::vector& packages,
+ const std::string& fullNamePath);
+
+ void get_availableInternal(const std::string& directory, std::vector &info);
+
+ std::vector _items; ///< The items.
+ SignRsa _rsa; ///< Security.
+ std::string _lastFileName; ///< Last package file name.
+ std::string _packageName; ///< The name.txt file contents.
+ std::string _packageVers; ///< The version.txt file contents.
+ std::string _signature; ///< Digital signature of this file.
+ std::string _xml; ///< XML catalog of this file.
+ std::string _info; ///< Progress info for UI and debug.
+ std::size_t _startXml; ///< Where XML starts in the file.
+ std::size_t _startData; ///< Where data starts in the file.
+ FILE * _pFileUpdating; ///< File being updated.
+
+ std::vector _streams; ///< Target streams.
+ std::vector _packages; ///< Packages.
+ std::vector _avoid; ///< Avoid these.
+};
+
+
+#endif // PACKAGE_H_
Index: sources/update/PackageItem.cpp
===================================================================
diff -u
--- sources/update/PackageItem.cpp (revision 0)
+++ sources/update/PackageItem.cpp (revision 20b370a54d2737831b307a0de82aec9e06e2b772)
@@ -0,0 +1,220 @@
+/*!
+ *
+ * Copyright (c) 2023 Diality Inc. - All Rights Reserved.
+ *
+ * \copyright
+ * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN
+ * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER.
+ *
+ * \file PackageItem.cpp
+ * \author (last) Phil Braica
+ * \date (last) 23-Jan-2023
+ * \author (original) Phil Braica
+ * \date (original) 23-Jan-2023
+ */
+
+#include "PackageItem.h"
+
+#include
+#include
+#include
+
+
+/*!
+ * \brief Constructor.
+ */
+PackageItem::PackageItem():
+ _fileType(""),
+ _destPath(""),
+ _desc(""),
+ _security(0),
+ _version(0),
+ _size(0),
+ _rawFilename(""),
+ _isScript(false),
+ _byteOffset(0) {
+
+}
+
+/*!
+ * \brief Destructor.
+ */
+PackageItem::~PackageItem() {
+ ; // NOP.
+}
+
+/*!
+ * \brief Unwrap xml for the next tag occurance.
+ *
+ * Doesn't work for recursion but in this case that's not an issue.
+ *
+ * Static function.
+ *
+ * \param xml_str The XML to look within.
+ * \param tag The tag to look for.
+ * \param offset The offset to look from.
+ *
+ * \return The unwrapped text.
+ */
+std::string PackageItem::unwrap(const std::string & xml_str, const std::string & tag, std::size_t & offset) {
+ std::string stag = "<" + tag + ">";
+ std::string etag = "" + tag + ">";
+
+ std::size_t stag_ii = xml_str.find(stag, offset);
+ if (stag_ii == std::string::npos) {
+ return "";
+ }
+ stag_ii += stag.length();
+ std::size_t etag_ii = xml_str.find(etag, stag_ii);
+ if (etag_ii == std::string::npos) {
+ return "";
+ }
+ offset = etag_ii + etag.length();
+ return trim_text(xml_str.substr(stag_ii, etag_ii - stag_ii));
+}
+
+/*!
+ * \brief Unwrap xml for the next tag occurance.
+ *
+ * Doesn't work for recursion but in this case that's not an issue.
+ *
+ * Static function, no offset is needed for this is looking for the one and only child.
+ *
+ * \param xml_str The XML to look within.
+ * \param tag The tag to look for.
+ *
+ * \return The unwrapped text.
+ */
+std::string PackageItem::unwrap(const std::string& xml_str, const std::string& tag) {
+ std::size_t dontcare = 0;
+ return unwrap(xml_str, tag, dontcare);
+}
+
+/*!
+ * \brief Wrap content in XML tag.
+ *
+ * \param tag Tag to use.
+ * \param content Content.
+ * \return XML fragment.
+ */
+std::string PackageItem::wrap(const std::string& tag, const std::string& content) {
+ return " <" + tag + ">" + content + "\n";
+}
+
+/*!
+ * \brief An array of items.
+ *
+ * \param xml_str The XML to look within.
+ *
+ * \return The array of items that are intact if any.
+ */
+std::vector PackageItem::fromXml(const std::string& xml_str) {
+ std::vector rv;
+
+ std::size_t offset = 0;
+ for (std::size_t ii = 0; ii < xml_str.size(); ii++) {
+
+ PackageItem item;
+ bool ok = false;
+ try {
+ std::string item_xml = unwrap(xml_str, "item", ii);
+ if (item_xml.size() == 0) {
+ break;
+ }
+ // Try to get the essentials to at least know enough about this thing to drop if it needed.
+ item._fileType = unwrap(item_xml, "filetype");
+ item._size = static_cast(std::stoul(unwrap(item_xml, "size")));
+ item._byteOffset = offset;
+ offset += item._size;
+ ok = true;
+
+ // These if they error only disable this piece of the file (for now).
+ item._destPath = unwrap(item_xml, "path");
+ item._desc = unwrap(item_xml, "desc");
+ item._rawFilename = unwrap(item_xml, "raw");
+ item._security = static_cast(std::stoul(unwrap(item_xml, "security")));
+ item._version = static_cast(std::stoul(unwrap(item_xml, "version")));
+ std::string scriptFlag = unwrap(item_xml, "script");
+ item._isScript =
+ scriptFlag.size() == 0 ? false :
+ (scriptFlag[0] == 't' || scriptFlag[0] == 'T');
+ }
+ catch (...) {
+ continue;
+ }
+ if (ok && (item._size > 0) && (item._fileType != "")) {
+ rv.push_back(item);
+ } else {
+ // Not ok? Then file too corrupt to proceed with any of this.
+ rv.clear();
+ break;
+ }
+ }
+
+ // Return the list.
+ return rv;
+}
+
+/*!
+ * \brief Turn this item into an XML fragment.
+ *
+ * \return XML fragment.
+ */
+std::string PackageItem::to_xml() const {
+
+ return
+ "- \n" +
+ wrap("filetype", _fileType) +
+ wrap("path", _destPath) +
+ wrap("desc", _desc) +
+ wrap("security", std::to_string(_security)) +
+ wrap("version", std::to_string(_version)) +
+ wrap("size", std::to_string(_size)) +
+ wrap("script", (_isScript ? "True" : "False")) +
+ wrap("raw", _rawFilename) + "
\n";
+}
+
+/*!
+ * \brief Turn a set into XML
+ *
+ * \param catalog Vector to run through.
+ *
+ * \return String (xml).
+ */
+std::string PackageItem::to_xml(const std::vector& catalog) {
+ std::string rv;
+ for (const PackageItem& item : catalog) {
+ rv += item.to_xml();
+ }
+ return rv;
+}
+
+/*!
+ * \brief Trim whitespace before and after.
+ *
+ * \param txt Input text.
+ *
+ * \return Trimmed text.
+ */
+std::string PackageItem::trim_text(const std::string& txt) {
+
+ // This is relatively quick, easy to test.
+ std::string rv = txt;
+
+ // Front.
+ rv.erase(
+ rv.begin(),
+ std::find_if(rv.begin(), rv.end(),
+ [](unsigned char c) {
+ return !std::isspace(c);
+ }));
+
+ // Tail.
+ rv.erase(
+ std::find_if(rv.rbegin(), rv.rend(),
+ [](unsigned char c) {
+ return !std::isspace(c);
+ }).base(), rv.end());
+
+ return rv;
+}
Index: sources/update/PackageItem.h
===================================================================
diff -u
--- sources/update/PackageItem.h (revision 0)
+++ sources/update/PackageItem.h (revision 20b370a54d2737831b307a0de82aec9e06e2b772)
@@ -0,0 +1,56 @@
+/*!
+ *
+ * Copyright (c) 2023 Diality Inc. - All Rights Reserved.
+ *
+ * \copyright
+ * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN
+ * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER.
+ *
+ * \file PackageItem.h
+ * \author (last) Phil Braica
+ * \date (last) 23-Jan-2023
+ * \author (original) Phil Braica
+ * \date (original) 23-Jan-2023
+ */
+
+#ifndef PACKAGE_ITEM_H_
+#define PACKAGE_ITEM_H_
+
+#include
+#include
+
+/*!
+ * \brief One item in a package.
+ */
+class PackageItem {
+public:
+ std::string _fileType; ///< Either "LINUX", "UI", "DG", "HD", "DGFPGA", "HDFPGA"
+ std::string _destPath; ///< Where to copy to and name (if any)
+ std::string _desc; ///< Descriptive tracking info.
+ uint32_t _security; ///< Security tag.
+ uint32_t _version; ///< 2nd security tag.
+ uint32_t _size; ///< Size bytes
+ std::string _rawFilename; ///< The original raw filename, informational.
+ bool _isScript; ///< Is this the update script? 1 per all package items.
+ std::size_t _byteOffset; ///< Offset into the file (cumulative _size).
+
+ PackageItem();
+
+ virtual ~PackageItem();
+
+ static std::vector fromXml(const std::string & xml_str);
+
+ std::string to_xml() const;
+
+ static std::string to_xml(const std::vector & catalog);
+
+ static std::string unwrap(const std::string& xml_str, const std::string& tag);
+ static std::string unwrap(const std::string& xml_str, const std::string& tag, std::size_t& offset);
+
+ static std::string wrap(const std::string& tag, const std::string& content);
+
+protected:
+ static std::string trim_text(const std::string& txt);
+};
+
+#endif // PACKAGE_ITEM_H_
Index: sources/update/SignRsa.cpp
===================================================================
diff -u
--- sources/update/SignRsa.cpp (revision 0)
+++ sources/update/SignRsa.cpp (revision 20b370a54d2737831b307a0de82aec9e06e2b772)
@@ -0,0 +1,753 @@
+/*!
+ * \brief This provides device independent RSA 2048 bit data signing.
+ *
+ * See the header file for details about this module.
+ *
+ * \copyright 2023, Sunrise Labs, Inc.
+ *
+ * \file sign_rsa.cpp
+ *
+ * \author PBraica
+ * \date March 2023
+ */
+
+#include "SignRsa.h"
+#include
+#include
+#include
+#include
+
+// Windows requires this, Linux doesn't have it.
+#if defined(_WIN32) || defined(WIN32)
+#include
+#endif
+
+/**
+ * This class pulls some of the RSA logic into one place that
+ * could be error prone, and it wraps the kind and size of the hash
+ * currently as SHA256 so that in the future other hashes or
+ * custom hashes could be used.
+ */
+class _InternalDigest {
+public:
+ /*!
+ * \brief Constructor.
+ */
+ _InternalDigest():
+ _ctx(),
+ _pDigest(),
+ _finalized(false)
+ {
+ SHA256_Init(&_ctx);
+ }
+
+ /*!
+ * \brief Copy from other.
+ *
+ * \param pOther Other object.
+ */
+ void copy_from(const _InternalDigest* pOther) {
+ // CTX is a structure in RSA.
+ memcpy(&_ctx, &pOther->_ctx, sizeof(SHA256_CTX));
+
+ // Copy the digest.
+ memcpy(_pDigest, pOther->_pDigest, SHA256_DIGEST_LENGTH);
+ }
+
+ /*!
+ * \brief Update the context.
+ *
+ * \param pData Buffer pointer.
+ * \param size_bytes Size in bytes.
+ */
+ void update(const unsigned char * pData, std::size_t size_bytes) {
+ if (size_bytes > 0) {
+ SHA256_Update(&_ctx, pData, size_bytes);
+ }
+ }
+
+ /*!
+ * \brief Finalize the context and create the digest bytes.
+ *
+ * \return Digest bytes.
+ */
+ unsigned char* get_digest() {
+ SHA256_Final(_pDigest, &_ctx);
+ return _pDigest;
+ }
+
+ /*!
+ * \brief Clear.
+ */
+ void clear() {
+ SHA256_Init(&_ctx);
+ }
+
+protected:
+ /*!
+ * \brief The context computation object.
+ */
+ SHA256_CTX _ctx;
+
+ /*!
+ * \brief The Digest bytes.
+ */
+ unsigned char _pDigest[SHA256_DIGEST_LENGTH];
+
+ /*! \brief Was this finalized? */
+ bool _finalized;
+};
+
+// Buffer for file read operations. The buffer must be able to accomodate
+// the RSA signature in whole (e.g. 4096-bit RSA key produces 512 byte signature)
+#define BUFFER_SIZE (16384)
+
+/*!
+ * \brief Do the initialization calls for openSSL.
+ */
+class DoOnce {
+public:
+ /*!
+ * \brief Constructor.
+ */
+ DoOnce() {
+ OpenSSL_add_all_algorithms();
+ ERR_load_crypto_strings();
+ }
+};
+
+/*!
+ * \brief Constructor.
+ */
+SignRsa::SignRsa():
+ _pDigest(new _InternalDigest()),
+ _pPublic(nullptr),
+ _pPrivate(nullptr) {
+
+ // On first entry do this.
+ static DoOnce doOnce;
+}
+
+/*!
+ * \brief Copy constructor.
+ */
+SignRsa::SignRsa(const SignRsa& other) :
+ _pDigest(new _InternalDigest()),
+ _pPublic(nullptr),
+ _pPrivate(nullptr),
+ _lastPub("")
+{
+ _pDigest->copy_from(other._pDigest);
+
+ _pPublic = (other._pPublic == nullptr) ?
+ nullptr : RSAPublicKey_dup(other._pPublic);
+ _pPrivate = (other._pPrivate == nullptr) ?
+ nullptr : RSAPrivateKey_dup(other._pPrivate);
+}
+
+/*!
+ * \brief Destructor.
+ */
+SignRsa::~SignRsa() {
+ if (_pDigest != nullptr) {
+ delete _pDigest;
+ }
+ if (_pPublic != nullptr) {
+ RSA_free(_pPublic);
+ }
+ if (_pPrivate != nullptr) {
+ RSA_free(_pPrivate);
+ }
+}
+
+/*!
+ * \brief Create new RSA keys.
+ */
+void SignRsa::create_keys() {
+
+ // Free old if needed.
+ if (_pPublic != nullptr) {
+ RSA_free(_pPublic);
+ }
+ if (_pPrivate != nullptr) {
+ RSA_free(_pPrivate);
+ }
+
+ // Create new.
+ unsigned long e = RSA_F4;
+ BIGNUM *bne = BN_new();
+ int ret = BN_set_word(bne, e);
+ if (ret == 1) {
+ _pPrivate = RSA_new();
+ ret = RSA_generate_key_ex(_pPrivate, 2048, bne, NULL);
+ if (ret == 1) {
+ _pPublic = RSAPublicKey_dup(_pPrivate);
+ } else {
+ RSA_free(_pPrivate);
+ }
+ }
+ BN_free(bne);
+ return;
+}
+
+/*!
+ * \brief Save this objects keys to the folder if they exist.
+ *
+ * Saves them as public.pem and private.pem, if they exist.
+ *
+ * \param folder_name Folder to use.
+ */
+void SignRsa::save_keys(const std::string& folder_name) {
+
+ const std::string pubName = folder_name + "/public.pem";
+ const std::string priName = folder_name + "/private.pem";
+
+ if (_pPublic != nullptr) {
+ FILE* pemFile = fopen(pubName.c_str(), "wb");
+ if (pemFile != NULL) {
+ PEM_write_RSA_PUBKEY(pemFile, _pPublic);
+ fclose(pemFile);
+ }
+ }
+ if (_pPrivate != nullptr) {
+ FILE* pemFile = fopen(priName.c_str(), "wb");
+ if (pemFile != NULL) {
+ PEM_write_RSAPrivateKey(pemFile, _pPrivate, NULL, NULL, 0, NULL, NULL);
+ fclose(pemFile);
+ }
+ }
+}
+
+/*!
+ * \brief Load the public.pem and private.pem, what ever exists.
+ *
+ * \param folder_name Folder to use, or the path+name of the public or private key.
+ */
+void SignRsa::load_keys(const std::string& folder_name) {
+
+ std::string key_loc = folder_name;
+ std::size_t clip = key_loc.find("public.pem");
+ if (clip != std::string::npos) {
+ key_loc.resize(clip - 1);
+ }
+ clip = key_loc.find("private.pem");
+ if (clip != std::string::npos) {
+ key_loc.resize(clip - 1);
+ }
+
+ const std::string pubName = key_loc + "/public.pem";
+ const std::string priName = key_loc + "/private.pem";
+
+ if (_pPublic != nullptr) {
+ RSA_free(_pPublic);
+ _pPublic = nullptr;
+ }
+ if (_pPrivate != nullptr) {
+ RSA_free(_pPrivate);
+ _pPrivate = nullptr;
+ }
+
+ FILE* pPubFile = NULL;
+ FILE* pPriFile = NULL;
+ std::string tmpString="";
+
+ // Try the _pPublic.
+ try {
+ pPubFile = fopen(pubName.c_str(), "r");
+ if (pPubFile != NULL) {
+ // Get size.
+ fseek(pPubFile, 0, SEEK_END);
+ int size = ftell(pPubFile);
+
+ // Got back and read into tmpString.
+ fseek(pPubFile, 0, SEEK_SET);
+ tmpString.resize(size, '\0');
+
+ // Read to cache the string value.
+ std::size_t readBytes = fread(&tmpString[0], sizeof(char), (size_t)size, pPubFile);
+ if (readBytes != (std::size_t)size) {
+ tmpString = "";
+ }
+
+ // Go back and read public key from file
+ fseek(pPubFile, 0, SEEK_SET);
+ _pPublic = PEM_read_RSA_PUBKEY(pPubFile, NULL, NULL, NULL);
+ }
+ } catch (...) {
+ _pPublic = nullptr;
+ }
+
+ // Cache the public key since it was read ok,
+ // this helps when validating many files from the same API.
+ if (_pPublic != nullptr) {
+ _lastPub = tmpString;
+ }
+
+ // Try the _pPrivate.
+ try {
+ pPriFile = fopen(priName.c_str(), "r");
+ if (pPriFile != NULL) {
+ // Read public key from file
+ _pPrivate = PEM_read_RSAPrivateKey(pPriFile, NULL, NULL, NULL);
+ }
+ }
+ catch (...) {
+ _pPrivate = nullptr;
+ }
+
+ // Because these are typically security procedures,
+ // if they fail we don't want to crash, we need to be able
+ // to keep going so the close is wrapped in try/catch.
+
+ try
+ {
+ if (pPubFile)
+ {
+ fclose(pPubFile);
+ }
+ }
+ catch (...) {
+ ; // NOP.
+ }
+
+ try
+ {
+ if (pPriFile)
+ {
+ fclose(pPriFile);
+ }
+ }
+ catch (...) {
+ ; // NOP.
+ }
+}
+
+/*!
+ * \brief Set the public key from a string.
+ *
+ * \note We cache public keys for speed / ease of use.
+ *
+ * \param key The key, base 64.
+ *
+ * \return True on success.
+ */
+bool SignRsa::set_public(const std::string& key) {
+
+ if (key == _lastPub)
+ {
+ return true; // No need to do anything and thus success.
+ }
+
+ if (_pPublic != nullptr) {
+ RSA_free(_pPublic);
+ }
+
+ BIO* keybio;
+ const char* cstr = key.c_str();
+ keybio = BIO_new_mem_buf((void*)cstr, -1);
+ if (keybio == NULL)
+ {
+ return false;
+ }
+ _pPublic = PEM_read_bio_RSA_PUBKEY(keybio, &_pPublic, NULL, NULL);
+ BIO_free(keybio);
+
+ _lastPub = key;
+ return true;
+}
+
+/*!
+ * \brief Set the private key from a string.
+ *
+ * \note Private keys are NOT cached as text for security!
+ *
+ * \param key The key, base 64
+ *
+ * \return True on success.
+ */
+bool SignRsa::set_private(const std::string& key) {
+ if (_pPrivate != nullptr) {
+ RSA_free(_pPrivate);
+ }
+ BIO* keybio;
+ const char* cstr = key.c_str();
+ keybio = BIO_new_mem_buf((void*)cstr, -1);
+ if (keybio == NULL) {
+ return false;
+ }
+ _pPrivate = PEM_read_bio_RSAPrivateKey(keybio, &_pPrivate, NULL, NULL);
+ return true;
+}
+
+/*!
+ * \brief Clear our digest to do a different new stream.
+ */
+void SignRsa::clear_digest() {
+ _pDigest->clear();
+}
+
+/*!
+ * \brief Update our Digest object from a buffer.
+ *
+ * \param pData Data pointer.
+ * \param size_bytes Size in bytes.
+ */
+void SignRsa::update_digest(const unsigned char* pData, std::size_t size_bytes) {
+ _pDigest->update(pData, size_bytes);
+}
+
+/*!
+ * \brief Create and return a signature for a buffer.
+ *
+ * \param pData Data pointer.
+ * \param size_bytes Size in bytes.
+ *
+ * \return A base64 signature.
+ */
+std::string SignRsa::sign_data(const unsigned char* pData, std::size_t size_bytes) {
+ _InternalDigest digest;
+ // Guard not being setup or given bad things.
+ if ((pData != nullptr) &&
+ (size_bytes != 0)) {
+ digest.update(pData, size_bytes);
+ }
+ return sign_digest(digest.get_digest());
+}
+
+/*!
+ * \brief Create and return a signature for a file.
+ *
+ * \param pFile File pointer.
+ *
+ * \return A base64 signature.
+ */
+std::string SignRsa::sign_file(FILE* pFile) {
+ // Guard not being setup or given bad things.
+ if (pFile == nullptr) {
+ return "";
+ }
+
+ // Stream file into buffer, update the digest.
+ static unsigned char pBuffer[BUFFER_SIZE];
+ _InternalDigest digest;
+ std::size_t size_bytes = fread(pBuffer, 1, BUFFER_SIZE, pFile);
+ std::size_t total = size_bytes;
+ // Read data in chunks and feed it to OpenSSL SHA256
+ while (size_bytes > 0)
+ {
+ digest.update(pBuffer, size_bytes);
+ size_bytes = fread(pBuffer, 1, BUFFER_SIZE, pFile);
+ total += size_bytes;
+ }
+ return sign_digest(digest.get_digest());
+}
+
+/*!
+ * \brief Create and return a signature for a file.
+ *
+ * \param pDigest Digest pointer.
+ *
+ * \return A base64 signature.
+ */
+std::string SignRsa::sign_digest(const unsigned char* pDigest) {
+
+ int result = 0; // Set to 0=invalid.
+
+ std::string signature;
+
+ // Guard not being setup or given bad things.
+ //
+ // Invalid signature size, etc. are handled by RSA_verify,
+ // here we make sure our pointers are ok
+ if ((_pDigest != nullptr) &&
+ (_pPrivate != nullptr)) {
+
+ char *pBuffer = (char *)malloc(RSA_size(_pPrivate));
+ if (pBuffer != NULL) {
+
+ unsigned int size_bytes = 0;
+
+ // Ask the library to sign.
+ result = RSA_sign(
+ NID_sha256,
+ pDigest,
+ SHA256_DIGEST_LENGTH,
+ (unsigned char *)pBuffer,
+ &size_bytes,
+ _pPrivate);
+
+ if (result == 1) {
+ signature = encodeBase64(pBuffer, size_bytes);
+ } else {
+ signature.resize(0);
+ }
+ }
+ }
+ return signature;
+}
+
+/*!
+ * \brief Given buffer and signature, verify they match w/ public key.
+ *
+ * \param pData Data pointer.
+ * \param size_bytes Size in bytes.
+ * \param signature The signature.
+ *
+ * \return True if they match, false if can't compare or failed.
+ */
+bool SignRsa::verify_data(const unsigned char* pData, std::size_t size_bytes, const std::string& signature) {
+
+ _InternalDigest digest;
+
+ // Guard not being setup or given bad things.
+ if ((pData != nullptr) &&
+ (size_bytes != 0)) {
+ digest.update(pData, size_bytes);
+ }
+ return verify_digest(digest.get_digest(), signature);
+}
+
+/*!
+ * \brief Given a file and signature verify they match w/ public key.
+ *
+ * \param pFile File pointer.
+ * \param signature The signature.
+ *
+ * \return True if they match, false if can't compare or failed.
+ */
+bool SignRsa::verify_file(FILE* pFile, const std::string& signature) {
+
+ // Guard not being setup or given bad things.
+ if (pFile == nullptr) {
+ return false;
+ }
+
+ // Stream file into buffer, update the digest.
+ static unsigned char pBuffer[BUFFER_SIZE];
+ _InternalDigest digest;
+ std::size_t size_bytes = fread(pBuffer, 1, BUFFER_SIZE, pFile);
+
+ // Read data in chunks and feed it to OpenSSL SHA256
+ while (size_bytes > 0)
+ {
+ digest.update(pBuffer, size_bytes);
+ size_bytes = fread(pBuffer, 1, BUFFER_SIZE, pFile);
+ }
+
+ // Verify the digest.
+ return verify_digest(digest.get_digest(), signature);
+}
+
+/*!
+ * \brief Given a Digest and signature verify they match w/ public key.
+ *
+ * \param pDigest File pointer.
+ * \param signature The signature.
+ *
+ * \return True if they match, false if can't compare or failed.
+ */
+bool SignRsa::verify_digest(
+ const unsigned char* pDigest,
+ const std::string& signature) {
+
+ int result = 0; // Set to 0=invalid.
+
+ // Guard not being setup or given bad things.
+ //
+ // Invalid signature size, etc. are handled by RSA_verify,
+ // here we make sure our pointers are ok
+ if ((_pDigest != nullptr) &&
+ (_pPublic != nullptr)) {
+
+ // Our signature is base64 without end of lines.
+
+ // For readability, get the pointer and size here.
+ const std::string decoded = decodeBase64(signature);
+ const unsigned char * pSigBuffer = (const unsigned char* )decoded.data();
+ const std::size_t sig_size_bytes = decoded.size();
+
+ // Ask the library to verify.
+ result = RSA_verify(
+ NID_sha256,
+ pDigest,
+ SHA256_DIGEST_LENGTH,
+ pSigBuffer,
+ (unsigned int)sig_size_bytes,
+ _pPublic);
+ }
+ return (result == 1);
+}
+
+/*!
+ * \brief Finalize the context and create the digest bytes.
+ *
+ * \return Digest bytes.
+ */
+const unsigned char* SignRsa::get_digest() const {
+ return _pDigest->get_digest();
+}
+
+/*!
+ * \brief Decode base64 to data.
+ *
+ * This is very fast and more permissive than OpenSSL's implimentation
+ * and thereby makes it compatible with how Python does things with OpenSSL.
+ *
+ * IF the input data is invalid, you get invalid output but it won't crash.
+ *
+ * \param base64_str String.
+ *
+ * \return String.
+ */
+std::string SignRsa::decodeBase64(const std::string& base64_str) const {
+
+ static constexpr char reverse_table[256] = {
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
+
+ 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
+ 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
+
+ // Unused unless a byte is invalid, we padd with
+ // stuff we don't care so we can skip array indexing for speed.
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
+ };
+
+ // This is meant to be simple and fast, but not validate the format.
+ // In the RSA sense the rest of this code consumes the decoded
+ // data and validates it's content so ... format checking is not
+ // useful it gets caught by the consumer.
+ //
+ // The fastest way to do this is process 3 input bytes at once
+ // and construct the output with unrolling but when it's coded
+ // that way it is 3-4x more complex with more corner cases to verify.
+ //
+ // This is almost that fast (fast enough for the application) and
+ // no corner cases.
+
+ std::string rv; // Return value.
+ unsigned int acc = 0; // Accumulate the value.
+ unsigned int bits = 0; // Bits in the accumulated.
+ for (unsigned char c : base64_str) {
+
+ // This handles the problem that openSSL has,
+ // it either requires line breaks or forbids line breaks.
+ if (::std::isspace(c) || c == '=') {
+ continue;
+ }
+
+ // Decode the byte, accumulate 6 bits.
+ char v = reverse_table[c];
+ acc = (acc << 6) | v;
+ bits += 6;
+
+ // Export a byte if needed.
+ if (bits >= 8) {
+ bits -= 8;
+ rv += (char)((acc >> bits) & 0xFF);
+ }
+ }
+
+ // Return.
+ return rv;
+}
+
+/*!
+ * \brief Encode base64 to data.
+ *
+ * IF the input data is invalid, you get invalid output but it won't crash.
+ *
+ * \param raw_str Raw data string.
+ * \param sizeBytes Size in bytes.
+ *
+ * \return String.
+ */
+std::string SignRsa::encodeBase64(const char* raw_str, std::size_t sizeBytes) const {
+ static constexpr char encode_table[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '+', '/'
+ };
+
+ std::string rv;
+
+ std::size_t phase = 0;
+ unsigned char lc = 0;
+ unsigned char byte;
+ for (std::size_t ii = 0; ii < sizeBytes; ii++) {
+ // Every 3 characters = 4 bytes.
+ unsigned char c = (unsigned char) raw_str[ii];
+
+ // NOTE: literals are used here instead of constexpr
+ // because it is dramatically easier to understand.
+ switch (phase) {
+ case 0:
+ // top 6 bits.
+ byte = (c >> 2) & 0x3F;
+ rv += encode_table[byte];
+ break;
+ case 1:
+ // last 2 bits + 4 bits.
+ byte = ((lc & 0x3) << 4) | (c >> 4);
+ rv += encode_table[byte];
+ break;
+ default:
+ // last 4 bits + 2 bits.
+ byte = ((lc & 0xF) << 2) | (c >> 6);
+ rv += encode_table[byte];
+
+ // Last 6 bits.
+ byte = (c & 0x3F);
+ rv += encode_table[byte];
+ }
+ phase = (phase + 1) % 3;
+ lc = c;
+ }
+
+ // Handle tail end.
+ // 0 means we lined up luckily ideally, no padding.
+ // 1 means we have 2 bits in lc to emit, two ==
+ // 2 means we have 4 bits in lc to emit, one =
+ if (phase == 1) {
+ byte = ((lc & 0x3) << 4);
+ rv += encode_table[byte];
+ rv += "==";
+ }
+ if (phase == 2) {
+ byte = (lc & 0xF) << 2;
+ rv += encode_table[byte];
+ rv += "=";
+ }
+
+ return rv;
+}
+
+/*!
+ * \brief Encode base64 to data.
+ *
+ * IF the input data is invalid, you get invalid output but it won't crash.
+ *
+ * \param raw_str Raw data string.
+ *
+ * \return String.
+ */
+std::string SignRsa::encodeBase64(const std::string& raw_str) const {
+ return encodeBase64(raw_str.c_str(), raw_str.length());
+}
Index: sources/update/SignRsa.h
===================================================================
diff -u
--- sources/update/SignRsa.h (revision 0)
+++ sources/update/SignRsa.h (revision 20b370a54d2737831b307a0de82aec9e06e2b772)
@@ -0,0 +1,151 @@
+/*!
+ * \brief This provides device independent RSA 2048 bit data signing.
+ *
+ * It is based on OpenSSL, which is the most maintained and most
+ * popular package, but it is not completely documented, and examples
+ * across the web use different levels of the API and different versions
+ * making this hard to code / recode.
+ *
+ * OpenSSL typically is part of most Linux distros and as it's the heart
+ * of most HTTPS operations on Linux almost always available. It is
+ * available as source code and there are binary installers for Windows,
+ * Linux, Android, iOS, iPhone and many others.
+ *
+ * Note to use it as a developer on Windows with visual studio:
+ * [C/C++ -> General -> Additional Include Directories] value: OpenSSL?s include directory in your machine (e.g C:\openssl\include)
+ * [Linker -> General -> Additional Library Directories] value: OpenSSL?s lib directory in your machine (e.g C:\openssl\lib)
+ * [Linker -> Input -> Additional Dependencies] value: libcrypto.lib libssl.lib
+ *
+ * Be aware some countries put import/export restrictions on anything cryptographic
+ * and the license for OpenSSL makes that very clear.
+ *
+ * For ITAR compliance this includes nothing novel or new. All algorithms are published,
+ * not restricted by US law and are used as is.
+ *
+ * Sunrise has an equivalent python3 module that is interoperable with this C++ code.
+ *
+ * \copyright 2023, Sunrise Labs, Inc.
+ *
+ * \file SignRsa.h
+ *
+ * \author PBraica
+ * \date March 2023
+ */
+
+
+#ifndef SIGN_RSA_H_
+#define SIGN_RSA_H_
+
+#include
+
+
+// Prototype internal class not part of the API.
+class _InternalDigest;
+
+// Forward declare RSA so that other classes building this object
+// don't pull in the giant includes of RSA over and over.
+typedef struct rsa_st RSA;
+
+/**
+ * This wraps OpenSSL, we have an equivalent sign_rsa.py file
+ * for scripting/tool based things that is basically call for call
+ * equivalent and compatible.
+ *
+ * In some cases, the software is only used to handle verify in which case
+ * the public key will exist and be loaded and the private is not.
+ *
+ *
+ * At the API level:
+ * Digests are 256-bit SHA hash
+ * Signatures are base64 strings.
+ *
+ */
+class SignRsa {
+
+public:
+ SignRsa();
+
+ SignRsa(const SignRsa &other);
+
+ virtual ~SignRsa();
+
+ void create_keys();
+
+ void save_keys(const std::string& folder_name);
+
+ void load_keys(const std::string& folder_name);
+
+ /*!
+ * \brief Set the keys from strings.
+ *
+ * \param public_key Public key string.
+ * \param private_key Private key string.
+ *
+ * \return True on success.
+ */
+ bool set_keys(const std::string& public_key, const std::string& private_key) {
+ return set_public(public_key) && set_private(private_key);
+ }
+
+ bool set_public(const std::string& key);
+
+ bool set_private(const std::string& key);
+
+ void clear_digest();
+
+ void update_digest(const unsigned char* pData, std::size_t size_bytes);
+
+ std::string sign_data(const unsigned char* pData, std::size_t size_bytes);
+
+ std::string sign_file(FILE * pFile);
+
+ std::string sign_digest(const unsigned char* pDigest);
+
+ bool verify_data(const unsigned char* pData, std::size_t size_bytes, const std::string& signature);
+
+ bool verify_file(FILE* pFile, const std::string& signature);
+
+ bool verify_digest(const unsigned char* pDigest, const std::string& signature);
+
+ /*!
+ * \brief Does the public key exist?
+ *
+ * \return True if it exists.
+ */
+ bool public_exists() const {
+ return _pPublic != nullptr;
+ }
+
+ /*!
+ * \brief Does the private key exist?
+ *
+ * \return True if it exists.
+ */
+ bool private_exists() const {
+ return _pPrivate != nullptr;
+ }
+
+ /*!
+ * \brief Finalize the context and create the digest bytes.
+ *
+ * \return Digest bytes.
+ */
+ const unsigned char* get_digest() const;
+
+ // These are more tolerant versions than openSSL, which has a known
+ // problem with it's decode making it incompatible with many
+ // base64 encoders.
+
+ std::string decodeBase64(const std::string& base64_str) const;
+ std::string encodeBase64(const std::string& raw_str) const;
+ std::string encodeBase64(const char * raw_str, std::size_t size) const;
+
+protected:
+ _InternalDigest* _pDigest; ///< Internal digest.
+ RSA* _pPublic; ///< Public key.
+ RSA* _pPrivate; ///< Private key.
+ std::string _lastPub; ///< Last public key.
+};
+
+
+#endif // SIGN_RSA_H_
Index: sources/update/UiProtocol.cpp
===================================================================
diff -u
--- sources/update/UiProtocol.cpp (revision 0)
+++ sources/update/UiProtocol.cpp (revision 20b370a54d2737831b307a0de82aec9e06e2b772)
@@ -0,0 +1,408 @@
+/*!
+ *
+ * Copyright (c) 2023 Diality Inc. - All Rights Reserved.
+ *
+ * \copyright
+ * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN
+ * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER.
+ *
+ * \file UiProtocol.cpp
+ * \author (last) Phil Braica
+ * \date (last) 23-Jan-2023
+ * \author (original) Phil Braica
+ * \date (original) 23-Jan-2023
+ *
+ */
+
+#include "UiProtocol.h"
+#include "UiSwUpdate.h"
+#include "IDataProvider.h"
+
+#include "Logger.h"
+
+#include
+#include
+
+namespace SwUpdate {
+
+uint16 UiProtocol::_gRandSeed = 0; ///< Seed value.
+uint8 UiProtocol::_gIdNum = 0; ///< Id number.
+
+
+/*!
+ * \brief Constructor.
+ *
+ * \param target Target to go after for update.
+ */
+UiProtocol::UiProtocol(SwUpdateTargetEnum target) :
+ _desired(Idle),
+ _state(None),
+ _lastState(None),
+ _target(target),
+ _pProvider(nullptr),
+ _transfered(0),
+ _maxTransfer(0),
+ _retries(0),
+ _lastRx{(uint8)0},
+ _link(KBPS_WIRE),
+ _pThread(nullptr),
+ _completedOk(false)
+{
+ ; // NOP.
+}
+
+/*!
+ * \brief Destructor (virtual).
+ */
+UiProtocol::~UiProtocol() {
+ // Set shutdown, then fire start,
+ // finally join thread.
+ _desired = Shutdown;
+
+ // It finished and ready to join, join and exit.
+ if (_pThread != nullptr) {
+ _pThread->join();
+ _pThread = nullptr;
+ }
+}
+
+/*!
+ * \brief Start updating.
+ *
+ * \return True on started ok.
+ */
+bool UiProtocol::start() {
+ std::lock_guard lockApi(_apiMutex);
+
+
+ if (_pThread != nullptr) {
+ _pThread->join();
+ delete _pThread;
+ _pThread = nullptr;
+ }
+ _pThread = new std::thread(&UiProtocol::_run, this);
+
+ // Copy the key info in.
+ _desired = Complete;
+ _lastState = None;
+
+ return true;
+}
+
+/*!
+ * \brief Abort update.
+ */
+bool UiProtocol::abort() {
+ std::lock_guard lockapi(_apiMutex);
+ _desired = Idle;
+ _link.abort();
+
+ return true;
+}
+
+/*!
+ * \brief Current estimated progress.
+ *
+ * When not "updating" it is the last known step
+ * which can describe a failure condition for debug.
+ *
+ * \return Status object.
+ */
+UiUpdateStatus UiProtocol::progress() {
+ // Our state to descriptive text.
+
+ static const std::string state_text[] = {
+ "Not started", "Starting", "Streaming", "Streamed",
+ "Verified", "Version checked", "Completed", "Start failed",
+ "Stream failed", "Verification failed", "Version check failed", "Reboot failed",
+ "Cyber attack", "File failure"};
+
+ // Grab mutex so status is always coherent.
+ std::lock_guard lockapi(_apiMutex);
+
+ UiUpdateStatus rv;
+
+ // One of 3 things is the current desired end state.
+ rv.goal =
+ _desired == Idle ? "Idle" :
+ _desired == Complete ? "Complete" :
+ "Shutdown";
+
+ // Show as text which target.
+ rv.targetName =
+ _target == HD ? "HD" :
+ _target == HDFPGA ? "HDFPGA" :
+ _target == DG ? "DG" :
+ _target == DGFPGA ? "DGFPGA" : "Files";
+
+ bool stillInProgress = (_state != UpdateState::None) && (_state < UpdateState::Completed);
+ rv.inProgress = stillInProgress;
+
+ rv.totalSteps = (uint16)UpdateState::Completed;
+
+ // Compute the descriptive text.
+ uint32 stepIndex = (uint32)_state;
+ rv.stepName = stepIndex <= FileFailure ? state_text[stepIndex] : "Unknown SW Err";
+ rv.stepIndex = _state < UpdateState::Completed ? (UpdateState)stepIndex : UpdateState::Completed;
+
+ const float one_hundred_percent = 100.0f;
+ rv.percentTotal = _maxTransfer == 0 ? 0 : (_transfered * one_hundred_percent / _maxTransfer);
+
+ return rv;
+}
+
+/*!
+ * \brief Receive and check if matched.
+ *
+ * \param msgId Can message ID.
+ * \param pData Data pointer.
+ *
+ * \return True if it matched.
+ */
+bool UiProtocol::receive(uint16 msgId, uint8 * pData) {
+
+ // Return value.
+ bool matched = false;
+
+ // IF we're aren't trying to make progress in this object
+ // towards completion, we don't care.
+ if (_desired == Complete) {
+
+ // If it's a message that matches what we'd want,
+ // then copy it.
+ matched = _link.checkReceived(msgId, pData);
+ if (matched) {
+ memcpy(_lastRx, pData, CAN_PAYLOAD_SIZE);
+ }
+ }
+ return matched;
+}
+
+///////////////////////////////////////////////////////////
+// Protected.
+///////////////////////////////////////////////////////////
+
+/**
+ * \brief Thread runs update process.
+ */
+void UiProtocol::_run() {
+
+ // Wait till we start.
+ if (_state != None) {
+ _lastState = _state;
+ _state = _desired == Complete ? Starting : None;
+ }
+
+ _completedOk = false; // In progress.
+
+ // Do most of the work, virtual call so
+ // it can be overriden for file like things and test.
+ _doUpdate();
+
+ // Throw away the pointer to the old data provider.
+ _pProvider = nullptr;
+
+ // Cyber check.
+ if (_link.numberRetries() > _maxCyberRetries) {
+ _state = UpdateState::CyberThreat;
+ }
+ if (_state == Completed) {
+ _completedOk = true;
+ }
+
+ // If anything is lingering, stop it.
+ _link.abort();
+
+ {
+ std::unique_lock lock2(_apiMutex);
+ _desired = _desired == Complete ? Idle : _desired;
+ }
+
+ // Finally tell UiSwUpdate that coordinates multiple things
+ // that this set of actions are complete, and if they completed ok.
+ UiSwUpdate::instance().taskCompleted(_completedOk == true, this);
+}
+
+/*!
+ * \brief Do the update for FW / FPGA
+ */
+void UiProtocol::_doUpdate() {
+
+ // Null check / race condition.
+ if (_pProvider == nullptr) {
+ return;
+ }
+
+ // Reset the transfered counter.
+ _transfered = 0;
+ _link.resetRetries();
+
+ // Send start.
+ _state = UpdateState::Starting;
+ bool ok = _sendCommand(SwUpdateCmdEnum::Start);
+ _state = ok ? UpdateState::Started : UpdateState::StartTimedOut;
+ if ((_desired != Complete) || (!ok)) {
+ return;
+ }
+
+ // Stream the data.
+ ok = _streamData();
+ _state = ok ? UpdateState::Streamed : UpdateState::StreamTimedOut;
+ if ((_desired != Complete) || (!ok)) {
+ return;
+ }
+
+ // Send signature.
+ ok = _sendSignature();
+ _state = ok ? UpdateState::Streamed : UpdateState::StreamTimedOut;
+ if ((_desired != Complete) || (!ok)) {
+ return;
+ }
+
+ // Get verification data.
+ ok = _sendCommand(SwUpdateCmdEnum::Verify);
+ SwUpdateVerifyResponse *pVerify = (SwUpdateVerifyResponse*)_lastRx;
+ ok &= pVerify->data == _pProvider->verifyData;
+ _state = ok ? UpdateState::Verified : UpdateState::FailedVerify;
+ if ((_desired != Complete) || (!ok)) {
+ return;
+ }
+
+ // Get the version data.
+ ok = _sendCommand(SwUpdateCmdEnum::Version);
+ pVerify = (SwUpdateVerifyResponse*)_lastRx;
+ ok &= pVerify->data == _pProvider->versionData;
+ _state = ok ? UpdateState::Versioned : UpdateState::FailedVersion;
+ if ((_desired != Complete) || (!ok)) {
+ return;
+ }
+
+ // Say if we agree.
+ ok = _sendCommand(SwUpdateCmdEnum::Verified);
+ if ((_desired != Complete) || (!ok)) {
+ return;
+ }
+
+ // Reboot but only if it's a FW target, not if its a FPGA target.
+ if ((_target == HD) || (_target == DG)) {
+ ok = _sendCommand(SwUpdateCmdEnum::RunApp);
+ }
+ _state = ok ? UpdateState::Completed : UpdateState::RebootTimeout;
+
+ // If it failed, tell the boot loader to abort,
+ // at least try to.
+ if (_state != UpdateState::Completed) {
+ // Best effort abort.
+ _sendCommand(SwUpdateCmdEnum::Abort);
+ }
+}
+
+/*!
+ * \brief Send command.
+ *
+ * \param cmd Command enum to use.
+ *
+ * \return True on success, else false.
+ */
+bool UiProtocol::_sendCommand(SwUpdateCmdEnum cmd) {
+ SwUpdateCommand msg;
+ msg.id = _gIdNum++;
+ msg.cmd = SwUpdate_FormCommand(_target, cmd);
+ msg.rand = _gRandSeed++;
+ SwUpdate_createSecurity((uint8 *)&msg, sizeof(SwUpdateCommand));
+ return _link.sendOk(SwUpdateCanMsgIds::CommandId, (uint8 *)&msg, _retryEffort);
+}
+
+/*!
+ * \brief Stream the data.
+ *
+ * \return True on completed ok.
+ */
+bool UiProtocol::_streamData() {
+
+ // Stream the data.
+ SwUpdateDataBuffer msg;
+ msg.id = _gIdNum++;
+ msg.kind = _target;
+ _transfered = 0;
+ msg.index = 0;
+
+ // While data to stream...
+ const std::size_t image_size = _pProvider->totalSize;
+ while (_transfered < image_size) {
+ // Compute the size in bytes of the transfer, upto MAX_TRANSFER_SIZE.
+ const uint32 sz = (uint32)(_transfered + MAX_TRANSFER_SIZE) < (uint32)image_size ?
+ (uint32)MAX_TRANSFER_SIZE : (uint32)(image_size - _transfered);
+
+ // Copy the data in.
+ std::size_t rd_sz = _pProvider->read(msg.index * BYTES_PER_INDEX, msg.buffer, sz);
+
+ if (rd_sz < MAX_TRANSFER_SIZE) {
+ memset(&msg.buffer[rd_sz], 0, MAX_TRANSFER_SIZE - rd_sz);
+ }
+
+ // Obscure it.
+ SwUpdate_encodeData(&msg);
+
+ // The read typically takes <1ms time, but it might not, and it might block or even fail.
+ bool ok = rd_sz > 0;
+ if (_desired != Complete) {
+ return false;
+ }
+ if (!ok) {
+ // Retry read.
+ continue;
+ }
+
+ // Create the security token and send it.
+ SwUpdate_createSecurity((uint8 *) &msg, sizeof(SwUpdateDataBuffer));
+ ok = _link.sendOk(
+ (_target == HD) || (_target == DG) ?
+ SwUpdateCanMsgIds::DataBufferId_FW : SwUpdateCanMsgIds::DataBufferId_FPGA,
+ (uint8*)&msg, _retryEffort);
+ if ((_desired != Complete) || (!ok)) {
+ return false;
+ }
+
+ // Cyber check.
+ if (_link.numberRetries() > _maxCyberRetries) {
+ return false;
+ }
+
+ // Good, so increment.
+ msg.index += (MAX_TRANSFER_SIZE / BYTES_PER_INDEX);
+ _transfered += MAX_TRANSFER_SIZE;
+ }
+ return true;
+}
+
+/*!
+ * \brief Stream the data.
+ *
+ * \return True on completed ok.
+ */
+bool UiProtocol::_sendSignature() {
+ uint32 crc1 = _pProvider->verifyData;
+ uint32 crc2 = _pProvider->versionData;
+ SwUpdateDataBuffer msg;
+ msg.id = _gIdNum++;
+ msg.kind = _target;
+ _transfered = 0;
+ msg.index = 0xFFFF;
+ SwUpdate_makeSignature(crc1, crc2, msg.buffer);
+
+ // Obscure it.
+ SwUpdate_encodeData(&msg);
+
+ // Create the security token and send it.
+ SwUpdate_createSecurity((uint8 *) &msg, sizeof(SwUpdateDataBuffer));
+ bool ok = _link.sendOk(
+ (_target == HD) || (_target == DG) ?
+ SwUpdateCanMsgIds::DataBufferId_FW : SwUpdateCanMsgIds::DataBufferId_FPGA,
+ (uint8*)&msg, _retryEffort);
+
+ return (ok && (_link.numberRetries() <= _maxCyberRetries));
+}
+
+
+} // namespace::SwUpdate
Index: sources/update/UiProtocol.h
===================================================================
diff -u
--- sources/update/UiProtocol.h (revision 0)
+++ sources/update/UiProtocol.h (revision 20b370a54d2737831b307a0de82aec9e06e2b772)
@@ -0,0 +1,303 @@
+/*!
+ *
+ * Copyright (c) 2023 Diality Inc. - All Rights Reserved.
+ *
+ * \copyright
+ * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN
+ * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER.
+ *
+ * \file UiProtocol.h
+ * \author (last) Phil Braica
+ * \date (last) 23-Jan-2023
+ * \author (original) Phil Braica
+ * \date (original) 23-Jan-2023
+ *
+ */
+#ifndef UI_PROTOCOL_H_
+#define UI_PROTOCOL_H_
+
+#include "HalStdTypes.h"
+#include "MsgLink.h"
+#include "UpdateProtocol.h"
+#include "UiUpdateStatus.h"
+#include "IDataProvider.h"
+
+#include
+#include
+#include
+#include
+
+namespace SwUpdate {
+
+/** Forward declare. */
+class IDataProvider;
+
+/*!
+ * This class uses a thread plus a waitable condition variable
+ * to impliment a linear procedure to update one firmware
+ * "target". It uses "ID" slots for commands so that in theory
+ * multiple updaters running in paralell won't conflict.
+ *
+ * Note that this is deliberately NOT a statemachine even
+ * though it could be coded as one. Statemachines have overhead
+ * waiting for events and the goal is to have virtually zero
+ * time between events and in theory never really yielding unless
+ * the Firmware image gets too busy.
+ */
+class UiProtocol {
+public:
+ /*!
+ * \brief Constructor.
+ *
+ * \param target Target to go after for update.
+ */
+ UiProtocol(SwUpdateTargetEnum target);
+
+ /*!
+ * \brief Destructor (virtual).
+ */
+ virtual ~UiProtocol();
+
+ /*!
+ * \brief Start updating.
+ *
+ * \return True on started ok.
+ */
+ virtual bool start();
+
+ /*!
+ * \brief Abort update.
+ *
+ * \return True on success, false on timeout trying to stop the process.
+ */
+ bool abort();
+
+ /*!
+ * \brief Current estimated progress.
+ *
+ * When not "updating" it is the last known step
+ * which can describe a failure condition for debug.
+ *
+ * \return Status object.
+ */
+ UiUpdateStatus progress();
+
+ /*!
+ * \brief Receive and check if matched.
+ *
+ * \param msgId Can message ID.
+ * \param pData Data pointer.
+ *
+ * \return True if it matched.
+ */
+ bool receive(uint16 msgId, uint8 * pData);
+
+ /*!
+ * \brief Get the target enum value.
+ *
+ * \return Target enum.
+ */
+ SwUpdateTargetEnum target() {
+ return _target;
+ }
+
+ /*!
+ * \brief Did this complete ok?
+ *
+ * \return True if it has ran and completed successfully since a "start" call.
+ */
+ bool completedOk() {
+ return _completedOk;
+ }
+
+ /*!
+ * \brief Start updating.
+ *
+ * \param pProvider Data provider.
+ * \param pNext Next if the updates occur in a chain.
+ *
+ * \return True on started ok.
+ */
+ void setProvider(IDataProvider* pProvider) {
+ std::lock_guard lockApi(_apiMutex);
+ _pProvider = pProvider;
+ _maxTransfer = pProvider->totalSize;
+ }
+
+protected:
+
+ /*!
+ * \brief Thread runs update process.
+ */
+ void _run();
+
+ /**
+ * \brief Do the update for FW / FPGA
+ */
+ virtual void _doUpdate();
+
+ /*!
+ * \brief Send command.
+ *
+ * \param cmd Command enum to use.
+ *
+ * \return True on success, else false.
+ */
+ virtual bool _sendCommand(SwUpdateCmdEnum cmd);
+
+ /*!
+ * \brief Stream the data.
+ *
+ * \return True on completed ok.
+ */
+ virtual bool _streamData();
+
+ /*!
+ * \brief Stream the data.
+ *
+ * \return True on completed ok.
+ */
+ virtual bool _sendSignature();
+
+ /*!
+ * \brief Wait until we are paused or started.
+ *
+ * \param tillStopped Wait till stopped else wait till started.
+ *
+ * \return True on success, false on timeout.
+ */
+ bool _wait_stop(bool tillStopped);
+
+ /*!
+ * Progress states.
+ */
+ enum UpdateState {
+ None = 0,
+ Starting,
+ Started,
+ Streamed,
+ Verified,
+ Versioned,
+ Completed,
+
+ StartTimedOut,
+ StreamTimedOut,
+ FailedVerify,
+ FailedVersion,
+ RebootTimeout,
+
+ CyberThreat,
+ FileFailure
+ };
+
+ /*!
+ * The desired state.
+ */
+ enum DesiredState {
+ Idle = 0,
+ Complete,
+ Shutdown
+ };
+
+ static uint16 _gRandSeed; ///< Used to "whiten" encoding.
+ static uint8 _gIdNum; ///< Keeps multiple Updaters seperate.
+
+ // Note: These would be tuned based on traffic to be as small as possible but reliable.
+ const uint32 _retryEffort = 40; ///< Retry effort.
+ const uint32 _maxCyberRetries = 200; ///< Max retries total all msgs.
+
+ // The goal is to apply reasonable effort, and be able to report status.
+ // Thus, there is a desired end-state goal we apply effort towards, and
+ // we track the progress. We also track when we abort / stop what was the
+ // last state to make any desired logging / status / debug easy.
+
+ DesiredState _desired; ///< Desired state.
+ UpdateState _state; ///< Current state.
+ UpdateState _lastState; ///< Last state before stopping.
+ SwUpdateTargetEnum _target; ///< Target for update.
+ IDataProvider* _pProvider; ///< Data provider.
+ uint32 _transfered; ///< Transfered bytes.
+ uint32 _maxTransfer; ///< Max to transfer.
+ uint32 _retries; ///< Number of packet retries.
+ uint8 _lastRx[CAN_PAYLOAD_SIZE]; ///< Last RX.
+ MsgLink _link; ///< Link.
+ ::std::thread* _pThread; ///< Thread.
+ ::std::mutex _apiMutex; ///< Mutex API.
+ bool _completedOk; ///< Completed ok, false is either not started or in progress.
+};
+
+/*!
+ * This class inherits from UiProtocol to do all of the file transfers.
+ */
+class UiProtocolFile : public UiProtocol {
+public:
+ /*!
+ * \brief Constructor.
+ *
+ * \param target Target to go after for update.
+ */
+ UiProtocolFile() :
+ UiProtocol(LINUX) {
+ ; // NOP.
+ }
+
+ /*!
+ * \brief Destructor (virtual).
+ */
+ virtual ~UiProtocolFile() {
+ ; // NOP.
+ }
+
+ /*!
+ * \brief Start updating.
+ *
+ * \param targets Data provider, destination pairs.
+ *
+ * \return True on started ok.
+ */
+ void setup(
+ std::vector& targets) {
+
+ std::lock_guard lockApi(_apiMutex);
+ _targets = targets;
+
+ // So UI threads can get transfer status asynchronously
+ // once we start, save off the transfer max in number of files.
+ _maxTransfer = (uint32)targets.size();
+
+ // Copy the key info in.
+ _desired = Complete;
+ _lastState = None;
+ }
+
+ /*!
+ * \brief Do the update for file.
+ */
+ virtual void _doUpdate() {
+ constexpr std::chrono::duration waitRetry(250);
+
+ // In theory this is fast, so "status" is about number of files copied.
+ bool ok = true;
+ for (IDataProvider* p : _targets) {
+ if (_desired == Complete) {
+ ok = p->copyFile();
+ }
+ else {
+ break;
+ }
+ if (!ok) {
+ break;
+ }
+ _transfered++;
+ }
+
+ _state = ok ? UpdateState::Completed : UpdateState::FileFailure;
+ }
+
+protected:
+ std::vector _targets; ///< File targets to copy.
+};
+
+} // namespace::SwUpdate
+
+#endif // UI_PROTOCOL_H_
Index: sources/update/UiSwUpdate.cpp
===================================================================
diff -u
--- sources/update/UiSwUpdate.cpp (revision 0)
+++ sources/update/UiSwUpdate.cpp (revision 20b370a54d2737831b307a0de82aec9e06e2b772)
@@ -0,0 +1,229 @@
+/*!
+ *
+ * Copyright (c) 2023 Diality Inc. - All Rights Reserved.
+ *
+ * \copyright
+ * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN
+ * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER.
+ *
+ * \file UiSwUpdate.cpp
+ * \author (last) Phil Braica
+ * \date (last) 23-Jan-2023
+ * \author (original) Phil Braica
+ * \date (original) 23-Jan-2023
+ *
+ */
+
+#include "UiSwUpdate.h"
+#include "IDataProvider.h"
+#include
+
+namespace SwUpdate {
+
+/*!
+ * \brief Create the instance.
+ *
+ * \return Instance reference.
+ */
+UiSwUpdate & UiSwUpdate::instance() {
+ static UiSwUpdate inst;
+ return inst;
+}
+
+/*!
+ * \brief Constructor.
+ *
+ * \param target Target to go after for update.
+ */
+UiSwUpdate::UiSwUpdate():
+ _hdUpdate(HD),
+ _hdFpgaUpdate(HDFPGA),
+ _dgUpdate(DG),
+ _dgFpgaUpdate(DGFPGA),
+ _fileUpdate() {
+
+ // Assign by target.
+ _all[HD] = &_hdUpdate;
+ _all[HDFPGA] = &_hdFpgaUpdate;
+ _all[DG] = &_dgUpdate;
+ _all[DGFPGA] = &_dgFpgaUpdate;
+ _all[LINUX] = &_fileUpdate;
+}
+
+
+/*!
+ * \brief Destructor (virtual).
+ */
+UiSwUpdate::~UiSwUpdate() {
+ std::lock_guard lock(_mutexApi);
+
+ // Abort all.
+ for (auto it = _all.begin(); it != _all.end(); ++it) {
+ it->second->abort();
+ }
+}
+
+
+/*!
+ * \brief Start updating.
+ *
+ * \param dataProviders
+ *
+ * \return True on successfully started.
+ */
+bool UiSwUpdate::start(
+ std::vector & dataProviders) {
+
+ // Lock.
+ std::lock_guard lock(_mutexApi);
+
+ // We first need to do 2 things:
+ // Make an order list of the things that are process based by target.
+ // Get a list of the files, UI then linux.
+
+ std::map aboutToStart;
+ std::vector uiFiles;
+ std::vector linuxFiles;
+ std::vector priorTargets;
+
+ // Make a list we can access via map to get ordering, and get the files.
+ for (IDataProvider* pDp : dataProviders) {
+
+ SwUpdateTargetEnum target = pDp->targetName;
+
+ // Handle files.
+ if (target == UI) {
+ uiFiles.push_back(pDp);
+ continue;
+ }
+ if (target == LINUX) {
+ linuxFiles.push_back(pDp);
+ continue;
+ }
+
+ // Set the provider.
+ UiProtocol* pTarget = _all[target];
+ aboutToStart[target] = pTarget;
+ _all[target]->setProvider(pDp);
+
+ // Ensure all non-file actions happen after the tasks.
+ priorTargets.push_back(pTarget);
+ }
+
+ // Ensure we get the UI files then linux.
+ uiFiles.insert(uiFiles.end(), linuxFiles.begin(), linuxFiles.end());
+
+ // Use the map to create the ordered set of tasks to do.
+ _tasks.clear();
+ SwUpdateTargetEnum targetOrder[] = { HDFPGA, HD, DGFPGA, DG };
+ for (SwUpdateTargetEnum t : targetOrder) {
+ auto it = aboutToStart.find(t);
+ if (it != aboutToStart.end()) {
+ _tasks.push_back(it->second);
+ }
+ }
+ // Last is files.
+ _tasks.push_back(&_fileUpdate);
+ _fileUpdate.setup(uiFiles);
+
+ // Kick the first task off.
+ bool ok = true;
+ if (_tasks.size() > 0) {
+ ok = _tasks.front()->start();
+ }
+
+ return ok;
+}
+
+/*!
+ * \brief Is it completed?
+ *
+ * \return True if completed.
+ */
+bool UiSwUpdate::completedAll() {
+ bool rv = true;
+
+ std::lock_guard lock(_mutexApi);
+
+ for (auto it = _all.begin(); it != _all.end(); ++it) {
+ if (it->second->progress().inProgress) {
+ rv = false;
+ break;
+ }
+ }
+ return rv;
+}
+
+/*!
+ * \brief Abort update.
+ */
+void UiSwUpdate::abort() {
+
+ std::lock_guard lock(_mutexApi);
+
+ // Doing all causes things that "finished" to also be checked
+ // in case of any possible thread race condition.
+ for (auto it = _all.begin(); it != _all.end(); ++it) {
+ it->second->abort();
+ }
+}
+
+/*!
+ * \brief Current estimated progress.
+ *
+ * \return Vector of statuses.
+ */
+std::vector UiSwUpdate::progress() {
+
+ std::lock_guard lock(_mutexApi);
+ std::vector rv;
+
+ for (auto it = _all.begin(); it != _all.end(); ++it) {
+ rv.push_back(it->second->progress());
+ }
+
+ return rv;
+}
+
+/*!
+ * \brief Receive and check if matched.
+ *
+ * \param msgId Can message ID.
+ * \param pData Data pointer.
+ *
+ * \return True if message was consumed.
+ */
+bool UiSwUpdate::receive(uint16 msgId, uint8 * pData) {
+
+ bool rv = false;
+ for (auto it = _all.begin(); it != _all.end(); ++it) {
+ rv |= it->second->receive(msgId, pData);
+ }
+ return rv;
+}
+
+/*!
+ * \brief This chains updates together, as a protocol completes, it calls this.
+ *
+ * \param ok The task completed ok.
+ * \param pCompleted Which protocol completed.
+ */
+void UiSwUpdate::taskCompleted(bool ok, UiProtocol* pCompleted) {
+
+ // Tasks are run in order: HDFPGA, HD, DGFPGA, DG, Linux, UI.
+ if (!ok) {
+ this->abort();
+ _tasks.clear();
+ } else {
+ std::vector::iterator position = std::find(_tasks.begin(), _tasks.end(), pCompleted);
+ if (position != _tasks.end()) {
+ _tasks.erase(position);
+ }
+ if (_tasks.size() > 0) {
+ _tasks.front()->start();
+ }
+ }
+}
+
+} //namespace SwUpdate
+
Index: sources/update/UiSwUpdate.h
===================================================================
diff -u
--- sources/update/UiSwUpdate.h (revision 0)
+++ sources/update/UiSwUpdate.h (revision 20b370a54d2737831b307a0de82aec9e06e2b772)
@@ -0,0 +1,127 @@
+/*!
+ *
+ * Copyright (c) 2023 Diality Inc. - All Rights Reserved.
+ *
+ * \copyright
+ * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN
+ * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER.
+ *
+ * \file UiSwUpdate.h
+ * \author (last) Phil Braica
+ * \date (last) 23-Jan-2023
+ * \author (original) Phil Braica
+ * \date (original) 23-Jan-2023
+ *
+ */
+
+#ifndef UI_SW_UPDATE_H_
+#define UI_SW_UPDATE_H_
+
+#include "HalStdTypes.h"
+#include "MsgLink.h"
+#include "UpdateProtocol.h"
+#include "UiProtocol.h"
+#include "UiUpdateStatus.h"
+
+#include
+#include