23#include <mach-o/loader.h>
30const char *
const TAG =
"ota.host";
32constexpr size_t MAX_OTA_SIZE = 256u * 1024u * 1024u;
33constexpr size_t HEADER_PEEK_SIZE = 64;
35ssize_t read_header_(
const char *path, uint8_t *buf,
size_t len) {
36 int fd = ::open(path, O_RDONLY);
53ElfIdent parse_elf_(
const uint8_t *buf,
size_t len) {
55 if (
len < EI_NIDENT + 4)
57 if (buf[EI_MAG0] != ELFMAG0 || buf[EI_MAG1] != ELFMAG1 || buf[EI_MAG2] != ELFMAG2 || buf[EI_MAG3] != ELFMAG3)
59 out.ei_class = buf[EI_CLASS];
60 out.ei_data = buf[EI_DATA];
66 if (out.ei_data == ELFDATA2LSB) {
67 out.e_type = le16toh(
e_type);
69 }
else if (out.ei_data == ELFDATA2MSB) {
70 out.e_type = be16toh(
e_type);
79bool validate_elf_(
const char *staging_path,
const std::string &exe_path) {
80 uint8_t new_buf[HEADER_PEEK_SIZE];
81 uint8_t cur_buf[HEADER_PEEK_SIZE];
82 ssize_t new_n = read_header_(staging_path, new_buf,
sizeof(new_buf));
83 ssize_t cur_n = read_header_(exe_path.c_str(), cur_buf,
sizeof(cur_buf));
84 if (new_n <
static_cast<ssize_t>(EI_NIDENT + 4) || cur_n <
static_cast<ssize_t>(EI_NIDENT + 4)) {
85 ESP_LOGE(TAG,
"ELF header read failed");
88 ElfIdent new_id = parse_elf_(new_buf, new_n);
89 ElfIdent cur_id = parse_elf_(cur_buf, cur_n);
91 ESP_LOGE(TAG,
"Uploaded payload is not a valid ELF");
95 ESP_LOGE(TAG,
"Could not parse running exe ELF header");
98 if (new_id.ei_class != cur_id.ei_class) {
99 ESP_LOGE(TAG,
"ELF class mismatch (uploaded=%u, running=%u)", new_id.ei_class, cur_id.ei_class);
102 if (new_id.ei_data != cur_id.ei_data) {
103 ESP_LOGE(TAG,
"ELF endianness mismatch");
106 if (new_id.e_machine != cur_id.e_machine) {
107 ESP_LOGE(TAG,
"ELF e_machine mismatch (uploaded=0x%04x, running=0x%04x)", new_id.e_machine, cur_id.e_machine);
110 if (new_id.e_type != ET_EXEC && new_id.e_type != ET_DYN) {
111 ESP_LOGE(TAG,
"ELF e_type=%u is not executable", new_id.e_type);
125MachOIdent parse_macho_(
const uint8_t *buf,
size_t len) {
129 if (
len <
sizeof(
struct mach_header))
132 std::memcpy(&magic, buf,
sizeof(magic));
134 if (magic == MH_MAGIC || magic == MH_MAGIC_64) {
136 }
else if (magic == MH_CIGAM || magic == MH_CIGAM_64) {
141 struct mach_header hdr;
142 std::memcpy(&hdr, buf,
sizeof(hdr));
144 hdr.cputype = OSSwapInt32(hdr.cputype);
145 hdr.cpusubtype = OSSwapInt32(hdr.cpusubtype);
146 hdr.filetype = OSSwapInt32(hdr.filetype);
148 if (hdr.filetype != MH_EXECUTE)
150 out.cputype = hdr.cputype;
151 out.cpusubtype = hdr.cpusubtype;
156bool validate_macho_(
const char *staging_path,
const std::string &exe_path) {
157 uint8_t new_buf[HEADER_PEEK_SIZE];
158 uint8_t cur_buf[HEADER_PEEK_SIZE];
159 ssize_t new_n = read_header_(staging_path, new_buf,
sizeof(new_buf));
160 ssize_t cur_n = read_header_(exe_path.c_str(), cur_buf,
sizeof(cur_buf));
161 if (new_n <
static_cast<ssize_t>(
sizeof(
struct mach_header)) ||
162 cur_n <
static_cast<ssize_t>(
sizeof(
struct mach_header))) {
163 ESP_LOGE(TAG,
"Mach-O header read failed");
166 MachOIdent new_id = parse_macho_(new_buf, new_n);
167 MachOIdent cur_id = parse_macho_(cur_buf, cur_n);
169 ESP_LOGE(TAG,
"Uploaded payload is not a valid thin Mach-O executable");
173 ESP_LOGE(TAG,
"Could not parse running exe Mach-O header");
176 if (new_id.cputype != cur_id.cputype || new_id.cpusubtype != cur_id.cpusubtype) {
177 ESP_LOGE(TAG,
"Mach-O arch mismatch (uploaded=0x%x/0x%x, running=0x%x/0x%x)", new_id.cputype, new_id.cpusubtype,
178 cur_id.cputype, cur_id.cpusubtype);
185bool validate_executable_(
const char *staging_path,
const std::string &exe_path) {
187 return validate_elf_(staging_path, exe_path);
188#elif defined(__APPLE__)
189 return validate_macho_(staging_path, exe_path);
193 ESP_LOGE(TAG,
"Host OTA validation not implemented for this OS");
200std::unique_ptr<HostOTABackend>
make_ota_backend() {
return make_unique<HostOTABackend>(); }
206 if (image_size > MAX_OTA_SIZE) {
207 ESP_LOGE(TAG,
"Refusing OTA of size %zu (exceeds %zu)", image_size, MAX_OTA_SIZE);
213 ESP_LOGE(TAG,
"Could not resolve running executable path; cannot stage OTA");
222 this->
fd_ = ::open(this->
staging_path_.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0755);
224 ESP_LOGE(TAG,
"Open '%s' failed: %s", this->
staging_path_.c_str(), std::strerror(errno));
233 ESP_LOGD(TAG,
"OTA begin: staging=%s, size=%zu", this->
staging_path_.c_str(), image_size);
247 ESP_LOGE(TAG,
"Write past size limit (%zu)", limit);
251 size_t remaining =
len;
252 const uint8_t *p = data;
253 while (remaining > 0) {
258 ESP_LOGE(TAG,
"Write failed: %s", std::strerror(errno));
274 ESP_LOGE(TAG,
"OTA ended with no data written");
287 ESP_LOGE(TAG,
"MD5 mismatch");
293 if (::fsync(this->
fd_) != 0) {
294 ESP_LOGW(TAG,
"fsync failed: %s", std::strerror(errno));
299 if (!validate_executable_(this->
staging_path_.c_str(), this->final_path_)) {
306 ESP_LOGW(TAG,
"chmod failed: %s", std::strerror(errno));
309 if (::rename(this->
staging_path_.c_str(), this->final_path_.c_str()) != 0) {
310 ESP_LOGE(TAG,
"rename '%s' -> '%s' failed: %s", this->
staging_path_.c_str(), this->final_path_.c_str(),
311 std::strerror(errno));
320 ESP_LOGI(TAG,
"OTA staged at %s; will re-exec on reboot", this->
final_path_.c_str());
325 if (this->
fd_ >= 0) {
bool equals_bytes(const uint8_t *expected)
Compare the hash against a provided byte-encoded hash.
void calculate() override
Compute the digest, based on the provided data.
void add(const uint8_t *data, size_t len) override
Add bytes of data for the digest.
void init() override
Initialize a new MD5 digest computation.
OTAResponseTypes write(uint8_t *data, size_t len)
OTAResponseTypes begin(size_t image_size, OTAType ota_type=OTA_TYPE_UPDATE_APP)
uint8_t expected_md5_[16]
void set_update_md5(const char *md5)
std::string staging_path_
const std::string & get_exe_path()
Absolute path to running exe (resolved at startup); empty on failure.
void arm_reexec(const std::string &path)
Arm an execv on the next arch_restart(). Pass empty to disarm.
@ OTA_RESPONSE_ERROR_MD5_MISMATCH
@ OTA_RESPONSE_ERROR_WRITING_FLASH
@ OTA_RESPONSE_ERROR_UNSUPPORTED_OTA_TYPE
@ OTA_RESPONSE_ERROR_UPDATE_END
@ OTA_RESPONSE_ERROR_UPDATE_PREPARE
std::unique_ptr< ArduinoLibreTinyOTABackend > make_ota_backend()
size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count)
Parse bytes from a hex-encoded string into a byte array.