feat: add gb32960 session diagnostics
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was canceled
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was canceled
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
package com.lingniu.ingest.gb32960app.actuator;
|
||||||
|
|
||||||
|
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960ConnectionDiagnostics;
|
||||||
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Endpoint(id = "gb32960-sessions")
|
||||||
|
public class Gb32960SessionsEndpoint {
|
||||||
|
|
||||||
|
private final Gb32960ConnectionDiagnostics diagnostics;
|
||||||
|
|
||||||
|
public Gb32960SessionsEndpoint(Gb32960ConnectionDiagnostics diagnostics) {
|
||||||
|
this.diagnostics = diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReadOperation
|
||||||
|
public Map<String, Object> sessions() {
|
||||||
|
var sessions = diagnostics.activeSessions();
|
||||||
|
return Map.of(
|
||||||
|
"activeCount", sessions.size(),
|
||||||
|
"sessions", sessions);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -119,7 +119,7 @@ management:
|
|||||||
endpoints:
|
endpoints:
|
||||||
web:
|
web:
|
||||||
exposure:
|
exposure:
|
||||||
include: health,info,metrics,prometheus,env
|
include: health,info,metrics,prometheus,env,gb32960-sessions
|
||||||
health:
|
health:
|
||||||
redis:
|
redis:
|
||||||
enabled: ${MANAGEMENT_HEALTH_REDIS_ENABLED:false}
|
enabled: ${MANAGEMENT_HEALTH_REDIS_ENABLED:false}
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ class Gb32960IngestAppDefaultsTest {
|
|||||||
.containsEntry("lingniu.ingest.event-file-store.enabled", false)
|
.containsEntry("lingniu.ingest.event-file-store.enabled", false)
|
||||||
.containsEntry("lingniu.ingest.event-history.enabled", false)
|
.containsEntry("lingniu.ingest.event-history.enabled", false)
|
||||||
.containsEntry("lingniu.ingest.vehicle-state.enabled", false)
|
.containsEntry("lingniu.ingest.vehicle-state.enabled", false)
|
||||||
.containsEntry("lingniu.ingest.vehicle-stat.enabled", false);
|
.containsEntry("lingniu.ingest.vehicle-stat.enabled", false)
|
||||||
|
.containsEntry("management.endpoints.web.exposure.include",
|
||||||
|
"health,info,metrics,prometheus,env,gb32960-sessions");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Properties applicationProperties() {
|
private static Properties applicationProperties() {
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.lingniu.ingest.gb32960app.actuator;
|
||||||
|
|
||||||
|
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960ConnectionDiagnostics;
|
||||||
|
import com.lingniu.ingest.protocol.gb32960.model.CommandType;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class Gb32960SessionsEndpointTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void returnsActiveGb32960Sessions() {
|
||||||
|
Gb32960ConnectionDiagnostics diagnostics = new Gb32960ConnectionDiagnostics();
|
||||||
|
diagnostics.opened("session-1", "127.0.0.1:65469");
|
||||||
|
diagnostics.received("session-1", CommandType.PLATFORM_LOGIN, "");
|
||||||
|
diagnostics.platformLoginAccepted("session-1", "Hyundai");
|
||||||
|
|
||||||
|
Gb32960SessionsEndpoint endpoint = new Gb32960SessionsEndpoint(diagnostics);
|
||||||
|
|
||||||
|
Map<String, Object> result = endpoint.sessions();
|
||||||
|
|
||||||
|
assertThat(result).containsEntry("activeCount", 1);
|
||||||
|
assertThat(result.get("sessions")).isInstanceOf(List.class);
|
||||||
|
assertThat((List<?>) result.get("sessions")).singleElement()
|
||||||
|
.hasFieldOrPropertyWithValue("sessionId", "session-1")
|
||||||
|
.hasFieldOrPropertyWithValue("peer", "127.0.0.1:65469")
|
||||||
|
.hasFieldOrPropertyWithValue("lastCommand", "PLATFORM_LOGIN")
|
||||||
|
.hasFieldOrPropertyWithValue("platformAccount", "Hyundai");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ import com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionSelector
|
|||||||
import com.lingniu.ingest.protocol.gb32960.handler.Gb32960RealtimeHandler;
|
import com.lingniu.ingest.protocol.gb32960.handler.Gb32960RealtimeHandler;
|
||||||
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960AccessService;
|
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960AccessService;
|
||||||
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960AckService;
|
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960AckService;
|
||||||
|
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960ConnectionDiagnostics;
|
||||||
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960FrameDiagnostics;
|
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960FrameDiagnostics;
|
||||||
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer;
|
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer;
|
||||||
import com.lingniu.ingest.protocol.gb32960.mapper.Gb32960EventMapper;
|
import com.lingniu.ingest.protocol.gb32960.mapper.Gb32960EventMapper;
|
||||||
@@ -220,6 +221,12 @@ public class Gb32960AutoConfiguration {
|
|||||||
props.getDiagnostics().getMaxLoggedFrameKeysPerChannel());
|
props.getDiagnostics().getMaxLoggedFrameKeysPerChannel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public Gb32960ConnectionDiagnostics gb32960ConnectionDiagnostics() {
|
||||||
|
return new Gb32960ConnectionDiagnostics();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
@ConditionalOnProperty(prefix = "lingniu.ingest.gb32960.server", name = "enabled",
|
@ConditionalOnProperty(prefix = "lingniu.ingest.gb32960.server", name = "enabled",
|
||||||
@@ -229,7 +236,8 @@ public class Gb32960AutoConfiguration {
|
|||||||
Dispatcher dispatcher,
|
Dispatcher dispatcher,
|
||||||
Gb32960AccessService accessService,
|
Gb32960AccessService accessService,
|
||||||
Gb32960AckService ackService,
|
Gb32960AckService ackService,
|
||||||
Gb32960FrameDiagnostics diagnostics) {
|
Gb32960FrameDiagnostics diagnostics,
|
||||||
|
Gb32960ConnectionDiagnostics connectionDiagnostics) {
|
||||||
return new Gb32960NettyServer(
|
return new Gb32960NettyServer(
|
||||||
props.getPort(),
|
props.getPort(),
|
||||||
props.getWorkerThreads(),
|
props.getWorkerThreads(),
|
||||||
@@ -239,6 +247,7 @@ public class Gb32960AutoConfiguration {
|
|||||||
accessService,
|
accessService,
|
||||||
ackService,
|
ackService,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
|
connectionDiagnostics,
|
||||||
props.getTls());
|
props.getTls());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,26 +64,31 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
|||||||
private final Gb32960AccessService accessService;
|
private final Gb32960AccessService accessService;
|
||||||
private final Gb32960AckService ackService;
|
private final Gb32960AckService ackService;
|
||||||
private final Gb32960FrameDiagnostics diagnostics;
|
private final Gb32960FrameDiagnostics diagnostics;
|
||||||
|
private final Gb32960ConnectionDiagnostics connectionDiagnostics;
|
||||||
|
|
||||||
public Gb32960ChannelHandler(Gb32960MessageDecoder decoder,
|
public Gb32960ChannelHandler(Gb32960MessageDecoder decoder,
|
||||||
Dispatcher dispatcher,
|
Dispatcher dispatcher,
|
||||||
Gb32960AccessService accessService,
|
Gb32960AccessService accessService,
|
||||||
Gb32960AckService ackService,
|
Gb32960AckService ackService,
|
||||||
Gb32960FrameDiagnostics diagnostics) {
|
Gb32960FrameDiagnostics diagnostics,
|
||||||
|
Gb32960ConnectionDiagnostics connectionDiagnostics) {
|
||||||
this.decoder = decoder;
|
this.decoder = decoder;
|
||||||
this.dispatcher = dispatcher;
|
this.dispatcher = dispatcher;
|
||||||
this.accessService = accessService;
|
this.accessService = accessService;
|
||||||
this.ackService = ackService;
|
this.ackService = ackService;
|
||||||
this.diagnostics = diagnostics;
|
this.diagnostics = diagnostics;
|
||||||
|
this.connectionDiagnostics = connectionDiagnostics;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelActive(ChannelHandlerContext ctx) {
|
public void channelActive(ChannelHandlerContext ctx) {
|
||||||
|
connectionDiagnostics.opened(sessionId(ctx), addr(ctx));
|
||||||
log.info("[gb32960] connection opened peer={}", addr(ctx));
|
log.info("[gb32960] connection opened peer={}", addr(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelInactive(ChannelHandlerContext ctx) {
|
public void channelInactive(ChannelHandlerContext ctx) {
|
||||||
|
connectionDiagnostics.closed(sessionId(ctx));
|
||||||
log.info("[gb32960] connection closed peer={}", addr(ctx));
|
log.info("[gb32960] connection closed peer={}", addr(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +99,7 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
|||||||
@Override
|
@Override
|
||||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
if (evt instanceof IdleStateEvent idle && idle.state() == IdleState.READER_IDLE) {
|
if (evt instanceof IdleStateEvent idle && idle.state() == IdleState.READER_IDLE) {
|
||||||
|
connectionDiagnostics.readIdle(sessionId(ctx));
|
||||||
log.warn("[gb32960] read idle peer={} noInboundFrameFor>=thresholdSeconds (see idleReadSeconds)",
|
log.warn("[gb32960] read idle peer={} noInboundFrameFor>=thresholdSeconds (see idleReadSeconds)",
|
||||||
addr(ctx));
|
addr(ctx));
|
||||||
}
|
}
|
||||||
@@ -115,6 +121,7 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
|||||||
|
|
||||||
String vin = msg.header().vin();
|
String vin = msg.header().vin();
|
||||||
CommandType cmd = msg.header().command();
|
CommandType cmd = msg.header().command();
|
||||||
|
connectionDiagnostics.received(sessionId(ctx), cmd, vin);
|
||||||
// 原样回显请求帧的 17 字节 VIN(frame[4..20]),避免平台登入 VIN 全 0
|
// 原样回显请求帧的 17 字节 VIN(frame[4..20]),避免平台登入 VIN 全 0
|
||||||
// 被 vinPadded 重写为空格导致对端判定 VIN 不匹配。
|
// 被 vinPadded 重写为空格导致对端判定 VIN 不匹配。
|
||||||
byte[] rawVin = new byte[17];
|
byte[] rawVin = new byte[17];
|
||||||
@@ -339,6 +346,7 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
|||||||
addr(ctx), pl.username(), pl.encryptRule(), pl.serialNo(), pl.loginTime(), result);
|
addr(ctx), pl.username(), pl.encryptRule(), pl.serialNo(), pl.loginTime(), result);
|
||||||
// 把 username 钉到 channel attribute,供后续 0x02/0x03 帧的 vendor profile 路由
|
// 把 username 钉到 channel attribute,供后续 0x02/0x03 帧的 vendor profile 路由
|
||||||
accessService.bindPlatformAccount(ctx.channel(), pl.username());
|
accessService.bindPlatformAccount(ctx.channel(), pl.username());
|
||||||
|
connectionDiagnostics.platformLoginAccepted(sessionId(ctx), pl.username());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,4 +502,8 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
|||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String sessionId(ChannelHandlerContext ctx) {
|
||||||
|
return ctx.channel().id().asLongText();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.lingniu.ingest.protocol.gb32960.inbound;
|
||||||
|
|
||||||
|
import com.lingniu.ingest.protocol.gb32960.model.CommandType;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
public class Gb32960ConnectionDiagnostics {
|
||||||
|
|
||||||
|
private final Clock clock;
|
||||||
|
private final ConcurrentMap<String, MutableSession> activeSessions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public Gb32960ConnectionDiagnostics() {
|
||||||
|
this(Clock.systemUTC());
|
||||||
|
}
|
||||||
|
|
||||||
|
Gb32960ConnectionDiagnostics(Clock clock) {
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void opened(String sessionId, String peer) {
|
||||||
|
Instant now = clock.instant();
|
||||||
|
activeSessions.put(sessionId, new MutableSession(sessionId, peer, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void received(String sessionId, CommandType command, String vin) {
|
||||||
|
activeSessions.computeIfPresent(sessionId, (ignored, session) -> {
|
||||||
|
session.lastInboundAt = clock.instant();
|
||||||
|
session.lastCommandCode = command.code();
|
||||||
|
session.lastCommand = command.name();
|
||||||
|
session.vin = vin == null ? "" : vin;
|
||||||
|
return session;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void platformLoginAccepted(String sessionId, String platformAccount) {
|
||||||
|
activeSessions.computeIfPresent(sessionId, (ignored, session) -> {
|
||||||
|
session.platformAccount = platformAccount == null ? "" : platformAccount;
|
||||||
|
return session;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readIdle(String sessionId) {
|
||||||
|
activeSessions.computeIfPresent(sessionId, (ignored, session) -> {
|
||||||
|
session.readIdleCount++;
|
||||||
|
return session;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closed(String sessionId) {
|
||||||
|
activeSessions.remove(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SessionSnapshot> activeSessions() {
|
||||||
|
Instant now = clock.instant();
|
||||||
|
return activeSessions.values().stream()
|
||||||
|
.map(session -> session.snapshot(now))
|
||||||
|
.sorted(Comparator.comparing(SessionSnapshot::connectedAt))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record SessionSnapshot(
|
||||||
|
String sessionId,
|
||||||
|
String peer,
|
||||||
|
Instant connectedAt,
|
||||||
|
Instant lastInboundAt,
|
||||||
|
long idleSeconds,
|
||||||
|
int lastCommandCode,
|
||||||
|
String lastCommand,
|
||||||
|
String vin,
|
||||||
|
String platformAccount,
|
||||||
|
long readIdleCount) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MutableSession {
|
||||||
|
private final String sessionId;
|
||||||
|
private final String peer;
|
||||||
|
private final Instant connectedAt;
|
||||||
|
private Instant lastInboundAt;
|
||||||
|
private int lastCommandCode;
|
||||||
|
private String lastCommand = "";
|
||||||
|
private String vin = "";
|
||||||
|
private String platformAccount = "";
|
||||||
|
private long readIdleCount;
|
||||||
|
|
||||||
|
private MutableSession(String sessionId, String peer, Instant connectedAt) {
|
||||||
|
this.sessionId = sessionId;
|
||||||
|
this.peer = peer;
|
||||||
|
this.connectedAt = connectedAt;
|
||||||
|
this.lastInboundAt = connectedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SessionSnapshot snapshot(Instant now) {
|
||||||
|
long idleSeconds = Math.max(0, Duration.between(lastInboundAt, now).toSeconds());
|
||||||
|
return new SessionSnapshot(
|
||||||
|
sessionId,
|
||||||
|
peer,
|
||||||
|
connectedAt,
|
||||||
|
lastInboundAt,
|
||||||
|
idleSeconds,
|
||||||
|
lastCommandCode,
|
||||||
|
lastCommand,
|
||||||
|
vin,
|
||||||
|
platformAccount,
|
||||||
|
readIdleCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,7 @@ public class Gb32960NettyServer implements InitializingBean, DisposableBean {
|
|||||||
private final Gb32960AccessService accessService;
|
private final Gb32960AccessService accessService;
|
||||||
private final Gb32960AckService ackService;
|
private final Gb32960AckService ackService;
|
||||||
private final Gb32960FrameDiagnostics diagnostics;
|
private final Gb32960FrameDiagnostics diagnostics;
|
||||||
|
private final Gb32960ConnectionDiagnostics connectionDiagnostics;
|
||||||
private final Gb32960Properties.Tls tlsConfig;
|
private final Gb32960Properties.Tls tlsConfig;
|
||||||
|
|
||||||
private EventLoopGroup boss;
|
private EventLoopGroup boss;
|
||||||
@@ -63,6 +64,7 @@ public class Gb32960NettyServer implements InitializingBean, DisposableBean {
|
|||||||
Gb32960AccessService accessService,
|
Gb32960AccessService accessService,
|
||||||
Gb32960AckService ackService,
|
Gb32960AckService ackService,
|
||||||
Gb32960FrameDiagnostics diagnostics,
|
Gb32960FrameDiagnostics diagnostics,
|
||||||
|
Gb32960ConnectionDiagnostics connectionDiagnostics,
|
||||||
Gb32960Properties.Tls tlsConfig) {
|
Gb32960Properties.Tls tlsConfig) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.workerThreads = workerThreads;
|
this.workerThreads = workerThreads;
|
||||||
@@ -72,6 +74,7 @@ public class Gb32960NettyServer implements InitializingBean, DisposableBean {
|
|||||||
this.accessService = accessService;
|
this.accessService = accessService;
|
||||||
this.ackService = ackService;
|
this.ackService = ackService;
|
||||||
this.diagnostics = diagnostics;
|
this.diagnostics = diagnostics;
|
||||||
|
this.connectionDiagnostics = connectionDiagnostics;
|
||||||
this.tlsConfig = tlsConfig;
|
this.tlsConfig = tlsConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +116,8 @@ public class Gb32960NettyServer implements InitializingBean, DisposableBean {
|
|||||||
.addLast("frame", new Gb32960FrameDecoder())
|
.addLast("frame", new Gb32960FrameDecoder())
|
||||||
.addLast("dispatch",
|
.addLast("dispatch",
|
||||||
new Gb32960ChannelHandler(messageDecoder, dispatcher,
|
new Gb32960ChannelHandler(messageDecoder, dispatcher,
|
||||||
accessService, ackService, diagnostics));
|
accessService, ackService, diagnostics,
|
||||||
|
connectionDiagnostics));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.serverChannel = b.bind(port).sync().channel();
|
this.serverChannel = b.bind(port).sync().channel();
|
||||||
|
|||||||
@@ -139,7 +139,8 @@ class Gb32960ChannelHandlerAckBoundaryTest {
|
|||||||
dispatcher,
|
dispatcher,
|
||||||
accessService(),
|
accessService(),
|
||||||
new Gb32960AckService(),
|
new Gb32960AckService(),
|
||||||
new Gb32960FrameDiagnostics(16));
|
new Gb32960FrameDiagnostics(16),
|
||||||
|
new Gb32960ConnectionDiagnostics());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Gb32960MessageDecoder decoder() {
|
private static Gb32960MessageDecoder decoder() {
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.lingniu.ingest.protocol.gb32960.inbound;
|
||||||
|
|
||||||
|
import com.lingniu.ingest.protocol.gb32960.model.CommandType;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class Gb32960ConnectionDiagnosticsTest {
|
||||||
|
|
||||||
|
private static final Instant NOW = Instant.parse("2026-06-29T10:00:00Z");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tracksActiveSessionCommandPlatformAccountAndReadIdleCount() {
|
||||||
|
MutableClock clock = new MutableClock(NOW);
|
||||||
|
Gb32960ConnectionDiagnostics diagnostics = new Gb32960ConnectionDiagnostics(clock);
|
||||||
|
|
||||||
|
diagnostics.opened("session-1", "127.0.0.1:65469");
|
||||||
|
clock.advanceSeconds(5);
|
||||||
|
diagnostics.received("session-1", CommandType.PLATFORM_LOGIN, "");
|
||||||
|
diagnostics.platformLoginAccepted("session-1", "Hyundai");
|
||||||
|
clock.advanceSeconds(30);
|
||||||
|
diagnostics.readIdle("session-1");
|
||||||
|
|
||||||
|
assertThat(diagnostics.activeSessions()).singleElement().satisfies(session -> {
|
||||||
|
assertThat(session.sessionId()).isEqualTo("session-1");
|
||||||
|
assertThat(session.peer()).isEqualTo("127.0.0.1:65469");
|
||||||
|
assertThat(session.connectedAt()).isEqualTo(NOW);
|
||||||
|
assertThat(session.lastInboundAt()).isEqualTo(NOW.plusSeconds(5));
|
||||||
|
assertThat(session.idleSeconds()).isEqualTo(30);
|
||||||
|
assertThat(session.lastCommandCode()).isEqualTo(0x05);
|
||||||
|
assertThat(session.lastCommand()).isEqualTo("PLATFORM_LOGIN");
|
||||||
|
assertThat(session.vin()).isEmpty();
|
||||||
|
assertThat(session.platformAccount()).isEqualTo("Hyundai");
|
||||||
|
assertThat(session.readIdleCount()).isEqualTo(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removesSessionWhenChannelCloses() {
|
||||||
|
Gb32960ConnectionDiagnostics diagnostics = new Gb32960ConnectionDiagnostics(Clock.fixed(NOW, ZoneOffset.UTC));
|
||||||
|
|
||||||
|
diagnostics.opened("session-1", "127.0.0.1:65469");
|
||||||
|
diagnostics.closed("session-1");
|
||||||
|
|
||||||
|
assertThat(diagnostics.activeSessions()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MutableClock extends Clock {
|
||||||
|
private Instant instant;
|
||||||
|
|
||||||
|
private MutableClock(Instant instant) {
|
||||||
|
this.instant = instant;
|
||||||
|
}
|
||||||
|
|
||||||
|
void advanceSeconds(long seconds) {
|
||||||
|
instant = instant.plusSeconds(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ZoneOffset getZone() {
|
||||||
|
return ZoneOffset.UTC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Clock withZone(java.time.ZoneId zone) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant instant() {
|
||||||
|
return instant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user