Skip to content

Instantly share code, notes, and snippets.

@noahp
Last active December 11, 2023 21:00
Show Gist options
  • Save noahp/379e9efe5432341a6841f2a7fd951bdf to your computer and use it in GitHub Desktop.
Save noahp/379e9efe5432341a6841f2a7fd951bdf to your computer and use it in GitHub Desktop.
Example bundle manifest reader
#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 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