Skip to content

Commit

Permalink
feat: Add batch messages conditional strict validation.
Browse files Browse the repository at this point in the history
  • Loading branch information
tzebrowski committed Apr 27, 2024
1 parent b18c69d commit a9aa168
Show file tree
Hide file tree
Showing 16 changed files with 422 additions and 327 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<artifactId>obd-metrics</artifactId>

<version>9.27.0-SNAPSHOT</version>
<version>9.28.0-SNAPSHOT</version>

<packaging>jar</packaging>

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/obd/metrics/api/model/BatchPolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public class BatchPolicy {
@Getter
private final Integer mode01BatchSize;

// Rejects incorrect ECU messages
@Getter
@Default
private final boolean strictValidationEnabled = Boolean.FALSE;

/*
* Add number of lines expected to return by Adapter which speedups the communication between Lib->Adapter.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ abstract class AbstractBatchCodec implements BatchCodec {
protected final String query;
protected final Init init;
protected final BatchCodecType codecType;
protected final BatchMessageDecoder decoder = BatchMessageDecoder.get();
protected final BatchMessageDecoder decoder;

AbstractBatchCodec(final BatchCodecType codecType, final Init init, final Adjustments adjustments,
final String query, final List<ObdCommand> commands) {
Expand All @@ -52,6 +52,7 @@ abstract class AbstractBatchCodec implements BatchCodec {
this.query = query;
this.commands = commands;
this.init = init;
this.decoder = BatchMessageDecoder.get(adjustments.getBatchPolicy());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@
@EqualsAndHashCode(of = "message")
final class BatchConnectorResponse implements ConnectorResponse {

private final PIDsPositionMapping mapping;
private final PIDPositionTemplate mapping;

private final ConnectorResponse buffer;

private long id = -1L;

private boolean cacheable;

BatchConnectorResponse(final PIDsPositionMapping mapping, final ConnectorResponse buffer) {
BatchConnectorResponse(final PIDPositionTemplate mapping, final ConnectorResponse buffer) {
this.mapping = mapping;
this.buffer = buffer;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.List;
import java.util.Map;

import org.obd.metrics.api.model.BatchPolicy;
import org.obd.metrics.command.obd.ObdCommand;
import org.obd.metrics.transport.message.ConnectorResponse;

Expand All @@ -29,7 +30,7 @@ public interface BatchMessageDecoder {
Map<ObdCommand, ConnectorResponse> decode(final String query, final List<ObdCommand> commands,
final ConnectorResponse connectorResponse);

static BatchMessageDecoder get() {
return new DefaultBatchMessageDecoder();
static BatchMessageDecoder get(BatchPolicy batchPolicy) {
return new DefaultBatchMessageDecoder(batchPolicy);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PACKAGE)
final class BatchMessagePositionMapping {
final class BatchMessagePositionTemplate {

@Getter
private final List<PIDsPositionMapping> mappings = new ArrayList<>();
private final List<PIDPositionTemplate> templates = new ArrayList<>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,62 +24,66 @@
import java.util.List;
import java.util.Map;

import org.obd.metrics.api.model.BatchPolicy;
import org.obd.metrics.command.obd.ObdCommand;
import org.obd.metrics.pid.PidDefinition;
import org.obd.metrics.transport.message.ConnectorResponse;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
final class DefaultBatchMessageDecoder implements BatchMessageDecoder {

private static final String[] DELIMETERS = new String[] { "0:", "1:", "2:", "3:", "4:", "5:" };
private final MappingsCache cache = new MappingsCache();

private final BatchPolicy batchPolicy;

@Override
public Map<ObdCommand, ConnectorResponse> decode(final String query, final List<ObdCommand> commands,
final ConnectorResponse connectorResponse) {
final BatchMessagePositionMapping mapping = getOrCreateMapping(query, commands, connectorResponse);
final BatchMessagePositionTemplate mapping = getOrCreateTemplate(query, commands, connectorResponse);

if (mapping == null) {
return Collections.emptyMap();
}

final Map<ObdCommand, ConnectorResponse> values = new HashMap<>();

mapping.getMappings().forEach(it -> {
mapping.getTemplates().forEach(it -> {
values.put(it.getCommand(), new BatchConnectorResponse(it, connectorResponse));
});

return values;
}

private BatchMessagePositionMapping getOrCreateMapping(final String query, final List<ObdCommand> commands,
private BatchMessagePositionTemplate getOrCreateTemplate(final String query, final List<ObdCommand> commands,
final ConnectorResponse connectorResponse) {
BatchMessagePositionMapping mapping = null;
BatchMessagePositionTemplate mapping = null;

final int[] colons = connectorResponse.getColonPositions();

if (cache.contains(query, colons)) {
mapping = cache.lookup(query, colons);
if (mapping == null) {
log.error("No mappings found. Creates new mappings for message: '{}'", connectorResponse.getMessage());
mapping = createMappingFromMessage(query, commands, connectorResponse);
log.error("No template found. Creates new template for message: '{}'", connectorResponse.getMessage());
mapping = createTemplateFor(query, commands, connectorResponse);
cache.insert(query, colons, mapping);
}
} else {
mapping = createMappingFromMessage(query, commands, connectorResponse);
mapping = createTemplateFor(query, commands, connectorResponse);
cache.insert(query, colons, mapping);
}

if (mapping == null) {
log.error("No mapping created for: '{}'", connectorResponse.getMessage());
log.error("No template created for: '{}'", connectorResponse.getMessage());
}

return mapping;
}

private BatchMessagePositionMapping createMappingFromMessage(final String query, final List<ObdCommand> commands,
private BatchMessagePositionTemplate createTemplateFor(final String query, final List<ObdCommand> commands,
final ConnectorResponse connectorResponse) {

final String predictedAnswerCode = commands.iterator().next().getPid().getPredictedSuccessCode();
Expand All @@ -88,10 +92,11 @@ private BatchMessagePositionMapping createMappingFromMessage(final String query,
final int codeIndexOf = connectorResponse.indexOf(predictedAnswerCode.getBytes(), predictedAnswerCode.length(),
colonFirstIndexOf > 0 ? colonFirstIndexOf : 0);


if (codeIndexOf == 0 || codeIndexOf == 3 || codeIndexOf == 5
|| (colonFirstIndexOf > 0 && (codeIndexOf - colonFirstIndexOf) == 1)) {

final BatchMessagePositionMapping result = new BatchMessagePositionMapping();
final BatchMessagePositionTemplate result = new BatchMessagePositionTemplate();

int start = codeIndexOf;

Expand All @@ -116,11 +121,11 @@ private BatchMessagePositionMapping createMappingFromMessage(final String query,
pidId = id;

if (pidLength == ConnectorResponse.TWO_TOKENS_LENGTH) {
pidId = pidId.substring(0, ConnectorResponse.TOKEN_LENGTH) +delim +
pidId.substring(ConnectorResponse.TOKEN_LENGTH, ConnectorResponse.TWO_TOKENS_LENGTH);
pidId = pidId.substring(0, ConnectorResponse.TOKEN_LENGTH) + delim + pidId
.substring(ConnectorResponse.TOKEN_LENGTH, ConnectorResponse.TWO_TOKENS_LENGTH);
pidLength = pidId.length();
pidIdIndexOf = connectorResponse.indexOf(pidId.getBytes(), pidLength, start);

if (log.isDebugEnabled()) {
log.debug("Another iteration. Found pid={}, indexOf={}", pidId, pidIdIndexOf);
}
Expand Down Expand Up @@ -158,11 +163,16 @@ private BatchMessagePositionMapping createMappingFromMessage(final String query,
}


result.getMappings().add(new PIDsPositionMapping(command, start, end));
result.getTemplates().add(new PIDPositionTemplate(command, start, end));
continue;
}

return result;
if (batchPolicy.isStrictValidationEnabled() && result.getTemplates().size() != commands.size()) {
log.error("Did not find all PIDs within given message template. "
+ "Found={}, expected={}",
result.getTemplates().size(), commands.size());
} else {
return result;
}
} else {
log.warn("Answer code for query: '{}' was not correct: {}. Predicated answer code: {}. "
+ "Predicted code index: {}, First colon index: {}. Colons: {}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
@NoArgsConstructor(access = AccessLevel.PACKAGE)
final class MappingsCache {

private final Map<String, BatchMessagePositionMapping> mappings = new HashMap<>();
private final Map<String, BatchMessagePositionTemplate> mappings = new HashMap<>();

BatchMessagePositionMapping lookup(final String query, final int[] delimeters) {
BatchMessagePositionTemplate lookup(final String query, final int[] delimeters) {
final String key = toKey(query, delimeters);
final BatchMessagePositionMapping mapping = mappings.get(key);
final BatchMessagePositionTemplate mapping = mappings.get(key);

if (mapping == null) {
log.error("no mapping found for {}", key);
Expand All @@ -51,7 +51,7 @@ boolean contains(final String query, final int[] delimeters) {
return mappings.containsKey(toKey(query, delimeters));
}

void insert(final String query, final int[] delimeters, BatchMessagePositionMapping mapping) {
void insert(final String query, final int[] delimeters, BatchMessagePositionTemplate mapping) {
mappings.put(toKey(query, delimeters), mapping);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

@ToString
@AllArgsConstructor(access = AccessLevel.PACKAGE)
final class PIDsPositionMapping {
final class PIDPositionTemplate {

@Getter
private final ObdCommand command;
Expand Down
118 changes: 118 additions & 0 deletions src/test/java/org/obd/metrics/codec/batch/CodecTestRunner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* Copyright 2019-2024, Tomasz Żebrowski
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.obd.metrics.codec.batch;

import static org.obd.metrics.codec.batch.decoder.BatchMessageBuilder.instance;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.assertj.core.api.Assertions;
import org.obd.metrics.PIDsRegistry;
import org.obd.metrics.PIDsRegistryFactory;
import org.obd.metrics.api.model.Adjustments;
import org.obd.metrics.codec.CodecRegistry;
import org.obd.metrics.codec.formula.FormulaEvaluatorConfig;
import org.obd.metrics.command.obd.ObdCommand;
import org.obd.metrics.transport.message.ConnectorResponse;
import org.obd.metrics.transport.message.ConnectorResponseFactory;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
abstract class CodecTestRunner {

static enum ValidationStrategy {
DEFAULT, INVALID_DATA
}

@RequiredArgsConstructor
static class ValidationInput {
@Getter
private final Map<String, Object> expectedValues;

@Getter
private final String message;

@Getter
private ValidationStrategy strategy = ValidationStrategy.DEFAULT;

public ValidationInput(Map<String, Object> expectedValues, String message, ValidationStrategy strategy) {
this.expectedValues = expectedValues;
this.message = message;
this.strategy = strategy;
}

}

protected void runTest(final String query, List<ValidationInput> input) {
runTest(query, input, Adjustments.DEFAULT, "giulia_2.0_gme.json", "mode01.json");
}

protected void runTest(final String query, List<ValidationInput> input, Adjustments adjustments,
String... resource) {
final CodecRegistry codecRegistry = CodecRegistry.builder().adjustments(adjustments)
.formulaEvaluatorConfig(FormulaEvaluatorConfig.builder().debug(true).build()).build();

final PIDsRegistry registry = PIDsRegistryFactory.get(resource);

final List<ObdCommand> commands = Arrays.asList(query.split(" ")).stream()
.filter(id -> registry.findBy(id) != null).map(pid -> new ObdCommand(registry.findBy(pid)))
.collect(Collectors.toList());
final BatchCodec codec = BatchCodec.builder().query(query).adjustments(adjustments).commands(commands).build();

for (final ValidationInput validationInput : input) {

final byte[] messageBytes = validationInput.getMessage().getBytes();
final Map<ObdCommand, ConnectorResponse> values = codec.decode(ConnectorResponseFactory.wrap(messageBytes));

final ConnectorResponse connectorResponse = instance(messageBytes);

if (validationInput.getStrategy() == ValidationStrategy.DEFAULT) {
Assertions.assertThat(values).isNotEmpty();
Assertions.assertThat(values).hasSize(commands.size());

for (final ObdCommand cmd : commands) {
Assertions.assertThat(values).containsEntry(cmd, connectorResponse);
}

commands.forEach(c -> {
final ConnectorResponse cr = values.get(c);
final Object value = codecRegistry.findCodec(c.getPid()).decode(c.getPid(), cr);
final String pid = c.getPid().getPid();
final Object expected = validationInput.getExpectedValues().get(pid);
if (expected != null) {
log.debug("PID={}, expected={}, evaluated={},mapping={}", pid, expected, value, cr);

Assertions.assertThat(value)
.overridingErrorMessage("PID: %s, expected: %s, evaluated=%s", pid, expected, value)
.isEqualTo(expected);
}
});
} else {
Assertions.assertThat(values).isEmpty();
}
}
}
}
Loading

0 comments on commit a9aa168

Please sign in to comment.