diff --git a/docs/50_Step记录/STEP-074_QBIN2_V4_EncodedIngress与Decoder_v4.md b/docs/50_Step记录/STEP-074_QBIN2_V4_EncodedIngress与Decoder_v4.md new file mode 100644 index 000000000..e67cfd452 --- /dev/null +++ b/docs/50_Step记录/STEP-074_QBIN2_V4_EncodedIngress与Decoder_v4.md @@ -0,0 +1,46 @@ +# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder v4 + +状态:完成 + +日期:2026-09-13 + +## 目标 + +仅修正 v3 `AcceptsRawLimitWithCanonicalPaddingExcluded` 的边界 fixture,使它严格证明 decoded raw 数据不超过 32 MiB、canonical packed payload 超过 32 MiB、整个 QBIN 文件不超过 64 MiB 时 production Decoder 仍接受。 + +## 基线 + +- 开始 HEAD:`f4a495ff38b77f586f97853493dda02ab067b26d`。 +- 开始前已有 STEP-062 文档修改及历史 untracked/build 文件;均未清理、覆盖、暂存或纳入本 Step。 + +## 必做项 + +- [x] `AcceptsRawLimitWithCanonicalPaddingExcluded` 改为 1101×1890(2,080,890 cells)、17 plane:1 个 single shared validity BITSET 与 16 个 UINT8 numeric plane;descriptor 依 semantic 3、4–19 canonical 排序。 +- [x] 测试直接计算并断言 `rawSum=33,554,352 <= 33,554,432`、`payloadLength=33,554,448 > 33,554,432`、`payloadLength > rawSum`、`totalQbinBytes=33,555,104 <= 67,108,864`。 +- [x] fixture 经 production `QbinV2Decoder::decode(...)` 返回 Accepted。 +- [x] cells、planes、canonical packing、single shared validity、descriptor 与 64 MiB 文件限制均在构造及断言内满足。 + +## 允许修改 + +- `test/particle/qbin_v2_decoder.test.cpp`。 +- 本 Step 记录、完成报告与粒子开发记录。 + +## 禁止修改 + +- Production Decoder、Host、网络、JNI、RN、Binding/Projection、Renderer、Store submission、Android/iOS 平台路径。 + +## 构建测试 + +- `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:PASS(输出节流器;实际目标已重新检查并确认最新)。 +- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:实际 Running 24,24/24 PASS(XML:Decoder 22、Ingress 2)。 +- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:实际 Running 11,11/11 PASS。 +- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:实际 Running 11,11/11 PASS。 +- `git diff --check`:PASS。 + +## 当前结果 + +旧 fixture 的 payload 仍低于 32 MiB,不能捕获把 packed payload 误当 decoded raw limit 的实现错误。新 fixture 以 16 个每个 6-byte canonical padding 的 numeric plane 将 payload 推至 limit 以上 16 bytes,同时 raw sum 比 limit 小 80 bytes;production Decoder 接受,未发现生产缺陷,故未修改 production 代码。 + +## 阻塞与下一步 + +无范围内阻塞。未执行 Android Gradle/APK/ADB/Jest;按任务要求停止,不进入下一 Step。 diff --git a/docs/报告/STEP-074_QBIN2_V4_EncodedIngress与Decoder_Codex完成报告_v4.md b/docs/报告/STEP-074_QBIN2_V4_EncodedIngress与Decoder_Codex完成报告_v4.md new file mode 100644 index 000000000..64e2f151e --- /dev/null +++ b/docs/报告/STEP-074_QBIN2_V4_EncodedIngress与Decoder_Codex完成报告_v4.md @@ -0,0 +1,31 @@ +# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder Codex 完成报告 v4 + +日期:2026-09-13 + +## 结果 + +已完成:只更新 `test/particle/qbin_v2_decoder.test.cpp` 中的 `AcceptsRawLimitWithCanonicalPaddingExcluded` fixture。新的 1101×1890、17-plane canonical QBIN 的数值为: + +- rawSum:33,554,352 bytes。 +- 32 MiB limit:33,554,432 bytes。 +- payloadLength:33,554,448 bytes。 +- totalQbinBytes:33,555,104 bytes。 + +测试直接断言 `rawSum <= limit`、`payloadLength > limit`、`payloadLength > rawSum` 与 `totalQbinBytes <= 64 MiB`,并调用 production `QbinV2Decoder::decode(...)`,结果 Accepted。 + +## 逐项状态 + +- 已完成:边界构造覆盖 canonical padding 将 payload 推过 32 MiB,而 decoded raw sum 未越界。 +- 已完成:cells=2,080,890(不超过 2,097,152),planes=17(不超过 32),single shared validity、descriptor 合法与 canonical packing 均成立。 +- 已完成:真实重建 `mbgl-test-runner`;Decoder/Ingress Running 24、Runtime/Profile Running 11、Source 回归 Running 11,均 PASS、零失败。 +- 已完成:`git diff --check` PASS。 +- 未完成:无。 +- 无法验证:Android Gradle/APK/ADB/Jest、iOS/Metal;本任务明确禁止执行,且本轮未涉及这些路径。 + +## 范围审计 + +未修改 production Decoder,未触及 Host、网络、JNI、RN、Binding/Projection、Renderer 或 Store submission。已有 STEP-062 修改及历史 untracked/build 文件原样保留。 + +## Git + +- 首个 scoped commit、push 与最终 HEAD:待本报告的 Git 字段补写后执行 docs-only commit 并推送;不会暂存既有 STEP-062 修改、任务书或其他 untracked 文件。 diff --git a/test/particle/qbin_v2_decoder.test.cpp b/test/particle/qbin_v2_decoder.test.cpp index 6f9054e8b..11ad2f485 100644 --- a/test/particle/qbin_v2_decoder.test.cpp +++ b/test/particle/qbin_v2_decoder.test.cpp @@ -50,7 +50,36 @@ TEST(ParticleQbinV2Decoder, RejectsFrameAndReservedHeader) { auto b=fixture();u1 TEST(ParticleQbinV2Decoder, RejectsGridTimeAndBounds) { auto b=fixture();u32(b,16,1);recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidHeader); b=fixture();u32(b,16,2049);u32(b,20,1024);recalc(b);EXPECT_EQ(decodeCode(b),Code::LimitExceeded); b=fixture();i64(b,24,0);recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidTime); b=fixture();i64(b,32,0);recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidTime); b=fixture();i64(b,24,std::numeric_limits::max());recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidTime); b=fixture();f64(b,40,std::numeric_limits::quiet_NaN());recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidBounds); b=fixture();f64(b,40,11);recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidBounds); b=fixture();f64(b,48,86);recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidBounds); } TEST(ParticleQbinV2Decoder, RejectsLengthAndFileLimits) { auto b=fixture();b.pop_back();EXPECT_EQ(decodeCode(b),Code::LengthMismatch); b=fixture();b.push_back(0);EXPECT_EQ(decodeCode(b),Code::LengthMismatch); std::vector huge(64u*1024u*1024u+1);EXPECT_EQ(decodeCode(huge),Code::LimitExceeded); } TEST(ParticleQbinV2Decoder, LimitsDecodedBytesWithoutChargingCanonicalPadding) { std::vector

ps={{3,0,0,6,1,3,0,1,0,{}}}; for(uint16_t semantic=4;semantic<13;++semantic) ps.push_back({semantic,77,0,5,0,0,0,1,0,{}}); EXPECT_EQ(decodeCode(fixture(1024,1024,ps)),Code::LimitExceeded); } -TEST(ParticleQbinV2Decoder, AcceptsRawLimitWithCanonicalPaddingExcluded) { const uint32_t width=1447, height=1449; const uint64_t cells=uint64_t(width)*height; ASSERT_LE(cells, 2'097'152u); std::vector

ps={{3,0,0,6,1,3,0,1,0,{}}}; for(uint16_t semantic=4;semantic<7;++semantic) ps.push_back({semantic,77,0,5,0,0,0,1,0,{}}); for(uint16_t semantic=7;semantic<10;++semantic) ps.push_back({semantic,77,0,3,0,0,0,1,0,{}}); uint64_t rawSum=0; size_t payloadLength=0; for (const auto& p : ps) { const size_t bytes=rawLength(p.type,cells); rawSum+=bytes; payloadLength=a16(payloadLength+bytes); } EXPECT_LE(rawSum, 32u*1024u*1024u); EXPECT_GT(rawSum, 30u*1024u*1024u); EXPECT_GT(payloadLength, rawSum); auto b=fixture(width,height,ps); ASSERT_EQ(b.size(), Header+ps.size()*Descriptor+payloadLength); auto r=particle::ingress::QbinV2Decoder::decode(meta(),b); EXPECT_TRUE(r.accepted())< ps = {{3, 0, 0, 6, 1, 3, 0, 1, 0, {}}}; + for (uint16_t semantic = 4; semantic < 20; ++semantic) { + ps.push_back({semantic, 77, 0, 3, 0, 0, 0, 1, 0, {}}); + } + + uint64_t rawSum = 0; + uint64_t payloadLength = 0; + for (const auto& p : ps) { + const uint64_t bytes = rawLength(p.type, cells); + rawSum += bytes; + payloadLength = a16(payloadLength + bytes); + } + + auto b = fixture(width, height, ps); + const uint64_t totalQbinBytes = b.size(); + EXPECT_LE(rawSum, limit); + EXPECT_GT(payloadLength, limit); + EXPECT_GT(payloadLength, rawSum); + EXPECT_LE(totalQbinBytes, 64ull * 1024ull * 1024ull); + ASSERT_EQ(totalQbinBytes, Header + ps.size() * Descriptor + payloadLength); + + auto r = particle::ingress::QbinV2Decoder::decode(meta(), b); + EXPECT_TRUE(r.accepted()) << r.detail; +} TEST(ParticleQbinV2Decoder, RejectsPlaneCountAndDescriptorEnums) { auto b=fixture();u16(b,12,0);recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidHeader); b=fixture();u16(b,12,33);recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidHeader); b=fixture();b[114]=0;recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidDescriptor); b=fixture();b[140]=4;recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidDescriptor); b=fixture();b[141]=4;recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidDescriptor); } TEST(ParticleQbinV2Decoder, RejectsDescriptorMetadataAndNumericParameters) { auto b=fixture();u16(b,142,1);recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidDescriptor); b=fixture();b[115]=2;recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidDescriptor); b=fixture();u32(b,124,3);recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidDescriptor); b=fixture();f32(b,116,0);recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidDescriptor); b=fixture();f32(b,116,std::numeric_limits::infinity());recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidDescriptor); b=fixture();f32(b,120,std::numeric_limits::quiet_NaN());recalc(b);EXPECT_EQ(decodeCode(b),Code::InvalidDescriptor); } TEST(ParticleQbinV2Decoder, EnforcesBitsetAndValidityPairing) { auto ps=basePlanes();ps[0].type=6;ps[0].raw={15};auto b=fixture(2,2,ps);EXPECT_EQ(decodeCode(b),Code::InvalidValidity); ps=basePlanes();ps[2].type=3;ps[2].raw={0,0,0,0};b=fixture(2,2,ps);EXPECT_EQ(decodeCode(b),Code::InvalidValidity); } diff --git a/粒子开发记录.md b/粒子开发记录.md index 23da2ae7c..e4361fc74 100644 --- a/粒子开发记录.md +++ b/粒子开发记录.md @@ -729,3 +729,8 @@ Android V3-08 单次综合验收已完成并关闭。Native `13.4.1-particle.2` - Header width 和 descriptor count 的 big/mixed-endian corruption 均在重算 CRC 后 fail-closed,分别结构化为 `LimitExceeded`、`InvalidDescriptor`。 - 1447×1449、7 plane 的 raw sum=31,712,633 bytes,canonical payload=31,712,648 bytes,15 bytes padding 不计入 32 MiB decoded raw limit 且 decoder Accepted。 - Linux runner 重建/重链、STEP-074 24/24、STEP-073 11/11、既有 V3 11/11 与 `git diff --check` 均 PASS;未改 production Decoder、Host、网络、JNI、Android/RN、Binding/Projection、Renderer 或 iOS/Metal。 + +### 2026-09-13 STEP-074 v4 审计最终修正 + +- `AcceptsRawLimitWithCanonicalPaddingExcluded` 改为 1101×1890(2,080,890 cells)的 17-plane canonical fixture:single shared validity BITSET 加 16 个 UINT8 numeric plane。rawSum=33,554,352 bytes,小于 32 MiB limit=33,554,432;payloadLength=33,554,448 bytes,大于 limit 与 rawSum;totalQbinBytes=33,555,104 bytes,小于 64 MiB。 +- production `QbinV2Decoder::decode(...)` Accepted;未发现生产缺陷,未改 Decoder 或 Host/网络/JNI/RN/Binding/Projection/Renderer。真实 runner 重建 PASS,Decoder/Ingress 24/24、Runtime/Profile 11/11、Source 回归 11/11 PASS,`git diff --check` PASS;未执行 Android Gradle/APK/ADB/Jest,按范围停止。