// assignment_request.cpp — see assignment_request.h. Pure: standard library only. #include "assignment_request.h" #include #include namespace reasampler { namespace { constexpr const char* kMagic = "rsassign1"; // Append one length-prefixed field: ':' . Mirror of // provenance's putField so the two seams share one wire idiom. void putField(std::string& out, const std::string& field) { out += std::to_string(field.size()); out += ':'; out += field; } // Cursor over the encoded string. All reads are bounds-checked; a short read fails // the whole parse (ok_ latches false). Mirror of provenance's Cursor, trimmed to the // three field kinds this record needs. class Cursor { public: explicit Cursor(const std::string& s) : s_(s) {} bool ok() const { return ok_; } bool atEnd() const { return pos_ >= s_.size(); } // Reads one length-prefixed field into `out`. Fails on a missing ':', an empty or // non-numeric length, a length that overflows SIZE_MAX, or a length that runs past // the end. The digit count is capped at 20 (the decimal width of SIZE_MAX on a // 64-bit host) so a crafted 200-digit length cannot accumulate past SIZE_MAX via // repeated multiply. "never UB" promise from the header is upheld here. bool field(std::string& out) { if (!ok_) return false; const std::size_t colon = s_.find(':', pos_); if (colon == std::string::npos) return fail(); if (colon == pos_) return fail(); // empty length token // Cap: SIZE_MAX fits in at most 20 decimal digits; a longer run is bogus. if (colon - pos_ > 20u) return fail(); std::size_t len = 0; for (std::size_t i = pos_; i < colon; ++i) { const char c = s_[i]; if (c < '0' || c > '9') return fail(); const std::size_t digit = static_cast(c - '0'); // Overflow guard: if len would exceed SIZE_MAX after multiply+add, fail. if (len > (std::numeric_limits::max() - digit) / 10u) return fail(); len = len * 10u + digit; } const std::size_t start = colon + 1; // Guard: start may equal s_.size() (empty remainder), in which case only len==0 // is valid; start > s_.size() cannot happen (colon < s_.size() by find()). // Use subtraction-first form to avoid start+len wrapping on a huge len. if (start > s_.size() || len > s_.size() - start) return fail(); out.assign(s_, start, len); pos_ = start + len; return true; } // Reads a length-prefixed field and parses it as a signed 64-bit decimal (an // optional leading '-'). Fails on empty, non-digit, trailing bytes, or a value // that would overflow INT64_MAX / underflow INT64_MIN. The digit count is capped // at 19 (the decimal width of INT64_MAX, plus 1 for the optional sign = 20 // characters maximum) so a crafted 21-digit field cannot accumulate UB. "never UB" // promise from the header is upheld: all arithmetic is done on positive digits // and capped before applying the sign. bool fieldInt64(std::int64_t& out) { std::string f; if (!field(f)) return false; if (f.empty()) return fail(); std::size_t i = 0; bool neg = false; if (f[0] == '-') { neg = true; i = 1; if (f.size() == 1) return fail(); // bare "-" } // Cap at 19 digits (INT64_MAX = 9223372036854775807 — 19 digits). A 20-digit // positive value would overflow INT64_MAX; a 20-digit negative might be valid // (INT64_MIN = -9223372036854775808) but we conservatively reject it too: the // generation field is a unix timestamp, never near INT64 limits in practice. if (f.size() - i > 19u) return fail(); std::int64_t v = 0; for (; i < f.size(); ++i) { const char c = f[i]; if (c < '0' || c > '9') return fail(); const std::int64_t digit = static_cast(c - '0'); // Overflow guard: v * 10 + digit must not exceed INT64_MAX. if (v > (std::numeric_limits::max() - digit) / 10) return fail(); v = v * 10 + digit; } out = neg ? -v : v; return true; } // Consumes an exact literal at the cursor (the magic tag). Fails if absent. bool literal(const char* lit) { if (!ok_) return false; std::size_t i = 0; for (; lit[i] != '\0'; ++i) { if (pos_ + i >= s_.size() || s_[pos_ + i] != lit[i]) return fail(); } pos_ += i; return true; } private: bool fail() { ok_ = false; return false; } const std::string& s_; std::size_t pos_ = 0; bool ok_ = true; }; } // namespace std::string encodeAssignmentRequest(const AssignmentRequest& req) { std::string out = kMagic; putField(out, req.bankId); putField(out, req.sampleId); putField(out, std::to_string(req.generation)); return out; } std::optional decodeAssignmentRequest(const std::string& wire) { Cursor cur(wire); if (!cur.literal(kMagic)) return std::nullopt; AssignmentRequest req; if (!cur.field(req.bankId)) return std::nullopt; if (!cur.field(req.sampleId)) return std::nullopt; if (!cur.fieldInt64(req.generation)) return std::nullopt; // Reject trailing garbage: a well-formed value ends exactly at the last field. if (!cur.ok() || !cur.atEnd()) return std::nullopt; return req; } } // namespace reasampler