Last active
December 11, 2023 21:00
-
-
Save noahp/379e9efe5432341a6841f2a7fd951bdf to your computer and use it in GitHub Desktop.
Example bundle manifest reader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdint.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
// supported manifest version, in case the structure needs to change | |
#define BUNDLE_MANIFEST_VERSION 1 | |
enum bundle_manifest_entry_type { | |
BUNDLE_MANIFEST_ENTRY_TYPE_MAIN_FW, | |
BUNDLE_MANIFEST_ENTRY_TYPE_MODEM_FW, | |
BUNDLE_MANIFEST_ENTRY_TYPE_MAX, | |
}; | |
// note: set to packed for consistent field spacing compared to | |
struct __attribute__((packed)) bundle_manifest { | |
// should be BUNDLE_MANIFEST_VERSION | |
uint8_t version; | |
// number of entries in the manifest. should always be = | |
// BUNDLE_MANIFEST_ENTRY_TYPE_MAX | |
uint8_t num_entries; | |
// crc32 of the entire manifest | |
uint32_t crc; | |
struct bundle_manifest_entry { | |
// should match enum bundle_manifest_entry_type | |
uint8_t type; | |
struct version { | |
uint8_t major; | |
uint8_t minor; | |
uint8_t patch; | |
} version; | |
// offset from the start of the bundle | |
uint32_t offset; | |
// size in bytes | |
uint32_t size; | |
} entries[BUNDLE_MANIFEST_ENTRY_TYPE_MAX]; | |
}; | |
int fetch_bundle_manifest(struct bundle_manifest *manifest, const int sock_fd) { | |
// fetch the bundle manifest from the remote server | |
// return 0 on success, -1 on failure | |
// on success, the manifest will be stored in the manifest struct | |
int rv = read(sock_fd, manifest, sizeof(struct bundle_manifest)); | |
if (rv != sizeof(struct bundle_manifest)) { | |
LOG_ERROR("unexpected manifest size when fetching: %d", rv); | |
return -1; | |
} | |
// check the version | |
if (manifest->version != BUNDLE_MANIFEST_VERSION) { | |
LOG_ERROR("unexpected manifest version: %d / %d", BUNDLE_MANIFEST_VERSION, | |
manifest->version); | |
return -1; | |
} | |
// verify manifest integrity | |
const uint32_t expected_crc32 = manifest->crc; | |
manifest->crc = 0; | |
const uint32_t computed_crc32 = crc32(manifest, sizeof(*manifest)); | |
if (expected_crc32 != computed_crc32) { | |
LOG_ERROR("manifest has bad crc32: 0x%x / 0x%x", expected_crc32, | |
computed_crc32); | |
return -1; | |
} | |
// check the entry count | |
if (manifest->num_entries != BUNDLE_MANIFEST_ENTRY_TYPE_MAX) { | |
LOG_ERROR("manifest invalid entry count: %d / %d", | |
BUNDLE_MANIFEST_ENTRY_TYPE_MAX, manifest->num_entries); | |
return -1; | |
} | |
// confirm there's a matching entry for each type. this also enforces ordering | |
// of entries, which is not mandatory for the implementation but makes things | |
// simpler. | |
for (int i = 0; i < BUNDLE_MANIFEST_ENTRY_TYPE_MAX; i++) { | |
if (manifest->entries[i].type != i) { | |
LOG_ERROR("mismatched entry: %d/%d", i, manifest->entries[i].type); | |
return -1; | |
} | |
} | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// this includes a sample main for testing the manifest loader | |
#include <fcntl.h> | |
#include <stdint.h> | |
// use zlib crc32 | |
#include <zlib.h> | |
#define LOG_ERROR(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__) | |
// zlib crc32 wrapper for less confusing example code | |
uint32_t crc32_(const void *buf, size_t len) { | |
return crc32(0, (const Bytef *)buf, len); | |
} | |
#define crc32 crc32_ | |
// core implementation below | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
// supported manifest version, in case the structure needs to change | |
#define BUNDLE_MANIFEST_VERSION 1 | |
enum bundle_manifest_entry_type { | |
BUNDLE_MANIFEST_ENTRY_TYPE_MAIN_FW, | |
BUNDLE_MANIFEST_ENTRY_TYPE_MODEM_FW, | |
BUNDLE_MANIFEST_ENTRY_TYPE_MAX, | |
}; | |
// note: set to packed for consistent field spacing compared to | |
struct __attribute__((packed)) bundle_manifest { | |
// should be BUNDLE_MANIFEST_VERSION | |
uint8_t version; | |
// number of entries in the manifest. should always be = | |
// BUNDLE_MANIFEST_ENTRY_TYPE_MAX | |
uint8_t num_entries; | |
// crc32 of the entire manifest | |
uint32_t crc; | |
struct bundle_manifest_entry { | |
// should match enum bundle_manifest_entry_type | |
uint8_t type; | |
struct version { | |
uint8_t major; | |
uint8_t minor; | |
uint8_t patch; | |
} version; | |
// offset from the start of the bundle | |
uint32_t offset; | |
// size in bytes | |
uint32_t size; | |
} entries[BUNDLE_MANIFEST_ENTRY_TYPE_MAX]; | |
}; | |
int fetch_bundle_manifest(struct bundle_manifest *manifest, const int sock_fd) { | |
// fetch the bundle manifest from the remote server | |
// return 0 on success, -1 on failure | |
// on success, the manifest will be stored in the manifest struct | |
int rv = read(sock_fd, manifest, sizeof(struct bundle_manifest)); | |
if (rv != sizeof(struct bundle_manifest)) { | |
LOG_ERROR("unexpected manifest size when fetching: %d", rv); | |
return -1; | |
} | |
// check the version | |
if (manifest->version != BUNDLE_MANIFEST_VERSION) { | |
LOG_ERROR("unexpected manifest version: %d / %d", BUNDLE_MANIFEST_VERSION, | |
manifest->version); | |
return -1; | |
} | |
// verify manifest integrity | |
const uint32_t expected_crc32 = manifest->crc; | |
manifest->crc = 0; | |
const uint32_t computed_crc32 = crc32(manifest, sizeof(*manifest)); | |
if (expected_crc32 != computed_crc32) { | |
LOG_ERROR("manifest has bad crc32: 0x%x / 0x%x", expected_crc32, | |
computed_crc32); | |
return -1; | |
} | |
// check the entry count | |
if (manifest->num_entries != BUNDLE_MANIFEST_ENTRY_TYPE_MAX) { | |
LOG_ERROR("manifest invalid entry count: %d / %d", | |
BUNDLE_MANIFEST_ENTRY_TYPE_MAX, manifest->num_entries); | |
return -1; | |
} | |
// confirm there's a matching entry for each type. this also enforces ordering | |
// of entries, which is not mandatory for the implementation but makes things | |
// simpler. | |
for (int i = 0; i < BUNDLE_MANIFEST_ENTRY_TYPE_MAX; i++) { | |
if (manifest->entries[i].type != i) { | |
LOG_ERROR("mismatched entry: %d/%d", i, manifest->entries[i].type); | |
return -1; | |
} | |
} | |
return 0; | |
} | |
// example test main below | |
int main(int argc, char **argv) { | |
if (argc != 3) { | |
LOG_ERROR("usage: %s [r|w] <bundle>", argv[0]); | |
return 1; | |
} | |
const char *mode = argv[1]; | |
const char *bundle_path = argv[2]; | |
// if writing a bundle, generate an example manifest and write it to the file | |
if (mode[0] == 'w') { | |
struct bundle_manifest example = { | |
.version = BUNDLE_MANIFEST_VERSION, | |
.num_entries = BUNDLE_MANIFEST_ENTRY_TYPE_MAX, | |
.crc = 0, | |
.entries = | |
{ | |
{ | |
.type = BUNDLE_MANIFEST_ENTRY_TYPE_MAIN_FW, | |
.version = {.major = 1, .minor = 5, .patch = 6}, | |
.offset = 0, | |
.size = 0x1000, | |
}, | |
{ | |
.type = BUNDLE_MANIFEST_ENTRY_TYPE_MODEM_FW, | |
.version = {.major = 2, .minor = 3, .patch = 4}, | |
.offset = 0x1000, | |
.size = 0x1000, | |
}, | |
}, | |
}; | |
// set the crc | |
example.crc = crc32(&example, sizeof(example)); | |
// now write it to the file | |
FILE *f = fopen(bundle_path, "wb"); | |
if (!f) { | |
LOG_ERROR("failed to open %s for writing", bundle_path); | |
return 1; | |
} | |
if (fwrite(&example, sizeof(example), 1, f) != 1) { | |
LOG_ERROR("failed to write manifest to %s", bundle_path); | |
return 1; | |
} | |
fclose(f); | |
} else if (mode[0] == 'r') { | |
// load and pretty-print the manifest | |
struct bundle_manifest manifest; | |
int fd = open(bundle_path, O_RDONLY); | |
if (fd < 0) { | |
LOG_ERROR("failed to open %s for reading", bundle_path); | |
return 1; | |
} | |
int rv = fetch_bundle_manifest(&manifest, fd); | |
if (rv != 0) { | |
LOG_ERROR("failed to fetch manifest from %s", bundle_path); | |
return 1; | |
} | |
printf("version: %d\n", manifest.version); | |
printf("num_entries: %d\n", manifest.num_entries); | |
printf("crc: 0x%x\n", manifest.crc); | |
for (int i = 0; i < BUNDLE_MANIFEST_ENTRY_TYPE_MAX; i++) { | |
printf("entry %d:\n", i); | |
printf(" type: %d\n", manifest.entries[i].type); | |
printf(" version: %d.%d.%d\n", manifest.entries[i].version.major, | |
manifest.entries[i].version.minor, | |
manifest.entries[i].version.patch); | |
printf(" offset: 0x%x\n", manifest.entries[i].offset); | |
printf(" size: 0x%x\n", manifest.entries[i].size); | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment