Viewing is open to everyone. Downloading and starring need a GitHub sign-in.
Sign in with GitHub
api_codec.inc
v0.1.0-build.85
Latest
Commit ede38f5
Sign in with GitHub to download. Each download is recorded with your GitHub username.
| 1 | // Public C ABI: codec functions. Included after core_internal.inc. |
| 2 | |
| 3 | extern "C" { |
| 4 | |
| 5 | uint32_t ckey_get_abi_version(void) { return CKEY_ABI_VERSION; } |
| 6 | |
| 7 | const char *ckey_get_software_version(void) { return CKEY_SOFTWARE_VERSION; } |
| 8 | |
| 9 | ckey_status ckey_encode_locator(const uint8_t *payload, size_t payload_len, |
| 10 | ckey_symbol *out, ckey_diagnostics *diag) { |
| 11 | if (!payload || !out) { set_status(diag, CKEY_INVALID_ARGUMENT, "payload and output are required"); return CKEY_INVALID_ARGUMENT; } |
| 12 | if (payload_len != CKEY_LOCATOR_PAYLOAD_BYTES) { set_status(diag, CKEY_INVALID_ARGUMENT, "locator payload must be exactly 16 bytes"); return CKEY_INVALID_ARGUMENT; } |
| 13 | |
| 14 | std::array<uint8_t, kCodewordBytes> cw{}; |
| 15 | // Frame: magic C7 4B, current format, profile 01, length u16be, 16 payload |
| 16 | // bytes, CRC-32C over the first 22 bytes, followed by 51 RS parity bytes. |
| 17 | cw[0] = 0xc7; cw[1] = 0x4b; cw[2] = CKEY_FORMAT_VERSION; cw[3] = 1; cw[4] = 0; cw[5] = 16; |
| 18 | std::memcpy(cw.data() + 6, payload, 16); |
| 19 | const uint32_t crc = crc32c(cw.data(), 22); |
| 20 | cw[22] = static_cast<uint8_t>(crc >> 24); cw[23] = static_cast<uint8_t>(crc >> 16); |
| 21 | cw[24] = static_cast<uint8_t>(crc >> 8); cw[25] = static_cast<uint8_t>(crc); |
| 22 | const auto gen = rs_generator(); |
| 23 | std::array<uint8_t, kCodewordBytes> work = cw; |
| 24 | for (size_t i = 0; i < kDataBytes; ++i) { |
| 25 | const uint8_t factor = work[i]; |
| 26 | for (size_t j = 0; j <= kParityBytes; ++j) work[i + j] ^= gf.mul(factor, gen[j]); |
| 27 | } |
| 28 | std::memcpy(cw.data() + kDataBytes, work.data() + kDataBytes, kParityBytes); |
| 29 | std::memset(out, 0, sizeof(*out)); out->abi_version = CKEY_ABI_VERSION; out->side = kSide; |
| 30 | std::memcpy(out->codeword, cw.data(), cw.size()); |
| 31 | set_status(diag, CKEY_OK, "encoded Profile 1 locator symbol"); |
| 32 | return CKEY_OK; |
| 33 | } |
| 34 | |
| 35 | ckey_status ckey_decode_locator(const uint8_t *codeword, size_t codeword_len, |
| 36 | uint8_t *payload, size_t out_capacity, |
| 37 | ckey_diagnostics *diag) { |
| 38 | if (!codeword || !payload) { set_status(diag, CKEY_INVALID_ARGUMENT, "codeword and payload output are required"); return CKEY_INVALID_ARGUMENT; } |
| 39 | if (codeword_len != kCodewordBytes || out_capacity < CKEY_LOCATOR_PAYLOAD_BYTES) { set_status(diag, CKEY_INVALID_ARGUMENT, "locator decode requires 77 bytes and a 16-byte output buffer"); return CKEY_INVALID_ARGUMENT; } |
| 40 | std::array<uint8_t,kCodewordBytes> cw{}; std::memcpy(cw.data(), codeword, cw.size()); |
| 41 | uint32_t corrected = 0; |
| 42 | if (!rs_correct(cw, corrected)) { set_status(diag, CKEY_UNCORRECTABLE, "Reed-Solomon correction failed"); return CKEY_UNCORRECTABLE; } |
| 43 | // Formats 8 and 11--13 remain readable for historic camera fixtures. |
| 44 | // Encoder output is the current format; the newer inset-finder geometry |
| 45 | // is unambiguously declared by format 11. |
| 46 | if (cw[0] != 0xc7 || cw[1] != 0x4b || (cw[2] != 8u && cw[2] != 11u && cw[2] != 12u && cw[2] != 13u && cw[2] != CKEY_FORMAT_VERSION) || cw[3] != 1 || cw[4] != 0 || cw[5] != 16) { |
| 47 | set_status(diag, CKEY_INTEGRITY_FAILURE, "corrected frame has invalid magic, version, profile, or length"); return CKEY_INTEGRITY_FAILURE; |
| 48 | } |
| 49 | const uint32_t expected = (static_cast<uint32_t>(cw[22]) << 24) | (static_cast<uint32_t>(cw[23]) << 16) | (static_cast<uint32_t>(cw[24]) << 8) | cw[25]; |
| 50 | if (crc32c(cw.data(), 22) != expected) { set_status(diag, CKEY_INTEGRITY_FAILURE, "CRC-32C validation failed"); return CKEY_INTEGRITY_FAILURE; } |
| 51 | std::memcpy(payload, cw.data() + 6, CKEY_LOCATOR_PAYLOAD_BYTES); |
| 52 | set_status(diag, CKEY_OK, corrected ? "decoded locator; Reed-Solomon symbols corrected" : "decoded locator; no correction needed"); |
| 53 | if (diag) diag->corrected_symbols = corrected; |
| 54 | return CKEY_OK; |
| 55 | } |
| 56 | |
| 57 | ckey_status ckey_encode_text(const uint8_t *payload, size_t payload_len, |
| 58 | ckey_text_symbol *out, ckey_diagnostics *diag) { |
| 59 | if (!payload || !out) { set_status(diag, CKEY_INVALID_ARGUMENT, "text payload and output are required"); return CKEY_INVALID_ARGUMENT; } |
| 60 | if (payload_len > CKEY_TEXT_PAYLOAD_MAX_BYTES) { set_status(diag, CKEY_INVALID_ARGUMENT, "text profile payload exceeds 200 bytes"); return CKEY_INVALID_ARGUMENT; } |
| 61 | std::memset(out, 0, sizeof(*out)); |
| 62 | out->abi_version = CKEY_ABI_VERSION; out->side = kSide; out->payload_length = static_cast<uint32_t>(payload_len); |
| 63 | // Frame: magic, current format, profile 2, u16be payload length, payload, |
| 64 | // CRC-32C, then deterministic SplitMix32 visual fill. All 210 data bytes |
| 65 | // remain protected by the full RS(255,210) code. |
| 66 | std::vector<uint8_t> codeword(kTextCodewordBytes, 0); |
| 67 | codeword[0] = 0xc7; codeword[1] = 0x4b; codeword[2] = CKEY_TEXT_FORMAT_VERSION; codeword[3] = 2; |
| 68 | codeword[4] = static_cast<uint8_t>(payload_len >> 8); codeword[5] = static_cast<uint8_t>(payload_len); |
| 69 | std::memcpy(codeword.data() + 6, payload, payload_len); |
| 70 | const uint32_t crc = crc32c(codeword.data(), 6 + payload_len); |
| 71 | const size_t crc_index = 6 + payload_len; |
| 72 | codeword[crc_index] = static_cast<uint8_t>(crc >> 24); codeword[crc_index + 1] = static_cast<uint8_t>(crc >> 16); |
| 73 | codeword[crc_index + 2] = static_cast<uint8_t>(crc >> 8); codeword[crc_index + 3] = static_cast<uint8_t>(crc); |
| 74 | fill_padding(codeword.data(), crc_index + 4, kTextDataBytes, crc_index + 4); |
| 75 | rs_encode_generic(codeword, kTextDataBytes, kTextParityBytes); |
| 76 | std::memcpy(out->codeword, codeword.data(), codeword.size()); |
| 77 | set_status(diag, CKEY_OK, "encoded Profile 2 text symbol"); |
| 78 | return CKEY_OK; |
| 79 | } |
| 80 | |
| 81 | ckey_status ckey_decode_text(const uint8_t *codeword, size_t codeword_len, |
| 82 | uint8_t *payload, size_t out_capacity, size_t *out_payload_len, |
| 83 | ckey_diagnostics *diag) { |
| 84 | if (!codeword || !payload || !out_payload_len || codeword_len != kTextCodewordBytes) { |
| 85 | set_status(diag, CKEY_INVALID_ARGUMENT, "text decode requires a 255-byte codeword and output length"); return CKEY_INVALID_ARGUMENT; |
| 86 | } |
| 87 | std::vector<uint8_t> corrected(codeword, codeword + codeword_len); |
| 88 | uint32_t corrections = 0; |
| 89 | if (!rs_correct_generic(corrected, kTextParityBytes, corrections)) { set_status(diag, CKEY_UNCORRECTABLE, "text Reed-Solomon correction failed"); return CKEY_UNCORRECTABLE; } |
| 90 | if (corrected[0] != 0xc7 || corrected[1] != 0x4b || |
| 91 | !is_text_format(corrected[2]) || corrected[3] != 2) { |
| 92 | set_status(diag, CKEY_INTEGRITY_FAILURE, "text frame has invalid magic, format, or profile"); return CKEY_INTEGRITY_FAILURE; |
| 93 | } |
| 94 | const size_t length = (static_cast<size_t>(corrected[4]) << 8) | corrected[5]; |
| 95 | if (length > CKEY_TEXT_PAYLOAD_MAX_BYTES || 6 + length + 4 > kTextDataBytes) { |
| 96 | set_status(diag, CKEY_INTEGRITY_FAILURE, "text frame declares a length beyond the profile"); return CKEY_INTEGRITY_FAILURE; |
| 97 | } |
| 98 | if (out_capacity < length) { |
| 99 | set_status(diag, CKEY_INSUFFICIENT_BUFFER, "text payload exceeds output capacity"); return CKEY_INSUFFICIENT_BUFFER; |
| 100 | } |
| 101 | const size_t crc_index = 6 + length; |
| 102 | const uint32_t expected = (static_cast<uint32_t>(corrected[crc_index]) << 24) | |
| 103 | (static_cast<uint32_t>(corrected[crc_index + 1]) << 16) | |
| 104 | (static_cast<uint32_t>(corrected[crc_index + 2]) << 8) | corrected[crc_index + 3]; |
| 105 | if (crc32c(corrected.data(), crc_index) != expected) { set_status(diag, CKEY_INTEGRITY_FAILURE, "text CRC-32C validation failed"); return CKEY_INTEGRITY_FAILURE; } |
| 106 | // Formats 0.23 and later are checked against the normative SplitMix32 fill. |
| 107 | if ((corrected[2] == 23u || corrected[2] == CKEY_TEXT_FORMAT_VERSION) && |
| 108 | !padding_matches(corrected.data(), crc_index + 4, kTextDataBytes, crc_index + 4)) { |
| 109 | set_status(diag, CKEY_INTEGRITY_FAILURE, "text visual-fill validation failed"); return CKEY_INTEGRITY_FAILURE; |
| 110 | } |
| 111 | std::memcpy(payload, corrected.data() + 6, length); *out_payload_len = length; |
| 112 | set_status(diag, CKEY_OK, corrections ? "decoded text profile; Reed-Solomon symbols corrected" : "decoded text profile; no correction needed"); |
| 113 | if (diag) diag->corrected_symbols = corrections; |
| 114 | return CKEY_OK; |
| 115 | } |
| 116 | |
| 117 | ckey_status ckey_encode_extended(const uint8_t *payload, size_t payload_len, |
| 118 | ckey_extended_symbol *out, ckey_diagnostics *diag) { |
| 119 | return ckey_encode_extended_tiered(payload, payload_len, CKEY_EXTENDED_TIER_AUTO, out, diag); |
| 120 | } |
| 121 | |
| 122 | ckey_status ckey_encode_extended_tiered(const uint8_t *payload, size_t payload_len, |
| 123 | ckey_extended_tier requested_tier, |
| 124 | ckey_extended_symbol *out, ckey_diagnostics *diag) { |
| 125 | if (!payload || !out) { set_status(diag, CKEY_INVALID_ARGUMENT, "extended payload and output are required"); return CKEY_INVALID_ARGUMENT; } |
| 126 | if (payload_len == 0 || payload_len > CKEY_EXTENDED_PAYLOAD_MAX_BYTES) { set_status(diag, CKEY_INVALID_ARGUMENT, "extended profile payload must contain 1 to 2048 bytes"); return CKEY_INVALID_ARGUMENT; } |
| 127 | const size_t tier_index = extended_tier_index(requested_tier, payload_len); |
| 128 | if (tier_index == kExtendedTierCapacities.size() || payload_len > kExtendedTierCapacities[tier_index]) { |
| 129 | set_status(diag, CKEY_INVALID_ARGUMENT, "payload does not fit the selected extended capacity tier"); return CKEY_INVALID_ARGUMENT; |
| 130 | } |
| 131 | const size_t data_bytes = (11 + kExtendedTierCapacities[tier_index] + kExtendedBlocks - 1) / kExtendedBlocks; |
| 132 | const size_t parity_bytes = kExtendedBlockBytes - data_bytes; |
| 133 | std::vector<uint8_t> framed(kExtendedBlocks * data_bytes, 0); |
| 134 | framed[0]=0xc7; framed[1]=0x4b; framed[2]=CKEY_EXTENDED_FORMAT_VERSION; framed[3]=3; |
| 135 | framed[4]=static_cast<uint8_t>(tier_index + 1); |
| 136 | framed[5]=static_cast<uint8_t>(payload_len >> 8); framed[6]=static_cast<uint8_t>(payload_len); |
| 137 | std::memcpy(framed.data()+7,payload,payload_len); |
| 138 | const size_t crc_index=7+payload_len; const uint32_t crc=crc32c(framed.data(),crc_index); |
| 139 | framed[crc_index]=static_cast<uint8_t>(crc>>24); framed[crc_index+1]=static_cast<uint8_t>(crc>>16); |
| 140 | framed[crc_index+2]=static_cast<uint8_t>(crc>>8); framed[crc_index+3]=static_cast<uint8_t>(crc); |
| 141 | fill_padding(framed.data(), crc_index + 4, framed.size(), crc_index + 4); |
| 142 | std::array<std::array<uint8_t,kExtendedBlockBytes>,kExtendedBlocks> blocks{}; |
| 143 | for (size_t block=0;block<kExtendedBlocks;++block) { |
| 144 | std::copy_n(framed.data()+block*data_bytes,data_bytes,blocks[block].begin()); |
| 145 | std::vector<uint8_t> work(blocks[block].begin(),blocks[block].end()); |
| 146 | rs_encode_generic(work,data_bytes,parity_bytes); |
| 147 | std::copy(work.begin(),work.end(),blocks[block].begin()); |
| 148 | } |
| 149 | std::memset(out,0,sizeof(*out)); out->abi_version=CKEY_ABI_VERSION; out->side=kExtendedSide; out->payload_length=static_cast<uint32_t>(payload_len); |
| 150 | // Byte interleaving maps local optical damage across all independent blocks. |
| 151 | for (size_t byte=0;byte<kExtendedBlockBytes;++byte) for (size_t block=0;block<kExtendedBlocks;++block) |
| 152 | out->codeword[byte*kExtendedBlocks+block]=blocks[block][byte]; |
| 153 | char message[96]; std::snprintf(message, sizeof(message), "encoded profile 3 tier %zu; RS(255,%zu) x 10", kExtendedTierCapacities[tier_index], data_bytes); |
| 154 | set_status(diag,CKEY_OK,message); return CKEY_OK; |
| 155 | } |
| 156 | |
| 157 | ckey_status ckey_decode_extended(const uint8_t *codeword, size_t codeword_len, |
| 158 | uint8_t *out_payload, size_t out_capacity, size_t *out_payload_len, |
| 159 | ckey_diagnostics *diag) { |
| 160 | if (!codeword || !out_payload || !out_payload_len || codeword_len != kExtendedCodewordBytes) { set_status(diag,CKEY_INVALID_ARGUMENT,"extended decode requires a 2550-byte codeword and output length"); return CKEY_INVALID_ARGUMENT; } |
| 161 | const auto attempt = [&](size_t data_bytes, size_t parity_bytes, bool tiered) -> ckey_status { |
| 162 | std::vector<uint8_t> framed(kExtendedBlocks * data_bytes, 0); |
| 163 | uint32_t corrections = 0; |
| 164 | for (size_t block=0; block<kExtendedBlocks; ++block) { |
| 165 | std::vector<uint8_t> work(kExtendedBlockBytes); |
| 166 | for (size_t byte=0; byte<kExtendedBlockBytes; ++byte) |
| 167 | work[byte] = codeword[byte*kExtendedBlocks+block]; |
| 168 | uint32_t fixed=0; |
| 169 | if (!rs_correct_generic(work, parity_bytes, fixed)) return CKEY_UNCORRECTABLE; |
| 170 | corrections += fixed; |
| 171 | std::copy_n(work.begin(), data_bytes, framed.data()+block*data_bytes); |
| 172 | } |
| 173 | if (framed[0]!=0xc7 || framed[1]!=0x4b || framed[3]!=3) return CKEY_INTEGRITY_FAILURE; |
| 174 | size_t length=0, payload_offset=0, crc_index=0; |
| 175 | if (tiered) { |
| 176 | if (!is_tiered_extended_format(framed[2]) || framed[4] == 0 || framed[4] > kExtendedTierCapacities.size()) return CKEY_INTEGRITY_FAILURE; |
| 177 | const size_t selected = framed[4]-1; |
| 178 | if (data_bytes != (11+kExtendedTierCapacities[selected]+kExtendedBlocks-1)/kExtendedBlocks) return CKEY_INTEGRITY_FAILURE; |
| 179 | length=(static_cast<size_t>(framed[5])<<8)|framed[6]; payload_offset=7; crc_index=payload_offset+length; |
| 180 | if (length==0 || length>kExtendedTierCapacities[selected] || crc_index+4>framed.size()) return CKEY_INTEGRITY_FAILURE; |
| 181 | } else { |
| 182 | if (framed[2]!=10u && framed[2]!=16u) return CKEY_INTEGRITY_FAILURE; |
| 183 | length=(static_cast<size_t>(framed[4])<<8)|framed[5]; payload_offset=6; crc_index=payload_offset+length; |
| 184 | if (length==0 || length>CKEY_EXTENDED_PAYLOAD_MAX_BYTES || crc_index+4>framed.size()) return CKEY_INTEGRITY_FAILURE; |
| 185 | } |
| 186 | const uint32_t expected=(static_cast<uint32_t>(framed[crc_index])<<24)|(static_cast<uint32_t>(framed[crc_index+1])<<16)|(static_cast<uint32_t>(framed[crc_index+2])<<8)|framed[crc_index+3]; |
| 187 | if (crc32c(framed.data(),crc_index)!=expected) return CKEY_INTEGRITY_FAILURE; |
| 188 | if (tiered && !padding_matches(framed.data(),crc_index+4,framed.size(),crc_index+4)) return CKEY_INTEGRITY_FAILURE; |
| 189 | if (out_capacity<length) { |
| 190 | set_status(diag,CKEY_INSUFFICIENT_BUFFER,"extended output buffer is smaller than decoded payload"); |
| 191 | return CKEY_INSUFFICIENT_BUFFER; |
| 192 | } |
| 193 | std::memcpy(out_payload,framed.data()+payload_offset,length); *out_payload_len=length; |
| 194 | set_status(diag,CKEY_OK,corrections ? "decoded extended profile; Reed-Solomon symbols corrected" : "decoded extended profile; no correction needed"); |
| 195 | if (diag) diag->corrected_symbols=corrections; |
| 196 | return CKEY_OK; |
| 197 | }; |
| 198 | |
| 199 | // Preserve the original Profile 3 RS(255,223) frame reader. The format and |
| 200 | // profile bytes are the third and fourth data bytes in block zero. Checking |
| 201 | // them before trying every new parity split avoids doing eight full RS |
| 202 | // decodes for an unrelated square candidate in the live camera path. |
| 203 | const bool tier_hint_valid = codeword[40] > 0 && codeword[40] <= kExtendedTierCapacities.size(); |
| 204 | const bool likely_tiered = is_tiered_extended_format(codeword[20]) || (codeword[30]==3 && tier_hint_valid); |
| 205 | if (!is_tiered_extended_format(codeword[20])) { |
| 206 | const ckey_status legacy=attempt(kExtendedDataBytes,kExtendedParityBytes,false); |
| 207 | if (legacy==CKEY_OK || legacy==CKEY_INSUFFICIENT_BUFFER) return legacy; |
| 208 | } |
| 209 | if (!likely_tiered) { set_status(diag,CKEY_UNCORRECTABLE,"sampled frame does not identify a supported Profile 3 format"); return CKEY_UNCORRECTABLE; } |
| 210 | |
| 211 | // New tiers share the same 2,550-byte optical codeword. The selected tier |
| 212 | // changes data/parity split per block, trading capacity for ECC while |
| 213 | // keeping the full visual lattice populated. Try the indicated tier first. |
| 214 | std::array<size_t, CKEY_EXTENDED_TIER_COUNT> order{}; |
| 215 | for(size_t i=0;i<order.size();++i) order[i]=i; |
| 216 | const size_t hinted = codeword[40] > 0 && codeword[40] <= order.size() ? codeword[40]-1 : order.size(); |
| 217 | if(hinted<order.size()) { order[0]=hinted; size_t next=1; for(size_t i=0;i<order.size();++i) if(i!=hinted) order[next++]=i; } |
| 218 | for(size_t tier:order) { |
| 219 | const size_t data_bytes=(11+kExtendedTierCapacities[tier]+kExtendedBlocks-1)/kExtendedBlocks; |
| 220 | const ckey_status result=attempt(data_bytes,kExtendedBlockBytes-data_bytes,true); |
| 221 | if(result==CKEY_OK || result==CKEY_INSUFFICIENT_BUFFER) return result; |
| 222 | } |
| 223 | set_status(diag,CKEY_UNCORRECTABLE,"no Profile 3 capacity tier passed Reed-Solomon, frame, padding, and CRC checks"); return CKEY_UNCORRECTABLE; |
| 224 | } |
| 225 | |
| 226 | uint32_t ckey_scalable_modules_across(uint32_t version) { |
| 227 | return version >= 1 && version <= CKEY_SCALABLE_VERSION_COUNT ? static_cast<uint32_t>(kScalableGrid[version - 1]) : 0u; |
| 228 | } |
| 229 | |
| 230 | size_t ckey_scalable_capacity(uint32_t version, ckey_ecc_level level, ckey_scalable_layout layout) { |
| 231 | return scalable_capacity(static_cast<int>(version), static_cast<int>(level), static_cast<int>(layout)); |
| 232 | } |
| 233 | |
| 234 | ckey_status ckey_encode_scalable(const uint8_t *payload, size_t payload_len, uint32_t version, ckey_ecc_level level, |
| 235 | ckey_scalable_layout layout, ckey_scalable_symbol *out, ckey_diagnostics *diag) { |
| 236 | const int family = static_cast<int>(layout); |
| 237 | if ((!payload && payload_len) || !out || static_cast<int>(level) < 0 || static_cast<int>(level) > 3 || version > CKEY_SCALABLE_VERSION_COUNT || |
| 238 | family < 0 || family >= kScalableFamilies) { |
| 239 | set_status(diag, CKEY_INVALID_ARGUMENT, "invalid Profile 0 payload, version, level, or output"); return CKEY_INVALID_ARGUMENT; |
| 240 | } |
| 241 | int v = static_cast<int>(version); |
| 242 | if (v == 0) { for (v = 1; v <= kScalableVersions && scalable_capacity(v, level, family) < payload_len; ++v) {} } |
| 243 | if (v > kScalableVersions || scalable_capacity(v, level, family) < payload_len) { |
| 244 | set_status(diag, CKEY_INVALID_ARGUMENT, "payload exceeds the selected Profile 0 version and level"); return CKEY_INVALID_ARGUMENT; |
| 245 | } |
| 246 | const ScalableLayout &L = scalable_layout(v, family); |
| 247 | if (L.codeword_bytes > CKEY_SCALABLE_MAX_CODEWORD_BYTES) { set_status(diag, CKEY_INTERNAL_ERROR, "Profile 0 layout exceeds codeword storage"); return CKEY_INTERNAL_ERROR; } |
| 248 | const auto blocks = scalable_blocks(L.codeword_bytes, level); |
| 249 | std::vector<uint8_t> framed(scalable_data_bytes(L.codeword_bytes, level), 0); |
| 250 | framed[0] = 0xc7; framed[1] = 0x4b; framed[2] = L.format; framed[3] = 0; |
| 251 | framed[4] = static_cast<uint8_t>(v); framed[5] = static_cast<uint8_t>(level); |
| 252 | framed[6] = static_cast<uint8_t>(payload_len >> 8); framed[7] = static_cast<uint8_t>(payload_len); |
| 253 | if (payload_len) std::memcpy(framed.data() + kScalableHeaderBytes, payload, payload_len); |
| 254 | const size_t crc_index = kScalableHeaderBytes + payload_len; const uint32_t crc = crc32c(framed.data(), crc_index); |
| 255 | for (int b = 0; b < 4; ++b) framed[crc_index + b] = static_cast<uint8_t>(crc >> (24 - 8 * b)); |
| 256 | fill_padding(framed.data(), crc_index + 4, framed.size(), crc_index + 4); |
| 257 | std::vector<std::vector<uint8_t>> coded; size_t offset = 0; |
| 258 | for (const auto &b : blocks) { |
| 259 | std::vector<uint8_t> work(b.n, 0); |
| 260 | std::copy_n(framed.begin() + static_cast<std::ptrdiff_t>(offset), b.k, work.begin()); offset += b.k; |
| 261 | rs_encode_generic(work, b.k, b.n - b.k); |
| 262 | coded.push_back(std::move(work)); |
| 263 | } |
| 264 | std::memset(out, 0, sizeof(*out)); |
| 265 | out->abi_version = CKEY_ABI_VERSION; out->version = static_cast<uint32_t>(v); out->ecc_level = static_cast<uint32_t>(level); |
| 266 | out->layout = static_cast<uint32_t>(family); |
| 267 | out->payload_length = static_cast<uint32_t>(payload_len); out->codeword_length = static_cast<uint32_t>(L.codeword_bytes); |
| 268 | // Byte-interleave the blocks so local damage spreads across all of them. |
| 269 | size_t pos = 0; |
| 270 | for (size_t j = 0; j < blocks.front().n; ++j) for (size_t b = 0; b < blocks.size(); ++b) if (j < blocks[b].n) out->codeword[pos++] = coded[b][j]; |
| 271 | char message[96]; std::snprintf(message, sizeof(message), "encoded Profile 0%s v%d level %c; %zu RS blocks", family ? ".1 (print)" : "", v, "LMQH"[level], blocks.size()); |
| 272 | set_status(diag, CKEY_OK, message); |
| 273 | return CKEY_OK; |
| 274 | } |
| 275 | |
| 276 | ckey_status ckey_decode_scalable(const uint8_t *codeword, size_t codeword_len, uint32_t version, ckey_scalable_layout layout, |
| 277 | uint8_t *out_payload, size_t out_capacity, size_t *out_payload_len, ckey_diagnostics *diag) { |
| 278 | const int family = static_cast<int>(layout); |
| 279 | if (!codeword || !out_payload_len || version < 1 || version > CKEY_SCALABLE_VERSION_COUNT || family < 0 || family >= kScalableFamilies) { |
| 280 | set_status(diag, CKEY_INVALID_ARGUMENT, "invalid Profile 0 codeword, version, or layout"); return CKEY_INVALID_ARGUMENT; |
| 281 | } |
| 282 | const ScalableLayout &L = scalable_layout(static_cast<int>(version), family); |
| 283 | if (codeword_len != L.codeword_bytes) { set_status(diag, CKEY_INVALID_ARGUMENT, "codeword length does not match the Profile 0 version"); return CKEY_INVALID_ARGUMENT; } |
| 284 | for (int level = 0; level < 4; ++level) { |
| 285 | const auto blocks = scalable_blocks(L.codeword_bytes, level); |
| 286 | std::vector<std::vector<uint8_t>> work(blocks.size()); |
| 287 | for (size_t b = 0; b < blocks.size(); ++b) work[b].resize(blocks[b].n); |
| 288 | size_t pos = 0; |
| 289 | for (size_t j = 0; j < blocks.front().n; ++j) for (size_t b = 0; b < blocks.size(); ++b) if (j < blocks[b].n) work[b][j] = codeword[pos++]; |
| 290 | std::vector<uint8_t> framed; uint32_t corrections = 0; bool ok = true; |
| 291 | for (size_t b = 0; b < blocks.size() && ok; ++b) { |
| 292 | uint32_t fixed = 0; |
| 293 | ok = rs_correct_generic(work[b], blocks[b].n - blocks[b].k, fixed); |
| 294 | corrections += fixed; |
| 295 | framed.insert(framed.end(), work[b].begin(), work[b].begin() + static_cast<std::ptrdiff_t>(blocks[b].k)); |
| 296 | } |
| 297 | if (!ok || framed.size() < kScalableOverheadBytes) continue; |
| 298 | if (framed[0] != 0xc7 || framed[1] != 0x4b || framed[2] != L.format || framed[3] != 0 || |
| 299 | framed[4] != version || framed[5] != level) continue; |
| 300 | const size_t length = (static_cast<size_t>(framed[6]) << 8) | framed[7]; |
| 301 | if (kScalableOverheadBytes + length > framed.size()) continue; |
| 302 | const size_t crc_index = kScalableHeaderBytes + length; |
| 303 | uint32_t expected = 0; for (int b = 0; b < 4; ++b) expected = (expected << 8) | framed[crc_index + b]; |
| 304 | if (crc32c(framed.data(), crc_index) != expected || !padding_matches(framed.data(), crc_index + 4, framed.size(), crc_index + 4)) continue; |
| 305 | if (out_capacity < length || (!out_payload && length)) { set_status(diag, CKEY_INSUFFICIENT_BUFFER, "Profile 0 payload exceeds output capacity"); return CKEY_INSUFFICIENT_BUFFER; } |
| 306 | if (length) std::memcpy(out_payload, framed.data() + kScalableHeaderBytes, length); |
| 307 | *out_payload_len = length; |
| 308 | char message[96]; std::snprintf(message, sizeof(message), "decoded Profile 0%s v%u level %c", family ? ".1 (print)" : "", version, "LMQH"[level]); |
| 309 | set_status(diag, CKEY_OK, message); if (diag) diag->corrected_symbols = corrections; |
| 310 | return CKEY_OK; |
| 311 | } |
| 312 | set_status(diag, CKEY_UNCORRECTABLE, "no Profile 0 level passed Reed-Solomon, frame, and CRC checks"); return CKEY_UNCORRECTABLE; |
| 313 | } |
| 314 | |
| 315 | } // extern "C" |