From 89a7c0710653d4b0466d7dc3090ad1af33fff0f4 Mon Sep 17 00:00:00 2001 From: kkfluous Date: Mon, 20 Apr 2026 15:15:41 +0800 Subject: [PATCH] feat(gb32960): isolate per-block parse failures in body parser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主循环改造:单信息块 parser 抛 DecodeException / BufferUnderflowException / IndexOutOfBoundsException 时不再放弃整帧。 - 固定长度块(fixedLen≥0):回滚 reader,按 fixedLen 截取字节兜成 InfoBlock.Raw, position 前进 fixedLen,continue 循环继续解析后续块 - 变长块(fixedLen=-1)或剩余不足 fixedLen:剩余字节全部兜成 Raw 后 break - parser 契约违反(consumed != declared fixedLen)仍抛异常,以暴露 parser bug, 不走 lenient 兜底,避免静默误解析长期误判 - 仅捕获 DecodeException / BufferUnderflowException / IndexOutOfBoundsException, RuntimeException 继续向上抛,保留程序 bug 可见性 - 通过 lingniu.ingest.gb32960.parse.lenientBlockFailure=false 可回退严格模式 不改 InfoBlock.Raw record 结构;下游 Gb32960EventMapper 按 findBlock(Class) 类型 查找,新增 Raw 不影响业务事件映射。 Gb32960BodyParserIsolationTest 4/4 绿;全模块 39 tests 全绿,无回归。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../gb32960/codec/Gb32960BodyParser.java | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java index 8a9dc857..ab637d37 100644 --- a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java @@ -141,14 +141,57 @@ public final class Gb32960BodyParser { int fixedLen = parser.fixedLength(); int posBefore = body.position(); + + // 预检:固定长度块在剩余字节不足时,lenient 模式兜 Raw 后 break;严格模式保持旧行为抛异常。 if (fixedLen >= 0 && body.remaining() < fixedLen) { - throw new DecodeException( - "info block 0x" + Integer.toHexString(typeCode) - + " needs " + fixedLen + " bytes but got " + body.remaining()); + if (!lenientBlockFailure) { + throw new DecodeException( + "info block 0x" + Integer.toHexString(typeCode) + + " needs " + fixedLen + " bytes but got " + body.remaining()); + } + int remaining = body.remaining(); + byte[] tail = new byte[remaining]; + body.get(tail); + log.warn("[gb32960] truncated block typeCode=0x{} declaredFixedLen={} remaining={} — wrapping tail as Raw", + Integer.toHexString(typeCode), fixedLen, remaining); + blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, tail)); + break; } - InfoBlock block = parser.parse(body); + + InfoBlock block; + try { + block = parser.parse(body); + } catch (DecodeException | java.nio.BufferUnderflowException | IndexOutOfBoundsException e) { + if (!lenientBlockFailure) { + if (e instanceof DecodeException de) throw de; + throw new DecodeException( + "parser " + parser.getClass().getSimpleName() + " failed: " + e.getMessage(), e); + } + // 回滚到 parser.parse 入口,按块类型分支处理。 + body.position(posBefore); + if (fixedLen >= 0) { + // 固定长度块:按 fixedLen 截取 → 兜 Raw → continue 循环 + byte[] corrupt = new byte[fixedLen]; + body.get(corrupt); + log.warn("[gb32960] block parse failed typeCode=0x{} parser={} fixedLen={} — isolated as Raw, continuing", + Integer.toHexString(typeCode), parser.getClass().getSimpleName(), fixedLen, e); + blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, corrupt)); + continue; + } + // 变长块:无法安全找下一块边界 → 剩余整段兜 Raw + break + int remaining = body.remaining(); + byte[] tail = new byte[remaining]; + body.get(tail); + log.warn("[gb32960] variable-length block parse failed typeCode=0x{} parser={} remaining={} — wrapping remainder as Raw, stopping loop", + Integer.toHexString(typeCode), parser.getClass().getSimpleName(), remaining, e); + blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, tail)); + break; + } + int consumed = body.position() - posBefore; if (fixedLen >= 0 && consumed != fixedLen) { + // parser 契约违反(声明 fixedLen=X 但实际读了 Y),保持抛异常以暴露 parser bug, + // 不走 lenient 兜底,避免静默误解析长期误判。 throw new DecodeException( "parser " + parser.getClass().getSimpleName() + " consumed " + consumed