fix: archive gb32960 decode failures
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
lingniu
2026-06-29 18:47:19 +08:00
parent 8804c01d99
commit d1748fcc2f
2 changed files with 48 additions and 0 deletions

View File

@@ -109,6 +109,7 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
} catch (Exception e) { } catch (Exception e) {
// 解码失败只丢当前帧不主动断开连接。真实设备偶发脏字节时FrameDecoder 会继续寻找下一帧头。 // 解码失败只丢当前帧不主动断开连接。真实设备偶发脏字节时FrameDecoder 会继续寻找下一帧头。
log.warn("[gb32960] decode failed peer={} len={}", addr(ctx), frame.length, e); log.warn("[gb32960] decode failed peer={} len={}", addr(ctx), frame.length, e);
dispatchMalformed(ctx, frame, e.getMessage());
return; return;
} }
@@ -224,6 +225,22 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
Instant.now()); Instant.now());
} }
private void dispatchMalformed(ChannelHandlerContext ctx, byte[] frame, String errorMessage) {
Map<String, String> sourceMeta = new HashMap<>(4);
sourceMeta.put("vin", "unknown");
sourceMeta.put("peer", addr(ctx));
sourceMeta.put("parseError", "true");
sourceMeta.put("parseErrorMessage", errorMessage == null ? "" : errorMessage);
dispatcher.dispatch(new RawFrame(
ProtocolId.GB32960,
0,
0,
frame,
frame == null ? new byte[0] : frame,
sourceMeta,
Instant.now()));
}
private static boolean requiresDurableAck(CommandType cmd) { private static boolean requiresDurableAck(CommandType cmd) {
return switch (cmd) { return switch (cmd) {
case VEHICLE_LOGIN, case VEHICLE_LOGIN,

View File

@@ -97,6 +97,29 @@ class Gb32960ChannelHandlerAckBoundaryTest {
} }
} }
@Test
void decodeFailureIsArchivedWithParseErrorMetadata() {
try (DispatchHarness dispatch = new DispatchHarness()) {
EmbeddedChannel channel = new EmbeddedChannel(handlerWith(dispatch.dispatcher()));
byte[] malformed = new byte[]{0x23, 0x23, 0x02};
assertThat(channel.writeInbound(malformed)).isFalse();
dispatch.awaitPublishCount(1);
VehicleEvent event = dispatch.event(0);
assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class);
VehicleEvent.RawArchive raw = (VehicleEvent.RawArchive) event;
assertThat(raw.source()).isEqualTo(com.lingniu.ingest.api.ProtocolId.GB32960);
assertThat(raw.command()).isZero();
assertThat(raw.rawBytes()).containsExactly(malformed);
assertThat(raw.metadata())
.containsEntry("vin", "unknown")
.containsEntry("parseError", "true")
.containsKey("parseErrorMessage");
channel.finishAndReleaseAll();
}
}
private static ByteBuf readOutboundAfterRunningTasks(EmbeddedChannel channel) { private static ByteBuf readOutboundAfterRunningTasks(EmbeddedChannel channel) {
long deadline = System.currentTimeMillis() + 3000; long deadline = System.currentTimeMillis() + 3000;
while (System.currentTimeMillis() < deadline) { while (System.currentTimeMillis() < deadline) {
@@ -190,6 +213,12 @@ class Gb32960ChannelHandlerAckBoundaryTest {
throw new AssertionError("expected " + count + " sink publishes, actual=" + sink.futures.size()); throw new AssertionError("expected " + count + " sink publishes, actual=" + sink.futures.size());
} }
VehicleEvent event(int index) {
synchronized (sink.futures) {
return sink.events.get(index);
}
}
@Override @Override
public void close() { public void close() {
batchExecutor.close(); batchExecutor.close();
@@ -199,6 +228,7 @@ class Gb32960ChannelHandlerAckBoundaryTest {
private static final class ControlledSink implements EventSink { private static final class ControlledSink implements EventSink {
private final List<CompletableFuture<Void>> futures = new ArrayList<>(); private final List<CompletableFuture<Void>> futures = new ArrayList<>();
private final List<VehicleEvent> events = new ArrayList<>();
@Override @Override
public String name() { public String name() {
@@ -209,6 +239,7 @@ class Gb32960ChannelHandlerAckBoundaryTest {
public CompletableFuture<Void> publish(VehicleEvent event) { public CompletableFuture<Void> publish(VehicleEvent event) {
CompletableFuture<Void> future = new CompletableFuture<>(); CompletableFuture<Void> future = new CompletableFuture<>();
synchronized (futures) { synchronized (futures) {
events.add(event);
futures.add(future); futures.add(future);
} }
return future; return future;