From 35f386b976df6a71b8ffde1ee15ed020cdb66266 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 17 Dec 2024 16:35:05 -0700 Subject: [PATCH 001/128] style: reformat Asn1EncodedDataRouter and Asn1CommandManager --- .../ode/services/asn1/Asn1CommandManager.java | 251 +++++++++--------- .../services/asn1/Asn1EncodedDataRouter.java | 12 +- 2 files changed, 136 insertions(+), 127 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index 2e3db752d..7d461d88c 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -1,4 +1,4 @@ -/******************************************************************************* +/*============================================================================= * Copyright 2018 572682 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -13,10 +13,14 @@ * License for the specific language governing permissions and limitations under * the License. ******************************************************************************/ + package us.dot.its.jpo.ode.services.asn1; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -36,8 +40,8 @@ import us.dot.its.jpo.ode.plugin.SituationDataWarehouse.SDW; import us.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData; import us.dot.its.jpo.ode.plugin.j2735.builders.GeoRegionBuilder; -import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.rsu.RsuDepositor; +import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; import us.dot.its.jpo.ode.util.JsonUtils; @@ -46,164 +50,169 @@ import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; import us.dot.its.jpo.ode.wrapper.MessageProducer; -import java.text.ParseException; -import java.util.HashMap; -import java.util.Map; - @Slf4j public class Asn1CommandManager { - public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; - - public static class Asn1CommandManagerException extends Exception { + public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; - private static final long serialVersionUID = 1L; + public static class Asn1CommandManagerException extends Exception { - public Asn1CommandManagerException(String string) { - super(string); - } - - public Asn1CommandManagerException(String msg, Exception e) { - super(msg, e); - } + private static final long serialVersionUID = 1L; + public Asn1CommandManagerException(String string) { + super(string); } - private final String signatureUri; - - private MessageProducer stringMessageProducer; - - private String depositTopic; - private RsuDepositor rsuDepositor; - - public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, SDXDepositorTopics sdxDepositorTopics, RsuProperties rsuProperties, SecurityServicesProperties securityServicesProperties) { - this.signatureUri = securityServicesProperties.getSignatureEndpoint(); - - try { - this.rsuDepositor = new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); - this.rsuDepositor.start(); - this.stringMessageProducer = MessageProducer.defaultStringMessageProducer(odeKafkaProperties.getBrokers(), - odeKafkaProperties.getKafkaType(), - odeKafkaProperties.getDisabledTopics()); - this.depositTopic = sdxDepositorTopics.getInput(); - } catch (Exception e) { - String msg = "Error starting SDW depositor"; - EventLogger.logger.error(msg, e); - log.error(msg, e); - } + public Asn1CommandManagerException(String msg, Exception e) { + super(msg, e); } - public void depositToSdw(String depositObj) throws Asn1CommandManagerException { - stringMessageProducer.send(this.depositTopic, null, depositObj); - log.info("Published message to SDW deposit topic {}", this.depositTopic); - EventLogger.logger.info("Published message to SDW deposit topic"); - log.debug("Message deposited: {}", depositObj); - EventLogger.logger.debug("Message deposited: {}", depositObj); + } + + private final String signatureUri; + + private MessageProducer stringMessageProducer; + + private String depositTopic; + private RsuDepositor rsuDepositor; + + public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, + SDXDepositorTopics sdxDepositorTopics, RsuProperties rsuProperties, + SecurityServicesProperties securityServicesProperties) { + this.signatureUri = securityServicesProperties.getSignatureEndpoint(); + + try { + this.rsuDepositor = + new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); + this.rsuDepositor.start(); + this.stringMessageProducer = + MessageProducer.defaultStringMessageProducer(odeKafkaProperties.getBrokers(), + odeKafkaProperties.getKafkaType(), + odeKafkaProperties.getDisabledTopics()); + this.depositTopic = sdxDepositorTopics.getInput(); + } catch (Exception e) { + String msg = "Error starting SDW depositor"; + EventLogger.logger.error(msg, e); + log.error(msg, e); } + } - public void sendToRsus(ServiceRequest request, String encodedMsg) { - rsuDepositor.deposit(request, encodedMsg); - } + public void depositToSdw(String depositObj) throws Asn1CommandManagerException { + stringMessageProducer.send(this.depositTopic, null, depositObj); + log.info("Published message to SDW deposit topic {}", this.depositTopic); + EventLogger.logger.info("Published message to SDW deposit topic"); + log.debug("Message deposited: {}", depositObj); + EventLogger.logger.debug("Message deposited: {}", depositObj); + } - public String sendForSignature(String message, int sigValidityOverride) { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - Map map = new HashMap<>(); - map.put("message", message); - map.put("sigValidityOverride", Integer.toString(sigValidityOverride)); + public void sendToRsus(ServiceRequest request, String encodedMsg) { + rsuDepositor.deposit(request, encodedMsg); + } - HttpEntity> entity = new HttpEntity<>(map, headers); - RestTemplate template = new RestTemplate(); + public String sendForSignature(String message, int sigValidityOverride) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + Map map = new HashMap<>(); + map.put("message", message); + map.put("sigValidityOverride", Integer.toString(sigValidityOverride)); - log.info("Sending data to security services module with validity override at {} to be signed", - signatureUri); - log.debug("Data to be signed: {}", entity); + HttpEntity> entity = new HttpEntity<>(map, headers); + RestTemplate template = new RestTemplate(); - ResponseEntity respEntity = template.postForEntity(signatureUri, entity, String.class); + log.info("Sending data to security services module with validity override at {} to be signed", + signatureUri); + log.debug("Data to be signed: {}", entity); - log.debug("Security services module response: {}", respEntity); + ResponseEntity respEntity = template.postForEntity(signatureUri, entity, String.class); - return respEntity.getBody(); - } - - public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { + log.debug("Security services module response: {}", respEntity); - SDW sdw = request.getSdw(); - SNMP snmp = request.getSnmp(); - DdsAdvisorySituationData asd; + return respEntity.getBody(); + } - byte sendToRsu = request.getRsus() != null ? DdsAdvisorySituationData.RSU : DdsAdvisorySituationData.NONE; - byte distroType = (byte) (DdsAdvisorySituationData.IP | sendToRsu); + public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { - String outputXml = null; - try { - if (null != snmp) { + SDW sdw = request.getSdw(); + SNMP snmp = request.getSnmp(); + DdsAdvisorySituationData asd; - asd = new DdsAdvisorySituationData() - .setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null) - .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())).setTimeToLive(sdw.getTtl()) - .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - } else { - asd = new DdsAdvisorySituationData() - .setAsdmDetails(sdw.getDeliverystart(), sdw.getDeliverystop(), distroType, null) - .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())).setTimeToLive(sdw.getTtl()) - .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - } + byte sendToRsu = + request.getRsus() != null ? DdsAdvisorySituationData.RSU : DdsAdvisorySituationData.NONE; + byte distroType = (byte) (DdsAdvisorySituationData.IP | sendToRsu); - OdeMsgPayload payload; + String outputXml = null; + try { + if (null != snmp) { - ObjectNode dataBodyObj = JsonUtils.newNode(); - ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); - ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); - admDetailsObj.remove("advisoryMessage"); - admDetailsObj.put("advisoryMessage", signedMsg); + asd = new DdsAdvisorySituationData() + .setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null) + .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) + .setTimeToLive(sdw.getTtl()) + .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); + } else { + asd = new DdsAdvisorySituationData() + .setAsdmDetails(sdw.getDeliverystart(), sdw.getDeliverystop(), distroType, null) + .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) + .setTimeToLive(sdw.getTtl()) + .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); + } - dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); + OdeMsgPayload payload; - payload = new OdeAsdPayload(asd); + ObjectNode dataBodyObj = JsonUtils.newNode(); + ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); + ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); + admDetailsObj.remove("advisoryMessage"); + admDetailsObj.put("advisoryMessage", signedMsg); - ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); - payloadObj.set(AppContext.DATA_STRING, dataBodyObj); + dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); - OdeMsgMetadata metadata = new OdeMsgMetadata(payload); - ObjectNode metaObject = JsonUtils.toObjectNode(metadata.toJson()); + payload = new OdeAsdPayload(asd); - ObjectNode requestObj = JsonUtils.toObjectNode(JsonUtils.toJson(request, false)); + ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); + payloadObj.set(AppContext.DATA_STRING, dataBodyObj); - requestObj.remove("tim"); + OdeMsgMetadata metadata = new OdeMsgMetadata(payload); + ObjectNode metaObject = JsonUtils.toObjectNode(metadata.toJson()); - metaObject.set("request", requestObj); + ObjectNode requestObj = JsonUtils.toObjectNode(JsonUtils.toJson(request, false)); - ArrayNode encodings = buildEncodings(); - ObjectNode enc = XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, encodings); - metaObject.set(AppContext.ENCODINGS_STRING, enc); + requestObj.remove("tim"); - ObjectNode message = JsonUtils.newNode(); - message.set(AppContext.METADATA_STRING, metaObject); - message.set(AppContext.PAYLOAD_STRING, payloadObj); + metaObject.set("request", requestObj); - ObjectNode root = JsonUtils.newNode(); - root.set(AppContext.ODE_ASN1_DATA, message); + ArrayNode encodings = buildEncodings(); + ObjectNode enc = + XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, encodings); + metaObject.set(AppContext.ENCODINGS_STRING, enc); - outputXml = XmlUtils.toXmlStatic(root); + ObjectNode message = JsonUtils.newNode(); + message.set(AppContext.METADATA_STRING, metaObject); + message.set(AppContext.PAYLOAD_STRING, payloadObj); - // remove the surrounding - outputXml = outputXml.replace("", ""); - outputXml = outputXml.replace("", ""); + ObjectNode root = JsonUtils.newNode(); + root.set(AppContext.ODE_ASN1_DATA, message); - } catch (ParseException | JsonUtilsException | XmlUtilsException e) { - log.error("Parsing exception thrown while populating ASD structure: ", e); - } + outputXml = XmlUtils.toXmlStatic(root); - log.debug("Fully crafted ASD to be encoded: {}", outputXml); + // remove the surrounding + outputXml = outputXml.replace("", ""); + outputXml = outputXml.replace("", ""); - return outputXml; + } catch (ParseException | JsonUtilsException | XmlUtilsException e) { + log.error("Parsing exception thrown while populating ASD structure: ", e); } - public static ArrayNode buildEncodings() throws JsonUtilsException { - ArrayNode encodings = JsonUtils.newArrayNode(); - encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, ADVISORY_SITUATION_DATA_STRING, - EncodingRule.UPER)); - return encodings; - } + log.debug("Fully crafted ASD to be encoded: {}", outputXml); + + return outputXml; + } + + public static ArrayNode buildEncodings() throws JsonUtilsException { + ArrayNode encodings = JsonUtils.newArrayNode(); + encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, + ADVISORY_SITUATION_DATA_STRING, + EncodingRule.UPER)); + return encodings; + } } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 297dcaade..d318053f1 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -1,4 +1,4 @@ -/******************************************************************************* +/*============================================================================= * Copyright 2018 572682 * *

Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -76,7 +76,7 @@ public Asn1EncodedDataRouterException(String string) { private final boolean dataSigningEnabledRSU; /** - * Instantiates the Asn1EncodedDataRouter to actively consume from Kafka and route the + * Instantiates the Asn1EncodedDataRouter to actively consume from Kafka and route * the encoded TIM messages to the SDX and RSUs. * * @param odeKafkaProperties The Kafka properties used to consume and produce to Kafka @@ -103,8 +103,8 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, odeKafkaProperties.getDisabledTopics()); this.asn1CommandManager = new Asn1CommandManager( - odeKafkaProperties, - sdxDepositorTopics, + odeKafkaProperties, + sdxDepositorTopics, rsuProperties, securityServicesProperties); this.dataSigningEnabledSDW = securityServicesProperties.getIsSdwSigningEnabled(); @@ -165,7 +165,7 @@ public Object process(String consumedData) { + TimTransmogrifier.REQUEST_STRING + "' object in the encoder response"); } } catch (Exception e) { - String msg = "Error in processing received message from ASN.1 Encoder module: " + String msg = "Error in processing received message from ASN.1 Encoder module: " + consumedData; if (log.isDebugEnabled()) { // print error message and stack trace @@ -403,7 +403,7 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu // get max duration time and convert from minutes to milliseconds (unsigned // integer valid 0 to 2^32-1 in units of // milliseconds.) from metadata - int maxDurationTime = Integer.valueOf(metadataObjs.get("maxDurationTime").toString()) + int maxDurationTime = Integer.valueOf(metadataObjs.get("maxDurationTime").toString()) * 60 * 1000; String timpacketID = metadataObjs.getString("odePacketID"); String timStartDateTime = metadataObjs.getString("odeTimStartDateTime"); From f7000c93647a824f35f5fe8e6a149c6fb470a617 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 17 Dec 2024 16:44:03 -0700 Subject: [PATCH 002/128] chore: delete disabled tests from Asn1EncodedDataRouterTest to start from clean slate --- .../asn1/Asn1EncodedDataRouterTest.java | 145 +----------------- 1 file changed, 1 insertion(+), 144 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index d18fcaba0..befe3cace 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -15,149 +15,6 @@ ******************************************************************************/ package us.dot.its.jpo.ode.services.asn1; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import mockit.Expectations; -import mockit.Mocked; -import mockit.Tested; -import us.dot.its.jpo.ode.context.AppContext; -import us.dot.its.jpo.ode.traveler.TimTransmogrifier; -import us.dot.its.jpo.ode.util.XmlUtils; -import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; - -public class Asn1EncodedDataRouterTest { - - @Tested - Asn1EncodedDataRouter testAsn1EncodedDataRouter; - - -// @Capturing -// MessageProducer capturingMessageProducer; -// -// @Capturing -// Asn1CommandManager capturingAsn1CommandManager; -// -// @Capturing -// XmlUtils capturingXmlUtils; - - @Test @Disabled - public void testNoRequest(@Mocked JSONObject mockJSONObject) throws XmlUtilsException { - new Expectations() {{ - XmlUtils.toJSONObject(anyString); - result = mockJSONObject; - - mockJSONObject.has("request"); - result = false; - }}; - testAsn1EncodedDataRouter.process("stringthing"); - } - - @Test @Disabled - public void testNoRsus(@Mocked JSONObject mockJSONObject) throws XmlUtilsException { - new Expectations() {{ - XmlUtils.toJSONObject(anyString); - result = mockJSONObject; - - mockJSONObject.has("request"); - result = true; - - mockJSONObject.has("rsus"); - result = false; - }}; - testAsn1EncodedDataRouter.process("stringthing"); - } - - @Test @Disabled - public void testSingleRsu(@Mocked JSONObject mockJSONObject) throws XmlUtilsException { - try { - new Expectations() {{ - XmlUtils.toJSONObject(anyString); - result = mockJSONObject; - - mockJSONObject.has("request"); - result = true; - - mockJSONObject.has("rsus"); - result = true; - - mockJSONObject.get("rsus"); - //result = new JSONObject(); - }}; - } catch (XmlUtilsException e) { - - e.printStackTrace(); - } catch (JSONException e) { - - e.printStackTrace(); - } - testAsn1EncodedDataRouter.process("stringthing"); - } - - @Test @Disabled - public void testRsuArray(@Mocked JSONObject mockJSONObject) throws XmlUtilsException { - try { - new Expectations() {{ - XmlUtils.toJSONObject(anyString); - result = mockJSONObject; - - mockJSONObject.has("request"); - result = true; - - mockJSONObject.has("rsus"); - result = true; - - mockJSONObject.get("rsus"); - result = new JSONArray(); - }}; - } catch (XmlUtilsException e) { - - e.printStackTrace(); - } catch (JSONException e) { - - e.printStackTrace(); - } - testAsn1EncodedDataRouter.process("stringthing"); - } - - @Test @Disabled - public void testWithASD(@Mocked JSONObject mockJSONObject) throws XmlUtilsException { - try { - new Expectations() {{ - XmlUtils.toJSONObject(anyString); - result = mockJSONObject; - - mockJSONObject.getJSONObject(AppContext.METADATA_STRING); - result = mockJSONObject; - - mockJSONObject.has(TimTransmogrifier.REQUEST_STRING); - result = true; - - mockJSONObject.getJSONObject(TimTransmogrifier.REQUEST_STRING); - result = mockJSONObject; - - mockJSONObject.has(TimTransmogrifier.RSUS_STRING); - result = true; - times = 2; - - mockJSONObject.get(TimTransmogrifier.RSUS_STRING); - result = mockJSONObject; - times = 2; - - mockJSONObject.has(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING); - result = true; - }}; - } catch (XmlUtilsException e) { - - e.printStackTrace(); - } catch (JSONException e) { - - e.printStackTrace(); - } - testAsn1EncodedDataRouter.process("stringthing"); - } +class Asn1EncodedDataRouterTest { } From 62b34b37532f0e616088df92054c5857087d3be7 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 17 Dec 2024 16:50:49 -0700 Subject: [PATCH 003/128] refactor: extract nested logic to named methods --- .../services/asn1/Asn1EncodedDataRouter.java | 142 +++++++++--------- 1 file changed, 75 insertions(+), 67 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index d318053f1..d079d3608 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -230,83 +230,89 @@ public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) { // - send to SDX if (!dataObj.has(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING)) { - log.debug("Unsigned message received"); - // We don't have ASD, therefore it must be just a MessageFrame that needs to be - // signed - // No support for unsecured MessageFrame only payload. - // Cases 1 & 2 - // Sign and send to RSUs - - JSONObject mfObj = dataObj.getJSONObject(MESSAGE_FRAME); - - String hexEncodedTim = mfObj.getString(BYTES); - log.debug("Encoded message - phase 1: {}", hexEncodedTim); - // use Asnc1 library to decode the encoded tim returned from ASNC1; another - // class two blockers: decode the tim and decode the message-sign - - // Case 1: SNMP-deposit - if (dataSigningEnabledRSU && request.getRsus() != null) { - hexEncodedTim = signTIMAndProduceToExpireTopic(hexEncodedTim, consumedObj); + processSNMPDepositOnly(request, consumedObj, dataObj, metadataObj); + } else { + // We have encoded ASD. It could be either UNSECURED or secured. + if (dataSigningEnabledSDW && request.getSdw() != null) { + processSignedMessage(request, dataObj); } else { - // if header is present, strip it - if (isHeaderPresent(hexEncodedTim)) { - String header = hexEncodedTim.substring(0, hexEncodedTim.indexOf("001F") + 4); - log.debug("Stripping header from unsigned message: {}", header); - hexEncodedTim = stripHeader(hexEncodedTim); - mfObj.remove(BYTES); - mfObj.put(BYTES, hexEncodedTim); - dataObj.remove(MESSAGE_FRAME); - dataObj.put(MESSAGE_FRAME, mfObj); - consumedObj.remove(AppContext.PAYLOAD_STRING); - consumedObj.put(AppContext.PAYLOAD_STRING, dataObj); - } + processEncodedTimUnsecured(request, consumedObj); } + } + } - if (null != request.getSnmp() && null != request.getRsus() && null != hexEncodedTim) { - log.info("Sending message to RSUs..."); - asn1CommandManager.sendToRsus(request, hexEncodedTim); + private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { + log.debug("Signed message received. Depositing it to SDW."); + // We have a ASD with signed MessageFrame + // Case 3 + JSONObject asdObj = dataObj.getJSONObject( + Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING); + try { + JSONObject deposit = new JSONObject(); + deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); + deposit.put("encodedMsg", asdObj.getString(BYTES)); + asn1CommandManager.depositToSdw(deposit.toString()); + } catch (JSONException | Asn1CommandManagerException e) { + String msg = ERROR_ON_SDX_DEPOSIT; + log.error(msg, e); + } + } + + private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedObj, JSONObject dataObj, + JSONObject metadataObj) { + log.debug("Unsigned message received"); + // We don't have ASD, therefore it must be just a MessageFrame that needs to be + // signed + // No support for unsecured MessageFrame only payload. + // Cases 1 & 2 + // Sign and send to RSUs + + JSONObject mfObj = dataObj.getJSONObject(MESSAGE_FRAME); + + String hexEncodedTim = mfObj.getString(BYTES); + log.debug("Encoded message - phase 1: {}", hexEncodedTim); + // use Asnc1 library to decode the encoded tim returned from ASNC1; another + // class two blockers: decode the tim and decode the message-sign + + // Case 1: SNMP-deposit + if (dataSigningEnabledRSU && request.getRsus() != null) { + hexEncodedTim = signTIMAndProduceToExpireTopic(hexEncodedTim, consumedObj); + } else { + // if header is present, strip it + if (isHeaderPresent(hexEncodedTim)) { + String header = hexEncodedTim.substring(0, hexEncodedTim.indexOf("001F") + 4); + log.debug("Stripping header from unsigned message: {}", header); + hexEncodedTim = stripHeader(hexEncodedTim); + mfObj.remove(BYTES); + mfObj.put(BYTES, hexEncodedTim); + dataObj.remove(MESSAGE_FRAME); + dataObj.put(MESSAGE_FRAME, mfObj); + consumedObj.remove(AppContext.PAYLOAD_STRING); + consumedObj.put(AppContext.PAYLOAD_STRING, dataObj); } + } - hexEncodedTim = mfObj.getString(BYTES); + if (null != request.getSnmp() && null != request.getRsus() && null != hexEncodedTim) { + log.info("Sending message to RSUs..."); + asn1CommandManager.sendToRsus(request, hexEncodedTim); + } - // Case 2: SDX-deposit - if (dataSigningEnabledSDW && request.getSdw() != null) { - hexEncodedTim = signTIMAndProduceToExpireTopic(hexEncodedTim, consumedObj); - } + hexEncodedTim = mfObj.getString(BYTES); - // Deposit encoded & signed TIM to TMC-filtered topic if TMC-generated - depositToFilteredTopic(metadataObj, hexEncodedTim); - if (request.getSdw() != null) { - // Case 2 only + // Case 2: SDX-deposit + if (dataSigningEnabledSDW && request.getSdw() != null) { + hexEncodedTim = signTIMAndProduceToExpireTopic(hexEncodedTim, consumedObj); + } - log.debug("Publishing message for round 2 encoding!"); - String xmlizedMessage = asn1CommandManager.packageSignedTimIntoAsd(request, hexEncodedTim); + // Deposit encoded & signed TIM to TMC-filtered topic if TMC-generated + depositToFilteredTopic(metadataObj, hexEncodedTim); + if (request.getSdw() != null) { + // Case 2 only - stringMsgProducer.send(asn1CoderTopics.getEncoderInput(), null, xmlizedMessage); - } + log.debug("Publishing message for round 2 encoding!"); + String xmlizedMessage = asn1CommandManager.packageSignedTimIntoAsd(request, hexEncodedTim); - } else { - // We have encoded ASD. It could be either UNSECURED or secured. - if (dataSigningEnabledSDW && request.getSdw() != null) { - log.debug("Signed message received. Depositing it to SDW."); - // We have a ASD with signed MessageFrame - // Case 3 - JSONObject asdObj = dataObj.getJSONObject( - Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING); - try { - JSONObject deposit = new JSONObject(); - deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); - deposit.put("encodedMsg", asdObj.getString(BYTES)); - asn1CommandManager.depositToSdw(deposit.toString()); - } catch (JSONException | Asn1CommandManagerException e) { - String msg = ERROR_ON_SDX_DEPOSIT; - log.error(msg, e); - } - } else { - log.debug("Unsigned ASD received. Depositing it to SDW."); - // We have ASD with UNSECURED MessageFrame - processEncodedTimUnsecured(request, consumedObj); - } + stringMsgProducer.send(asn1CoderTopics.getEncoderInput(), null, xmlizedMessage); } } @@ -317,6 +323,8 @@ public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) { * @param consumedObj The consumed JSON object */ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consumedObj) { + log.debug("Unsigned ASD received. Depositing it to SDW."); + // We have ASD with UNSECURED MessageFrame // Send TIMs and record results HashMap responseList = new HashMap<>(); JSONObject metadataObj = consumedObj.getJSONObject(AppContext.METADATA_STRING); From d04010d3bbbe62916c32da97226360f08bc2a4c9 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 09:16:34 -0700 Subject: [PATCH 004/128] test: add asn1-encoder-output-tim.xml Generated by sending tim to deposit tim endpoint and grabbing value produced to the `topic.Asn1EncoderOutput` topic via kafka-ui --- .../services/asn1/asn1-encoder-output-tim.xml | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-tim.xml diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-tim.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-tim.xml new file mode 100644 index 000000000..4c0a4f497 --- /dev/null +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-tim.xml @@ -0,0 +1,78 @@ + + + + us.dot.its.jpo.ode.model.OdeAsdPayload + + b01f24dc-4822-413f-8ce5-6a8f93346b20 + 1 + 0 + 0 + 0 + + 2024-12-18T16:14:09.690Z + 7 + 297 + 2024-02-29T14:15:47.000Z + TMC + false + 03B027CF2071328E12 + 2024-03-08T16:37:05.414Z + + + 3 + POST + + + + + 38.98721843900006 + -104.76767069499999 + + + 38.96666515900006 + -104.74048299899994 + + + oneday + 6B573067 + + + + + MessageFrame + MessageFrame + UPER + + + Ieee1609Dot2Data + Ieee1609Dot2Data + COER + + + AdvisorySituationData + AdvisorySituationData + UPER + + + + + us.dot.its.jpo.ode.model.OdeHexByteArray + + + + 001F808470114F3703B027CF2071328E120F775D9B0301C2670F374166BC3027FFF93F40BE628129A007F93937E1CF5AC98DFA706A5F6D09AB766C1B3780000000099C3CDD059AF0C084E2302000844033611407401733CDC2D6203FDA1756398358ED27557C6856FBAAC1C3A0F8D9C221DD858A60E0ADA596124567F2B182001002009EEEBB3600 + + + + + 03808188001F808470114F3703B027CF2071328E120F775D9B0301C2670F374166BC3027FFF93F40BE628129A007F93937E1CF5AC98DFA706A5F6D09AB766C1B3780000000099C3CDD059AF0C084E2302000844033611407401733CDC2D6203FDA1756398358ED27557C6856FBAAC1C3A0F8D9C221DD858A60E0ADA596124567F2B182001002009EEEBB3600 + + + + + C4400000000B72268E26B57306742670F19C166BC56E099BD80B859B761C3DB913471202000007FC000007FC11807010310003F0108E0229E6E07604F9E40E2651C241EEEBB36060384CE1E6E82CD78604FFFF27E817CC50253400FF2726FC39EB5931BF4E0D4BEDA1356ECD8366F00000000133879BA0B35E18109C4604001088066C2280E802E679B85AC407FB42EAC7306B1DA4EAAF8D0ADF755838741F1B38443BB0B14C1C15B4B2C248ACFE56304002004013DDD766C000 + + + + + \ No newline at end of file From b51b14a7a3d3ccf7344f9ca464d01152438cb08c Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 09:32:39 -0700 Subject: [PATCH 005/128] refactor: encapsulate date setting logic for code clarity --- .../services/asn1/Asn1EncodedDataRouter.java | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index d079d3608..76f854e8d 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -425,27 +425,11 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu JSONObject timWithExpiration = new JSONObject(); timWithExpiration.put("packetID", timpacketID); timWithExpiration.put("startDateTime", timStartDateTime); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - try { - JSONObject jsonResult = JsonUtils - .toJSONObject((JsonUtils.toJSONObject(signedResponse).getString("result"))); - // messageExpiry uses unit of seconds - long messageExpiry = Long.parseLong(jsonResult.getString("message-expiry")); - timWithExpiration.put("expirationDate", dateFormat.format(new Date(messageExpiry * 1000))); - } catch (Exception e) { - log.error("Unable to get expiration date from signed messages response ", e); - timWithExpiration.put("expirationDate", "null"); - } + setExpiryDate(signedResponse, timWithExpiration, dateFormat); + setRequiredExpiryDate(dateFormat, timStartDateTime, maxDurationTime, timWithExpiration); - try { - Date parsedtimTimeStamp = dateFormat.parse(timStartDateTime); - Date requiredExpirationDate = new Date(); - requiredExpirationDate.setTime(parsedtimTimeStamp.getTime() + maxDurationTime); - timWithExpiration.put("requiredExpirationDate", dateFormat.format(requiredExpirationDate)); - } catch (Exception e) { - log.error("Unable to parse requiredExpirationDate ", e); - timWithExpiration.put("requiredExpirationDate", "null"); - } // publish to Tim expiration kafka stringMsgProducer.send(jsonTopics.getTimCertExpiration(), null, timWithExpiration.toString()); @@ -458,6 +442,33 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu return encodedTIM; } + private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String timStartDateTime, + int maxDurationTime, JSONObject timWithExpiration) { + try { + Date parsedtimTimeStamp = dateFormat.parse(timStartDateTime); + Date requiredExpirationDate = new Date(); + requiredExpirationDate.setTime(parsedtimTimeStamp.getTime() + maxDurationTime); + timWithExpiration.put("requiredExpirationDate", dateFormat.format(requiredExpirationDate)); + } catch (Exception e) { + log.error("Unable to parse requiredExpirationDate ", e); + timWithExpiration.put("requiredExpirationDate", "null"); + } + } + + private static void setExpiryDate(String signedResponse, JSONObject timWithExpiration, + SimpleDateFormat dateFormat) { + try { + JSONObject jsonResult = JsonUtils + .toJSONObject((JsonUtils.toJSONObject(signedResponse).getString("result"))); + // messageExpiry uses unit of seconds + long messageExpiry = Long.parseLong(jsonResult.getString("message-expiry")); + timWithExpiration.put("expirationDate", dateFormat.format(new Date(messageExpiry * 1000))); + } catch (Exception e) { + log.error("Unable to get expiration date from signed messages response ", e); + timWithExpiration.put("expirationDate", "null"); + } + } + /** * Checks if header is present in encoded message. */ From 23821e413bf633c7b037ce2115e4dcf6ece4974b Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 09:48:12 -0700 Subject: [PATCH 006/128] chore: rename asn1-encoder-output-tim.xml to asn1-encoder-output-unsigned-tim.xml --- ...ncoder-output-tim.xml => asn1-encoder-output-unsigned-tim.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/{asn1-encoder-output-tim.xml => asn1-encoder-output-unsigned-tim.xml} (100%) diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-tim.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-tim.xml rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml From 1cb9d970a3c0339ffeb0de40d74874d6fd72925e Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 12:02:28 -0700 Subject: [PATCH 007/128] refactor: pull object creation up to allow mocking of dependencies --- .../ode/services/asn1/Asn1CommandManager.java | 9 ++-- .../services/asn1/Asn1EncodedDataRouter.java | 54 ++++++++++--------- .../asn1/AsnCodecRouterServiceController.java | 53 ++++++++++-------- 3 files changed, 63 insertions(+), 53 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index 7d461d88c..ee90f0273 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -41,7 +41,6 @@ import us.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData; import us.dot.its.jpo.ode.plugin.j2735.builders.GeoRegionBuilder; import us.dot.its.jpo.ode.rsu.RsuDepositor; -import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; import us.dot.its.jpo.ode.util.JsonUtils; @@ -77,13 +76,13 @@ public Asn1CommandManagerException(String msg, Exception e) { private RsuDepositor rsuDepositor; public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, - SDXDepositorTopics sdxDepositorTopics, RsuProperties rsuProperties, - SecurityServicesProperties securityServicesProperties) { + SDXDepositorTopics sdxDepositorTopics, + SecurityServicesProperties securityServicesProperties, + RsuDepositor rsuDepositor) { this.signatureUri = securityServicesProperties.getSignatureEndpoint(); try { - this.rsuDepositor = - new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); + this.rsuDepositor = rsuDepositor; this.rsuDepositor.start(); this.stringMessageProducer = MessageProducer.defaultStringMessageProducer(odeKafkaProperties.getBrokers(), diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 76f854e8d..3866aa7fe 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -32,7 +32,7 @@ import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.model.OdeAsn1Data; import us.dot.its.jpo.ode.plugin.ServiceRequest; -import us.dot.its.jpo.ode.rsu.RsuProperties; +import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.services.asn1.Asn1CommandManager.Asn1CommandManagerException; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; @@ -44,9 +44,8 @@ import us.dot.its.jpo.ode.wrapper.MessageProducer; /** - * The Asn1EncodedDataRouter is responsible for routing encoded TIM messages - * that are consumed from the Kafka topic.Asn1EncoderOutput topic and decide - * whether to route to the SDX or an RSU. + * The Asn1EncodedDataRouter is responsible for routing encoded TIM messages that are consumed from + * the Kafka topic.Asn1EncoderOutput topic and decide whether to route to the SDX or an RSU. **/ @Slf4j public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor { @@ -59,6 +58,7 @@ public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - odeKafkaProperties.getBrokers(), this.getClass().getSimpleName(), encoderRouter); + MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( + odeKafkaProperties.getBrokers(), this.getClass().getSimpleName(), encoderRouter); - encoderConsumer.setName("Asn1EncoderConsumer"); - encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); - } + encoderConsumer.setName("Asn1EncoderConsumer"); + encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); + } } From f4147e4deb8723e81ab48ae26a71c6b26436c602 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 12:07:21 -0700 Subject: [PATCH 008/128] test: initial processSNMPDeposit test setup. encoder not consuming --- .../asn1/Asn1EncodedDataRouterTest.java | 135 +++++++++++++++++- 1 file changed, 132 insertions(+), 3 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index befe3cace..84848d1f4 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -1,20 +1,149 @@ /******************************************************************************* * Copyright 2018 572682 - * + * * Licensed 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 us.dot.its.jpo.ode.services.asn1; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.kafka.KafkaProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.test.EmbeddedKafkaBroker; +import org.springframework.kafka.test.utils.KafkaTestUtils; +import org.springframework.test.annotation.DirtiesContext; +import us.dot.its.jpo.ode.OdeTimJsonTopology; +import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; +import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; +import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; +import us.dot.its.jpo.ode.kafka.topics.JsonTopics; +import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; +import us.dot.its.jpo.ode.rsu.RsuDepositor; +import us.dot.its.jpo.ode.rsu.RsuProperties; +import us.dot.its.jpo.ode.security.SecurityServicesProperties; +import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; +import us.dot.its.jpo.ode.wrapper.MessageConsumer; + +@SpringBootTest( + properties = { + "ode.security-services.is-rsu-signing-enabled=false", + "ode.security-services.is-sdw-signing-enabled=false", + "ode.kafka.topics.json.tim-cert-expiration=topic.Asn1EncodedDataRouterTestTimCertExpiration", + "ode.kafka.topics.json.tim-tmc-filtered=topic.Asn1EncodedDataRouterTestTimTmcFiltered", + "ode.kafka.topics.asn1.encoder-input=topic.Asn1EncodedDataRouterTestEncoderInput", + "ode.kafka.topics.asn1.encoder-output=topic.Asn1EncodedDataRouterTestEncoderOutput" + }, + classes = { + KafkaProducerConfig.class, + KafkaProperties.class, + Asn1CoderTopics.class, + SDXDepositorTopics.class, + JsonTopics.class, + SecurityServicesProperties.class, + RsuProperties.class, + OdeKafkaProperties.class + } +) +@EnableConfigurationProperties +@DirtiesContext class Asn1EncodedDataRouterTest { + @Autowired + Asn1CoderTopics asn1CoderTopics; + @Autowired + JsonTopics jsonTopics; + @Autowired + SecurityServicesProperties securityServicesProperties; + @Autowired + RsuProperties rsuProperties; + @Autowired + OdeKafkaProperties odeKafkaProperties; + @Autowired + SDXDepositorTopics sdxDepositorTopics; + @Autowired + KafkaTemplate kafkaTemplate; + @Mock + RsuDepositor mockRsuDepositor; + @Mock + OdeTimJsonTopology mockOdeTimJsonTopology; + + EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); + + @Test + void processSNMPDeposit() throws IOException { + //`dataSigningEnabledRSU` AND request.getRsus() is not null AND recordGeneratedBy set to "TMC" + // - produced to getTimCertExpiration topic + // - produced to getEncoderInput topic + // - produced to getTimTmcFiltered + String[] topics = { + asn1CoderTopics.getEncoderInput(), + jsonTopics.getTimCertExpiration(), + asn1CoderTopics.getEncoderOutput(), + jsonTopics.getTimTmcFiltered() + }; + EmbeddedKafkaHolder.addTopics(topics); +// EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput()); + + // mock RsuDepositor + // mock Asn1CommandManager? + + Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( + odeKafkaProperties, + asn1CoderTopics, + jsonTopics, + sdxDepositorTopics, + securityServicesProperties, + mockRsuDepositor, + mockOdeTimJsonTopology); + + MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( + embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); + + encoderConsumer.setName("Asn1EncoderConsumer"); + encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); + + InputStream inputStream = getClass().getClassLoader() + .getResourceAsStream("us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml"); + assert inputStream != null; + var input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); + + var consumerProps = KafkaTestUtils.consumerProps( + "Asn1EncodedDataRouterTest-asasas", "false", embeddedKafka); + var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, + new StringDeserializer(), new StringDeserializer()); + var testConsumer = consumerFactory.createConsumer(); + embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topics); + + var encoderOutputRecord = KafkaTestUtils.getSingleRecord(testConsumer, asn1CoderTopics.getEncoderOutput()); + var timCertExpirationRecord = KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimCertExpiration()); + var timTmcFilteredRecord = KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimTmcFiltered()); + var encoderInputRecord = KafkaTestUtils.getSingleRecord(testConsumer, asn1CoderTopics.getEncoderInput()); + + assertEquals("", timCertExpirationRecord.value()); + assertEquals("", timTmcFilteredRecord.value()); + assertEquals("", encoderInputRecord.value()); + } + } From eed0a82cc064e43e15fbecae68ead1e59b996894 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 13:35:50 -0700 Subject: [PATCH 009/128] test: processEncodedTimUnsecured_depositsToSdxTopic --- .../asn1/Asn1EncodedDataRouterTest.java | 46 +++++++++---------- ...ected-asn1-encoded-router-sdx-deposit.json | 1 + 2 files changed, 24 insertions(+), 23 deletions(-) create mode 100644 jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 84848d1f4..118d56617 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import org.apache.kafka.common.serialization.StringDeserializer; +import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; @@ -52,7 +53,8 @@ "ode.kafka.topics.json.tim-cert-expiration=topic.Asn1EncodedDataRouterTestTimCertExpiration", "ode.kafka.topics.json.tim-tmc-filtered=topic.Asn1EncodedDataRouterTestTimTmcFiltered", "ode.kafka.topics.asn1.encoder-input=topic.Asn1EncodedDataRouterTestEncoderInput", - "ode.kafka.topics.asn1.encoder-output=topic.Asn1EncodedDataRouterTestEncoderOutput" + "ode.kafka.topics.asn1.encoder-output=topic.Asn1EncodedDataRouterTestEncoderOutput", + "ode.kafka.topics.sdx-depositor.input=topic.Asn1EncodedDataRouterTestSDXDepositor" }, classes = { KafkaProducerConfig.class, @@ -91,22 +93,14 @@ class Asn1EncodedDataRouterTest { EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Test - void processSNMPDeposit() throws IOException { - //`dataSigningEnabledRSU` AND request.getRsus() is not null AND recordGeneratedBy set to "TMC" - // - produced to getTimCertExpiration topic - // - produced to getEncoderInput topic - // - produced to getTimTmcFiltered + void processEncodedTimUnsecured_depositsToSdxTopic() throws IOException, InterruptedException { + String[] topics = { asn1CoderTopics.getEncoderInput(), - jsonTopics.getTimCertExpiration(), - asn1CoderTopics.getEncoderOutput(), - jsonTopics.getTimTmcFiltered() + sdxDepositorTopics.getInput() }; EmbeddedKafkaHolder.addTopics(topics); -// EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput()); - - // mock RsuDepositor - // mock Asn1CommandManager? + EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput()); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( odeKafkaProperties, @@ -123,11 +117,17 @@ void processSNMPDeposit() throws IOException { encoderConsumer.setName("Asn1EncoderConsumer"); encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); - InputStream inputStream = getClass().getClassLoader() - .getResourceAsStream("us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml"); + // Wait for encoderRouter to connect to the broker otherwise the test will fail :( + Thread.sleep(2000); + + var classLoader = getClass().getClassLoader(); + InputStream inputStream = classLoader + .getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml"); assert inputStream != null; var input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); + var completableFuture = kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); + Awaitility.await().until(completableFuture::isDone); var consumerProps = KafkaTestUtils.consumerProps( "Asn1EncodedDataRouterTest-asasas", "false", embeddedKafka); @@ -136,14 +136,14 @@ void processSNMPDeposit() throws IOException { var testConsumer = consumerFactory.createConsumer(); embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topics); - var encoderOutputRecord = KafkaTestUtils.getSingleRecord(testConsumer, asn1CoderTopics.getEncoderOutput()); - var timCertExpirationRecord = KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimCertExpiration()); - var timTmcFilteredRecord = KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimTmcFiltered()); - var encoderInputRecord = KafkaTestUtils.getSingleRecord(testConsumer, asn1CoderTopics.getEncoderInput()); + inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json"); + assert inputStream != null; + var expected = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - assertEquals("", timCertExpirationRecord.value()); - assertEquals("", timTmcFilteredRecord.value()); - assertEquals("", encoderInputRecord.value()); + var sdxDepositorRecord = + KafkaTestUtils.getSingleRecord(testConsumer, sdxDepositorTopics.getInput()); + assertEquals(expected, sdxDepositorRecord.value()); } } diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json new file mode 100644 index 000000000..5bcea7230 --- /dev/null +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json @@ -0,0 +1 @@ +{"encodedMsg":"C4400000000B72268E26B57306742670F19C166BC56E099BD80B859B761C3DB913471202000007FC000007FC11807010310003F0108E0229E6E07604F9E40E2651C241EEEBB36060384CE1E6E82CD78604FFFF27E817CC50253400FF2726FC39EB5931BF4E0D4BEDA1356ECD8366F00000000133879BA0B35E18109C4604001088066C2280E802E679B85AC407FB42EAC7306B1DA4EAAF8D0ADF755838741F1B38443BB0B14C1C15B4B2C248ACFE56304002004013DDD766C000"} \ No newline at end of file From c43c3e8428313697346d390cf8379d307530c1ed Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 14:14:31 -0700 Subject: [PATCH 010/128] test: processEncodedTimUnsecured_depositsToSdxTopic -> processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered --- .../asn1/Asn1EncodedDataRouterTest.java | 37 ++- .../asn1/asn1-encoder-output-unsigned-tim.xml | 2 +- ...expected-asn1-encoded-router-tim-json.json | 224 ++++++++++++++++++ .../asn1/expected-tim-tmc-filtered.json | 1 + 4 files changed, 252 insertions(+), 12 deletions(-) create mode 100644 jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json create mode 100644 jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 118d56617..fbb551cd5 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -78,8 +78,6 @@ class Asn1EncodedDataRouterTest { @Autowired SecurityServicesProperties securityServicesProperties; @Autowired - RsuProperties rsuProperties; - @Autowired OdeKafkaProperties odeKafkaProperties; @Autowired SDXDepositorTopics sdxDepositorTopics; @@ -87,21 +85,22 @@ class Asn1EncodedDataRouterTest { KafkaTemplate kafkaTemplate; @Mock RsuDepositor mockRsuDepositor; - @Mock - OdeTimJsonTopology mockOdeTimJsonTopology; EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Test - void processEncodedTimUnsecured_depositsToSdxTopic() throws IOException, InterruptedException { + void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() + throws IOException, InterruptedException { - String[] topics = { + String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), + jsonTopics.getTimTmcFiltered(), sdxDepositorTopics.getInput() }; - EmbeddedKafkaHolder.addTopics(topics); - EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput()); + EmbeddedKafkaHolder.addTopics(topicsForConsumption); + EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); + var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( odeKafkaProperties, asn1CoderTopics, @@ -109,7 +108,7 @@ void processEncodedTimUnsecured_depositsToSdxTopic() throws IOException, Interru sdxDepositorTopics, securityServicesProperties, mockRsuDepositor, - mockOdeTimJsonTopology); + odeTimJsonTopology); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); @@ -121,7 +120,14 @@ void processEncodedTimUnsecured_depositsToSdxTopic() throws IOException, Interru Thread.sleep(2000); var classLoader = getClass().getClassLoader(); - InputStream inputStream = classLoader + InputStream inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json"); + assert inputStream != null; + var odeJsonTim = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return + kafkaTemplate.send(jsonTopics.getTim(), "266e6742-40fb-4c9e-a6b0-72ed2dddddfe", odeJsonTim); + + inputStream = classLoader .getResourceAsStream( "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml"); assert inputStream != null; @@ -134,7 +140,7 @@ void processEncodedTimUnsecured_depositsToSdxTopic() throws IOException, Interru var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); var testConsumer = consumerFactory.createConsumer(); - embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topics); + embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topicsForConsumption); inputStream = classLoader.getResourceAsStream( "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json"); @@ -144,6 +150,15 @@ void processEncodedTimUnsecured_depositsToSdxTopic() throws IOException, Interru var sdxDepositorRecord = KafkaTestUtils.getSingleRecord(testConsumer, sdxDepositorTopics.getInput()); assertEquals(expected, sdxDepositorRecord.value()); + + inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); + assert inputStream != null; + var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + + var timTmcFilteredRecord = + KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimTmcFiltered()); + assertEquals(expectedTimTmcFiltered, timTmcFilteredRecord.value()); } } diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml index 4c0a4f497..11834db3a 100644 --- a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml @@ -3,7 +3,7 @@ us.dot.its.jpo.ode.model.OdeAsdPayload - b01f24dc-4822-413f-8ce5-6a8f93346b20 + 266e6742-40fb-4c9e-a6b0-72ed2dddddfe 1 0 0 diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json new file mode 100644 index 000000000..fa0a17775 --- /dev/null +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json @@ -0,0 +1,224 @@ +{ + "metadata": { + "payloadType": "us.dot.its.jpo.ode.model.OdeTimPayload", + "serialId": { + "streamId": "266e6742-40fb-4c9e-a6b0-72ed2dddddfe", + "bundleSize": 1, + "bundleId": 1, + "recordId": 0, + "serialNumber": 1 + }, + "odeReceivedAt": "2024-12-18T20:58:49.419Z", + "schemaVersion": 7, + "maxDurationTime": 297, + "recordGeneratedAt": "2024-02-29T14:15:47.000Z", + "recordGeneratedBy": "TMC", + "sanitized": false, + "odePacketID": "03B027CF2071328E12", + "odeTimStartDateTime": "2024-03-08T16:37:05.414Z", + "request": { + "ode": { + "version": 3, + "verb": "POST" + }, + "sdw": { + "serviceRegion": { + "nwCorner": { + "latitude": 38.98721843900006, + "longitude": -104.76767069499999 + }, + "seCorner": { + "latitude": 38.96666515900006, + "longitude": -104.74048299899994 + } + }, + "ttl": "oneday", + "recordId": "6B573067" + } + } + }, + "payload": { + "data": { + "msgCnt": 1, + "timeStamp": 85815, + "packetID": "03B027CF2071328E12", + "urlB": "null", + "dataFrames": [ + { + "notUsed": 0, + "frameType": "advisory", + "msgId": { + "roadSignID": { + "position": { + "lat": 389873128, + "long": -1047677947 + }, + "viewAngle": { + "from000-0to022-5degrees": true, + "from022-5to045-0degrees": true, + "from045-0to067-5degrees": true, + "from067-5to090-0degrees": true, + "from090-0to112-5degrees": true, + "from112-5to135-0degrees": true, + "from135-0to157-5degrees": true, + "from157-5to180-0degrees": true, + "from180-0to202-5degrees": true, + "from202-5to225-0degrees": true, + "from225-0to247-5degrees": true, + "from247-5to270-0degrees": true, + "from270-0to292-5degrees": true, + "from292-5to315-0degrees": true, + "from315-0to337-5degrees": true, + "from337-5to360-0degrees": true + }, + "mutcdCode": "warning" + } + }, + "startYear": 2024, + "startTime": 97477, + "durationTime": 297, + "priority": 5, + "notUsed1": 0, + "regions": [ + { + "name": "I_CO-21_SAT_6B573067", + "id": { + "region": 0, + "id": 0 + }, + "anchor": { + "lat": 389873128, + "long": -1047677947 + }, + "laneWidth": 5000, + "directionality": "both", + "closedPath": false, + "direction": { + "from000-0to022-5degrees": false, + "from022-5to045-0degrees": false, + "from045-0to067-5degrees": false, + "from067-5to090-0degrees": false, + "from090-0to112-5degrees": false, + "from112-5to135-0degrees": true, + "from135-0to157-5degrees": false, + "from157-5to180-0degrees": false, + "from180-0to202-5degrees": false, + "from202-5to225-0degrees": false, + "from225-0to247-5degrees": false, + "from247-5to270-0degrees": false, + "from270-0to292-5degrees": false, + "from292-5to315-0degrees": false, + "from315-0to337-5degrees": false, + "from337-5to360-0degrees": false + }, + "description": { + "path": { + "scale": 0, + "offset": { + "ll": { + "nodes": [ + { + "delta": { + "node-LL1": { + "lon": 1240, + "lat": -944 + } + } + }, + { + "delta": { + "node-LL4": { + "lon": 32814, + "lat": -24978 + } + } + }, + { + "delta": { + "node-LL3": { + "lon": 22048, + "lat": -16422 + } + } + }, + { + "delta": { + "node-LL3": { + "lon": 27335, + "lat": -20373 + } + } + }, + { + "delta": { + "node-LL4": { + "lon": 53877, + "lat": -41190 + } + } + }, + { + "delta": { + "node-LL3": { + "lon": 14301, + "lat": -10738 + } + } + }, + { + "delta": { + "node-LL4": { + "lon": 33763, + "lat": -25566 + } + } + }, + { + "delta": { + "node-LL4": { + "lon": 60460, + "lat": -46052 + } + } + }, + { + "delta": { + "node-LL3": { + "lon": 13974, + "lat": -10167 + } + } + }, + { + "delta": { + "node-LL3": { + "lon": 13305, + "lat": -10047 + } + } + } + ] + } + } + } + } + } + ], + "notUsed2": 0, + "notUsed3": 0, + "content": { + "workZone": [ + { + "item": { + "itis": 1025 + } + } + ] + }, + "url": "null" + } + ] + }, + "dataType": "us.dot.its.jpo.ode.plugin.j2735.travelerinformation.TravelerInformation" + } +} \ No newline at end of file diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json new file mode 100644 index 000000000..b9345b554 --- /dev/null +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json @@ -0,0 +1 @@ +{"metadata":{"request":{"ode":{"verb":"POST","version":3},"sdw":{"recordId":"6B573067","serviceRegion":{"nwCorner":{"latitude":38.98721843900006,"longitude":-104.76767069499999},"seCorner":{"latitude":38.96666515900006,"longitude":-104.74048299899994}},"ttl":"oneday"}},"recordGeneratedBy":"TMC","schemaVersion":7,"payloadType":"us.dot.its.jpo.ode.model.OdeTimPayload","odePacketID":"03B027CF2071328E12","serialId":{"recordId":0,"serialNumber":1,"streamId":"266e6742-40fb-4c9e-a6b0-72ed2dddddfe","bundleSize":1,"bundleId":1},"sanitized":false,"recordGeneratedAt":"2024-02-29T14:15:47.000Z","asn1":"001F808470114F3703B027CF2071328E120F775D9B0301C2670F374166BC3027FFF93F40BE628129A007F93937E1CF5AC98DFA706A5F6D09AB766C1B3780000000099C3CDD059AF0C084E2302000844033611407401733CDC2D6203FDA1756398358ED27557C6856FBAAC1C3A0F8D9C221DD858A60E0ADA596124567F2B182001002009EEEBB3600","maxDurationTime":297,"odeTimStartDateTime":"2024-03-08T16:37:05.414Z","odeReceivedAt":"2024-12-18T20:58:49.419Z"},"payload":{"data":{"timeStamp":85815,"packetID":"03B027CF2071328E12","urlB":"null","dataFrames":[{"durationTime":297,"regions":[{"closedPath":false,"anchor":{"lat":389873128,"long":-1047677947},"name":"I_CO-21_SAT_6B573067","laneWidth":5000,"directionality":"both","description":{"path":{"offset":{"ll":{"nodes":[{"delta":{"node-LL1":{"lon":1240,"lat":-944}}},{"delta":{"node-LL4":{"lon":32814,"lat":-24978}}},{"delta":{"node-LL3":{"lon":22048,"lat":-16422}}},{"delta":{"node-LL3":{"lon":27335,"lat":-20373}}},{"delta":{"node-LL4":{"lon":53877,"lat":-41190}}},{"delta":{"node-LL3":{"lon":14301,"lat":-10738}}},{"delta":{"node-LL4":{"lon":33763,"lat":-25566}}},{"delta":{"node-LL4":{"lon":60460,"lat":-46052}}},{"delta":{"node-LL3":{"lon":13974,"lat":-10167}}},{"delta":{"node-LL3":{"lon":13305,"lat":-10047}}}]}},"scale":0}},"id":{"id":0,"region":0},"direction":{"from315-0to337-5degrees":false,"from202-5to225-0degrees":false,"from067-5to090-0degrees":false,"from270-0to292-5degrees":false,"from247-5to270-0degrees":false,"from112-5to135-0degrees":true,"from292-5to315-0degrees":false,"from180-0to202-5degrees":false,"from022-5to045-0degrees":false,"from045-0to067-5degrees":false,"from157-5to180-0degrees":false,"from000-0to022-5degrees":false,"from135-0to157-5degrees":false,"from225-0to247-5degrees":false,"from337-5to360-0degrees":false,"from090-0to112-5degrees":false}}],"startYear":2024,"notUsed2":0,"msgId":{"roadSignID":{"viewAngle":{"from315-0to337-5degrees":true,"from202-5to225-0degrees":true,"from067-5to090-0degrees":true,"from270-0to292-5degrees":true,"from247-5to270-0degrees":true,"from112-5to135-0degrees":true,"from292-5to315-0degrees":true,"from180-0to202-5degrees":true,"from022-5to045-0degrees":true,"from045-0to067-5degrees":true,"from157-5to180-0degrees":true,"from000-0to022-5degrees":true,"from135-0to157-5degrees":true,"from225-0to247-5degrees":true,"from337-5to360-0degrees":true,"from090-0to112-5degrees":true},"mutcdCode":"warning","position":{"lat":389873128,"long":-1047677947}}},"notUsed3":0,"notUsed1":0,"priority":5,"content":{"workZone":[{"item":{"itis":1025}}]},"url":"null","notUsed":0,"frameType":"advisory","startTime":97477}],"msgCnt":1},"dataType":"us.dot.its.jpo.ode.plugin.j2735.travelerinformation.TravelerInformation"}} \ No newline at end of file From 785795a0c66aea558a1ad5136c9373857179843d Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 15:57:53 -0700 Subject: [PATCH 011/128] refactor: pass asn1CommandManager into Asn1EncodedDataRouter to improve DI and testability --- .../ode/services/asn1/Asn1EncodedDataRouter.java | 14 +++----------- .../asn1/AsnCodecRouterServiceController.java | 15 ++++++++++----- .../services/asn1/Asn1EncodedDataRouterTest.java | 13 +++++++++---- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 3866aa7fe..2698298fa 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -29,10 +29,8 @@ import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; -import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.model.OdeAsn1Data; import us.dot.its.jpo.ode.plugin.ServiceRequest; -import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.services.asn1.Asn1CommandManager.Asn1CommandManagerException; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; @@ -89,10 +87,9 @@ public Asn1EncodedDataRouterException(String string) { public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, Asn1CoderTopics asn1CoderTopics, JsonTopics jsonTopics, - SDXDepositorTopics sdxDepositorTopics, SecurityServicesProperties securityServicesProperties, - RsuDepositor rsuDepositor, - OdeTimJsonTopology odeTimJsonTopology) { + OdeTimJsonTopology odeTimJsonTopology, + Asn1CommandManager asn1CommandManager) { super(); this.asn1CoderTopics = asn1CoderTopics; @@ -103,12 +100,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, odeKafkaProperties.getKafkaType(), odeKafkaProperties.getDisabledTopics()); - this.asn1CommandManager = new Asn1CommandManager( - odeKafkaProperties, - sdxDepositorTopics, - securityServicesProperties, - rsuDepositor - ); + this.asn1CommandManager = asn1CommandManager; this.dataSigningEnabledSDW = securityServicesProperties.getIsSdwSigningEnabled(); this.dataSigningEnabledRSU = securityServicesProperties.getIsRsuSigningEnabled(); diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java index a9bdf4549..d29115e59 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java @@ -54,12 +54,17 @@ public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, odeKafkaProperties, asn1CoderTopics, jsonTopics, - sdxDepositorTopics, securityServicesProperties, - new RsuDepositor( - rsuProperties, securityServicesProperties.getIsRsuSigningEnabled() - ), - new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()) + new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()), + new Asn1CommandManager( + odeKafkaProperties, + sdxDepositorTopics, + securityServicesProperties, + new RsuDepositor( + rsuProperties, + securityServicesProperties.getIsRsuSigningEnabled() + ) + ) ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index fbb551cd5..577a8be16 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -101,14 +101,19 @@ void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); + var asn1CommandManager = new Asn1CommandManager( + odeKafkaProperties, + sdxDepositorTopics, + securityServicesProperties, + mockRsuDepositor + ); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( odeKafkaProperties, asn1CoderTopics, jsonTopics, - sdxDepositorTopics, securityServicesProperties, - mockRsuDepositor, - odeTimJsonTopology); + odeTimJsonTopology, + asn1CommandManager); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); @@ -136,7 +141,7 @@ void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() Awaitility.await().until(completableFuture::isDone); var consumerProps = KafkaTestUtils.consumerProps( - "Asn1EncodedDataRouterTest-asasas", "false", embeddedKafka); + "Asn1EncodedDataRouterTest-unsecured", "false", embeddedKafka); var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); var testConsumer = consumerFactory.createConsumer(); From 80295bc2b84b70a610dc6b934f2f326585c60b6b Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 16:39:39 -0700 Subject: [PATCH 012/128] refactor: introduce ISecurityServicesClient and SecurityServicesClientImpl These can be used to wrap the calls to security services and allow easier mocking of responses. It also allows us to better shield ourselves from API contract changes. users of the client don't need to be aware of changes to the external API as all interactions can be encapsulated within the client --- .../ode/security/ISecurityServicesClient.java | 10 ++++ .../security/SecurityServicesClientImpl.java | 47 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java new file mode 100644 index 000000000..71420f16b --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java @@ -0,0 +1,10 @@ +package us.dot.its.jpo.ode.security; + +/** + * Interface for interacting with security services that provide cryptographic operations. + * It defines methods for signing messages with optional validity overrides. + */ +public interface ISecurityServicesClient { + + public String signMessage(String message, int sigValidityOverride); +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java new file mode 100644 index 000000000..d160d98aa --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java @@ -0,0 +1,47 @@ +package us.dot.its.jpo.ode.security; + +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +/** + * Implementation of the ISecurityServicesClient interface for interacting with security + * services that provide cryptographic operations such as message signing. + * + */ +@Slf4j +public class SecurityServicesClientImpl implements ISecurityServicesClient { + + private final String signatureUri; + + public SecurityServicesClientImpl(String signatureUri) { + this.signatureUri = signatureUri; + } + + @Override + public String signMessage(String message, int sigValidityOverride) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + Map map = new HashMap<>(); + map.put("message", message); + map.put("sigValidityOverride", Integer.toString(sigValidityOverride)); + + HttpEntity> entity = new HttpEntity<>(map, headers); + RestTemplate template = new RestTemplate(); + + log.info("Sending data to security services module with validity override at {} to be signed", + signatureUri); + log.debug("Data to be signed: {}", entity); + + ResponseEntity respEntity = template.postForEntity(signatureUri, entity, String.class); + + log.debug("Security services module response: {}", respEntity); + + return respEntity.getBody(); + } +} From 10116881706318ad37c084b2e460c6042a322748 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 16:41:37 -0700 Subject: [PATCH 013/128] refactor: use ISecurityServicesClient in Asn1EncodedDataRouter This moves the calls to security services from Asn1CommandManager to SecurityServicesClientImpl which allows for more testability, flexibility (mocking client calls), and modularity --- .../ode/services/asn1/Asn1CommandManager.java | 33 +------------------ .../services/asn1/Asn1EncodedDataRouter.java | 11 ++++--- .../asn1/AsnCodecRouterServiceController.java | 5 +-- .../asn1/Asn1EncodedDataRouterTest.java | 10 ++++-- 4 files changed, 19 insertions(+), 40 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index ee90f0273..af92ac0c2 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -19,14 +19,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.text.ParseException; -import java.util.HashMap; -import java.util.Map; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.RestTemplate; import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; @@ -41,7 +34,6 @@ import us.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData; import us.dot.its.jpo.ode.plugin.j2735.builders.GeoRegionBuilder; import us.dot.its.jpo.ode.rsu.RsuDepositor; -import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; import us.dot.its.jpo.ode.util.JsonUtils; import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; @@ -68,7 +60,7 @@ public Asn1CommandManagerException(String msg, Exception e) { } - private final String signatureUri; + private MessageProducer stringMessageProducer; @@ -77,9 +69,7 @@ public Asn1CommandManagerException(String msg, Exception e) { public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, SDXDepositorTopics sdxDepositorTopics, - SecurityServicesProperties securityServicesProperties, RsuDepositor rsuDepositor) { - this.signatureUri = securityServicesProperties.getSignatureEndpoint(); try { this.rsuDepositor = rsuDepositor; @@ -108,27 +98,6 @@ public void sendToRsus(ServiceRequest request, String encodedMsg) { rsuDepositor.deposit(request, encodedMsg); } - public String sendForSignature(String message, int sigValidityOverride) { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - Map map = new HashMap<>(); - map.put("message", message); - map.put("sigValidityOverride", Integer.toString(sigValidityOverride)); - - HttpEntity> entity = new HttpEntity<>(map, headers); - RestTemplate template = new RestTemplate(); - - log.info("Sending data to security services module with validity override at {} to be signed", - signatureUri); - log.debug("Data to be signed: {}", entity); - - ResponseEntity respEntity = template.postForEntity(signatureUri, entity, String.class); - - log.debug("Security services module response: {}", respEntity); - - return respEntity.getBody(); - } - public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { SDW sdw = request.getSdw(); diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 2698298fa..529fb25bc 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -31,6 +31,7 @@ import us.dot.its.jpo.ode.kafka.topics.JsonTopics; import us.dot.its.jpo.ode.model.OdeAsn1Data; import us.dot.its.jpo.ode.plugin.ServiceRequest; +import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.services.asn1.Asn1CommandManager.Asn1CommandManagerException; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; @@ -66,6 +67,7 @@ public Asn1EncodedDataRouterException(String string) { private final Asn1CoderTopics asn1CoderTopics; private final JsonTopics jsonTopics; + private final ISecurityServicesClient securityServicesClient; private final MessageProducer stringMsgProducer; private final OdeTimJsonTopology odeTimJsonTopology; @@ -80,20 +82,21 @@ public Asn1EncodedDataRouterException(String string) { * @param odeKafkaProperties The Kafka properties used to consume and produce to Kafka * @param asn1CoderTopics The specified ASN1 Coder topics * @param jsonTopics The specified JSON topics to write to - * @param sdxDepositorTopics The SDX depositor topics to write to - * @param rsuProperties The RSU properties to use * @param securityServicesProperties The security services properties to use + * @param securityServicesClient **/ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, Asn1CoderTopics asn1CoderTopics, JsonTopics jsonTopics, SecurityServicesProperties securityServicesProperties, OdeTimJsonTopology odeTimJsonTopology, - Asn1CommandManager asn1CommandManager) { + Asn1CommandManager asn1CommandManager, + ISecurityServicesClient securityServicesClient) { super(); this.asn1CoderTopics = asn1CoderTopics; this.jsonTopics = jsonTopics; + this.securityServicesClient = securityServicesClient; this.stringMsgProducer = MessageProducer.defaultStringMessageProducer( odeKafkaProperties.getBrokers(), @@ -411,7 +414,7 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu String timpacketID = metadataObjs.getString("odePacketID"); String timStartDateTime = metadataObjs.getString("odeTimStartDateTime"); log.debug("SENDING: {}", base64EncodedTim); - String signedResponse = asn1CommandManager.sendForSignature(base64EncodedTim, maxDurationTime); + String signedResponse = securityServicesClient.signMessage(base64EncodedTim, maxDurationTime); try { final String hexEncodedTim = CodecUtils.toHex( CodecUtils.fromBase64( diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java index d29115e59..67aa2e6f9 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java @@ -26,6 +26,7 @@ import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.rsu.RsuProperties; +import us.dot.its.jpo.ode.security.SecurityServicesClientImpl; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.wrapper.MessageConsumer; @@ -59,12 +60,12 @@ public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, new Asn1CommandManager( odeKafkaProperties, sdxDepositorTopics, - securityServicesProperties, new RsuDepositor( rsuProperties, securityServicesProperties.getIsRsuSigningEnabled() ) - ) + ), + new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()) ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 577a8be16..729e30c15 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -17,6 +17,10 @@ package us.dot.its.jpo.ode.services.asn1; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.IOException; import java.io.InputStream; @@ -104,16 +108,18 @@ void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() var asn1CommandManager = new Asn1CommandManager( odeKafkaProperties, sdxDepositorTopics, - securityServicesProperties, mockRsuDepositor ); + var mockSecServClient = mock(ISecurityServicesClient.class); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( odeKafkaProperties, asn1CoderTopics, jsonTopics, securityServicesProperties, odeTimJsonTopology, - asn1CommandManager); + asn1CommandManager, + mockSecServClient + ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); From 6a85fbf8241a7d9d0a4de22b619e7b31e859dbe8 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Wed, 18 Dec 2024 17:03:44 -0700 Subject: [PATCH 014/128] test: processSNMPDepositOnly --- .../services/asn1/Asn1EncodedDataRouter.java | 2 +- .../asn1/Asn1EncodedDataRouterTest.java | 112 +++++++++++++++++- ...r-output-unsigned-tim-no-advisory-data.xml | 71 +++++++++++ ...ected-asn1-encoded-router-snmp-deposit.xml | 1 + .../asn1/expected-tim-cert-expired.json | 1 + 5 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml create mode 100644 jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml create mode 100644 jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 529fb25bc..e72c30bb9 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -418,7 +418,7 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu try { final String hexEncodedTim = CodecUtils.toHex( CodecUtils.fromBase64( - JsonUtils.toJSONObject(JsonUtils.toJSONObject(signedResponse).getString("result")) + JsonUtils.toJSONObject(signedResponse).getJSONObject("result") .getString("message-signed"))); JSONObject timWithExpiration = new JSONObject(); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 729e30c15..c47a3eddc 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -17,16 +17,14 @@ package us.dot.its.jpo.ode.services.asn1; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import org.apache.kafka.common.serialization.StringDeserializer; import org.awaitility.Awaitility; +import org.json.JSONObject; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; @@ -46,6 +44,7 @@ import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.rsu.RsuProperties; +import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; import us.dot.its.jpo.ode.wrapper.MessageConsumer; @@ -122,7 +121,8 @@ void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); + embeddedKafka.getBrokersAsString(), + "processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered", encoderRouter); encoderConsumer.setName("Asn1EncoderConsumer"); encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); @@ -172,4 +172,108 @@ void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() assertEquals(expectedTimTmcFiltered, timTmcFilteredRecord.value()); } + @Test + void processSNMPDepositOnly() + throws IOException, InterruptedException { + String[] topicsForConsumption = { + asn1CoderTopics.getEncoderInput(), + jsonTopics.getTimCertExpiration(), + jsonTopics.getTimTmcFiltered() + }; + EmbeddedKafkaHolder.addTopics(topicsForConsumption); + EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); + + securityServicesProperties.setIsSdwSigningEnabled(true); + var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); + var asn1CommandManager = new Asn1CommandManager( + odeKafkaProperties, + sdxDepositorTopics, + mockRsuDepositor + ); + var mockSecServClient = new ISecurityServicesClient() { + @Override + public String signMessage(String message, int sigValidityOverride) { + JSONObject json = new JSONObject(); + JSONObject result = new JSONObject(); + result.put("message-signed", "<%s>".formatted(message)); + result.put("message-expiry", "123124124124124141"); + json.put("result", result); + return json.toString(); + } + }; + + Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( + odeKafkaProperties, + asn1CoderTopics, + jsonTopics, + securityServicesProperties, + odeTimJsonTopology, + asn1CommandManager, + mockSecServClient + ); + MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( + embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); + + encoderConsumer.setName("Asn1EncoderConsumer"); + encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); + + // Wait for encoderRouter to connect to the broker otherwise the test will fail :( + Thread.sleep(2000); + + var classLoader = getClass().getClassLoader(); + InputStream inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json"); + assert inputStream != null; + var odeJsonTim = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return + kafkaTemplate.send(jsonTopics.getTim(), "266e6742-40fb-4c9e-a6b0-72ed2dddddfe", odeJsonTim); + + inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml"); + assert inputStream != null; + var input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); + + var consumerProps = KafkaTestUtils.consumerProps( + "Asn1EncodedDataRouterTest-unsecured", "false", embeddedKafka); + var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, + new StringDeserializer(), new StringDeserializer()); + var testConsumer = consumerFactory.createConsumer(); + embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topicsForConsumption); + + inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json"); + assert inputStream != null; + var expectedTimCertExpiry = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + var timCertExpirationRecord = + KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimCertExpiration()); + assertEquals(expectedTimCertExpiry, timCertExpirationRecord.value()); + + inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); + assert inputStream != null; + var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + var timTmcFilteredRecord = + KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimTmcFiltered()); + assertEquals(expectedTimTmcFiltered, timTmcFilteredRecord.value()); + + inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml"); + assert inputStream != null; + var expectedEncoderInput = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + var encoderInputRecord = + KafkaTestUtils.getSingleRecord(testConsumer, asn1CoderTopics.getEncoderInput()); + var encoderInputWithStableFieldsOnly = encoderInputRecord.value() + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", ""); + var expectedEncoderInputWithStableFieldsOnly = expectedEncoderInput + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", ""); + assertEquals(expectedEncoderInputWithStableFieldsOnly, encoderInputWithStableFieldsOnly); + } + } diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml new file mode 100644 index 000000000..33d95c2a7 --- /dev/null +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml @@ -0,0 +1,71 @@ + + + + us.dot.its.jpo.ode.model.OdeAsdPayload + + 266e6742-40fb-4c9e-a6b0-72ed2dddddfe + 1 + 0 + 0 + 0 + + 2024-12-18T16:14:09.690Z + 7 + 297 + 2024-02-29T14:15:47.000Z + TMC + false + 03B027CF2071328E12 + 2024-03-08T16:37:05.414Z + + + 3 + POST + + + + + 38.98721843900006 + -104.76767069499999 + + + 38.96666515900006 + -104.74048299899994 + + + oneday + 6B573067 + + + + + MessageFrame + MessageFrame + UPER + + + Ieee1609Dot2Data + Ieee1609Dot2Data + COER + + + UPER + + + + + us.dot.its.jpo.ode.model.OdeHexByteArray + + + + 001F808470114F3703B027CF2071328E120F775D9B0301C2670F374166BC3027FFF93F40BE628129A007F93937E1CF5AC98DFA706A5F6D09AB766C1B3780000000099C3CDD059AF0C084E2302000844033611407401733CDC2D6203FDA1756398358ED27557C6856FBAAC1C3A0F8D9C221DD858A60E0ADA596124567F2B182001002009EEEBB3600 + + + + + 03808188001F808470114F3703B027CF2071328E120F775D9B0301C2670F374166BC3027FFF93F40BE628129A007F93937E1CF5AC98DFA706A5F6D09AB766C1B3780000000099C3CDD059AF0C084E2302000844033611407401733CDC2D6203FDA1756398358ED27557C6856FBAAC1C3A0F8D9C221DD858A60E0ADA596124567F2B182001002009EEEBB3600 + + + + + \ No newline at end of file diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml new file mode 100644 index 000000000..2e25d4f21 --- /dev/null +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml @@ -0,0 +1 @@ +us.dot.its.jpo.ode.model.OdeAsdPayloaddb2e2c93-c87a-488c-b54b-e0dfcbb08fca10002024-12-18T23:59:55.081Z70false3POST38.98721843900006-104.7676706949999938.96666515900006-104.74048299899994oneday6B573067AdvisorySituationDataAdvisorySituationDataUPERus.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData156500000000B673BA356B5730672389872184-1047676707389666652-1047404830B673BA3520200031600003160001F808470114F3703B027CF2071328E120F775D9B0301C2670F374166BC3027FFF93F40BE628129A007F93937E1CF5AC98DFA706A5F6D09AB766C1B3780000000099C3CDD059AF0C084E2302000844033611407401733CDC2D6203FDA1756398358ED27557C6856FBAAC1C3A0F8D9C221DD858A60E0ADA596124567F2B182001002009EEEBB3600 \ No newline at end of file diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json new file mode 100644 index 000000000..a2ca5a8eb --- /dev/null +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json @@ -0,0 +1 @@ +{"packetID":"03B027CF2071328E12","startDateTime":"2024-03-08T16:37:05.414Z","requiredExpirationDate":"2024-03-08T21:34:05.414Z","expirationDate":"null"} \ No newline at end of file From 3dce2a43d1752cba71fd8668d73bd6bd11465e5e Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 09:56:20 -0700 Subject: [PATCH 015/128] style: update license header to not be javadoc --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index c47a3eddc..82a586928 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -1,4 +1,4 @@ -/******************************************************************************* +/*============================================================================ * Copyright 2018 572682 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not From 3898ad903492d2084502589dbfd4bf98edeac8f5 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 11:08:19 -0700 Subject: [PATCH 016/128] test: update tests in Asn1EncodedDataRouterTest to enable running together (make data dynamic) --- .../services/asn1/Asn1EncodedDataRouter.java | 3 +- .../asn1/Asn1EncodedDataRouterTest.java | 33 +++++++++++++------ .../asn1/expected-tim-cert-expired.json | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index e72c30bb9..7a8fc6c43 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -457,8 +457,7 @@ private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String ti private static void setExpiryDate(String signedResponse, JSONObject timWithExpiration, SimpleDateFormat dateFormat) { try { - JSONObject jsonResult = JsonUtils - .toJSONObject((JsonUtils.toJSONObject(signedResponse).getString("result"))); + JSONObject jsonResult = JsonUtils.toJSONObject(signedResponse).getJSONObject("result"); // messageExpiry uses unit of seconds long messageExpiry = Long.parseLong(jsonResult.getString("message-expiry")); timWithExpiration.put("expirationDate", dateFormat.format(new Date(messageExpiry * 1000))); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 82a586928..932ea1ef8 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.UUID; import org.apache.kafka.common.serialization.StringDeserializer; import org.awaitility.Awaitility; import org.json.JSONObject; @@ -136,7 +137,8 @@ void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() assert inputStream != null; var odeJsonTim = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return - kafkaTemplate.send(jsonTopics.getTim(), "266e6742-40fb-4c9e-a6b0-72ed2dddddfe", odeJsonTim); + var streamId = "266e6742-40fb-4c9e-a6b0-72ed2dddddfe"; + kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); inputStream = classLoader .getResourceAsStream( @@ -147,7 +149,7 @@ void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() Awaitility.await().until(completableFuture::isDone); var consumerProps = KafkaTestUtils.consumerProps( - "Asn1EncodedDataRouterTest-unsecured", "false", embeddedKafka); + "processEncodedTimUnsecured", "false", embeddedKafka); var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); var testConsumer = consumerFactory.createConsumer(); @@ -158,18 +160,25 @@ void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() assert inputStream != null; var expected = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - var sdxDepositorRecord = - KafkaTestUtils.getSingleRecord(testConsumer, sdxDepositorTopics.getInput()); - assertEquals(expected, sdxDepositorRecord.value()); + var records = KafkaTestUtils.getRecords(testConsumer); + var sdxDepositorRecord = records + .records(sdxDepositorTopics.getInput()); + for (var consumerRecord : sdxDepositorRecord) { + if (consumerRecord.value().contains(streamId)) { + assertEquals(expected, consumerRecord.value()); + } + } inputStream = classLoader.getResourceAsStream( "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); assert inputStream != null; var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - var timTmcFilteredRecord = - KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimTmcFiltered()); - assertEquals(expectedTimTmcFiltered, timTmcFilteredRecord.value()); + for (var consumerRecord : records.records(jsonTopics.getTimTmcFiltered())) { + if (consumerRecord.value().contains(streamId)) { + assertEquals(expectedTimTmcFiltered, consumerRecord.value()); + } + } } @Test @@ -226,16 +235,19 @@ public String signMessage(String message, int sigValidityOverride) { assert inputStream != null; var odeJsonTim = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return - kafkaTemplate.send(jsonTopics.getTim(), "266e6742-40fb-4c9e-a6b0-72ed2dddddfe", odeJsonTim); + var streamId = UUID.randomUUID().toString(); + odeJsonTim = odeJsonTim.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); inputStream = classLoader.getResourceAsStream( "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml"); assert inputStream != null; var input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + input = input.replaceAll(".*?", "" + streamId + ""); kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); var consumerProps = KafkaTestUtils.consumerProps( - "Asn1EncodedDataRouterTest-unsecured", "false", embeddedKafka); + "processSNMPDepositOnly", "false", embeddedKafka); var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); var testConsumer = consumerFactory.createConsumer(); @@ -255,6 +267,7 @@ public String signMessage(String message, int sigValidityOverride) { var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); var timTmcFilteredRecord = KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimTmcFiltered()); + expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); assertEquals(expectedTimTmcFiltered, timTmcFilteredRecord.value()); inputStream = classLoader.getResourceAsStream( diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json index a2ca5a8eb..201a12a94 100644 --- a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json @@ -1 +1 @@ -{"packetID":"03B027CF2071328E12","startDateTime":"2024-03-08T16:37:05.414Z","requiredExpirationDate":"2024-03-08T21:34:05.414Z","expirationDate":"null"} \ No newline at end of file +{"packetID":"03B027CF2071328E12","startDateTime":"2024-03-08T16:37:05.414Z","requiredExpirationDate":"2024-03-08T21:34:05.414Z","expirationDate":"190224297-08-07T14:27:59.688Z"} \ No newline at end of file From 225960cd72b8b8de9ddab1598cb996b34d744778 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 11:25:51 -0700 Subject: [PATCH 017/128] test: processEncodedTimUnsecured in Asn1EncodedDataRouterTest --- .../asn1/Asn1EncodedDataRouterTest.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 932ea1ef8..3f10f3257 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -289,4 +289,101 @@ public String signMessage(String message, int sigValidityOverride) { assertEquals(expectedEncoderInputWithStableFieldsOnly, encoderInputWithStableFieldsOnly); } + @Test + void processEncodedTimUnsecured() + throws IOException, InterruptedException { + String[] topicsForConsumption = { + asn1CoderTopics.getEncoderInput(), + jsonTopics.getTimTmcFiltered() + }; + EmbeddedKafkaHolder.addTopics(topicsForConsumption); + EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); + + var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); + var asn1CommandManager = new Asn1CommandManager( + odeKafkaProperties, + sdxDepositorTopics, + mockRsuDepositor + ); + var mockSecServClient = new ISecurityServicesClient() { + @Override + public String signMessage(String message, int sigValidityOverride) { + JSONObject json = new JSONObject(); + JSONObject result = new JSONObject(); + result.put("message-signed", "<%s>".formatted(message)); + result.put("message-expiry", "123124124124124141"); + json.put("result", result); + return json.toString(); + } + }; + + Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( + odeKafkaProperties, + asn1CoderTopics, + jsonTopics, + securityServicesProperties, + odeTimJsonTopology, + asn1CommandManager, + mockSecServClient + ); + MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( + embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); + + encoderConsumer.setName("Asn1EncoderConsumer"); + encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); + + // Wait for encoderRouter to connect to the broker otherwise the test will fail :( + Thread.sleep(2000); + + var classLoader = getClass().getClassLoader(); + InputStream inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json"); + assert inputStream != null; + var odeJsonTim = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return + var streamId = UUID.randomUUID().toString(); + odeJsonTim = odeJsonTim.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); + + inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml"); + assert inputStream != null; + var input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + input = input.replaceAll(".*?", "" + streamId + ""); + kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); + + var consumerProps = KafkaTestUtils.consumerProps( + "processSNMPDepositOnly", "false", embeddedKafka); + var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, + new StringDeserializer(), new StringDeserializer()); + var testConsumer = consumerFactory.createConsumer(); + embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topicsForConsumption); + + inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json"); + assert inputStream != null; + var expected = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + + var records = KafkaTestUtils.getRecords(testConsumer); + var sdxDepositorRecord = records + .records(sdxDepositorTopics.getInput()); + for (var consumerRecord : sdxDepositorRecord) { + if (consumerRecord.value().contains(streamId)) { + assertEquals(expected, consumerRecord.value()); + } + } + + inputStream = classLoader.getResourceAsStream( + "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); + assert inputStream != null; + var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + + for (var consumerRecord : records.records(jsonTopics.getTimTmcFiltered())) { + if (consumerRecord.value().contains(streamId)) { + assertEquals(expectedTimTmcFiltered, consumerRecord.value()); + } + } + } + } From 1aba9e0c7caa2c014a389a67dbb900950cfe1748 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 11:44:42 -0700 Subject: [PATCH 018/128] test: make consumer groups unique between tests to keep them isolated --- .../asn1/Asn1EncodedDataRouterTest.java | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 3f10f3257..250751c24 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -93,7 +93,7 @@ class Asn1EncodedDataRouterTest { EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Test - void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() + void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOException, InterruptedException { String[] topicsForConsumption = { @@ -123,7 +123,7 @@ void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), - "processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered", encoderRouter); + "processSignedMessage_depositsToSdxTopicAndTimTmcFiltered", encoderRouter); encoderConsumer.setName("Asn1EncoderConsumer"); encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); @@ -149,7 +149,7 @@ void processEncodedTimUnsecured_depositsToSdxTopicAndTimTmcFiltered() Awaitility.await().until(completableFuture::isDone); var consumerProps = KafkaTestUtils.consumerProps( - "processEncodedTimUnsecured", "false", embeddedKafka); + "processSignedMessage_depositsToSdxTopicAndTimTmcFiltered-test", "false", embeddedKafka); var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); var testConsumer = consumerFactory.createConsumer(); @@ -221,7 +221,7 @@ public String signMessage(String message, int sigValidityOverride) { mockSecServClient ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); + embeddedKafka.getBrokersAsString(), "processSNMPDepositOnly-default", encoderRouter); encoderConsumer.setName("Asn1EncoderConsumer"); encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); @@ -265,28 +265,33 @@ public String signMessage(String message, int sigValidityOverride) { "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); assert inputStream != null; var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - var timTmcFilteredRecord = - KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimTmcFiltered()); + var records = KafkaTestUtils.getRecords(testConsumer); expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); - assertEquals(expectedTimTmcFiltered, timTmcFilteredRecord.value()); + for (var consumerRecord : records.records(jsonTopics.getTimTmcFiltered())) { + if (consumerRecord.value().contains(streamId)) { + assertEquals(expectedTimTmcFiltered, consumerRecord.value()); + } + } inputStream = classLoader.getResourceAsStream( "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml"); assert inputStream != null; var expectedEncoderInput = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - var encoderInputRecord = - KafkaTestUtils.getSingleRecord(testConsumer, asn1CoderTopics.getEncoderInput()); - var encoderInputWithStableFieldsOnly = encoderInputRecord.value() - .replaceAll(".*?", "") - .replaceAll(".*?", "") - .replaceAll(".*?", "") - .replaceAll(".*?", ""); var expectedEncoderInputWithStableFieldsOnly = expectedEncoderInput .replaceAll(".*?", "") .replaceAll(".*?", "") .replaceAll(".*?", "") .replaceAll(".*?", ""); - assertEquals(expectedEncoderInputWithStableFieldsOnly, encoderInputWithStableFieldsOnly); + for (var consumerRecord : records.records(asn1CoderTopics.getEncoderInput())) { + if (consumerRecord.value().contains(streamId)) { + var encoderInputWithStableFieldsOnly = consumerRecord.value() + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", ""); + assertEquals(expectedEncoderInputWithStableFieldsOnly, encoderInputWithStableFieldsOnly); + } + } } @Test @@ -353,7 +358,7 @@ public String signMessage(String message, int sigValidityOverride) { kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); var consumerProps = KafkaTestUtils.consumerProps( - "processSNMPDepositOnly", "false", embeddedKafka); + "processEncodedTimUnsecured", "false", embeddedKafka); var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); var testConsumer = consumerFactory.createConsumer(); From e7e7bf1cec700a5274ff62951a304a0ccf74b0e6 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 11:49:30 -0700 Subject: [PATCH 019/128] test: tweak securityServices settings to get code to run through processSignedMessage --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 250751c24..e0e2f41b8 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -111,6 +111,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() mockRsuDepositor ); var mockSecServClient = mock(ISecurityServicesClient.class); + securityServicesProperties.setIsSdwSigningEnabled(true); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( odeKafkaProperties, asn1CoderTopics, @@ -322,6 +323,8 @@ public String signMessage(String message, int sigValidityOverride) { } }; + securityServicesProperties.setIsSdwSigningEnabled(false); + securityServicesProperties.setIsRsuSigningEnabled(false); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( odeKafkaProperties, asn1CoderTopics, From 7aa492010e3eeaeab0bf22ed3e2491b529ba96f2 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 12:02:16 -0700 Subject: [PATCH 020/128] test: disable all Asn1CommandManagerTest tests as they don't work with refactor nor do they confirm behavior --- .../services/asn1/Asn1CommandManagerTest.java | 110 +++++++++--------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java index 46474407d..25693a51a 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java @@ -1,30 +1,28 @@ -/******************************************************************************* +/*=========================================================================== * Copyright 2018 572682 - * + * * Licensed 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 us.dot.its.jpo.ode.services.asn1; - -import java.io.IOException; -import java.text.ParseException; -import org.json.JSONObject; -import org.junit.jupiter.api.Test; +package us.dot.its.jpo.ode.services.asn1; import mockit.Capturing; import mockit.Injectable; import mockit.Mocked; import mockit.Tested; +import org.json.JSONObject; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.model.OdeTravelerInputData; @@ -34,65 +32,69 @@ import us.dot.its.jpo.ode.snmp.SnmpSession; import us.dot.its.jpo.ode.wrapper.MessageProducer; -public class Asn1CommandManagerTest { +class Asn1CommandManagerTest { - @Tested - Asn1CommandManager testAsn1CommandManager; + @Tested + Asn1CommandManager testAsn1CommandManager; - @Injectable - OdeKafkaProperties injectableOdeKafkaProperties; + @Injectable + OdeKafkaProperties injectableOdeKafkaProperties; - @Injectable - SDXDepositorTopics injectableSDXDepositorTopics; + @Injectable + SDXDepositorTopics injectableSDXDepositorTopics; - @Injectable - RsuProperties injectableRsuProperties; + @Injectable + RsuProperties injectableRsuProperties; - @Injectable - SecurityServicesProperties injectableSecurityServicesProperties; + @Injectable + SecurityServicesProperties injectableSecurityServicesProperties; - @Capturing - MessageProducer capturingMessageProducer; - @Capturing - SnmpSession capturingSnmpSession; + @Capturing + MessageProducer capturingMessageProducer; + @Capturing + SnmpSession capturingSnmpSession; - @Injectable - OdeTravelerInputData injectableOdeTravelerInputData; + @Injectable + OdeTravelerInputData injectableOdeTravelerInputData; - @Mocked - MessageProducer mockMessageProducer; + @Mocked + MessageProducer mockMessageProducer; - @Test - public void testPackageSignedTimIntoAsd() { - testAsn1CommandManager.packageSignedTimIntoAsd(injectableOdeTravelerInputData.getRequest(), "message"); - } + @Test + @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") + void testPackageSignedTimIntoAsd() { + testAsn1CommandManager.packageSignedTimIntoAsd(injectableOdeTravelerInputData.getRequest(), + "message"); + } - @Test - public void depositToSDWJsonShouldCallMessageProducer() throws Asn1CommandManagerException { - JSONObject deposit = new JSONObject(); - deposit.put("estimatedRemovalDate", "2023-11-04T17:47:11-05:00"); - deposit.put("encodedMsg", "message"); + @Test + @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") + void depositToSDWJsonShouldCallMessageProducer() throws Asn1CommandManagerException { + JSONObject deposit = new JSONObject(); + deposit.put("estimatedRemovalDate", "2023-11-04T17:47:11-05:00"); + deposit.put("encodedMsg", "message"); - testAsn1CommandManager.depositToSdw(deposit.toString()); - } + testAsn1CommandManager.depositToSdw(deposit.toString()); + } - @Test - public void depositToSDWShouldCallMessageProducer() throws Asn1CommandManagerException { - testAsn1CommandManager.depositToSdw("message"); - } + @Test + @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") + void depositToSDWShouldCallMessageProducer() throws Asn1CommandManagerException { + testAsn1CommandManager.depositToSdw("message"); + } - @Test - public void testSendToRsus(@Mocked OdeTravelerInputData mockOdeTravelerInputData) - throws IOException, ParseException { + @Test + @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") + void testSendToRsus(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { - testAsn1CommandManager.sendToRsus(mockOdeTravelerInputData.getRequest(), "message"); - } + testAsn1CommandManager.sendToRsus(mockOdeTravelerInputData.getRequest(), "message"); + } - @Test - public void testSendToRsusSnmpException(@Mocked OdeTravelerInputData mockOdeTravelerInputData) - throws IOException, ParseException { + @Test + @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") + void testSendToRsusSnmpException(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { - testAsn1CommandManager.sendToRsus(mockOdeTravelerInputData.getRequest(), "message"); - } + testAsn1CommandManager.sendToRsus(mockOdeTravelerInputData.getRequest(), "message"); + } } From 7e69562731a44eff57bc7bd3a4f77881485bcc31 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 13:52:39 -0700 Subject: [PATCH 021/128] refactor: move asn1CommandManager.depositToSdw to Asn1EncodedDataRouter The responsibility to submit data to topics is already contained within Asn1EncodedDataRouter, and Asn1CommandManager is not responsible for any kafka interactions otherwise, so the responsibility was moved to Asn1EncodedDataRouter --- .../ode/services/asn1/Asn1CommandManager.java | 19 ------------------- .../services/asn1/Asn1EncodedDataRouter.java | 16 +++++++++------- .../asn1/AsnCodecRouterServiceController.java | 3 ++- .../services/asn1/Asn1CommandManagerTest.java | 18 ------------------ .../asn1/Asn1EncodedDataRouterTest.java | 9 ++++++--- 5 files changed, 17 insertions(+), 48 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index af92ac0c2..cffc20bea 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -39,7 +39,6 @@ import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; -import us.dot.its.jpo.ode.wrapper.MessageProducer; @Slf4j public class Asn1CommandManager { @@ -60,11 +59,6 @@ public Asn1CommandManagerException(String msg, Exception e) { } - - - private MessageProducer stringMessageProducer; - - private String depositTopic; private RsuDepositor rsuDepositor; public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, @@ -74,11 +68,6 @@ public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, try { this.rsuDepositor = rsuDepositor; this.rsuDepositor.start(); - this.stringMessageProducer = - MessageProducer.defaultStringMessageProducer(odeKafkaProperties.getBrokers(), - odeKafkaProperties.getKafkaType(), - odeKafkaProperties.getDisabledTopics()); - this.depositTopic = sdxDepositorTopics.getInput(); } catch (Exception e) { String msg = "Error starting SDW depositor"; EventLogger.logger.error(msg, e); @@ -86,14 +75,6 @@ public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, } } - public void depositToSdw(String depositObj) throws Asn1CommandManagerException { - stringMessageProducer.send(this.depositTopic, null, depositObj); - log.info("Published message to SDW deposit topic {}", this.depositTopic); - EventLogger.logger.info("Published message to SDW deposit topic"); - log.debug("Message deposited: {}", depositObj); - EventLogger.logger.debug("Message deposited: {}", depositObj); - } - public void sendToRsus(ServiceRequest request, String encodedMsg) { rsuDepositor.deposit(request, encodedMsg); } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 7a8fc6c43..f923ab44e 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -33,7 +33,6 @@ import us.dot.its.jpo.ode.plugin.ServiceRequest; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.services.asn1.Asn1CommandManager.Asn1CommandManagerException; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; import us.dot.its.jpo.ode.util.CodecUtils; import us.dot.its.jpo.ode.util.JsonUtils; @@ -67,6 +66,7 @@ public Asn1EncodedDataRouterException(String string) { private final Asn1CoderTopics asn1CoderTopics; private final JsonTopics jsonTopics; + private final String sdxDepositTopic; private final ISecurityServicesClient securityServicesClient; private final MessageProducer stringMsgProducer; @@ -84,6 +84,7 @@ public Asn1EncodedDataRouterException(String string) { * @param jsonTopics The specified JSON topics to write to * @param securityServicesProperties The security services properties to use * @param securityServicesClient + * @param sdxDepositTopic **/ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, Asn1CoderTopics asn1CoderTopics, @@ -91,11 +92,13 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, SecurityServicesProperties securityServicesProperties, OdeTimJsonTopology odeTimJsonTopology, Asn1CommandManager asn1CommandManager, - ISecurityServicesClient securityServicesClient) { + ISecurityServicesClient securityServicesClient, + String sdxDepositTopic) { super(); this.asn1CoderTopics = asn1CoderTopics; this.jsonTopics = jsonTopics; + this.sdxDepositTopic = sdxDepositTopic; this.securityServicesClient = securityServicesClient; this.stringMsgProducer = MessageProducer.defaultStringMessageProducer( @@ -248,10 +251,9 @@ private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); deposit.put("encodedMsg", asdObj.getString(BYTES)); - asn1CommandManager.depositToSdw(deposit.toString()); - } catch (JSONException | Asn1CommandManagerException e) { - String msg = ERROR_ON_SDX_DEPOSIT; - log.error(msg, e); + stringMsgProducer.send(this.sdxDepositTopic, null, deposit.toString()); + } catch (JSONException e) { + log.error(ERROR_ON_SDX_DEPOSIT, e); } } @@ -346,7 +348,7 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); deposit.put("encodedMsg", asdBytes); - asn1CommandManager.depositToSdw(deposit.toString()); + stringMsgProducer.send(this.sdxDepositTopic, null, deposit.toString()); log.info("SDX deposit successful."); } catch (Exception e) { String msg = ERROR_ON_SDX_DEPOSIT; diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java index 67aa2e6f9..fa91f8cad 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java @@ -65,7 +65,8 @@ public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, securityServicesProperties.getIsRsuSigningEnabled() ) ), - new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()) + new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()), + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java index 25693a51a..9207afd96 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java @@ -20,7 +20,6 @@ import mockit.Injectable; import mockit.Mocked; import mockit.Tested; -import org.json.JSONObject; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; @@ -28,7 +27,6 @@ import us.dot.its.jpo.ode.model.OdeTravelerInputData; import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.services.asn1.Asn1CommandManager.Asn1CommandManagerException; import us.dot.its.jpo.ode.snmp.SnmpSession; import us.dot.its.jpo.ode.wrapper.MessageProducer; @@ -67,22 +65,6 @@ void testPackageSignedTimIntoAsd() { "message"); } - @Test - @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") - void depositToSDWJsonShouldCallMessageProducer() throws Asn1CommandManagerException { - JSONObject deposit = new JSONObject(); - deposit.put("estimatedRemovalDate", "2023-11-04T17:47:11-05:00"); - deposit.put("encodedMsg", "message"); - - testAsn1CommandManager.depositToSdw(deposit.toString()); - } - - @Test - @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") - void depositToSDWShouldCallMessageProducer() throws Asn1CommandManagerException { - testAsn1CommandManager.depositToSdw("message"); - } - @Test @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") void testSendToRsus(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index e0e2f41b8..127eea7da 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -119,7 +119,8 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() securityServicesProperties, odeTimJsonTopology, asn1CommandManager, - mockSecServClient + mockSecServClient, + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( @@ -219,7 +220,8 @@ public String signMessage(String message, int sigValidityOverride) { securityServicesProperties, odeTimJsonTopology, asn1CommandManager, - mockSecServClient + mockSecServClient, + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), "processSNMPDepositOnly-default", encoderRouter); @@ -332,7 +334,8 @@ public String signMessage(String message, int sigValidityOverride) { securityServicesProperties, odeTimJsonTopology, asn1CommandManager, - mockSecServClient + mockSecServClient, + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); From ab2a63358d5692109fbe87bc5d911b7b6fe2fa93 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 13:57:55 -0700 Subject: [PATCH 022/128] style: added missing Javadocs and reorganized Asn1CommandManager --- .../ode/services/asn1/Asn1CommandManager.java | 55 +++++++++++-------- .../asn1/AsnCodecRouterServiceController.java | 2 - .../asn1/Asn1EncodedDataRouterTest.java | 6 -- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index cffc20bea..deb30a9d7 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -22,8 +22,6 @@ import lombok.extern.slf4j.Slf4j; import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.eventlog.EventLogger; -import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; -import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; import us.dot.its.jpo.ode.model.OdeAsdPayload; import us.dot.its.jpo.ode.model.OdeMsgMetadata; @@ -40,31 +38,25 @@ import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; +/** + * Manages operations related to ASN.1 command processing and interaction with RSUs (Road Side + * Units). Handles the preparation, packaging, and distribution of messages to RSUs using an + * {@link RsuDepositor}. + */ @Slf4j public class Asn1CommandManager { public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; - public static class Asn1CommandManagerException extends Exception { - - private static final long serialVersionUID = 1L; - - public Asn1CommandManagerException(String string) { - super(string); - } - - public Asn1CommandManagerException(String msg, Exception e) { - super(msg, e); - } - - } - private RsuDepositor rsuDepositor; - public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, - SDXDepositorTopics sdxDepositorTopics, - RsuDepositor rsuDepositor) { - + /** + * Constructs an instance of Asn1CommandManager and initializes the RSU depositor. + * + * @param rsuDepositor The RSU depositor instance to be used for managing ASN.1 commands. This + * instance is started during initialization. + */ + public Asn1CommandManager(RsuDepositor rsuDepositor) { try { this.rsuDepositor = rsuDepositor; this.rsuDepositor.start(); @@ -75,10 +67,27 @@ public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, } } + /** + * Sends a message to the RSUs (Road Side Units) through the RSU depositor. + * + * @param request the service request containing relevant details for the operation + * @param encodedMsg the message to be sent, encoded in the desired format + */ public void sendToRsus(ServiceRequest request, String encodedMsg) { rsuDepositor.deposit(request, encodedMsg); } + /** + * Constructs an XML representation of an Advisory Situation Data (ASD) message containing a + * signed Traveler Information Message (TIM). Processes the provided service request and signed + * message to create and structure the ASD before converting it to an XML string output. + * + * @param request the service request object containing meta information, service region, + * delivery time, and other necessary data for ASD creation. + * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. + * @return a String containing the fully crafted ASD message in XML format. Returns null if the + * message could not be constructed due to exceptions. + */ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { SDW sdw = request.getSdw(); @@ -106,8 +115,6 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); } - OdeMsgPayload payload; - ObjectNode dataBodyObj = JsonUtils.newNode(); ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); @@ -116,7 +123,7 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); - payload = new OdeAsdPayload(asd); + OdeMsgPayload payload = new OdeAsdPayload(asd); ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); payloadObj.set(AppContext.DATA_STRING, dataBodyObj); @@ -157,7 +164,7 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) return outputXml; } - public static ArrayNode buildEncodings() throws JsonUtilsException { + private ArrayNode buildEncodings() throws JsonUtilsException { ArrayNode encodings = JsonUtils.newArrayNode(); encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, ADVISORY_SITUATION_DATA_STRING, diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java index fa91f8cad..c6e0ed63a 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java @@ -58,8 +58,6 @@ public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, securityServicesProperties, new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()), new Asn1CommandManager( - odeKafkaProperties, - sdxDepositorTopics, new RsuDepositor( rsuProperties, securityServicesProperties.getIsRsuSigningEnabled() diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 127eea7da..0534a0578 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -106,8 +106,6 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); var asn1CommandManager = new Asn1CommandManager( - odeKafkaProperties, - sdxDepositorTopics, mockRsuDepositor ); var mockSecServClient = mock(ISecurityServicesClient.class); @@ -197,8 +195,6 @@ void processSNMPDepositOnly() securityServicesProperties.setIsSdwSigningEnabled(true); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); var asn1CommandManager = new Asn1CommandManager( - odeKafkaProperties, - sdxDepositorTopics, mockRsuDepositor ); var mockSecServClient = new ISecurityServicesClient() { @@ -309,8 +305,6 @@ void processEncodedTimUnsecured() var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); var asn1CommandManager = new Asn1CommandManager( - odeKafkaProperties, - sdxDepositorTopics, mockRsuDepositor ); var mockSecServClient = new ISecurityServicesClient() { From b4473a37b040727f45a55e8368855b9cdef565c8 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 14:02:29 -0700 Subject: [PATCH 023/128] refactor: don't swallow exception on startup of rsuDepositor in Asn1CommandManager --- .../its/jpo/ode/services/asn1/Asn1CommandManager.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index deb30a9d7..3b5401447 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -21,7 +21,6 @@ import java.text.ParseException; import lombok.extern.slf4j.Slf4j; import us.dot.its.jpo.ode.context.AppContext; -import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; import us.dot.its.jpo.ode.model.OdeAsdPayload; import us.dot.its.jpo.ode.model.OdeMsgMetadata; @@ -57,14 +56,8 @@ public class Asn1CommandManager { * instance is started during initialization. */ public Asn1CommandManager(RsuDepositor rsuDepositor) { - try { - this.rsuDepositor = rsuDepositor; - this.rsuDepositor.start(); - } catch (Exception e) { - String msg = "Error starting SDW depositor"; - EventLogger.logger.error(msg, e); - log.error(msg, e); - } + this.rsuDepositor = rsuDepositor; + this.rsuDepositor.start(); } /** From c1f11784a6b53a4b30f8f2efe5308021a33e5397 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 14:23:04 -0700 Subject: [PATCH 024/128] refactor: ResponseEvent to use generic Address type Updated method signatures and instance usage to specify ResponseEvent with Address as the generic type. This change improves type safety and aligns with best practices for clearer code readability and maintainability. --- .../us/dot/its/jpo/ode/rsu/RsuDepositor.java | 14 ++++---- .../us/dot/its/jpo/ode/snmp/SnmpSession.java | 35 +++++++------------ 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java index 265c704c5..572bbfcb8 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java @@ -16,19 +16,19 @@ package us.dot.its.jpo.ode.rsu; +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; import lombok.extern.slf4j.Slf4j; import org.snmp4j.event.ResponseEvent; +import org.snmp4j.smi.Address; import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.plugin.RoadSideUnit.RSU; import us.dot.its.jpo.ode.plugin.ServiceRequest; import us.dot.its.jpo.ode.snmp.SnmpSession; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.HashMap; - @Slf4j public class RsuDepositor extends Thread { private final boolean dataSigningEnabled; @@ -77,7 +77,7 @@ public void run() { TimTransmogrifier.updateRsuCreds(curRsu, rsuProperties); String httpResponseStatus; try { - ResponseEvent rsuResponse = SnmpSession.createAndSend(entry.request.getSnmp(), + ResponseEvent

rsuResponse = SnmpSession.createAndSend(entry.request.getSnmp(), curRsu, entry.encodedMsg, entry.request.getOde().getVerb(), @@ -101,7 +101,7 @@ public void run() { } } - private String getResponseStatus(ResponseEvent rsuResponse, RSU curRsu) { + private String getResponseStatus(ResponseEvent
rsuResponse, RSU curRsu) { String httpResponseStatus; if (null == rsuResponse || null == rsuResponse.getResponse()) { diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java index 9fdeeede0..b904f9837 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java @@ -15,6 +15,8 @@ ******************************************************************************/ package us.dot.its.jpo.ode.snmp; +import java.io.IOException; +import java.text.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snmp4j.PDU; @@ -38,16 +40,12 @@ import org.snmp4j.smi.OctetString; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultUdpTransportMapping; -import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.plugin.RoadSideUnit.RSU; import us.dot.its.jpo.ode.plugin.SNMP; import us.dot.its.jpo.ode.plugin.ServiceRequest; import us.dot.its.jpo.ode.plugin.ServiceRequest.OdeInternal.RequestVerb; import us.dot.its.jpo.ode.plugin.SnmpProtocol; -import java.io.IOException; -import java.text.ParseException; - /** * This object is used to abstract away the complexities of SNMP calls and allow * a user to more quickly and easily send SNMP requests. Note that the @@ -119,7 +117,7 @@ public SnmpSession(RSU rsu) throws IOException { * @return ResponseEvent * @throws IOException */ - public ResponseEvent set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { + public ResponseEvent
set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { // Ensure the object has been instantiated if (!ready) { @@ -199,28 +197,19 @@ public void startListen() throws IOException { listening = true; } - /** - * Create an SNMP session given the values in - * - * @param tim - The TIM parameters (payload, channel, mode, etc) - * @param props - The SNMP properties (ip, username, password, etc) - * @return ResponseEvent - * @throws TimPduCreatorException - * @throws IOException - * @throws ParseException - */ - public static ResponseEvent createAndSend(SNMP snmp, RSU rsu, String payload, RequestVerb requestVerb, boolean dataSigningEnabledRSU) - throws ParseException, IOException { + + public static ResponseEvent
createAndSend(SNMP snmp, RSU rsu, String payload, + RequestVerb requestVerb, boolean dataSigningEnabledRSU) throws ParseException, IOException { SnmpSession session = new SnmpSession(rsu); // Send the PDU - ResponseEvent response = null; - ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, rsu.getSnmpProtocol(), dataSigningEnabledRSU); - response = session.set(pdu, session.getSnmp(), session.getTarget(), false); - String msg = "Message Sent to {}, index {}: {}"; - EventLogger.logger.debug(msg, rsu.getRsuTarget(), rsu.getRsuIndex(), payload); - logger.debug(msg, rsu.getRsuTarget(), rsu.getRsuIndex(), payload); + ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, + rsu.getSnmpProtocol(), dataSigningEnabledRSU); + ResponseEvent
response = + session.set(pdu, session.getSnmp(), session.getTarget(), false); + logger.debug("Message Sent to {}, index {}: {}", rsu.getRsuTarget(), rsu.getRsuIndex(), + payload); return response; } From 79e0e0ca446f73561bf3c2eac2332537e9ecde58 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 14:40:55 -0700 Subject: [PATCH 025/128] style: reformat and add missing Javadocs SnmpSession --- .../us/dot/its/jpo/ode/snmp/SnmpSession.java | 697 +++++++++--------- 1 file changed, 361 insertions(+), 336 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java index b904f9837..86a55c41c 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java @@ -1,4 +1,4 @@ -/******************************************************************************* +/*============================================================================ * Copyright 2018 572682 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. ******************************************************************************/ + package us.dot.its.jpo.ode.snmp; import java.io.IOException; @@ -47,366 +48,390 @@ import us.dot.its.jpo.ode.plugin.SnmpProtocol; /** - * This object is used to abstract away the complexities of SNMP calls and allow - * a user to more quickly and easily send SNMP requests. Note that the - * "connection" aspect of this class is an abstraction meant to reinforce that - * these objects correspond 1-to-1 with a destination server, while SNMP is sent - * over UDP and is actually connection-less. + * This object is used to abstract away the complexities of SNMP calls and allow a user to more + * quickly and easily send SNMP requests. Note that the "connection" aspect of this class is an + * abstraction meant to reinforce that these objects correspond 1-to-1 with a destination server, + * while SNMP is sent over UDP and is actually connection-less. */ public class SnmpSession { - private static final Logger logger = LoggerFactory.getLogger(SnmpSession.class); - - private Snmp snmp; - private TransportMapping transport; - private UserTarget target; - - private boolean ready = false; - private boolean listening; - - /** - * Constructor for SnmpSession - * - * @param props SnmpProperties for the session (target address, retries, - * timeout, etc) - * @throws IOException - */ - public SnmpSession(RSU rsu) throws IOException { - Address addr = GenericAddress.parse(rsu.getRsuTarget() + "/161"); - - // Create a "target" to which a request is sent - target = new UserTarget(); - target.setAddress(addr); - target.setRetries(rsu.getRsuRetries()); - target.setTimeout(rsu.getRsuTimeout()); - target.setVersion(SnmpConstants.version3); - if (rsu.getRsuUsername() != null) { - target.setSecurityLevel(SecurityLevel.AUTH_PRIV); - target.setSecurityName(new OctetString(rsu.getRsuUsername())); - } else { - target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); - } - - // Set up the UDP transport mapping over which requests are sent - transport = null; - try { - transport = new DefaultUdpTransportMapping(); - } catch (IOException e) { - throw new IOException("Failed to create UDP transport mapping: {}", e); - } - - // Instantiate the SNMP instance - snmp = new Snmp(transport); - - // Register the security options and create an SNMP "user" - USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); - SecurityModels.getInstance().addSecurityModel(usm); - if (rsu.getRsuUsername() != null) { - snmp.getUSM().addUser(new OctetString(rsu.getRsuUsername()), new UsmUser(new OctetString(rsu.getRsuUsername()), - AuthSHA.ID, new OctetString(rsu.getRsuPassword()), PrivAES128.ID, new OctetString(rsu.getRsuPassword()))); - } - - // Assert the ready flag so the user can begin sending messages - ready = true; + private static final Logger logger = LoggerFactory.getLogger(SnmpSession.class); + + private Snmp snmp; + private TransportMapping transport; + private UserTarget target; + + private boolean ready = false; + private boolean listening; + + /** + * Constructor for SnmpSession. + * + * @throws IOException when failing to create the snmp Transport + */ + public SnmpSession(RSU rsu) throws IOException { + Address addr = GenericAddress.parse(rsu.getRsuTarget() + "/161"); + + // Create a "target" to which a request is sent + target = new UserTarget(); + target.setAddress(addr); + target.setRetries(rsu.getRsuRetries()); + target.setTimeout(rsu.getRsuTimeout()); + target.setVersion(SnmpConstants.version3); + if (rsu.getRsuUsername() != null) { + target.setSecurityLevel(SecurityLevel.AUTH_PRIV); + target.setSecurityName(new OctetString(rsu.getRsuUsername())); + } else { + target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); } - /** - * Sends a SET-type PDU to the target specified by the constructor. - * - * @param pdu The message content to be sent to the target - * @return ResponseEvent - * @throws IOException - */ - public ResponseEvent
set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { - - // Ensure the object has been instantiated - if (!ready) { - throw new IOException("Tried to send PDU before SNMP sending service is ready."); - } - - if (!listening) { - startListen(); - } - - // Try to send the SNMP request (synchronously) - ResponseEvent
responseEvent; - try { - // attempt to discover & set authoritative engine ID - byte[] authEngineID = snmpob.discoverAuthoritativeEngineID(targetob.getAddress(), 1000); - if (authEngineID != null && authEngineID.length > 0) { - targetob.setAuthoritativeEngineID(authEngineID); - } - - // send request - responseEvent = snmpob.set(pdu, targetob); - if (!keepOpen) { - snmpob.close(); - } - - // if RSU responded and we didn't get an authEngineID, log a warning - if (responseEvent != null && responseEvent.getResponse() != null && authEngineID == null) { - logger.warn("Failed to discover authoritative engine ID for SNMP target: {}", targetob.getAddress()); - } - } catch (IOException e) { - throw new IOException("Failed to send SNMP request: " + e); - } - - return responseEvent; + // Set up the UDP transport mapping over which requests are sent + transport = new DefaultUdpTransportMapping(); + + // Instantiate the SNMP instance + snmp = new Snmp(transport); + + // Register the security options and create an SNMP "user" + USM usm = + new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); + SecurityModels.getInstance().addSecurityModel(usm); + if (rsu.getRsuUsername() != null) { + snmp.getUSM().addUser(new OctetString(rsu.getRsuUsername()), + new UsmUser(new OctetString(rsu.getRsuUsername()), + AuthSHA.ID, new OctetString(rsu.getRsuPassword()), PrivAES128.ID, + new OctetString(rsu.getRsuPassword()))); } - /** - * Sends a SET-type PDU to the target specified by the constructor. - * - * @param pdu The message content to be sent to the target - * @return ResponseEvent - * @throws IOException - */ - public ResponseEvent get(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { - - // Ensure the object has been instantiated - if (!ready) { - throw new IOException("Tried to send PDU before SNMP sending service is ready."); - } - - // Start listening on UDP - if (!listening) { - startListen(); - } - - // Try to send the SNMP request (synchronously) - ResponseEvent responseEvent = null; - try { - responseEvent = snmpob.get(pdu, targetob); - if (!keepOpen) { - snmpob.close(); - } - } catch (IOException e) { - throw new IOException("Failed to send SNMP request: " + e); - } - - return responseEvent; + // Assert the ready flag so the user can begin sending messages + ready = true; + } + + /** + * Sends a SET-type PDU to the target specified by the constructor. + * + * @param pdu The message content to be sent to the target + * @return ResponseEvent + * @throws IOException when calling this method before the session is ready + */ + public ResponseEvent
set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) + throws IOException { + + // Ensure the object has been instantiated + if (!ready) { + throw new IOException("Tried to send PDU before SNMP sending service is ready."); } - /** - * Start listening for responses - * - * @throws IOException - */ - public void startListen() throws IOException { - transport.listen(); - listening = true; + if (!listening) { + startListen(); } - - public static ResponseEvent
createAndSend(SNMP snmp, RSU rsu, String payload, - RequestVerb requestVerb, boolean dataSigningEnabledRSU) throws ParseException, IOException { - - SnmpSession session = new SnmpSession(rsu); - - // Send the PDU - ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, - rsu.getSnmpProtocol(), dataSigningEnabledRSU); - ResponseEvent
response = - session.set(pdu, session.getSnmp(), session.getTarget(), false); - logger.debug("Message Sent to {}, index {}: {}", rsu.getRsuTarget(), rsu.getRsuIndex(), - payload); - return response; + // Try to send the SNMP request (synchronously) + ResponseEvent
responseEvent; + try { + // attempt to discover & set authoritative engine ID + byte[] authEngineID = snmpob.discoverAuthoritativeEngineID(targetob.getAddress(), 1000); + if (authEngineID != null && authEngineID.length > 0) { + targetob.setAuthoritativeEngineID(authEngineID); + } + + // send request + responseEvent = snmpob.set(pdu, targetob); + if (!keepOpen) { + snmpob.close(); + } + + // if RSU responded and we didn't get an authEngineID, log a warning + if (responseEvent != null && responseEvent.getResponse() != null && authEngineID == null) { + logger.warn("Failed to discover authoritative engine ID for SNMP target: {}", + targetob.getAddress()); + } + } catch (IOException e) { + throw new IOException("Failed to send SNMP request: " + e); } - public Snmp getSnmp() { - return snmp; + return responseEvent; + } + + /** + * Sends a SET-type PDU to the target specified by the constructor. + * + * @param pdu The message content to be sent to the target + * @return ResponseEvent + * @throws IOException when calling this method before the session is ready + */ + public ResponseEvent get(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) + throws IOException { + + // Ensure the object has been instantiated + if (!ready) { + throw new IOException("Tried to send PDU before SNMP sending service is ready."); } - public void setSnmp(Snmp snmp) { - this.snmp = snmp; + // Start listening on UDP + if (!listening) { + startListen(); } - public TransportMapping getTransport() { - return transport; + // Try to send the SNMP request (synchronously) + ResponseEvent responseEvent = null; + try { + responseEvent = snmpob.get(pdu, targetob); + if (!keepOpen) { + snmpob.close(); + } + } catch (IOException e) { + throw new IOException("Failed to send SNMP request: " + e); } - public void setTransport(TransportMapping transport) { - this.transport = transport; + return responseEvent; + } + + /** + * Start listening for responses. + * + * @throws IOException when listening failed + */ + public void startListen() throws IOException { + transport.listen(); + listening = true; + } + + + /** + * Creates and sends a PDU to a specific RSU (Road Side Unit) using the provided SNMP session. The + * request is built with specified payload, request verb, and data signing option. + * + * @param snmp The SNMP object containing configuration and metadata for + * constructing the request. + * @param rsu The RSU object that provides target and protocol information for + * the request destination. + * @param payload The payload string to be included in the PDU of the request. + * @param requestVerb The type of SNMP request verb (e.g., GET, SET) for the operation. + * @param dataSigningEnabledRSU A boolean flag indicating if data signing is enabled for the RSU. + * @return A ResponseEvent representing the response received from the RSU. + * @throws ParseException If there is an error in parsing the payload or PDU creation. + * @throws IOException If there is an error in establishing or using the SNMP transport. + */ + public static ResponseEvent
createAndSend(SNMP snmp, RSU rsu, String payload, + RequestVerb requestVerb, boolean dataSigningEnabledRSU) throws ParseException, IOException { + + SnmpSession session = new SnmpSession(rsu); + + // Send the PDU + ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, + rsu.getSnmpProtocol(), dataSigningEnabledRSU); + ResponseEvent
response = + session.set(pdu, session.getSnmp(), session.getTarget(), false); + logger.debug("Message Sent to {}, index {}: {}", rsu.getRsuTarget(), rsu.getRsuIndex(), + payload); + return response; + } + + public Snmp getSnmp() { + return snmp; + } + + public void setSnmp(Snmp snmp) { + this.snmp = snmp; + } + + public TransportMapping getTransport() { + return transport; + } + + public void setTransport(TransportMapping transport) { + this.transport = transport; + } + + public UserTarget getTarget() { + return target; + } + + public void setTarget(UserTarget target) { + this.target = target; + } + + public void endSession() throws IOException { + this.snmp.close(); + } + + /** + * Creates a ScopedPDU object configured based on the specified SNMP protocol. + * + * @param snmp The SNMP object containing configuration and metadata for + * constructing the PDU. + * @param payload The payload string to be included in the PDU. + * @param index The index value associated with the PDU construction. + * @param verb The request verb (e.g., GET, SET) for the PDU. + * @param snmpProtocol The SNMP protocol version or type used for constructing the PDU. + * @param dataSigningEnabledRSU A boolean flag indicating whether data signing is enabled for the + * RSU. + * @return A ScopedPDU object constructed based on the provided parameters, or null if the + * protocol is unknown. + * @throws ParseException If there is an error in parsing the payload or during PDU creation. + */ + public static ScopedPDU createPDU(SNMP snmp, String payload, int index, RequestVerb verb, + SnmpProtocol snmpProtocol, boolean dataSigningEnabledRSU) throws ParseException { + switch (snmpProtocol) { + case FOURDOT1: + return createPDUWithFourDot1Protocol(snmp, payload, index, verb); + case NTCIP1218: + return createPDUWithNTCIP1218Protocol(snmp, payload, index, verb, dataSigningEnabledRSU); + default: + logger.error("Unknown SNMP protocol: {}", snmpProtocol); + return null; } - - public UserTarget getTarget() { - return target; + } + + /** + * Encodes the given value into an SNMP variable binding using specific encoding rules + * based on the value's range. + * + * @param oid The Object Identifier (OID) as a string, representing the SNMP object ID. + * @param val The value to be encoded, provided as a hexadecimal string. + * @return A VariableBinding object that contains the OID and the encoded value, or null + * if the value does not fall within any of the predefined ranges. + */ + public static VariableBinding getPEncodedVariableBinding(String oid, String val) { + Integer intVal = Integer.parseInt(val, 16); + Integer additionValue = null; + + if (intVal >= 0 && intVal <= 127) { + // P = V + // here we must instantiate the OctetString directly with the hex string to + // avoid inadvertently creating the ASCII character codes + // for instance OctetString.fromString("20", 16) produces the space character (" + // ") rather than hex 20 + return new VariableBinding(new OID(oid), new OctetString(Integer.toHexString(intVal))); + } else if (intVal >= 128 && intVal <= 16511) { + // P = V + 0x7F80 + additionValue = 0x7F80; + } else if (intVal >= 016512 && intVal <= 2113663) { + // P = V + 0xBFBF80 + additionValue = 0xBFBF80; + } else if (intVal >= 2113664 && intVal <= 270549119) { + // P = V + 0xDFDFBF80 + additionValue = 0xDFDFBF80; } - public void setTarget(UserTarget target) { - this.target = target; + if (additionValue != null) { + return new VariableBinding(new OID(oid), + OctetString.fromString(Integer.toHexString(intVal + additionValue), 16)); } - - public void endSession() throws IOException { - this.snmp.close(); + return null; + } + + private static ScopedPDU createPDUWithFourDot1Protocol(SNMP snmp, String payload, int index, + RequestVerb verb) throws ParseException { + ////////////////////////////// + // - OID examples - // + ////////////////////////////// + // rsuSRMStatus.3 = 4 + // --> 1.4.1.11.3 = 4 + // rsuSRMTxChannel.3 = 3 + // --> 1.4.1.5.3 = 178 + // rsuSRMTxMode.3 = 1 + // --> 1.4.1.4.3 = 1 + // rsuSRMPsid.3 x "8003" + // --> 1.4.1.2.3 x "8003" + // rsuSRMDsrcMsgId.3 = 31 + // --> 1.4.1.3.3 = 31 + // rsuSRMTxInterval.3 = 10 + // --> 1.4.1.6.3 = 10 + // rsuSRMDeliveryStart.3 x "07e7051f0c000000" + // --> 1.4.1.7.3 = "07e7051f0c000000" + // rsuSRMDeliveryStop.3 x "07e7060f0c000000" + // --> 1.4.1.8.3 = "07e7060f0c000000" + // rsuSRMPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // --> 1.4.1.9.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // rsuSRMEnable.3 = 1 + // --> 1.4.1.10.3 = 1 + ////////////////////////////// + + VariableBinding rsuSRMPsid = SnmpFourDot1Protocol.getVbRsuSrmPsid(index, snmp.getRsuid()); + VariableBinding rsuSRMDsrcMsgId = + SnmpFourDot1Protocol.getVbRsuSrmDsrcMsgId(index, snmp.getMsgid()); + VariableBinding rsuSRMTxMode = SnmpFourDot1Protocol.getVbRsuSrmTxMode(index, snmp.getMode()); + VariableBinding rsuSRMTxChannel = + SnmpFourDot1Protocol.getVbRsuSrmTxChannel(index, snmp.getChannel()); + VariableBinding rsuSRMTxInterval = + SnmpFourDot1Protocol.getVbRsuSrmTxInterval(index, snmp.getInterval()); + VariableBinding rsuSRMDeliveryStart = + SnmpFourDot1Protocol.getVbRsuSrmDeliveryStart(index, snmp.getDeliverystart()); + VariableBinding rsuSRMDeliveryStop = + SnmpFourDot1Protocol.getVbRsuSrmDeliveryStop(index, snmp.getDeliverystop()); + VariableBinding rsuSRMPayload = SnmpFourDot1Protocol.getVbRsuSrmPayload(index, payload); + VariableBinding rsuSRMEnable = SnmpFourDot1Protocol.getVbRsuSrmEnable(index, snmp.getEnable()); + + ScopedPDU pdu = new ScopedPDU(); + pdu.add(rsuSRMPsid); + pdu.add(rsuSRMDsrcMsgId); + pdu.add(rsuSRMTxMode); + pdu.add(rsuSRMTxChannel); + pdu.add(rsuSRMTxInterval); + pdu.add(rsuSRMDeliveryStart); + pdu.add(rsuSRMDeliveryStop); + pdu.add(rsuSRMPayload); + pdu.add(rsuSRMEnable); + if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { + VariableBinding rsuSRMStatus = SnmpFourDot1Protocol.getVbRsuSrmStatus(index, snmp.getStatus()); + pdu.add(rsuSRMStatus); } - - /** - * Assembles the various RSU elements of a TimParameters object into a usable - * PDU. - * - * @param index Storage index on the RSU - * @param params TimParameters POJO that stores status, channel, payload, etc. - * @return PDU - * @throws ParseException - */ - public static ScopedPDU createPDU(SNMP snmp, String payload, int index, RequestVerb verb, SnmpProtocol snmpProtocol, boolean dataSigningEnabledRSU) throws ParseException { - switch (snmpProtocol) { - case FOURDOT1: - return createPDUWithFourDot1Protocol(snmp, payload, index, verb); - case NTCIP1218: - return createPDUWithNTCIP1218Protocol(snmp, payload, index, verb, dataSigningEnabledRSU); - default: - logger.error("Unknown SNMP protocol: {}", snmpProtocol); - return null; - } - } - - public static VariableBinding getPEncodedVariableBinding(String oid, String val) { - Integer intVal = Integer.parseInt(val, 16); - Integer additionValue = null; - - if (intVal >= 0 && intVal <= 127) { - // P = V - // here we must instantiate the OctetString directly with the hex string to - // avoid inadvertently creating the ASCII character codes - // for instance OctetString.fromString("20", 16) produces the space character (" - // ") rather than hex 20 - return new VariableBinding(new OID(oid), new OctetString(Integer.toHexString(intVal))); - } else if (intVal >= 128 && intVal <= 16511) { - // P = V + 0x7F80 - additionValue = 0x7F80; - } else if (intVal >= 016512 && intVal <= 2113663) { - // P = V + 0xBFBF80 - additionValue = 0xBFBF80; - } else if (intVal >= 2113664 && intVal <= 270549119) { - // P = V + 0xDFDFBF80 - additionValue = 0xDFDFBF80; - } - - if (additionValue != null) { - return new VariableBinding(new OID(oid), - OctetString.fromString(Integer.toHexString(intVal + additionValue), 16)); - } - return null; + pdu.setType(PDU.SET); + + return pdu; + } + + private static ScopedPDU createPDUWithNTCIP1218Protocol(SNMP snmp, String payload, int index, + RequestVerb verb, boolean dataSigningEnabledRSU) throws ParseException { + ////////////////////////////// + // - OID examples - // + ////////////////////////////// + // rsuMsgRepeatPsid.3 x "8003" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.2.3 x "8003" + // rsuMsgRepeatTxChannel.3 = 3 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.3.3 = 183 + // rsuMsgRepeatTxInterval.3 = 10 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.4.3 = 10 + // rsuMsgRepeatDeliveryStart.3 x "07e7051f0c000000" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.5.3 = "07e7051f0c000000" + // rsuMsgRepeatDeliveryStop.3 x "07e7060f0c000000" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.6.3 = "07e7060f0c000000" + // rsuMsgRepeatPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.7.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // rsuMsgRepeatEnable.3 = 1 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.8.3 = 1 + // rsuMsgRepeatStatus.3 = 4 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.9.3 = 4 + // rsuMsgRepeatPriority.3 = 6 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.10.3 = 6 + // rsuMsgRepeatOptions.3 = "00" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.11.3 = "00" + ////////////////////////////// + + // note: dsrc_msg_id is not in NTCIP 1218 + // note: tx_mode is not in NTCIP 1218 + ScopedPDU pdu = new ScopedPDU(); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPsid(index, snmp.getRsuid())); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxChannel(index, snmp.getChannel())); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxInterval(index, snmp.getInterval())); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStart(index, snmp.getDeliverystart())); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStop(index, snmp.getDeliverystop())); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPayload(index, payload)); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatEnable(index, snmp.getEnable())); + if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatStatus(index, snmp.getStatus())); } - - private static ScopedPDU createPDUWithFourDot1Protocol(SNMP snmp, String payload, int index, RequestVerb verb) throws ParseException { - ////////////////////////////// - // - OID examples - // - ////////////////////////////// - // rsuSRMStatus.3 = 4 - // --> 1.4.1.11.3 = 4 - // rsuSRMTxChannel.3 = 3 - // --> 1.4.1.5.3 = 178 - // rsuSRMTxMode.3 = 1 - // --> 1.4.1.4.3 = 1 - // rsuSRMPsid.3 x "8003" - // --> 1.4.1.2.3 x "8003" - // rsuSRMDsrcMsgId.3 = 31 - // --> 1.4.1.3.3 = 31 - // rsuSRMTxInterval.3 = 10 - // --> 1.4.1.6.3 = 10 - // rsuSRMDeliveryStart.3 x "07e7051f0c000000" - // --> 1.4.1.7.3 = "07e7051f0c000000" - // rsuSRMDeliveryStop.3 x "07e7060f0c000000" - // --> 1.4.1.8.3 = "07e7060f0c000000" - // rsuSRMPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // --> 1.4.1.9.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // rsuSRMEnable.3 = 1 - // --> 1.4.1.10.3 = 1 - ////////////////////////////// - - VariableBinding rsuSRMPsid = SnmpFourDot1Protocol.getVbRsuSrmPsid(index, snmp.getRsuid()); - VariableBinding rsuSRMDsrcMsgId = SnmpFourDot1Protocol.getVbRsuSrmDsrcMsgId(index, snmp.getMsgid()); - VariableBinding rsuSRMTxMode = SnmpFourDot1Protocol.getVbRsuSrmTxMode(index, snmp.getMode()); - VariableBinding rsuSRMTxChannel = SnmpFourDot1Protocol.getVbRsuSrmTxChannel(index, snmp.getChannel()); - VariableBinding rsuSRMTxInterval = SnmpFourDot1Protocol.getVbRsuSrmTxInterval(index, snmp.getInterval()); - VariableBinding rsuSRMDeliveryStart = SnmpFourDot1Protocol.getVbRsuSrmDeliveryStart(index, snmp.getDeliverystart()); - VariableBinding rsuSRMDeliveryStop = SnmpFourDot1Protocol.getVbRsuSrmDeliveryStop(index, snmp.getDeliverystop()); - VariableBinding rsuSRMPayload = SnmpFourDot1Protocol.getVbRsuSrmPayload(index, payload); - VariableBinding rsuSRMEnable = SnmpFourDot1Protocol.getVbRsuSrmEnable(index, snmp.getEnable()); - VariableBinding rsuSRMStatus = SnmpFourDot1Protocol.getVbRsuSrmStatus(index, snmp.getStatus()); - - ScopedPDU pdu = new ScopedPDU(); - pdu.add(rsuSRMPsid); - pdu.add(rsuSRMDsrcMsgId); - pdu.add(rsuSRMTxMode); - pdu.add(rsuSRMTxChannel); - pdu.add(rsuSRMTxInterval); - pdu.add(rsuSRMDeliveryStart); - pdu.add(rsuSRMDeliveryStop); - pdu.add(rsuSRMPayload); - pdu.add(rsuSRMEnable); - if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { - pdu.add(rsuSRMStatus); - } - pdu.setType(PDU.SET); - - return pdu; + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPriority(index)); + if (dataSigningEnabledRSU) { + // set options to 0x00 to tell RSU to broadcast message without signing or attaching a 1609.2 header + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x00)); + } else { + // set options to 0x80 to tell RSU to sign & attach a 1609.2 header before broadcasting + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x80)); } + pdu.setType(PDU.SET); - private static ScopedPDU createPDUWithNTCIP1218Protocol(SNMP snmp, String payload, int index, RequestVerb verb, boolean dataSigningEnabledRSU) throws ParseException { - ////////////////////////////// - // - OID examples - // - ////////////////////////////// - // rsuMsgRepeatPsid.3 x "8003" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.2.3 x "8003" - // rsuMsgRepeatTxChannel.3 = 3 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.3.3 = 183 - // rsuMsgRepeatTxInterval.3 = 10 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.4.3 = 10 - // rsuMsgRepeatDeliveryStart.3 x "07e7051f0c000000" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.5.3 = "07e7051f0c000000" - // rsuMsgRepeatDeliveryStop.3 x "07e7060f0c000000" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.6.3 = "07e7060f0c000000" - // rsuMsgRepeatPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.7.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // rsuMsgRepeatEnable.3 = 1 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.8.3 = 1 - // rsuMsgRepeatStatus.3 = 4 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.9.3 = 4 - // rsuMsgRepeatPriority.3 = 6 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.10.3 = 6 - // rsuMsgRepeatOptions.3 = "00" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.11.3 = "00" - ////////////////////////////// - - VariableBinding rsuMsgRepeatPsid = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPsid(index, snmp.getRsuid()); - // note: dsrc_msg_id is not in NTCIP 1218 - // note: tx_mode is not in NTCIP 1218 - VariableBinding rsuMsgRepeatTxChannel = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxChannel(index, snmp.getChannel()); - VariableBinding rsuMsgRepeatTxInterval = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxInterval(index, snmp.getInterval()); - VariableBinding rsuMsgRepeatDeliveryStart = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStart(index, snmp.getDeliverystart()); - VariableBinding rsuMsgRepeatDeliveryStop = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStop(index, snmp.getDeliverystop()); - VariableBinding rsuMsgRepeatPayload = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPayload(index, payload); - VariableBinding rsuMsgRepeatEnable = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatEnable(index, snmp.getEnable()); - VariableBinding rsuMsgRepeatStatus = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatStatus(index, snmp.getStatus()); - VariableBinding rsuMsgRepeatPriority = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPriority(index); - VariableBinding rsuMsgRepeatOptions; - if (dataSigningEnabledRSU) { - // set options to 0x00 to tell RSU to broadcast message without signing or attaching a 1609.2 header - rsuMsgRepeatOptions = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x00); - } else { - // set options to 0x80 to tell RSU to sign & attach a 1609.2 header before broadcasting - rsuMsgRepeatOptions = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x80); - } - - ScopedPDU pdu = new ScopedPDU(); - pdu.add(rsuMsgRepeatPsid); - pdu.add(rsuMsgRepeatTxChannel); - pdu.add(rsuMsgRepeatTxInterval); - pdu.add(rsuMsgRepeatDeliveryStart); - pdu.add(rsuMsgRepeatDeliveryStop); - pdu.add(rsuMsgRepeatPayload); - pdu.add(rsuMsgRepeatEnable); - if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { - pdu.add(rsuMsgRepeatStatus); - } - pdu.add(rsuMsgRepeatPriority); - pdu.add(rsuMsgRepeatOptions); - pdu.setType(PDU.SET); - - return pdu; - } + return pdu; + } } \ No newline at end of file From 24fbba78c352fa84fbef523840d097ac9a7832e6 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 14:41:19 -0700 Subject: [PATCH 026/128] chore: delete unused AsnCodecRouterServiceController.java --- .../asn1/AsnCodecRouterServiceController.java | 76 ------------------- 1 file changed, 76 deletions(-) delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java deleted file mode 100644 index c6e0ed63a..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************* - * Copyright 2018 572682 - * - * Licensed 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 us.dot.its.jpo.ode.services.asn1; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import us.dot.its.jpo.ode.OdeTimJsonTopology; -import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; -import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; -import us.dot.its.jpo.ode.kafka.topics.JsonTopics; -import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; -import us.dot.its.jpo.ode.rsu.RsuDepositor; -import us.dot.its.jpo.ode.rsu.RsuProperties; -import us.dot.its.jpo.ode.security.SecurityServicesClientImpl; -import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.wrapper.MessageConsumer; - -/** - * Launches ToJsonConverter service - */ -@Controller -@Slf4j -public class AsnCodecRouterServiceController { - - @Autowired - public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, - JsonTopics jsonTopics, - Asn1CoderTopics asn1CoderTopics, - SDXDepositorTopics sdxDepositorTopics, - RsuProperties rsuProperties, - SecurityServicesProperties securityServicesProperties) { - super(); - - log.info("Starting {}", this.getClass().getSimpleName()); - - // asn1_codec Encoder Routing - log.info("Routing ENCODED data received ASN.1 Encoder"); - - Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( - odeKafkaProperties, - asn1CoderTopics, - jsonTopics, - securityServicesProperties, - new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()), - new Asn1CommandManager( - new RsuDepositor( - rsuProperties, - securityServicesProperties.getIsRsuSigningEnabled() - ) - ), - new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()), - sdxDepositorTopics.getInput() - ); - - MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - odeKafkaProperties.getBrokers(), this.getClass().getSimpleName(), encoderRouter); - - encoderConsumer.setName("Asn1EncoderConsumer"); - encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); - } -} From c99bbe8aebef56e6a2078f08b2e4c890defb3c39 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 14:56:19 -0700 Subject: [PATCH 027/128] refactor: pull Asn1CommandManager functionality into Asn1EncodedDataRouter --- .../ode/services/asn1/Asn1CommandManager.java | 167 ------------------ .../services/asn1/Asn1EncodedDataRouter.java | 122 +++++++++++-- .../services/asn1/Asn1CommandManagerTest.java | 82 --------- 3 files changed, 111 insertions(+), 260 deletions(-) delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java delete mode 100644 jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java deleted file mode 100644 index 3b5401447..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ /dev/null @@ -1,167 +0,0 @@ -/*============================================================================= - * Copyright 2018 572682 - * - * Licensed 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 us.dot.its.jpo.ode.services.asn1; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.text.ParseException; -import lombok.extern.slf4j.Slf4j; -import us.dot.its.jpo.ode.context.AppContext; -import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; -import us.dot.its.jpo.ode.model.OdeAsdPayload; -import us.dot.its.jpo.ode.model.OdeMsgMetadata; -import us.dot.its.jpo.ode.model.OdeMsgPayload; -import us.dot.its.jpo.ode.plugin.SNMP; -import us.dot.its.jpo.ode.plugin.ServiceRequest; -import us.dot.its.jpo.ode.plugin.SituationDataWarehouse.SDW; -import us.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData; -import us.dot.its.jpo.ode.plugin.j2735.builders.GeoRegionBuilder; -import us.dot.its.jpo.ode.rsu.RsuDepositor; -import us.dot.its.jpo.ode.traveler.TimTransmogrifier; -import us.dot.its.jpo.ode.util.JsonUtils; -import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; -import us.dot.its.jpo.ode.util.XmlUtils; -import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; - -/** - * Manages operations related to ASN.1 command processing and interaction with RSUs (Road Side - * Units). Handles the preparation, packaging, and distribution of messages to RSUs using an - * {@link RsuDepositor}. - */ -@Slf4j -public class Asn1CommandManager { - - public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; - - private RsuDepositor rsuDepositor; - - /** - * Constructs an instance of Asn1CommandManager and initializes the RSU depositor. - * - * @param rsuDepositor The RSU depositor instance to be used for managing ASN.1 commands. This - * instance is started during initialization. - */ - public Asn1CommandManager(RsuDepositor rsuDepositor) { - this.rsuDepositor = rsuDepositor; - this.rsuDepositor.start(); - } - - /** - * Sends a message to the RSUs (Road Side Units) through the RSU depositor. - * - * @param request the service request containing relevant details for the operation - * @param encodedMsg the message to be sent, encoded in the desired format - */ - public void sendToRsus(ServiceRequest request, String encodedMsg) { - rsuDepositor.deposit(request, encodedMsg); - } - - /** - * Constructs an XML representation of an Advisory Situation Data (ASD) message containing a - * signed Traveler Information Message (TIM). Processes the provided service request and signed - * message to create and structure the ASD before converting it to an XML string output. - * - * @param request the service request object containing meta information, service region, - * delivery time, and other necessary data for ASD creation. - * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. - * @return a String containing the fully crafted ASD message in XML format. Returns null if the - * message could not be constructed due to exceptions. - */ - public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { - - SDW sdw = request.getSdw(); - SNMP snmp = request.getSnmp(); - DdsAdvisorySituationData asd; - - byte sendToRsu = - request.getRsus() != null ? DdsAdvisorySituationData.RSU : DdsAdvisorySituationData.NONE; - byte distroType = (byte) (DdsAdvisorySituationData.IP | sendToRsu); - - String outputXml = null; - try { - if (null != snmp) { - - asd = new DdsAdvisorySituationData() - .setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null) - .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) - .setTimeToLive(sdw.getTtl()) - .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - } else { - asd = new DdsAdvisorySituationData() - .setAsdmDetails(sdw.getDeliverystart(), sdw.getDeliverystop(), distroType, null) - .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) - .setTimeToLive(sdw.getTtl()) - .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - } - - ObjectNode dataBodyObj = JsonUtils.newNode(); - ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); - ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); - admDetailsObj.remove("advisoryMessage"); - admDetailsObj.put("advisoryMessage", signedMsg); - - dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); - - OdeMsgPayload payload = new OdeAsdPayload(asd); - - ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); - payloadObj.set(AppContext.DATA_STRING, dataBodyObj); - - OdeMsgMetadata metadata = new OdeMsgMetadata(payload); - ObjectNode metaObject = JsonUtils.toObjectNode(metadata.toJson()); - - ObjectNode requestObj = JsonUtils.toObjectNode(JsonUtils.toJson(request, false)); - - requestObj.remove("tim"); - - metaObject.set("request", requestObj); - - ArrayNode encodings = buildEncodings(); - ObjectNode enc = - XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, encodings); - metaObject.set(AppContext.ENCODINGS_STRING, enc); - - ObjectNode message = JsonUtils.newNode(); - message.set(AppContext.METADATA_STRING, metaObject); - message.set(AppContext.PAYLOAD_STRING, payloadObj); - - ObjectNode root = JsonUtils.newNode(); - root.set(AppContext.ODE_ASN1_DATA, message); - - outputXml = XmlUtils.toXmlStatic(root); - - // remove the surrounding - outputXml = outputXml.replace("", ""); - outputXml = outputXml.replace("", ""); - - } catch (ParseException | JsonUtilsException | XmlUtilsException e) { - log.error("Parsing exception thrown while populating ASD structure: ", e); - } - - log.debug("Fully crafted ASD to be encoded: {}", outputXml); - - return outputXml; - } - - private ArrayNode buildEncodings() throws JsonUtilsException { - ArrayNode encodings = JsonUtils.newArrayNode(); - encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, - ADVISORY_SITUATION_DATA_STRING, - EncodingRule.UPER)); - return encodings; - } -} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index f923ab44e..7b5c97624 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -51,6 +51,7 @@ public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor stringMsgProducer; private final OdeTimJsonTopology odeTimJsonTopology; - private final Asn1CommandManager asn1CommandManager; + private final RsuDepositor rsuDepositor; private final boolean dataSigningEnabledSDW; private final boolean dataSigningEnabledRSU; @@ -91,7 +92,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, JsonTopics jsonTopics, SecurityServicesProperties securityServicesProperties, OdeTimJsonTopology odeTimJsonTopology, - Asn1CommandManager asn1CommandManager, + RsuDepositor rsuDepositor, ISecurityServicesClient securityServicesClient, String sdxDepositTopic) { super(); @@ -106,7 +107,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, odeKafkaProperties.getKafkaType(), odeKafkaProperties.getDisabledTopics()); - this.asn1CommandManager = asn1CommandManager; + this.rsuDepositor = rsuDepositor; this.dataSigningEnabledSDW = securityServicesProperties.getIsSdwSigningEnabled(); this.dataSigningEnabledRSU = securityServicesProperties.getIsRsuSigningEnabled(); @@ -229,7 +230,7 @@ public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) { // CASE 3: If SDW in metadata and ASD in body (double encoding complete) // - send to SDX - if (!dataObj.has(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING)) { + if (!dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { processSNMPDepositOnly(request, consumedObj, dataObj, metadataObj); } else { // We have encoded ASD. It could be either UNSECURED or secured. @@ -246,7 +247,7 @@ private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { // We have a ASD with signed MessageFrame // Case 3 JSONObject asdObj = dataObj.getJSONObject( - Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING); + ADVISORY_SITUATION_DATA_STRING); try { JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); @@ -294,7 +295,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO if (null != request.getSnmp() && null != request.getRsus() && null != hexEncodedTim) { log.info("Sending message to RSUs..."); - asn1CommandManager.sendToRsus(request, hexEncodedTim); + sendToRsus(request, hexEncodedTim); } hexEncodedTim = mfObj.getString(BYTES); @@ -310,7 +311,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO // Case 2 only log.debug("Publishing message for round 2 encoding!"); - String xmlizedMessage = asn1CommandManager.packageSignedTimIntoAsd(request, hexEncodedTim); + String xmlizedMessage = packageSignedTimIntoAsd(request, hexEncodedTim); stringMsgProducer.send(asn1CoderTopics.getEncoderInput(), null, xmlizedMessage); } @@ -335,8 +336,8 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum if (null != request.getSdw()) { JSONObject asdObj = null; - if (dataObj.has(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING)) { - asdObj = dataObj.getJSONObject(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING); + if (dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { + asdObj = dataObj.getJSONObject(ADVISORY_SITUATION_DATA_STRING); } else { log.error("ASD structure present in metadata but not in JSONObject!"); } @@ -387,9 +388,9 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum log.debug("Encoded message - phase 2: {}", encodedTim); // only send message to rsu if snmp, rsus, and message frame fields are present - if (null != request.getSnmp() && null != request.getRsus() && null != encodedTim) { + if (null != request.getSnmp() && null != request.getRsus()) { log.debug("Encoded message phase 3: {}", encodedTim); - asn1CommandManager.sendToRsus(request, encodedTim); + sendToRsus(request, encodedTim); } } @@ -443,6 +444,105 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu return encodedTIM; } + /** + * Constructs an XML representation of an Advisory Situation Data (ASD) message containing a + * signed Traveler Information Message (TIM). Processes the provided service request and signed + * message to create and structure the ASD before converting it to an XML string output. + * + * @param request the service request object containing meta information, service region, + * delivery time, and other necessary data for ASD creation. + * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. + * @return a String containing the fully crafted ASD message in XML format. Returns null if the + * message could not be constructed due to exceptions. + */ + public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { + + SDW sdw = request.getSdw(); + SNMP snmp = request.getSnmp(); + DdsAdvisorySituationData asd; + + byte sendToRsu = + request.getRsus() != null ? DdsAdvisorySituationData.RSU : DdsAdvisorySituationData.NONE; + byte distroType = (byte) (DdsAdvisorySituationData.IP | sendToRsu); + + String outputXml = null; + try { + if (null != snmp) { + + asd = new DdsAdvisorySituationData() + .setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null) + .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) + .setTimeToLive(sdw.getTtl()) + .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); + } else { + asd = new DdsAdvisorySituationData() + .setAsdmDetails(sdw.getDeliverystart(), sdw.getDeliverystop(), distroType, null) + .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) + .setTimeToLive(sdw.getTtl()) + .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); + } + + ObjectNode dataBodyObj = JsonUtils.newNode(); + ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); + ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); + admDetailsObj.remove("advisoryMessage"); + admDetailsObj.put("advisoryMessage", signedMsg); + + dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); + + OdeMsgPayload payload = new OdeAsdPayload(asd); + + ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); + payloadObj.set(AppContext.DATA_STRING, dataBodyObj); + + OdeMsgMetadata metadata = new OdeMsgMetadata(payload); + ObjectNode metaObject = JsonUtils.toObjectNode(metadata.toJson()); + + ObjectNode requestObj = JsonUtils.toObjectNode(JsonUtils.toJson(request, false)); + + requestObj.remove("tim"); + + metaObject.set("request", requestObj); + + ArrayNode encodings = buildEncodings(); + ObjectNode enc = + XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, encodings); + metaObject.set(AppContext.ENCODINGS_STRING, enc); + + ObjectNode message = JsonUtils.newNode(); + message.set(AppContext.METADATA_STRING, metaObject); + message.set(AppContext.PAYLOAD_STRING, payloadObj); + + ObjectNode root = JsonUtils.newNode(); + root.set(AppContext.ODE_ASN1_DATA, message); + + outputXml = XmlUtils.toXmlStatic(root); + + // remove the surrounding + outputXml = outputXml.replace("", ""); + outputXml = outputXml.replace("", ""); + + } catch (ParseException | JsonUtilsException | XmlUtilsException e) { + log.error("Parsing exception thrown while populating ASD structure: ", e); + } + + log.debug("Fully crafted ASD to be encoded: {}", outputXml); + + return outputXml; + } + + private ArrayNode buildEncodings() throws JsonUtilsException { + ArrayNode encodings = JsonUtils.newArrayNode(); + encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, + ADVISORY_SITUATION_DATA_STRING, + EncodingRule.UPER)); + return encodings; + } + + private void sendToRsus(ServiceRequest request, String encodedMsg) { + rsuDepositor.deposit(request, encodedMsg); + } + private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String timStartDateTime, int maxDurationTime, JSONObject timWithExpiration) { try { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java deleted file mode 100644 index 9207afd96..000000000 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/*=========================================================================== - * Copyright 2018 572682 - * - * Licensed 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 us.dot.its.jpo.ode.services.asn1; - -import mockit.Capturing; -import mockit.Injectable; -import mockit.Mocked; -import mockit.Tested; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; -import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; -import us.dot.its.jpo.ode.model.OdeTravelerInputData; -import us.dot.its.jpo.ode.rsu.RsuProperties; -import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.snmp.SnmpSession; -import us.dot.its.jpo.ode.wrapper.MessageProducer; - -class Asn1CommandManagerTest { - - @Tested - Asn1CommandManager testAsn1CommandManager; - - @Injectable - OdeKafkaProperties injectableOdeKafkaProperties; - - @Injectable - SDXDepositorTopics injectableSDXDepositorTopics; - - @Injectable - RsuProperties injectableRsuProperties; - - @Injectable - SecurityServicesProperties injectableSecurityServicesProperties; - - @Capturing - MessageProducer capturingMessageProducer; - @Capturing - SnmpSession capturingSnmpSession; - - @Injectable - OdeTravelerInputData injectableOdeTravelerInputData; - - @Mocked - MessageProducer mockMessageProducer; - - @Test - @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") - void testPackageSignedTimIntoAsd() { - testAsn1CommandManager.packageSignedTimIntoAsd(injectableOdeTravelerInputData.getRequest(), - "message"); - } - - @Test - @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") - void testSendToRsus(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { - - testAsn1CommandManager.sendToRsus(mockOdeTravelerInputData.getRequest(), "message"); - } - - @Test - @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") - void testSendToRsusSnmpException(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { - - testAsn1CommandManager.sendToRsus(mockOdeTravelerInputData.getRequest(), "message"); - } - -} From cdb14a2798df85fc0365f6a6b3e914b5bce9da75 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 15:02:13 -0700 Subject: [PATCH 028/128] refactor: replace SDXDepositorTopics.java reference with Value reference for topic --- .../ode/kafka/topics/SDXDepositorTopics.java | 12 --------- .../services/asn1/Asn1EncodedDataRouter.java | 2 +- .../kafka/topics/SDXDepositorTopicsTest.java | 25 ------------------- .../asn1/Asn1EncodedDataRouterTest.java | 17 ++++++------- 4 files changed, 9 insertions(+), 47 deletions(-) delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java delete mode 100644 jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java deleted file mode 100644 index d98e76e8b..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java +++ /dev/null @@ -1,12 +0,0 @@ -package us.dot.its.jpo.ode.kafka.topics; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ConfigurationProperties(prefix = "ode.kafka.topics.sdx-depositor") -@Data -public class SDXDepositorTopics { - private String input; -} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 7b5c97624..f0add78c0 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -94,7 +94,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, OdeTimJsonTopology odeTimJsonTopology, RsuDepositor rsuDepositor, ISecurityServicesClient securityServicesClient, - String sdxDepositTopic) { + @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic) { super(); this.asn1CoderTopics = asn1CoderTopics; diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java deleted file mode 100644 index bc700a7dd..000000000 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package us.dot.its.jpo.ode.kafka.topics; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.junit.jupiter.api.Assertions.*; - -@ExtendWith(SpringExtension.class) -@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) -@EnableConfigurationProperties(value = SDXDepositorTopics.class) -class SDXDepositorTopicsTest { - - @Autowired - SDXDepositorTopics sdxDepositorTopics; - - @Test - void getInput() { - assertEquals("topic.SDWDepositorInput", sdxDepositorTopics.getInput()); - } -} \ No newline at end of file diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 0534a0578..c9a69df2b 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -64,7 +64,6 @@ KafkaProducerConfig.class, KafkaProperties.class, Asn1CoderTopics.class, - SDXDepositorTopics.class, JsonTopics.class, SecurityServicesProperties.class, RsuProperties.class, @@ -83,8 +82,8 @@ class Asn1EncodedDataRouterTest { SecurityServicesProperties securityServicesProperties; @Autowired OdeKafkaProperties odeKafkaProperties; - @Autowired - SDXDepositorTopics sdxDepositorTopics; + @Value("${ode.kafka.topics.sdx-depositor.input}") + String sdxDepositorTopic; @Autowired KafkaTemplate kafkaTemplate; @Mock @@ -99,7 +98,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimTmcFiltered(), - sdxDepositorTopics.getInput() + sdxDepositorTopic }; EmbeddedKafkaHolder.addTopics(topicsForConsumption); EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); @@ -118,7 +117,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() odeTimJsonTopology, asn1CommandManager, mockSecServClient, - sdxDepositorTopics.getInput() + sdxDepositorTopic ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( @@ -162,7 +161,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() var records = KafkaTestUtils.getRecords(testConsumer); var sdxDepositorRecord = records - .records(sdxDepositorTopics.getInput()); + .records(sdxDepositorTopic); for (var consumerRecord : sdxDepositorRecord) { if (consumerRecord.value().contains(streamId)) { assertEquals(expected, consumerRecord.value()); @@ -217,7 +216,7 @@ public String signMessage(String message, int sigValidityOverride) { odeTimJsonTopology, asn1CommandManager, mockSecServClient, - sdxDepositorTopics.getInput() + sdxDepositorTopic ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), "processSNMPDepositOnly-default", encoderRouter); @@ -329,7 +328,7 @@ public String signMessage(String message, int sigValidityOverride) { odeTimJsonTopology, asn1CommandManager, mockSecServClient, - sdxDepositorTopics.getInput() + sdxDepositorTopic ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); @@ -371,7 +370,7 @@ public String signMessage(String message, int sigValidityOverride) { var records = KafkaTestUtils.getRecords(testConsumer); var sdxDepositorRecord = records - .records(sdxDepositorTopics.getInput()); + .records(sdxDepositorTopic); for (var consumerRecord : sdxDepositorRecord) { if (consumerRecord.value().contains(streamId)) { assertEquals(expected, consumerRecord.value()); From da1b379eb5e88f59340ea19337c7dd38cc6d4497 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 15:03:16 -0700 Subject: [PATCH 029/128] refactor: introduce Config beans for Asn1EncodedDataRouter dependencies --- .../its/jpo/ode/kafka/KafkaStreamsConfig.java | 21 +++++++++++++++++++ .../us/dot/its/jpo/ode/rsu/RsuDepositor.java | 8 ------- .../its/jpo/ode/rsu/RsuDepositorConfig.java | 16 ++++++++++++++ .../ode/security/SecurityServicesConfig.java | 12 +++++++++++ .../services/asn1/Asn1EncodedDataRouter.java | 17 ++++++++++++++- .../dot/its/jpo/ode/rsu/RsuDepositorTest.java | 15 ++----------- .../asn1/Asn1EncodedDataRouterTest.java | 17 ++++----------- 7 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java new file mode 100644 index 000000000..3c2576881 --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java @@ -0,0 +1,21 @@ +package us.dot.its.jpo.ode.kafka; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import us.dot.its.jpo.ode.OdeTimJsonTopology; + +/** + * KafkaStreamsConfig is a Spring configuration class that provides + * beans related to Kafka Streams topology setup. + */ +@Configuration +public class KafkaStreamsConfig { + + @Bean + public OdeTimJsonTopology odeTimJsonTopology( + @Value("${ode.kafka.topics.json.tim}") String timTopic, + OdeKafkaProperties odeKafkaProperties) { + return new OdeTimJsonTopology(odeKafkaProperties, timTopic); + } +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java index 572bbfcb8..68234bfe7 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java @@ -51,14 +51,6 @@ public RsuDepositor(RsuProperties rsuProperties, boolean isDataSigningEnabled) { this.dataSigningEnabled = isDataSigningEnabled; } - public void shutdown() { - running = false; - } - - public boolean isRunning() { - return running; - } - @Override public void run() { try { diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java new file mode 100644 index 000000000..d146fb156 --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java @@ -0,0 +1,16 @@ +package us.dot.its.jpo.ode.rsu; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import us.dot.its.jpo.ode.security.SecurityServicesProperties; + +@Configuration +public class RsuDepositorConfig { + + @Bean + public RsuDepositor rsuDepositor(RsuProperties rsuProperties, SecurityServicesProperties securityServicesProps) { + var rsuDepositor = new RsuDepositor(rsuProperties, securityServicesProps.getIsRsuSigningEnabled()); + rsuDepositor.start(); + return rsuDepositor; + } +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java new file mode 100644 index 000000000..8e6c4fa53 --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java @@ -0,0 +1,12 @@ +package us.dot.its.jpo.ode.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SecurityServicesConfig { + @Bean + public ISecurityServicesClient securityServicesClient(SecurityServicesProperties securityServicesProperties) { + return new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()); + } +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index f0add78c0..f0b2b34cf 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -16,6 +16,9 @@ package us.dot.its.jpo.ode.services.asn1; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; @@ -23,14 +26,25 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; +import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; +import us.dot.its.jpo.ode.model.OdeAsdPayload; import us.dot.its.jpo.ode.model.OdeAsn1Data; +import us.dot.its.jpo.ode.model.OdeMsgMetadata; +import us.dot.its.jpo.ode.model.OdeMsgPayload; +import us.dot.its.jpo.ode.plugin.SNMP; import us.dot.its.jpo.ode.plugin.ServiceRequest; +import us.dot.its.jpo.ode.plugin.SituationDataWarehouse.SDW; +import us.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData; +import us.dot.its.jpo.ode.plugin.j2735.builders.GeoRegionBuilder; +import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; @@ -38,6 +52,7 @@ import us.dot.its.jpo.ode.util.JsonUtils; import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; +import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; import us.dot.its.jpo.ode.wrapper.AbstractSubscriberProcessor; import us.dot.its.jpo.ode.wrapper.MessageProducer; @@ -45,6 +60,7 @@ * The Asn1EncodedDataRouter is responsible for routing encoded TIM messages that are consumed from * the Kafka topic.Asn1EncoderOutput topic and decide whether to route to the SDX or an RSU. **/ +@Component @Slf4j public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor { @@ -114,7 +130,6 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, this.odeTimJsonTopology = odeTimJsonTopology; } - @Override public Object process(String consumedData) { try { log.debug("Consumed: {}", consumedData); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java index a5451376f..7f3b99c46 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java @@ -16,6 +16,8 @@ package us.dot.its.jpo.ode.rsu; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -26,9 +28,6 @@ import us.dot.its.jpo.ode.model.OdeTravelerInputData; import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; - @ExtendWith(SpringExtension.class) @ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) @EnableConfigurationProperties(value = {RsuProperties.class, SecurityServicesProperties.class}) @@ -40,16 +39,6 @@ class RsuDepositorTest { @Autowired SecurityServicesProperties securityServicesProperties; - - @Test - void testShutdown() { - RsuDepositor testRsuDepositor = new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); - testRsuDepositor.shutdown(); - assertFalse(testRsuDepositor.isRunning()); - assertFalse(testRsuDepositor.isAlive()); - } - - @Test void testDeposit() { RsuDepositor testRsuDepositor = new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index c9a69df2b..8767c132c 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; @@ -42,7 +43,6 @@ import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; -import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.ISecurityServicesClient; @@ -104,9 +104,6 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); - var asn1CommandManager = new Asn1CommandManager( - mockRsuDepositor - ); var mockSecServClient = mock(ISecurityServicesClient.class); securityServicesProperties.setIsSdwSigningEnabled(true); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( @@ -115,7 +112,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() jsonTopics, securityServicesProperties, odeTimJsonTopology, - asn1CommandManager, + mockRsuDepositor, mockSecServClient, sdxDepositorTopic ); @@ -193,9 +190,6 @@ void processSNMPDepositOnly() securityServicesProperties.setIsSdwSigningEnabled(true); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); - var asn1CommandManager = new Asn1CommandManager( - mockRsuDepositor - ); var mockSecServClient = new ISecurityServicesClient() { @Override public String signMessage(String message, int sigValidityOverride) { @@ -214,7 +208,7 @@ public String signMessage(String message, int sigValidityOverride) { jsonTopics, securityServicesProperties, odeTimJsonTopology, - asn1CommandManager, + mockRsuDepositor, mockSecServClient, sdxDepositorTopic ); @@ -303,9 +297,6 @@ void processEncodedTimUnsecured() EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); - var asn1CommandManager = new Asn1CommandManager( - mockRsuDepositor - ); var mockSecServClient = new ISecurityServicesClient() { @Override public String signMessage(String message, int sigValidityOverride) { @@ -326,7 +317,7 @@ public String signMessage(String message, int sigValidityOverride) { jsonTopics, securityServicesProperties, odeTimJsonTopology, - asn1CommandManager, + mockRsuDepositor, mockSecServClient, sdxDepositorTopic ); From 1c0e2606f107441eed1ad03aaa612afccc480d08 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 15:33:25 -0700 Subject: [PATCH 030/128] refactor: convert Asn1EncodedDataRouter to KafkaListener and update tests - not working well with other tests --- .../services/asn1/Asn1EncodedDataRouter.java | 41 +++++------- .../asn1/Asn1EncodedDataRouterTest.java | 67 ++++++++++--------- 2 files changed, 51 insertions(+), 57 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index f0b2b34cf..959deb608 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -23,10 +23,12 @@ import java.util.Date; import java.util.HashMap; import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecord; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; @@ -53,7 +55,6 @@ import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; -import us.dot.its.jpo.ode.wrapper.AbstractSubscriberProcessor; import us.dot.its.jpo.ode.wrapper.MessageProducer; /** @@ -62,12 +63,12 @@ **/ @Component @Slf4j -public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor { +public class Asn1EncodedDataRouter { private static final String BYTES = "bytes"; private static final String MESSAGE_FRAME = "MessageFrame"; private static final String ERROR_ON_SDX_DEPOSIT = "Error on SDX deposit."; - public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; + private static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; /** * Exception for Asn1EncodedDataRouter specific failures. @@ -100,8 +101,6 @@ public Asn1EncodedDataRouterException(String string) { * @param asn1CoderTopics The specified ASN1 Coder topics * @param jsonTopics The specified JSON topics to write to * @param securityServicesProperties The security services properties to use - * @param securityServicesClient - * @param sdxDepositTopic **/ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, Asn1CoderTopics asn1CoderTopics, @@ -130,10 +129,11 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, this.odeTimJsonTopology = odeTimJsonTopology; } - public Object process(String consumedData) { + @KafkaListener(topics = "${ode.kafka.topics.asn1EncoderOutput}") + public void listen(ConsumerRecord consumerRecord) { try { - log.debug("Consumed: {}", consumedData); - JSONObject consumedObj = XmlUtils.toJSONObject(consumedData).getJSONObject( + log.debug("Consumed: {}", consumerRecord.value()); + JSONObject consumedObj = XmlUtils.toJSONObject(consumerRecord.value()).getJSONObject( OdeAsn1Data.class.getSimpleName()); /* @@ -181,21 +181,12 @@ public Object process(String consumedData) { + TimTransmogrifier.REQUEST_STRING + "' object in the encoder response"); } } catch (Exception e) { - String msg = "Error in processing received message from ASN.1 Encoder module: " - + consumedData; - if (log.isDebugEnabled()) { - // print error message and stack trace - EventLogger.logger.error(msg, e); - log.error(msg, e); - } else { - // print error message only - EventLogger.logger.error(msg); - log.error(msg); - } + log.error("Error in processing received message with key {} from ASN.1 Encoder module", + consumerRecord.key(), e); } - return null; } + /** * Gets the service request based on the consumed JSONObject. * @@ -287,7 +278,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO String hexEncodedTim = mfObj.getString(BYTES); log.debug("Encoded message - phase 1: {}", hexEncodedTim); - // use Asnc1 library to decode the encoded tim returned from ASNC1; another + // use ASN.1 library to decode the encoded tim returned from ASN.1; another // class two blockers: decode the tim and decode the message-sign // Case 1: SNMP-deposit @@ -467,8 +458,8 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu * @param request the service request object containing meta information, service region, * delivery time, and other necessary data for ASD creation. * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. - * @return a String containing the fully crafted ASD message in XML format. Returns null if the - * message could not be constructed due to exceptions. + * @return a String containing the fully crafted ASD message in XML format. Returns null if the + * message could not be constructed due to exceptions. */ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { @@ -595,7 +586,6 @@ private boolean isHeaderPresent(String encodedTim) { * Strips header from unsigned message (all bytes before 001F hex value). */ private String stripHeader(String encodedUnsignedTim) { - String toReturn = ""; // find 001F hex value int index = encodedUnsignedTim.indexOf("001F"); if (index == -1) { @@ -603,8 +593,7 @@ private String stripHeader(String encodedUnsignedTim) { return encodedUnsignedTim; } // strip everything before 001F - toReturn = encodedUnsignedTim.substring(index); - return toReturn; + return encodedUnsignedTim.substring(index); } private void depositToFilteredTopic(JSONObject metadataObj, String hexEncodedTim) { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 8767c132c..ac5e35599 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -35,10 +35,13 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.listener.MessageListener; import org.springframework.kafka.test.EmbeddedKafkaBroker; +import org.springframework.kafka.test.utils.ContainerTestUtils; import org.springframework.kafka.test.utils.KafkaTestUtils; import org.springframework.test.annotation.DirtiesContext; import us.dot.its.jpo.ode.OdeTimJsonTopology; +import us.dot.its.jpo.ode.kafka.KafkaConsumerConfig; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; @@ -48,7 +51,6 @@ import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; -import us.dot.its.jpo.ode.wrapper.MessageConsumer; @SpringBootTest( properties = { @@ -63,6 +65,7 @@ classes = { KafkaProducerConfig.class, KafkaProperties.class, + KafkaConsumerConfig.class, Asn1CoderTopics.class, JsonTopics.class, SecurityServicesProperties.class, @@ -86,14 +89,15 @@ class Asn1EncodedDataRouterTest { String sdxDepositorTopic; @Autowired KafkaTemplate kafkaTemplate; + @Autowired + KafkaConsumerConfig kafkaConsumerConfig; @Mock RsuDepositor mockRsuDepositor; EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Test - void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() - throws IOException, InterruptedException { + void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), @@ -117,15 +121,14 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() sdxDepositorTopic ); - MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - embeddedKafka.getBrokersAsString(), - "processSignedMessage_depositsToSdxTopicAndTimTmcFiltered", encoderRouter); - - encoderConsumer.setName("Asn1EncoderConsumer"); - encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); - - // Wait for encoderRouter to connect to the broker otherwise the test will fail :( - Thread.sleep(2000); + var container = kafkaConsumerConfig.kafkaListenerContainerFactory() + .createContainer(asn1CoderTopics.getEncoderOutput()); + container.setupMessageListener( + (MessageListener) encoderRouter::listen + ); + container.setBeanName("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered"); + container.start(); + ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( @@ -212,14 +215,14 @@ public String signMessage(String message, int sigValidityOverride) { mockSecServClient, sdxDepositorTopic ); - MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - embeddedKafka.getBrokersAsString(), "processSNMPDepositOnly-default", encoderRouter); - - encoderConsumer.setName("Asn1EncoderConsumer"); - encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); - - // Wait for encoderRouter to connect to the broker otherwise the test will fail :( - Thread.sleep(2000); + var container = kafkaConsumerConfig.kafkaListenerContainerFactory() + .createContainer(asn1CoderTopics.getEncoderOutput()); + container.setupMessageListener( + (MessageListener) encoderRouter::listen + ); + container.setBeanName("processSNMPDepositOnly"); + container.start(); + ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( @@ -258,7 +261,8 @@ public String signMessage(String message, int sigValidityOverride) { assert inputStream != null; var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); var records = KafkaTestUtils.getRecords(testConsumer); - expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + expectedTimTmcFiltered = + expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); for (var consumerRecord : records.records(jsonTopics.getTimTmcFiltered())) { if (consumerRecord.value().contains(streamId)) { assertEquals(expectedTimTmcFiltered, consumerRecord.value()); @@ -287,8 +291,7 @@ public String signMessage(String message, int sigValidityOverride) { } @Test - void processEncodedTimUnsecured() - throws IOException, InterruptedException { + void processEncodedTimUnsecured() throws IOException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimTmcFiltered() @@ -321,14 +324,15 @@ public String signMessage(String message, int sigValidityOverride) { mockSecServClient, sdxDepositorTopic ); - MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); - encoderConsumer.setName("Asn1EncoderConsumer"); - encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); - - // Wait for encoderRouter to connect to the broker otherwise the test will fail :( - Thread.sleep(2000); + var container = kafkaConsumerConfig.kafkaListenerContainerFactory() + .createContainer(asn1CoderTopics.getEncoderOutput()); + container.setupMessageListener( + (MessageListener) encoderRouter::listen + ); + container.setBeanName("processEncodedTimUnsecured"); + container.start(); + ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( @@ -372,7 +376,8 @@ public String signMessage(String message, int sigValidityOverride) { "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); assert inputStream != null; var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + expectedTimTmcFiltered = + expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); for (var consumerRecord : records.records(jsonTopics.getTimTmcFiltered())) { if (consumerRecord.value().contains(streamId)) { From 50605605e05459fe7d6068483fd7ad346801958d Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 20 Dec 2024 08:27:58 -0700 Subject: [PATCH 031/128] Revert "refactor: convert Asn1EncodedDataRouter to KafkaListener and update tests - not working well with other tests" This reverts commit 1c0e2606f107441eed1ad03aaa612afccc480d08. --- .../services/asn1/Asn1EncodedDataRouter.java | 41 +++++++----- .../asn1/Asn1EncodedDataRouterTest.java | 67 +++++++++---------- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 959deb608..f0b2b34cf 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -23,12 +23,10 @@ import java.util.Date; import java.util.HashMap; import lombok.extern.slf4j.Slf4j; -import org.apache.kafka.clients.consumer.ConsumerRecord; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; -import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; @@ -55,6 +53,7 @@ import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; +import us.dot.its.jpo.ode.wrapper.AbstractSubscriberProcessor; import us.dot.its.jpo.ode.wrapper.MessageProducer; /** @@ -63,12 +62,12 @@ **/ @Component @Slf4j -public class Asn1EncodedDataRouter { +public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor { private static final String BYTES = "bytes"; private static final String MESSAGE_FRAME = "MessageFrame"; private static final String ERROR_ON_SDX_DEPOSIT = "Error on SDX deposit."; - private static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; + public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; /** * Exception for Asn1EncodedDataRouter specific failures. @@ -101,6 +100,8 @@ public Asn1EncodedDataRouterException(String string) { * @param asn1CoderTopics The specified ASN1 Coder topics * @param jsonTopics The specified JSON topics to write to * @param securityServicesProperties The security services properties to use + * @param securityServicesClient + * @param sdxDepositTopic **/ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, Asn1CoderTopics asn1CoderTopics, @@ -129,11 +130,10 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, this.odeTimJsonTopology = odeTimJsonTopology; } - @KafkaListener(topics = "${ode.kafka.topics.asn1EncoderOutput}") - public void listen(ConsumerRecord consumerRecord) { + public Object process(String consumedData) { try { - log.debug("Consumed: {}", consumerRecord.value()); - JSONObject consumedObj = XmlUtils.toJSONObject(consumerRecord.value()).getJSONObject( + log.debug("Consumed: {}", consumedData); + JSONObject consumedObj = XmlUtils.toJSONObject(consumedData).getJSONObject( OdeAsn1Data.class.getSimpleName()); /* @@ -181,12 +181,21 @@ public void listen(ConsumerRecord consumerRecord) { + TimTransmogrifier.REQUEST_STRING + "' object in the encoder response"); } } catch (Exception e) { - log.error("Error in processing received message with key {} from ASN.1 Encoder module", - consumerRecord.key(), e); + String msg = "Error in processing received message from ASN.1 Encoder module: " + + consumedData; + if (log.isDebugEnabled()) { + // print error message and stack trace + EventLogger.logger.error(msg, e); + log.error(msg, e); + } else { + // print error message only + EventLogger.logger.error(msg); + log.error(msg); + } } + return null; } - /** * Gets the service request based on the consumed JSONObject. * @@ -278,7 +287,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO String hexEncodedTim = mfObj.getString(BYTES); log.debug("Encoded message - phase 1: {}", hexEncodedTim); - // use ASN.1 library to decode the encoded tim returned from ASN.1; another + // use Asnc1 library to decode the encoded tim returned from ASNC1; another // class two blockers: decode the tim and decode the message-sign // Case 1: SNMP-deposit @@ -458,8 +467,8 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu * @param request the service request object containing meta information, service region, * delivery time, and other necessary data for ASD creation. * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. - * @return a String containing the fully crafted ASD message in XML format. Returns null if the - * message could not be constructed due to exceptions. + * @return a String containing the fully crafted ASD message in XML format. Returns null if the + * message could not be constructed due to exceptions. */ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { @@ -586,6 +595,7 @@ private boolean isHeaderPresent(String encodedTim) { * Strips header from unsigned message (all bytes before 001F hex value). */ private String stripHeader(String encodedUnsignedTim) { + String toReturn = ""; // find 001F hex value int index = encodedUnsignedTim.indexOf("001F"); if (index == -1) { @@ -593,7 +603,8 @@ private String stripHeader(String encodedUnsignedTim) { return encodedUnsignedTim; } // strip everything before 001F - return encodedUnsignedTim.substring(index); + toReturn = encodedUnsignedTim.substring(index); + return toReturn; } private void depositToFilteredTopic(JSONObject metadataObj, String hexEncodedTim) { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index ac5e35599..8767c132c 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -35,13 +35,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.kafka.listener.MessageListener; import org.springframework.kafka.test.EmbeddedKafkaBroker; -import org.springframework.kafka.test.utils.ContainerTestUtils; import org.springframework.kafka.test.utils.KafkaTestUtils; import org.springframework.test.annotation.DirtiesContext; import us.dot.its.jpo.ode.OdeTimJsonTopology; -import us.dot.its.jpo.ode.kafka.KafkaConsumerConfig; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; @@ -51,6 +48,7 @@ import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; +import us.dot.its.jpo.ode.wrapper.MessageConsumer; @SpringBootTest( properties = { @@ -65,7 +63,6 @@ classes = { KafkaProducerConfig.class, KafkaProperties.class, - KafkaConsumerConfig.class, Asn1CoderTopics.class, JsonTopics.class, SecurityServicesProperties.class, @@ -89,15 +86,14 @@ class Asn1EncodedDataRouterTest { String sdxDepositorTopic; @Autowired KafkaTemplate kafkaTemplate; - @Autowired - KafkaConsumerConfig kafkaConsumerConfig; @Mock RsuDepositor mockRsuDepositor; EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Test - void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOException { + void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() + throws IOException, InterruptedException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), @@ -121,14 +117,15 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti sdxDepositorTopic ); - var container = kafkaConsumerConfig.kafkaListenerContainerFactory() - .createContainer(asn1CoderTopics.getEncoderOutput()); - container.setupMessageListener( - (MessageListener) encoderRouter::listen - ); - container.setBeanName("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered"); - container.start(); - ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); + MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( + embeddedKafka.getBrokersAsString(), + "processSignedMessage_depositsToSdxTopicAndTimTmcFiltered", encoderRouter); + + encoderConsumer.setName("Asn1EncoderConsumer"); + encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); + + // Wait for encoderRouter to connect to the broker otherwise the test will fail :( + Thread.sleep(2000); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( @@ -215,14 +212,14 @@ public String signMessage(String message, int sigValidityOverride) { mockSecServClient, sdxDepositorTopic ); - var container = kafkaConsumerConfig.kafkaListenerContainerFactory() - .createContainer(asn1CoderTopics.getEncoderOutput()); - container.setupMessageListener( - (MessageListener) encoderRouter::listen - ); - container.setBeanName("processSNMPDepositOnly"); - container.start(); - ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); + MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( + embeddedKafka.getBrokersAsString(), "processSNMPDepositOnly-default", encoderRouter); + + encoderConsumer.setName("Asn1EncoderConsumer"); + encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); + + // Wait for encoderRouter to connect to the broker otherwise the test will fail :( + Thread.sleep(2000); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( @@ -261,8 +258,7 @@ public String signMessage(String message, int sigValidityOverride) { assert inputStream != null; var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); var records = KafkaTestUtils.getRecords(testConsumer); - expectedTimTmcFiltered = - expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); for (var consumerRecord : records.records(jsonTopics.getTimTmcFiltered())) { if (consumerRecord.value().contains(streamId)) { assertEquals(expectedTimTmcFiltered, consumerRecord.value()); @@ -291,7 +287,8 @@ public String signMessage(String message, int sigValidityOverride) { } @Test - void processEncodedTimUnsecured() throws IOException { + void processEncodedTimUnsecured() + throws IOException, InterruptedException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimTmcFiltered() @@ -324,15 +321,14 @@ public String signMessage(String message, int sigValidityOverride) { mockSecServClient, sdxDepositorTopic ); + MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( + embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); - var container = kafkaConsumerConfig.kafkaListenerContainerFactory() - .createContainer(asn1CoderTopics.getEncoderOutput()); - container.setupMessageListener( - (MessageListener) encoderRouter::listen - ); - container.setBeanName("processEncodedTimUnsecured"); - container.start(); - ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); + encoderConsumer.setName("Asn1EncoderConsumer"); + encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); + + // Wait for encoderRouter to connect to the broker otherwise the test will fail :( + Thread.sleep(2000); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( @@ -376,8 +372,7 @@ public String signMessage(String message, int sigValidityOverride) { "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); assert inputStream != null; var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - expectedTimTmcFiltered = - expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); for (var consumerRecord : records.records(jsonTopics.getTimTmcFiltered())) { if (consumerRecord.value().contains(streamId)) { From d5e8f1b3b8188e9a14cc5d7eb6fa4bbecf0fc259 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 20 Dec 2024 08:27:58 -0700 Subject: [PATCH 032/128] Revert "refactor: introduce Config beans for Asn1EncodedDataRouter dependencies" This reverts commit da1b379eb5e88f59340ea19337c7dd38cc6d4497. --- .../its/jpo/ode/kafka/KafkaStreamsConfig.java | 21 ------------------- .../us/dot/its/jpo/ode/rsu/RsuDepositor.java | 8 +++++++ .../its/jpo/ode/rsu/RsuDepositorConfig.java | 16 -------------- .../ode/security/SecurityServicesConfig.java | 12 ----------- .../services/asn1/Asn1EncodedDataRouter.java | 17 +-------------- .../dot/its/jpo/ode/rsu/RsuDepositorTest.java | 15 +++++++++++-- .../asn1/Asn1EncodedDataRouterTest.java | 17 +++++++++++---- 7 files changed, 35 insertions(+), 71 deletions(-) delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java deleted file mode 100644 index 3c2576881..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package us.dot.its.jpo.ode.kafka; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import us.dot.its.jpo.ode.OdeTimJsonTopology; - -/** - * KafkaStreamsConfig is a Spring configuration class that provides - * beans related to Kafka Streams topology setup. - */ -@Configuration -public class KafkaStreamsConfig { - - @Bean - public OdeTimJsonTopology odeTimJsonTopology( - @Value("${ode.kafka.topics.json.tim}") String timTopic, - OdeKafkaProperties odeKafkaProperties) { - return new OdeTimJsonTopology(odeKafkaProperties, timTopic); - } -} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java index 68234bfe7..572bbfcb8 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java @@ -51,6 +51,14 @@ public RsuDepositor(RsuProperties rsuProperties, boolean isDataSigningEnabled) { this.dataSigningEnabled = isDataSigningEnabled; } + public void shutdown() { + running = false; + } + + public boolean isRunning() { + return running; + } + @Override public void run() { try { diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java deleted file mode 100644 index d146fb156..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java +++ /dev/null @@ -1,16 +0,0 @@ -package us.dot.its.jpo.ode.rsu; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import us.dot.its.jpo.ode.security.SecurityServicesProperties; - -@Configuration -public class RsuDepositorConfig { - - @Bean - public RsuDepositor rsuDepositor(RsuProperties rsuProperties, SecurityServicesProperties securityServicesProps) { - var rsuDepositor = new RsuDepositor(rsuProperties, securityServicesProps.getIsRsuSigningEnabled()); - rsuDepositor.start(); - return rsuDepositor; - } -} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java deleted file mode 100644 index 8e6c4fa53..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java +++ /dev/null @@ -1,12 +0,0 @@ -package us.dot.its.jpo.ode.security; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class SecurityServicesConfig { - @Bean - public ISecurityServicesClient securityServicesClient(SecurityServicesProperties securityServicesProperties) { - return new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()); - } -} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index f0b2b34cf..f0add78c0 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -16,9 +16,6 @@ package us.dot.its.jpo.ode.services.asn1; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; @@ -26,25 +23,14 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; -import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; -import us.dot.its.jpo.ode.model.OdeAsdPayload; import us.dot.its.jpo.ode.model.OdeAsn1Data; -import us.dot.its.jpo.ode.model.OdeMsgMetadata; -import us.dot.its.jpo.ode.model.OdeMsgPayload; -import us.dot.its.jpo.ode.plugin.SNMP; import us.dot.its.jpo.ode.plugin.ServiceRequest; -import us.dot.its.jpo.ode.plugin.SituationDataWarehouse.SDW; -import us.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData; -import us.dot.its.jpo.ode.plugin.j2735.builders.GeoRegionBuilder; -import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; @@ -52,7 +38,6 @@ import us.dot.its.jpo.ode.util.JsonUtils; import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; -import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; import us.dot.its.jpo.ode.wrapper.AbstractSubscriberProcessor; import us.dot.its.jpo.ode.wrapper.MessageProducer; @@ -60,7 +45,6 @@ * The Asn1EncodedDataRouter is responsible for routing encoded TIM messages that are consumed from * the Kafka topic.Asn1EncoderOutput topic and decide whether to route to the SDX or an RSU. **/ -@Component @Slf4j public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor { @@ -130,6 +114,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, this.odeTimJsonTopology = odeTimJsonTopology; } + @Override public Object process(String consumedData) { try { log.debug("Consumed: {}", consumedData); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java index 7f3b99c46..a5451376f 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java @@ -16,8 +16,6 @@ package us.dot.its.jpo.ode.rsu; -import static org.junit.jupiter.api.Assertions.assertEquals; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +26,9 @@ import us.dot.its.jpo.ode.model.OdeTravelerInputData; import us.dot.its.jpo.ode.security.SecurityServicesProperties; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + @ExtendWith(SpringExtension.class) @ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) @EnableConfigurationProperties(value = {RsuProperties.class, SecurityServicesProperties.class}) @@ -39,6 +40,16 @@ class RsuDepositorTest { @Autowired SecurityServicesProperties securityServicesProperties; + + @Test + void testShutdown() { + RsuDepositor testRsuDepositor = new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); + testRsuDepositor.shutdown(); + assertFalse(testRsuDepositor.isRunning()); + assertFalse(testRsuDepositor.isAlive()); + } + + @Test void testDeposit() { RsuDepositor testRsuDepositor = new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 8767c132c..c9a69df2b 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -29,7 +29,6 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; @@ -43,6 +42,7 @@ import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; +import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.ISecurityServicesClient; @@ -104,6 +104,9 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); + var asn1CommandManager = new Asn1CommandManager( + mockRsuDepositor + ); var mockSecServClient = mock(ISecurityServicesClient.class); securityServicesProperties.setIsSdwSigningEnabled(true); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( @@ -112,7 +115,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() jsonTopics, securityServicesProperties, odeTimJsonTopology, - mockRsuDepositor, + asn1CommandManager, mockSecServClient, sdxDepositorTopic ); @@ -190,6 +193,9 @@ void processSNMPDepositOnly() securityServicesProperties.setIsSdwSigningEnabled(true); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); + var asn1CommandManager = new Asn1CommandManager( + mockRsuDepositor + ); var mockSecServClient = new ISecurityServicesClient() { @Override public String signMessage(String message, int sigValidityOverride) { @@ -208,7 +214,7 @@ public String signMessage(String message, int sigValidityOverride) { jsonTopics, securityServicesProperties, odeTimJsonTopology, - mockRsuDepositor, + asn1CommandManager, mockSecServClient, sdxDepositorTopic ); @@ -297,6 +303,9 @@ void processEncodedTimUnsecured() EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); + var asn1CommandManager = new Asn1CommandManager( + mockRsuDepositor + ); var mockSecServClient = new ISecurityServicesClient() { @Override public String signMessage(String message, int sigValidityOverride) { @@ -317,7 +326,7 @@ public String signMessage(String message, int sigValidityOverride) { jsonTopics, securityServicesProperties, odeTimJsonTopology, - mockRsuDepositor, + asn1CommandManager, mockSecServClient, sdxDepositorTopic ); From b57d7660acf83203f4e06b0f185df4f7fb505387 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 20 Dec 2024 08:27:58 -0700 Subject: [PATCH 033/128] Revert "refactor: replace SDXDepositorTopics.java reference with Value reference for topic" This reverts commit cdb14a2798df85fc0365f6a6b3e914b5bce9da75. --- .../ode/kafka/topics/SDXDepositorTopics.java | 12 +++++++++ .../services/asn1/Asn1EncodedDataRouter.java | 2 +- .../kafka/topics/SDXDepositorTopicsTest.java | 25 +++++++++++++++++++ .../asn1/Asn1EncodedDataRouterTest.java | 17 +++++++------ 4 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java create mode 100644 jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java new file mode 100644 index 000000000..d98e76e8b --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java @@ -0,0 +1,12 @@ +package us.dot.its.jpo.ode.kafka.topics; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "ode.kafka.topics.sdx-depositor") +@Data +public class SDXDepositorTopics { + private String input; +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index f0add78c0..7b5c97624 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -94,7 +94,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, OdeTimJsonTopology odeTimJsonTopology, RsuDepositor rsuDepositor, ISecurityServicesClient securityServicesClient, - @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic) { + String sdxDepositTopic) { super(); this.asn1CoderTopics = asn1CoderTopics; diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java new file mode 100644 index 000000000..bc700a7dd --- /dev/null +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java @@ -0,0 +1,25 @@ +package us.dot.its.jpo.ode.kafka.topics; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) +@EnableConfigurationProperties(value = SDXDepositorTopics.class) +class SDXDepositorTopicsTest { + + @Autowired + SDXDepositorTopics sdxDepositorTopics; + + @Test + void getInput() { + assertEquals("topic.SDWDepositorInput", sdxDepositorTopics.getInput()); + } +} \ No newline at end of file diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index c9a69df2b..0534a0578 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -64,6 +64,7 @@ KafkaProducerConfig.class, KafkaProperties.class, Asn1CoderTopics.class, + SDXDepositorTopics.class, JsonTopics.class, SecurityServicesProperties.class, RsuProperties.class, @@ -82,8 +83,8 @@ class Asn1EncodedDataRouterTest { SecurityServicesProperties securityServicesProperties; @Autowired OdeKafkaProperties odeKafkaProperties; - @Value("${ode.kafka.topics.sdx-depositor.input}") - String sdxDepositorTopic; + @Autowired + SDXDepositorTopics sdxDepositorTopics; @Autowired KafkaTemplate kafkaTemplate; @Mock @@ -98,7 +99,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimTmcFiltered(), - sdxDepositorTopic + sdxDepositorTopics.getInput() }; EmbeddedKafkaHolder.addTopics(topicsForConsumption); EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); @@ -117,7 +118,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() odeTimJsonTopology, asn1CommandManager, mockSecServClient, - sdxDepositorTopic + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( @@ -161,7 +162,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() var records = KafkaTestUtils.getRecords(testConsumer); var sdxDepositorRecord = records - .records(sdxDepositorTopic); + .records(sdxDepositorTopics.getInput()); for (var consumerRecord : sdxDepositorRecord) { if (consumerRecord.value().contains(streamId)) { assertEquals(expected, consumerRecord.value()); @@ -216,7 +217,7 @@ public String signMessage(String message, int sigValidityOverride) { odeTimJsonTopology, asn1CommandManager, mockSecServClient, - sdxDepositorTopic + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), "processSNMPDepositOnly-default", encoderRouter); @@ -328,7 +329,7 @@ public String signMessage(String message, int sigValidityOverride) { odeTimJsonTopology, asn1CommandManager, mockSecServClient, - sdxDepositorTopic + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); @@ -370,7 +371,7 @@ public String signMessage(String message, int sigValidityOverride) { var records = KafkaTestUtils.getRecords(testConsumer); var sdxDepositorRecord = records - .records(sdxDepositorTopic); + .records(sdxDepositorTopics.getInput()); for (var consumerRecord : sdxDepositorRecord) { if (consumerRecord.value().contains(streamId)) { assertEquals(expected, consumerRecord.value()); From 34a9dc1587ac6f413352c133a96741bdaebeadb9 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 20 Dec 2024 08:27:58 -0700 Subject: [PATCH 034/128] Revert "refactor: pull Asn1CommandManager functionality into Asn1EncodedDataRouter" This reverts commit c99bbe8aebef56e6a2078f08b2e4c890defb3c39. --- .../ode/services/asn1/Asn1CommandManager.java | 167 ++++++++++++++++++ .../services/asn1/Asn1EncodedDataRouter.java | 122 ++----------- .../services/asn1/Asn1CommandManagerTest.java | 82 +++++++++ 3 files changed, 260 insertions(+), 111 deletions(-) create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java create mode 100644 jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java new file mode 100644 index 000000000..3b5401447 --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -0,0 +1,167 @@ +/*============================================================================= + * Copyright 2018 572682 + * + * Licensed 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 us.dot.its.jpo.ode.services.asn1; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.text.ParseException; +import lombok.extern.slf4j.Slf4j; +import us.dot.its.jpo.ode.context.AppContext; +import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; +import us.dot.its.jpo.ode.model.OdeAsdPayload; +import us.dot.its.jpo.ode.model.OdeMsgMetadata; +import us.dot.its.jpo.ode.model.OdeMsgPayload; +import us.dot.its.jpo.ode.plugin.SNMP; +import us.dot.its.jpo.ode.plugin.ServiceRequest; +import us.dot.its.jpo.ode.plugin.SituationDataWarehouse.SDW; +import us.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData; +import us.dot.its.jpo.ode.plugin.j2735.builders.GeoRegionBuilder; +import us.dot.its.jpo.ode.rsu.RsuDepositor; +import us.dot.its.jpo.ode.traveler.TimTransmogrifier; +import us.dot.its.jpo.ode.util.JsonUtils; +import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; +import us.dot.its.jpo.ode.util.XmlUtils; +import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; + +/** + * Manages operations related to ASN.1 command processing and interaction with RSUs (Road Side + * Units). Handles the preparation, packaging, and distribution of messages to RSUs using an + * {@link RsuDepositor}. + */ +@Slf4j +public class Asn1CommandManager { + + public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; + + private RsuDepositor rsuDepositor; + + /** + * Constructs an instance of Asn1CommandManager and initializes the RSU depositor. + * + * @param rsuDepositor The RSU depositor instance to be used for managing ASN.1 commands. This + * instance is started during initialization. + */ + public Asn1CommandManager(RsuDepositor rsuDepositor) { + this.rsuDepositor = rsuDepositor; + this.rsuDepositor.start(); + } + + /** + * Sends a message to the RSUs (Road Side Units) through the RSU depositor. + * + * @param request the service request containing relevant details for the operation + * @param encodedMsg the message to be sent, encoded in the desired format + */ + public void sendToRsus(ServiceRequest request, String encodedMsg) { + rsuDepositor.deposit(request, encodedMsg); + } + + /** + * Constructs an XML representation of an Advisory Situation Data (ASD) message containing a + * signed Traveler Information Message (TIM). Processes the provided service request and signed + * message to create and structure the ASD before converting it to an XML string output. + * + * @param request the service request object containing meta information, service region, + * delivery time, and other necessary data for ASD creation. + * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. + * @return a String containing the fully crafted ASD message in XML format. Returns null if the + * message could not be constructed due to exceptions. + */ + public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { + + SDW sdw = request.getSdw(); + SNMP snmp = request.getSnmp(); + DdsAdvisorySituationData asd; + + byte sendToRsu = + request.getRsus() != null ? DdsAdvisorySituationData.RSU : DdsAdvisorySituationData.NONE; + byte distroType = (byte) (DdsAdvisorySituationData.IP | sendToRsu); + + String outputXml = null; + try { + if (null != snmp) { + + asd = new DdsAdvisorySituationData() + .setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null) + .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) + .setTimeToLive(sdw.getTtl()) + .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); + } else { + asd = new DdsAdvisorySituationData() + .setAsdmDetails(sdw.getDeliverystart(), sdw.getDeliverystop(), distroType, null) + .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) + .setTimeToLive(sdw.getTtl()) + .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); + } + + ObjectNode dataBodyObj = JsonUtils.newNode(); + ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); + ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); + admDetailsObj.remove("advisoryMessage"); + admDetailsObj.put("advisoryMessage", signedMsg); + + dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); + + OdeMsgPayload payload = new OdeAsdPayload(asd); + + ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); + payloadObj.set(AppContext.DATA_STRING, dataBodyObj); + + OdeMsgMetadata metadata = new OdeMsgMetadata(payload); + ObjectNode metaObject = JsonUtils.toObjectNode(metadata.toJson()); + + ObjectNode requestObj = JsonUtils.toObjectNode(JsonUtils.toJson(request, false)); + + requestObj.remove("tim"); + + metaObject.set("request", requestObj); + + ArrayNode encodings = buildEncodings(); + ObjectNode enc = + XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, encodings); + metaObject.set(AppContext.ENCODINGS_STRING, enc); + + ObjectNode message = JsonUtils.newNode(); + message.set(AppContext.METADATA_STRING, metaObject); + message.set(AppContext.PAYLOAD_STRING, payloadObj); + + ObjectNode root = JsonUtils.newNode(); + root.set(AppContext.ODE_ASN1_DATA, message); + + outputXml = XmlUtils.toXmlStatic(root); + + // remove the surrounding + outputXml = outputXml.replace("", ""); + outputXml = outputXml.replace("", ""); + + } catch (ParseException | JsonUtilsException | XmlUtilsException e) { + log.error("Parsing exception thrown while populating ASD structure: ", e); + } + + log.debug("Fully crafted ASD to be encoded: {}", outputXml); + + return outputXml; + } + + private ArrayNode buildEncodings() throws JsonUtilsException { + ArrayNode encodings = JsonUtils.newArrayNode(); + encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, + ADVISORY_SITUATION_DATA_STRING, + EncodingRule.UPER)); + return encodings; + } +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 7b5c97624..f923ab44e 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -51,7 +51,6 @@ public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor stringMsgProducer; private final OdeTimJsonTopology odeTimJsonTopology; - private final RsuDepositor rsuDepositor; + private final Asn1CommandManager asn1CommandManager; private final boolean dataSigningEnabledSDW; private final boolean dataSigningEnabledRSU; @@ -92,7 +91,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, JsonTopics jsonTopics, SecurityServicesProperties securityServicesProperties, OdeTimJsonTopology odeTimJsonTopology, - RsuDepositor rsuDepositor, + Asn1CommandManager asn1CommandManager, ISecurityServicesClient securityServicesClient, String sdxDepositTopic) { super(); @@ -107,7 +106,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, odeKafkaProperties.getKafkaType(), odeKafkaProperties.getDisabledTopics()); - this.rsuDepositor = rsuDepositor; + this.asn1CommandManager = asn1CommandManager; this.dataSigningEnabledSDW = securityServicesProperties.getIsSdwSigningEnabled(); this.dataSigningEnabledRSU = securityServicesProperties.getIsRsuSigningEnabled(); @@ -230,7 +229,7 @@ public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) { // CASE 3: If SDW in metadata and ASD in body (double encoding complete) // - send to SDX - if (!dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { + if (!dataObj.has(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING)) { processSNMPDepositOnly(request, consumedObj, dataObj, metadataObj); } else { // We have encoded ASD. It could be either UNSECURED or secured. @@ -247,7 +246,7 @@ private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { // We have a ASD with signed MessageFrame // Case 3 JSONObject asdObj = dataObj.getJSONObject( - ADVISORY_SITUATION_DATA_STRING); + Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING); try { JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); @@ -295,7 +294,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO if (null != request.getSnmp() && null != request.getRsus() && null != hexEncodedTim) { log.info("Sending message to RSUs..."); - sendToRsus(request, hexEncodedTim); + asn1CommandManager.sendToRsus(request, hexEncodedTim); } hexEncodedTim = mfObj.getString(BYTES); @@ -311,7 +310,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO // Case 2 only log.debug("Publishing message for round 2 encoding!"); - String xmlizedMessage = packageSignedTimIntoAsd(request, hexEncodedTim); + String xmlizedMessage = asn1CommandManager.packageSignedTimIntoAsd(request, hexEncodedTim); stringMsgProducer.send(asn1CoderTopics.getEncoderInput(), null, xmlizedMessage); } @@ -336,8 +335,8 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum if (null != request.getSdw()) { JSONObject asdObj = null; - if (dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { - asdObj = dataObj.getJSONObject(ADVISORY_SITUATION_DATA_STRING); + if (dataObj.has(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING)) { + asdObj = dataObj.getJSONObject(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING); } else { log.error("ASD structure present in metadata but not in JSONObject!"); } @@ -388,9 +387,9 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum log.debug("Encoded message - phase 2: {}", encodedTim); // only send message to rsu if snmp, rsus, and message frame fields are present - if (null != request.getSnmp() && null != request.getRsus()) { + if (null != request.getSnmp() && null != request.getRsus() && null != encodedTim) { log.debug("Encoded message phase 3: {}", encodedTim); - sendToRsus(request, encodedTim); + asn1CommandManager.sendToRsus(request, encodedTim); } } @@ -444,105 +443,6 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu return encodedTIM; } - /** - * Constructs an XML representation of an Advisory Situation Data (ASD) message containing a - * signed Traveler Information Message (TIM). Processes the provided service request and signed - * message to create and structure the ASD before converting it to an XML string output. - * - * @param request the service request object containing meta information, service region, - * delivery time, and other necessary data for ASD creation. - * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. - * @return a String containing the fully crafted ASD message in XML format. Returns null if the - * message could not be constructed due to exceptions. - */ - public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { - - SDW sdw = request.getSdw(); - SNMP snmp = request.getSnmp(); - DdsAdvisorySituationData asd; - - byte sendToRsu = - request.getRsus() != null ? DdsAdvisorySituationData.RSU : DdsAdvisorySituationData.NONE; - byte distroType = (byte) (DdsAdvisorySituationData.IP | sendToRsu); - - String outputXml = null; - try { - if (null != snmp) { - - asd = new DdsAdvisorySituationData() - .setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null) - .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) - .setTimeToLive(sdw.getTtl()) - .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - } else { - asd = new DdsAdvisorySituationData() - .setAsdmDetails(sdw.getDeliverystart(), sdw.getDeliverystop(), distroType, null) - .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) - .setTimeToLive(sdw.getTtl()) - .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - } - - ObjectNode dataBodyObj = JsonUtils.newNode(); - ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); - ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); - admDetailsObj.remove("advisoryMessage"); - admDetailsObj.put("advisoryMessage", signedMsg); - - dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); - - OdeMsgPayload payload = new OdeAsdPayload(asd); - - ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); - payloadObj.set(AppContext.DATA_STRING, dataBodyObj); - - OdeMsgMetadata metadata = new OdeMsgMetadata(payload); - ObjectNode metaObject = JsonUtils.toObjectNode(metadata.toJson()); - - ObjectNode requestObj = JsonUtils.toObjectNode(JsonUtils.toJson(request, false)); - - requestObj.remove("tim"); - - metaObject.set("request", requestObj); - - ArrayNode encodings = buildEncodings(); - ObjectNode enc = - XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, encodings); - metaObject.set(AppContext.ENCODINGS_STRING, enc); - - ObjectNode message = JsonUtils.newNode(); - message.set(AppContext.METADATA_STRING, metaObject); - message.set(AppContext.PAYLOAD_STRING, payloadObj); - - ObjectNode root = JsonUtils.newNode(); - root.set(AppContext.ODE_ASN1_DATA, message); - - outputXml = XmlUtils.toXmlStatic(root); - - // remove the surrounding - outputXml = outputXml.replace("", ""); - outputXml = outputXml.replace("", ""); - - } catch (ParseException | JsonUtilsException | XmlUtilsException e) { - log.error("Parsing exception thrown while populating ASD structure: ", e); - } - - log.debug("Fully crafted ASD to be encoded: {}", outputXml); - - return outputXml; - } - - private ArrayNode buildEncodings() throws JsonUtilsException { - ArrayNode encodings = JsonUtils.newArrayNode(); - encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, - ADVISORY_SITUATION_DATA_STRING, - EncodingRule.UPER)); - return encodings; - } - - private void sendToRsus(ServiceRequest request, String encodedMsg) { - rsuDepositor.deposit(request, encodedMsg); - } - private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String timStartDateTime, int maxDurationTime, JSONObject timWithExpiration) { try { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java new file mode 100644 index 000000000..9207afd96 --- /dev/null +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java @@ -0,0 +1,82 @@ +/*=========================================================================== + * Copyright 2018 572682 + * + * Licensed 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 us.dot.its.jpo.ode.services.asn1; + +import mockit.Capturing; +import mockit.Injectable; +import mockit.Mocked; +import mockit.Tested; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; +import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; +import us.dot.its.jpo.ode.model.OdeTravelerInputData; +import us.dot.its.jpo.ode.rsu.RsuProperties; +import us.dot.its.jpo.ode.security.SecurityServicesProperties; +import us.dot.its.jpo.ode.snmp.SnmpSession; +import us.dot.its.jpo.ode.wrapper.MessageProducer; + +class Asn1CommandManagerTest { + + @Tested + Asn1CommandManager testAsn1CommandManager; + + @Injectable + OdeKafkaProperties injectableOdeKafkaProperties; + + @Injectable + SDXDepositorTopics injectableSDXDepositorTopics; + + @Injectable + RsuProperties injectableRsuProperties; + + @Injectable + SecurityServicesProperties injectableSecurityServicesProperties; + + @Capturing + MessageProducer capturingMessageProducer; + @Capturing + SnmpSession capturingSnmpSession; + + @Injectable + OdeTravelerInputData injectableOdeTravelerInputData; + + @Mocked + MessageProducer mockMessageProducer; + + @Test + @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") + void testPackageSignedTimIntoAsd() { + testAsn1CommandManager.packageSignedTimIntoAsd(injectableOdeTravelerInputData.getRequest(), + "message"); + } + + @Test + @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") + void testSendToRsus(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { + + testAsn1CommandManager.sendToRsus(mockOdeTravelerInputData.getRequest(), "message"); + } + + @Test + @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") + void testSendToRsusSnmpException(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { + + testAsn1CommandManager.sendToRsus(mockOdeTravelerInputData.getRequest(), "message"); + } + +} From 692d3b26880a9f09270a9e2b6aa640dd3bd886ec Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 20 Dec 2024 08:27:59 -0700 Subject: [PATCH 035/128] Revert "chore: delete unused AsnCodecRouterServiceController.java" This reverts commit 24fbba78c352fa84fbef523840d097ac9a7832e6. --- .../asn1/AsnCodecRouterServiceController.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java new file mode 100644 index 000000000..c6e0ed63a --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright 2018 572682 + * + * Licensed 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 us.dot.its.jpo.ode.services.asn1; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import us.dot.its.jpo.ode.OdeTimJsonTopology; +import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; +import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; +import us.dot.its.jpo.ode.kafka.topics.JsonTopics; +import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; +import us.dot.its.jpo.ode.rsu.RsuDepositor; +import us.dot.its.jpo.ode.rsu.RsuProperties; +import us.dot.its.jpo.ode.security.SecurityServicesClientImpl; +import us.dot.its.jpo.ode.security.SecurityServicesProperties; +import us.dot.its.jpo.ode.wrapper.MessageConsumer; + +/** + * Launches ToJsonConverter service + */ +@Controller +@Slf4j +public class AsnCodecRouterServiceController { + + @Autowired + public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, + JsonTopics jsonTopics, + Asn1CoderTopics asn1CoderTopics, + SDXDepositorTopics sdxDepositorTopics, + RsuProperties rsuProperties, + SecurityServicesProperties securityServicesProperties) { + super(); + + log.info("Starting {}", this.getClass().getSimpleName()); + + // asn1_codec Encoder Routing + log.info("Routing ENCODED data received ASN.1 Encoder"); + + Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( + odeKafkaProperties, + asn1CoderTopics, + jsonTopics, + securityServicesProperties, + new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()), + new Asn1CommandManager( + new RsuDepositor( + rsuProperties, + securityServicesProperties.getIsRsuSigningEnabled() + ) + ), + new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()), + sdxDepositorTopics.getInput() + ); + + MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( + odeKafkaProperties.getBrokers(), this.getClass().getSimpleName(), encoderRouter); + + encoderConsumer.setName("Asn1EncoderConsumer"); + encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); + } +} From f08c0fdec564f4cb03c1dac458d2982d99a551f7 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 20 Dec 2024 08:27:59 -0700 Subject: [PATCH 036/128] Revert "style: reformat and add missing Javadocs SnmpSession" This reverts commit 79e0e0ca446f73561bf3c2eac2332537e9ecde58. --- .../us/dot/its/jpo/ode/snmp/SnmpSession.java | 697 +++++++++--------- 1 file changed, 336 insertions(+), 361 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java index 86a55c41c..b904f9837 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java @@ -1,4 +1,4 @@ -/*============================================================================ +/******************************************************************************* * Copyright 2018 572682 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -13,7 +13,6 @@ * License for the specific language governing permissions and limitations under * the License. ******************************************************************************/ - package us.dot.its.jpo.ode.snmp; import java.io.IOException; @@ -48,390 +47,366 @@ import us.dot.its.jpo.ode.plugin.SnmpProtocol; /** - * This object is used to abstract away the complexities of SNMP calls and allow a user to more - * quickly and easily send SNMP requests. Note that the "connection" aspect of this class is an - * abstraction meant to reinforce that these objects correspond 1-to-1 with a destination server, - * while SNMP is sent over UDP and is actually connection-less. + * This object is used to abstract away the complexities of SNMP calls and allow + * a user to more quickly and easily send SNMP requests. Note that the + * "connection" aspect of this class is an abstraction meant to reinforce that + * these objects correspond 1-to-1 with a destination server, while SNMP is sent + * over UDP and is actually connection-less. */ public class SnmpSession { - private static final Logger logger = LoggerFactory.getLogger(SnmpSession.class); - - private Snmp snmp; - private TransportMapping transport; - private UserTarget target; - - private boolean ready = false; - private boolean listening; - - /** - * Constructor for SnmpSession. - * - * @throws IOException when failing to create the snmp Transport - */ - public SnmpSession(RSU rsu) throws IOException { - Address addr = GenericAddress.parse(rsu.getRsuTarget() + "/161"); - - // Create a "target" to which a request is sent - target = new UserTarget(); - target.setAddress(addr); - target.setRetries(rsu.getRsuRetries()); - target.setTimeout(rsu.getRsuTimeout()); - target.setVersion(SnmpConstants.version3); - if (rsu.getRsuUsername() != null) { - target.setSecurityLevel(SecurityLevel.AUTH_PRIV); - target.setSecurityName(new OctetString(rsu.getRsuUsername())); - } else { - target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); + private static final Logger logger = LoggerFactory.getLogger(SnmpSession.class); + + private Snmp snmp; + private TransportMapping transport; + private UserTarget target; + + private boolean ready = false; + private boolean listening; + + /** + * Constructor for SnmpSession + * + * @param props SnmpProperties for the session (target address, retries, + * timeout, etc) + * @throws IOException + */ + public SnmpSession(RSU rsu) throws IOException { + Address addr = GenericAddress.parse(rsu.getRsuTarget() + "/161"); + + // Create a "target" to which a request is sent + target = new UserTarget(); + target.setAddress(addr); + target.setRetries(rsu.getRsuRetries()); + target.setTimeout(rsu.getRsuTimeout()); + target.setVersion(SnmpConstants.version3); + if (rsu.getRsuUsername() != null) { + target.setSecurityLevel(SecurityLevel.AUTH_PRIV); + target.setSecurityName(new OctetString(rsu.getRsuUsername())); + } else { + target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); + } + + // Set up the UDP transport mapping over which requests are sent + transport = null; + try { + transport = new DefaultUdpTransportMapping(); + } catch (IOException e) { + throw new IOException("Failed to create UDP transport mapping: {}", e); + } + + // Instantiate the SNMP instance + snmp = new Snmp(transport); + + // Register the security options and create an SNMP "user" + USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); + SecurityModels.getInstance().addSecurityModel(usm); + if (rsu.getRsuUsername() != null) { + snmp.getUSM().addUser(new OctetString(rsu.getRsuUsername()), new UsmUser(new OctetString(rsu.getRsuUsername()), + AuthSHA.ID, new OctetString(rsu.getRsuPassword()), PrivAES128.ID, new OctetString(rsu.getRsuPassword()))); + } + + // Assert the ready flag so the user can begin sending messages + ready = true; } - // Set up the UDP transport mapping over which requests are sent - transport = new DefaultUdpTransportMapping(); - - // Instantiate the SNMP instance - snmp = new Snmp(transport); - - // Register the security options and create an SNMP "user" - USM usm = - new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); - SecurityModels.getInstance().addSecurityModel(usm); - if (rsu.getRsuUsername() != null) { - snmp.getUSM().addUser(new OctetString(rsu.getRsuUsername()), - new UsmUser(new OctetString(rsu.getRsuUsername()), - AuthSHA.ID, new OctetString(rsu.getRsuPassword()), PrivAES128.ID, - new OctetString(rsu.getRsuPassword()))); + /** + * Sends a SET-type PDU to the target specified by the constructor. + * + * @param pdu The message content to be sent to the target + * @return ResponseEvent + * @throws IOException + */ + public ResponseEvent
set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { + + // Ensure the object has been instantiated + if (!ready) { + throw new IOException("Tried to send PDU before SNMP sending service is ready."); + } + + if (!listening) { + startListen(); + } + + // Try to send the SNMP request (synchronously) + ResponseEvent
responseEvent; + try { + // attempt to discover & set authoritative engine ID + byte[] authEngineID = snmpob.discoverAuthoritativeEngineID(targetob.getAddress(), 1000); + if (authEngineID != null && authEngineID.length > 0) { + targetob.setAuthoritativeEngineID(authEngineID); + } + + // send request + responseEvent = snmpob.set(pdu, targetob); + if (!keepOpen) { + snmpob.close(); + } + + // if RSU responded and we didn't get an authEngineID, log a warning + if (responseEvent != null && responseEvent.getResponse() != null && authEngineID == null) { + logger.warn("Failed to discover authoritative engine ID for SNMP target: {}", targetob.getAddress()); + } + } catch (IOException e) { + throw new IOException("Failed to send SNMP request: " + e); + } + + return responseEvent; } - // Assert the ready flag so the user can begin sending messages - ready = true; - } - - /** - * Sends a SET-type PDU to the target specified by the constructor. - * - * @param pdu The message content to be sent to the target - * @return ResponseEvent - * @throws IOException when calling this method before the session is ready - */ - public ResponseEvent
set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) - throws IOException { - - // Ensure the object has been instantiated - if (!ready) { - throw new IOException("Tried to send PDU before SNMP sending service is ready."); + /** + * Sends a SET-type PDU to the target specified by the constructor. + * + * @param pdu The message content to be sent to the target + * @return ResponseEvent + * @throws IOException + */ + public ResponseEvent get(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { + + // Ensure the object has been instantiated + if (!ready) { + throw new IOException("Tried to send PDU before SNMP sending service is ready."); + } + + // Start listening on UDP + if (!listening) { + startListen(); + } + + // Try to send the SNMP request (synchronously) + ResponseEvent responseEvent = null; + try { + responseEvent = snmpob.get(pdu, targetob); + if (!keepOpen) { + snmpob.close(); + } + } catch (IOException e) { + throw new IOException("Failed to send SNMP request: " + e); + } + + return responseEvent; } - if (!listening) { - startListen(); + /** + * Start listening for responses + * + * @throws IOException + */ + public void startListen() throws IOException { + transport.listen(); + listening = true; } - // Try to send the SNMP request (synchronously) - ResponseEvent
responseEvent; - try { - // attempt to discover & set authoritative engine ID - byte[] authEngineID = snmpob.discoverAuthoritativeEngineID(targetob.getAddress(), 1000); - if (authEngineID != null && authEngineID.length > 0) { - targetob.setAuthoritativeEngineID(authEngineID); - } - - // send request - responseEvent = snmpob.set(pdu, targetob); - if (!keepOpen) { - snmpob.close(); - } - - // if RSU responded and we didn't get an authEngineID, log a warning - if (responseEvent != null && responseEvent.getResponse() != null && authEngineID == null) { - logger.warn("Failed to discover authoritative engine ID for SNMP target: {}", - targetob.getAddress()); - } - } catch (IOException e) { - throw new IOException("Failed to send SNMP request: " + e); + + public static ResponseEvent
createAndSend(SNMP snmp, RSU rsu, String payload, + RequestVerb requestVerb, boolean dataSigningEnabledRSU) throws ParseException, IOException { + + SnmpSession session = new SnmpSession(rsu); + + // Send the PDU + ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, + rsu.getSnmpProtocol(), dataSigningEnabledRSU); + ResponseEvent
response = + session.set(pdu, session.getSnmp(), session.getTarget(), false); + logger.debug("Message Sent to {}, index {}: {}", rsu.getRsuTarget(), rsu.getRsuIndex(), + payload); + return response; } - return responseEvent; - } - - /** - * Sends a SET-type PDU to the target specified by the constructor. - * - * @param pdu The message content to be sent to the target - * @return ResponseEvent - * @throws IOException when calling this method before the session is ready - */ - public ResponseEvent get(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) - throws IOException { - - // Ensure the object has been instantiated - if (!ready) { - throw new IOException("Tried to send PDU before SNMP sending service is ready."); + public Snmp getSnmp() { + return snmp; } - // Start listening on UDP - if (!listening) { - startListen(); + public void setSnmp(Snmp snmp) { + this.snmp = snmp; } - // Try to send the SNMP request (synchronously) - ResponseEvent responseEvent = null; - try { - responseEvent = snmpob.get(pdu, targetob); - if (!keepOpen) { - snmpob.close(); - } - } catch (IOException e) { - throw new IOException("Failed to send SNMP request: " + e); + public TransportMapping getTransport() { + return transport; } - return responseEvent; - } - - /** - * Start listening for responses. - * - * @throws IOException when listening failed - */ - public void startListen() throws IOException { - transport.listen(); - listening = true; - } - - - /** - * Creates and sends a PDU to a specific RSU (Road Side Unit) using the provided SNMP session. The - * request is built with specified payload, request verb, and data signing option. - * - * @param snmp The SNMP object containing configuration and metadata for - * constructing the request. - * @param rsu The RSU object that provides target and protocol information for - * the request destination. - * @param payload The payload string to be included in the PDU of the request. - * @param requestVerb The type of SNMP request verb (e.g., GET, SET) for the operation. - * @param dataSigningEnabledRSU A boolean flag indicating if data signing is enabled for the RSU. - * @return A ResponseEvent representing the response received from the RSU. - * @throws ParseException If there is an error in parsing the payload or PDU creation. - * @throws IOException If there is an error in establishing or using the SNMP transport. - */ - public static ResponseEvent
createAndSend(SNMP snmp, RSU rsu, String payload, - RequestVerb requestVerb, boolean dataSigningEnabledRSU) throws ParseException, IOException { - - SnmpSession session = new SnmpSession(rsu); - - // Send the PDU - ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, - rsu.getSnmpProtocol(), dataSigningEnabledRSU); - ResponseEvent
response = - session.set(pdu, session.getSnmp(), session.getTarget(), false); - logger.debug("Message Sent to {}, index {}: {}", rsu.getRsuTarget(), rsu.getRsuIndex(), - payload); - return response; - } - - public Snmp getSnmp() { - return snmp; - } - - public void setSnmp(Snmp snmp) { - this.snmp = snmp; - } - - public TransportMapping getTransport() { - return transport; - } - - public void setTransport(TransportMapping transport) { - this.transport = transport; - } - - public UserTarget getTarget() { - return target; - } - - public void setTarget(UserTarget target) { - this.target = target; - } - - public void endSession() throws IOException { - this.snmp.close(); - } - - /** - * Creates a ScopedPDU object configured based on the specified SNMP protocol. - * - * @param snmp The SNMP object containing configuration and metadata for - * constructing the PDU. - * @param payload The payload string to be included in the PDU. - * @param index The index value associated with the PDU construction. - * @param verb The request verb (e.g., GET, SET) for the PDU. - * @param snmpProtocol The SNMP protocol version or type used for constructing the PDU. - * @param dataSigningEnabledRSU A boolean flag indicating whether data signing is enabled for the - * RSU. - * @return A ScopedPDU object constructed based on the provided parameters, or null if the - * protocol is unknown. - * @throws ParseException If there is an error in parsing the payload or during PDU creation. - */ - public static ScopedPDU createPDU(SNMP snmp, String payload, int index, RequestVerb verb, - SnmpProtocol snmpProtocol, boolean dataSigningEnabledRSU) throws ParseException { - switch (snmpProtocol) { - case FOURDOT1: - return createPDUWithFourDot1Protocol(snmp, payload, index, verb); - case NTCIP1218: - return createPDUWithNTCIP1218Protocol(snmp, payload, index, verb, dataSigningEnabledRSU); - default: - logger.error("Unknown SNMP protocol: {}", snmpProtocol); - return null; + public void setTransport(TransportMapping transport) { + this.transport = transport; } - } - - /** - * Encodes the given value into an SNMP variable binding using specific encoding rules - * based on the value's range. - * - * @param oid The Object Identifier (OID) as a string, representing the SNMP object ID. - * @param val The value to be encoded, provided as a hexadecimal string. - * @return A VariableBinding object that contains the OID and the encoded value, or null - * if the value does not fall within any of the predefined ranges. - */ - public static VariableBinding getPEncodedVariableBinding(String oid, String val) { - Integer intVal = Integer.parseInt(val, 16); - Integer additionValue = null; - - if (intVal >= 0 && intVal <= 127) { - // P = V - // here we must instantiate the OctetString directly with the hex string to - // avoid inadvertently creating the ASCII character codes - // for instance OctetString.fromString("20", 16) produces the space character (" - // ") rather than hex 20 - return new VariableBinding(new OID(oid), new OctetString(Integer.toHexString(intVal))); - } else if (intVal >= 128 && intVal <= 16511) { - // P = V + 0x7F80 - additionValue = 0x7F80; - } else if (intVal >= 016512 && intVal <= 2113663) { - // P = V + 0xBFBF80 - additionValue = 0xBFBF80; - } else if (intVal >= 2113664 && intVal <= 270549119) { - // P = V + 0xDFDFBF80 - additionValue = 0xDFDFBF80; + + public UserTarget getTarget() { + return target; } - if (additionValue != null) { - return new VariableBinding(new OID(oid), - OctetString.fromString(Integer.toHexString(intVal + additionValue), 16)); + public void setTarget(UserTarget target) { + this.target = target; } - return null; - } - - private static ScopedPDU createPDUWithFourDot1Protocol(SNMP snmp, String payload, int index, - RequestVerb verb) throws ParseException { - ////////////////////////////// - // - OID examples - // - ////////////////////////////// - // rsuSRMStatus.3 = 4 - // --> 1.4.1.11.3 = 4 - // rsuSRMTxChannel.3 = 3 - // --> 1.4.1.5.3 = 178 - // rsuSRMTxMode.3 = 1 - // --> 1.4.1.4.3 = 1 - // rsuSRMPsid.3 x "8003" - // --> 1.4.1.2.3 x "8003" - // rsuSRMDsrcMsgId.3 = 31 - // --> 1.4.1.3.3 = 31 - // rsuSRMTxInterval.3 = 10 - // --> 1.4.1.6.3 = 10 - // rsuSRMDeliveryStart.3 x "07e7051f0c000000" - // --> 1.4.1.7.3 = "07e7051f0c000000" - // rsuSRMDeliveryStop.3 x "07e7060f0c000000" - // --> 1.4.1.8.3 = "07e7060f0c000000" - // rsuSRMPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // --> 1.4.1.9.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // rsuSRMEnable.3 = 1 - // --> 1.4.1.10.3 = 1 - ////////////////////////////// - - VariableBinding rsuSRMPsid = SnmpFourDot1Protocol.getVbRsuSrmPsid(index, snmp.getRsuid()); - VariableBinding rsuSRMDsrcMsgId = - SnmpFourDot1Protocol.getVbRsuSrmDsrcMsgId(index, snmp.getMsgid()); - VariableBinding rsuSRMTxMode = SnmpFourDot1Protocol.getVbRsuSrmTxMode(index, snmp.getMode()); - VariableBinding rsuSRMTxChannel = - SnmpFourDot1Protocol.getVbRsuSrmTxChannel(index, snmp.getChannel()); - VariableBinding rsuSRMTxInterval = - SnmpFourDot1Protocol.getVbRsuSrmTxInterval(index, snmp.getInterval()); - VariableBinding rsuSRMDeliveryStart = - SnmpFourDot1Protocol.getVbRsuSrmDeliveryStart(index, snmp.getDeliverystart()); - VariableBinding rsuSRMDeliveryStop = - SnmpFourDot1Protocol.getVbRsuSrmDeliveryStop(index, snmp.getDeliverystop()); - VariableBinding rsuSRMPayload = SnmpFourDot1Protocol.getVbRsuSrmPayload(index, payload); - VariableBinding rsuSRMEnable = SnmpFourDot1Protocol.getVbRsuSrmEnable(index, snmp.getEnable()); - - ScopedPDU pdu = new ScopedPDU(); - pdu.add(rsuSRMPsid); - pdu.add(rsuSRMDsrcMsgId); - pdu.add(rsuSRMTxMode); - pdu.add(rsuSRMTxChannel); - pdu.add(rsuSRMTxInterval); - pdu.add(rsuSRMDeliveryStart); - pdu.add(rsuSRMDeliveryStop); - pdu.add(rsuSRMPayload); - pdu.add(rsuSRMEnable); - if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { - VariableBinding rsuSRMStatus = SnmpFourDot1Protocol.getVbRsuSrmStatus(index, snmp.getStatus()); - pdu.add(rsuSRMStatus); + + public void endSession() throws IOException { + this.snmp.close(); } - pdu.setType(PDU.SET); - - return pdu; - } - - private static ScopedPDU createPDUWithNTCIP1218Protocol(SNMP snmp, String payload, int index, - RequestVerb verb, boolean dataSigningEnabledRSU) throws ParseException { - ////////////////////////////// - // - OID examples - // - ////////////////////////////// - // rsuMsgRepeatPsid.3 x "8003" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.2.3 x "8003" - // rsuMsgRepeatTxChannel.3 = 3 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.3.3 = 183 - // rsuMsgRepeatTxInterval.3 = 10 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.4.3 = 10 - // rsuMsgRepeatDeliveryStart.3 x "07e7051f0c000000" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.5.3 = "07e7051f0c000000" - // rsuMsgRepeatDeliveryStop.3 x "07e7060f0c000000" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.6.3 = "07e7060f0c000000" - // rsuMsgRepeatPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.7.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // rsuMsgRepeatEnable.3 = 1 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.8.3 = 1 - // rsuMsgRepeatStatus.3 = 4 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.9.3 = 4 - // rsuMsgRepeatPriority.3 = 6 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.10.3 = 6 - // rsuMsgRepeatOptions.3 = "00" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.11.3 = "00" - ////////////////////////////// - - // note: dsrc_msg_id is not in NTCIP 1218 - // note: tx_mode is not in NTCIP 1218 - ScopedPDU pdu = new ScopedPDU(); - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPsid(index, snmp.getRsuid())); - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxChannel(index, snmp.getChannel())); - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxInterval(index, snmp.getInterval())); - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStart(index, snmp.getDeliverystart())); - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStop(index, snmp.getDeliverystop())); - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPayload(index, payload)); - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatEnable(index, snmp.getEnable())); - if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatStatus(index, snmp.getStatus())); + + /** + * Assembles the various RSU elements of a TimParameters object into a usable + * PDU. + * + * @param index Storage index on the RSU + * @param params TimParameters POJO that stores status, channel, payload, etc. + * @return PDU + * @throws ParseException + */ + public static ScopedPDU createPDU(SNMP snmp, String payload, int index, RequestVerb verb, SnmpProtocol snmpProtocol, boolean dataSigningEnabledRSU) throws ParseException { + switch (snmpProtocol) { + case FOURDOT1: + return createPDUWithFourDot1Protocol(snmp, payload, index, verb); + case NTCIP1218: + return createPDUWithNTCIP1218Protocol(snmp, payload, index, verb, dataSigningEnabledRSU); + default: + logger.error("Unknown SNMP protocol: {}", snmpProtocol); + return null; + } + } + + public static VariableBinding getPEncodedVariableBinding(String oid, String val) { + Integer intVal = Integer.parseInt(val, 16); + Integer additionValue = null; + + if (intVal >= 0 && intVal <= 127) { + // P = V + // here we must instantiate the OctetString directly with the hex string to + // avoid inadvertently creating the ASCII character codes + // for instance OctetString.fromString("20", 16) produces the space character (" + // ") rather than hex 20 + return new VariableBinding(new OID(oid), new OctetString(Integer.toHexString(intVal))); + } else if (intVal >= 128 && intVal <= 16511) { + // P = V + 0x7F80 + additionValue = 0x7F80; + } else if (intVal >= 016512 && intVal <= 2113663) { + // P = V + 0xBFBF80 + additionValue = 0xBFBF80; + } else if (intVal >= 2113664 && intVal <= 270549119) { + // P = V + 0xDFDFBF80 + additionValue = 0xDFDFBF80; + } + + if (additionValue != null) { + return new VariableBinding(new OID(oid), + OctetString.fromString(Integer.toHexString(intVal + additionValue), 16)); + } + return null; } - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPriority(index)); - if (dataSigningEnabledRSU) { - // set options to 0x00 to tell RSU to broadcast message without signing or attaching a 1609.2 header - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x00)); - } else { - // set options to 0x80 to tell RSU to sign & attach a 1609.2 header before broadcasting - pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x80)); + + private static ScopedPDU createPDUWithFourDot1Protocol(SNMP snmp, String payload, int index, RequestVerb verb) throws ParseException { + ////////////////////////////// + // - OID examples - // + ////////////////////////////// + // rsuSRMStatus.3 = 4 + // --> 1.4.1.11.3 = 4 + // rsuSRMTxChannel.3 = 3 + // --> 1.4.1.5.3 = 178 + // rsuSRMTxMode.3 = 1 + // --> 1.4.1.4.3 = 1 + // rsuSRMPsid.3 x "8003" + // --> 1.4.1.2.3 x "8003" + // rsuSRMDsrcMsgId.3 = 31 + // --> 1.4.1.3.3 = 31 + // rsuSRMTxInterval.3 = 10 + // --> 1.4.1.6.3 = 10 + // rsuSRMDeliveryStart.3 x "07e7051f0c000000" + // --> 1.4.1.7.3 = "07e7051f0c000000" + // rsuSRMDeliveryStop.3 x "07e7060f0c000000" + // --> 1.4.1.8.3 = "07e7060f0c000000" + // rsuSRMPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // --> 1.4.1.9.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // rsuSRMEnable.3 = 1 + // --> 1.4.1.10.3 = 1 + ////////////////////////////// + + VariableBinding rsuSRMPsid = SnmpFourDot1Protocol.getVbRsuSrmPsid(index, snmp.getRsuid()); + VariableBinding rsuSRMDsrcMsgId = SnmpFourDot1Protocol.getVbRsuSrmDsrcMsgId(index, snmp.getMsgid()); + VariableBinding rsuSRMTxMode = SnmpFourDot1Protocol.getVbRsuSrmTxMode(index, snmp.getMode()); + VariableBinding rsuSRMTxChannel = SnmpFourDot1Protocol.getVbRsuSrmTxChannel(index, snmp.getChannel()); + VariableBinding rsuSRMTxInterval = SnmpFourDot1Protocol.getVbRsuSrmTxInterval(index, snmp.getInterval()); + VariableBinding rsuSRMDeliveryStart = SnmpFourDot1Protocol.getVbRsuSrmDeliveryStart(index, snmp.getDeliverystart()); + VariableBinding rsuSRMDeliveryStop = SnmpFourDot1Protocol.getVbRsuSrmDeliveryStop(index, snmp.getDeliverystop()); + VariableBinding rsuSRMPayload = SnmpFourDot1Protocol.getVbRsuSrmPayload(index, payload); + VariableBinding rsuSRMEnable = SnmpFourDot1Protocol.getVbRsuSrmEnable(index, snmp.getEnable()); + VariableBinding rsuSRMStatus = SnmpFourDot1Protocol.getVbRsuSrmStatus(index, snmp.getStatus()); + + ScopedPDU pdu = new ScopedPDU(); + pdu.add(rsuSRMPsid); + pdu.add(rsuSRMDsrcMsgId); + pdu.add(rsuSRMTxMode); + pdu.add(rsuSRMTxChannel); + pdu.add(rsuSRMTxInterval); + pdu.add(rsuSRMDeliveryStart); + pdu.add(rsuSRMDeliveryStop); + pdu.add(rsuSRMPayload); + pdu.add(rsuSRMEnable); + if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { + pdu.add(rsuSRMStatus); + } + pdu.setType(PDU.SET); + + return pdu; } - pdu.setType(PDU.SET); - return pdu; - } + private static ScopedPDU createPDUWithNTCIP1218Protocol(SNMP snmp, String payload, int index, RequestVerb verb, boolean dataSigningEnabledRSU) throws ParseException { + ////////////////////////////// + // - OID examples - // + ////////////////////////////// + // rsuMsgRepeatPsid.3 x "8003" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.2.3 x "8003" + // rsuMsgRepeatTxChannel.3 = 3 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.3.3 = 183 + // rsuMsgRepeatTxInterval.3 = 10 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.4.3 = 10 + // rsuMsgRepeatDeliveryStart.3 x "07e7051f0c000000" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.5.3 = "07e7051f0c000000" + // rsuMsgRepeatDeliveryStop.3 x "07e7060f0c000000" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.6.3 = "07e7060f0c000000" + // rsuMsgRepeatPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.7.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // rsuMsgRepeatEnable.3 = 1 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.8.3 = 1 + // rsuMsgRepeatStatus.3 = 4 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.9.3 = 4 + // rsuMsgRepeatPriority.3 = 6 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.10.3 = 6 + // rsuMsgRepeatOptions.3 = "00" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.11.3 = "00" + ////////////////////////////// + + VariableBinding rsuMsgRepeatPsid = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPsid(index, snmp.getRsuid()); + // note: dsrc_msg_id is not in NTCIP 1218 + // note: tx_mode is not in NTCIP 1218 + VariableBinding rsuMsgRepeatTxChannel = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxChannel(index, snmp.getChannel()); + VariableBinding rsuMsgRepeatTxInterval = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxInterval(index, snmp.getInterval()); + VariableBinding rsuMsgRepeatDeliveryStart = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStart(index, snmp.getDeliverystart()); + VariableBinding rsuMsgRepeatDeliveryStop = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStop(index, snmp.getDeliverystop()); + VariableBinding rsuMsgRepeatPayload = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPayload(index, payload); + VariableBinding rsuMsgRepeatEnable = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatEnable(index, snmp.getEnable()); + VariableBinding rsuMsgRepeatStatus = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatStatus(index, snmp.getStatus()); + VariableBinding rsuMsgRepeatPriority = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPriority(index); + VariableBinding rsuMsgRepeatOptions; + if (dataSigningEnabledRSU) { + // set options to 0x00 to tell RSU to broadcast message without signing or attaching a 1609.2 header + rsuMsgRepeatOptions = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x00); + } else { + // set options to 0x80 to tell RSU to sign & attach a 1609.2 header before broadcasting + rsuMsgRepeatOptions = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x80); + } + + ScopedPDU pdu = new ScopedPDU(); + pdu.add(rsuMsgRepeatPsid); + pdu.add(rsuMsgRepeatTxChannel); + pdu.add(rsuMsgRepeatTxInterval); + pdu.add(rsuMsgRepeatDeliveryStart); + pdu.add(rsuMsgRepeatDeliveryStop); + pdu.add(rsuMsgRepeatPayload); + pdu.add(rsuMsgRepeatEnable); + if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { + pdu.add(rsuMsgRepeatStatus); + } + pdu.add(rsuMsgRepeatPriority); + pdu.add(rsuMsgRepeatOptions); + pdu.setType(PDU.SET); + + return pdu; + } } \ No newline at end of file From f05adffac779d176466283010b47dac4b9935586 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 20 Dec 2024 08:27:59 -0700 Subject: [PATCH 037/128] Revert "refactor: ResponseEvent to use generic Address type" This reverts commit c1f11784a6b53a4b30f8f2efe5308021a33e5397. --- .../us/dot/its/jpo/ode/rsu/RsuDepositor.java | 14 ++++---- .../us/dot/its/jpo/ode/snmp/SnmpSession.java | 35 ++++++++++++------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java index 572bbfcb8..265c704c5 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java @@ -16,19 +16,19 @@ package us.dot.its.jpo.ode.rsu; -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.HashMap; import lombok.extern.slf4j.Slf4j; import org.snmp4j.event.ResponseEvent; -import org.snmp4j.smi.Address; import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.plugin.RoadSideUnit.RSU; import us.dot.its.jpo.ode.plugin.ServiceRequest; import us.dot.its.jpo.ode.snmp.SnmpSession; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; + @Slf4j public class RsuDepositor extends Thread { private final boolean dataSigningEnabled; @@ -77,7 +77,7 @@ public void run() { TimTransmogrifier.updateRsuCreds(curRsu, rsuProperties); String httpResponseStatus; try { - ResponseEvent
rsuResponse = SnmpSession.createAndSend(entry.request.getSnmp(), + ResponseEvent rsuResponse = SnmpSession.createAndSend(entry.request.getSnmp(), curRsu, entry.encodedMsg, entry.request.getOde().getVerb(), @@ -101,7 +101,7 @@ public void run() { } } - private String getResponseStatus(ResponseEvent
rsuResponse, RSU curRsu) { + private String getResponseStatus(ResponseEvent rsuResponse, RSU curRsu) { String httpResponseStatus; if (null == rsuResponse || null == rsuResponse.getResponse()) { diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java index b904f9837..9fdeeede0 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java @@ -15,8 +15,6 @@ ******************************************************************************/ package us.dot.its.jpo.ode.snmp; -import java.io.IOException; -import java.text.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snmp4j.PDU; @@ -40,12 +38,16 @@ import org.snmp4j.smi.OctetString; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultUdpTransportMapping; +import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.plugin.RoadSideUnit.RSU; import us.dot.its.jpo.ode.plugin.SNMP; import us.dot.its.jpo.ode.plugin.ServiceRequest; import us.dot.its.jpo.ode.plugin.ServiceRequest.OdeInternal.RequestVerb; import us.dot.its.jpo.ode.plugin.SnmpProtocol; +import java.io.IOException; +import java.text.ParseException; + /** * This object is used to abstract away the complexities of SNMP calls and allow * a user to more quickly and easily send SNMP requests. Note that the @@ -117,7 +119,7 @@ public SnmpSession(RSU rsu) throws IOException { * @return ResponseEvent * @throws IOException */ - public ResponseEvent
set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { + public ResponseEvent set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { // Ensure the object has been instantiated if (!ready) { @@ -197,19 +199,28 @@ public void startListen() throws IOException { listening = true; } - - public static ResponseEvent
createAndSend(SNMP snmp, RSU rsu, String payload, - RequestVerb requestVerb, boolean dataSigningEnabledRSU) throws ParseException, IOException { + /** + * Create an SNMP session given the values in + * + * @param tim - The TIM parameters (payload, channel, mode, etc) + * @param props - The SNMP properties (ip, username, password, etc) + * @return ResponseEvent + * @throws TimPduCreatorException + * @throws IOException + * @throws ParseException + */ + public static ResponseEvent createAndSend(SNMP snmp, RSU rsu, String payload, RequestVerb requestVerb, boolean dataSigningEnabledRSU) + throws ParseException, IOException { SnmpSession session = new SnmpSession(rsu); // Send the PDU - ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, - rsu.getSnmpProtocol(), dataSigningEnabledRSU); - ResponseEvent
response = - session.set(pdu, session.getSnmp(), session.getTarget(), false); - logger.debug("Message Sent to {}, index {}: {}", rsu.getRsuTarget(), rsu.getRsuIndex(), - payload); + ResponseEvent response = null; + ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, rsu.getSnmpProtocol(), dataSigningEnabledRSU); + response = session.set(pdu, session.getSnmp(), session.getTarget(), false); + String msg = "Message Sent to {}, index {}: {}"; + EventLogger.logger.debug(msg, rsu.getRsuTarget(), rsu.getRsuIndex(), payload); + logger.debug(msg, rsu.getRsuTarget(), rsu.getRsuIndex(), payload); return response; } From 1bc84ab63e02df578e6ca05b798af2f1e186636b Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 20 Dec 2024 08:28:00 -0700 Subject: [PATCH 038/128] Revert "refactor: don't swallow exception on startup of rsuDepositor in Asn1CommandManager" This reverts commit b4473a37b040727f45a55e8368855b9cdef565c8. --- .../its/jpo/ode/services/asn1/Asn1CommandManager.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index 3b5401447..deb30a9d7 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -21,6 +21,7 @@ import java.text.ParseException; import lombok.extern.slf4j.Slf4j; import us.dot.its.jpo.ode.context.AppContext; +import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; import us.dot.its.jpo.ode.model.OdeAsdPayload; import us.dot.its.jpo.ode.model.OdeMsgMetadata; @@ -56,8 +57,14 @@ public class Asn1CommandManager { * instance is started during initialization. */ public Asn1CommandManager(RsuDepositor rsuDepositor) { - this.rsuDepositor = rsuDepositor; - this.rsuDepositor.start(); + try { + this.rsuDepositor = rsuDepositor; + this.rsuDepositor.start(); + } catch (Exception e) { + String msg = "Error starting SDW depositor"; + EventLogger.logger.error(msg, e); + log.error(msg, e); + } } /** From bd4b72e1bd157b8f45dfe1dc6d216788b633bec9 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 20 Dec 2024 08:28:00 -0700 Subject: [PATCH 039/128] Revert "style: added missing Javadocs and reorganized Asn1CommandManager" This reverts commit ab2a63358d5692109fbe87bc5d911b7b6fe2fa93. --- .../ode/services/asn1/Asn1CommandManager.java | 55 ++++++++----------- .../asn1/AsnCodecRouterServiceController.java | 2 + .../asn1/Asn1EncodedDataRouterTest.java | 6 ++ 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index deb30a9d7..cffc20bea 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -22,6 +22,8 @@ import lombok.extern.slf4j.Slf4j; import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.eventlog.EventLogger; +import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; +import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; import us.dot.its.jpo.ode.model.OdeAsdPayload; import us.dot.its.jpo.ode.model.OdeMsgMetadata; @@ -38,25 +40,31 @@ import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; -/** - * Manages operations related to ASN.1 command processing and interaction with RSUs (Road Side - * Units). Handles the preparation, packaging, and distribution of messages to RSUs using an - * {@link RsuDepositor}. - */ @Slf4j public class Asn1CommandManager { public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; + public static class Asn1CommandManagerException extends Exception { + + private static final long serialVersionUID = 1L; + + public Asn1CommandManagerException(String string) { + super(string); + } + + public Asn1CommandManagerException(String msg, Exception e) { + super(msg, e); + } + + } + private RsuDepositor rsuDepositor; - /** - * Constructs an instance of Asn1CommandManager and initializes the RSU depositor. - * - * @param rsuDepositor The RSU depositor instance to be used for managing ASN.1 commands. This - * instance is started during initialization. - */ - public Asn1CommandManager(RsuDepositor rsuDepositor) { + public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, + SDXDepositorTopics sdxDepositorTopics, + RsuDepositor rsuDepositor) { + try { this.rsuDepositor = rsuDepositor; this.rsuDepositor.start(); @@ -67,27 +75,10 @@ public Asn1CommandManager(RsuDepositor rsuDepositor) { } } - /** - * Sends a message to the RSUs (Road Side Units) through the RSU depositor. - * - * @param request the service request containing relevant details for the operation - * @param encodedMsg the message to be sent, encoded in the desired format - */ public void sendToRsus(ServiceRequest request, String encodedMsg) { rsuDepositor.deposit(request, encodedMsg); } - /** - * Constructs an XML representation of an Advisory Situation Data (ASD) message containing a - * signed Traveler Information Message (TIM). Processes the provided service request and signed - * message to create and structure the ASD before converting it to an XML string output. - * - * @param request the service request object containing meta information, service region, - * delivery time, and other necessary data for ASD creation. - * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. - * @return a String containing the fully crafted ASD message in XML format. Returns null if the - * message could not be constructed due to exceptions. - */ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { SDW sdw = request.getSdw(); @@ -115,6 +106,8 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); } + OdeMsgPayload payload; + ObjectNode dataBodyObj = JsonUtils.newNode(); ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); @@ -123,7 +116,7 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); - OdeMsgPayload payload = new OdeAsdPayload(asd); + payload = new OdeAsdPayload(asd); ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); payloadObj.set(AppContext.DATA_STRING, dataBodyObj); @@ -164,7 +157,7 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) return outputXml; } - private ArrayNode buildEncodings() throws JsonUtilsException { + public static ArrayNode buildEncodings() throws JsonUtilsException { ArrayNode encodings = JsonUtils.newArrayNode(); encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, ADVISORY_SITUATION_DATA_STRING, diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java index c6e0ed63a..fa91f8cad 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java @@ -58,6 +58,8 @@ public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, securityServicesProperties, new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()), new Asn1CommandManager( + odeKafkaProperties, + sdxDepositorTopics, new RsuDepositor( rsuProperties, securityServicesProperties.getIsRsuSigningEnabled() diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 0534a0578..127eea7da 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -106,6 +106,8 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); var asn1CommandManager = new Asn1CommandManager( + odeKafkaProperties, + sdxDepositorTopics, mockRsuDepositor ); var mockSecServClient = mock(ISecurityServicesClient.class); @@ -195,6 +197,8 @@ void processSNMPDepositOnly() securityServicesProperties.setIsSdwSigningEnabled(true); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); var asn1CommandManager = new Asn1CommandManager( + odeKafkaProperties, + sdxDepositorTopics, mockRsuDepositor ); var mockSecServClient = new ISecurityServicesClient() { @@ -305,6 +309,8 @@ void processEncodedTimUnsecured() var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); var asn1CommandManager = new Asn1CommandManager( + odeKafkaProperties, + sdxDepositorTopics, mockRsuDepositor ); var mockSecServClient = new ISecurityServicesClient() { From d898850037b75cd52f1a076f1aef8c8df8afa4d7 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 20 Dec 2024 08:28:00 -0700 Subject: [PATCH 040/128] Revert "refactor: move asn1CommandManager.depositToSdw to Asn1EncodedDataRouter" This reverts commit 7e69562731a44eff57bc7bd3a4f77881485bcc31. --- .../ode/services/asn1/Asn1CommandManager.java | 19 +++++++++++++++++++ .../services/asn1/Asn1EncodedDataRouter.java | 16 +++++++--------- .../asn1/AsnCodecRouterServiceController.java | 3 +-- .../services/asn1/Asn1CommandManagerTest.java | 18 ++++++++++++++++++ .../asn1/Asn1EncodedDataRouterTest.java | 9 +++------ 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index cffc20bea..af92ac0c2 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -39,6 +39,7 @@ import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; +import us.dot.its.jpo.ode.wrapper.MessageProducer; @Slf4j public class Asn1CommandManager { @@ -59,6 +60,11 @@ public Asn1CommandManagerException(String msg, Exception e) { } + + + private MessageProducer stringMessageProducer; + + private String depositTopic; private RsuDepositor rsuDepositor; public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, @@ -68,6 +74,11 @@ public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, try { this.rsuDepositor = rsuDepositor; this.rsuDepositor.start(); + this.stringMessageProducer = + MessageProducer.defaultStringMessageProducer(odeKafkaProperties.getBrokers(), + odeKafkaProperties.getKafkaType(), + odeKafkaProperties.getDisabledTopics()); + this.depositTopic = sdxDepositorTopics.getInput(); } catch (Exception e) { String msg = "Error starting SDW depositor"; EventLogger.logger.error(msg, e); @@ -75,6 +86,14 @@ public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, } } + public void depositToSdw(String depositObj) throws Asn1CommandManagerException { + stringMessageProducer.send(this.depositTopic, null, depositObj); + log.info("Published message to SDW deposit topic {}", this.depositTopic); + EventLogger.logger.info("Published message to SDW deposit topic"); + log.debug("Message deposited: {}", depositObj); + EventLogger.logger.debug("Message deposited: {}", depositObj); + } + public void sendToRsus(ServiceRequest request, String encodedMsg) { rsuDepositor.deposit(request, encodedMsg); } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index f923ab44e..7a8fc6c43 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -33,6 +33,7 @@ import us.dot.its.jpo.ode.plugin.ServiceRequest; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; +import us.dot.its.jpo.ode.services.asn1.Asn1CommandManager.Asn1CommandManagerException; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; import us.dot.its.jpo.ode.util.CodecUtils; import us.dot.its.jpo.ode.util.JsonUtils; @@ -66,7 +67,6 @@ public Asn1EncodedDataRouterException(String string) { private final Asn1CoderTopics asn1CoderTopics; private final JsonTopics jsonTopics; - private final String sdxDepositTopic; private final ISecurityServicesClient securityServicesClient; private final MessageProducer stringMsgProducer; @@ -84,7 +84,6 @@ public Asn1EncodedDataRouterException(String string) { * @param jsonTopics The specified JSON topics to write to * @param securityServicesProperties The security services properties to use * @param securityServicesClient - * @param sdxDepositTopic **/ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, Asn1CoderTopics asn1CoderTopics, @@ -92,13 +91,11 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, SecurityServicesProperties securityServicesProperties, OdeTimJsonTopology odeTimJsonTopology, Asn1CommandManager asn1CommandManager, - ISecurityServicesClient securityServicesClient, - String sdxDepositTopic) { + ISecurityServicesClient securityServicesClient) { super(); this.asn1CoderTopics = asn1CoderTopics; this.jsonTopics = jsonTopics; - this.sdxDepositTopic = sdxDepositTopic; this.securityServicesClient = securityServicesClient; this.stringMsgProducer = MessageProducer.defaultStringMessageProducer( @@ -251,9 +248,10 @@ private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); deposit.put("encodedMsg", asdObj.getString(BYTES)); - stringMsgProducer.send(this.sdxDepositTopic, null, deposit.toString()); - } catch (JSONException e) { - log.error(ERROR_ON_SDX_DEPOSIT, e); + asn1CommandManager.depositToSdw(deposit.toString()); + } catch (JSONException | Asn1CommandManagerException e) { + String msg = ERROR_ON_SDX_DEPOSIT; + log.error(msg, e); } } @@ -348,7 +346,7 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); deposit.put("encodedMsg", asdBytes); - stringMsgProducer.send(this.sdxDepositTopic, null, deposit.toString()); + asn1CommandManager.depositToSdw(deposit.toString()); log.info("SDX deposit successful."); } catch (Exception e) { String msg = ERROR_ON_SDX_DEPOSIT; diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java index fa91f8cad..67aa2e6f9 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java @@ -65,8 +65,7 @@ public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, securityServicesProperties.getIsRsuSigningEnabled() ) ), - new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()), - sdxDepositorTopics.getInput() + new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()) ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java index 9207afd96..25693a51a 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java @@ -20,6 +20,7 @@ import mockit.Injectable; import mockit.Mocked; import mockit.Tested; +import org.json.JSONObject; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; @@ -27,6 +28,7 @@ import us.dot.its.jpo.ode.model.OdeTravelerInputData; import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.SecurityServicesProperties; +import us.dot.its.jpo.ode.services.asn1.Asn1CommandManager.Asn1CommandManagerException; import us.dot.its.jpo.ode.snmp.SnmpSession; import us.dot.its.jpo.ode.wrapper.MessageProducer; @@ -65,6 +67,22 @@ void testPackageSignedTimIntoAsd() { "message"); } + @Test + @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") + void depositToSDWJsonShouldCallMessageProducer() throws Asn1CommandManagerException { + JSONObject deposit = new JSONObject(); + deposit.put("estimatedRemovalDate", "2023-11-04T17:47:11-05:00"); + deposit.put("encodedMsg", "message"); + + testAsn1CommandManager.depositToSdw(deposit.toString()); + } + + @Test + @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") + void depositToSDWShouldCallMessageProducer() throws Asn1CommandManagerException { + testAsn1CommandManager.depositToSdw("message"); + } + @Test @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") void testSendToRsus(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 127eea7da..e0e2f41b8 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -119,8 +119,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() securityServicesProperties, odeTimJsonTopology, asn1CommandManager, - mockSecServClient, - sdxDepositorTopics.getInput() + mockSecServClient ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( @@ -220,8 +219,7 @@ public String signMessage(String message, int sigValidityOverride) { securityServicesProperties, odeTimJsonTopology, asn1CommandManager, - mockSecServClient, - sdxDepositorTopics.getInput() + mockSecServClient ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), "processSNMPDepositOnly-default", encoderRouter); @@ -334,8 +332,7 @@ public String signMessage(String message, int sigValidityOverride) { securityServicesProperties, odeTimJsonTopology, asn1CommandManager, - mockSecServClient, - sdxDepositorTopics.getInput() + mockSecServClient ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); From e995cd10e07d902209db5596960422ce4cdcf4b8 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 09:16:59 -0700 Subject: [PATCH 041/128] test: adjust tests to expect different streamId when message is signed --- .../ode/services/asn1/Asn1CommandManager.java | 4 + .../services/asn1/Asn1EncodedDataRouter.java | 2 +- .../asn1/Asn1EncodedDataRouterTest.java | 74 +++++++++++-------- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index af92ac0c2..7ea971c19 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -140,6 +140,10 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); payloadObj.set(AppContext.DATA_STRING, dataBodyObj); + // This call will generate a new metadata object with a new streamId. This seems to be intentional + // as it will allow depositing this message onto the encoderInput topic without conflicts. + // We send messages for encoding twice, so we are trying to avoid conflicts by using a unique + // streamId OdeMsgMetadata metadata = new OdeMsgMetadata(payload); ObjectNode metaObject = JsonUtils.toObjectNode(metadata.toJson()); diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 7a8fc6c43..15ae54bc7 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -308,7 +308,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO // Case 2 only log.debug("Publishing message for round 2 encoding!"); - String xmlizedMessage = asn1CommandManager.packageSignedTimIntoAsd(request, hexEncodedTim); + String xmlizedMessage = asn1CommandManager.packageSignedTimIntoAsd(request, hexEncodedTim, metadataObj); stringMsgProducer.send(asn1CoderTopics.getEncoderInput(), null, xmlizedMessage); } diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index e0e2f41b8..ae044b921 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -17,6 +17,7 @@ package us.dot.its.jpo.ode.services.asn1; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import java.io.IOException; @@ -93,7 +94,7 @@ class Asn1EncodedDataRouterTest { EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Test - void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() + void processSignedMessage_depositsToSdxTopic() throws IOException, InterruptedException { String[] topicsForConsumption = { @@ -164,27 +165,17 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() var records = KafkaTestUtils.getRecords(testConsumer); var sdxDepositorRecord = records .records(sdxDepositorTopics.getInput()); + var foundValidRecord = false; for (var consumerRecord : sdxDepositorRecord) { - if (consumerRecord.value().contains(streamId)) { - assertEquals(expected, consumerRecord.value()); - } - } - - inputStream = classLoader.getResourceAsStream( - "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); - assert inputStream != null; - var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - - for (var consumerRecord : records.records(jsonTopics.getTimTmcFiltered())) { - if (consumerRecord.value().contains(streamId)) { - assertEquals(expectedTimTmcFiltered, consumerRecord.value()); + if (consumerRecord.value().equals(expected)) { + foundValidRecord = true; } } + assertTrue(foundValidRecord); } @Test - void processSNMPDepositOnly() - throws IOException, InterruptedException { + void processSNMPDepositOnly() throws IOException, InterruptedException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimCertExpiration(), @@ -194,6 +185,7 @@ void processSNMPDepositOnly() EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); securityServicesProperties.setIsSdwSigningEnabled(true); + securityServicesProperties.setIsRsuSigningEnabled(true); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); var asn1CommandManager = new Asn1CommandManager( odeKafkaProperties, @@ -251,28 +243,41 @@ public String signMessage(String message, int sigValidityOverride) { "processSNMPDepositOnly", "false", embeddedKafka); var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); - var testConsumer = consumerFactory.createConsumer(); - embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topicsForConsumption); + var timCertConsumer = + consumerFactory.createConsumer("timCertExpiration", "processSNMPDepositOnly"); + embeddedKafka.consumeFromAnEmbeddedTopic(timCertConsumer, jsonTopics.getTimCertExpiration()); + var timTmcFilteredConsumer = + consumerFactory.createConsumer("timTmcFiltered", "processSNMPDepositOnly"); + embeddedKafka.consumeFromAnEmbeddedTopic(timTmcFilteredConsumer, + jsonTopics.getTimTmcFiltered()); + var encoderInputConsumer = + consumerFactory.createConsumer("encoderInput", "processSNMPDepositOnly"); + embeddedKafka.consumeFromAnEmbeddedTopic(encoderInputConsumer, + asn1CoderTopics.getEncoderInput()); inputStream = classLoader.getResourceAsStream( "us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json"); assert inputStream != null; var expectedTimCertExpiry = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); var timCertExpirationRecord = - KafkaTestUtils.getSingleRecord(testConsumer, jsonTopics.getTimCertExpiration()); + KafkaTestUtils.getSingleRecord(timCertConsumer, jsonTopics.getTimCertExpiration()); assertEquals(expectedTimCertExpiry, timCertExpirationRecord.value()); inputStream = classLoader.getResourceAsStream( "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); assert inputStream != null; var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - var records = KafkaTestUtils.getRecords(testConsumer); - expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + var records = KafkaTestUtils.getRecords(timTmcFilteredConsumer); + expectedTimTmcFiltered = + expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + var foundValidRecord = false; for (var consumerRecord : records.records(jsonTopics.getTimTmcFiltered())) { if (consumerRecord.value().contains(streamId)) { assertEquals(expectedTimTmcFiltered, consumerRecord.value()); + foundValidRecord = true; } } + assertTrue(foundValidRecord); inputStream = classLoader.getResourceAsStream( "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml"); @@ -283,16 +288,20 @@ public String signMessage(String message, int sigValidityOverride) { .replaceAll(".*?", "") .replaceAll(".*?", "") .replaceAll(".*?", ""); - for (var consumerRecord : records.records(asn1CoderTopics.getEncoderInput())) { - if (consumerRecord.value().contains(streamId)) { - var encoderInputWithStableFieldsOnly = consumerRecord.value() - .replaceAll(".*?", "") - .replaceAll(".*?", "") - .replaceAll(".*?", "") - .replaceAll(".*?", ""); - assertEquals(expectedEncoderInputWithStableFieldsOnly, encoderInputWithStableFieldsOnly); + var foundValidRecordInEncoderInput = false; + var records1 = KafkaTestUtils.getRecords(encoderInputConsumer); + for (var consumerRecord : records1.records(asn1CoderTopics.getEncoderInput())) { + var encoderInputWithStableFieldsOnly = consumerRecord.value() + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", ""); + if (expectedEncoderInputWithStableFieldsOnly.equals(encoderInputWithStableFieldsOnly)) { + foundValidRecordInEncoderInput = true; + break; } } + assertTrue(foundValidRecordInEncoderInput); } @Test @@ -385,13 +394,16 @@ public String signMessage(String message, int sigValidityOverride) { "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); assert inputStream != null; var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + expectedTimTmcFiltered = + expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); + var foundValidRecord = false; for (var consumerRecord : records.records(jsonTopics.getTimTmcFiltered())) { if (consumerRecord.value().contains(streamId)) { assertEquals(expectedTimTmcFiltered, consumerRecord.value()); + foundValidRecord = true; } } + assertTrue(foundValidRecord); } - } From 9a5ade1b635ab1cf04b253af5d71356c8987994d Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 13:52:39 -0700 Subject: [PATCH 042/128] refactor: move asn1CommandManager.depositToSdw to Asn1EncodedDataRouter The responsibility to submit data to topics is already contained within Asn1EncodedDataRouter, and Asn1CommandManager is not responsible for any kafka interactions otherwise, so the responsibility was moved to Asn1EncodedDataRouter --- .../ode/services/asn1/Asn1CommandManager.java | 19 ------------------- .../services/asn1/Asn1EncodedDataRouter.java | 16 +++++++++------- .../asn1/AsnCodecRouterServiceController.java | 3 ++- .../services/asn1/Asn1CommandManagerTest.java | 18 ------------------ .../asn1/Asn1EncodedDataRouterTest.java | 9 ++++++--- 5 files changed, 17 insertions(+), 48 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index 7ea971c19..81b02e082 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -39,7 +39,6 @@ import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; -import us.dot.its.jpo.ode.wrapper.MessageProducer; @Slf4j public class Asn1CommandManager { @@ -60,11 +59,6 @@ public Asn1CommandManagerException(String msg, Exception e) { } - - - private MessageProducer stringMessageProducer; - - private String depositTopic; private RsuDepositor rsuDepositor; public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, @@ -74,11 +68,6 @@ public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, try { this.rsuDepositor = rsuDepositor; this.rsuDepositor.start(); - this.stringMessageProducer = - MessageProducer.defaultStringMessageProducer(odeKafkaProperties.getBrokers(), - odeKafkaProperties.getKafkaType(), - odeKafkaProperties.getDisabledTopics()); - this.depositTopic = sdxDepositorTopics.getInput(); } catch (Exception e) { String msg = "Error starting SDW depositor"; EventLogger.logger.error(msg, e); @@ -86,14 +75,6 @@ public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, } } - public void depositToSdw(String depositObj) throws Asn1CommandManagerException { - stringMessageProducer.send(this.depositTopic, null, depositObj); - log.info("Published message to SDW deposit topic {}", this.depositTopic); - EventLogger.logger.info("Published message to SDW deposit topic"); - log.debug("Message deposited: {}", depositObj); - EventLogger.logger.debug("Message deposited: {}", depositObj); - } - public void sendToRsus(ServiceRequest request, String encodedMsg) { rsuDepositor.deposit(request, encodedMsg); } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 15ae54bc7..7180bc5fb 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -33,7 +33,6 @@ import us.dot.its.jpo.ode.plugin.ServiceRequest; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.services.asn1.Asn1CommandManager.Asn1CommandManagerException; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; import us.dot.its.jpo.ode.util.CodecUtils; import us.dot.its.jpo.ode.util.JsonUtils; @@ -67,6 +66,7 @@ public Asn1EncodedDataRouterException(String string) { private final Asn1CoderTopics asn1CoderTopics; private final JsonTopics jsonTopics; + private final String sdxDepositTopic; private final ISecurityServicesClient securityServicesClient; private final MessageProducer stringMsgProducer; @@ -84,6 +84,7 @@ public Asn1EncodedDataRouterException(String string) { * @param jsonTopics The specified JSON topics to write to * @param securityServicesProperties The security services properties to use * @param securityServicesClient + * @param sdxDepositTopic **/ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, Asn1CoderTopics asn1CoderTopics, @@ -91,11 +92,13 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, SecurityServicesProperties securityServicesProperties, OdeTimJsonTopology odeTimJsonTopology, Asn1CommandManager asn1CommandManager, - ISecurityServicesClient securityServicesClient) { + ISecurityServicesClient securityServicesClient, + String sdxDepositTopic) { super(); this.asn1CoderTopics = asn1CoderTopics; this.jsonTopics = jsonTopics; + this.sdxDepositTopic = sdxDepositTopic; this.securityServicesClient = securityServicesClient; this.stringMsgProducer = MessageProducer.defaultStringMessageProducer( @@ -248,10 +251,9 @@ private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); deposit.put("encodedMsg", asdObj.getString(BYTES)); - asn1CommandManager.depositToSdw(deposit.toString()); - } catch (JSONException | Asn1CommandManagerException e) { - String msg = ERROR_ON_SDX_DEPOSIT; - log.error(msg, e); + stringMsgProducer.send(this.sdxDepositTopic, null, deposit.toString()); + } catch (JSONException e) { + log.error(ERROR_ON_SDX_DEPOSIT, e); } } @@ -346,7 +348,7 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); deposit.put("encodedMsg", asdBytes); - asn1CommandManager.depositToSdw(deposit.toString()); + stringMsgProducer.send(this.sdxDepositTopic, null, deposit.toString()); log.info("SDX deposit successful."); } catch (Exception e) { String msg = ERROR_ON_SDX_DEPOSIT; diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java index 67aa2e6f9..fa91f8cad 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java @@ -65,7 +65,8 @@ public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, securityServicesProperties.getIsRsuSigningEnabled() ) ), - new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()) + new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()), + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java index 25693a51a..9207afd96 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java @@ -20,7 +20,6 @@ import mockit.Injectable; import mockit.Mocked; import mockit.Tested; -import org.json.JSONObject; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; @@ -28,7 +27,6 @@ import us.dot.its.jpo.ode.model.OdeTravelerInputData; import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.services.asn1.Asn1CommandManager.Asn1CommandManagerException; import us.dot.its.jpo.ode.snmp.SnmpSession; import us.dot.its.jpo.ode.wrapper.MessageProducer; @@ -67,22 +65,6 @@ void testPackageSignedTimIntoAsd() { "message"); } - @Test - @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") - void depositToSDWJsonShouldCallMessageProducer() throws Asn1CommandManagerException { - JSONObject deposit = new JSONObject(); - deposit.put("estimatedRemovalDate", "2023-11-04T17:47:11-05:00"); - deposit.put("encodedMsg", "message"); - - testAsn1CommandManager.depositToSdw(deposit.toString()); - } - - @Test - @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") - void depositToSDWShouldCallMessageProducer() throws Asn1CommandManagerException { - testAsn1CommandManager.depositToSdw("message"); - } - @Test @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") void testSendToRsus(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index ae044b921..29a571b86 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -120,7 +120,8 @@ void processSignedMessage_depositsToSdxTopic() securityServicesProperties, odeTimJsonTopology, asn1CommandManager, - mockSecServClient + mockSecServClient, + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( @@ -211,7 +212,8 @@ public String signMessage(String message, int sigValidityOverride) { securityServicesProperties, odeTimJsonTopology, asn1CommandManager, - mockSecServClient + mockSecServClient, + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), "processSNMPDepositOnly-default", encoderRouter); @@ -341,7 +343,8 @@ public String signMessage(String message, int sigValidityOverride) { securityServicesProperties, odeTimJsonTopology, asn1CommandManager, - mockSecServClient + mockSecServClient, + sdxDepositorTopics.getInput() ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); From 5acc7d872a6230b02b302793e48a08eee9143653 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 13:57:55 -0700 Subject: [PATCH 043/128] style: added missing Javadocs and reorganized Asn1CommandManager --- .../ode/services/asn1/Asn1CommandManager.java | 55 +++++++++++-------- .../asn1/AsnCodecRouterServiceController.java | 2 - .../asn1/Asn1EncodedDataRouterTest.java | 6 -- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index 81b02e082..b013abe9c 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -22,8 +22,6 @@ import lombok.extern.slf4j.Slf4j; import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.eventlog.EventLogger; -import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; -import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; import us.dot.its.jpo.ode.model.OdeAsdPayload; import us.dot.its.jpo.ode.model.OdeMsgMetadata; @@ -40,31 +38,25 @@ import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; +/** + * Manages operations related to ASN.1 command processing and interaction with RSUs (Road Side + * Units). Handles the preparation, packaging, and distribution of messages to RSUs using an + * {@link RsuDepositor}. + */ @Slf4j public class Asn1CommandManager { public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; - public static class Asn1CommandManagerException extends Exception { - - private static final long serialVersionUID = 1L; - - public Asn1CommandManagerException(String string) { - super(string); - } - - public Asn1CommandManagerException(String msg, Exception e) { - super(msg, e); - } - - } - private RsuDepositor rsuDepositor; - public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, - SDXDepositorTopics sdxDepositorTopics, - RsuDepositor rsuDepositor) { - + /** + * Constructs an instance of Asn1CommandManager and initializes the RSU depositor. + * + * @param rsuDepositor The RSU depositor instance to be used for managing ASN.1 commands. This + * instance is started during initialization. + */ + public Asn1CommandManager(RsuDepositor rsuDepositor) { try { this.rsuDepositor = rsuDepositor; this.rsuDepositor.start(); @@ -75,10 +67,27 @@ public Asn1CommandManager(OdeKafkaProperties odeKafkaProperties, } } + /** + * Sends a message to the RSUs (Road Side Units) through the RSU depositor. + * + * @param request the service request containing relevant details for the operation + * @param encodedMsg the message to be sent, encoded in the desired format + */ public void sendToRsus(ServiceRequest request, String encodedMsg) { rsuDepositor.deposit(request, encodedMsg); } + /** + * Constructs an XML representation of an Advisory Situation Data (ASD) message containing a + * signed Traveler Information Message (TIM). Processes the provided service request and signed + * message to create and structure the ASD before converting it to an XML string output. + * + * @param request the service request object containing meta information, service region, + * delivery time, and other necessary data for ASD creation. + * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. + * @return a String containing the fully crafted ASD message in XML format. Returns null if the + * message could not be constructed due to exceptions. + */ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { SDW sdw = request.getSdw(); @@ -106,8 +115,6 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); } - OdeMsgPayload payload; - ObjectNode dataBodyObj = JsonUtils.newNode(); ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); @@ -116,7 +123,7 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); - payload = new OdeAsdPayload(asd); + OdeMsgPayload payload = new OdeAsdPayload(asd); ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); payloadObj.set(AppContext.DATA_STRING, dataBodyObj); @@ -161,7 +168,7 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) return outputXml; } - public static ArrayNode buildEncodings() throws JsonUtilsException { + private ArrayNode buildEncodings() throws JsonUtilsException { ArrayNode encodings = JsonUtils.newArrayNode(); encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, ADVISORY_SITUATION_DATA_STRING, diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java index fa91f8cad..c6e0ed63a 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java @@ -58,8 +58,6 @@ public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, securityServicesProperties, new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()), new Asn1CommandManager( - odeKafkaProperties, - sdxDepositorTopics, new RsuDepositor( rsuProperties, securityServicesProperties.getIsRsuSigningEnabled() diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 29a571b86..1628d2bef 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -107,8 +107,6 @@ void processSignedMessage_depositsToSdxTopic() var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); var asn1CommandManager = new Asn1CommandManager( - odeKafkaProperties, - sdxDepositorTopics, mockRsuDepositor ); var mockSecServClient = mock(ISecurityServicesClient.class); @@ -189,8 +187,6 @@ void processSNMPDepositOnly() throws IOException, InterruptedException { securityServicesProperties.setIsRsuSigningEnabled(true); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); var asn1CommandManager = new Asn1CommandManager( - odeKafkaProperties, - sdxDepositorTopics, mockRsuDepositor ); var mockSecServClient = new ISecurityServicesClient() { @@ -318,8 +314,6 @@ void processEncodedTimUnsecured() var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); var asn1CommandManager = new Asn1CommandManager( - odeKafkaProperties, - sdxDepositorTopics, mockRsuDepositor ); var mockSecServClient = new ISecurityServicesClient() { From ca18d0aef1967621475f020635290e50aa20b32e Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 14:02:29 -0700 Subject: [PATCH 044/128] refactor: don't swallow exception on startup of rsuDepositor in Asn1CommandManager --- .../its/jpo/ode/services/asn1/Asn1CommandManager.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java index b013abe9c..fb2d2ded9 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java @@ -21,7 +21,6 @@ import java.text.ParseException; import lombok.extern.slf4j.Slf4j; import us.dot.its.jpo.ode.context.AppContext; -import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; import us.dot.its.jpo.ode.model.OdeAsdPayload; import us.dot.its.jpo.ode.model.OdeMsgMetadata; @@ -57,14 +56,8 @@ public class Asn1CommandManager { * instance is started during initialization. */ public Asn1CommandManager(RsuDepositor rsuDepositor) { - try { - this.rsuDepositor = rsuDepositor; - this.rsuDepositor.start(); - } catch (Exception e) { - String msg = "Error starting SDW depositor"; - EventLogger.logger.error(msg, e); - log.error(msg, e); - } + this.rsuDepositor = rsuDepositor; + this.rsuDepositor.start(); } /** From fb9d43a01f2f7d9de7cc754072401e7635ff8263 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 14:23:04 -0700 Subject: [PATCH 045/128] refactor: ResponseEvent to use generic Address type Updated method signatures and instance usage to specify ResponseEvent with Address as the generic type. This change improves type safety and aligns with best practices for clearer code readability and maintainability. --- .../us/dot/its/jpo/ode/rsu/RsuDepositor.java | 14 ++++---- .../us/dot/its/jpo/ode/snmp/SnmpSession.java | 35 +++++++------------ 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java index 265c704c5..572bbfcb8 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java @@ -16,19 +16,19 @@ package us.dot.its.jpo.ode.rsu; +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; import lombok.extern.slf4j.Slf4j; import org.snmp4j.event.ResponseEvent; +import org.snmp4j.smi.Address; import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.plugin.RoadSideUnit.RSU; import us.dot.its.jpo.ode.plugin.ServiceRequest; import us.dot.its.jpo.ode.snmp.SnmpSession; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.HashMap; - @Slf4j public class RsuDepositor extends Thread { private final boolean dataSigningEnabled; @@ -77,7 +77,7 @@ public void run() { TimTransmogrifier.updateRsuCreds(curRsu, rsuProperties); String httpResponseStatus; try { - ResponseEvent rsuResponse = SnmpSession.createAndSend(entry.request.getSnmp(), + ResponseEvent
rsuResponse = SnmpSession.createAndSend(entry.request.getSnmp(), curRsu, entry.encodedMsg, entry.request.getOde().getVerb(), @@ -101,7 +101,7 @@ public void run() { } } - private String getResponseStatus(ResponseEvent rsuResponse, RSU curRsu) { + private String getResponseStatus(ResponseEvent
rsuResponse, RSU curRsu) { String httpResponseStatus; if (null == rsuResponse || null == rsuResponse.getResponse()) { diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java index 9fdeeede0..b904f9837 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java @@ -15,6 +15,8 @@ ******************************************************************************/ package us.dot.its.jpo.ode.snmp; +import java.io.IOException; +import java.text.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snmp4j.PDU; @@ -38,16 +40,12 @@ import org.snmp4j.smi.OctetString; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultUdpTransportMapping; -import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.plugin.RoadSideUnit.RSU; import us.dot.its.jpo.ode.plugin.SNMP; import us.dot.its.jpo.ode.plugin.ServiceRequest; import us.dot.its.jpo.ode.plugin.ServiceRequest.OdeInternal.RequestVerb; import us.dot.its.jpo.ode.plugin.SnmpProtocol; -import java.io.IOException; -import java.text.ParseException; - /** * This object is used to abstract away the complexities of SNMP calls and allow * a user to more quickly and easily send SNMP requests. Note that the @@ -119,7 +117,7 @@ public SnmpSession(RSU rsu) throws IOException { * @return ResponseEvent * @throws IOException */ - public ResponseEvent set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { + public ResponseEvent
set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { // Ensure the object has been instantiated if (!ready) { @@ -199,28 +197,19 @@ public void startListen() throws IOException { listening = true; } - /** - * Create an SNMP session given the values in - * - * @param tim - The TIM parameters (payload, channel, mode, etc) - * @param props - The SNMP properties (ip, username, password, etc) - * @return ResponseEvent - * @throws TimPduCreatorException - * @throws IOException - * @throws ParseException - */ - public static ResponseEvent createAndSend(SNMP snmp, RSU rsu, String payload, RequestVerb requestVerb, boolean dataSigningEnabledRSU) - throws ParseException, IOException { + + public static ResponseEvent
createAndSend(SNMP snmp, RSU rsu, String payload, + RequestVerb requestVerb, boolean dataSigningEnabledRSU) throws ParseException, IOException { SnmpSession session = new SnmpSession(rsu); // Send the PDU - ResponseEvent response = null; - ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, rsu.getSnmpProtocol(), dataSigningEnabledRSU); - response = session.set(pdu, session.getSnmp(), session.getTarget(), false); - String msg = "Message Sent to {}, index {}: {}"; - EventLogger.logger.debug(msg, rsu.getRsuTarget(), rsu.getRsuIndex(), payload); - logger.debug(msg, rsu.getRsuTarget(), rsu.getRsuIndex(), payload); + ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, + rsu.getSnmpProtocol(), dataSigningEnabledRSU); + ResponseEvent
response = + session.set(pdu, session.getSnmp(), session.getTarget(), false); + logger.debug("Message Sent to {}, index {}: {}", rsu.getRsuTarget(), rsu.getRsuIndex(), + payload); return response; } From e5d4b26f1dfbe9ddc49e3979c2a591f270459e7b Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 14:40:55 -0700 Subject: [PATCH 046/128] style: reformat and add missing Javadocs SnmpSession --- .../us/dot/its/jpo/ode/snmp/SnmpSession.java | 697 +++++++++--------- 1 file changed, 361 insertions(+), 336 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java index b904f9837..86a55c41c 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/snmp/SnmpSession.java @@ -1,4 +1,4 @@ -/******************************************************************************* +/*============================================================================ * Copyright 2018 572682 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. ******************************************************************************/ + package us.dot.its.jpo.ode.snmp; import java.io.IOException; @@ -47,366 +48,390 @@ import us.dot.its.jpo.ode.plugin.SnmpProtocol; /** - * This object is used to abstract away the complexities of SNMP calls and allow - * a user to more quickly and easily send SNMP requests. Note that the - * "connection" aspect of this class is an abstraction meant to reinforce that - * these objects correspond 1-to-1 with a destination server, while SNMP is sent - * over UDP and is actually connection-less. + * This object is used to abstract away the complexities of SNMP calls and allow a user to more + * quickly and easily send SNMP requests. Note that the "connection" aspect of this class is an + * abstraction meant to reinforce that these objects correspond 1-to-1 with a destination server, + * while SNMP is sent over UDP and is actually connection-less. */ public class SnmpSession { - private static final Logger logger = LoggerFactory.getLogger(SnmpSession.class); - - private Snmp snmp; - private TransportMapping transport; - private UserTarget target; - - private boolean ready = false; - private boolean listening; - - /** - * Constructor for SnmpSession - * - * @param props SnmpProperties for the session (target address, retries, - * timeout, etc) - * @throws IOException - */ - public SnmpSession(RSU rsu) throws IOException { - Address addr = GenericAddress.parse(rsu.getRsuTarget() + "/161"); - - // Create a "target" to which a request is sent - target = new UserTarget(); - target.setAddress(addr); - target.setRetries(rsu.getRsuRetries()); - target.setTimeout(rsu.getRsuTimeout()); - target.setVersion(SnmpConstants.version3); - if (rsu.getRsuUsername() != null) { - target.setSecurityLevel(SecurityLevel.AUTH_PRIV); - target.setSecurityName(new OctetString(rsu.getRsuUsername())); - } else { - target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); - } - - // Set up the UDP transport mapping over which requests are sent - transport = null; - try { - transport = new DefaultUdpTransportMapping(); - } catch (IOException e) { - throw new IOException("Failed to create UDP transport mapping: {}", e); - } - - // Instantiate the SNMP instance - snmp = new Snmp(transport); - - // Register the security options and create an SNMP "user" - USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); - SecurityModels.getInstance().addSecurityModel(usm); - if (rsu.getRsuUsername() != null) { - snmp.getUSM().addUser(new OctetString(rsu.getRsuUsername()), new UsmUser(new OctetString(rsu.getRsuUsername()), - AuthSHA.ID, new OctetString(rsu.getRsuPassword()), PrivAES128.ID, new OctetString(rsu.getRsuPassword()))); - } - - // Assert the ready flag so the user can begin sending messages - ready = true; + private static final Logger logger = LoggerFactory.getLogger(SnmpSession.class); + + private Snmp snmp; + private TransportMapping transport; + private UserTarget target; + + private boolean ready = false; + private boolean listening; + + /** + * Constructor for SnmpSession. + * + * @throws IOException when failing to create the snmp Transport + */ + public SnmpSession(RSU rsu) throws IOException { + Address addr = GenericAddress.parse(rsu.getRsuTarget() + "/161"); + + // Create a "target" to which a request is sent + target = new UserTarget(); + target.setAddress(addr); + target.setRetries(rsu.getRsuRetries()); + target.setTimeout(rsu.getRsuTimeout()); + target.setVersion(SnmpConstants.version3); + if (rsu.getRsuUsername() != null) { + target.setSecurityLevel(SecurityLevel.AUTH_PRIV); + target.setSecurityName(new OctetString(rsu.getRsuUsername())); + } else { + target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); } - /** - * Sends a SET-type PDU to the target specified by the constructor. - * - * @param pdu The message content to be sent to the target - * @return ResponseEvent - * @throws IOException - */ - public ResponseEvent
set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { - - // Ensure the object has been instantiated - if (!ready) { - throw new IOException("Tried to send PDU before SNMP sending service is ready."); - } - - if (!listening) { - startListen(); - } - - // Try to send the SNMP request (synchronously) - ResponseEvent
responseEvent; - try { - // attempt to discover & set authoritative engine ID - byte[] authEngineID = snmpob.discoverAuthoritativeEngineID(targetob.getAddress(), 1000); - if (authEngineID != null && authEngineID.length > 0) { - targetob.setAuthoritativeEngineID(authEngineID); - } - - // send request - responseEvent = snmpob.set(pdu, targetob); - if (!keepOpen) { - snmpob.close(); - } - - // if RSU responded and we didn't get an authEngineID, log a warning - if (responseEvent != null && responseEvent.getResponse() != null && authEngineID == null) { - logger.warn("Failed to discover authoritative engine ID for SNMP target: {}", targetob.getAddress()); - } - } catch (IOException e) { - throw new IOException("Failed to send SNMP request: " + e); - } - - return responseEvent; + // Set up the UDP transport mapping over which requests are sent + transport = new DefaultUdpTransportMapping(); + + // Instantiate the SNMP instance + snmp = new Snmp(transport); + + // Register the security options and create an SNMP "user" + USM usm = + new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); + SecurityModels.getInstance().addSecurityModel(usm); + if (rsu.getRsuUsername() != null) { + snmp.getUSM().addUser(new OctetString(rsu.getRsuUsername()), + new UsmUser(new OctetString(rsu.getRsuUsername()), + AuthSHA.ID, new OctetString(rsu.getRsuPassword()), PrivAES128.ID, + new OctetString(rsu.getRsuPassword()))); } - /** - * Sends a SET-type PDU to the target specified by the constructor. - * - * @param pdu The message content to be sent to the target - * @return ResponseEvent - * @throws IOException - */ - public ResponseEvent get(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) throws IOException { - - // Ensure the object has been instantiated - if (!ready) { - throw new IOException("Tried to send PDU before SNMP sending service is ready."); - } - - // Start listening on UDP - if (!listening) { - startListen(); - } - - // Try to send the SNMP request (synchronously) - ResponseEvent responseEvent = null; - try { - responseEvent = snmpob.get(pdu, targetob); - if (!keepOpen) { - snmpob.close(); - } - } catch (IOException e) { - throw new IOException("Failed to send SNMP request: " + e); - } - - return responseEvent; + // Assert the ready flag so the user can begin sending messages + ready = true; + } + + /** + * Sends a SET-type PDU to the target specified by the constructor. + * + * @param pdu The message content to be sent to the target + * @return ResponseEvent + * @throws IOException when calling this method before the session is ready + */ + public ResponseEvent
set(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) + throws IOException { + + // Ensure the object has been instantiated + if (!ready) { + throw new IOException("Tried to send PDU before SNMP sending service is ready."); } - /** - * Start listening for responses - * - * @throws IOException - */ - public void startListen() throws IOException { - transport.listen(); - listening = true; + if (!listening) { + startListen(); } - - public static ResponseEvent
createAndSend(SNMP snmp, RSU rsu, String payload, - RequestVerb requestVerb, boolean dataSigningEnabledRSU) throws ParseException, IOException { - - SnmpSession session = new SnmpSession(rsu); - - // Send the PDU - ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, - rsu.getSnmpProtocol(), dataSigningEnabledRSU); - ResponseEvent
response = - session.set(pdu, session.getSnmp(), session.getTarget(), false); - logger.debug("Message Sent to {}, index {}: {}", rsu.getRsuTarget(), rsu.getRsuIndex(), - payload); - return response; + // Try to send the SNMP request (synchronously) + ResponseEvent
responseEvent; + try { + // attempt to discover & set authoritative engine ID + byte[] authEngineID = snmpob.discoverAuthoritativeEngineID(targetob.getAddress(), 1000); + if (authEngineID != null && authEngineID.length > 0) { + targetob.setAuthoritativeEngineID(authEngineID); + } + + // send request + responseEvent = snmpob.set(pdu, targetob); + if (!keepOpen) { + snmpob.close(); + } + + // if RSU responded and we didn't get an authEngineID, log a warning + if (responseEvent != null && responseEvent.getResponse() != null && authEngineID == null) { + logger.warn("Failed to discover authoritative engine ID for SNMP target: {}", + targetob.getAddress()); + } + } catch (IOException e) { + throw new IOException("Failed to send SNMP request: " + e); } - public Snmp getSnmp() { - return snmp; + return responseEvent; + } + + /** + * Sends a SET-type PDU to the target specified by the constructor. + * + * @param pdu The message content to be sent to the target + * @return ResponseEvent + * @throws IOException when calling this method before the session is ready + */ + public ResponseEvent get(PDU pdu, Snmp snmpob, UserTarget targetob, Boolean keepOpen) + throws IOException { + + // Ensure the object has been instantiated + if (!ready) { + throw new IOException("Tried to send PDU before SNMP sending service is ready."); } - public void setSnmp(Snmp snmp) { - this.snmp = snmp; + // Start listening on UDP + if (!listening) { + startListen(); } - public TransportMapping getTransport() { - return transport; + // Try to send the SNMP request (synchronously) + ResponseEvent responseEvent = null; + try { + responseEvent = snmpob.get(pdu, targetob); + if (!keepOpen) { + snmpob.close(); + } + } catch (IOException e) { + throw new IOException("Failed to send SNMP request: " + e); } - public void setTransport(TransportMapping transport) { - this.transport = transport; + return responseEvent; + } + + /** + * Start listening for responses. + * + * @throws IOException when listening failed + */ + public void startListen() throws IOException { + transport.listen(); + listening = true; + } + + + /** + * Creates and sends a PDU to a specific RSU (Road Side Unit) using the provided SNMP session. The + * request is built with specified payload, request verb, and data signing option. + * + * @param snmp The SNMP object containing configuration and metadata for + * constructing the request. + * @param rsu The RSU object that provides target and protocol information for + * the request destination. + * @param payload The payload string to be included in the PDU of the request. + * @param requestVerb The type of SNMP request verb (e.g., GET, SET) for the operation. + * @param dataSigningEnabledRSU A boolean flag indicating if data signing is enabled for the RSU. + * @return A ResponseEvent representing the response received from the RSU. + * @throws ParseException If there is an error in parsing the payload or PDU creation. + * @throws IOException If there is an error in establishing or using the SNMP transport. + */ + public static ResponseEvent
createAndSend(SNMP snmp, RSU rsu, String payload, + RequestVerb requestVerb, boolean dataSigningEnabledRSU) throws ParseException, IOException { + + SnmpSession session = new SnmpSession(rsu); + + // Send the PDU + ScopedPDU pdu = SnmpSession.createPDU(snmp, payload, rsu.getRsuIndex(), requestVerb, + rsu.getSnmpProtocol(), dataSigningEnabledRSU); + ResponseEvent
response = + session.set(pdu, session.getSnmp(), session.getTarget(), false); + logger.debug("Message Sent to {}, index {}: {}", rsu.getRsuTarget(), rsu.getRsuIndex(), + payload); + return response; + } + + public Snmp getSnmp() { + return snmp; + } + + public void setSnmp(Snmp snmp) { + this.snmp = snmp; + } + + public TransportMapping getTransport() { + return transport; + } + + public void setTransport(TransportMapping transport) { + this.transport = transport; + } + + public UserTarget getTarget() { + return target; + } + + public void setTarget(UserTarget target) { + this.target = target; + } + + public void endSession() throws IOException { + this.snmp.close(); + } + + /** + * Creates a ScopedPDU object configured based on the specified SNMP protocol. + * + * @param snmp The SNMP object containing configuration and metadata for + * constructing the PDU. + * @param payload The payload string to be included in the PDU. + * @param index The index value associated with the PDU construction. + * @param verb The request verb (e.g., GET, SET) for the PDU. + * @param snmpProtocol The SNMP protocol version or type used for constructing the PDU. + * @param dataSigningEnabledRSU A boolean flag indicating whether data signing is enabled for the + * RSU. + * @return A ScopedPDU object constructed based on the provided parameters, or null if the + * protocol is unknown. + * @throws ParseException If there is an error in parsing the payload or during PDU creation. + */ + public static ScopedPDU createPDU(SNMP snmp, String payload, int index, RequestVerb verb, + SnmpProtocol snmpProtocol, boolean dataSigningEnabledRSU) throws ParseException { + switch (snmpProtocol) { + case FOURDOT1: + return createPDUWithFourDot1Protocol(snmp, payload, index, verb); + case NTCIP1218: + return createPDUWithNTCIP1218Protocol(snmp, payload, index, verb, dataSigningEnabledRSU); + default: + logger.error("Unknown SNMP protocol: {}", snmpProtocol); + return null; } - - public UserTarget getTarget() { - return target; + } + + /** + * Encodes the given value into an SNMP variable binding using specific encoding rules + * based on the value's range. + * + * @param oid The Object Identifier (OID) as a string, representing the SNMP object ID. + * @param val The value to be encoded, provided as a hexadecimal string. + * @return A VariableBinding object that contains the OID and the encoded value, or null + * if the value does not fall within any of the predefined ranges. + */ + public static VariableBinding getPEncodedVariableBinding(String oid, String val) { + Integer intVal = Integer.parseInt(val, 16); + Integer additionValue = null; + + if (intVal >= 0 && intVal <= 127) { + // P = V + // here we must instantiate the OctetString directly with the hex string to + // avoid inadvertently creating the ASCII character codes + // for instance OctetString.fromString("20", 16) produces the space character (" + // ") rather than hex 20 + return new VariableBinding(new OID(oid), new OctetString(Integer.toHexString(intVal))); + } else if (intVal >= 128 && intVal <= 16511) { + // P = V + 0x7F80 + additionValue = 0x7F80; + } else if (intVal >= 016512 && intVal <= 2113663) { + // P = V + 0xBFBF80 + additionValue = 0xBFBF80; + } else if (intVal >= 2113664 && intVal <= 270549119) { + // P = V + 0xDFDFBF80 + additionValue = 0xDFDFBF80; } - public void setTarget(UserTarget target) { - this.target = target; + if (additionValue != null) { + return new VariableBinding(new OID(oid), + OctetString.fromString(Integer.toHexString(intVal + additionValue), 16)); } - - public void endSession() throws IOException { - this.snmp.close(); + return null; + } + + private static ScopedPDU createPDUWithFourDot1Protocol(SNMP snmp, String payload, int index, + RequestVerb verb) throws ParseException { + ////////////////////////////// + // - OID examples - // + ////////////////////////////// + // rsuSRMStatus.3 = 4 + // --> 1.4.1.11.3 = 4 + // rsuSRMTxChannel.3 = 3 + // --> 1.4.1.5.3 = 178 + // rsuSRMTxMode.3 = 1 + // --> 1.4.1.4.3 = 1 + // rsuSRMPsid.3 x "8003" + // --> 1.4.1.2.3 x "8003" + // rsuSRMDsrcMsgId.3 = 31 + // --> 1.4.1.3.3 = 31 + // rsuSRMTxInterval.3 = 10 + // --> 1.4.1.6.3 = 10 + // rsuSRMDeliveryStart.3 x "07e7051f0c000000" + // --> 1.4.1.7.3 = "07e7051f0c000000" + // rsuSRMDeliveryStop.3 x "07e7060f0c000000" + // --> 1.4.1.8.3 = "07e7060f0c000000" + // rsuSRMPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // --> 1.4.1.9.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // rsuSRMEnable.3 = 1 + // --> 1.4.1.10.3 = 1 + ////////////////////////////// + + VariableBinding rsuSRMPsid = SnmpFourDot1Protocol.getVbRsuSrmPsid(index, snmp.getRsuid()); + VariableBinding rsuSRMDsrcMsgId = + SnmpFourDot1Protocol.getVbRsuSrmDsrcMsgId(index, snmp.getMsgid()); + VariableBinding rsuSRMTxMode = SnmpFourDot1Protocol.getVbRsuSrmTxMode(index, snmp.getMode()); + VariableBinding rsuSRMTxChannel = + SnmpFourDot1Protocol.getVbRsuSrmTxChannel(index, snmp.getChannel()); + VariableBinding rsuSRMTxInterval = + SnmpFourDot1Protocol.getVbRsuSrmTxInterval(index, snmp.getInterval()); + VariableBinding rsuSRMDeliveryStart = + SnmpFourDot1Protocol.getVbRsuSrmDeliveryStart(index, snmp.getDeliverystart()); + VariableBinding rsuSRMDeliveryStop = + SnmpFourDot1Protocol.getVbRsuSrmDeliveryStop(index, snmp.getDeliverystop()); + VariableBinding rsuSRMPayload = SnmpFourDot1Protocol.getVbRsuSrmPayload(index, payload); + VariableBinding rsuSRMEnable = SnmpFourDot1Protocol.getVbRsuSrmEnable(index, snmp.getEnable()); + + ScopedPDU pdu = new ScopedPDU(); + pdu.add(rsuSRMPsid); + pdu.add(rsuSRMDsrcMsgId); + pdu.add(rsuSRMTxMode); + pdu.add(rsuSRMTxChannel); + pdu.add(rsuSRMTxInterval); + pdu.add(rsuSRMDeliveryStart); + pdu.add(rsuSRMDeliveryStop); + pdu.add(rsuSRMPayload); + pdu.add(rsuSRMEnable); + if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { + VariableBinding rsuSRMStatus = SnmpFourDot1Protocol.getVbRsuSrmStatus(index, snmp.getStatus()); + pdu.add(rsuSRMStatus); } - - /** - * Assembles the various RSU elements of a TimParameters object into a usable - * PDU. - * - * @param index Storage index on the RSU - * @param params TimParameters POJO that stores status, channel, payload, etc. - * @return PDU - * @throws ParseException - */ - public static ScopedPDU createPDU(SNMP snmp, String payload, int index, RequestVerb verb, SnmpProtocol snmpProtocol, boolean dataSigningEnabledRSU) throws ParseException { - switch (snmpProtocol) { - case FOURDOT1: - return createPDUWithFourDot1Protocol(snmp, payload, index, verb); - case NTCIP1218: - return createPDUWithNTCIP1218Protocol(snmp, payload, index, verb, dataSigningEnabledRSU); - default: - logger.error("Unknown SNMP protocol: {}", snmpProtocol); - return null; - } - } - - public static VariableBinding getPEncodedVariableBinding(String oid, String val) { - Integer intVal = Integer.parseInt(val, 16); - Integer additionValue = null; - - if (intVal >= 0 && intVal <= 127) { - // P = V - // here we must instantiate the OctetString directly with the hex string to - // avoid inadvertently creating the ASCII character codes - // for instance OctetString.fromString("20", 16) produces the space character (" - // ") rather than hex 20 - return new VariableBinding(new OID(oid), new OctetString(Integer.toHexString(intVal))); - } else if (intVal >= 128 && intVal <= 16511) { - // P = V + 0x7F80 - additionValue = 0x7F80; - } else if (intVal >= 016512 && intVal <= 2113663) { - // P = V + 0xBFBF80 - additionValue = 0xBFBF80; - } else if (intVal >= 2113664 && intVal <= 270549119) { - // P = V + 0xDFDFBF80 - additionValue = 0xDFDFBF80; - } - - if (additionValue != null) { - return new VariableBinding(new OID(oid), - OctetString.fromString(Integer.toHexString(intVal + additionValue), 16)); - } - return null; + pdu.setType(PDU.SET); + + return pdu; + } + + private static ScopedPDU createPDUWithNTCIP1218Protocol(SNMP snmp, String payload, int index, + RequestVerb verb, boolean dataSigningEnabledRSU) throws ParseException { + ////////////////////////////// + // - OID examples - // + ////////////////////////////// + // rsuMsgRepeatPsid.3 x "8003" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.2.3 x "8003" + // rsuMsgRepeatTxChannel.3 = 3 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.3.3 = 183 + // rsuMsgRepeatTxInterval.3 = 10 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.4.3 = 10 + // rsuMsgRepeatDeliveryStart.3 x "07e7051f0c000000" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.5.3 = "07e7051f0c000000" + // rsuMsgRepeatDeliveryStop.3 x "07e7060f0c000000" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.6.3 = "07e7060f0c000000" + // rsuMsgRepeatPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.7.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" + // rsuMsgRepeatEnable.3 = 1 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.8.3 = 1 + // rsuMsgRepeatStatus.3 = 4 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.9.3 = 4 + // rsuMsgRepeatPriority.3 = 6 + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.10.3 = 6 + // rsuMsgRepeatOptions.3 = "00" + // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.11.3 = "00" + ////////////////////////////// + + // note: dsrc_msg_id is not in NTCIP 1218 + // note: tx_mode is not in NTCIP 1218 + ScopedPDU pdu = new ScopedPDU(); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPsid(index, snmp.getRsuid())); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxChannel(index, snmp.getChannel())); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxInterval(index, snmp.getInterval())); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStart(index, snmp.getDeliverystart())); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStop(index, snmp.getDeliverystop())); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPayload(index, payload)); + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatEnable(index, snmp.getEnable())); + if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatStatus(index, snmp.getStatus())); } - - private static ScopedPDU createPDUWithFourDot1Protocol(SNMP snmp, String payload, int index, RequestVerb verb) throws ParseException { - ////////////////////////////// - // - OID examples - // - ////////////////////////////// - // rsuSRMStatus.3 = 4 - // --> 1.4.1.11.3 = 4 - // rsuSRMTxChannel.3 = 3 - // --> 1.4.1.5.3 = 178 - // rsuSRMTxMode.3 = 1 - // --> 1.4.1.4.3 = 1 - // rsuSRMPsid.3 x "8003" - // --> 1.4.1.2.3 x "8003" - // rsuSRMDsrcMsgId.3 = 31 - // --> 1.4.1.3.3 = 31 - // rsuSRMTxInterval.3 = 10 - // --> 1.4.1.6.3 = 10 - // rsuSRMDeliveryStart.3 x "07e7051f0c000000" - // --> 1.4.1.7.3 = "07e7051f0c000000" - // rsuSRMDeliveryStop.3 x "07e7060f0c000000" - // --> 1.4.1.8.3 = "07e7060f0c000000" - // rsuSRMPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // --> 1.4.1.9.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // rsuSRMEnable.3 = 1 - // --> 1.4.1.10.3 = 1 - ////////////////////////////// - - VariableBinding rsuSRMPsid = SnmpFourDot1Protocol.getVbRsuSrmPsid(index, snmp.getRsuid()); - VariableBinding rsuSRMDsrcMsgId = SnmpFourDot1Protocol.getVbRsuSrmDsrcMsgId(index, snmp.getMsgid()); - VariableBinding rsuSRMTxMode = SnmpFourDot1Protocol.getVbRsuSrmTxMode(index, snmp.getMode()); - VariableBinding rsuSRMTxChannel = SnmpFourDot1Protocol.getVbRsuSrmTxChannel(index, snmp.getChannel()); - VariableBinding rsuSRMTxInterval = SnmpFourDot1Protocol.getVbRsuSrmTxInterval(index, snmp.getInterval()); - VariableBinding rsuSRMDeliveryStart = SnmpFourDot1Protocol.getVbRsuSrmDeliveryStart(index, snmp.getDeliverystart()); - VariableBinding rsuSRMDeliveryStop = SnmpFourDot1Protocol.getVbRsuSrmDeliveryStop(index, snmp.getDeliverystop()); - VariableBinding rsuSRMPayload = SnmpFourDot1Protocol.getVbRsuSrmPayload(index, payload); - VariableBinding rsuSRMEnable = SnmpFourDot1Protocol.getVbRsuSrmEnable(index, snmp.getEnable()); - VariableBinding rsuSRMStatus = SnmpFourDot1Protocol.getVbRsuSrmStatus(index, snmp.getStatus()); - - ScopedPDU pdu = new ScopedPDU(); - pdu.add(rsuSRMPsid); - pdu.add(rsuSRMDsrcMsgId); - pdu.add(rsuSRMTxMode); - pdu.add(rsuSRMTxChannel); - pdu.add(rsuSRMTxInterval); - pdu.add(rsuSRMDeliveryStart); - pdu.add(rsuSRMDeliveryStop); - pdu.add(rsuSRMPayload); - pdu.add(rsuSRMEnable); - if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { - pdu.add(rsuSRMStatus); - } - pdu.setType(PDU.SET); - - return pdu; + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPriority(index)); + if (dataSigningEnabledRSU) { + // set options to 0x00 to tell RSU to broadcast message without signing or attaching a 1609.2 header + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x00)); + } else { + // set options to 0x80 to tell RSU to sign & attach a 1609.2 header before broadcasting + pdu.add(SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x80)); } + pdu.setType(PDU.SET); - private static ScopedPDU createPDUWithNTCIP1218Protocol(SNMP snmp, String payload, int index, RequestVerb verb, boolean dataSigningEnabledRSU) throws ParseException { - ////////////////////////////// - // - OID examples - // - ////////////////////////////// - // rsuMsgRepeatPsid.3 x "8003" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.2.3 x "8003" - // rsuMsgRepeatTxChannel.3 = 3 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.3.3 = 183 - // rsuMsgRepeatTxInterval.3 = 10 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.4.3 = 10 - // rsuMsgRepeatDeliveryStart.3 x "07e7051f0c000000" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.5.3 = "07e7051f0c000000" - // rsuMsgRepeatDeliveryStop.3 x "07e7060f0c000000" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.6.3 = "07e7060f0c000000" - // rsuMsgRepeatPayload.3 x "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.7.3 = "001f6020100000000000de8f834082729de80d80734d37862d2187864fc2099f1f4028407e53bd01b00e69a6f0c5a409c46c3c300118e69a26fa77a0104b8e69a2e86779e21981414e39a68fd29de697d804fb38e69a50e27796151013d81080020290" - // rsuMsgRepeatEnable.3 = 1 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.8.3 = 1 - // rsuMsgRepeatStatus.3 = 4 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.9.3 = 4 - // rsuMsgRepeatPriority.3 = 6 - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.10.3 = 6 - // rsuMsgRepeatOptions.3 = "00" - // --> 1.3.6.1.4.1.1206.4.2.18.3.2.1.11.3 = "00" - ////////////////////////////// - - VariableBinding rsuMsgRepeatPsid = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPsid(index, snmp.getRsuid()); - // note: dsrc_msg_id is not in NTCIP 1218 - // note: tx_mode is not in NTCIP 1218 - VariableBinding rsuMsgRepeatTxChannel = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxChannel(index, snmp.getChannel()); - VariableBinding rsuMsgRepeatTxInterval = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatTxInterval(index, snmp.getInterval()); - VariableBinding rsuMsgRepeatDeliveryStart = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStart(index, snmp.getDeliverystart()); - VariableBinding rsuMsgRepeatDeliveryStop = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatDeliveryStop(index, snmp.getDeliverystop()); - VariableBinding rsuMsgRepeatPayload = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPayload(index, payload); - VariableBinding rsuMsgRepeatEnable = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatEnable(index, snmp.getEnable()); - VariableBinding rsuMsgRepeatStatus = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatStatus(index, snmp.getStatus()); - VariableBinding rsuMsgRepeatPriority = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatPriority(index); - VariableBinding rsuMsgRepeatOptions; - if (dataSigningEnabledRSU) { - // set options to 0x00 to tell RSU to broadcast message without signing or attaching a 1609.2 header - rsuMsgRepeatOptions = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x00); - } else { - // set options to 0x80 to tell RSU to sign & attach a 1609.2 header before broadcasting - rsuMsgRepeatOptions = SnmpNTCIP1218Protocol.getVbRsuMsgRepeatOptions(index, 0x80); - } - - ScopedPDU pdu = new ScopedPDU(); - pdu.add(rsuMsgRepeatPsid); - pdu.add(rsuMsgRepeatTxChannel); - pdu.add(rsuMsgRepeatTxInterval); - pdu.add(rsuMsgRepeatDeliveryStart); - pdu.add(rsuMsgRepeatDeliveryStop); - pdu.add(rsuMsgRepeatPayload); - pdu.add(rsuMsgRepeatEnable); - if (verb == ServiceRequest.OdeInternal.RequestVerb.POST) { - pdu.add(rsuMsgRepeatStatus); - } - pdu.add(rsuMsgRepeatPriority); - pdu.add(rsuMsgRepeatOptions); - pdu.setType(PDU.SET); - - return pdu; - } + return pdu; + } } \ No newline at end of file From edce8c714bab7c22a1816c421063c2b292df20e8 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 14:41:19 -0700 Subject: [PATCH 047/128] chore: delete unused AsnCodecRouterServiceController.java --- .../asn1/AsnCodecRouterServiceController.java | 76 ------------------- 1 file changed, 76 deletions(-) delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java deleted file mode 100644 index c6e0ed63a..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/AsnCodecRouterServiceController.java +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************* - * Copyright 2018 572682 - * - * Licensed 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 us.dot.its.jpo.ode.services.asn1; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import us.dot.its.jpo.ode.OdeTimJsonTopology; -import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; -import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; -import us.dot.its.jpo.ode.kafka.topics.JsonTopics; -import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; -import us.dot.its.jpo.ode.rsu.RsuDepositor; -import us.dot.its.jpo.ode.rsu.RsuProperties; -import us.dot.its.jpo.ode.security.SecurityServicesClientImpl; -import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.wrapper.MessageConsumer; - -/** - * Launches ToJsonConverter service - */ -@Controller -@Slf4j -public class AsnCodecRouterServiceController { - - @Autowired - public AsnCodecRouterServiceController(OdeKafkaProperties odeKafkaProperties, - JsonTopics jsonTopics, - Asn1CoderTopics asn1CoderTopics, - SDXDepositorTopics sdxDepositorTopics, - RsuProperties rsuProperties, - SecurityServicesProperties securityServicesProperties) { - super(); - - log.info("Starting {}", this.getClass().getSimpleName()); - - // asn1_codec Encoder Routing - log.info("Routing ENCODED data received ASN.1 Encoder"); - - Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( - odeKafkaProperties, - asn1CoderTopics, - jsonTopics, - securityServicesProperties, - new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()), - new Asn1CommandManager( - new RsuDepositor( - rsuProperties, - securityServicesProperties.getIsRsuSigningEnabled() - ) - ), - new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()), - sdxDepositorTopics.getInput() - ); - - MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - odeKafkaProperties.getBrokers(), this.getClass().getSimpleName(), encoderRouter); - - encoderConsumer.setName("Asn1EncoderConsumer"); - encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); - } -} From 6ffa17c7cc63d2ecf0abb31177cce04c9ed2b5b2 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 14:56:19 -0700 Subject: [PATCH 048/128] refactor: pull Asn1CommandManager functionality into Asn1EncodedDataRouter --- .../services/asn1/Asn1EncodedDataRouter.java | 135 ++++++++++++++++-- .../services/asn1/Asn1CommandManagerTest.java | 82 ----------- 2 files changed, 124 insertions(+), 93 deletions(-) delete mode 100644 jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 7180bc5fb..4f06358af 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -16,6 +16,9 @@ package us.dot.its.jpo.ode.services.asn1; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; @@ -29,8 +32,17 @@ import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; +import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; +import us.dot.its.jpo.ode.model.OdeAsdPayload; import us.dot.its.jpo.ode.model.OdeAsn1Data; +import us.dot.its.jpo.ode.model.OdeMsgMetadata; +import us.dot.its.jpo.ode.model.OdeMsgPayload; +import us.dot.its.jpo.ode.plugin.SNMP; import us.dot.its.jpo.ode.plugin.ServiceRequest; +import us.dot.its.jpo.ode.plugin.SituationDataWarehouse.SDW; +import us.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData; +import us.dot.its.jpo.ode.plugin.j2735.builders.GeoRegionBuilder; +import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; @@ -38,6 +50,7 @@ import us.dot.its.jpo.ode.util.JsonUtils; import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; +import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; import us.dot.its.jpo.ode.wrapper.AbstractSubscriberProcessor; import us.dot.its.jpo.ode.wrapper.MessageProducer; @@ -51,6 +64,7 @@ public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor stringMsgProducer; private final OdeTimJsonTopology odeTimJsonTopology; - private final Asn1CommandManager asn1CommandManager; + private final RsuDepositor rsuDepositor; private final boolean dataSigningEnabledSDW; private final boolean dataSigningEnabledRSU; @@ -91,7 +105,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, JsonTopics jsonTopics, SecurityServicesProperties securityServicesProperties, OdeTimJsonTopology odeTimJsonTopology, - Asn1CommandManager asn1CommandManager, + RsuDepositor rsuDepositor, ISecurityServicesClient securityServicesClient, String sdxDepositTopic) { super(); @@ -106,7 +120,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, odeKafkaProperties.getKafkaType(), odeKafkaProperties.getDisabledTopics()); - this.asn1CommandManager = asn1CommandManager; + this.rsuDepositor = rsuDepositor; this.dataSigningEnabledSDW = securityServicesProperties.getIsSdwSigningEnabled(); this.dataSigningEnabledRSU = securityServicesProperties.getIsRsuSigningEnabled(); @@ -229,7 +243,7 @@ public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) { // CASE 3: If SDW in metadata and ASD in body (double encoding complete) // - send to SDX - if (!dataObj.has(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING)) { + if (!dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { processSNMPDepositOnly(request, consumedObj, dataObj, metadataObj); } else { // We have encoded ASD. It could be either UNSECURED or secured. @@ -246,7 +260,7 @@ private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { // We have a ASD with signed MessageFrame // Case 3 JSONObject asdObj = dataObj.getJSONObject( - Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING); + ADVISORY_SITUATION_DATA_STRING); try { JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); @@ -294,7 +308,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO if (null != request.getSnmp() && null != request.getRsus() && null != hexEncodedTim) { log.info("Sending message to RSUs..."); - asn1CommandManager.sendToRsus(request, hexEncodedTim); + sendToRsus(request, hexEncodedTim); } hexEncodedTim = mfObj.getString(BYTES); @@ -310,7 +324,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO // Case 2 only log.debug("Publishing message for round 2 encoding!"); - String xmlizedMessage = asn1CommandManager.packageSignedTimIntoAsd(request, hexEncodedTim, metadataObj); + String xmlizedMessage = packageSignedTimIntoAsd(request, hexEncodedTim); stringMsgProducer.send(asn1CoderTopics.getEncoderInput(), null, xmlizedMessage); } @@ -335,8 +349,8 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum if (null != request.getSdw()) { JSONObject asdObj = null; - if (dataObj.has(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING)) { - asdObj = dataObj.getJSONObject(Asn1CommandManager.ADVISORY_SITUATION_DATA_STRING); + if (dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { + asdObj = dataObj.getJSONObject(ADVISORY_SITUATION_DATA_STRING); } else { log.error("ASD structure present in metadata but not in JSONObject!"); } @@ -387,9 +401,9 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum log.debug("Encoded message - phase 2: {}", encodedTim); // only send message to rsu if snmp, rsus, and message frame fields are present - if (null != request.getSnmp() && null != request.getRsus() && null != encodedTim) { + if (null != request.getSnmp() && null != request.getRsus()) { log.debug("Encoded message phase 3: {}", encodedTim); - asn1CommandManager.sendToRsus(request, encodedTim); + sendToRsus(request, encodedTim); } } @@ -443,6 +457,105 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu return encodedTIM; } + /** + * Constructs an XML representation of an Advisory Situation Data (ASD) message containing a + * signed Traveler Information Message (TIM). Processes the provided service request and signed + * message to create and structure the ASD before converting it to an XML string output. + * + * @param request the service request object containing meta information, service region, + * delivery time, and other necessary data for ASD creation. + * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. + * @return a String containing the fully crafted ASD message in XML format. Returns null if the + * message could not be constructed due to exceptions. + */ + public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { + + SDW sdw = request.getSdw(); + SNMP snmp = request.getSnmp(); + DdsAdvisorySituationData asd; + + byte sendToRsu = + request.getRsus() != null ? DdsAdvisorySituationData.RSU : DdsAdvisorySituationData.NONE; + byte distroType = (byte) (DdsAdvisorySituationData.IP | sendToRsu); + + String outputXml = null; + try { + if (null != snmp) { + + asd = new DdsAdvisorySituationData() + .setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null) + .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) + .setTimeToLive(sdw.getTtl()) + .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); + } else { + asd = new DdsAdvisorySituationData() + .setAsdmDetails(sdw.getDeliverystart(), sdw.getDeliverystop(), distroType, null) + .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) + .setTimeToLive(sdw.getTtl()) + .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); + } + + ObjectNode dataBodyObj = JsonUtils.newNode(); + ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); + ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); + admDetailsObj.remove("advisoryMessage"); + admDetailsObj.put("advisoryMessage", signedMsg); + + dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); + + OdeMsgPayload payload = new OdeAsdPayload(asd); + + ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); + payloadObj.set(AppContext.DATA_STRING, dataBodyObj); + + OdeMsgMetadata metadata = new OdeMsgMetadata(payload); + ObjectNode metaObject = JsonUtils.toObjectNode(metadata.toJson()); + + ObjectNode requestObj = JsonUtils.toObjectNode(JsonUtils.toJson(request, false)); + + requestObj.remove("tim"); + + metaObject.set("request", requestObj); + + ArrayNode encodings = buildEncodings(); + ObjectNode enc = + XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, encodings); + metaObject.set(AppContext.ENCODINGS_STRING, enc); + + ObjectNode message = JsonUtils.newNode(); + message.set(AppContext.METADATA_STRING, metaObject); + message.set(AppContext.PAYLOAD_STRING, payloadObj); + + ObjectNode root = JsonUtils.newNode(); + root.set(AppContext.ODE_ASN1_DATA, message); + + outputXml = XmlUtils.toXmlStatic(root); + + // remove the surrounding + outputXml = outputXml.replace("", ""); + outputXml = outputXml.replace("", ""); + + } catch (ParseException | JsonUtilsException | XmlUtilsException e) { + log.error("Parsing exception thrown while populating ASD structure: ", e); + } + + log.debug("Fully crafted ASD to be encoded: {}", outputXml); + + return outputXml; + } + + private ArrayNode buildEncodings() throws JsonUtilsException { + ArrayNode encodings = JsonUtils.newArrayNode(); + encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, + ADVISORY_SITUATION_DATA_STRING, + EncodingRule.UPER)); + return encodings; + } + + private void sendToRsus(ServiceRequest request, String encodedMsg) { + rsuDepositor.deposit(request, encodedMsg); + } + private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String timStartDateTime, int maxDurationTime, JSONObject timWithExpiration) { try { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java deleted file mode 100644 index 9207afd96..000000000 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManagerTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/*=========================================================================== - * Copyright 2018 572682 - * - * Licensed 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 us.dot.its.jpo.ode.services.asn1; - -import mockit.Capturing; -import mockit.Injectable; -import mockit.Mocked; -import mockit.Tested; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; -import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; -import us.dot.its.jpo.ode.model.OdeTravelerInputData; -import us.dot.its.jpo.ode.rsu.RsuProperties; -import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.snmp.SnmpSession; -import us.dot.its.jpo.ode.wrapper.MessageProducer; - -class Asn1CommandManagerTest { - - @Tested - Asn1CommandManager testAsn1CommandManager; - - @Injectable - OdeKafkaProperties injectableOdeKafkaProperties; - - @Injectable - SDXDepositorTopics injectableSDXDepositorTopics; - - @Injectable - RsuProperties injectableRsuProperties; - - @Injectable - SecurityServicesProperties injectableSecurityServicesProperties; - - @Capturing - MessageProducer capturingMessageProducer; - @Capturing - SnmpSession capturingSnmpSession; - - @Injectable - OdeTravelerInputData injectableOdeTravelerInputData; - - @Mocked - MessageProducer mockMessageProducer; - - @Test - @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") - void testPackageSignedTimIntoAsd() { - testAsn1CommandManager.packageSignedTimIntoAsd(injectableOdeTravelerInputData.getRequest(), - "message"); - } - - @Test - @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") - void testSendToRsus(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { - - testAsn1CommandManager.sendToRsus(mockOdeTravelerInputData.getRequest(), "message"); - } - - @Test - @Disabled("These tests don't confirm behavior and don't work with Spring Kafka refactors") - void testSendToRsusSnmpException(@Mocked OdeTravelerInputData mockOdeTravelerInputData) { - - testAsn1CommandManager.sendToRsus(mockOdeTravelerInputData.getRequest(), "message"); - } - -} From 9a652e2000bcc12cba6a025c72c91bb6bf95bdda Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 15:02:13 -0700 Subject: [PATCH 049/128] refactor: replace SDXDepositorTopics.java reference with Value reference for topic --- .../ode/kafka/topics/SDXDepositorTopics.java | 12 --------- .../services/asn1/Asn1EncodedDataRouter.java | 2 +- .../kafka/topics/SDXDepositorTopicsTest.java | 25 ------------------- .../asn1/Asn1EncodedDataRouterTest.java | 20 +++++++-------- 4 files changed, 10 insertions(+), 49 deletions(-) delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java delete mode 100644 jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java deleted file mode 100644 index d98e76e8b..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopics.java +++ /dev/null @@ -1,12 +0,0 @@ -package us.dot.its.jpo.ode.kafka.topics; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ConfigurationProperties(prefix = "ode.kafka.topics.sdx-depositor") -@Data -public class SDXDepositorTopics { - private String input; -} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 4f06358af..d3a8d1334 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -107,7 +107,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, OdeTimJsonTopology odeTimJsonTopology, RsuDepositor rsuDepositor, ISecurityServicesClient securityServicesClient, - String sdxDepositTopic) { + @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic) { super(); this.asn1CoderTopics = asn1CoderTopics; diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java deleted file mode 100644 index bc700a7dd..000000000 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/topics/SDXDepositorTopicsTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package us.dot.its.jpo.ode.kafka.topics; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.junit.jupiter.api.Assertions.*; - -@ExtendWith(SpringExtension.class) -@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) -@EnableConfigurationProperties(value = SDXDepositorTopics.class) -class SDXDepositorTopicsTest { - - @Autowired - SDXDepositorTopics sdxDepositorTopics; - - @Test - void getInput() { - assertEquals("topic.SDWDepositorInput", sdxDepositorTopics.getInput()); - } -} \ No newline at end of file diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 1628d2bef..55f8249ae 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -43,7 +43,6 @@ import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; -import us.dot.its.jpo.ode.kafka.topics.SDXDepositorTopics; import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.ISecurityServicesClient; @@ -65,7 +64,6 @@ KafkaProducerConfig.class, KafkaProperties.class, Asn1CoderTopics.class, - SDXDepositorTopics.class, JsonTopics.class, SecurityServicesProperties.class, RsuProperties.class, @@ -84,8 +82,8 @@ class Asn1EncodedDataRouterTest { SecurityServicesProperties securityServicesProperties; @Autowired OdeKafkaProperties odeKafkaProperties; - @Autowired - SDXDepositorTopics sdxDepositorTopics; + @Value("${ode.kafka.topics.sdx-depositor.input}") + String sdxDepositorTopic; @Autowired KafkaTemplate kafkaTemplate; @Mock @@ -94,13 +92,13 @@ class Asn1EncodedDataRouterTest { EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Test - void processSignedMessage_depositsToSdxTopic() + void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOException, InterruptedException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimTmcFiltered(), - sdxDepositorTopics.getInput() + sdxDepositorTopic }; EmbeddedKafkaHolder.addTopics(topicsForConsumption); EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); @@ -119,7 +117,7 @@ void processSignedMessage_depositsToSdxTopic() odeTimJsonTopology, asn1CommandManager, mockSecServClient, - sdxDepositorTopics.getInput() + sdxDepositorTopic ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( @@ -163,7 +161,7 @@ void processSignedMessage_depositsToSdxTopic() var records = KafkaTestUtils.getRecords(testConsumer); var sdxDepositorRecord = records - .records(sdxDepositorTopics.getInput()); + .records(sdxDepositorTopic); var foundValidRecord = false; for (var consumerRecord : sdxDepositorRecord) { if (consumerRecord.value().equals(expected)) { @@ -209,7 +207,7 @@ public String signMessage(String message, int sigValidityOverride) { odeTimJsonTopology, asn1CommandManager, mockSecServClient, - sdxDepositorTopics.getInput() + sdxDepositorTopic ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), "processSNMPDepositOnly-default", encoderRouter); @@ -338,7 +336,7 @@ public String signMessage(String message, int sigValidityOverride) { odeTimJsonTopology, asn1CommandManager, mockSecServClient, - sdxDepositorTopics.getInput() + sdxDepositorTopic ); MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); @@ -380,7 +378,7 @@ public String signMessage(String message, int sigValidityOverride) { var records = KafkaTestUtils.getRecords(testConsumer); var sdxDepositorRecord = records - .records(sdxDepositorTopics.getInput()); + .records(sdxDepositorTopic); for (var consumerRecord : sdxDepositorRecord) { if (consumerRecord.value().contains(streamId)) { assertEquals(expected, consumerRecord.value()); From b82e513a04e96d36c977240e330dc14d0857ab0b Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 15:03:16 -0700 Subject: [PATCH 050/128] refactor: introduce Config beans for Asn1EncodedDataRouter dependencies --- .../its/jpo/ode/kafka/KafkaStreamsConfig.java | 21 +++++++++++++++++++ .../us/dot/its/jpo/ode/rsu/RsuDepositor.java | 8 ------- .../its/jpo/ode/rsu/RsuDepositorConfig.java | 16 ++++++++++++++ .../ode/security/SecurityServicesConfig.java | 12 +++++++++++ .../services/asn1/Asn1EncodedDataRouter.java | 4 +++- .../dot/its/jpo/ode/rsu/RsuDepositorTest.java | 15 ++----------- .../asn1/Asn1EncodedDataRouterTest.java | 16 ++++---------- 7 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java new file mode 100644 index 000000000..3c2576881 --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java @@ -0,0 +1,21 @@ +package us.dot.its.jpo.ode.kafka; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import us.dot.its.jpo.ode.OdeTimJsonTopology; + +/** + * KafkaStreamsConfig is a Spring configuration class that provides + * beans related to Kafka Streams topology setup. + */ +@Configuration +public class KafkaStreamsConfig { + + @Bean + public OdeTimJsonTopology odeTimJsonTopology( + @Value("${ode.kafka.topics.json.tim}") String timTopic, + OdeKafkaProperties odeKafkaProperties) { + return new OdeTimJsonTopology(odeKafkaProperties, timTopic); + } +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java index 572bbfcb8..68234bfe7 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java @@ -51,14 +51,6 @@ public RsuDepositor(RsuProperties rsuProperties, boolean isDataSigningEnabled) { this.dataSigningEnabled = isDataSigningEnabled; } - public void shutdown() { - running = false; - } - - public boolean isRunning() { - return running; - } - @Override public void run() { try { diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java new file mode 100644 index 000000000..d146fb156 --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java @@ -0,0 +1,16 @@ +package us.dot.its.jpo.ode.rsu; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import us.dot.its.jpo.ode.security.SecurityServicesProperties; + +@Configuration +public class RsuDepositorConfig { + + @Bean + public RsuDepositor rsuDepositor(RsuProperties rsuProperties, SecurityServicesProperties securityServicesProps) { + var rsuDepositor = new RsuDepositor(rsuProperties, securityServicesProps.getIsRsuSigningEnabled()); + rsuDepositor.start(); + return rsuDepositor; + } +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java new file mode 100644 index 000000000..8e6c4fa53 --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java @@ -0,0 +1,12 @@ +package us.dot.its.jpo.ode.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SecurityServicesConfig { + @Bean + public ISecurityServicesClient securityServicesClient(SecurityServicesProperties securityServicesProperties) { + return new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()); + } +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index d3a8d1334..f0b2b34cf 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -26,6 +26,8 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.eventlog.EventLogger; @@ -58,6 +60,7 @@ * The Asn1EncodedDataRouter is responsible for routing encoded TIM messages that are consumed from * the Kafka topic.Asn1EncoderOutput topic and decide whether to route to the SDX or an RSU. **/ +@Component @Slf4j public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor { @@ -127,7 +130,6 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, this.odeTimJsonTopology = odeTimJsonTopology; } - @Override public Object process(String consumedData) { try { log.debug("Consumed: {}", consumedData); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java index a5451376f..7f3b99c46 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java @@ -16,6 +16,8 @@ package us.dot.its.jpo.ode.rsu; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -26,9 +28,6 @@ import us.dot.its.jpo.ode.model.OdeTravelerInputData; import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; - @ExtendWith(SpringExtension.class) @ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) @EnableConfigurationProperties(value = {RsuProperties.class, SecurityServicesProperties.class}) @@ -40,16 +39,6 @@ class RsuDepositorTest { @Autowired SecurityServicesProperties securityServicesProperties; - - @Test - void testShutdown() { - RsuDepositor testRsuDepositor = new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); - testRsuDepositor.shutdown(); - assertFalse(testRsuDepositor.isRunning()); - assertFalse(testRsuDepositor.isAlive()); - } - - @Test void testDeposit() { RsuDepositor testRsuDepositor = new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 55f8249ae..13a9196f1 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; @@ -104,9 +105,6 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); - var asn1CommandManager = new Asn1CommandManager( - mockRsuDepositor - ); var mockSecServClient = mock(ISecurityServicesClient.class); securityServicesProperties.setIsSdwSigningEnabled(true); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( @@ -115,7 +113,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() jsonTopics, securityServicesProperties, odeTimJsonTopology, - asn1CommandManager, + mockRsuDepositor, mockSecServClient, sdxDepositorTopic ); @@ -184,9 +182,6 @@ void processSNMPDepositOnly() throws IOException, InterruptedException { securityServicesProperties.setIsSdwSigningEnabled(true); securityServicesProperties.setIsRsuSigningEnabled(true); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); - var asn1CommandManager = new Asn1CommandManager( - mockRsuDepositor - ); var mockSecServClient = new ISecurityServicesClient() { @Override public String signMessage(String message, int sigValidityOverride) { @@ -205,7 +200,7 @@ public String signMessage(String message, int sigValidityOverride) { jsonTopics, securityServicesProperties, odeTimJsonTopology, - asn1CommandManager, + mockRsuDepositor, mockSecServClient, sdxDepositorTopic ); @@ -311,9 +306,6 @@ void processEncodedTimUnsecured() EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); - var asn1CommandManager = new Asn1CommandManager( - mockRsuDepositor - ); var mockSecServClient = new ISecurityServicesClient() { @Override public String signMessage(String message, int sigValidityOverride) { @@ -334,7 +326,7 @@ public String signMessage(String message, int sigValidityOverride) { jsonTopics, securityServicesProperties, odeTimJsonTopology, - asn1CommandManager, + mockRsuDepositor, mockSecServClient, sdxDepositorTopic ); From 70ebddf0503ce1076bd891ec65641fb0879921dd Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 19 Dec 2024 15:33:25 -0700 Subject: [PATCH 051/128] refactor: convert Asn1EncodedDataRouter to KafkaListener and update tests - not working well with other tests --- .../services/asn1/Asn1EncodedDataRouter.java | 41 +++++-------- .../asn1/Asn1EncodedDataRouterTest.java | 61 ++++++++++--------- 2 files changed, 47 insertions(+), 55 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index f0b2b34cf..959deb608 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -23,10 +23,12 @@ import java.util.Date; import java.util.HashMap; import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecord; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; @@ -53,7 +55,6 @@ import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; -import us.dot.its.jpo.ode.wrapper.AbstractSubscriberProcessor; import us.dot.its.jpo.ode.wrapper.MessageProducer; /** @@ -62,12 +63,12 @@ **/ @Component @Slf4j -public class Asn1EncodedDataRouter extends AbstractSubscriberProcessor { +public class Asn1EncodedDataRouter { private static final String BYTES = "bytes"; private static final String MESSAGE_FRAME = "MessageFrame"; private static final String ERROR_ON_SDX_DEPOSIT = "Error on SDX deposit."; - public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; + private static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; /** * Exception for Asn1EncodedDataRouter specific failures. @@ -100,8 +101,6 @@ public Asn1EncodedDataRouterException(String string) { * @param asn1CoderTopics The specified ASN1 Coder topics * @param jsonTopics The specified JSON topics to write to * @param securityServicesProperties The security services properties to use - * @param securityServicesClient - * @param sdxDepositTopic **/ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, Asn1CoderTopics asn1CoderTopics, @@ -130,10 +129,11 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, this.odeTimJsonTopology = odeTimJsonTopology; } - public Object process(String consumedData) { + @KafkaListener(topics = "${ode.kafka.topics.asn1EncoderOutput}") + public void listen(ConsumerRecord consumerRecord) { try { - log.debug("Consumed: {}", consumedData); - JSONObject consumedObj = XmlUtils.toJSONObject(consumedData).getJSONObject( + log.debug("Consumed: {}", consumerRecord.value()); + JSONObject consumedObj = XmlUtils.toJSONObject(consumerRecord.value()).getJSONObject( OdeAsn1Data.class.getSimpleName()); /* @@ -181,21 +181,12 @@ public Object process(String consumedData) { + TimTransmogrifier.REQUEST_STRING + "' object in the encoder response"); } } catch (Exception e) { - String msg = "Error in processing received message from ASN.1 Encoder module: " - + consumedData; - if (log.isDebugEnabled()) { - // print error message and stack trace - EventLogger.logger.error(msg, e); - log.error(msg, e); - } else { - // print error message only - EventLogger.logger.error(msg); - log.error(msg); - } + log.error("Error in processing received message with key {} from ASN.1 Encoder module", + consumerRecord.key(), e); } - return null; } + /** * Gets the service request based on the consumed JSONObject. * @@ -287,7 +278,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO String hexEncodedTim = mfObj.getString(BYTES); log.debug("Encoded message - phase 1: {}", hexEncodedTim); - // use Asnc1 library to decode the encoded tim returned from ASNC1; another + // use ASN.1 library to decode the encoded tim returned from ASN.1; another // class two blockers: decode the tim and decode the message-sign // Case 1: SNMP-deposit @@ -467,8 +458,8 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu * @param request the service request object containing meta information, service region, * delivery time, and other necessary data for ASD creation. * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. - * @return a String containing the fully crafted ASD message in XML format. Returns null if the - * message could not be constructed due to exceptions. + * @return a String containing the fully crafted ASD message in XML format. Returns null if the + * message could not be constructed due to exceptions. */ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { @@ -595,7 +586,6 @@ private boolean isHeaderPresent(String encodedTim) { * Strips header from unsigned message (all bytes before 001F hex value). */ private String stripHeader(String encodedUnsignedTim) { - String toReturn = ""; // find 001F hex value int index = encodedUnsignedTim.indexOf("001F"); if (index == -1) { @@ -603,8 +593,7 @@ private String stripHeader(String encodedUnsignedTim) { return encodedUnsignedTim; } // strip everything before 001F - toReturn = encodedUnsignedTim.substring(index); - return toReturn; + return encodedUnsignedTim.substring(index); } private void depositToFilteredTopic(JSONObject metadataObj, String hexEncodedTim) { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 13a9196f1..fd0a93437 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -36,10 +36,13 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.listener.MessageListener; import org.springframework.kafka.test.EmbeddedKafkaBroker; +import org.springframework.kafka.test.utils.ContainerTestUtils; import org.springframework.kafka.test.utils.KafkaTestUtils; import org.springframework.test.annotation.DirtiesContext; import us.dot.its.jpo.ode.OdeTimJsonTopology; +import us.dot.its.jpo.ode.kafka.KafkaConsumerConfig; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; @@ -49,7 +52,6 @@ import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; -import us.dot.its.jpo.ode.wrapper.MessageConsumer; @SpringBootTest( properties = { @@ -64,6 +66,7 @@ classes = { KafkaProducerConfig.class, KafkaProperties.class, + KafkaConsumerConfig.class, Asn1CoderTopics.class, JsonTopics.class, SecurityServicesProperties.class, @@ -87,14 +90,15 @@ class Asn1EncodedDataRouterTest { String sdxDepositorTopic; @Autowired KafkaTemplate kafkaTemplate; + @Autowired + KafkaConsumerConfig kafkaConsumerConfig; @Mock RsuDepositor mockRsuDepositor; EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Test - void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() - throws IOException, InterruptedException { + void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), @@ -118,15 +122,14 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() sdxDepositorTopic ); - MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - embeddedKafka.getBrokersAsString(), - "processSignedMessage_depositsToSdxTopicAndTimTmcFiltered", encoderRouter); - - encoderConsumer.setName("Asn1EncoderConsumer"); - encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); - - // Wait for encoderRouter to connect to the broker otherwise the test will fail :( - Thread.sleep(2000); + var container = kafkaConsumerConfig.kafkaListenerContainerFactory() + .createContainer(asn1CoderTopics.getEncoderOutput()); + container.setupMessageListener( + (MessageListener) encoderRouter::listen + ); + container.setBeanName("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered"); + container.start(); + ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( @@ -204,14 +207,14 @@ public String signMessage(String message, int sigValidityOverride) { mockSecServClient, sdxDepositorTopic ); - MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - embeddedKafka.getBrokersAsString(), "processSNMPDepositOnly-default", encoderRouter); - - encoderConsumer.setName("Asn1EncoderConsumer"); - encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); - - // Wait for encoderRouter to connect to the broker otherwise the test will fail :( - Thread.sleep(2000); + var container = kafkaConsumerConfig.kafkaListenerContainerFactory() + .createContainer(asn1CoderTopics.getEncoderOutput()); + container.setupMessageListener( + (MessageListener) encoderRouter::listen + ); + container.setBeanName("processSNMPDepositOnly"); + container.start(); + ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( @@ -296,8 +299,7 @@ public String signMessage(String message, int sigValidityOverride) { } @Test - void processEncodedTimUnsecured() - throws IOException, InterruptedException { + void processEncodedTimUnsecured() throws IOException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimTmcFiltered() @@ -330,14 +332,15 @@ public String signMessage(String message, int sigValidityOverride) { mockSecServClient, sdxDepositorTopic ); - MessageConsumer encoderConsumer = MessageConsumer.defaultStringMessageConsumer( - embeddedKafka.getBrokersAsString(), this.getClass().getSimpleName(), encoderRouter); - encoderConsumer.setName("Asn1EncoderConsumer"); - encoderRouter.start(encoderConsumer, asn1CoderTopics.getEncoderOutput()); - - // Wait for encoderRouter to connect to the broker otherwise the test will fail :( - Thread.sleep(2000); + var container = kafkaConsumerConfig.kafkaListenerContainerFactory() + .createContainer(asn1CoderTopics.getEncoderOutput()); + container.setupMessageListener( + (MessageListener) encoderRouter::listen + ); + container.setBeanName("processEncodedTimUnsecured"); + container.start(); + ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( From 572248682c93de5c557c6befe382874043840689 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 11:22:11 -0700 Subject: [PATCH 052/128] chore: delete now unused Asn1CommandManager.java --- .../ode/services/asn1/Asn1CommandManager.java | 171 ------------------ 1 file changed, 171 deletions(-) delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java deleted file mode 100644 index fb2d2ded9..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1CommandManager.java +++ /dev/null @@ -1,171 +0,0 @@ -/*============================================================================= - * Copyright 2018 572682 - * - * Licensed 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 us.dot.its.jpo.ode.services.asn1; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import java.text.ParseException; -import lombok.extern.slf4j.Slf4j; -import us.dot.its.jpo.ode.context.AppContext; -import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; -import us.dot.its.jpo.ode.model.OdeAsdPayload; -import us.dot.its.jpo.ode.model.OdeMsgMetadata; -import us.dot.its.jpo.ode.model.OdeMsgPayload; -import us.dot.its.jpo.ode.plugin.SNMP; -import us.dot.its.jpo.ode.plugin.ServiceRequest; -import us.dot.its.jpo.ode.plugin.SituationDataWarehouse.SDW; -import us.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData; -import us.dot.its.jpo.ode.plugin.j2735.builders.GeoRegionBuilder; -import us.dot.its.jpo.ode.rsu.RsuDepositor; -import us.dot.its.jpo.ode.traveler.TimTransmogrifier; -import us.dot.its.jpo.ode.util.JsonUtils; -import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; -import us.dot.its.jpo.ode.util.XmlUtils; -import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; - -/** - * Manages operations related to ASN.1 command processing and interaction with RSUs (Road Side - * Units). Handles the preparation, packaging, and distribution of messages to RSUs using an - * {@link RsuDepositor}. - */ -@Slf4j -public class Asn1CommandManager { - - public static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; - - private RsuDepositor rsuDepositor; - - /** - * Constructs an instance of Asn1CommandManager and initializes the RSU depositor. - * - * @param rsuDepositor The RSU depositor instance to be used for managing ASN.1 commands. This - * instance is started during initialization. - */ - public Asn1CommandManager(RsuDepositor rsuDepositor) { - this.rsuDepositor = rsuDepositor; - this.rsuDepositor.start(); - } - - /** - * Sends a message to the RSUs (Road Side Units) through the RSU depositor. - * - * @param request the service request containing relevant details for the operation - * @param encodedMsg the message to be sent, encoded in the desired format - */ - public void sendToRsus(ServiceRequest request, String encodedMsg) { - rsuDepositor.deposit(request, encodedMsg); - } - - /** - * Constructs an XML representation of an Advisory Situation Data (ASD) message containing a - * signed Traveler Information Message (TIM). Processes the provided service request and signed - * message to create and structure the ASD before converting it to an XML string output. - * - * @param request the service request object containing meta information, service region, - * delivery time, and other necessary data for ASD creation. - * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. - * @return a String containing the fully crafted ASD message in XML format. Returns null if the - * message could not be constructed due to exceptions. - */ - public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { - - SDW sdw = request.getSdw(); - SNMP snmp = request.getSnmp(); - DdsAdvisorySituationData asd; - - byte sendToRsu = - request.getRsus() != null ? DdsAdvisorySituationData.RSU : DdsAdvisorySituationData.NONE; - byte distroType = (byte) (DdsAdvisorySituationData.IP | sendToRsu); - - String outputXml = null; - try { - if (null != snmp) { - - asd = new DdsAdvisorySituationData() - .setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null) - .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) - .setTimeToLive(sdw.getTtl()) - .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - } else { - asd = new DdsAdvisorySituationData() - .setAsdmDetails(sdw.getDeliverystart(), sdw.getDeliverystop(), distroType, null) - .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) - .setTimeToLive(sdw.getTtl()) - .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - } - - ObjectNode dataBodyObj = JsonUtils.newNode(); - ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); - ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); - admDetailsObj.remove("advisoryMessage"); - admDetailsObj.put("advisoryMessage", signedMsg); - - dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); - - OdeMsgPayload payload = new OdeAsdPayload(asd); - - ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); - payloadObj.set(AppContext.DATA_STRING, dataBodyObj); - - // This call will generate a new metadata object with a new streamId. This seems to be intentional - // as it will allow depositing this message onto the encoderInput topic without conflicts. - // We send messages for encoding twice, so we are trying to avoid conflicts by using a unique - // streamId - OdeMsgMetadata metadata = new OdeMsgMetadata(payload); - ObjectNode metaObject = JsonUtils.toObjectNode(metadata.toJson()); - - ObjectNode requestObj = JsonUtils.toObjectNode(JsonUtils.toJson(request, false)); - - requestObj.remove("tim"); - - metaObject.set("request", requestObj); - - ArrayNode encodings = buildEncodings(); - ObjectNode enc = - XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, encodings); - metaObject.set(AppContext.ENCODINGS_STRING, enc); - - ObjectNode message = JsonUtils.newNode(); - message.set(AppContext.METADATA_STRING, metaObject); - message.set(AppContext.PAYLOAD_STRING, payloadObj); - - ObjectNode root = JsonUtils.newNode(); - root.set(AppContext.ODE_ASN1_DATA, message); - - outputXml = XmlUtils.toXmlStatic(root); - - // remove the surrounding - outputXml = outputXml.replace("", ""); - outputXml = outputXml.replace("", ""); - - } catch (ParseException | JsonUtilsException | XmlUtilsException e) { - log.error("Parsing exception thrown while populating ASD structure: ", e); - } - - log.debug("Fully crafted ASD to be encoded: {}", outputXml); - - return outputXml; - } - - private ArrayNode buildEncodings() throws JsonUtilsException { - ArrayNode encodings = JsonUtils.newArrayNode(); - encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, - ADVISORY_SITUATION_DATA_STRING, - EncodingRule.UPER)); - return encodings; - } -} From 4f4737d1c1716224b298f4e8a4cd41151042a42f Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 11:33:55 -0700 Subject: [PATCH 053/128] refactor: isRunning now only checks for RUNNING in OdeTimJsonTopology This is to allow test code to await valid RUNNING state so that the tests complete as expected --- .../src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java index 6c6e2b1c0..e24f40e65 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java @@ -58,7 +58,7 @@ public void stop() { } public boolean isRunning() { - return streams.state().isRunningOrRebalancing(); + return streams.state().equals(KafkaStreams.State.RUNNING); } /** From 2a8b541be0e854f47b1d4a558f593d078d7c68ff Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 11:46:03 -0700 Subject: [PATCH 054/128] test: Asn1EncodedDataRouterTest await running state of odeTimJsonTopology If the k-stream in odeTimJsonTopology was in any state other than RUNNING, we would not be able to query the k-table, and the tests would fail. We wouldn't be able to produce to the filtered topic because the code would throw an exception and skip the message production --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 2 ++ .../jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 959deb608..ebaf33c00 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -615,6 +615,8 @@ private void depositToFilteredTopic(JSONObject metadataObj, String hexEncodedTim // Send the message w/ asn1 data to the TMC-filtered topic stringMsgProducer.send(jsonTopics.getTimTmcFiltered(), null, timJSON.toString()); + } else{ + log.debug("TIM not found in k-table. Skipping deposit to TMC-filtered topic."); } } catch (JSONException e) { log.error("Error while fetching recordGeneratedBy field: {}", e.getMessage()); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index fd0a93437..ce8fddcea 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -121,6 +121,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti mockSecServClient, sdxDepositorTopic ); + Awaitility.await().until(odeTimJsonTopology::isRunning); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() .createContainer(asn1CoderTopics.getEncoderOutput()); @@ -173,7 +174,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti } @Test - void processSNMPDepositOnly() throws IOException, InterruptedException { + void processSNMPDepositOnly() throws IOException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimCertExpiration(), @@ -207,6 +208,8 @@ public String signMessage(String message, int sigValidityOverride) { mockSecServClient, sdxDepositorTopic ); + Awaitility.await().until(odeTimJsonTopology::isRunning); + var container = kafkaConsumerConfig.kafkaListenerContainerFactory() .createContainer(asn1CoderTopics.getEncoderOutput()); container.setupMessageListener( @@ -332,6 +335,7 @@ public String signMessage(String message, int sigValidityOverride) { mockSecServClient, sdxDepositorTopic ); + Awaitility.await().until(odeTimJsonTopology::isRunning); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() .createContainer(asn1CoderTopics.getEncoderOutput()); From 975c3f22de9b11c05c974208fcf1110a4f294eec Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 11:49:28 -0700 Subject: [PATCH 055/128] chore: use one mock ISecurityServicesClient for the test suite --- .../services/asn1/Asn1EncodedDataRouter.java | 1 + .../asn1/Asn1EncodedDataRouterTest.java | 36 ++++++------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index ebaf33c00..da5047caf 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -618,6 +618,7 @@ private void depositToFilteredTopic(JSONObject metadataObj, String hexEncodedTim } else{ log.debug("TIM not found in k-table. Skipping deposit to TMC-filtered topic."); } + } catch (JSONException e) { log.error("Error while fetching recordGeneratedBy field: {}", e.getMessage()); } catch (Exception e) { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index ce8fddcea..09ff6c4ad 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; import java.io.IOException; import java.io.InputStream; @@ -95,6 +94,15 @@ class Asn1EncodedDataRouterTest { @Mock RsuDepositor mockRsuDepositor; + ISecurityServicesClient mockSecServClient = (message, sigValidityOverride) -> { + JSONObject json = new JSONObject(); + JSONObject result = new JSONObject(); + result.put("message-signed", "<%s>".formatted(message)); + result.put("message-expiry", "123124124124124141"); + json.put("result", result); + return json.toString(); + }; + EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Test @@ -109,7 +117,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); - var mockSecServClient = mock(ISecurityServicesClient.class); + securityServicesProperties.setIsSdwSigningEnabled(true); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( odeKafkaProperties, @@ -186,18 +194,6 @@ void processSNMPDepositOnly() throws IOException { securityServicesProperties.setIsSdwSigningEnabled(true); securityServicesProperties.setIsRsuSigningEnabled(true); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); - var mockSecServClient = new ISecurityServicesClient() { - @Override - public String signMessage(String message, int sigValidityOverride) { - JSONObject json = new JSONObject(); - JSONObject result = new JSONObject(); - result.put("message-signed", "<%s>".formatted(message)); - result.put("message-expiry", "123124124124124141"); - json.put("result", result); - return json.toString(); - } - }; - Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( odeKafkaProperties, asn1CoderTopics, @@ -311,18 +307,6 @@ void processEncodedTimUnsecured() throws IOException { EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); - var mockSecServClient = new ISecurityServicesClient() { - @Override - public String signMessage(String message, int sigValidityOverride) { - JSONObject json = new JSONObject(); - JSONObject result = new JSONObject(); - result.put("message-signed", "<%s>".formatted(message)); - result.put("message-expiry", "123124124124124141"); - json.put("result", result); - return json.toString(); - } - }; - securityServicesProperties.setIsSdwSigningEnabled(false); securityServicesProperties.setIsRsuSigningEnabled(false); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( From 1bb81ec3f34798d77d21e002c002df336a7ba394 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 14:15:30 -0700 Subject: [PATCH 056/128] chore: properly cleanup OdeTimJsonTopology on shutdown --- .../dot/its/jpo/ode/OdeTimJsonTopology.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java index e24f40e65..8e8936405 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java @@ -1,5 +1,6 @@ package us.dot.its.jpo.ode; +import java.time.Duration; import java.util.Properties; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.common.serialization.Serdes; @@ -52,9 +53,14 @@ public OdeTimJsonTopology(OdeKafkaProperties odeKafkaProps, String topic) { streams.start(); } + /** + * Stops the Ode Tim Json Topology by gracefully shutting down streams. + */ public void stop() { log.info("Stopping Ode Tim Json Topology"); - streams.close(); + streams.close(Duration.ofMillis(250)); + streams.cleanUp(); + log.info("Stopped Ode Tim Json Topology"); } public boolean isRunning() { @@ -80,9 +86,19 @@ public Topology buildTopology(String topic) { * * @param uuid The specified UUID to query for. **/ - public String query(String uuid) { - return (String) streams.store( + public String query(String uuid) throws InterruptedException { + var attempt = 0; + while (attempt++ < 10) { + if (isRunning()) { + return (String) streams.store( StoreQueryParameters.fromNameAndType("timjson-store", QueryableStoreTypes.keyValueStore())) - .get(uuid); + .get(uuid); + } else { + log.warn("TimJsonTopology is not running, waiting for it to start..."); + Thread.sleep(50); + } + } + log.error("TimJsonTopology failed to start after 10 attempts"); + throw new IllegalStateException("TimJsonTopology failed to start after 10 attempts"); } } From 0fc4bdfa03a7a084506018e7523ade491d9cf6eb Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 14:15:53 -0700 Subject: [PATCH 057/128] chore: set destroyMethod for odeTimJsonTopology bean --- .../main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java index 3c2576881..f0fd6770d 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java @@ -12,7 +12,7 @@ @Configuration public class KafkaStreamsConfig { - @Bean + @Bean(destroyMethod = "stop") public OdeTimJsonTopology odeTimJsonTopology( @Value("${ode.kafka.topics.json.tim}") String timTopic, OdeKafkaProperties odeKafkaProperties) { From b85bbcb51e6fbce261e8822f71899e0b90559e94 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 14:16:16 -0700 Subject: [PATCH 058/128] chore: more gracefully handle/prevent errors when adding topics to EmbeddedKafkaHolder --- .../dot/its/jpo/ode/test/utilities/EmbeddedKafkaHolder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/test/utilities/EmbeddedKafkaHolder.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/test/utilities/EmbeddedKafkaHolder.java index 95fcc5366..a41184ada 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/test/utilities/EmbeddedKafkaHolder.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/test/utilities/EmbeddedKafkaHolder.java @@ -58,7 +58,12 @@ public static EmbeddedKafkaBroker getEmbeddedKafka() { * @param topics one or more topic names to be added to the embedded Kafka broker */ public static void addTopics(String... topics) { + var existingTopics = embeddedKafka.getTopics(); for (String topic : topics) { + if (existingTopics.contains(topic)) { + log.debug("topic {} already exists in embedded kafka broker. Skipping creation", topic); + continue; + } NewTopic newTopic = new NewTopic(topic, 1, (short) 1); try { embeddedKafka.addTopics(newTopic); From 130dd7b81ca100dc9135d72d7610b475a79afe23 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 14:17:42 -0700 Subject: [PATCH 059/128] chore: add debug message when skipping depositing to filtered tim topic --- .../us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index da5047caf..383186df0 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -615,7 +615,7 @@ private void depositToFilteredTopic(JSONObject metadataObj, String hexEncodedTim // Send the message w/ asn1 data to the TMC-filtered topic stringMsgProducer.send(jsonTopics.getTimTmcFiltered(), null, timJSON.toString()); - } else{ + } else { log.debug("TIM not found in k-table. Skipping deposit to TMC-filtered topic."); } From f07f4937fea500e0daa536d26dd0522d93c56d4e Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 14:18:07 -0700 Subject: [PATCH 060/128] chore: use a test configuration bean to manage OdeTimJsonTopology streams lifecycle during tests --- .../asn1/Asn1EncodedDataRouterTest.java | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 09ff6c4ad..7a2da073a 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.UUID; +import lombok.extern.slf4j.Slf4j; import org.apache.kafka.common.serialization.StringDeserializer; import org.awaitility.Awaitility; import org.json.JSONObject; @@ -33,6 +34,8 @@ import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.listener.MessageListener; @@ -50,8 +53,10 @@ import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; +import us.dot.its.jpo.ode.services.asn1.Asn1EncodedDataRouterTest.TestKafkaStreamsConfig; import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; +@Slf4j @SpringBootTest( properties = { "ode.security-services.is-rsu-signing-enabled=false", @@ -66,6 +71,7 @@ KafkaProducerConfig.class, KafkaProperties.class, KafkaConsumerConfig.class, + TestKafkaStreamsConfig.class, Asn1CoderTopics.class, JsonTopics.class, SecurityServicesProperties.class, @@ -93,6 +99,8 @@ class Asn1EncodedDataRouterTest { KafkaConsumerConfig kafkaConsumerConfig; @Mock RsuDepositor mockRsuDepositor; + @Autowired + OdeTimJsonTopology odeTimJsonTopology; ISecurityServicesClient mockSecServClient = (message, sigValidityOverride) -> { JSONObject json = new JSONObject(); @@ -114,9 +122,6 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti sdxDepositorTopic }; EmbeddedKafkaHolder.addTopics(topicsForConsumption); - EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); - - var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); securityServicesProperties.setIsSdwSigningEnabled(true); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( @@ -129,7 +134,6 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti mockSecServClient, sdxDepositorTopic ); - Awaitility.await().until(odeTimJsonTopology::isRunning); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() .createContainer(asn1CoderTopics.getEncoderOutput()); @@ -139,6 +143,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti container.setBeanName("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered"); container.start(); ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); + log.debug("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered container started"); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( @@ -179,6 +184,8 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti } } assertTrue(foundValidRecord); + container.stop(); + log.debug("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered container stopped"); } @Test @@ -189,11 +196,9 @@ void processSNMPDepositOnly() throws IOException { jsonTopics.getTimTmcFiltered() }; EmbeddedKafkaHolder.addTopics(topicsForConsumption); - EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); securityServicesProperties.setIsSdwSigningEnabled(true); securityServicesProperties.setIsRsuSigningEnabled(true); - var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( odeKafkaProperties, asn1CoderTopics, @@ -204,7 +209,6 @@ void processSNMPDepositOnly() throws IOException { mockSecServClient, sdxDepositorTopic ); - Awaitility.await().until(odeTimJsonTopology::isRunning); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() .createContainer(asn1CoderTopics.getEncoderOutput()); @@ -214,6 +218,7 @@ void processSNMPDepositOnly() throws IOException { container.setBeanName("processSNMPDepositOnly"); container.start(); ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); + log.debug("processSNMPDepositOnly container started"); var classLoader = getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream( @@ -223,14 +228,16 @@ void processSNMPDepositOnly() throws IOException { // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return var streamId = UUID.randomUUID().toString(); odeJsonTim = odeJsonTim.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); - kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); + var topologySendFuture = kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); + Awaitility.await().until(topologySendFuture::isDone); inputStream = classLoader.getResourceAsStream( "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml"); assert inputStream != null; var input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); input = input.replaceAll(".*?", "" + streamId + ""); - kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); + var completableFuture = kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); + Awaitility.await().until(completableFuture::isDone); var consumerProps = KafkaTestUtils.consumerProps( "processSNMPDepositOnly", "false", embeddedKafka); @@ -295,6 +302,8 @@ void processSNMPDepositOnly() throws IOException { } } assertTrue(foundValidRecordInEncoderInput); + container.stop(); + log.debug("processSNMPDepositOnly container stopped"); } @Test @@ -304,9 +313,7 @@ void processEncodedTimUnsecured() throws IOException { jsonTopics.getTimTmcFiltered() }; EmbeddedKafkaHolder.addTopics(topicsForConsumption); - EmbeddedKafkaHolder.addTopics(asn1CoderTopics.getEncoderOutput(), jsonTopics.getTim()); - var odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, jsonTopics.getTim()); securityServicesProperties.setIsSdwSigningEnabled(false); securityServicesProperties.setIsRsuSigningEnabled(false); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( @@ -319,7 +326,6 @@ void processEncodedTimUnsecured() throws IOException { mockSecServClient, sdxDepositorTopic ); - Awaitility.await().until(odeTimJsonTopology::isRunning); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() .createContainer(asn1CoderTopics.getEncoderOutput()); @@ -383,5 +389,20 @@ void processEncodedTimUnsecured() throws IOException { } } assertTrue(foundValidRecord); + container.stop(); + log.debug("processEncodedTimUnsecured container stopped"); + } + + @TestConfiguration + static class TestKafkaStreamsConfig { + + @Bean + public OdeTimJsonTopology odeTimJsonTopology(OdeKafkaProperties odeKafkaProperties, + @Value("${ode.kafka.topics.json.tim}") String timTopic) { + EmbeddedKafkaHolder.addTopics(timTopic); + var topology = new OdeTimJsonTopology(odeKafkaProperties, timTopic); + Awaitility.await().until(topology::isRunning); + return topology; + } } } From aebe53565d12894c1354f0b311f99887dd2df9be Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 14:49:24 -0700 Subject: [PATCH 061/128] refactor: move TestKafkaStreamsConfig to own file for reusability --- .../jpo/ode/kafka/TestKafkaStreamsConfig.java | 21 +++++++++++++++++++ .../asn1/Asn1EncodedDataRouterTest.java | 13 ------------ 2 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/TestKafkaStreamsConfig.java diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/TestKafkaStreamsConfig.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/TestKafkaStreamsConfig.java new file mode 100644 index 000000000..3841a5264 --- /dev/null +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/TestKafkaStreamsConfig.java @@ -0,0 +1,21 @@ +package us.dot.its.jpo.ode.kafka; + +import org.awaitility.Awaitility; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import us.dot.its.jpo.ode.OdeTimJsonTopology; +import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; + +@TestConfiguration +public class TestKafkaStreamsConfig { + + @Bean + public OdeTimJsonTopology odeTimJsonTopology(OdeKafkaProperties odeKafkaProperties, + @Value("${ode.kafka.topics.json.tim}") String timTopic) { + EmbeddedKafkaHolder.addTopics(timTopic); + var topology = new OdeTimJsonTopology(odeKafkaProperties, timTopic); + Awaitility.await().until(topology::isRunning); + return topology; + } +} diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 7a2da073a..c50921ad4 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -392,17 +392,4 @@ void processEncodedTimUnsecured() throws IOException { container.stop(); log.debug("processEncodedTimUnsecured container stopped"); } - - @TestConfiguration - static class TestKafkaStreamsConfig { - - @Bean - public OdeTimJsonTopology odeTimJsonTopology(OdeKafkaProperties odeKafkaProperties, - @Value("${ode.kafka.topics.json.tim}") String timTopic) { - EmbeddedKafkaHolder.addTopics(timTopic); - var topology = new OdeTimJsonTopology(odeKafkaProperties, timTopic); - Awaitility.await().until(topology::isRunning); - return topology; - } - } } From 17eb75fda2099a801026cc682397605fe7968bb4 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 14:50:46 -0700 Subject: [PATCH 062/128] chore: reorganize Asn1EncodedDataRouterTest --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index c50921ad4..94b228a14 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -34,8 +34,6 @@ import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.listener.MessageListener; @@ -46,6 +44,7 @@ import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.kafka.KafkaConsumerConfig; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; +import us.dot.its.jpo.ode.kafka.TestKafkaStreamsConfig; import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; @@ -53,7 +52,6 @@ import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.services.asn1.Asn1EncodedDataRouterTest.TestKafkaStreamsConfig; import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; @Slf4j From b46b790ec3fcb6f4bad0a06c37bd2b6fdae84c76 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 15:50:29 -0700 Subject: [PATCH 063/128] test: delete OdeTimJsonTopologyTest.java as it is indirectly tested (better) during the Asn1EncodedDataRouterTest suite --- .../its/jpo/ode/OdeTimJsonTopologyTest.java | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/OdeTimJsonTopologyTest.java diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/OdeTimJsonTopologyTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/OdeTimJsonTopologyTest.java deleted file mode 100644 index 32017ef36..000000000 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/OdeTimJsonTopologyTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package us.dot.its.jpo.ode; - -import org.awaitility.Awaitility; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@ExtendWith(SpringExtension.class) -@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) -@EnableConfigurationProperties(value = OdeKafkaProperties.class) -class OdeTimJsonTopologyTest { - - @Autowired - private OdeKafkaProperties odeKafkaProperties; - - @Value("${ode.kafka.topics.json.tim}") - private String timTopic; - - private OdeTimJsonTopology odeTimJsonTopology; - - @BeforeEach - void setUp() throws SecurityException, IllegalArgumentException { - odeTimJsonTopology = new OdeTimJsonTopology(odeKafkaProperties, timTopic); - Awaitility.setDefaultTimeout(250, java.util.concurrent.TimeUnit.MILLISECONDS); - } - - @Test - void testStop() { - odeTimJsonTopology.stop(); - Awaitility.await().untilAsserted(() -> assertFalse(odeTimJsonTopology.isRunning())); - } - - @Test - void testIsRunning() { - Awaitility.await().untilAsserted(() -> assertTrue(odeTimJsonTopology.isRunning())); - } -} \ No newline at end of file From ac8771ee85ad5b0dc60895035ce03e021576c871 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 15:52:00 -0700 Subject: [PATCH 064/128] refactor: undo unnecessary retries in OdeTimJsonTopology.query --- .../dot/its/jpo/ode/OdeTimJsonTopology.java | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java index 8e8936405..99d16fe17 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java @@ -16,11 +16,9 @@ /** - * The OdeTimJsonTopology class sets up and manages a Kafka Streams topology - * for processing TIM (Traveler Information Message) JSON data from the - * OdeTimJson Kafka topic. - * This class creates a K-Table that houses TMC-generated TIMs which can be - * queried by UUID. + * The OdeTimJsonTopology class sets up and manages a Kafka Streams topology for processing TIM + * (Traveler Information Message) JSON data from the OdeTimJson Kafka topic. This class creates a + * K-Table that houses TMC-generated TIMs which can be queried by UUID. **/ @Slf4j public class OdeTimJsonTopology { @@ -28,12 +26,13 @@ public class OdeTimJsonTopology { private final KafkaStreams streams; /** - * Constructs an instance of OdeTimJsonTopology to set up and manage a Kafka Streams - * topology for processing TIM JSON data. + * Constructs an instance of OdeTimJsonTopology to set up and manage a Kafka Streams topology for + * processing TIM JSON data. * - * @param odeKafkaProps the properties containing Kafka configuration, including brokers - * and optional Confluent-specific configuration for authentication. - * @param topic the Kafka topic from which TIM JSON data is consumed to build the topology. + * @param odeKafkaProps the properties containing Kafka configuration, including brokers and + * optional Confluent-specific configuration for authentication. + * @param topic the Kafka topic from which TIM JSON data is consumed to build the + * topology. */ public OdeTimJsonTopology(OdeKafkaProperties odeKafkaProps, String topic) { @@ -70,8 +69,8 @@ public boolean isRunning() { /** * Builds a Kafka Streams topology for processing TIM JSON data. * - * @param topic the Kafka topic from which TIM JSON data is consumed and used - * to build the topology. + * @param topic the Kafka topic from which TIM JSON data is consumed and used to build the + * topology. * @return the constructed Kafka Streams topology. */ public Topology buildTopology(String topic) { @@ -86,19 +85,9 @@ public Topology buildTopology(String topic) { * * @param uuid The specified UUID to query for. **/ - public String query(String uuid) throws InterruptedException { - var attempt = 0; - while (attempt++ < 10) { - if (isRunning()) { - return (String) streams.store( + public String query(String uuid) { + return (String) streams.store( StoreQueryParameters.fromNameAndType("timjson-store", QueryableStoreTypes.keyValueStore())) - .get(uuid); - } else { - log.warn("TimJsonTopology is not running, waiting for it to start..."); - Thread.sleep(50); - } - } - log.error("TimJsonTopology failed to start after 10 attempts"); - throw new IllegalStateException("TimJsonTopology failed to start after 10 attempts"); + .get(uuid); } } From 6d433299d80bbed7d3e3ff259ace1e625877f593 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 16:02:27 -0700 Subject: [PATCH 065/128] refactor: swap KafkaTemplate for defaultStringMessageProducer in Asn1EncodedDataRouter --- .../services/asn1/Asn1EncodedDataRouter.java | 24 +++++++++---------- .../asn1/Asn1EncodedDataRouterTest.java | 9 ++++--- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 383186df0..ed76e11fa 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -29,6 +29,7 @@ import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Component; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; @@ -55,7 +56,6 @@ import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; -import us.dot.its.jpo.ode.wrapper.MessageProducer; /** * The Asn1EncodedDataRouter is responsible for routing encoded TIM messages that are consumed from @@ -69,6 +69,7 @@ public class Asn1EncodedDataRouter { private static final String MESSAGE_FRAME = "MessageFrame"; private static final String ERROR_ON_SDX_DEPOSIT = "Error on SDX deposit."; private static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; + private final KafkaTemplate kafkaTemplate; /** * Exception for Asn1EncodedDataRouter specific failures. @@ -87,7 +88,6 @@ public Asn1EncodedDataRouterException(String string) { private final String sdxDepositTopic; private final ISecurityServicesClient securityServicesClient; - private final MessageProducer stringMsgProducer; private final OdeTimJsonTopology odeTimJsonTopology; private final RsuDepositor rsuDepositor; private final boolean dataSigningEnabledSDW; @@ -109,7 +109,8 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, OdeTimJsonTopology odeTimJsonTopology, RsuDepositor rsuDepositor, ISecurityServicesClient securityServicesClient, - @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic) { + @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic, + KafkaTemplate kafkaTemplate) { super(); this.asn1CoderTopics = asn1CoderTopics; @@ -117,10 +118,7 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, this.sdxDepositTopic = sdxDepositTopic; this.securityServicesClient = securityServicesClient; - this.stringMsgProducer = MessageProducer.defaultStringMessageProducer( - odeKafkaProperties.getBrokers(), - odeKafkaProperties.getKafkaType(), - odeKafkaProperties.getDisabledTopics()); + this.kafkaTemplate = kafkaTemplate; this.rsuDepositor = rsuDepositor; this.dataSigningEnabledSDW = securityServicesProperties.getIsSdwSigningEnabled(); @@ -258,7 +256,7 @@ private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); deposit.put("encodedMsg", asdObj.getString(BYTES)); - stringMsgProducer.send(this.sdxDepositTopic, null, deposit.toString()); + kafkaTemplate.send(this.sdxDepositTopic, deposit.toString()); } catch (JSONException e) { log.error(ERROR_ON_SDX_DEPOSIT, e); } @@ -319,7 +317,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO log.debug("Publishing message for round 2 encoding!"); String xmlizedMessage = packageSignedTimIntoAsd(request, hexEncodedTim); - stringMsgProducer.send(asn1CoderTopics.getEncoderInput(), null, xmlizedMessage); + kafkaTemplate.send(asn1CoderTopics.getEncoderInput(), null, xmlizedMessage); } } @@ -355,7 +353,7 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum JSONObject deposit = new JSONObject(); deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); deposit.put("encodedMsg", asdBytes); - stringMsgProducer.send(this.sdxDepositTopic, null, deposit.toString()); + kafkaTemplate.send(this.sdxDepositTopic, null, deposit.toString()); log.info("SDX deposit successful."); } catch (Exception e) { String msg = ERROR_ON_SDX_DEPOSIT; @@ -439,7 +437,7 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu setRequiredExpiryDate(dateFormat, timStartDateTime, maxDurationTime, timWithExpiration); // publish to Tim expiration kafka - stringMsgProducer.send(jsonTopics.getTimCertExpiration(), null, + kafkaTemplate.send(jsonTopics.getTimCertExpiration(), null, timWithExpiration.toString()); return hexEncodedTim; @@ -459,7 +457,7 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu * delivery time, and other necessary data for ASD creation. * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. * @return a String containing the fully crafted ASD message in XML format. Returns null if the - * message could not be constructed due to exceptions. + * message could not be constructed due to exceptions. */ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { @@ -614,7 +612,7 @@ private void depositToFilteredTopic(JSONObject metadataObj, String hexEncodedTim timJSON.put("metadata", metadataJSON); // Send the message w/ asn1 data to the TMC-filtered topic - stringMsgProducer.send(jsonTopics.getTimTmcFiltered(), null, timJSON.toString()); + kafkaTemplate.send(jsonTopics.getTimTmcFiltered(), null, timJSON.toString()); } else { log.debug("TIM not found in k-table. Skipping deposit to TMC-filtered topic."); } diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 94b228a14..d98fbdaba 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -130,7 +130,8 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti odeTimJsonTopology, mockRsuDepositor, mockSecServClient, - sdxDepositorTopic + sdxDepositorTopic, + kafkaTemplate ); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() @@ -205,7 +206,8 @@ void processSNMPDepositOnly() throws IOException { odeTimJsonTopology, mockRsuDepositor, mockSecServClient, - sdxDepositorTopic + sdxDepositorTopic, + kafkaTemplate ); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() @@ -322,7 +324,8 @@ void processEncodedTimUnsecured() throws IOException { odeTimJsonTopology, mockRsuDepositor, mockSecServClient, - sdxDepositorTopic + sdxDepositorTopic, + kafkaTemplate ); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() From 072b74f0cd113ec857ee0fbc8f043c2bdaf8075f Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 16:06:28 -0700 Subject: [PATCH 066/128] style: add Javadocs and remove unused parameters from constructor in Asn1EncodedDataRouter --- .../services/asn1/Asn1EncodedDataRouter.java | 37 +++++++++++-------- .../asn1/Asn1EncodedDataRouterTest.java | 3 -- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index ed76e11fa..512527be7 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -34,7 +34,6 @@ import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.eventlog.EventLogger; -import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; @@ -97,13 +96,11 @@ public Asn1EncodedDataRouterException(String string) { * Instantiates the Asn1EncodedDataRouter to actively consume from Kafka and route the encoded TIM * messages to the SDX and RSUs. * - * @param odeKafkaProperties The Kafka properties used to consume and produce to Kafka * @param asn1CoderTopics The specified ASN1 Coder topics * @param jsonTopics The specified JSON topics to write to * @param securityServicesProperties The security services properties to use **/ - public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, - Asn1CoderTopics asn1CoderTopics, + public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, JsonTopics jsonTopics, SecurityServicesProperties securityServicesProperties, OdeTimJsonTopology odeTimJsonTopology, @@ -127,6 +124,26 @@ public Asn1EncodedDataRouter(OdeKafkaProperties odeKafkaProperties, this.odeTimJsonTopology = odeTimJsonTopology; } + /** + * Listens for messages from the specified Kafka topic and processes them. + * + *

Cases: + * - CASE 1: no SDW in metadata (SNMP deposit only) + * - sign MF + * - send to RSU + * - CASE 2: SDW in metadata but no ASD in body (send back for another encoding) + * - sign MF + * - send to RSU + * - craft ASD object + * - publish back to encoder stream + * - CASE 3: If SDW in metadata and ASD in body (double encoding complete) + * - send to SDX + * + *

+ * + * @param consumerRecord The Kafka consumer record containing the key and value of the consumed + * message. + */ @KafkaListener(topics = "${ode.kafka.topics.asn1EncoderOutput}") public void listen(ConsumerRecord consumerRecord) { try { @@ -222,18 +239,6 @@ public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) { AppContext.DATA_STRING); JSONObject metadataObj = consumedObj.getJSONObject(AppContext.METADATA_STRING); - // CASE 1: no SDW in metadata (SNMP deposit only) - // - sign MF - // - send to RSU - // CASE 2: SDW in metadata but no ASD in body (send back for another - // encoding) - // - sign MF - // - send to RSU - // - craft ASD object - // - publish back to encoder stream - // CASE 3: If SDW in metadata and ASD in body (double encoding complete) - // - send to SDX - if (!dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { processSNMPDepositOnly(request, consumedObj, dataObj, metadataObj); } else { diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index d98fbdaba..97e405990 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -123,7 +123,6 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti securityServicesProperties.setIsSdwSigningEnabled(true); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( - odeKafkaProperties, asn1CoderTopics, jsonTopics, securityServicesProperties, @@ -199,7 +198,6 @@ void processSNMPDepositOnly() throws IOException { securityServicesProperties.setIsSdwSigningEnabled(true); securityServicesProperties.setIsRsuSigningEnabled(true); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( - odeKafkaProperties, asn1CoderTopics, jsonTopics, securityServicesProperties, @@ -317,7 +315,6 @@ void processEncodedTimUnsecured() throws IOException { securityServicesProperties.setIsSdwSigningEnabled(false); securityServicesProperties.setIsRsuSigningEnabled(false); Asn1EncodedDataRouter encoderRouter = new Asn1EncodedDataRouter( - odeKafkaProperties, asn1CoderTopics, jsonTopics, securityServicesProperties, From 087a4e82ad2ca0b3be95b98dd6c6e4518039e24f Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 16:13:40 -0700 Subject: [PATCH 067/128] refactor: remove redundant EventLogger usage --- .../services/asn1/Asn1EncodedDataRouter.java | 115 +++++++----------- 1 file changed, 46 insertions(+), 69 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 512527be7..0db663b68 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -33,7 +33,6 @@ import org.springframework.stereotype.Component; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; -import us.dot.its.jpo.ode.eventlog.EventLogger; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; @@ -128,16 +127,10 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, * Listens for messages from the specified Kafka topic and processes them. * *

Cases: - * - CASE 1: no SDW in metadata (SNMP deposit only) - * - sign MF - * - send to RSU - * - CASE 2: SDW in metadata but no ASD in body (send back for another encoding) - * - sign MF - * - send to RSU - * - craft ASD object - * - publish back to encoder stream - * - CASE 3: If SDW in metadata and ASD in body (double encoding complete) - * - send to SDX + * - CASE 1: no SDW in metadata (SNMP deposit only) - sign MF - send to RSU - CASE 2: SDW in + * metadata but no ASD in body (send back for another encoding) - sign MF - send to RSU - craft + * ASD object - publish back to encoder stream - CASE 3: If SDW in metadata and ASD in body + * (double encoding complete) - send to SDX * *

* @@ -213,15 +206,11 @@ public ServiceRequest getServicerequest(JSONObject consumedObj) { TimTransmogrifier.REQUEST_STRING).toString(); log.debug("ServiceRequest: {}", sr); - // Convert JSON to POJO ServiceRequest serviceRequest = null; try { serviceRequest = (ServiceRequest) JsonUtils.fromJson(sr, ServiceRequest.class); - } catch (Exception e) { - String errMsg = "Malformed JSON."; - EventLogger.logger.error(errMsg, e); - log.error(errMsg, e); + log.error("Unable to convert JSON to ServiceRequest", e); } return serviceRequest; @@ -289,17 +278,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO hexEncodedTim = signTIMAndProduceToExpireTopic(hexEncodedTim, consumedObj); } else { // if header is present, strip it - if (isHeaderPresent(hexEncodedTim)) { - String header = hexEncodedTim.substring(0, hexEncodedTim.indexOf("001F") + 4); - log.debug("Stripping header from unsigned message: {}", header); - hexEncodedTim = stripHeader(hexEncodedTim); - mfObj.remove(BYTES); - mfObj.put(BYTES, hexEncodedTim); - dataObj.remove(MESSAGE_FRAME); - dataObj.put(MESSAGE_FRAME, mfObj); - consumedObj.remove(AppContext.PAYLOAD_STRING); - consumedObj.put(AppContext.PAYLOAD_STRING, dataObj); - } + hexEncodedTim = stripHeaderFromUnsignedMessage(consumedObj, dataObj, mfObj, hexEncodedTim); } if (null != request.getSnmp() && null != request.getRsus() && null != hexEncodedTim) { @@ -326,6 +305,22 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO } } + private String stripHeaderFromUnsignedMessage(JSONObject consumedObj, JSONObject dataObj, + JSONObject mfObj, String hexEncodedTim) { + if (isHeaderPresent(hexEncodedTim)) { + String header = hexEncodedTim.substring(0, hexEncodedTim.indexOf("001F") + 4); + log.debug("Stripping header from unsigned message: {}", header); + hexEncodedTim = stripHeader(hexEncodedTim); + mfObj.remove(BYTES); + mfObj.put(BYTES, hexEncodedTim); + dataObj.remove(MESSAGE_FRAME); + dataObj.put(MESSAGE_FRAME, mfObj); + consumedObj.remove(AppContext.PAYLOAD_STRING); + consumedObj.put(AppContext.PAYLOAD_STRING, dataObj); + } + return hexEncodedTim; + } + /** * Process the unsigned encoded TIM message. * @@ -352,25 +347,9 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum } if (null != asdObj) { - String asdBytes = asdObj.getString(BYTES); - - try { - JSONObject deposit = new JSONObject(); - deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); - deposit.put("encodedMsg", asdBytes); - kafkaTemplate.send(this.sdxDepositTopic, null, deposit.toString()); - log.info("SDX deposit successful."); - } catch (Exception e) { - String msg = ERROR_ON_SDX_DEPOSIT; - log.error(msg, e); - EventLogger.logger.error(msg, e); - } - - } else if (log.isErrorEnabled()) { - // Added to avoid Sonar's "Invoke method(s) only conditionally." code smell - String msg = "ASN.1 Encoder did not return ASD encoding {}"; - EventLogger.logger.error(msg, consumedObj); - log.error(msg, consumedObj); + depositToSdx(request, asdObj.getString(BYTES)); + } else { + log.error("ASN.1 Encoder did not return ASD encoding {}", consumedObj); } } @@ -378,34 +357,34 @@ public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consum JSONObject mfObj = dataObj.getJSONObject(MESSAGE_FRAME); String encodedTim = mfObj.getString(BYTES); - // Deposit encoded TIM to TMC-filtered topic if TMC-generated depositToFilteredTopic(metadataObj, encodedTim); - // if header is present, strip it - if (isHeaderPresent(encodedTim)) { - String header = encodedTim.substring(0, encodedTim.indexOf("001F") + 4); - log.debug("Stripping header from unsigned message: {}", header); - encodedTim = stripHeader(encodedTim); - mfObj.remove(BYTES); - mfObj.put(BYTES, encodedTim); - dataObj.remove(MESSAGE_FRAME); - dataObj.put(MESSAGE_FRAME, mfObj); - consumedObj.remove(AppContext.PAYLOAD_STRING); - consumedObj.put(AppContext.PAYLOAD_STRING, dataObj); - } - - log.debug("Encoded message - phase 2: {}", encodedTim); + var encodedTimWithoutHeader = + stripHeaderFromUnsignedMessage(consumedObj, dataObj, mfObj, encodedTim); + log.debug("Encoded message - phase 2: {}", encodedTimWithoutHeader); // only send message to rsu if snmp, rsus, and message frame fields are present if (null != request.getSnmp() && null != request.getRsus()) { - log.debug("Encoded message phase 3: {}", encodedTim); - sendToRsus(request, encodedTim); + log.debug("Encoded message phase 3: {}", encodedTimWithoutHeader); + sendToRsus(request, encodedTimWithoutHeader); } } log.info("TIM deposit response {}", responseList); } + private void depositToSdx(ServiceRequest request, String asdBytes) { + try { + JSONObject deposit = new JSONObject(); + deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); + deposit.put("encodedMsg", asdBytes); + kafkaTemplate.send(this.sdxDepositTopic, deposit.toString()); + log.info("SDX deposit successful."); + } catch (Exception e) { + log.error(ERROR_ON_SDX_DEPOSIT, e); + } + } + /** * Sign the encoded TIM message and write to Kafka with an expiration time. * @@ -421,9 +400,9 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu // get max duration time and convert from minutes to milliseconds (unsigned // integer valid 0 to 2^32-1 in units of // milliseconds.) from metadata - int maxDurationTime = Integer.valueOf(metadataObjs.get("maxDurationTime").toString()) + int maxDurationTime = Integer.parseInt(metadataObjs.get("maxDurationTime").toString()) * 60 * 1000; - String timpacketID = metadataObjs.getString("odePacketID"); + String packetId = metadataObjs.getString("odePacketID"); String timStartDateTime = metadataObjs.getString("odeTimStartDateTime"); log.debug("SENDING: {}", base64EncodedTim); String signedResponse = securityServicesClient.signMessage(base64EncodedTim, maxDurationTime); @@ -434,7 +413,7 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu .getString("message-signed"))); JSONObject timWithExpiration = new JSONObject(); - timWithExpiration.put("packetID", timpacketID); + timWithExpiration.put("packetID", packetId); timWithExpiration.put("startDateTime", timStartDateTime); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); @@ -442,11 +421,9 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu setRequiredExpiryDate(dateFormat, timStartDateTime, maxDurationTime, timWithExpiration); // publish to Tim expiration kafka - kafkaTemplate.send(jsonTopics.getTimCertExpiration(), null, - timWithExpiration.toString()); + kafkaTemplate.send(jsonTopics.getTimCertExpiration(), timWithExpiration.toString()); return hexEncodedTim; - } catch (JsonUtilsException e1) { log.error("Unable to parse signed message response ", e1); } From 5ec266d05076748fd2a4e9d0fe628f083fab92a4 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 16:17:17 -0700 Subject: [PATCH 068/128] refactor: remove dead code from Asn1EncodedDataRouter.listen --- .../services/asn1/Asn1EncodedDataRouter.java | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 0db663b68..e1907ffae 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -24,7 +24,6 @@ import java.util.HashMap; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; @@ -144,45 +143,10 @@ public void listen(ConsumerRecord consumerRecord) { JSONObject consumedObj = XmlUtils.toJSONObject(consumerRecord.value()).getJSONObject( OdeAsn1Data.class.getSimpleName()); - /* - * When receiving the 'rsus' in xml, since there is only one 'rsu' and - * there is no construct for array in xml, the rsus does not translate - * to an array of 1 element. The following workaround, resolves this - * issue. - */ JSONObject metadata = consumedObj.getJSONObject(AppContext.METADATA_STRING); if (metadata.has(TimTransmogrifier.REQUEST_STRING)) { - JSONObject request = metadata.getJSONObject(TimTransmogrifier.REQUEST_STRING); - if (request.has(TimTransmogrifier.RSUS_STRING)) { - Object rsus = request.get(TimTransmogrifier.RSUS_STRING); - if (rsus instanceof JSONObject) { - JSONObject rsusIn = (JSONObject) request.get(TimTransmogrifier.RSUS_STRING); - if (rsusIn.has(TimTransmogrifier.RSUS_STRING)) { - Object rsu = rsusIn.get(TimTransmogrifier.RSUS_STRING); - JSONArray rsusOut = new JSONArray(); - if (rsu instanceof JSONArray) { - log.debug("Multiple RSUs exist in the request: {}", request); - JSONArray rsusInArray = (JSONArray) rsu; - for (int i = 0; i < rsusInArray.length(); i++) { - rsusOut.put(rsusInArray.get(i)); - } - request.put(TimTransmogrifier.RSUS_STRING, rsusOut); - } else if (rsu instanceof JSONObject) { - log.debug("Single RSU exists in the request: {}", request); - rsusOut.put(rsu); - request.put(TimTransmogrifier.RSUS_STRING, rsusOut); - } else { - log.debug("No RSUs exist in the request: {}", request); - request.remove(TimTransmogrifier.RSUS_STRING); - } - } - } - } - - // Convert JSON to POJO ServiceRequest servicerequest = getServicerequest(consumedObj); - processEncodedTim(servicerequest, consumedObj); } else { throw new Asn1EncodedDataRouterException("Invalid or missing '" From 01f73333f92329326b972d23cef7387596d2c514 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 16:19:22 -0700 Subject: [PATCH 069/128] style: rename variables for better clarity in Asn1EncodedDataRouter --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 7 +++---- .../jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 6 +----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index e1907ffae..a28e94e2e 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -263,9 +263,9 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO // Case 2 only log.debug("Publishing message for round 2 encoding!"); - String xmlizedMessage = packageSignedTimIntoAsd(request, hexEncodedTim); + String asdPackagedTim = packageSignedTimIntoAsd(request, hexEncodedTim); - kafkaTemplate.send(asn1CoderTopics.getEncoderInput(), null, xmlizedMessage); + kafkaTemplate.send(asn1CoderTopics.getEncoderInput(), asdPackagedTim); } } @@ -418,7 +418,6 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) String outputXml = null; try { if (null != snmp) { - asd = new DdsAdvisorySituationData() .setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null) .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) @@ -558,7 +557,7 @@ private void depositToFilteredTopic(JSONObject metadataObj, String hexEncodedTim timJSON.put("metadata", metadataJSON); // Send the message w/ asn1 data to the TMC-filtered topic - kafkaTemplate.send(jsonTopics.getTimTmcFiltered(), null, timJSON.toString()); + kafkaTemplate.send(jsonTopics.getTimTmcFiltered(), timJSON.toString()); } else { log.debug("TIM not found in k-table. Skipping deposit to TMC-filtered topic."); } diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 97e405990..c3a6697c1 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -43,7 +43,6 @@ import org.springframework.test.annotation.DirtiesContext; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.kafka.KafkaConsumerConfig; -import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.TestKafkaStreamsConfig; import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; @@ -73,8 +72,7 @@ Asn1CoderTopics.class, JsonTopics.class, SecurityServicesProperties.class, - RsuProperties.class, - OdeKafkaProperties.class + RsuProperties.class } ) @EnableConfigurationProperties @@ -87,8 +85,6 @@ class Asn1EncodedDataRouterTest { JsonTopics jsonTopics; @Autowired SecurityServicesProperties securityServicesProperties; - @Autowired - OdeKafkaProperties odeKafkaProperties; @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositorTopic; @Autowired From 9c3a3a3c8049a454559c84aeb34ef578834ef15c Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 16:41:42 -0700 Subject: [PATCH 070/128] chore: replace OdeKafkaProperties loading in Asn1EncodedDataRouterTest to support KafkaProducerConfig loading --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index c3a6697c1..b0a3b570f 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -43,6 +43,7 @@ import org.springframework.test.annotation.DirtiesContext; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.kafka.KafkaConsumerConfig; +import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.TestKafkaStreamsConfig; import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; @@ -65,6 +66,7 @@ "ode.kafka.topics.sdx-depositor.input=topic.Asn1EncodedDataRouterTestSDXDepositor" }, classes = { + OdeKafkaProperties.class, KafkaProducerConfig.class, KafkaProperties.class, KafkaConsumerConfig.class, From 6e4ca6b6b6de13241a915f8004f9647c43c067bb Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 16:42:25 -0700 Subject: [PATCH 071/128] refactor: untangle message signing from message production --- .../services/asn1/Asn1EncodedDataRouter.java | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index a28e94e2e..b48a904fb 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -206,7 +206,7 @@ public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) { private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { log.debug("Signed message received. Depositing it to SDW."); - // We have a ASD with signed MessageFrame + // We have an ASD with signed MessageFrame // Case 3 JSONObject asdObj = dataObj.getJSONObject( ADVISORY_SITUATION_DATA_STRING); @@ -239,7 +239,8 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO // Case 1: SNMP-deposit if (dataSigningEnabledRSU && request.getRsus() != null) { - hexEncodedTim = signTIMAndProduceToExpireTopic(hexEncodedTim, consumedObj); + hexEncodedTim = signTimWithExpiration(hexEncodedTim, consumedObj); + kafkaTemplate.send(jsonTopics.getTimCertExpiration(), hexEncodedTim); } else { // if header is present, strip it hexEncodedTim = stripHeaderFromUnsignedMessage(consumedObj, dataObj, mfObj, hexEncodedTim); @@ -254,7 +255,8 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO // Case 2: SDX-deposit if (dataSigningEnabledSDW && request.getSdw() != null) { - hexEncodedTim = signTIMAndProduceToExpireTopic(hexEncodedTim, consumedObj); + var signedTimWithExpiration = signTimWithExpiration(hexEncodedTim, consumedObj); + kafkaTemplate.send(jsonTopics.getTimCertExpiration(), signedTimWithExpiration); } // Deposit encoded & signed TIM to TMC-filtered topic if TMC-generated @@ -350,16 +352,16 @@ private void depositToSdx(ServiceRequest request, String asdBytes) { } /** - * Sign the encoded TIM message and write to Kafka with an expiration time. + * Sign the encoded TIM message, add expiration times, and return the JSON string. * * @param encodedTIM The encoded TIM message to be signed * @param consumedObj The JSON object to be consumed - * @return The String representation of the encodedTim payload */ - public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consumedObj) { - log.debug("Sending message for signature! "); + private String signTimWithExpiration(String encodedTIM, JSONObject consumedObj) { + log.debug("Signing encoded TIM message..."); String base64EncodedTim = CodecUtils.toBase64( CodecUtils.fromHex(encodedTIM)); + JSONObject metadataObjs = consumedObj.getJSONObject(AppContext.METADATA_STRING); // get max duration time and convert from minutes to milliseconds (unsigned // integer valid 0 to 2^32-1 in units of @@ -370,28 +372,15 @@ public String signTIMAndProduceToExpireTopic(String encodedTIM, JSONObject consu String timStartDateTime = metadataObjs.getString("odeTimStartDateTime"); log.debug("SENDING: {}", base64EncodedTim); String signedResponse = securityServicesClient.signMessage(base64EncodedTim, maxDurationTime); - try { - final String hexEncodedTim = CodecUtils.toHex( - CodecUtils.fromBase64( - JsonUtils.toJSONObject(signedResponse).getJSONObject("result") - .getString("message-signed"))); - - JSONObject timWithExpiration = new JSONObject(); - timWithExpiration.put("packetID", packetId); - timWithExpiration.put("startDateTime", timStartDateTime); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - setExpiryDate(signedResponse, timWithExpiration, dateFormat); - setRequiredExpiryDate(dateFormat, timStartDateTime, maxDurationTime, timWithExpiration); + JSONObject timWithExpiration = new JSONObject(); + timWithExpiration.put("packetID", packetId); + timWithExpiration.put("startDateTime", timStartDateTime); - // publish to Tim expiration kafka - kafkaTemplate.send(jsonTopics.getTimCertExpiration(), timWithExpiration.toString()); - - return hexEncodedTim; - } catch (JsonUtilsException e1) { - log.error("Unable to parse signed message response ", e1); - } - return encodedTIM; + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + setExpiryDate(signedResponse, timWithExpiration, dateFormat); + setRequiredExpiryDate(dateFormat, timStartDateTime, maxDurationTime, timWithExpiration); + return timWithExpiration.toString(); } /** @@ -455,7 +444,8 @@ public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) ArrayNode encodings = buildEncodings(); ObjectNode enc = - XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, encodings); + XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, + encodings); metaObject.set(AppContext.ENCODINGS_STRING, enc); ObjectNode message = JsonUtils.newNode(); @@ -495,9 +485,9 @@ private void sendToRsus(ServiceRequest request, String encodedMsg) { private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String timStartDateTime, int maxDurationTime, JSONObject timWithExpiration) { try { - Date parsedtimTimeStamp = dateFormat.parse(timStartDateTime); + Date timTimestamp = dateFormat.parse(timStartDateTime); Date requiredExpirationDate = new Date(); - requiredExpirationDate.setTime(parsedtimTimeStamp.getTime() + maxDurationTime); + requiredExpirationDate.setTime(timTimestamp.getTime() + maxDurationTime); timWithExpiration.put("requiredExpirationDate", dateFormat.format(requiredExpirationDate)); } catch (Exception e) { log.error("Unable to parse requiredExpirationDate ", e); From b002fbb2a583055c8ecd7c5f29b7937698455c18 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 16:50:17 -0700 Subject: [PATCH 072/128] chore: add explicit imports to KafkaProducerConfig to ensure correct context loading --- .../us/dot/its/jpo/ode/kafka/producer/KafkaProducerConfig.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/producer/KafkaProducerConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/producer/KafkaProducerConfig.java index f70004df7..c27726463 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/producer/KafkaProducerConfig.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/producer/KafkaProducerConfig.java @@ -6,10 +6,12 @@ import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.kafka.annotation.EnableKafka; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; +import us.dot.its.jpo.ode.config.SerializationConfig; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.XMLOdeObjectSerializer; import us.dot.its.jpo.ode.model.OdeBsmData; @@ -30,6 +32,7 @@ */ @EnableKafka @Configuration +@Import(value = {KafkaProperties.class, OdeKafkaProperties.class, SerializationConfig.class}) public class KafkaProducerConfig { private final KafkaProperties kafkaProperties; From b19e3287ca938fa0965a66f20911ce461364217e Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 16:53:32 -0700 Subject: [PATCH 073/128] chore: add explicit load of OdeKafkaProperties in RawEncodedPSMJsonRouterTest --- .../ode/kafka/listeners/asn1/RawEncodedPSMJsonRouterTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/RawEncodedPSMJsonRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/RawEncodedPSMJsonRouterTest.java index f10f4604e..bb622a306 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/RawEncodedPSMJsonRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/RawEncodedPSMJsonRouterTest.java @@ -28,6 +28,7 @@ @SpringBootTest( classes = { + OdeKafkaProperties.class, KafkaProducerConfig.class, KafkaConsumerConfig.class, KafkaProperties.class, From da4f8577d9cc837fd29e4357e1f6bbdfd5200d69 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 17:02:34 -0700 Subject: [PATCH 074/128] chore: reorder constructor params for easier reading Asn1EncodedDataRouter --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 4 ++-- .../jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index b48a904fb..1fe750598 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -104,8 +104,8 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, OdeTimJsonTopology odeTimJsonTopology, RsuDepositor rsuDepositor, ISecurityServicesClient securityServicesClient, - @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic, - KafkaTemplate kafkaTemplate) { + KafkaTemplate kafkaTemplate, + @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic) { super(); this.asn1CoderTopics = asn1CoderTopics; diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index b0a3b570f..23b18d1d6 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -127,8 +127,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti odeTimJsonTopology, mockRsuDepositor, mockSecServClient, - sdxDepositorTopic, - kafkaTemplate + kafkaTemplate, sdxDepositorTopic ); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() @@ -202,8 +201,7 @@ void processSNMPDepositOnly() throws IOException { odeTimJsonTopology, mockRsuDepositor, mockSecServClient, - sdxDepositorTopic, - kafkaTemplate + kafkaTemplate, sdxDepositorTopic ); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() @@ -319,8 +317,7 @@ void processEncodedTimUnsecured() throws IOException { odeTimJsonTopology, mockRsuDepositor, mockSecServClient, - sdxDepositorTopic, - kafkaTemplate + kafkaTemplate, sdxDepositorTopic ); var container = kafkaConsumerConfig.kafkaListenerContainerFactory() From c7136d1615c326eb42ce3dc061f99738dc38bb2b Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 17:06:57 -0700 Subject: [PATCH 075/128] refactor: take all internal methods private in Asn1EncodedDataRouter --- .../services/asn1/Asn1EncodedDataRouter.java | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 1fe750598..b93430c0e 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -140,20 +140,34 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, public void listen(ConsumerRecord consumerRecord) { try { log.debug("Consumed: {}", consumerRecord.value()); - JSONObject consumedObj = XmlUtils.toJSONObject(consumerRecord.value()).getJSONObject( - OdeAsn1Data.class.getSimpleName()); + JSONObject consumedObj = XmlUtils.toJSONObject(consumerRecord.value()) + .getJSONObject(OdeAsn1Data.class.getSimpleName()); JSONObject metadata = consumedObj.getJSONObject(AppContext.METADATA_STRING); if (metadata.has(TimTransmogrifier.REQUEST_STRING)) { - ServiceRequest servicerequest = getServicerequest(consumedObj); - processEncodedTim(servicerequest, consumedObj); + ServiceRequest request = getServicerequest(consumedObj); + + JSONObject dataObj = consumedObj.getJSONObject(AppContext.PAYLOAD_STRING).getJSONObject( + AppContext.DATA_STRING); + JSONObject metadataObj = consumedObj.getJSONObject(AppContext.METADATA_STRING); + + if (!dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { + processSNMPDepositOnly(request, consumedObj, dataObj, metadataObj); + } else { + // We have encoded ASD. It could be either UNSECURED or secured. + if (dataSigningEnabledSDW && request.getSdw() != null) { + processSignedMessage(request, dataObj); + } else { + processEncodedTimUnsecured(request, consumedObj); + } + } } else { throw new Asn1EncodedDataRouterException("Invalid or missing '" + TimTransmogrifier.REQUEST_STRING + "' object in the encoder response"); } } catch (Exception e) { - log.error("Error in processing received message with key {} from ASN.1 Encoder module", + log.error("Error processing received message with key {} from ASN.1 Encoder module", consumerRecord.key(), e); } } @@ -165,7 +179,7 @@ public void listen(ConsumerRecord consumerRecord) { * @param consumedObj The object to retrieve the service request for * @return The service request */ - public ServiceRequest getServicerequest(JSONObject consumedObj) { + private ServiceRequest getServicerequest(JSONObject consumedObj) { String sr = consumedObj.getJSONObject(AppContext.METADATA_STRING).getJSONObject( TimTransmogrifier.REQUEST_STRING).toString(); log.debug("ServiceRequest: {}", sr); @@ -180,30 +194,6 @@ public ServiceRequest getServicerequest(JSONObject consumedObj) { return serviceRequest; } - /** - * Process the signed encoded TIM message. - * - * @param request The service request - * @param consumedObj The consumed JSON object - */ - public void processEncodedTim(ServiceRequest request, JSONObject consumedObj) { - - JSONObject dataObj = consumedObj.getJSONObject(AppContext.PAYLOAD_STRING).getJSONObject( - AppContext.DATA_STRING); - JSONObject metadataObj = consumedObj.getJSONObject(AppContext.METADATA_STRING); - - if (!dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { - processSNMPDepositOnly(request, consumedObj, dataObj, metadataObj); - } else { - // We have encoded ASD. It could be either UNSECURED or secured. - if (dataSigningEnabledSDW && request.getSdw() != null) { - processSignedMessage(request, dataObj); - } else { - processEncodedTimUnsecured(request, consumedObj); - } - } - } - private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { log.debug("Signed message received. Depositing it to SDW."); // We have an ASD with signed MessageFrame @@ -293,7 +283,7 @@ private String stripHeaderFromUnsignedMessage(JSONObject consumedObj, JSONObject * @param request The service request * @param consumedObj The consumed JSON object */ - public void processEncodedTimUnsecured(ServiceRequest request, JSONObject consumedObj) { + private void processEncodedTimUnsecured(ServiceRequest request, JSONObject consumedObj) { log.debug("Unsigned ASD received. Depositing it to SDW."); // We have ASD with UNSECURED MessageFrame // Send TIMs and record results @@ -394,7 +384,7 @@ private String signTimWithExpiration(String encodedTIM, JSONObject consumedObj) * @return a String containing the fully crafted ASD message in XML format. Returns null if the * message could not be constructed due to exceptions. */ - public String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { + private String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { SDW sdw = request.getSdw(); SNMP snmp = request.getSnmp(); From 6578b372b4c39030df65db506cc9dceaec8dda99 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 23 Dec 2024 17:11:05 -0700 Subject: [PATCH 076/128] refactor: move Asn1EncodedDataRouter to kafka.listeners.asn1 package --- .../listeners}/asn1/Asn1EncodedDataRouter.java | 2 +- .../listeners}/asn1/Asn1EncodedDataRouterTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/{services => kafka/listeners}/asn1/Asn1EncodedDataRouter.java (99%) rename jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/{services => kafka/listeners}/asn1/Asn1EncodedDataRouterTest.java (99%) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java similarity index 99% rename from jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java rename to jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java index b93430c0e..f604c563f 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java @@ -14,7 +14,7 @@ * the License. ******************************************************************************/ -package us.dot.its.jpo.ode.services.asn1; +package us.dot.its.jpo.ode.kafka.listeners.asn1; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java similarity index 99% rename from jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java rename to jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java index 23b18d1d6..58648d00b 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java @@ -14,7 +14,7 @@ * the License. ******************************************************************************/ -package us.dot.its.jpo.ode.services.asn1; +package us.dot.its.jpo.ode.kafka.listeners.asn1; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; From 60b89b9da373a4184de7796b61f06459099d47b5 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 10:21:18 -0700 Subject: [PATCH 077/128] chore: remove explicit imports. they cause errors with resolution --- .../us/dot/its/jpo/ode/kafka/producer/KafkaProducerConfig.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/producer/KafkaProducerConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/producer/KafkaProducerConfig.java index c27726463..f70004df7 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/producer/KafkaProducerConfig.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/producer/KafkaProducerConfig.java @@ -6,12 +6,10 @@ import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.kafka.annotation.EnableKafka; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; -import us.dot.its.jpo.ode.config.SerializationConfig; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.XMLOdeObjectSerializer; import us.dot.its.jpo.ode.model.OdeBsmData; @@ -32,7 +30,6 @@ */ @EnableKafka @Configuration -@Import(value = {KafkaProperties.class, OdeKafkaProperties.class, SerializationConfig.class}) public class KafkaProducerConfig { private final KafkaProperties kafkaProperties; From 84079e5766117e223720192c8e916aea97d39568 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 10:25:59 -0700 Subject: [PATCH 078/128] chore: correct topic in Asn1EncodedDataRouter.listen annotation --- .../jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java index f604c563f..a88723187 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java @@ -136,7 +136,7 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, * @param consumerRecord The Kafka consumer record containing the key and value of the consumed * message. */ - @KafkaListener(topics = "${ode.kafka.topics.asn1EncoderOutput}") + @KafkaListener(topics = "${ode.kafka.topics.asn1.encoder-output}") public void listen(ConsumerRecord consumerRecord) { try { log.debug("Consumed: {}", consumerRecord.value()); @@ -502,7 +502,7 @@ private static void setExpiryDate(String signedResponse, JSONObject timWithExpir * Checks if header is present in encoded message. */ private boolean isHeaderPresent(String encodedTim) { - return encodedTim.indexOf("001F") > 0; + return encodedTim.contains("001F"); } /** From 586f701ef49abe04c3db229be9493af30cf57913 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 10:28:19 -0700 Subject: [PATCH 079/128] chore: remove unused stop method from OdeTimJsonTopology. When using it as the `destroyMethod` in KafkaStreamsConfig it was causing bean lifecycle errors with every other managed bean. Not sure why this happens, but we should be safe to continue without a destroy method as it didn't exist prior to this changeset --- .../java/us/dot/its/jpo/ode/OdeTimJsonTopology.java | 11 ----------- .../us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java index 99d16fe17..4f89a7d23 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/OdeTimJsonTopology.java @@ -1,6 +1,5 @@ package us.dot.its.jpo.ode; -import java.time.Duration; import java.util.Properties; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.common.serialization.Serdes; @@ -52,16 +51,6 @@ public OdeTimJsonTopology(OdeKafkaProperties odeKafkaProps, String topic) { streams.start(); } - /** - * Stops the Ode Tim Json Topology by gracefully shutting down streams. - */ - public void stop() { - log.info("Stopping Ode Tim Json Topology"); - streams.close(Duration.ofMillis(250)); - streams.cleanUp(); - log.info("Stopped Ode Tim Json Topology"); - } - public boolean isRunning() { return streams.state().equals(KafkaStreams.State.RUNNING); } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java index f0fd6770d..3c2576881 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/KafkaStreamsConfig.java @@ -12,7 +12,7 @@ @Configuration public class KafkaStreamsConfig { - @Bean(destroyMethod = "stop") + @Bean public OdeTimJsonTopology odeTimJsonTopology( @Value("${ode.kafka.topics.json.tim}") String timTopic, OdeKafkaProperties odeKafkaProperties) { From f0f7030b9d679b9c4e60d880d04fd44e7ca0ee56 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 10:30:05 -0700 Subject: [PATCH 080/128] chore: include id in KafkaListener annotation on Asn1EncodedDataRouter.listen --- .../its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java index a88723187..85638aee2 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java @@ -136,7 +136,7 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, * @param consumerRecord The Kafka consumer record containing the key and value of the consumed * message. */ - @KafkaListener(topics = "${ode.kafka.topics.asn1.encoder-output}") + @KafkaListener(id = "Asn1EncodedDataRouter", topics = "${ode.kafka.topics.asn1.encoder-output}") public void listen(ConsumerRecord consumerRecord) { try { log.debug("Consumed: {}", consumerRecord.value()); From ee88d536be37eb4ebca2bae0141bff626360b669 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 12:12:34 -0700 Subject: [PATCH 081/128] refactor: extract streamId from message body to use as key when producing to topic.RawEncodedTIMJson This is needed so that the OdeTimJsonTopology can look up the object by the streamId and subsequently publish the JSON to topic.OdeTimJson --- .../listeners/asn1/Asn1DecodedDataRouter.java | 27 ++++++++++++------- .../us/dot/its/jpo/ode/udp/UdpHexDecoder.java | 4 +-- .../jpo/ode/udp/generic/GenericReceiver.java | 8 ++++-- .../dot/its/jpo/ode/udp/tim/TimReceiver.java | 5 ++-- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java index 72fa6d839..a5755263d 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java @@ -1,5 +1,7 @@ package us.dot.its.jpo.ode.kafka.listeners.asn1; +import com.fasterxml.jackson.core.JsonProcessingException; +import joptsimple.internal.Strings; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.json.JSONObject; @@ -68,7 +70,8 @@ public Asn1DecodedDataRouter(KafkaTemplate kafkaTemplate, id = "Asn1DecodedDataRouter", topics = "${ode.kafka.topics.asn1.decoder-output}" ) - public void listen(ConsumerRecord consumerRecord) throws XmlUtilsException { + public void listen(ConsumerRecord consumerRecord) + throws XmlUtilsException, JsonProcessingException { log.debug("Key: {} payload: {}", consumerRecord.key(), consumerRecord.value()); JSONObject consumed = XmlUtils.toJSONObject(consumerRecord.value()) @@ -80,16 +83,22 @@ public void listen(ConsumerRecord consumerRecord) throws XmlUtil .getInt("messageId") ); + var metadataJson = XmlUtils.toJSONObject(consumerRecord.value()) + .getJSONObject(OdeAsn1Data.class.getSimpleName()) + .getJSONObject(AppContext.METADATA_STRING); OdeLogMetadata.RecordType recordType = OdeLogMetadata.RecordType - .valueOf(XmlUtils.toJSONObject(consumerRecord.value()) - .getJSONObject(OdeAsn1Data.class.getSimpleName()) - .getJSONObject(AppContext.METADATA_STRING) - .getString("recordType") - ); + .valueOf(metadataJson.getString("recordType")); + + String serialId; + if (!Strings.isNullOrEmpty(consumerRecord.key()) && !"null".equalsIgnoreCase(consumerRecord.key())) { + serialId = consumerRecord.key(); + } else { + serialId = metadataJson.getJSONObject("serialId").getString("streamId"); + } switch (messageId) { case BasicSafetyMessage -> routeBSM(consumerRecord, recordType); - case TravelerInformation -> routeTIM(consumerRecord, recordType); + case TravelerInformation -> routeTIM(consumerRecord, serialId, recordType); case SPATMessage -> routeSPAT(consumerRecord, recordType); case MAPMessage -> routeMAP(consumerRecord, recordType); case SSMMessage -> routeSSM(consumerRecord, recordType); @@ -156,7 +165,7 @@ private void routeMAP(ConsumerRecord consumerRecord, RecordType kafkaTemplate.send(jsonTopics.getMap(), odeMapData); } - private void routeTIM(ConsumerRecord consumerRecord, RecordType recordType) + private void routeTIM(ConsumerRecord consumerRecord, String serialId, RecordType recordType) throws XmlUtilsException { String odeTimData = OdeTimDataCreatorHelper.createOdeTimDataFromDecoded(consumerRecord.value()).toString(); @@ -166,7 +175,7 @@ private void routeTIM(ConsumerRecord consumerRecord, RecordType default -> log.trace("Consumed TIM data with record type: {}", recordType); } // Send all TIMs also to OdeTimJson - kafkaTemplate.send(jsonTopics.getTim(), consumerRecord.key(), odeTimData); + kafkaTemplate.send(jsonTopics.getTim(), serialId, odeTimData); } private void routeBSM(ConsumerRecord consumerRecord, RecordType recordType) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/UdpHexDecoder.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/UdpHexDecoder.java index 4e26744fd..50fdbd0c6 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/UdpHexDecoder.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/UdpHexDecoder.java @@ -149,7 +149,7 @@ public static String buildJsonSpatFromPacket(DatagramPacket packet) * @return a JSON string representing the TIM message * @throws InvalidPayloadException if the payload extraction fails */ - public static String buildJsonTimFromPacket(DatagramPacket packet) + public static OdeAsn1Data buildJsonTimFromPacket(DatagramPacket packet) throws InvalidPayloadException { String senderIp = packet.getAddress().getHostAddress(); int senderPort = packet.getPort(); @@ -166,7 +166,7 @@ public static String buildJsonTimFromPacket(DatagramPacket packet) timMetadata.setRecordType(RecordType.timMsg); timMetadata.setRecordGeneratedBy(GeneratedBy.RSU); timMetadata.setSecurityResultCode(SecurityResultCode.success); - return JsonUtils.toJson(new OdeAsn1Data(timMetadata, timPayload), false); + return new OdeAsn1Data(timMetadata, timPayload); } /** diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java index f2810860d..72597d65f 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java @@ -11,6 +11,7 @@ import us.dot.its.jpo.ode.udp.UdpHexDecoder; import us.dot.its.jpo.ode.udp.controller.UDPReceiverProperties.ReceiverProperties; import us.dot.its.jpo.ode.uper.UperUtil; +import us.dot.its.jpo.ode.util.JsonUtils; /** * GenericReceiver is a class that listens for UDP packets and processes them based on the @@ -102,9 +103,12 @@ private void routeMessageByMessageType( } } case "TIM" -> { - String timJson = UdpHexDecoder.buildJsonTimFromPacket(packet); + var tim = UdpHexDecoder.buildJsonTimFromPacket(packet); + var timJson = JsonUtils.toJson(tim, false); if (timJson != null) { - publisher.send(rawEncodedJsonTopics.getTim(), timJson); + // We need to include the serialID as the key when publishing TIMs. Otherwise, the + // OdeTimJsonTopology won't work as intended. + publisher.send(rawEncodedJsonTopics.getTim(), tim.getMetadata().getSerialId().toString(), timJson); } } case "BSM" -> { diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/tim/TimReceiver.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/tim/TimReceiver.java index 26dcddebb..9cfff90ce 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/tim/TimReceiver.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/tim/TimReceiver.java @@ -52,9 +52,10 @@ public void run() { socket.receive(packet); if (packet.getLength() > 0) { - String timJson = UdpHexDecoder.buildJsonTimFromPacket(packet); + var tim = UdpHexDecoder.buildJsonTimFromPacket(packet); + var timJson = tim.toJson(); if (timJson != null) { - timPublisher.send(publishTopic, timJson); + timPublisher.send(publishTopic, tim.getMetadata().getSerialId().toString(), timJson); } } } catch (InvalidPayloadException e) { From 531ee0cd27c3ddbeb479055d9e3f7bd1b506352f Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 12:14:48 -0700 Subject: [PATCH 082/128] style: reformat files edited in previous commit --- .../kafka/listeners/asn1/Asn1DecodedDataRouter.java | 10 ++++++---- .../dot/its/jpo/ode/udp/generic/GenericReceiver.java | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java index a5755263d..4b251e093 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java @@ -32,8 +32,8 @@ * processing and forwarding it to different topics based on specific criteria. * *

This listener is specifically designed to handle decoded data produced by the asn1_codec. - * Upon receiving a payload, it transforms the payload and then determines the appropriate - * Kafka topic to forward the processed data.

+ * Upon receiving a payload, it transforms the payload and then determines the appropriate Kafka + * topic to forward the processed data.

* *

The class utilizes Spring Kafka's annotation-driven listener configuration, * allowing it to automatically consume messages from a configured Kafka topic.

@@ -90,7 +90,8 @@ public void listen(ConsumerRecord consumerRecord) .valueOf(metadataJson.getString("recordType")); String serialId; - if (!Strings.isNullOrEmpty(consumerRecord.key()) && !"null".equalsIgnoreCase(consumerRecord.key())) { + if (!Strings.isNullOrEmpty(consumerRecord.key()) + && !"null".equalsIgnoreCase(consumerRecord.key())) { serialId = consumerRecord.key(); } else { serialId = metadataJson.getJSONObject("serialId").getString("streamId"); @@ -165,7 +166,8 @@ private void routeMAP(ConsumerRecord consumerRecord, RecordType kafkaTemplate.send(jsonTopics.getMap(), odeMapData); } - private void routeTIM(ConsumerRecord consumerRecord, String serialId, RecordType recordType) + private void routeTIM(ConsumerRecord consumerRecord, String serialId, + RecordType recordType) throws XmlUtilsException { String odeTimData = OdeTimDataCreatorHelper.createOdeTimDataFromDecoded(consumerRecord.value()).toString(); diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java index 72597d65f..5f2486700 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java @@ -108,7 +108,8 @@ private void routeMessageByMessageType( if (timJson != null) { // We need to include the serialID as the key when publishing TIMs. Otherwise, the // OdeTimJsonTopology won't work as intended. - publisher.send(rawEncodedJsonTopics.getTim(), tim.getMetadata().getSerialId().toString(), timJson); + publisher.send(rawEncodedJsonTopics.getTim(), tim.getMetadata().getSerialId().toString(), + timJson); } } case "BSM" -> { From f250b8cd7355be9d4525382d9b111c5fa87313be Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 13:27:34 -0700 Subject: [PATCH 083/128] chore: add SerializationConfig to test class dependencies SerializationConfig was imported and added to the test configuration for Asn1EncodedDataRouterTest. --- .../jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java index 58648d00b..d5346ec2e 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java @@ -42,6 +42,7 @@ import org.springframework.kafka.test.utils.KafkaTestUtils; import org.springframework.test.annotation.DirtiesContext; import us.dot.its.jpo.ode.OdeTimJsonTopology; +import us.dot.its.jpo.ode.config.SerializationConfig; import us.dot.its.jpo.ode.kafka.KafkaConsumerConfig; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.TestKafkaStreamsConfig; @@ -68,6 +69,7 @@ classes = { OdeKafkaProperties.class, KafkaProducerConfig.class, + SerializationConfig.class, KafkaProperties.class, KafkaConsumerConfig.class, TestKafkaStreamsConfig.class, From c128e7e8803cebbf51b6638525eed53b9645c6f4 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 13:46:53 -0700 Subject: [PATCH 084/128] test: encapsulate duplicate setup logic in Asn1EncodedDataRouterTest Simplifies resource loading with reusable methods for reduced redundancy. Refactors test setup logic by extracting reusable utilities for creating consumers and listener containers. These changes enhance code readability, maintainability, and test efficiency. --- .../asn1/Asn1EncodedDataRouterTest.java | 194 ++++++++---------- 1 file changed, 88 insertions(+), 106 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java index d5346ec2e..d72e8705a 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java @@ -24,6 +24,7 @@ import java.nio.charset.StandardCharsets; import java.util.UUID; import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.common.serialization.StringDeserializer; import org.awaitility.Awaitility; import org.json.JSONObject; @@ -36,6 +37,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; import org.springframework.kafka.listener.MessageListener; import org.springframework.kafka.test.EmbeddedKafkaBroker; import org.springframework.kafka.test.utils.ContainerTestUtils; @@ -100,7 +102,9 @@ class Asn1EncodedDataRouterTest { @Autowired OdeTimJsonTopology odeTimJsonTopology; - ISecurityServicesClient mockSecServClient = (message, sigValidityOverride) -> { + private final EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); + + private final ISecurityServicesClient mockSecServClient = (message, sigValidityOverride) -> { JSONObject json = new JSONObject(); JSONObject result = new JSONObject(); result.put("message-signed", "<%s>".formatted(message)); @@ -109,11 +113,16 @@ class Asn1EncodedDataRouterTest { return json.toString(); }; - EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); + private static String loadResourceString(String name) + throws IOException { + InputStream inputStream; + inputStream = Asn1EncodedDataRouterTest.class.getClassLoader().getResourceAsStream(name); + assert inputStream != null; + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } @Test void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOException { - String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimTmcFiltered(), @@ -132,48 +141,31 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti kafkaTemplate, sdxDepositorTopic ); - var container = kafkaConsumerConfig.kafkaListenerContainerFactory() - .createContainer(asn1CoderTopics.getEncoderOutput()); - container.setupMessageListener( - (MessageListener) encoderRouter::listen + final var container = setupListenerContainer(encoderRouter, + "processSignedMessage_depositsToSdxTopicAndTimTmcFiltered" ); - container.setBeanName("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered"); - container.start(); - ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); - log.debug("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered container started"); - var classLoader = getClass().getClassLoader(); - InputStream inputStream = classLoader.getResourceAsStream( + var odeJsonTim = loadResourceString( "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json"); - assert inputStream != null; - var odeJsonTim = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return + + // send to tim topic so that the OdeTimJsonTopology k-table has the correct record to return var streamId = "266e6742-40fb-4c9e-a6b0-72ed2dddddfe"; kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); - inputStream = classLoader - .getResourceAsStream( - "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml"); - assert inputStream != null; - var input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + var input = loadResourceString( + "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml"); var completableFuture = kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); Awaitility.await().until(completableFuture::isDone); - var consumerProps = KafkaTestUtils.consumerProps( - "processSignedMessage_depositsToSdxTopicAndTimTmcFiltered-test", "false", embeddedKafka); - var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, - new StringDeserializer(), new StringDeserializer()); - var testConsumer = consumerFactory.createConsumer(); + var testConsumer = + createTestConsumer("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered"); embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topicsForConsumption); - inputStream = classLoader.getResourceAsStream( + var expected = loadResourceString( "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json"); - assert inputStream != null; - var expected = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); var records = KafkaTestUtils.getRecords(testConsumer); - var sdxDepositorRecord = records - .records(sdxDepositorTopic); + var sdxDepositorRecord = records.records(sdxDepositorTopic); var foundValidRecord = false; for (var consumerRecord : sdxDepositorRecord) { if (consumerRecord.value().equals(expected)) { @@ -206,32 +198,20 @@ void processSNMPDepositOnly() throws IOException { kafkaTemplate, sdxDepositorTopic ); - var container = kafkaConsumerConfig.kafkaListenerContainerFactory() - .createContainer(asn1CoderTopics.getEncoderOutput()); - container.setupMessageListener( - (MessageListener) encoderRouter::listen - ); - container.setBeanName("processSNMPDepositOnly"); - container.start(); - ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); - log.debug("processSNMPDepositOnly container started"); + final var container = setupListenerContainer(encoderRouter, "processSNMPDepositOnly"); - var classLoader = getClass().getClassLoader(); - InputStream inputStream = classLoader.getResourceAsStream( + var odeJsonTim = loadResourceString( "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json"); - assert inputStream != null; - var odeJsonTim = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return var streamId = UUID.randomUUID().toString(); odeJsonTim = odeJsonTim.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); var topologySendFuture = kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); Awaitility.await().until(topologySendFuture::isDone); - inputStream = classLoader.getResourceAsStream( + var input = loadResourceString( "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml"); - assert inputStream != null; - var input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - input = input.replaceAll(".*?", "" + streamId + ""); + input = replaceStreamId(input, streamId); + var completableFuture = kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); Awaitility.await().until(completableFuture::isDone); @@ -239,30 +219,22 @@ void processSNMPDepositOnly() throws IOException { "processSNMPDepositOnly", "false", embeddedKafka); var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); + var timCertConsumer = consumerFactory.createConsumer("timCertExpiration", "processSNMPDepositOnly"); embeddedKafka.consumeFromAnEmbeddedTopic(timCertConsumer, jsonTopics.getTimCertExpiration()); - var timTmcFilteredConsumer = - consumerFactory.createConsumer("timTmcFiltered", "processSNMPDepositOnly"); - embeddedKafka.consumeFromAnEmbeddedTopic(timTmcFilteredConsumer, - jsonTopics.getTimTmcFiltered()); - var encoderInputConsumer = - consumerFactory.createConsumer("encoderInput", "processSNMPDepositOnly"); - embeddedKafka.consumeFromAnEmbeddedTopic(encoderInputConsumer, - asn1CoderTopics.getEncoderInput()); - - inputStream = classLoader.getResourceAsStream( + var expectedTimCertExpiry = loadResourceString( "us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json"); - assert inputStream != null; - var expectedTimCertExpiry = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); var timCertExpirationRecord = KafkaTestUtils.getSingleRecord(timCertConsumer, jsonTopics.getTimCertExpiration()); assertEquals(expectedTimCertExpiry, timCertExpirationRecord.value()); - inputStream = classLoader.getResourceAsStream( + var timTmcFilteredConsumer = + consumerFactory.createConsumer("timTmcFiltered", "processSNMPDepositOnly"); + embeddedKafka.consumeFromAnEmbeddedTopic(timTmcFilteredConsumer, + jsonTopics.getTimTmcFiltered()); + var expectedTimTmcFiltered = loadResourceString( "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); - assert inputStream != null; - var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); var records = KafkaTestUtils.getRecords(timTmcFilteredConsumer); expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); @@ -275,33 +247,36 @@ void processSNMPDepositOnly() throws IOException { } assertTrue(foundValidRecord); - inputStream = classLoader.getResourceAsStream( + var encoderInputConsumer = + consumerFactory.createConsumer("encoderInput", "processSNMPDepositOnly"); + embeddedKafka.consumeFromAnEmbeddedTopic(encoderInputConsumer, + asn1CoderTopics.getEncoderInput()); + var expectedEncoderInput = loadResourceString( "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml"); - assert inputStream != null; - var expectedEncoderInput = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - var expectedEncoderInputWithStableFieldsOnly = expectedEncoderInput - .replaceAll(".*?", "") - .replaceAll(".*?", "") - .replaceAll(".*?", "") - .replaceAll(".*?", ""); + var expectedEncoderInputWithStableFieldsOnly = stripGeneratedFields(expectedEncoderInput); var foundValidRecordInEncoderInput = false; - var records1 = KafkaTestUtils.getRecords(encoderInputConsumer); - for (var consumerRecord : records1.records(asn1CoderTopics.getEncoderInput())) { - var encoderInputWithStableFieldsOnly = consumerRecord.value() - .replaceAll(".*?", "") - .replaceAll(".*?", "") - .replaceAll(".*?", "") - .replaceAll(".*?", ""); + var encoderInputRecords = KafkaTestUtils.getRecords(encoderInputConsumer); + for (var consumerRecord : encoderInputRecords.records(asn1CoderTopics.getEncoderInput())) { + var encoderInputWithStableFieldsOnly = stripGeneratedFields(consumerRecord.value()); if (expectedEncoderInputWithStableFieldsOnly.equals(encoderInputWithStableFieldsOnly)) { foundValidRecordInEncoderInput = true; break; } } assertTrue(foundValidRecordInEncoderInput); + container.stop(); log.debug("processSNMPDepositOnly container stopped"); } + private static String stripGeneratedFields(String expectedEncoderInput) { + return expectedEncoderInput + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", ""); + } + @Test void processEncodedTimUnsecured() throws IOException { String[] topicsForConsumption = { @@ -322,43 +297,25 @@ void processEncodedTimUnsecured() throws IOException { kafkaTemplate, sdxDepositorTopic ); - var container = kafkaConsumerConfig.kafkaListenerContainerFactory() - .createContainer(asn1CoderTopics.getEncoderOutput()); - container.setupMessageListener( - (MessageListener) encoderRouter::listen - ); - container.setBeanName("processEncodedTimUnsecured"); - container.start(); - ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); - - var classLoader = getClass().getClassLoader(); - InputStream inputStream = classLoader.getResourceAsStream( + final var container = setupListenerContainer(encoderRouter, "processEncodedTimUnsecured"); + var odeJsonTim = loadResourceString( "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json"); - assert inputStream != null; - var odeJsonTim = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return var streamId = UUID.randomUUID().toString(); odeJsonTim = odeJsonTim.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); - inputStream = classLoader.getResourceAsStream( + var input = loadResourceString( "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml"); - assert inputStream != null; - var input = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - input = input.replaceAll(".*?", "" + streamId + ""); + input = replaceStreamId(input, streamId); kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); - var consumerProps = KafkaTestUtils.consumerProps( - "processEncodedTimUnsecured", "false", embeddedKafka); - var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, - new StringDeserializer(), new StringDeserializer()); - var testConsumer = consumerFactory.createConsumer(); + var testConsumer = createTestConsumer("processEncodedTimUnsecured"); embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topicsForConsumption); - inputStream = classLoader.getResourceAsStream( + var expected = loadResourceString( "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json"); - assert inputStream != null; - var expected = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); var records = KafkaTestUtils.getRecords(testConsumer); var sdxDepositorRecord = records @@ -369,10 +326,8 @@ void processEncodedTimUnsecured() throws IOException { } } - inputStream = classLoader.getResourceAsStream( + var expectedTimTmcFiltered = loadResourceString( "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); - assert inputStream != null; - var expectedTimTmcFiltered = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); @@ -387,4 +342,31 @@ void processEncodedTimUnsecured() throws IOException { container.stop(); log.debug("processEncodedTimUnsecured container stopped"); } + + private static String replaceStreamId(String input, String streamId) { + return input.replaceAll(".*?", "" + streamId + ""); + } + + private ConcurrentMessageListenerContainer setupListenerContainer( + Asn1EncodedDataRouter encoderRouter, + String containerName) { + var container = kafkaConsumerConfig.kafkaListenerContainerFactory() + .createContainer(asn1CoderTopics.getEncoderOutput()); + container.setupMessageListener( + (MessageListener) encoderRouter::listen + ); + container.setBeanName(containerName); + container.start(); + ContainerTestUtils.waitForAssignment(container, embeddedKafka.getPartitionsPerTopic()); + log.debug("{} started", containerName); + return container; + } + + private Consumer createTestConsumer(String group) { + var consumerProps = KafkaTestUtils.consumerProps( + group, "false", embeddedKafka); + var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, + new StringDeserializer(), new StringDeserializer()); + return consumerFactory.createConsumer(); + } } From a09fbb177deb92cd43948065334e496678054bea Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 13:53:40 -0700 Subject: [PATCH 085/128] test: streamline resource loading Consolidated test resource lookup into a single method with a standardized path structure. Updated references in tests to use this method and moved resources under a more relevant package structure. This change improves maintainability and aligns resources with the corresponding test classes. --- .../asn1/Asn1EncodedDataRouterTest.java | 54 ++++++++----------- ...r-output-unsigned-tim-no-advisory-data.xml | 0 .../asn1/asn1-encoder-output-unsigned-tim.xml | 0 ...ected-asn1-encoded-router-sdx-deposit.json | 0 ...ected-asn1-encoded-router-snmp-deposit.xml | 0 ...expected-asn1-encoded-router-tim-json.json | 0 .../asn1/expected-tim-cert-expired.json | 0 .../asn1/expected-tim-tmc-filtered.json | 0 8 files changed, 22 insertions(+), 32 deletions(-) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{services => kafka/listeners}/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{services => kafka/listeners}/asn1/asn1-encoder-output-unsigned-tim.xml (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{services => kafka/listeners}/asn1/expected-asn1-encoded-router-sdx-deposit.json (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{services => kafka/listeners}/asn1/expected-asn1-encoded-router-snmp-deposit.xml (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{services => kafka/listeners}/asn1/expected-asn1-encoded-router-tim-json.json (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{services => kafka/listeners}/asn1/expected-tim-cert-expired.json (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{services => kafka/listeners}/asn1/expected-tim-tmc-filtered.json (100%) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java index d72e8705a..2059b96a3 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java @@ -113,14 +113,6 @@ class Asn1EncodedDataRouterTest { return json.toString(); }; - private static String loadResourceString(String name) - throws IOException { - InputStream inputStream; - inputStream = Asn1EncodedDataRouterTest.class.getClassLoader().getResourceAsStream(name); - assert inputStream != null; - return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - } - @Test void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOException { String[] topicsForConsumption = { @@ -145,15 +137,13 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti "processSignedMessage_depositsToSdxTopicAndTimTmcFiltered" ); - var odeJsonTim = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json"); + var odeJsonTim = loadResourceString("expected-asn1-encoded-router-tim-json.json"); // send to tim topic so that the OdeTimJsonTopology k-table has the correct record to return var streamId = "266e6742-40fb-4c9e-a6b0-72ed2dddddfe"; kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); - var input = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml"); + var input = loadResourceString("asn1-encoder-output-unsigned-tim.xml"); var completableFuture = kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); Awaitility.await().until(completableFuture::isDone); @@ -161,8 +151,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti createTestConsumer("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered"); embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topicsForConsumption); - var expected = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json"); + var expected = loadResourceString("expected-asn1-encoded-router-sdx-deposit.json"); var records = KafkaTestUtils.getRecords(testConsumer); var sdxDepositorRecord = records.records(sdxDepositorTopic); @@ -200,16 +189,14 @@ void processSNMPDepositOnly() throws IOException { final var container = setupListenerContainer(encoderRouter, "processSNMPDepositOnly"); - var odeJsonTim = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json"); + var odeJsonTim = loadResourceString("expected-asn1-encoded-router-tim-json.json"); // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return var streamId = UUID.randomUUID().toString(); odeJsonTim = odeJsonTim.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); var topologySendFuture = kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); Awaitility.await().until(topologySendFuture::isDone); - var input = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml"); + var input = loadResourceString("asn1-encoder-output-unsigned-tim-no-advisory-data.xml"); input = replaceStreamId(input, streamId); var completableFuture = kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); @@ -223,8 +210,7 @@ void processSNMPDepositOnly() throws IOException { var timCertConsumer = consumerFactory.createConsumer("timCertExpiration", "processSNMPDepositOnly"); embeddedKafka.consumeFromAnEmbeddedTopic(timCertConsumer, jsonTopics.getTimCertExpiration()); - var expectedTimCertExpiry = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json"); + var expectedTimCertExpiry = loadResourceString("expected-tim-cert-expired.json"); var timCertExpirationRecord = KafkaTestUtils.getSingleRecord(timCertConsumer, jsonTopics.getTimCertExpiration()); assertEquals(expectedTimCertExpiry, timCertExpirationRecord.value()); @@ -233,8 +219,7 @@ void processSNMPDepositOnly() throws IOException { consumerFactory.createConsumer("timTmcFiltered", "processSNMPDepositOnly"); embeddedKafka.consumeFromAnEmbeddedTopic(timTmcFilteredConsumer, jsonTopics.getTimTmcFiltered()); - var expectedTimTmcFiltered = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); + var expectedTimTmcFiltered = loadResourceString("expected-tim-tmc-filtered.json"); var records = KafkaTestUtils.getRecords(timTmcFilteredConsumer); expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); @@ -251,8 +236,7 @@ void processSNMPDepositOnly() throws IOException { consumerFactory.createConsumer("encoderInput", "processSNMPDepositOnly"); embeddedKafka.consumeFromAnEmbeddedTopic(encoderInputConsumer, asn1CoderTopics.getEncoderInput()); - var expectedEncoderInput = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml"); + var expectedEncoderInput = loadResourceString("expected-asn1-encoded-router-snmp-deposit.xml"); var expectedEncoderInputWithStableFieldsOnly = stripGeneratedFields(expectedEncoderInput); var foundValidRecordInEncoderInput = false; var encoderInputRecords = KafkaTestUtils.getRecords(encoderInputConsumer); @@ -298,24 +282,21 @@ void processEncodedTimUnsecured() throws IOException { ); final var container = setupListenerContainer(encoderRouter, "processEncodedTimUnsecured"); - var odeJsonTim = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json"); + var odeJsonTim = loadResourceString("expected-asn1-encoded-router-tim-json.json"); // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return var streamId = UUID.randomUUID().toString(); odeJsonTim = odeJsonTim.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); - var input = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml"); + var input = loadResourceString("asn1-encoder-output-unsigned-tim.xml"); input = replaceStreamId(input, streamId); kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); var testConsumer = createTestConsumer("processEncodedTimUnsecured"); embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topicsForConsumption); - var expected = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json"); + var expected = loadResourceString("expected-asn1-encoded-router-sdx-deposit.json"); var records = KafkaTestUtils.getRecords(testConsumer); var sdxDepositorRecord = records @@ -326,8 +307,7 @@ void processEncodedTimUnsecured() throws IOException { } } - var expectedTimTmcFiltered = loadResourceString( - "us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json"); + var expectedTimTmcFiltered = loadResourceString("expected-tim-tmc-filtered.json"); expectedTimTmcFiltered = expectedTimTmcFiltered.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); @@ -369,4 +349,14 @@ private Consumer createTestConsumer(String group) { new StringDeserializer(), new StringDeserializer()); return consumerFactory.createConsumer(); } + + private static String loadResourceString(String name) + throws IOException { + String resourcePackagePath = "us/dot/its/jpo/ode/kafka/listeners/asn1/"; + InputStream inputStream; + inputStream = Asn1EncodedDataRouterTest.class.getClassLoader() + .getResourceAsStream(resourcePackagePath + name); + assert inputStream != null; + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } } diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/asn1-encoder-output-unsigned-tim.xml similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/asn1-encoder-output-unsigned-tim.xml diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-sdx-deposit.json similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-sdx-deposit.json diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-snmp-deposit.xml similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-snmp-deposit.xml diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-tim-json.json similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-tim-json.json diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-tim-cert-expired.json similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-tim-cert-expired.json diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-tim-tmc-filtered.json similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-tim-tmc-filtered.json From 9c04c3d6e7094aca58f48474db7da94ff2b69992 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 14:12:12 -0700 Subject: [PATCH 086/128] chore: use correct key deserializer in Receiver tests where consuming tim messages with streamIds as keys --- .../dot/its/jpo/ode/udp/generic/GenericReceiverTest.java | 3 ++- .../java/us/dot/its/jpo/ode/udp/tim/TimReceiverTest.java | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/udp/generic/GenericReceiverTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/udp/generic/GenericReceiverTest.java index 854071a6b..2eba1a9db 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/udp/generic/GenericReceiverTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/udp/generic/GenericReceiverTest.java @@ -10,6 +10,7 @@ import java.time.ZoneOffset; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.apache.kafka.common.serialization.StringDeserializer; import org.json.JSONObject; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -93,7 +94,7 @@ void testRun() throws Exception { udpReceiverProperties.getGeneric().getReceiverPort()); var consumerProps = KafkaTestUtils.consumerProps("GenericReceiverTest", "true", embeddedKafka); - var cf = new DefaultKafkaConsumerFactory(consumerProps); + var cf = new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); var consumer = cf.createConsumer(); embeddedKafka.consumeFromEmbeddedTopics(consumer, topics); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/udp/tim/TimReceiverTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/udp/tim/TimReceiverTest.java index 602cba274..2b34dba04 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/udp/tim/TimReceiverTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/udp/tim/TimReceiverTest.java @@ -11,6 +11,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.common.serialization.StringDeserializer; import org.json.JSONObject; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -86,9 +87,9 @@ void testRun() throws Exception { var consumerProps = KafkaTestUtils.consumerProps( "TimReceiverTest", "true", embeddedKafka); - DefaultKafkaConsumerFactory cf = - new DefaultKafkaConsumerFactory<>(consumerProps); - Consumer consumer = cf.createConsumer(); + DefaultKafkaConsumerFactory cf = + new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); + Consumer consumer = cf.createConsumer(); embeddedKafka.consumeFromAnEmbeddedTopic(consumer, rawEncodedJsonTopics.getTim()); var singleRecord = KafkaTestUtils.getSingleRecord(consumer, rawEncodedJsonTopics.getTim()); From 853ce65fe9d6b006c38de2840966751f6959d58c Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 14:18:13 -0700 Subject: [PATCH 087/128] style: correct naming of streamId variable --- .../listeners/asn1/Asn1DecodedDataRouter.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java index 4b251e093..ba282aa4e 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1DecodedDataRouter.java @@ -89,17 +89,17 @@ public void listen(ConsumerRecord consumerRecord) OdeLogMetadata.RecordType recordType = OdeLogMetadata.RecordType .valueOf(metadataJson.getString("recordType")); - String serialId; - if (!Strings.isNullOrEmpty(consumerRecord.key()) - && !"null".equalsIgnoreCase(consumerRecord.key())) { - serialId = consumerRecord.key(); + String streamId; + if (Strings.isNullOrEmpty(consumerRecord.key()) + || "null".equalsIgnoreCase(consumerRecord.key())) { + streamId = metadataJson.getJSONObject("serialId").getString("streamId"); } else { - serialId = metadataJson.getJSONObject("serialId").getString("streamId"); + streamId = consumerRecord.key(); } switch (messageId) { case BasicSafetyMessage -> routeBSM(consumerRecord, recordType); - case TravelerInformation -> routeTIM(consumerRecord, serialId, recordType); + case TravelerInformation -> routeTIM(consumerRecord, streamId, recordType); case SPATMessage -> routeSPAT(consumerRecord, recordType); case MAPMessage -> routeMAP(consumerRecord, recordType); case SSMMessage -> routeSSM(consumerRecord, recordType); @@ -166,18 +166,18 @@ private void routeMAP(ConsumerRecord consumerRecord, RecordType kafkaTemplate.send(jsonTopics.getMap(), odeMapData); } - private void routeTIM(ConsumerRecord consumerRecord, String serialId, - RecordType recordType) - throws XmlUtilsException { + private void routeTIM(ConsumerRecord consumerRecord, + String streamId, + RecordType type) throws XmlUtilsException { String odeTimData = OdeTimDataCreatorHelper.createOdeTimDataFromDecoded(consumerRecord.value()).toString(); - switch (recordType) { + switch (type) { case dnMsg -> kafkaTemplate.send(jsonTopics.getDnMessage(), consumerRecord.key(), odeTimData); case rxMsg -> kafkaTemplate.send(jsonTopics.getRxTim(), consumerRecord.key(), odeTimData); - default -> log.trace("Consumed TIM data with record type: {}", recordType); + default -> log.trace("Consumed TIM data with record type: {}", type); } // Send all TIMs also to OdeTimJson - kafkaTemplate.send(jsonTopics.getTim(), serialId, odeTimData); + kafkaTemplate.send(jsonTopics.getTim(), streamId, odeTimData); } private void routeBSM(ConsumerRecord consumerRecord, RecordType recordType) From c7b893823b19b9aab435eb704e762922cd77ed0d Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 14:19:58 -0700 Subject: [PATCH 088/128] style: add missing Javadocs to RsuDepositorConfig --- .../dot/its/jpo/ode/rsu/RsuDepositorConfig.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java index d146fb156..bc7fe7d05 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositorConfig.java @@ -4,9 +4,26 @@ import org.springframework.context.annotation.Configuration; import us.dot.its.jpo.ode.security.SecurityServicesProperties; +/** + * Configuration class responsible for creating and configuring an instance of RsuDepositor. + * + *

This class sets up the necessary bean for RsuDepositor, ensuring its lifecycle + * management is handled by the Spring framework. The RsuDepositor is initialized + * with the required properties and started to begin its operations.

+ */ @Configuration public class RsuDepositorConfig { + /** + * Creates and configures an instance of {@link RsuDepositor} as a Spring-managed bean. + * The RsuDepositor is initialized with the provided properties and started immediately. + * + * @param rsuProperties the configuration properties for RSU, including such values + * as username, password, and the number of "store and repeat" message slots. + * @param securityServicesProps the configuration properties for security services, + * used to determine if RSU signing is enabled. + * @return an initialized and running instance of {@link RsuDepositor}. + */ @Bean public RsuDepositor rsuDepositor(RsuProperties rsuProperties, SecurityServicesProperties securityServicesProps) { var rsuDepositor = new RsuDepositor(rsuProperties, securityServicesProps.getIsRsuSigningEnabled()); From 3e414a13cad3c0e4f51f0d89cd96b8d18f811570 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 14:28:00 -0700 Subject: [PATCH 089/128] style: add missing Javadocs and reformat RsuDepositor and test --- .../us/dot/its/jpo/ode/rsu/RsuDepositor.java | 227 ++++++++++-------- .../dot/its/jpo/ode/rsu/RsuDepositorTest.java | 24 +- 2 files changed, 139 insertions(+), 112 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java index 68234bfe7..a56de9f4d 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/rsu/RsuDepositor.java @@ -1,4 +1,4 @@ -/******************************************************************************* +/*============================================================================= * Copyright 2020 572682 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -29,119 +29,146 @@ import us.dot.its.jpo.ode.snmp.SnmpSession; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; +/** + * The RsuDepositor class represents a thread that is responsible for depositing TIM messages to a + * set of RSUs (Roadside Units). The deposits are performed via SNMP and are processed in a + * continuous loop until the thread is stopped. + * + *

+ * This class manages a queue of deposit requests (RsuDepositorEntry) and processes them + * sequentially. Each entry in the queue consists of a service request and an encoded message to be + * deposited. It also handles response processing from the RSUs and logs the status of the + * deposits. + *

+ */ @Slf4j public class RsuDepositor extends Thread { - private final boolean dataSigningEnabled; - private volatile boolean running = true; - private final RsuProperties rsuProperties; - private final ArrayList depositorEntries = new ArrayList<>(); - - protected static class RsuDepositorEntry { - public RsuDepositorEntry(ServiceRequest request, String encodedMsg) { - this.request = request; - this.encodedMsg = encodedMsg; - } - - ServiceRequest request; - String encodedMsg; + private final boolean dataSigningEnabled; + private volatile boolean running = true; + private final RsuProperties rsuProperties; + private final ArrayList depositorEntries = new ArrayList<>(); + + /** + * Represents an entry to be deposited in the RSU (Road Side Unit) system. + * This class is used to encapsulate a service request and an associated encoded message. + * It acts as a data container for passing the required information to RSU-related operations. + */ + protected static class RsuDepositorEntry { + public RsuDepositorEntry(ServiceRequest request, String encodedMsg) { + this.request = request; + this.encodedMsg = encodedMsg; } - public RsuDepositor(RsuProperties rsuProperties, boolean isDataSigningEnabled) { - this.rsuProperties = rsuProperties; - this.dataSigningEnabled = isDataSigningEnabled; - } + ServiceRequest request; + String encodedMsg; + } - @Override - public void run() { - try { - while (running) { - RsuDepositorEntry[] entryList; - synchronized (depositorEntries) { - entryList = new RsuDepositorEntry[depositorEntries.size()]; - entryList = depositorEntries.toArray(entryList); - depositorEntries.clear(); - } - - for (RsuDepositorEntry entry : entryList) { - HashMap responseList = new HashMap<>(); - for (RSU curRsu : entry.request.getRsus()) { - - TimTransmogrifier.updateRsuCreds(curRsu, rsuProperties); - String httpResponseStatus; - try { - ResponseEvent
rsuResponse = SnmpSession.createAndSend(entry.request.getSnmp(), - curRsu, - entry.encodedMsg, - entry.request.getOde().getVerb(), - dataSigningEnabled); - httpResponseStatus = getResponseStatus(rsuResponse, curRsu); - } catch (IOException | ParseException e) { - String msg = "Exception caught in TIM RSU deposit loop."; - EventLogger.logger.error(msg, e); - log.error(msg, e); - httpResponseStatus = e.getClass().getName() + ": " + e.getMessage(); - } - - responseList.put(curRsu.getRsuTarget(), httpResponseStatus); - } - log.info("TIM deposit response {}", responseList); - } - Thread.sleep(100); - } - } catch (InterruptedException e) { - log.error("RsuDepositor thread interrupted", e); + public RsuDepositor(RsuProperties rsuProperties, boolean isDataSigningEnabled) { + this.rsuProperties = rsuProperties; + this.dataSigningEnabled = isDataSigningEnabled; + } + + @Override + public void run() { + try { + while (running) { + RsuDepositorEntry[] entryList; + synchronized (depositorEntries) { + entryList = new RsuDepositorEntry[depositorEntries.size()]; + entryList = depositorEntries.toArray(entryList); + depositorEntries.clear(); } - } - private String getResponseStatus(ResponseEvent
rsuResponse, RSU curRsu) { - String httpResponseStatus; + for (RsuDepositorEntry entry : entryList) { + HashMap responseList = new HashMap<>(); + for (RSU curRsu : entry.request.getRsus()) { + + TimTransmogrifier.updateRsuCreds(curRsu, rsuProperties); + String httpResponseStatus; + try { + ResponseEvent
rsuResponse = SnmpSession.createAndSend(entry.request.getSnmp(), + curRsu, + entry.encodedMsg, + entry.request.getOde().getVerb(), + dataSigningEnabled); + httpResponseStatus = getResponseStatus(rsuResponse, curRsu); + } catch (IOException | ParseException e) { + String msg = "Exception caught in TIM RSU deposit loop."; + EventLogger.logger.error(msg, e); + log.error(msg, e); + httpResponseStatus = e.getClass().getName() + ": " + e.getMessage(); + } - if (null == rsuResponse || null == rsuResponse.getResponse()) { - // Timeout - httpResponseStatus = "Timeout"; - log.error("Error on RSU SNMP deposit to {}: timed out.", curRsu.getRsuTarget()); - return httpResponseStatus; + responseList.put(curRsu.getRsuTarget(), httpResponseStatus); + } + log.info("TIM deposit response {}", responseList); } + Thread.sleep(100); + } + } catch (InterruptedException e) { + log.error("RsuDepositor thread interrupted", e); + } + } - RsuResponseCode responseCode = RsuResponseCode.fromInt(rsuResponse.getResponse().getErrorStatus()); - switch (responseCode) { - case SUCCESS: - httpResponseStatus = "Success"; - log.info("RSU SNMP deposit to {} successful.", curRsu.getRsuTarget()); - break; - case DUPLICATE_MESSAGE: - httpResponseStatus = "Message already exists at ".concat(Integer.toString(curRsu.getRsuIndex())); - Integer destIndex = curRsu.getRsuIndex(); - log.error("Error on RSU SNMP deposit to {}: message already exists at index {}.", curRsu.getRsuTarget(), - destIndex); - break; - case POSSIBLE_SNMP_PROTOCOL_MISMATCH: - httpResponseStatus = "Possible SNMP protocol mismatch, check RSU configuration"; - log.error("Error on RSU SNMP deposit to {}: Possible SNMP protocol mismatch, check RSU configuration.", - curRsu.getRsuTarget()); - break; - case null, default: - httpResponseStatus = "Error code " + rsuResponse.getResponse().getErrorStatus() + " " - + rsuResponse.getResponse().getErrorStatusText(); - // Misc error - log.error("Error on RSU SNMP deposit to {}: Error code '{}' '{}'", curRsu.getRsuTarget(), rsuResponse.getResponse().getErrorStatus(), rsuResponse.getResponse().getErrorStatusText() + "'"); - // Log the PDUs involved in the failed deposit - log.debug("PDUs involved in failed RSU SNMP deposit to {} => Request PDU: {} Response PDU: {}", curRsu.getRsuTarget(), rsuResponse.getRequest(), rsuResponse.getResponse()); - break; - } + private String getResponseStatus(ResponseEvent
rsuResponse, RSU curRsu) { + String httpResponseStatus; - return httpResponseStatus; + if (null == rsuResponse || null == rsuResponse.getResponse()) { + // Timeout + httpResponseStatus = "Timeout"; + log.error("Error on RSU SNMP deposit to {}: timed out.", curRsu.getRsuTarget()); + return httpResponseStatus; } - public void deposit(ServiceRequest request, String encodedMsg) { - synchronized (depositorEntries) { - depositorEntries.add(new RsuDepositorEntry(request, encodedMsg)); - } + RsuResponseCode responseCode = RsuResponseCode.fromInt(rsuResponse.getResponse().getErrorStatus()); + switch (responseCode) { + case SUCCESS: + httpResponseStatus = "Success"; + log.info("RSU SNMP deposit to {} successful.", curRsu.getRsuTarget()); + break; + case DUPLICATE_MESSAGE: + httpResponseStatus = "Message already exists at ".concat(Integer.toString(curRsu.getRsuIndex())); + Integer destIndex = curRsu.getRsuIndex(); + log.error("Error on RSU SNMP deposit to {}: message already exists at index {}.", curRsu.getRsuTarget(), + destIndex); + break; + case POSSIBLE_SNMP_PROTOCOL_MISMATCH: + httpResponseStatus = "Possible SNMP protocol mismatch, check RSU configuration"; + log.error("Error on RSU SNMP deposit to {}: Possible SNMP protocol mismatch, check RSU configuration.", + curRsu.getRsuTarget()); + break; + case null, default: + httpResponseStatus = "Error code " + rsuResponse.getResponse().getErrorStatus() + " " + + rsuResponse.getResponse().getErrorStatusText(); + // Misc error + log.error("Error on RSU SNMP deposit to {}: Error code '{}' '{}'", curRsu.getRsuTarget(), rsuResponse.getResponse().getErrorStatus(), + rsuResponse.getResponse().getErrorStatusText() + "'"); + // Log the PDUs involved in the failed deposit + log.debug("PDUs involved in failed RSU SNMP deposit to {} => Request PDU: {} Response PDU: {}", curRsu.getRsuTarget(), + rsuResponse.getRequest(), rsuResponse.getResponse()); + break; } - protected ArrayList getDepositorEntries() { - synchronized (depositorEntries) { - return depositorEntries; - } + return httpResponseStatus; + } + + /** + * Adds a new deposit entry to the queue for processing. + * The entry consists of a ServiceRequest object and the corresponding encoded message. + * + * @param request The ServiceRequest containing the details for the deposit, + * including RSUs to target and relevant parameters. + * @param encodedMsg The encoded message to be deposited in the RSUs. + */ + public void deposit(ServiceRequest request, String encodedMsg) { + synchronized (depositorEntries) { + depositorEntries.add(new RsuDepositorEntry(request, encodedMsg)); + } + } + + protected ArrayList getDepositorEntries() { + synchronized (depositorEntries) { + return depositorEntries; } + } } diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java index 7f3b99c46..41c9686b4 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/rsu/RsuDepositorTest.java @@ -1,4 +1,4 @@ -/******************************************************************************* +/*============================================================================= * Copyright 2020 572682 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -33,18 +33,18 @@ @EnableConfigurationProperties(value = {RsuProperties.class, SecurityServicesProperties.class}) class RsuDepositorTest { - @Autowired - RsuProperties rsuProperties; + @Autowired + RsuProperties rsuProperties; - @Autowired - SecurityServicesProperties securityServicesProperties; + @Autowired + SecurityServicesProperties securityServicesProperties; - @Test - void testDeposit() { - RsuDepositor testRsuDepositor = new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); - OdeTravelerInputData mockOdeTravelerInputData = new OdeTravelerInputData(); + @Test + void testDeposit() { + RsuDepositor testRsuDepositor = new RsuDepositor(rsuProperties, securityServicesProperties.getIsRsuSigningEnabled()); + OdeTravelerInputData mockOdeTravelerInputData = new OdeTravelerInputData(); - testRsuDepositor.deposit(mockOdeTravelerInputData.getRequest(), "message"); - assertEquals(1, testRsuDepositor.getDepositorEntries().size()); - } + testRsuDepositor.deposit(mockOdeTravelerInputData.getRequest(), "message"); + assertEquals(1, testRsuDepositor.getDepositorEntries().size()); + } } From 2c01cb3f634096a90564f7a1bfbed01c1d787631 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 14:31:06 -0700 Subject: [PATCH 090/128] style: add missing Javadocs to TestKafkaStreamsConfig --- .../its/jpo/ode/kafka/TestKafkaStreamsConfig.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/TestKafkaStreamsConfig.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/TestKafkaStreamsConfig.java index 3841a5264..6c6e5d8a1 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/TestKafkaStreamsConfig.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/TestKafkaStreamsConfig.java @@ -7,9 +7,24 @@ import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; +/** + * TestKafkaStreamsConfig is a test configuration class that provides a Kafka Streams topology + * for testing purposes. It utilizes an embedded Kafka broker to facilitate the testing of TIM + * (Traveler Information Message) JSON data processing. + */ @TestConfiguration public class TestKafkaStreamsConfig { + /** + * Creates and initializes an instance of OdeTimJsonTopology for processing TIM (Traveler Information Message) JSON data. + * This method adds the specified Kafka topic to the embedded Kafka broker, creates the topology, + * and ensures it is in a running state before returning. + * + * @param odeKafkaProperties the configuration properties for Kafka + * @param timTopic the name of the Kafka topic used for consuming TIM JSON data. + * + * @return the initialized instance of OdeTimJsonTopology. + */ @Bean public OdeTimJsonTopology odeTimJsonTopology(OdeKafkaProperties odeKafkaProperties, @Value("${ode.kafka.topics.json.tim}") String timTopic) { From 599989800685578426bd4b9fd5565bbdc32ca20e Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 14:41:47 -0700 Subject: [PATCH 091/128] refactor: use ObjectMapper bean in Asn1EncodedDataRouter where possible --- .../listeners/asn1/Asn1EncodedDataRouter.java | 109 ++++++++---------- .../asn1/Asn1EncodedDataRouterTest.java | 23 ++-- 2 files changed, 67 insertions(+), 65 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java index 85638aee2..8874a2ec9 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java @@ -16,6 +16,8 @@ package us.dot.its.jpo.ode.kafka.listeners.asn1; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.text.ParseException; @@ -84,6 +86,7 @@ public Asn1EncodedDataRouterException(String string) { private final JsonTopics jsonTopics; private final String sdxDepositTopic; private final ISecurityServicesClient securityServicesClient; + private final ObjectMapper mapper; private final OdeTimJsonTopology odeTimJsonTopology; private final RsuDepositor rsuDepositor; @@ -97,15 +100,17 @@ public Asn1EncodedDataRouterException(String string) { * @param asn1CoderTopics The specified ASN1 Coder topics * @param jsonTopics The specified JSON topics to write to * @param securityServicesProperties The security services properties to use + * @param mapper The ObjectMapper used for serialization/deserialization **/ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, - JsonTopics jsonTopics, - SecurityServicesProperties securityServicesProperties, - OdeTimJsonTopology odeTimJsonTopology, - RsuDepositor rsuDepositor, - ISecurityServicesClient securityServicesClient, - KafkaTemplate kafkaTemplate, - @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic) { + JsonTopics jsonTopics, + SecurityServicesProperties securityServicesProperties, + OdeTimJsonTopology odeTimJsonTopology, + RsuDepositor rsuDepositor, + ISecurityServicesClient securityServicesClient, + KafkaTemplate kafkaTemplate, + @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic, + ObjectMapper mapper) { super(); this.asn1CoderTopics = asn1CoderTopics; @@ -120,6 +125,7 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, this.dataSigningEnabledRSU = securityServicesProperties.getIsRsuSigningEnabled(); this.odeTimJsonTopology = odeTimJsonTopology; + this.mapper = mapper; } /** @@ -137,38 +143,31 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, * message. */ @KafkaListener(id = "Asn1EncodedDataRouter", topics = "${ode.kafka.topics.asn1.encoder-output}") - public void listen(ConsumerRecord consumerRecord) { - try { - log.debug("Consumed: {}", consumerRecord.value()); - JSONObject consumedObj = XmlUtils.toJSONObject(consumerRecord.value()) - .getJSONObject(OdeAsn1Data.class.getSimpleName()); - - JSONObject metadata = consumedObj.getJSONObject(AppContext.METADATA_STRING); - - if (metadata.has(TimTransmogrifier.REQUEST_STRING)) { - ServiceRequest request = getServicerequest(consumedObj); - - JSONObject dataObj = consumedObj.getJSONObject(AppContext.PAYLOAD_STRING).getJSONObject( - AppContext.DATA_STRING); - JSONObject metadataObj = consumedObj.getJSONObject(AppContext.METADATA_STRING); - - if (!dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { - processSNMPDepositOnly(request, consumedObj, dataObj, metadataObj); - } else { - // We have encoded ASD. It could be either UNSECURED or secured. - if (dataSigningEnabledSDW && request.getSdw() != null) { - processSignedMessage(request, dataObj); - } else { - processEncodedTimUnsecured(request, consumedObj); - } - } - } else { - throw new Asn1EncodedDataRouterException("Invalid or missing '" - + TimTransmogrifier.REQUEST_STRING + "' object in the encoder response"); - } - } catch (Exception e) { - log.error("Error processing received message with key {} from ASN.1 Encoder module", - consumerRecord.key(), e); + public void listen(ConsumerRecord consumerRecord) throws XmlUtilsException, JsonProcessingException { + log.debug("Consumed: {}", consumerRecord.value()); + JSONObject consumedObj = XmlUtils.toJSONObject(consumerRecord.value()) + .getJSONObject(OdeAsn1Data.class.getSimpleName()); + + JSONObject metadata = consumedObj.getJSONObject(AppContext.METADATA_STRING); + + if (!metadata.has(TimTransmogrifier.REQUEST_STRING)) { + log.error("Invalid or missing '{}' object in the encoder response", TimTransmogrifier.REQUEST_STRING); + return; + } + + ServiceRequest request = getServicerequest(consumedObj); + + JSONObject dataObj = consumedObj.getJSONObject(AppContext.PAYLOAD_STRING).getJSONObject( + AppContext.DATA_STRING); + JSONObject metadataObj = consumedObj.getJSONObject(AppContext.METADATA_STRING); + + if (!dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { + processSNMPDepositOnly(request, consumedObj, dataObj, metadataObj); + } else if (dataSigningEnabledSDW && request.getSdw() != null) { + // We have encoded ASD. It could be either UNSECURED or secured. + processSignedMessage(request, dataObj); + } else { + processEncodedTimUnsecured(request, consumedObj); } } @@ -177,21 +176,14 @@ public void listen(ConsumerRecord consumerRecord) { * Gets the service request based on the consumed JSONObject. * * @param consumedObj The object to retrieve the service request for + * * @return The service request */ - private ServiceRequest getServicerequest(JSONObject consumedObj) { - String sr = consumedObj.getJSONObject(AppContext.METADATA_STRING).getJSONObject( - TimTransmogrifier.REQUEST_STRING).toString(); - log.debug("ServiceRequest: {}", sr); - - ServiceRequest serviceRequest = null; - try { - serviceRequest = (ServiceRequest) JsonUtils.fromJson(sr, ServiceRequest.class); - } catch (Exception e) { - log.error("Unable to convert JSON to ServiceRequest", e); - } - - return serviceRequest; + private ServiceRequest getServicerequest(JSONObject consumedObj) throws JsonProcessingException { + String serviceRequestJson = consumedObj.getJSONObject(AppContext.METADATA_STRING) + .getJSONObject(TimTransmogrifier.REQUEST_STRING).toString(); + log.debug("ServiceRequest: {}", serviceRequestJson); + return mapper.readValue(serviceRequestJson, ServiceRequest.class); } private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { @@ -211,8 +203,8 @@ private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { } private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedObj, - JSONObject dataObj, - JSONObject metadataObj) { + JSONObject dataObj, + JSONObject metadataObj) { log.debug("Unsigned message received"); // We don't have ASD, therefore it must be just a MessageFrame that needs to be // signed @@ -262,7 +254,7 @@ private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedO } private String stripHeaderFromUnsignedMessage(JSONObject consumedObj, JSONObject dataObj, - JSONObject mfObj, String hexEncodedTim) { + JSONObject mfObj, String hexEncodedTim) { if (isHeaderPresent(hexEncodedTim)) { String header = hexEncodedTim.substring(0, hexEncodedTim.indexOf("001F") + 4); log.debug("Stripping header from unsigned message: {}", header); @@ -381,8 +373,9 @@ private String signTimWithExpiration(String encodedTIM, JSONObject consumedObj) * @param request the service request object containing meta information, service region, * delivery time, and other necessary data for ASD creation. * @param signedMsg the signed Traveler Information Message (TIM) to be included in the ASD. + * * @return a String containing the fully crafted ASD message in XML format. Returns null if the - * message could not be constructed due to exceptions. + * message could not be constructed due to exceptions. */ private String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { @@ -473,7 +466,7 @@ private void sendToRsus(ServiceRequest request, String encodedMsg) { } private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String timStartDateTime, - int maxDurationTime, JSONObject timWithExpiration) { + int maxDurationTime, JSONObject timWithExpiration) { try { Date timTimestamp = dateFormat.parse(timStartDateTime); Date requiredExpirationDate = new Date(); @@ -486,7 +479,7 @@ private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String ti } private static void setExpiryDate(String signedResponse, JSONObject timWithExpiration, - SimpleDateFormat dateFormat) { + SimpleDateFormat dateFormat) { try { JSONObject jsonResult = JsonUtils.toJSONObject(signedResponse).getJSONObject("result"); // messageExpiry uses unit of seconds diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java index 2059b96a3..72f884a0e 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -101,6 +102,8 @@ class Asn1EncodedDataRouterTest { RsuDepositor mockRsuDepositor; @Autowired OdeTimJsonTopology odeTimJsonTopology; + @Autowired + ObjectMapper objectMapper; private final EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @@ -130,8 +133,8 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti odeTimJsonTopology, mockRsuDepositor, mockSecServClient, - kafkaTemplate, sdxDepositorTopic - ); + kafkaTemplate, sdxDepositorTopic, + objectMapper); final var container = setupListenerContainer(encoderRouter, "processSignedMessage_depositsToSdxTopicAndTimTmcFiltered" @@ -184,8 +187,8 @@ void processSNMPDepositOnly() throws IOException { odeTimJsonTopology, mockRsuDepositor, mockSecServClient, - kafkaTemplate, sdxDepositorTopic - ); + kafkaTemplate, sdxDepositorTopic, + objectMapper); final var container = setupListenerContainer(encoderRouter, "processSNMPDepositOnly"); @@ -278,8 +281,8 @@ void processEncodedTimUnsecured() throws IOException { odeTimJsonTopology, mockRsuDepositor, mockSecServClient, - kafkaTemplate, sdxDepositorTopic - ); + kafkaTemplate, sdxDepositorTopic, + objectMapper); final var container = setupListenerContainer(encoderRouter, "processEncodedTimUnsecured"); var odeJsonTim = loadResourceString("expected-asn1-encoded-router-tim-json.json"); @@ -333,7 +336,13 @@ private ConcurrentMessageListenerContainer setupListenerContaine var container = kafkaConsumerConfig.kafkaListenerContainerFactory() .createContainer(asn1CoderTopics.getEncoderOutput()); container.setupMessageListener( - (MessageListener) encoderRouter::listen + (MessageListener) consumerRecord -> { + try { + encoderRouter.listen(consumerRecord); + } catch (Exception e) { + throw new RuntimeException(e); + } + } ); container.setBeanName(containerName); container.start(); From 82af676e2023c76c66d8d1d9c2e839cbfba8e7db Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 15:09:24 -0700 Subject: [PATCH 092/128] refactor: renamed and reorganized processUnsignedMessage path in Asn1EncodedDataRouter --- .../listeners/asn1/Asn1EncodedDataRouter.java | 132 +++++++----------- .../asn1/Asn1EncodedDataRouterTest.java | 14 +- 2 files changed, 56 insertions(+), 90 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java index 8874a2ec9..ade6d6d54 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java @@ -26,11 +26,11 @@ import java.util.HashMap; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; @@ -74,9 +74,6 @@ public class Asn1EncodedDataRouter { * Exception for Asn1EncodedDataRouter specific failures. */ public static class Asn1EncodedDataRouterException extends Exception { - - private static final long serialVersionUID = 1L; - public Asn1EncodedDataRouterException(String string) { super(string); } @@ -143,26 +140,29 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, * message. */ @KafkaListener(id = "Asn1EncodedDataRouter", topics = "${ode.kafka.topics.asn1.encoder-output}") - public void listen(ConsumerRecord consumerRecord) throws XmlUtilsException, JsonProcessingException { - log.debug("Consumed: {}", consumerRecord.value()); + public void listen(ConsumerRecord consumerRecord) + throws XmlUtilsException, JsonProcessingException, Asn1EncodedDataRouterException { JSONObject consumedObj = XmlUtils.toJSONObject(consumerRecord.value()) .getJSONObject(OdeAsn1Data.class.getSimpleName()); JSONObject metadata = consumedObj.getJSONObject(AppContext.METADATA_STRING); if (!metadata.has(TimTransmogrifier.REQUEST_STRING)) { - log.error("Invalid or missing '{}' object in the encoder response", TimTransmogrifier.REQUEST_STRING); - return; + throw new Asn1EncodedDataRouterException( + String.format("Invalid or missing '%s' object in the encoder response. Unable to process record with key '%s'", + TimTransmogrifier.REQUEST_STRING, + consumerRecord.key()) + ); } ServiceRequest request = getServicerequest(consumedObj); - JSONObject dataObj = consumedObj.getJSONObject(AppContext.PAYLOAD_STRING).getJSONObject( - AppContext.DATA_STRING); + JSONObject dataObj = consumedObj.getJSONObject(AppContext.PAYLOAD_STRING).getJSONObject(AppContext.DATA_STRING); JSONObject metadataObj = consumedObj.getJSONObject(AppContext.METADATA_STRING); if (!dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { - processSNMPDepositOnly(request, consumedObj, dataObj, metadataObj); + log.debug("Unsigned message received with key {}", consumerRecord.key()); + processUnsignedMessage(request, consumedObj, dataObj, metadataObj); } else if (dataSigningEnabledSDW && request.getSdw() != null) { // We have encoded ASD. It could be either UNSECURED or secured. processSignedMessage(request, dataObj); @@ -186,68 +186,41 @@ private ServiceRequest getServicerequest(JSONObject consumedObj) throws JsonProc return mapper.readValue(serviceRequestJson, ServiceRequest.class); } - private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { + private void processSignedMessage(@NonNull ServiceRequest request, @NonNull JSONObject dataObj) { log.debug("Signed message received. Depositing it to SDW."); - // We have an ASD with signed MessageFrame - // Case 3 - JSONObject asdObj = dataObj.getJSONObject( - ADVISORY_SITUATION_DATA_STRING); - try { - JSONObject deposit = new JSONObject(); - deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); - deposit.put("encodedMsg", asdObj.getString(BYTES)); - kafkaTemplate.send(this.sdxDepositTopic, deposit.toString()); - } catch (JSONException e) { - log.error(ERROR_ON_SDX_DEPOSIT, e); - } + // Case 3: We have an ASD with signed MessageFrame + JSONObject asdObj = dataObj.getJSONObject(ADVISORY_SITUATION_DATA_STRING); + + JSONObject deposit = new JSONObject(); + deposit.put("estimatedRemovalDate", request.getSdw().getEstimatedRemovalDate()); + deposit.put("encodedMsg", asdObj.getString(BYTES)); + kafkaTemplate.send(this.sdxDepositTopic, deposit.toString()); } - private void processSNMPDepositOnly(ServiceRequest request, JSONObject consumedObj, + private void processUnsignedMessage(ServiceRequest request, + JSONObject consumedObj, JSONObject dataObj, JSONObject metadataObj) { - log.debug("Unsigned message received"); - // We don't have ASD, therefore it must be just a MessageFrame that needs to be - // signed - // No support for unsecured MessageFrame only payload. - // Cases 1 & 2 - // Sign and send to RSUs - JSONObject mfObj = dataObj.getJSONObject(MESSAGE_FRAME); - - String hexEncodedTim = mfObj.getString(BYTES); - log.debug("Encoded message - phase 1: {}", hexEncodedTim); - // use ASN.1 library to decode the encoded tim returned from ASN.1; another - // class two blockers: decode the tim and decode the message-sign - + var hexEncodedTimBytes = mfObj.getString(BYTES); // Case 1: SNMP-deposit - if (dataSigningEnabledRSU && request.getRsus() != null) { - hexEncodedTim = signTimWithExpiration(hexEncodedTim, consumedObj); - kafkaTemplate.send(jsonTopics.getTimCertExpiration(), hexEncodedTim); - } else { - // if header is present, strip it - hexEncodedTim = stripHeaderFromUnsignedMessage(consumedObj, dataObj, mfObj, hexEncodedTim); - } - - if (null != request.getSnmp() && null != request.getRsus() && null != hexEncodedTim) { - log.info("Sending message to RSUs..."); - sendToRsus(request, hexEncodedTim); + if (dataSigningEnabledRSU && (request.getSdw() != null || request.getRsus() != null)) { + var signedTimWithExpiration = signTimWithExpiration(hexEncodedTimBytes, consumedObj); + kafkaTemplate.send(jsonTopics.getTimCertExpiration(), signedTimWithExpiration); } - hexEncodedTim = mfObj.getString(BYTES); + log.debug("Encoded message - phase 1: {}", hexEncodedTimBytes); + var encodedTimWithoutHeaders = stripHeaderFromUnsignedMessage(consumedObj, dataObj, mfObj, hexEncodedTimBytes); - // Case 2: SDX-deposit - if (dataSigningEnabledSDW && request.getSdw() != null) { - var signedTimWithExpiration = signTimWithExpiration(hexEncodedTim, consumedObj); - kafkaTemplate.send(jsonTopics.getTimCertExpiration(), signedTimWithExpiration); + if (null != request.getSnmp() && null != request.getRsus() && null != hexEncodedTimBytes) { + log.info("Sending message to RSUs..."); + sendToRsus(request, encodedTimWithoutHeaders); } - // Deposit encoded & signed TIM to TMC-filtered topic if TMC-generated - depositToFilteredTopic(metadataObj, hexEncodedTim); + depositToFilteredTopic(metadataObj, encodedTimWithoutHeaders); if (request.getSdw() != null) { - // Case 2 only - log.debug("Publishing message for round 2 encoding!"); - String asdPackagedTim = packageSignedTimIntoAsd(request, hexEncodedTim); + String asdPackagedTim = packageSignedTimIntoAsd(request, encodedTimWithoutHeaders); kafkaTemplate.send(asn1CoderTopics.getEncoderInput(), asdPackagedTim); } @@ -513,32 +486,25 @@ private String stripHeader(String encodedUnsignedTim) { } private void depositToFilteredTopic(JSONObject metadataObj, String hexEncodedTim) { - try { - String generatedBy = metadataObj.getString("recordGeneratedBy"); - String streamId = metadataObj.getJSONObject("serialId").getString("streamId"); - if (!generatedBy.equalsIgnoreCase("TMC")) { - log.debug("Not a TMC-generated TIM. Skipping deposit to TMC-filtered topic."); - return; - } + String generatedBy = metadataObj.getString("recordGeneratedBy"); + String streamId = metadataObj.getJSONObject("serialId").getString("streamId"); + if (!generatedBy.equalsIgnoreCase("TMC")) { + log.debug("Not a TMC-generated TIM. Skipping deposit to TMC-filtered topic."); + return; + } - String timString = odeTimJsonTopology.query(streamId); - if (timString != null) { - // Set ASN1 data in TIM metadata - JSONObject timJSON = new JSONObject(timString); - JSONObject metadataJSON = timJSON.getJSONObject("metadata"); - metadataJSON.put("asn1", hexEncodedTim); - timJSON.put("metadata", metadataJSON); + String timString = odeTimJsonTopology.query(streamId); + if (timString != null) { + // Set ASN1 data in TIM metadata + JSONObject timJSON = new JSONObject(timString); + JSONObject metadataJSON = timJSON.getJSONObject("metadata"); + metadataJSON.put("asn1", hexEncodedTim); + timJSON.put("metadata", metadataJSON); - // Send the message w/ asn1 data to the TMC-filtered topic - kafkaTemplate.send(jsonTopics.getTimTmcFiltered(), timJSON.toString()); - } else { - log.debug("TIM not found in k-table. Skipping deposit to TMC-filtered topic."); - } - - } catch (JSONException e) { - log.error("Error while fetching recordGeneratedBy field: {}", e.getMessage()); - } catch (Exception e) { - log.error("Error while updating TIM: {}", e.getMessage()); + // Send the message w/ asn1 data to the TMC-filtered topic + kafkaTemplate.send(jsonTopics.getTimTmcFiltered(), timJSON.toString()); + } else { + log.debug("TIM not found in k-table. Skipping deposit to TMC-filtered topic."); } } } diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java index 72f884a0e..a1f2aa8af 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java @@ -170,7 +170,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti } @Test - void processSNMPDepositOnly() throws IOException { + void processUnsignedMessage() throws IOException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimCertExpiration(), @@ -190,7 +190,7 @@ void processSNMPDepositOnly() throws IOException { kafkaTemplate, sdxDepositorTopic, objectMapper); - final var container = setupListenerContainer(encoderRouter, "processSNMPDepositOnly"); + final var container = setupListenerContainer(encoderRouter, "processUnsignedMessage"); var odeJsonTim = loadResourceString("expected-asn1-encoded-router-tim-json.json"); // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return @@ -206,12 +206,12 @@ void processSNMPDepositOnly() throws IOException { Awaitility.await().until(completableFuture::isDone); var consumerProps = KafkaTestUtils.consumerProps( - "processSNMPDepositOnly", "false", embeddedKafka); + "processUnsignedMessage", "false", embeddedKafka); var consumerFactory = new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); var timCertConsumer = - consumerFactory.createConsumer("timCertExpiration", "processSNMPDepositOnly"); + consumerFactory.createConsumer("timCertExpiration", "processUnsignedMessage"); embeddedKafka.consumeFromAnEmbeddedTopic(timCertConsumer, jsonTopics.getTimCertExpiration()); var expectedTimCertExpiry = loadResourceString("expected-tim-cert-expired.json"); var timCertExpirationRecord = @@ -219,7 +219,7 @@ void processSNMPDepositOnly() throws IOException { assertEquals(expectedTimCertExpiry, timCertExpirationRecord.value()); var timTmcFilteredConsumer = - consumerFactory.createConsumer("timTmcFiltered", "processSNMPDepositOnly"); + consumerFactory.createConsumer("timTmcFiltered", "processUnsignedMessage"); embeddedKafka.consumeFromAnEmbeddedTopic(timTmcFilteredConsumer, jsonTopics.getTimTmcFiltered()); var expectedTimTmcFiltered = loadResourceString("expected-tim-tmc-filtered.json"); @@ -236,7 +236,7 @@ void processSNMPDepositOnly() throws IOException { assertTrue(foundValidRecord); var encoderInputConsumer = - consumerFactory.createConsumer("encoderInput", "processSNMPDepositOnly"); + consumerFactory.createConsumer("encoderInput", "processUnsignedMessage"); embeddedKafka.consumeFromAnEmbeddedTopic(encoderInputConsumer, asn1CoderTopics.getEncoderInput()); var expectedEncoderInput = loadResourceString("expected-asn1-encoded-router-snmp-deposit.xml"); @@ -253,7 +253,7 @@ void processSNMPDepositOnly() throws IOException { assertTrue(foundValidRecordInEncoderInput); container.stop(); - log.debug("processSNMPDepositOnly container stopped"); + log.debug("processUnsignedMessage container stopped"); } private static String stripGeneratedFields(String expectedEncoderInput) { From 735dfa73b376088cf4e724880ca59d18fdee8a18 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 15:25:37 -0700 Subject: [PATCH 093/128] refactor: deduplicate extraction of metadata and payload JSONObjects --- .../listeners/asn1/Asn1EncodedDataRouter.java | 120 +++++------------- .../asn1/Asn1EncodedDataRouterTest.java | 16 +-- 2 files changed, 40 insertions(+), 96 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java index ade6d6d54..536f0b7bb 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java @@ -23,14 +23,12 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.HashMap; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Value; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; @@ -157,17 +155,15 @@ public void listen(ConsumerRecord consumerRecord) ServiceRequest request = getServicerequest(consumedObj); - JSONObject dataObj = consumedObj.getJSONObject(AppContext.PAYLOAD_STRING).getJSONObject(AppContext.DATA_STRING); - JSONObject metadataObj = consumedObj.getJSONObject(AppContext.METADATA_STRING); + JSONObject payloadData = consumedObj.getJSONObject(AppContext.PAYLOAD_STRING).getJSONObject(AppContext.DATA_STRING); + JSONObject metadataJson = consumedObj.getJSONObject(AppContext.METADATA_STRING); - if (!dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { - log.debug("Unsigned message received with key {}", consumerRecord.key()); - processUnsignedMessage(request, consumedObj, dataObj, metadataObj); + if (!payloadData.has(ADVISORY_SITUATION_DATA_STRING)) { + processUnsignedMessage(request, metadataJson, payloadData); } else if (dataSigningEnabledSDW && request.getSdw() != null) { - // We have encoded ASD. It could be either UNSECURED or secured. - processSignedMessage(request, dataObj); + processSignedMessage(request, payloadData); } else { - processEncodedTimUnsecured(request, consumedObj); + processEncodedTimUnsigned(request, metadataJson, payloadData); } } @@ -186,8 +182,8 @@ private ServiceRequest getServicerequest(JSONObject consumedObj) throws JsonProc return mapper.readValue(serviceRequestJson, ServiceRequest.class); } - private void processSignedMessage(@NonNull ServiceRequest request, @NonNull JSONObject dataObj) { - log.debug("Signed message received. Depositing it to SDW."); + private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { + // Case 3: We have an ASD with signed MessageFrame JSONObject asdObj = dataObj.getJSONObject(ADVISORY_SITUATION_DATA_STRING); @@ -198,100 +194,61 @@ private void processSignedMessage(@NonNull ServiceRequest request, @NonNull JSON } private void processUnsignedMessage(ServiceRequest request, - JSONObject consumedObj, - JSONObject dataObj, - JSONObject metadataObj) { - JSONObject mfObj = dataObj.getJSONObject(MESSAGE_FRAME); - var hexEncodedTimBytes = mfObj.getString(BYTES); + JSONObject metadataJson, + JSONObject payloadJson) { + log.info("Processing unsigned message."); + JSONObject messageFrameJson = payloadJson.getJSONObject(MESSAGE_FRAME); + var hexEncodedTimBytes = messageFrameJson.getString(BYTES); + // Case 1: SNMP-deposit if (dataSigningEnabledRSU && (request.getSdw() != null || request.getRsus() != null)) { - var signedTimWithExpiration = signTimWithExpiration(hexEncodedTimBytes, consumedObj); + var signedTimWithExpiration = signTimWithExpiration(hexEncodedTimBytes, metadataJson); kafkaTemplate.send(jsonTopics.getTimCertExpiration(), signedTimWithExpiration); } log.debug("Encoded message - phase 1: {}", hexEncodedTimBytes); - var encodedTimWithoutHeaders = stripHeaderFromUnsignedMessage(consumedObj, dataObj, mfObj, hexEncodedTimBytes); + var encodedTimWithoutHeaders = stripHeader(hexEncodedTimBytes); - if (null != request.getSnmp() && null != request.getRsus() && null != hexEncodedTimBytes) { + if (null != request.getSnmp() && null != request.getRsus()) { log.info("Sending message to RSUs..."); sendToRsus(request, encodedTimWithoutHeaders); } - depositToFilteredTopic(metadataObj, encodedTimWithoutHeaders); + depositToFilteredTopic(metadataJson, encodedTimWithoutHeaders); if (request.getSdw() != null) { - log.debug("Publishing message for round 2 encoding!"); + log.debug("Publishing message for round 2 encoding"); String asdPackagedTim = packageSignedTimIntoAsd(request, encodedTimWithoutHeaders); kafkaTemplate.send(asn1CoderTopics.getEncoderInput(), asdPackagedTim); } } - private String stripHeaderFromUnsignedMessage(JSONObject consumedObj, JSONObject dataObj, - JSONObject mfObj, String hexEncodedTim) { - if (isHeaderPresent(hexEncodedTim)) { - String header = hexEncodedTim.substring(0, hexEncodedTim.indexOf("001F") + 4); - log.debug("Stripping header from unsigned message: {}", header); - hexEncodedTim = stripHeader(hexEncodedTim); - mfObj.remove(BYTES); - mfObj.put(BYTES, hexEncodedTim); - dataObj.remove(MESSAGE_FRAME); - dataObj.put(MESSAGE_FRAME, mfObj); - consumedObj.remove(AppContext.PAYLOAD_STRING); - consumedObj.put(AppContext.PAYLOAD_STRING, dataObj); - } - return hexEncodedTim; - } - - /** - * Process the unsigned encoded TIM message. - * - * @param request The service request - * @param consumedObj The consumed JSON object - */ - private void processEncodedTimUnsecured(ServiceRequest request, JSONObject consumedObj) { + private void processEncodedTimUnsigned(ServiceRequest request, JSONObject metadataJson, JSONObject payloadJson) { log.debug("Unsigned ASD received. Depositing it to SDW."); - // We have ASD with UNSECURED MessageFrame - // Send TIMs and record results - HashMap responseList = new HashMap<>(); - JSONObject metadataObj = consumedObj.getJSONObject(AppContext.METADATA_STRING); - - JSONObject dataObj = consumedObj - .getJSONObject(AppContext.PAYLOAD_STRING) - .getJSONObject(AppContext.DATA_STRING); if (null != request.getSdw()) { - JSONObject asdObj = null; - if (dataObj.has(ADVISORY_SITUATION_DATA_STRING)) { - asdObj = dataObj.getJSONObject(ADVISORY_SITUATION_DATA_STRING); - } else { - log.error("ASD structure present in metadata but not in JSONObject!"); - } - + var asdObj = payloadJson.getJSONObject(ADVISORY_SITUATION_DATA_STRING); if (null != asdObj) { depositToSdx(request, asdObj.getString(BYTES)); } else { - log.error("ASN.1 Encoder did not return ASD encoding {}", consumedObj); + log.error("ASN.1 Encoder did not return ASD encoding {}", payloadJson); } } - if (dataObj.has(MESSAGE_FRAME)) { - JSONObject mfObj = dataObj.getJSONObject(MESSAGE_FRAME); + if (payloadJson.has(MESSAGE_FRAME)) { + JSONObject mfObj = payloadJson.getJSONObject(MESSAGE_FRAME); String encodedTim = mfObj.getString(BYTES); - depositToFilteredTopic(metadataObj, encodedTim); + depositToFilteredTopic(metadataJson, encodedTim); - var encodedTimWithoutHeader = - stripHeaderFromUnsignedMessage(consumedObj, dataObj, mfObj, encodedTim); + var encodedTimWithoutHeader = stripHeader(encodedTim); log.debug("Encoded message - phase 2: {}", encodedTimWithoutHeader); // only send message to rsu if snmp, rsus, and message frame fields are present if (null != request.getSnmp() && null != request.getRsus()) { - log.debug("Encoded message phase 3: {}", encodedTimWithoutHeader); sendToRsus(request, encodedTimWithoutHeader); } } - - log.info("TIM deposit response {}", responseList); } private void depositToSdx(ServiceRequest request, String asdBytes) { @@ -306,25 +263,18 @@ private void depositToSdx(ServiceRequest request, String asdBytes) { } } - /** - * Sign the encoded TIM message, add expiration times, and return the JSON string. - * - * @param encodedTIM The encoded TIM message to be signed - * @param consumedObj The JSON object to be consumed - */ - private String signTimWithExpiration(String encodedTIM, JSONObject consumedObj) { + private String signTimWithExpiration(String encodedTIM, JSONObject metadataJson) { log.debug("Signing encoded TIM message..."); String base64EncodedTim = CodecUtils.toBase64( CodecUtils.fromHex(encodedTIM)); - JSONObject metadataObjs = consumedObj.getJSONObject(AppContext.METADATA_STRING); // get max duration time and convert from minutes to milliseconds (unsigned // integer valid 0 to 2^32-1 in units of // milliseconds.) from metadata - int maxDurationTime = Integer.parseInt(metadataObjs.get("maxDurationTime").toString()) + int maxDurationTime = Integer.parseInt(metadataJson.get("maxDurationTime").toString()) * 60 * 1000; - String packetId = metadataObjs.getString("odePacketID"); - String timStartDateTime = metadataObjs.getString("odeTimStartDateTime"); + String packetId = metadataJson.getString("odePacketID"); + String timStartDateTime = metadataJson.getString("odeTimStartDateTime"); log.debug("SENDING: {}", base64EncodedTim); String signedResponse = securityServicesClient.signMessage(base64EncodedTim, maxDurationTime); @@ -451,7 +401,8 @@ private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String ti } } - private static void setExpiryDate(String signedResponse, JSONObject timWithExpiration, + private static void setExpiryDate(String signedResponse, + JSONObject timWithExpiration, SimpleDateFormat dateFormat) { try { JSONObject jsonResult = JsonUtils.toJSONObject(signedResponse).getJSONObject("result"); @@ -464,13 +415,6 @@ private static void setExpiryDate(String signedResponse, JSONObject timWithExpir } } - /** - * Checks if header is present in encoded message. - */ - private boolean isHeaderPresent(String encodedTim) { - return encodedTim.contains("001F"); - } - /** * Strips header from unsigned message (all bytes before 001F hex value). */ diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java index a1f2aa8af..077068f4d 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java @@ -117,7 +117,7 @@ class Asn1EncodedDataRouterTest { }; @Test - void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOException { + void processSignedMessage() throws IOException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimTmcFiltered(), @@ -137,7 +137,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti objectMapper); final var container = setupListenerContainer(encoderRouter, - "processSignedMessage_depositsToSdxTopicAndTimTmcFiltered" + "processSignedMessage" ); var odeJsonTim = loadResourceString("expected-asn1-encoded-router-tim-json.json"); @@ -151,7 +151,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti Awaitility.await().until(completableFuture::isDone); var testConsumer = - createTestConsumer("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered"); + createTestConsumer("processSignedMessage"); embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topicsForConsumption); var expected = loadResourceString("expected-asn1-encoded-router-sdx-deposit.json"); @@ -166,7 +166,7 @@ void processSignedMessage_depositsToSdxTopicAndTimTmcFiltered() throws IOExcepti } assertTrue(foundValidRecord); container.stop(); - log.debug("processSignedMessage_depositsToSdxTopicAndTimTmcFiltered container stopped"); + log.debug("processSignedMessage container stopped"); } @Test @@ -265,7 +265,7 @@ private static String stripGeneratedFields(String expectedEncoderInput) { } @Test - void processEncodedTimUnsecured() throws IOException { + void processEncodedTimUnsigned() throws IOException { String[] topicsForConsumption = { asn1CoderTopics.getEncoderInput(), jsonTopics.getTimTmcFiltered() @@ -284,7 +284,7 @@ void processEncodedTimUnsecured() throws IOException { kafkaTemplate, sdxDepositorTopic, objectMapper); - final var container = setupListenerContainer(encoderRouter, "processEncodedTimUnsecured"); + final var container = setupListenerContainer(encoderRouter, "processEncodedTimUnsigned"); var odeJsonTim = loadResourceString("expected-asn1-encoded-router-tim-json.json"); // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return @@ -296,7 +296,7 @@ void processEncodedTimUnsecured() throws IOException { input = replaceStreamId(input, streamId); kafkaTemplate.send(asn1CoderTopics.getEncoderOutput(), input); - var testConsumer = createTestConsumer("processEncodedTimUnsecured"); + var testConsumer = createTestConsumer("processEncodedTimUnsigned"); embeddedKafka.consumeFromEmbeddedTopics(testConsumer, topicsForConsumption); var expected = loadResourceString("expected-asn1-encoded-router-sdx-deposit.json"); @@ -323,7 +323,7 @@ void processEncodedTimUnsecured() throws IOException { } assertTrue(foundValidRecord); container.stop(); - log.debug("processEncodedTimUnsecured container stopped"); + log.debug("processEncodedTimUnsigned container stopped"); } private static String replaceStreamId(String input, String streamId) { From 1b1c39c6da4c37bb960b428bff107bf314a65525 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 15:31:24 -0700 Subject: [PATCH 094/128] refactor: error handling and use enum for start flag Updated error handling to clarify null assignments in logs and added "SupportedMessageType" enum to replace the hardcoded start flag "001F". These changes improve log clarity and code maintainability. --- .../ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java index 536f0b7bb..6c8ce5888 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java @@ -48,6 +48,7 @@ import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; +import us.dot.its.jpo.ode.uper.SupportedMessageType; import us.dot.its.jpo.ode.util.CodecUtils; import us.dot.its.jpo.ode.util.JsonUtils; import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; @@ -396,7 +397,7 @@ private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String ti requiredExpirationDate.setTime(timTimestamp.getTime() + maxDurationTime); timWithExpiration.put("requiredExpirationDate", dateFormat.format(requiredExpirationDate)); } catch (Exception e) { - log.error("Unable to parse requiredExpirationDate ", e); + log.error("Unable to parse requiredExpirationDate. Setting requiredExpirationDate to 'null'", e); timWithExpiration.put("requiredExpirationDate", "null"); } } @@ -410,7 +411,7 @@ private static void setExpiryDate(String signedResponse, long messageExpiry = Long.parseLong(jsonResult.getString("message-expiry")); timWithExpiration.put("expirationDate", dateFormat.format(new Date(messageExpiry * 1000))); } catch (Exception e) { - log.error("Unable to get expiration date from signed messages response ", e); + log.error("Unable to get expiration date from signed messages response. Setting expirationData to 'null'", e); timWithExpiration.put("expirationDate", "null"); } } @@ -420,7 +421,7 @@ private static void setExpiryDate(String signedResponse, */ private String stripHeader(String encodedUnsignedTim) { // find 001F hex value - int index = encodedUnsignedTim.indexOf("001F"); + int index = encodedUnsignedTim.indexOf(SupportedMessageType.TIM.getStartFlag()); if (index == -1) { log.warn("No '001F' hex value found in encoded message"); return encodedUnsignedTim; From 977a2b72a68e36982b2b0ddc4a0bcf9a2738ab59 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Tue, 24 Dec 2024 16:07:25 -0700 Subject: [PATCH 095/128] refactor: add XmlMapper for XML handling Refactored the ASD packaging logic for clarity and consistency, reducing redundancy and improving maintainability. Introduced `XmlMapper` to streamline XML processing and decoupled responsibilities such as RSU handling and second-encoding publishing. Updated tests to include the new `XmlMapper` dependency, ensuring comprehensive coverage of changes. --- .../listeners/asn1/Asn1EncodedDataRouter.java | 123 +++++++++--------- .../asn1/Asn1EncodedDataRouterTest.java | 16 ++- 2 files changed, 73 insertions(+), 66 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java index 6c8ce5888..b2872fa11 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -68,6 +69,7 @@ public class Asn1EncodedDataRouter { private static final String ERROR_ON_SDX_DEPOSIT = "Error on SDX deposit."; private static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; private final KafkaTemplate kafkaTemplate; + private final XmlMapper xmlMapper; /** * Exception for Asn1EncodedDataRouter specific failures. @@ -106,7 +108,7 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, ISecurityServicesClient securityServicesClient, KafkaTemplate kafkaTemplate, @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic, - ObjectMapper mapper) { + ObjectMapper mapper, XmlMapper xmlMapper) { super(); this.asn1CoderTopics = asn1CoderTopics; @@ -122,6 +124,7 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, this.odeTimJsonTopology = odeTimJsonTopology; this.mapper = mapper; + this.xmlMapper = xmlMapper; } /** @@ -210,18 +213,9 @@ private void processUnsignedMessage(ServiceRequest request, log.debug("Encoded message - phase 1: {}", hexEncodedTimBytes); var encodedTimWithoutHeaders = stripHeader(hexEncodedTimBytes); - if (null != request.getSnmp() && null != request.getRsus()) { - log.info("Sending message to RSUs..."); - sendToRsus(request, encodedTimWithoutHeaders); - } - + sendToRsus(request, encodedTimWithoutHeaders); depositToFilteredTopic(metadataJson, encodedTimWithoutHeaders); - if (request.getSdw() != null) { - log.debug("Publishing message for round 2 encoding"); - String asdPackagedTim = packageSignedTimIntoAsd(request, encodedTimWithoutHeaders); - - kafkaTemplate.send(asn1CoderTopics.getEncoderInput(), asdPackagedTim); - } + publishForSecondEncoding(request, encodedTimWithoutHeaders); } private void processEncodedTimUnsigned(ServiceRequest request, JSONObject metadataJson, JSONObject payloadJson) { @@ -301,8 +295,8 @@ private String signTimWithExpiration(String encodedTIM, JSONObject metadataJson) * @return a String containing the fully crafted ASD message in XML format. Returns null if the * message could not be constructed due to exceptions. */ - private String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) { - + private String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) throws JsonProcessingException, ParseException, + JsonUtilsException { SDW sdw = request.getSdw(); SNMP snmp = request.getSnmp(); DdsAdvisorySituationData asd; @@ -311,69 +305,56 @@ private String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) request.getRsus() != null ? DdsAdvisorySituationData.RSU : DdsAdvisorySituationData.NONE; byte distroType = (byte) (DdsAdvisorySituationData.IP | sendToRsu); - String outputXml = null; - try { - if (null != snmp) { - asd = new DdsAdvisorySituationData() - .setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null) - .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) - .setTimeToLive(sdw.getTtl()) - .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - } else { - asd = new DdsAdvisorySituationData() - .setAsdmDetails(sdw.getDeliverystart(), sdw.getDeliverystop(), distroType, null) - .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) - .setTimeToLive(sdw.getTtl()) - .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - } + asd = new DdsAdvisorySituationData() + .setServiceRegion(GeoRegionBuilder.ddsGeoRegion(sdw.getServiceRegion())) + .setTimeToLive(sdw.getTtl()) + .setGroupID(sdw.getGroupID()).setRecordID(sdw.getRecordId()); - ObjectNode dataBodyObj = JsonUtils.newNode(); - ObjectNode asdObj = JsonUtils.toObjectNode(asd.toJson()); - ObjectNode admDetailsObj = (ObjectNode) asdObj.findValue("asdmDetails"); - admDetailsObj.remove("advisoryMessage"); - admDetailsObj.put("advisoryMessage", signedMsg); + if (null != snmp) { + asd.setAsdmDetails(snmp.getDeliverystart(), snmp.getDeliverystop(), distroType, null); + } else { + asd.setAsdmDetails(sdw.getDeliverystart(), sdw.getDeliverystop(), distroType, null); + } - dataBodyObj.set(ADVISORY_SITUATION_DATA_STRING, asdObj); - OdeMsgPayload payload = new OdeAsdPayload(asd); + var asdJson = (ObjectNode) mapper.readTree(asd.toJson()); - ObjectNode payloadObj = JsonUtils.toObjectNode(payload.toJson()); - payloadObj.set(AppContext.DATA_STRING, dataBodyObj); + var admDetailsObj = (ObjectNode) asdJson.findValue("asdmDetails"); + admDetailsObj.remove("advisoryMessage"); + admDetailsObj.put("advisoryMessage", signedMsg); - OdeMsgMetadata metadata = new OdeMsgMetadata(payload); - ObjectNode metaObject = JsonUtils.toObjectNode(metadata.toJson()); + asdJson.set("asdmDetails", admDetailsObj); - ObjectNode requestObj = JsonUtils.toObjectNode(JsonUtils.toJson(request, false)); + ObjectNode advisorySituationDataNode = mapper.createObjectNode(); + advisorySituationDataNode.set(ADVISORY_SITUATION_DATA_STRING, asdJson); - requestObj.remove("tim"); + OdeMsgPayload payload = new OdeAsdPayload(asd); - metaObject.set("request", requestObj); + var payloadNode = (ObjectNode) mapper.readTree(payload.toJson()); + payloadNode.set(AppContext.DATA_STRING, advisorySituationDataNode); - ArrayNode encodings = buildEncodings(); - ObjectNode enc = - XmlUtils.createEmbeddedJsonArrayForXmlConversion(AppContext.ENCODINGS_STRING, - encodings); - metaObject.set(AppContext.ENCODINGS_STRING, enc); + OdeMsgMetadata metadata = new OdeMsgMetadata(payload); + var metadataNode = (ObjectNode) mapper.readTree(metadata.toJson()); - ObjectNode message = JsonUtils.newNode(); - message.set(AppContext.METADATA_STRING, metaObject); - message.set(AppContext.PAYLOAD_STRING, payloadObj); + metadataNode.set("request", mapper.readTree(request.toJson())); - ObjectNode root = JsonUtils.newNode(); - root.set(AppContext.ODE_ASN1_DATA, message); + ArrayNode encodings = buildEncodings(); + var embeddedEncodings = xmlMapper.createObjectNode(); + embeddedEncodings.set(AppContext.ENCODINGS_STRING, encodings); - outputXml = XmlUtils.toXmlStatic(root); + metadataNode.set(AppContext.ENCODINGS_STRING, embeddedEncodings); - // remove the surrounding - outputXml = outputXml.replace("", ""); - outputXml = outputXml.replace("", ""); + ObjectNode message = mapper.createObjectNode(); + message.set(AppContext.METADATA_STRING, metadataNode); + message.set(AppContext.PAYLOAD_STRING, payloadNode); - } catch (ParseException | JsonUtilsException | XmlUtilsException e) { - log.error("Parsing exception thrown while populating ASD structure: ", e); - } + ObjectNode root = mapper.createObjectNode(); + root.set(AppContext.ODE_ASN1_DATA, message); + var outputXml = xmlMapper.writeValueAsString(root) + .replace("", "") + .replace("", ""); log.debug("Fully crafted ASD to be encoded: {}", outputXml); - return outputXml; } @@ -386,6 +367,11 @@ private ArrayNode buildEncodings() throws JsonUtilsException { } private void sendToRsus(ServiceRequest request, String encodedMsg) { + if (null == request.getSnmp() || null == request.getRsus()) { + log.debug("No RSUs or SNMP provided. Skipping sending to RSUs."); + return; + } + log.info("Sending message to RSUs..."); rsuDepositor.deposit(request, encodedMsg); } @@ -452,4 +438,19 @@ private void depositToFilteredTopic(JSONObject metadataObj, String hexEncodedTim log.debug("TIM not found in k-table. Skipping deposit to TMC-filtered topic."); } } + + private void publishForSecondEncoding(ServiceRequest request, String encodedTimWithoutHeaders) { + if (request.getSdw() == null) { + log.debug("SDW not present. No second encoding required."); + return; + } + + try { + log.debug("Publishing message for round 2 encoding"); + String asdPackagedTim = packageSignedTimIntoAsd(request, encodedTimWithoutHeaders); + kafkaTemplate.send(asn1CoderTopics.getEncoderInput(), asdPackagedTim); + } catch (Exception e) { + log.error("Error packaging ASD for round 2 encoding", e); + } + } } diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java index 077068f4d..9156e4255 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -104,6 +105,8 @@ class Asn1EncodedDataRouterTest { OdeTimJsonTopology odeTimJsonTopology; @Autowired ObjectMapper objectMapper; + @Autowired + private XmlMapper xmlMapper; private final EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @@ -134,7 +137,8 @@ void processSignedMessage() throws IOException { mockRsuDepositor, mockSecServClient, kafkaTemplate, sdxDepositorTopic, - objectMapper); + objectMapper, + xmlMapper); final var container = setupListenerContainer(encoderRouter, "processSignedMessage" @@ -188,12 +192,13 @@ void processUnsignedMessage() throws IOException { mockRsuDepositor, mockSecServClient, kafkaTemplate, sdxDepositorTopic, - objectMapper); + objectMapper, + xmlMapper); final var container = setupListenerContainer(encoderRouter, "processUnsignedMessage"); var odeJsonTim = loadResourceString("expected-asn1-encoded-router-tim-json.json"); - // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return + // send to tim topic so that the OdeTimJsonTopology k-table has the correct record to return var streamId = UUID.randomUUID().toString(); odeJsonTim = odeJsonTim.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); var topologySendFuture = kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); @@ -282,12 +287,13 @@ void processEncodedTimUnsigned() throws IOException { mockRsuDepositor, mockSecServClient, kafkaTemplate, sdxDepositorTopic, - objectMapper); + objectMapper, + xmlMapper); final var container = setupListenerContainer(encoderRouter, "processEncodedTimUnsigned"); var odeJsonTim = loadResourceString("expected-asn1-encoded-router-tim-json.json"); - // send to tim topic so that the OdeTimJsonTopology ktable has the correct record to return + // send to tim topic so that the OdeTimJsonTopology k-table has the correct record to return var streamId = UUID.randomUUID().toString(); odeJsonTim = odeJsonTim.replaceAll("266e6742-40fb-4c9e-a6b0-72ed2dddddfe", streamId); kafkaTemplate.send(jsonTopics.getTim(), streamId, odeJsonTim); From fcc2e8c6217d33789c7bc688625846c685b52048 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 10:24:08 -0700 Subject: [PATCH 096/128] revert: undo move of resources to different package GitHub doesn't do well with changed files *also* getting moved. It tracks them as deleted files and new files, so reviewing is difficult. I will follow up in a new PR with the moves of these files to the correct package --- .../asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml | 0 .../asn1/asn1-encoder-output-unsigned-tim.xml | 0 .../asn1/expected-asn1-encoded-router-sdx-deposit.json | 0 .../asn1/expected-asn1-encoded-router-snmp-deposit.xml | 0 .../asn1/expected-asn1-encoded-router-tim-json.json | 0 .../listeners => services}/asn1/expected-tim-cert-expired.json | 0 .../listeners => services}/asn1/expected-tim-tmc-filtered.json | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{kafka/listeners => services}/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{kafka/listeners => services}/asn1/asn1-encoder-output-unsigned-tim.xml (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{kafka/listeners => services}/asn1/expected-asn1-encoded-router-sdx-deposit.json (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{kafka/listeners => services}/asn1/expected-asn1-encoded-router-snmp-deposit.xml (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{kafka/listeners => services}/asn1/expected-asn1-encoded-router-tim-json.json (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{kafka/listeners => services}/asn1/expected-tim-cert-expired.json (100%) rename jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/{kafka/listeners => services}/asn1/expected-tim-tmc-filtered.json (100%) diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim-no-advisory-data.xml diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/asn1-encoder-output-unsigned-tim.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/asn1-encoder-output-unsigned-tim.xml rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/asn1-encoder-output-unsigned-tim.xml diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-sdx-deposit.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-sdx-deposit.json rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-sdx-deposit.json diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-snmp-deposit.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-snmp-deposit.xml rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-tim-json.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-asn1-encoded-router-tim-json.json rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-tim-json.json diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-tim-cert-expired.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-tim-cert-expired.json rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-tim-tmc-filtered.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json similarity index 100% rename from jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/kafka/listeners/asn1/expected-tim-tmc-filtered.json rename to jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-tmc-filtered.json From 7c8e68c4940514ac6acae6bf43dad5055a134c34 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 10:24:13 -0700 Subject: [PATCH 097/128] Revert "refactor: move Asn1EncodedDataRouter to kafka.listeners.asn1 package" This reverts commit 6578b372b4c39030df65db506cc9dceaec8dda99. --- .../listeners => services}/asn1/Asn1EncodedDataRouter.java | 2 +- .../listeners => services}/asn1/Asn1EncodedDataRouterTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/{kafka/listeners => services}/asn1/Asn1EncodedDataRouter.java (99%) rename jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/{kafka/listeners => services}/asn1/Asn1EncodedDataRouterTest.java (99%) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java similarity index 99% rename from jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java rename to jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index b2872fa11..8531082de 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -14,7 +14,7 @@ * the License. ******************************************************************************/ -package us.dot.its.jpo.ode.kafka.listeners.asn1; +package us.dot.its.jpo.ode.services.asn1; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java similarity index 99% rename from jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java rename to jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 9156e4255..b3665d1dc 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/kafka/listeners/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -14,7 +14,7 @@ * the License. ******************************************************************************/ -package us.dot.its.jpo.ode.kafka.listeners.asn1; +package us.dot.its.jpo.ode.services.asn1; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; From 0eee3357b7a46d0524260294b4183c75fa44e4f0 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 10:27:32 -0700 Subject: [PATCH 098/128] test: update resource package path after reverting package move --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index b3665d1dc..dd9c11523 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -367,7 +367,7 @@ private Consumer createTestConsumer(String group) { private static String loadResourceString(String name) throws IOException { - String resourcePackagePath = "us/dot/its/jpo/ode/kafka/listeners/asn1/"; + String resourcePackagePath = "us/dot/its/jpo/ode/services/asn1/"; InputStream inputStream; inputStream = Asn1EncodedDataRouterTest.class.getClassLoader() .getResourceAsStream(resourcePackagePath + name); From 08550c05e0e31a32a64cf024e7e1f1767f61516d Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 10:49:04 -0700 Subject: [PATCH 099/128] refactor: introduce SignatureResultModel Use a POJO data bag to encapsulate response data from ISecurityServicesClient.signMessage so that the logic is in one place, and it's clearer what to expect from the API call. --- .../ode/security/ISecurityServicesClient.java | 2 +- .../security/SecurityServicesClientImpl.java | 9 +++-- .../ode/security/SecurityServicesConfig.java | 13 +++++-- .../ode/security/SignatureResultModel.java | 35 +++++++++++++++++++ .../services/asn1/Asn1EncodedDataRouter.java | 26 +++++++------- .../asn1/Asn1EncodedDataRouterTest.java | 12 +++---- 6 files changed, 71 insertions(+), 26 deletions(-) create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SignatureResultModel.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java index 71420f16b..5c3437a69 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java @@ -6,5 +6,5 @@ */ public interface ISecurityServicesClient { - public String signMessage(String message, int sigValidityOverride); + public SignatureResultModel signMessage(String message, int sigValidityOverride); } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java index d160d98aa..7b571b6b1 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java @@ -1,5 +1,6 @@ package us.dot.its.jpo.ode.security; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; import lombok.extern.slf4j.Slf4j; @@ -18,13 +19,15 @@ public class SecurityServicesClientImpl implements ISecurityServicesClient { private final String signatureUri; + private final ObjectMapper mapper; - public SecurityServicesClientImpl(String signatureUri) { + public SecurityServicesClientImpl(ObjectMapper mapper, String signatureUri) { + this.mapper = mapper; this.signatureUri = signatureUri; } @Override - public String signMessage(String message, int sigValidityOverride) { + public SignatureResultModel signMessage(String message, int sigValidityOverride) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); Map map = new HashMap<>(); @@ -42,6 +45,6 @@ public String signMessage(String message, int sigValidityOverride) { log.debug("Security services module response: {}", respEntity); - return respEntity.getBody(); + return mapper.convertValue(respEntity.getBody(), SignatureResultModel.class); } } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java index 8e6c4fa53..56c4dee5e 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java @@ -1,12 +1,21 @@ package us.dot.its.jpo.ode.security; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +/** + * Configuration class for setting up security services related beans. + * + *

This class provides a Spring-managed bean of type ISecurityServicesClient + * that is configured using SecurityServicesProperties and ObjectMapper. + * The client facilitates interaction with external security services for + * cryptographic operations such as message signing.

+ */ @Configuration public class SecurityServicesConfig { @Bean - public ISecurityServicesClient securityServicesClient(SecurityServicesProperties securityServicesProperties) { - return new SecurityServicesClientImpl(securityServicesProperties.getSignatureEndpoint()); + public ISecurityServicesClient securityServicesClient(SecurityServicesProperties securityServicesProperties, ObjectMapper mapper) { + return new SecurityServicesClientImpl(mapper, securityServicesProperties.getSignatureEndpoint()); } } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SignatureResultModel.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SignatureResultModel.java new file mode 100644 index 000000000..0315fa207 --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SignatureResultModel.java @@ -0,0 +1,35 @@ +package us.dot.its.jpo.ode.security; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Represents the result of a message signing operation provided by a security + * service. This model encapsulates the signed message and its expiration + * information. + * + *

The signing operation is performed by the security service and the result + * is returned in this structure. The {@code Result} inner class contains specific + * details about the signed message.

+ */ +@Data +@NoArgsConstructor +public class SignatureResultModel { + private Result result = new Result(); + + /** + * Represents the result of a cryptographic operation that includes a signed message + * and its associated expiration information. + * + *

This class is typically used to encapsulate the output of a message signing + * process performed by a security service. The signed message is stored in the + * {@code messageSigned} field, while the expiration time for the message, expressed + * in seconds, is stored in the {@code messageExpiry} field.

+ */ + @Data + @NoArgsConstructor + public static class Result { + private String messageSigned; + private Long messageExpiry; + } +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 8531082de..c20858c42 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -48,10 +48,10 @@ import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; +import us.dot.its.jpo.ode.security.SignatureResultModel; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; import us.dot.its.jpo.ode.uper.SupportedMessageType; import us.dot.its.jpo.ode.util.CodecUtils; -import us.dot.its.jpo.ode.util.JsonUtils; import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; @@ -108,7 +108,8 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, ISecurityServicesClient securityServicesClient, KafkaTemplate kafkaTemplate, @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic, - ObjectMapper mapper, XmlMapper xmlMapper) { + ObjectMapper mapper, + XmlMapper xmlMapper) { super(); this.asn1CoderTopics = asn1CoderTopics; @@ -271,7 +272,7 @@ private String signTimWithExpiration(String encodedTIM, JSONObject metadataJson) String packetId = metadataJson.getString("odePacketID"); String timStartDateTime = metadataJson.getString("odeTimStartDateTime"); log.debug("SENDING: {}", base64EncodedTim); - String signedResponse = securityServicesClient.signMessage(base64EncodedTim, maxDurationTime); + var signedResponse = securityServicesClient.signMessage(base64EncodedTim, maxDurationTime); JSONObject timWithExpiration = new JSONObject(); timWithExpiration.put("packetID", packetId); @@ -359,7 +360,7 @@ private String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) } private ArrayNode buildEncodings() throws JsonUtilsException { - ArrayNode encodings = JsonUtils.newArrayNode(); + ArrayNode encodings = mapper.createArrayNode(); encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, ADVISORY_SITUATION_DATA_STRING, EncodingRule.UPER)); @@ -375,8 +376,8 @@ private void sendToRsus(ServiceRequest request, String encodedMsg) { rsuDepositor.deposit(request, encodedMsg); } - private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String timStartDateTime, - int maxDurationTime, JSONObject timWithExpiration) { + private void setRequiredExpiryDate(SimpleDateFormat dateFormat, String timStartDateTime, + int maxDurationTime, JSONObject timWithExpiration) { try { Date timTimestamp = dateFormat.parse(timStartDateTime); Date requiredExpirationDate = new Date(); @@ -388,14 +389,13 @@ private static void setRequiredExpiryDate(SimpleDateFormat dateFormat, String ti } } - private static void setExpiryDate(String signedResponse, - JSONObject timWithExpiration, - SimpleDateFormat dateFormat) { + private void setExpiryDate(SignatureResultModel signedResponse, + JSONObject timWithExpiration, + SimpleDateFormat dateFormat) { try { - JSONObject jsonResult = JsonUtils.toJSONObject(signedResponse).getJSONObject("result"); - // messageExpiry uses unit of seconds - long messageExpiry = Long.parseLong(jsonResult.getString("message-expiry")); - timWithExpiration.put("expirationDate", dateFormat.format(new Date(messageExpiry * 1000))); + timWithExpiration.put("expirationDate", + dateFormat.format(new Date(signedResponse.getResult().getMessageExpiry() * 1000)) + ); } catch (Exception e) { log.error("Unable to get expiration date from signed messages response. Setting expirationData to 'null'", e); timWithExpiration.put("expirationDate", "null"); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index dd9c11523..15fc6091f 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -29,7 +29,6 @@ import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.common.serialization.StringDeserializer; import org.awaitility.Awaitility; -import org.json.JSONObject; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; @@ -57,6 +56,7 @@ import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; +import us.dot.its.jpo.ode.security.SignatureResultModel; import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; @Slf4j @@ -111,12 +111,10 @@ class Asn1EncodedDataRouterTest { private final EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); private final ISecurityServicesClient mockSecServClient = (message, sigValidityOverride) -> { - JSONObject json = new JSONObject(); - JSONObject result = new JSONObject(); - result.put("message-signed", "<%s>".formatted(message)); - result.put("message-expiry", "123124124124124141"); - json.put("result", result); - return json.toString(); + var signatureResponse = new SignatureResultModel(); + signatureResponse.getResult().setMessageSigned("<%s>".formatted(message)); + signatureResponse.getResult().setMessageExpiry(123124124124124141L); + return signatureResponse; }; @Test From bcb69014708efcd74d5e0d4f2684c0688d30900b Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 11:20:39 -0700 Subject: [PATCH 100/128] refactor: rename SecurityServicesClientImpl.java to SecurityServicesClient --- ...ityServicesClientImpl.java => SecurityServicesClient.java} | 4 ++-- .../us/dot/its/jpo/ode/security/SecurityServicesConfig.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/{SecurityServicesClientImpl.java => SecurityServicesClient.java} (91%) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java similarity index 91% rename from jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java rename to jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java index 7b571b6b1..4a8c00001 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClientImpl.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java @@ -16,12 +16,12 @@ * */ @Slf4j -public class SecurityServicesClientImpl implements ISecurityServicesClient { +public class SecurityServicesClient implements ISecurityServicesClient { private final String signatureUri; private final ObjectMapper mapper; - public SecurityServicesClientImpl(ObjectMapper mapper, String signatureUri) { + public SecurityServicesClient(ObjectMapper mapper, String signatureUri) { this.mapper = mapper; this.signatureUri = signatureUri; } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java index 56c4dee5e..cbd3d12af 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java @@ -16,6 +16,6 @@ public class SecurityServicesConfig { @Bean public ISecurityServicesClient securityServicesClient(SecurityServicesProperties securityServicesProperties, ObjectMapper mapper) { - return new SecurityServicesClientImpl(mapper, securityServicesProperties.getSignatureEndpoint()); + return new SecurityServicesClient(mapper, securityServicesProperties.getSignatureEndpoint()); } } From dbe49ea35a898b4b56018dbbd4d25c32b7d40272 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 14:37:55 -0700 Subject: [PATCH 101/128] refactor: SecurityServicesClient and add unit test coverage Refactored the SecurityServicesClient to use dependency-injected RestTemplate and improved the request payload structure with a dedicated SignatureRequestModel class. Added unit tests for signMessage functionality and introduced configuration for RestTemplate with WebClientConfig. --- .../dot/its/jpo/ode/http/WebClientConfig.java | 42 +++++ .../ode/security/SecurityServicesClient.java | 36 ++--- .../models/SignatureRequestModel.java | 20 +++ .../{ => models}/SignatureResultModel.java | 2 +- .../security/SecurityServicesClientTest.java | 147 ++++++++++++++++++ 5 files changed, 227 insertions(+), 20 deletions(-) create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/http/WebClientConfig.java create mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureRequestModel.java rename jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/{ => models}/SignatureResultModel.java (96%) create mode 100644 jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/http/WebClientConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/http/WebClientConfig.java new file mode 100644 index 000000000..ee170d3ee --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/http/WebClientConfig.java @@ -0,0 +1,42 @@ +package us.dot.its.jpo.ode.http; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +/** + * This class provides a configuration for creating and managing + * a {@link RestTemplate} bean, which is used for making HTTP requests + * to external services. + * + *

NOTE: As of 5.0 the {@link RestTemplate} class is in maintenance mode, with + * only minor requests for changes and bugs to be accepted going forward. Please, + * consider using the {@code org.springframework.web.reactive.client.WebClient} + * which has a more modern API and supports sync, async, and streaming scenarios. + * Whenever we the time or resources to update our Spring version, + * we should replace usages of RestTemplate with WebClient.

+ */ +@Configuration +public class WebClientConfig { + + /** + * Creates and configures a {@link RestTemplate} bean with a custom + * {@link MappingJackson2HttpMessageConverter} to use the provided + * {@link ObjectMapper} for JSON serialization and deserialization. + * + * @param mapper the {@link ObjectMapper} to be used for configuring + * JSON message conversion. + * @return a configured {@link RestTemplate} instance that includes + * the custom JSON message converter. + */ + @Bean + public RestTemplate restTemplate(ObjectMapper mapper) { + var template = new RestTemplate(); + MappingJackson2HttpMessageConverter customConverter = new MappingJackson2HttpMessageConverter(); + customConverter.setObjectMapper(mapper); + template.getMessageConverters().add(customConverter); + return template; + } +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java index 4a8c00001..1bd4c7792 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java @@ -1,50 +1,48 @@ package us.dot.its.jpo.ode.security; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.HashMap; -import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; +import us.dot.its.jpo.ode.security.configuration.SecurityServicesProperties; +import us.dot.its.jpo.ode.security.models.SignatureRequestModel; +import us.dot.its.jpo.ode.security.models.SignatureResultModel; /** * Implementation of the ISecurityServicesClient interface for interacting with security * services that provide cryptographic operations such as message signing. - * */ @Slf4j +@Service public class SecurityServicesClient implements ISecurityServicesClient { private final String signatureUri; - private final ObjectMapper mapper; + private final RestTemplate restTemplate; - public SecurityServicesClient(ObjectMapper mapper, String signatureUri) { - this.mapper = mapper; - this.signatureUri = signatureUri; + public SecurityServicesClient(RestTemplate restTemplate, SecurityServicesProperties securityServicesProperties) { + this.restTemplate = restTemplate; + this.signatureUri = securityServicesProperties.getSignatureEndpoint(); } @Override public SignatureResultModel signMessage(String message, int sigValidityOverride) { + log.info("Sending data to security services module at {} with validity override {} to be signed", signatureUri, sigValidityOverride); + HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - Map map = new HashMap<>(); - map.put("message", message); - map.put("sigValidityOverride", Integer.toString(sigValidityOverride)); + var requestBody = new SignatureRequestModel(); + requestBody.setMessage(message); + requestBody.setSigValidityOverride(sigValidityOverride); - HttpEntity> entity = new HttpEntity<>(map, headers); - RestTemplate template = new RestTemplate(); + HttpEntity entity = new HttpEntity<>(requestBody, headers); - log.info("Sending data to security services module with validity override at {} to be signed", - signatureUri); log.debug("Data to be signed: {}", entity); - - ResponseEntity respEntity = template.postForEntity(signatureUri, entity, String.class); - + ResponseEntity respEntity = restTemplate.postForEntity(signatureUri, entity, SignatureResultModel.class); log.debug("Security services module response: {}", respEntity); - return mapper.convertValue(respEntity.getBody(), SignatureResultModel.class); + return respEntity.getBody(); } } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureRequestModel.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureRequestModel.java new file mode 100644 index 000000000..7a5280f1c --- /dev/null +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureRequestModel.java @@ -0,0 +1,20 @@ +package us.dot.its.jpo.ode.security.models; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Represents a request model for a message signing operation in a security service. + * This model is used to encapsulate the message to be signed and an optional + * validity period override for the signed message. + * + *

The {@code message} field is the text message that needs to be signed by a + * security service. The {@code sigValidityOverride} field provides an optional + * override for the signature validity period, expressed in seconds since epoch.

+ */ +@Data +@NoArgsConstructor +public class SignatureRequestModel { + private String message; + private Integer sigValidityOverride; +} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SignatureResultModel.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java similarity index 96% rename from jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SignatureResultModel.java rename to jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java index 0315fa207..9b04b2ab6 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SignatureResultModel.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java @@ -1,4 +1,4 @@ -package us.dot.its.jpo.ode.security; +package us.dot.its.jpo.ode.security.models; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java new file mode 100644 index 000000000..3de115801 --- /dev/null +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java @@ -0,0 +1,147 @@ +package us.dot.its.jpo.ode.security; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.client.ExpectedCount; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; +import us.dot.its.jpo.ode.config.SerializationConfig; +import us.dot.its.jpo.ode.http.WebClientConfig; +import us.dot.its.jpo.ode.security.configuration.SecurityServicesProperties; +import us.dot.its.jpo.ode.security.models.SignatureRequestModel; +import us.dot.its.jpo.ode.security.models.SignatureResultModel; + +@ExtendWith(SpringExtension.class) +@SpringBootTest( + classes = { + SerializationConfig.class, + SecurityServicesClient.class, + SecurityServicesProperties.class, + WebClientConfig.class, + } +) +@EnableConfigurationProperties +class SecurityServicesClientTest { + + @Autowired + private RestTemplate restTemplate; + @Autowired + private SecurityServicesClient securityServicesClient; + + private MockRestServiceServer mockServer; + private final Clock clock = Clock.fixed(Instant.parse("2024-12-26T23:53:21.120Z"), ZoneId.of("UTC")); + @Autowired + private ObjectMapper objectMapper; + + @BeforeEach + void beforeEach() { + mockServer = MockRestServiceServer.createServer(restTemplate); + } + + @Test + void testSignMessage_WithMockServerSuccessfulResponse() throws JsonProcessingException { + // Arrange + String message = "TestMessage"; + SignatureResultModel expectedResult = new SignatureResultModel(); + expectedResult.getResult().setMessageSigned("signed message<%s>".formatted(message)); + expectedResult.getResult().setMessageExpiry(clock.instant().getEpochSecond()); + + SignatureRequestModel signatureRequestModel = new SignatureRequestModel(); + signatureRequestModel.setMessage(message); + var expiryTimeInSeconds = (int) clock.instant().plusSeconds(3600).getEpochSecond(); + signatureRequestModel.setSigValidityOverride(expiryTimeInSeconds); + + mockServer.expect(ExpectedCount.once(), requestTo("http://localhost:8090/sign")) + .andRespond(withSuccess(objectMapper.writeValueAsString(expectedResult), MediaType.APPLICATION_JSON)); + + SignatureResultModel result = securityServicesClient.signMessage(message, expiryTimeInSeconds); + assertEquals(expectedResult, result); + } +// +// @Test +// void testSignMessage_EmptyMessage() { +// // Arrange +// RestTemplate mockRestTemplate = Mockito.mock(RestTemplate.class); +// String signatureUri = "http://example.com/signature"; +// String message = ""; +// int sigValidityOverride = 10; +// String mockResponse = "{\"result\":\"mockedResponseEmptyMessage\"}"; +// +// SecurityServicesClient client = new SecurityServicesClient(restTemplate, signatureUri); +// SignatureResultModel expectedResult = new SignatureResultModel(); +// +// ResponseEntity responseEntity = ResponseEntity.ok(mockResponse); +// +// when(mockRestTemplate.postForEntity(eq(signatureUri), any(HttpEntity.class), eq(String.class))) +// .thenReturn(responseEntity); +// when(mapper.convertValue(mockResponse, SignatureResultModel.class)) +// .thenReturn(expectedResult); +// +// // Act +// SignatureResultModel result = client.signMessage(message, sigValidityOverride); +// +// // Assert +// assertNotNull(result); +// assertEquals(expectedResult, result); +// } +// +// @Test +// void testSignMessage_InvalidSignatureUri() { +// // Arrange +// RestTemplate mockRestTemplate = Mockito.mock(RestTemplate.class); +// String signatureUri = "http://invaliduri/signature"; +// String message = "TestMessage"; +// int sigValidityOverride = 10; +// +// SecurityServicesClient client = new SecurityServicesClient(restTemplate, signatureUri); +// +// when(mockRestTemplate.postForEntity(eq(signatureUri), any(HttpEntity.class), eq(String.class))) +// .thenThrow(new RuntimeException("Invalid URI")); +// +// // Act & Assert +// try { +// client.signMessage(message, sigValidityOverride); +// } catch (RuntimeException e) { +// assertEquals("Invalid URI", e.getMessage()); +// } +// } +// +// @Test +// void testSignMessage_NullResponseBody() { +// // Arrange +// RestTemplate mockRestTemplate = Mockito.mock(RestTemplate.class); +// String signatureUri = "http://example.com/signature"; +// String message = "TestMessage"; +// int sigValidityOverride = 10; +// +// SecurityServicesClient client = new SecurityServicesClient(restTemplate, signatureUri); +// +// ResponseEntity responseEntity = ResponseEntity.ok(null); +// +// when(mockRestTemplate.postForEntity(eq(signatureUri), any(HttpEntity.class), eq(String.class))) +// .thenReturn(responseEntity); +// +// // Act & Assert +// try { +// client.signMessage(message, sigValidityOverride); +// } catch (NullPointerException e) { +// assertNotNull(e.getMessage()); +// } +// } + +} \ No newline at end of file From 44b73cc102dbc1daee09d8090bf2d527c1eeec81 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 14:41:55 -0700 Subject: [PATCH 102/128] chore: remove SecurityServicesConfig and related dependencies. --- .../ode/security/ISecurityServicesClient.java | 2 + .../ode/security/SecurityServicesClient.java | 1 - .../ode/security/SecurityServicesConfig.java | 21 ------ .../security/SecurityServicesClientTest.java | 73 ------------------- 4 files changed, 2 insertions(+), 95 deletions(-) delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java index 5c3437a69..436a25c97 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java @@ -1,5 +1,7 @@ package us.dot.its.jpo.ode.security; +import us.dot.its.jpo.ode.security.models.SignatureResultModel; + /** * Interface for interacting with security services that provide cryptographic operations. * It defines methods for signing messages with optional validity overrides. diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java index 1bd4c7792..176418f3b 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java @@ -7,7 +7,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; -import us.dot.its.jpo.ode.security.configuration.SecurityServicesProperties; import us.dot.its.jpo.ode.security.models.SignatureRequestModel; import us.dot.its.jpo.ode.security.models.SignatureResultModel; diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java deleted file mode 100644 index cbd3d12af..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package us.dot.its.jpo.ode.security; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Configuration class for setting up security services related beans. - * - *

This class provides a Spring-managed bean of type ISecurityServicesClient - * that is configured using SecurityServicesProperties and ObjectMapper. - * The client facilitates interaction with external security services for - * cryptographic operations such as message signing.

- */ -@Configuration -public class SecurityServicesConfig { - @Bean - public ISecurityServicesClient securityServicesClient(SecurityServicesProperties securityServicesProperties, ObjectMapper mapper) { - return new SecurityServicesClient(mapper, securityServicesProperties.getSignatureEndpoint()); - } -} diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java index 3de115801..af3d19479 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java @@ -22,7 +22,6 @@ import org.springframework.web.client.RestTemplate; import us.dot.its.jpo.ode.config.SerializationConfig; import us.dot.its.jpo.ode.http.WebClientConfig; -import us.dot.its.jpo.ode.security.configuration.SecurityServicesProperties; import us.dot.its.jpo.ode.security.models.SignatureRequestModel; import us.dot.its.jpo.ode.security.models.SignatureResultModel; @@ -72,76 +71,4 @@ void testSignMessage_WithMockServerSuccessfulResponse() throws JsonProcessingExc SignatureResultModel result = securityServicesClient.signMessage(message, expiryTimeInSeconds); assertEquals(expectedResult, result); } -// -// @Test -// void testSignMessage_EmptyMessage() { -// // Arrange -// RestTemplate mockRestTemplate = Mockito.mock(RestTemplate.class); -// String signatureUri = "http://example.com/signature"; -// String message = ""; -// int sigValidityOverride = 10; -// String mockResponse = "{\"result\":\"mockedResponseEmptyMessage\"}"; -// -// SecurityServicesClient client = new SecurityServicesClient(restTemplate, signatureUri); -// SignatureResultModel expectedResult = new SignatureResultModel(); -// -// ResponseEntity responseEntity = ResponseEntity.ok(mockResponse); -// -// when(mockRestTemplate.postForEntity(eq(signatureUri), any(HttpEntity.class), eq(String.class))) -// .thenReturn(responseEntity); -// when(mapper.convertValue(mockResponse, SignatureResultModel.class)) -// .thenReturn(expectedResult); -// -// // Act -// SignatureResultModel result = client.signMessage(message, sigValidityOverride); -// -// // Assert -// assertNotNull(result); -// assertEquals(expectedResult, result); -// } -// -// @Test -// void testSignMessage_InvalidSignatureUri() { -// // Arrange -// RestTemplate mockRestTemplate = Mockito.mock(RestTemplate.class); -// String signatureUri = "http://invaliduri/signature"; -// String message = "TestMessage"; -// int sigValidityOverride = 10; -// -// SecurityServicesClient client = new SecurityServicesClient(restTemplate, signatureUri); -// -// when(mockRestTemplate.postForEntity(eq(signatureUri), any(HttpEntity.class), eq(String.class))) -// .thenThrow(new RuntimeException("Invalid URI")); -// -// // Act & Assert -// try { -// client.signMessage(message, sigValidityOverride); -// } catch (RuntimeException e) { -// assertEquals("Invalid URI", e.getMessage()); -// } -// } -// -// @Test -// void testSignMessage_NullResponseBody() { -// // Arrange -// RestTemplate mockRestTemplate = Mockito.mock(RestTemplate.class); -// String signatureUri = "http://example.com/signature"; -// String message = "TestMessage"; -// int sigValidityOverride = 10; -// -// SecurityServicesClient client = new SecurityServicesClient(restTemplate, signatureUri); -// -// ResponseEntity responseEntity = ResponseEntity.ok(null); -// -// when(mockRestTemplate.postForEntity(eq(signatureUri), any(HttpEntity.class), eq(String.class))) -// .thenReturn(responseEntity); -// -// // Act & Assert -// try { -// client.signMessage(message, sigValidityOverride); -// } catch (NullPointerException e) { -// assertNotNull(e.getMessage()); -// } -// } - } \ No newline at end of file From 2246c2d79f01be8d67141593ff5dd4320619cfd5 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 14:48:48 -0700 Subject: [PATCH 103/128] chore: correct import paths in Asn1EncodedDataRouter and test --- .../us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 2 +- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index c20858c42..4bef3aa98 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -48,7 +48,7 @@ import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.security.SignatureResultModel; +import us.dot.its.jpo.ode.security.models.SignatureResultModel; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; import us.dot.its.jpo.ode.uper.SupportedMessageType; import us.dot.its.jpo.ode.util.CodecUtils; diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 15fc6091f..178bffc72 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -56,7 +56,7 @@ import us.dot.its.jpo.ode.rsu.RsuProperties; import us.dot.its.jpo.ode.security.ISecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; -import us.dot.its.jpo.ode.security.SignatureResultModel; +import us.dot.its.jpo.ode.security.models.SignatureResultModel; import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; @Slf4j From b920745b3ff2468e3e3b3265af2f8680289c9cdf Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 14:49:04 -0700 Subject: [PATCH 104/128] test: handle null responses and improve error handling in signing. Added `RestClientException` to `signMessage` for better error handling. Implemented a new test case to verify behavior when the signing service returns a null response. --- .../ode/security/SecurityServicesClient.java | 3 ++- .../security/SecurityServicesClientTest.java | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java index 176418f3b..7496a5848 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java @@ -6,6 +6,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import us.dot.its.jpo.ode.security.models.SignatureRequestModel; import us.dot.its.jpo.ode.security.models.SignatureResultModel; @@ -27,7 +28,7 @@ public SecurityServicesClient(RestTemplate restTemplate, SecurityServicesPropert } @Override - public SignatureResultModel signMessage(String message, int sigValidityOverride) { + public SignatureResultModel signMessage(String message, int sigValidityOverride) throws RestClientException { log.info("Sending data to security services module at {} with validity override {} to be signed", signatureUri, sigValidityOverride); HttpHeaders headers = new HttpHeaders(); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java index af3d19479..0d5ab5c15 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java @@ -1,6 +1,7 @@ package us.dot.its.jpo.ode.security; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; @@ -71,4 +72,20 @@ void testSignMessage_WithMockServerSuccessfulResponse() throws JsonProcessingExc SignatureResultModel result = securityServicesClient.signMessage(message, expiryTimeInSeconds); assertEquals(expectedResult, result); } + + @Test + void testSignMessage_WithNullResponse() { + // Arrange + String message = "NullResponseTest"; + var expiryTimeInSeconds = (int) clock.instant().plusSeconds(3600).getEpochSecond(); + + mockServer.expect(ExpectedCount.once(), requestTo("http://localhost:8090/sign")) + .andRespond(withSuccess("", MediaType.APPLICATION_JSON)); + + // Act + SignatureResultModel result = securityServicesClient.signMessage(message, expiryTimeInSeconds); + + // Assert + assertNull(result); + } } \ No newline at end of file From 5c5ba738f56bf94d8f9277f263ee006829e76b21 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 15:45:18 -0700 Subject: [PATCH 105/128] refactor: use autowired props to drive URL in SecurityServicesClientTest --- .../its/jpo/ode/security/SecurityServicesClientTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java index 0d5ab5c15..d965f9aeb 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java @@ -42,6 +42,8 @@ class SecurityServicesClientTest { private RestTemplate restTemplate; @Autowired private SecurityServicesClient securityServicesClient; + @Autowired + private SecurityServicesProperties securityServicesProperties; private MockRestServiceServer mockServer; private final Clock clock = Clock.fixed(Instant.parse("2024-12-26T23:53:21.120Z"), ZoneId.of("UTC")); @@ -66,7 +68,7 @@ void testSignMessage_WithMockServerSuccessfulResponse() throws JsonProcessingExc var expiryTimeInSeconds = (int) clock.instant().plusSeconds(3600).getEpochSecond(); signatureRequestModel.setSigValidityOverride(expiryTimeInSeconds); - mockServer.expect(ExpectedCount.once(), requestTo("http://localhost:8090/sign")) + mockServer.expect(ExpectedCount.once(), requestTo(securityServicesProperties.getSignatureEndpoint())) .andRespond(withSuccess(objectMapper.writeValueAsString(expectedResult), MediaType.APPLICATION_JSON)); SignatureResultModel result = securityServicesClient.signMessage(message, expiryTimeInSeconds); @@ -79,7 +81,7 @@ void testSignMessage_WithNullResponse() { String message = "NullResponseTest"; var expiryTimeInSeconds = (int) clock.instant().plusSeconds(3600).getEpochSecond(); - mockServer.expect(ExpectedCount.once(), requestTo("http://localhost:8090/sign")) + mockServer.expect(ExpectedCount.once(), requestTo(securityServicesProperties.getSignatureEndpoint())) .andRespond(withSuccess("", MediaType.APPLICATION_JSON)); // Act From 411c562654232d935178a646e6da093de5fa9cc2 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 16:05:49 -0700 Subject: [PATCH 106/128] refactor: use MockSecurityServicesClient in Asn1EncodedDataRouterTest --- .../ode/security/ISecurityServicesClient.java | 12 ---- .../ode/security/SecurityServicesClient.java | 23 ++++++-- .../services/asn1/Asn1EncodedDataRouter.java | 6 +- .../asn1/Asn1EncodedDataRouterTest.java | 57 +++++++++++++------ 4 files changed, 60 insertions(+), 38 deletions(-) delete mode 100644 jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java deleted file mode 100644 index 436a25c97..000000000 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/ISecurityServicesClient.java +++ /dev/null @@ -1,12 +0,0 @@ -package us.dot.its.jpo.ode.security; - -import us.dot.its.jpo.ode.security.models.SignatureResultModel; - -/** - * Interface for interacting with security services that provide cryptographic operations. - * It defines methods for signing messages with optional validity overrides. - */ -public interface ISecurityServicesClient { - - public SignatureResultModel signMessage(String message, int sigValidityOverride); -} diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java index 7496a5848..de64cd550 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java @@ -11,13 +11,15 @@ import us.dot.its.jpo.ode.security.models.SignatureRequestModel; import us.dot.its.jpo.ode.security.models.SignatureResultModel; + /** - * Implementation of the ISecurityServicesClient interface for interacting with security - * services that provide cryptographic operations such as message signing. + * A client service that interacts with the security services module to handle + * cryptographic operations requiring signed messages. This service facilitates + * communication with the security services module using REST APIs. */ @Slf4j @Service -public class SecurityServicesClient implements ISecurityServicesClient { +public class SecurityServicesClient { private final String signatureUri; private final RestTemplate restTemplate; @@ -27,16 +29,25 @@ public SecurityServicesClient(RestTemplate restTemplate, SecurityServicesPropert this.signatureUri = securityServicesProperties.getSignatureEndpoint(); } - @Override + /** + * Signs a given message using the security services module. + * + * @param message the message to be signed + * @param sigValidityOverride an optional integer value to override the default validity period of the signature + * + * @return the result of the signing operation including the signed message and its expiration details + * + * @throws RestClientException if there is an error during communication with the security services module + */ public SignatureResultModel signMessage(String message, int sigValidityOverride) throws RestClientException { log.info("Sending data to security services module at {} with validity override {} to be signed", signatureUri, sigValidityOverride); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); var requestBody = new SignatureRequestModel(); requestBody.setMessage(message); requestBody.setSigValidityOverride(sigValidityOverride); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity entity = new HttpEntity<>(requestBody, headers); log.debug("Data to be signed: {}", entity); diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 4bef3aa98..49d6a377e 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -46,7 +46,7 @@ import us.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData; import us.dot.its.jpo.ode.plugin.j2735.builders.GeoRegionBuilder; import us.dot.its.jpo.ode.rsu.RsuDepositor; -import us.dot.its.jpo.ode.security.ISecurityServicesClient; +import us.dot.its.jpo.ode.security.SecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.security.models.SignatureResultModel; import us.dot.its.jpo.ode.traveler.TimTransmogrifier; @@ -83,7 +83,7 @@ public Asn1EncodedDataRouterException(String string) { private final Asn1CoderTopics asn1CoderTopics; private final JsonTopics jsonTopics; private final String sdxDepositTopic; - private final ISecurityServicesClient securityServicesClient; + private final SecurityServicesClient securityServicesClient; private final ObjectMapper mapper; private final OdeTimJsonTopology odeTimJsonTopology; @@ -105,7 +105,7 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, SecurityServicesProperties securityServicesProperties, OdeTimJsonTopology odeTimJsonTopology, RsuDepositor rsuDepositor, - ISecurityServicesClient securityServicesClient, + SecurityServicesClient securityServicesClient, KafkaTemplate kafkaTemplate, @Value("${ode.kafka.topics.sdx-depositor.input}") String sdxDepositTopic, ObjectMapper mapper, diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 178bffc72..c3fb14bd6 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -36,6 +36,7 @@ import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Profile; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; @@ -43,9 +44,14 @@ import org.springframework.kafka.test.EmbeddedKafkaBroker; import org.springframework.kafka.test.utils.ContainerTestUtils; import org.springframework.kafka.test.utils.KafkaTestUtils; +import org.springframework.stereotype.Service; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.config.SerializationConfig; +import us.dot.its.jpo.ode.http.WebClientConfig; import us.dot.its.jpo.ode.kafka.KafkaConsumerConfig; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.TestKafkaStreamsConfig; @@ -54,7 +60,7 @@ import us.dot.its.jpo.ode.kafka.topics.JsonTopics; import us.dot.its.jpo.ode.rsu.RsuDepositor; import us.dot.its.jpo.ode.rsu.RsuProperties; -import us.dot.its.jpo.ode.security.ISecurityServicesClient; +import us.dot.its.jpo.ode.security.SecurityServicesClient; import us.dot.its.jpo.ode.security.SecurityServicesProperties; import us.dot.its.jpo.ode.security.models.SignatureResultModel; import us.dot.its.jpo.ode.test.utilities.EmbeddedKafkaHolder; @@ -80,11 +86,14 @@ Asn1CoderTopics.class, JsonTopics.class, SecurityServicesProperties.class, - RsuProperties.class + RsuProperties.class, + Asn1EncodedDataRouterTest.MockSecurityServicesClient.class, + WebClientConfig.class } ) @EnableConfigurationProperties @DirtiesContext +@ActiveProfiles("test") class Asn1EncodedDataRouterTest { @Autowired @@ -93,29 +102,26 @@ class Asn1EncodedDataRouterTest { JsonTopics jsonTopics; @Autowired SecurityServicesProperties securityServicesProperties; - @Value("${ode.kafka.topics.sdx-depositor.input}") - String sdxDepositorTopic; @Autowired KafkaTemplate kafkaTemplate; @Autowired KafkaConsumerConfig kafkaConsumerConfig; - @Mock - RsuDepositor mockRsuDepositor; @Autowired OdeTimJsonTopology odeTimJsonTopology; @Autowired ObjectMapper objectMapper; @Autowired private XmlMapper xmlMapper; + @Autowired + MockSecurityServicesClient secServicesClient; - private final EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); + @Value("${ode.kafka.topics.sdx-depositor.input}") + String sdxDepositorTopic; + + @Mock + RsuDepositor mockRsuDepositor; - private final ISecurityServicesClient mockSecServClient = (message, sigValidityOverride) -> { - var signatureResponse = new SignatureResultModel(); - signatureResponse.getResult().setMessageSigned("<%s>".formatted(message)); - signatureResponse.getResult().setMessageExpiry(123124124124124141L); - return signatureResponse; - }; + private final EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Test void processSignedMessage() throws IOException { @@ -133,7 +139,7 @@ void processSignedMessage() throws IOException { securityServicesProperties, odeTimJsonTopology, mockRsuDepositor, - mockSecServClient, + secServicesClient, kafkaTemplate, sdxDepositorTopic, objectMapper, xmlMapper); @@ -188,7 +194,7 @@ void processUnsignedMessage() throws IOException { securityServicesProperties, odeTimJsonTopology, mockRsuDepositor, - mockSecServClient, + secServicesClient, kafkaTemplate, sdxDepositorTopic, objectMapper, xmlMapper); @@ -283,8 +289,9 @@ void processEncodedTimUnsigned() throws IOException { securityServicesProperties, odeTimJsonTopology, mockRsuDepositor, - mockSecServClient, - kafkaTemplate, sdxDepositorTopic, + secServicesClient, + kafkaTemplate, + sdxDepositorTopic, objectMapper, xmlMapper); @@ -372,4 +379,20 @@ private static String loadResourceString(String name) assert inputStream != null; return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); } + + @Service + @Profile("test") + static class MockSecurityServicesClient extends SecurityServicesClient { + public MockSecurityServicesClient(RestTemplate restTemplate, SecurityServicesProperties securityServicesProperties) { + super(restTemplate, securityServicesProperties); + } + + @Override + public SignatureResultModel signMessage(String message, int sigValidityOverride) throws RestClientException { + var signatureResponse = new SignatureResultModel(); + signatureResponse.getResult().setMessageSigned("<%s>".formatted(message)); + signatureResponse.getResult().setMessageExpiry(123124124124124141L); + return signatureResponse; + } + } } From 6d60ed2b2ebcea9462bebf42c6f3e0e131034ede Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 16:42:19 -0700 Subject: [PATCH 107/128] refactor: service request extraction to use metadata JSON. Simplified `getServiceRequest` method by directly passing the metadata JSON object instead of the full consumed object. Updated relevant calls to align with this change, improving clarity and reducing redundant operations. --- .../jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 49d6a377e..e43739dd5 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -158,10 +158,9 @@ public void listen(ConsumerRecord consumerRecord) ); } - ServiceRequest request = getServicerequest(consumedObj); - JSONObject payloadData = consumedObj.getJSONObject(AppContext.PAYLOAD_STRING).getJSONObject(AppContext.DATA_STRING); JSONObject metadataJson = consumedObj.getJSONObject(AppContext.METADATA_STRING); + ServiceRequest request = getServiceRequest(metadataJson); if (!payloadData.has(ADVISORY_SITUATION_DATA_STRING)) { processUnsignedMessage(request, metadataJson, payloadData); @@ -176,13 +175,12 @@ public void listen(ConsumerRecord consumerRecord) /** * Gets the service request based on the consumed JSONObject. * - * @param consumedObj The object to retrieve the service request for + * @param metadataJson The metadata JSON object to retrieve the service request for * * @return The service request */ - private ServiceRequest getServicerequest(JSONObject consumedObj) throws JsonProcessingException { - String serviceRequestJson = consumedObj.getJSONObject(AppContext.METADATA_STRING) - .getJSONObject(TimTransmogrifier.REQUEST_STRING).toString(); + private ServiceRequest getServiceRequest(JSONObject metadataJson) throws JsonProcessingException { + String serviceRequestJson = metadataJson.getJSONObject(TimTransmogrifier.REQUEST_STRING).toString(); log.debug("ServiceRequest: {}", serviceRequestJson); return mapper.readValue(serviceRequestJson, ServiceRequest.class); } From 0d30254e7bd15b313c2702c53f23a92ad2a18fc3 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 16:53:48 -0700 Subject: [PATCH 108/128] docs: simplify comments and remove redundant JavaDoc. Removed verbose and redundant JavaDoc comments, replacing them with concise inline comments to improve code readability. This change ensures that key processing logic remains documented without unnecessary detail duplication. --- .../services/asn1/Asn1EncodedDataRouter.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index e43739dd5..dd5596dc8 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -131,14 +131,6 @@ public Asn1EncodedDataRouter(Asn1CoderTopics asn1CoderTopics, /** * Listens for messages from the specified Kafka topic and processes them. * - *

Cases: - * - CASE 1: no SDW in metadata (SNMP deposit only) - sign MF - send to RSU - CASE 2: SDW in - * metadata but no ASD in body (send back for another encoding) - sign MF - send to RSU - craft - * ASD object - publish back to encoder stream - CASE 3: If SDW in metadata and ASD in body - * (double encoding complete) - send to SDX - * - *

- * * @param consumerRecord The Kafka consumer record containing the key and value of the consumed * message. */ @@ -171,20 +163,13 @@ public void listen(ConsumerRecord consumerRecord) } } - - /** - * Gets the service request based on the consumed JSONObject. - * - * @param metadataJson The metadata JSON object to retrieve the service request for - * - * @return The service request - */ private ServiceRequest getServiceRequest(JSONObject metadataJson) throws JsonProcessingException { String serviceRequestJson = metadataJson.getJSONObject(TimTransmogrifier.REQUEST_STRING).toString(); log.debug("ServiceRequest: {}", serviceRequestJson); return mapper.readValue(serviceRequestJson, ServiceRequest.class); } + // If SDW in metadata and ASD in body (double encoding complete) -> send to SDX private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { // Case 3: We have an ASD with signed MessageFrame @@ -196,6 +181,7 @@ private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { kafkaTemplate.send(this.sdxDepositTopic, deposit.toString()); } + // no SDW in metadata (SNMP deposit only) -> sign MF -> send to RSU private void processUnsignedMessage(ServiceRequest request, JSONObject metadataJson, JSONObject payloadJson) { @@ -217,6 +203,8 @@ private void processUnsignedMessage(ServiceRequest request, publishForSecondEncoding(request, encodedTimWithoutHeaders); } + // SDW in metadata but no ASD in body (send back for another encoding) -> sign MessageFrame + // -> send to RSU -> craft ASD object -> publish back to encoder stream private void processEncodedTimUnsigned(ServiceRequest request, JSONObject metadataJson, JSONObject payloadJson) { log.debug("Unsigned ASD received. Depositing it to SDW."); From 69ac11e86a0df8872b223ddf9c704a9c776caaeb Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 16:58:13 -0700 Subject: [PATCH 109/128] docs: remove dead comments --- .../us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index dd5596dc8..6e44f0a90 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -172,7 +172,6 @@ private ServiceRequest getServiceRequest(JSONObject metadataJson) throws JsonPro // If SDW in metadata and ASD in body (double encoding complete) -> send to SDX private void processSignedMessage(ServiceRequest request, JSONObject dataObj) { - // Case 3: We have an ASD with signed MessageFrame JSONObject asdObj = dataObj.getJSONObject(ADVISORY_SITUATION_DATA_STRING); JSONObject deposit = new JSONObject(); @@ -189,7 +188,6 @@ private void processUnsignedMessage(ServiceRequest request, JSONObject messageFrameJson = payloadJson.getJSONObject(MESSAGE_FRAME); var hexEncodedTimBytes = messageFrameJson.getString(BYTES); - // Case 1: SNMP-deposit if (dataSigningEnabledRSU && (request.getSdw() != null || request.getRsus() != null)) { var signedTimWithExpiration = signTimWithExpiration(hexEncodedTimBytes, metadataJson); kafkaTemplate.send(jsonTopics.getTimCertExpiration(), signedTimWithExpiration); From 1e7ffbcca986cdb980933fbefd50bb0dd6696c70 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 17:00:06 -0700 Subject: [PATCH 110/128] chore: simplify RSU message handling logic. Removed redundant null checks before calling `sendToRsus` in the main logic. The null checks are properly handled within the `sendToRsus` method, ensuring clearer and more maintainable code. --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 6e44f0a90..5775a84a4 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -224,10 +224,7 @@ private void processEncodedTimUnsigned(ServiceRequest request, JSONObject metada var encodedTimWithoutHeader = stripHeader(encodedTim); log.debug("Encoded message - phase 2: {}", encodedTimWithoutHeader); - // only send message to rsu if snmp, rsus, and message frame fields are present - if (null != request.getSnmp() && null != request.getRsus()) { - sendToRsus(request, encodedTimWithoutHeader); - } + sendToRsus(request, encodedTimWithoutHeader); } } @@ -353,7 +350,7 @@ private ArrayNode buildEncodings() throws JsonUtilsException { private void sendToRsus(ServiceRequest request, String encodedMsg) { if (null == request.getSnmp() || null == request.getRsus()) { - log.debug("No RSUs or SNMP provided. Skipping sending to RSUs."); + log.debug("No RSUs or SNMP provided. Not sending to RSUs."); return; } log.info("Sending message to RSUs..."); From 7a58bcefc5f41eb8711adbe598c61c95f4d62bd3 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 17:01:15 -0700 Subject: [PATCH 111/128] chore: simplify error message for SDX deposit failure --- .../dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 5775a84a4..cde245dbd 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -66,7 +66,6 @@ public class Asn1EncodedDataRouter { private static final String BYTES = "bytes"; private static final String MESSAGE_FRAME = "MessageFrame"; - private static final String ERROR_ON_SDX_DEPOSIT = "Error on SDX deposit."; private static final String ADVISORY_SITUATION_DATA_STRING = "AdvisorySituationData"; private final KafkaTemplate kafkaTemplate; private final XmlMapper xmlMapper; @@ -236,7 +235,7 @@ private void depositToSdx(ServiceRequest request, String asdBytes) { kafkaTemplate.send(this.sdxDepositTopic, deposit.toString()); log.info("SDX deposit successful."); } catch (Exception e) { - log.error(ERROR_ON_SDX_DEPOSIT, e); + log.error("Failed to deposit to SDX", e); } } From fd715ab5000aac705933a7f7b422f49ca857d214 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 17:10:18 -0700 Subject: [PATCH 112/128] refactor: don't use TimTransmorgrifier to buildEncodings Standardizing on the ObjectMapper provided in the app context will reduce variability and decrease risk of unintended differences in serdes behavior --- .../jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index cde245dbd..2635181a1 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -35,6 +35,7 @@ import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; import us.dot.its.jpo.ode.kafka.topics.JsonTopics; +import us.dot.its.jpo.ode.model.Asn1Encoding; import us.dot.its.jpo.ode.model.Asn1Encoding.EncodingRule; import us.dot.its.jpo.ode.model.OdeAsdPayload; import us.dot.its.jpo.ode.model.OdeAsn1Data; @@ -52,7 +53,6 @@ import us.dot.its.jpo.ode.traveler.TimTransmogrifier; import us.dot.its.jpo.ode.uper.SupportedMessageType; import us.dot.its.jpo.ode.util.CodecUtils; -import us.dot.its.jpo.ode.util.JsonUtils.JsonUtilsException; import us.dot.its.jpo.ode.util.XmlUtils; import us.dot.its.jpo.ode.util.XmlUtils.XmlUtilsException; @@ -276,8 +276,7 @@ private String signTimWithExpiration(String encodedTIM, JSONObject metadataJson) * @return a String containing the fully crafted ASD message in XML format. Returns null if the * message could not be constructed due to exceptions. */ - private String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) throws JsonProcessingException, ParseException, - JsonUtilsException { + private String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) throws JsonProcessingException, ParseException { SDW sdw = request.getSdw(); SNMP snmp = request.getSnmp(); DdsAdvisorySituationData asd; @@ -339,11 +338,10 @@ private String packageSignedTimIntoAsd(ServiceRequest request, String signedMsg) return outputXml; } - private ArrayNode buildEncodings() throws JsonUtilsException { + private ArrayNode buildEncodings() throws JsonProcessingException { ArrayNode encodings = mapper.createArrayNode(); - encodings.add(TimTransmogrifier.buildEncodingNode(ADVISORY_SITUATION_DATA_STRING, - ADVISORY_SITUATION_DATA_STRING, - EncodingRule.UPER)); + var encoding = new Asn1Encoding(ADVISORY_SITUATION_DATA_STRING, ADVISORY_SITUATION_DATA_STRING, EncodingRule.UPER); + encodings.add(mapper.readTree(mapper.writeValueAsString(encoding))); return encodings; } From bcfcfc69679a8da21f21d4f243412291122fafdf Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 26 Dec 2024 17:29:33 -0700 Subject: [PATCH 113/128] chore: rename buildJsonTimFromPacket to buildTimFromPacket This name better represents the behavior of the method. The Javadocs were updated to be accurate as well --- .../main/java/us/dot/its/jpo/ode/udp/UdpHexDecoder.java | 8 ++++---- .../us/dot/its/jpo/ode/udp/generic/GenericReceiver.java | 2 +- .../main/java/us/dot/its/jpo/ode/udp/tim/TimReceiver.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/UdpHexDecoder.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/UdpHexDecoder.java index 50fdbd0c6..bdf485070 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/UdpHexDecoder.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/UdpHexDecoder.java @@ -142,14 +142,14 @@ public static String buildJsonSpatFromPacket(DatagramPacket packet) } /** - * Converts the data from the given {@link DatagramPacket} into a JSON string representing a TIM - * message. It extracts metadata and payload, then structures them into a JSON format. + * Converts the data from the given {@link DatagramPacket} into a TIM + * message. It extracts metadata and payload, then structures them into an {@link OdeAsn1Data} object * * @param packet the DatagramPacket containing the TIM data - * @return a JSON string representing the TIM message + * @return an {@link OdeAsn1Data} object representing the TIM message * @throws InvalidPayloadException if the payload extraction fails */ - public static OdeAsn1Data buildJsonTimFromPacket(DatagramPacket packet) + public static OdeAsn1Data buildTimFromPacket(DatagramPacket packet) throws InvalidPayloadException { String senderIp = packet.getAddress().getHostAddress(); int senderPort = packet.getPort(); diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java index 5f2486700..c56d2a858 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/generic/GenericReceiver.java @@ -103,7 +103,7 @@ private void routeMessageByMessageType( } } case "TIM" -> { - var tim = UdpHexDecoder.buildJsonTimFromPacket(packet); + var tim = UdpHexDecoder.buildTimFromPacket(packet); var timJson = JsonUtils.toJson(tim, false); if (timJson != null) { // We need to include the serialID as the key when publishing TIMs. Otherwise, the diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/tim/TimReceiver.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/tim/TimReceiver.java index 9cfff90ce..85a1033cc 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/tim/TimReceiver.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/udp/tim/TimReceiver.java @@ -52,7 +52,7 @@ public void run() { socket.receive(packet); if (packet.getLength() > 0) { - var tim = UdpHexDecoder.buildJsonTimFromPacket(packet); + var tim = UdpHexDecoder.buildTimFromPacket(packet); var timJson = tim.toJson(); if (timJson != null) { timPublisher.send(publishTopic, tim.getMetadata().getSerialId().toString(), timJson); From e7577463e9e01b178914e96d3fd745b11f325f54 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 27 Dec 2024 10:21:12 -0700 Subject: [PATCH 114/128] refactor: don't use KafkaListenerConfig in Asn1EncodedDataRouterTest for Kafka container setup Replaced ConcurrentMessageListenerContainer with KafkaMessageListenerContainer and removed KafkaConsumerConfig dependency. This fixes an issue where we would intermittently encounter the following error when running tests: java.lang.IllegalStateException: Expected 1 but got 0 partitions at org.springframework.kafka.test.utils.ContainerTestUtils.waitForAssignment(ContainerTestUtils.java:85) --- .../services/asn1/Asn1EncodedDataRouterTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index c3fb14bd6..b9a432ed7 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -39,7 +39,8 @@ import org.springframework.context.annotation.Profile; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; +import org.springframework.kafka.listener.ContainerProperties; +import org.springframework.kafka.listener.KafkaMessageListenerContainer; import org.springframework.kafka.listener.MessageListener; import org.springframework.kafka.test.EmbeddedKafkaBroker; import org.springframework.kafka.test.utils.ContainerTestUtils; @@ -52,7 +53,6 @@ import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.config.SerializationConfig; import us.dot.its.jpo.ode.http.WebClientConfig; -import us.dot.its.jpo.ode.kafka.KafkaConsumerConfig; import us.dot.its.jpo.ode.kafka.OdeKafkaProperties; import us.dot.its.jpo.ode.kafka.TestKafkaStreamsConfig; import us.dot.its.jpo.ode.kafka.producer.KafkaProducerConfig; @@ -81,7 +81,6 @@ KafkaProducerConfig.class, SerializationConfig.class, KafkaProperties.class, - KafkaConsumerConfig.class, TestKafkaStreamsConfig.class, Asn1CoderTopics.class, JsonTopics.class, @@ -105,8 +104,6 @@ class Asn1EncodedDataRouterTest { @Autowired KafkaTemplate kafkaTemplate; @Autowired - KafkaConsumerConfig kafkaConsumerConfig; - @Autowired OdeTimJsonTopology odeTimJsonTopology; @Autowired ObjectMapper objectMapper; @@ -341,11 +338,14 @@ private static String replaceStreamId(String input, String streamId) { return input.replaceAll(".*?", "" + streamId + ""); } - private ConcurrentMessageListenerContainer setupListenerContainer( + private KafkaMessageListenerContainer setupListenerContainer( Asn1EncodedDataRouter encoderRouter, String containerName) { - var container = kafkaConsumerConfig.kafkaListenerContainerFactory() - .createContainer(asn1CoderTopics.getEncoderOutput()); + var consumerProps = KafkaTestUtils.consumerProps(containerName, "false", embeddedKafka); + DefaultKafkaConsumerFactory consumerFactory = + new DefaultKafkaConsumerFactory<>(consumerProps, new StringDeserializer(), new StringDeserializer()); + ContainerProperties containerProperties = new ContainerProperties(asn1CoderTopics.getEncoderOutput()); + KafkaMessageListenerContainer container = new KafkaMessageListenerContainer<>(consumerFactory, containerProperties); container.setupMessageListener( (MessageListener) consumerRecord -> { try { From 1018461710ab6cfc2021ab63aae361c2973e73a3 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 27 Dec 2024 11:32:44 -0700 Subject: [PATCH 115/128] fix: incorrect expiration date calculation in TIM processing. Updated the expiration date calculation to use `Instant` and ensure accuracy by handling milliseconds properly. Adjusted test cases and mock classes to align with the updated logic. Fixed incorrect test data to reflect the corrected expiration date format. --- .../its/jpo/ode/security/models/SignatureResultModel.java | 1 + .../its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 7 ++++--- .../jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 7 ++++++- .../jpo/ode/services/asn1/expected-tim-cert-expired.json | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java index 9b04b2ab6..3377b62ed 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java @@ -30,6 +30,7 @@ public class SignatureResultModel { @NoArgsConstructor public static class Result { private String messageSigned; + // messageExpiry is in seconds private Long messageExpiry; } } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 2635181a1..798387e00 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Date; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; @@ -371,9 +372,9 @@ private void setExpiryDate(SignatureResultModel signedResponse, JSONObject timWithExpiration, SimpleDateFormat dateFormat) { try { - timWithExpiration.put("expirationDate", - dateFormat.format(new Date(signedResponse.getResult().getMessageExpiry() * 1000)) - ); + var messageExpiryMillis = signedResponse.getResult().getMessageExpiry() * 1000; + var expiryDate = Date.from(Instant.ofEpochMilli(messageExpiryMillis)); + timWithExpiration.put("expirationDate", dateFormat.format(expiryDate)); } catch (Exception e) { log.error("Unable to get expiration date from signed messages response. Setting expirationData to 'null'", e); timWithExpiration.put("expirationDate", "null"); diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index b9a432ed7..bab8a2910 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -24,6 +24,9 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.Consumer; @@ -383,6 +386,8 @@ private static String loadResourceString(String name) @Service @Profile("test") static class MockSecurityServicesClient extends SecurityServicesClient { + private static final Clock clock = Clock.fixed(Instant.parse("2024-03-08T16:37:05.414Z"), ZoneId.of("UTC")); + public MockSecurityServicesClient(RestTemplate restTemplate, SecurityServicesProperties securityServicesProperties) { super(restTemplate, securityServicesProperties); } @@ -391,7 +396,7 @@ public MockSecurityServicesClient(RestTemplate restTemplate, SecurityServicesPro public SignatureResultModel signMessage(String message, int sigValidityOverride) throws RestClientException { var signatureResponse = new SignatureResultModel(); signatureResponse.getResult().setMessageSigned("<%s>".formatted(message)); - signatureResponse.getResult().setMessageExpiry(123124124124124141L); + signatureResponse.getResult().setMessageExpiry(clock.instant().getEpochSecond() + 1000); return signatureResponse; } } diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json index 201a12a94..95c06d453 100644 --- a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json @@ -1 +1 @@ -{"packetID":"03B027CF2071328E12","startDateTime":"2024-03-08T16:37:05.414Z","requiredExpirationDate":"2024-03-08T21:34:05.414Z","expirationDate":"190224297-08-07T14:27:59.688Z"} \ No newline at end of file +{"packetID":"03B027CF2071328E12","startDateTime":"2024-03-08T16:37:05.414Z","requiredExpirationDate":"2024-03-08T21:34:05.414Z","expirationDate":"2024-03-08T09:53:45.000Z"} \ No newline at end of file From a765be75742b1d1f9bb59cadc9f116c12000b728 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 27 Dec 2024 12:57:59 -0700 Subject: [PATCH 116/128] style: reorganize methods in Asn1EncodedDataRouterTest --- .../asn1/Asn1EncodedDataRouterTest.java | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index bab8a2910..f006549ce 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -98,6 +98,7 @@ @ActiveProfiles("test") class Asn1EncodedDataRouterTest { + private final EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); @Autowired Asn1CoderTopics asn1CoderTopics; @Autowired @@ -111,8 +112,6 @@ class Asn1EncodedDataRouterTest { @Autowired ObjectMapper objectMapper; @Autowired - private XmlMapper xmlMapper; - @Autowired MockSecurityServicesClient secServicesClient; @Value("${ode.kafka.topics.sdx-depositor.input}") @@ -120,8 +119,30 @@ class Asn1EncodedDataRouterTest { @Mock RsuDepositor mockRsuDepositor; + @Autowired + private XmlMapper xmlMapper; - private final EmbeddedKafkaBroker embeddedKafka = EmbeddedKafkaHolder.getEmbeddedKafka(); + private static String stripGeneratedFields(String expectedEncoderInput) { + return expectedEncoderInput + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", "") + .replaceAll(".*?", ""); + } + + private static String replaceStreamId(String input, String streamId) { + return input.replaceAll(".*?", "" + streamId + ""); + } + + private static String loadResourceString(String name) + throws IOException { + String resourcePackagePath = "us/dot/its/jpo/ode/services/asn1/"; + InputStream inputStream; + inputStream = Asn1EncodedDataRouterTest.class.getClassLoader() + .getResourceAsStream(resourcePackagePath + name); + assert inputStream != null; + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } @Test void processSignedMessage() throws IOException { @@ -265,14 +286,6 @@ void processUnsignedMessage() throws IOException { log.debug("processUnsignedMessage container stopped"); } - private static String stripGeneratedFields(String expectedEncoderInput) { - return expectedEncoderInput - .replaceAll(".*?", "") - .replaceAll(".*?", "") - .replaceAll(".*?", "") - .replaceAll(".*?", ""); - } - @Test void processEncodedTimUnsigned() throws IOException { String[] topicsForConsumption = { @@ -337,10 +350,6 @@ void processEncodedTimUnsigned() throws IOException { log.debug("processEncodedTimUnsigned container stopped"); } - private static String replaceStreamId(String input, String streamId) { - return input.replaceAll(".*?", "" + streamId + ""); - } - private KafkaMessageListenerContainer setupListenerContainer( Asn1EncodedDataRouter encoderRouter, String containerName) { @@ -373,16 +382,6 @@ private Consumer createTestConsumer(String group) { return consumerFactory.createConsumer(); } - private static String loadResourceString(String name) - throws IOException { - String resourcePackagePath = "us/dot/its/jpo/ode/services/asn1/"; - InputStream inputStream; - inputStream = Asn1EncodedDataRouterTest.class.getClassLoader() - .getResourceAsStream(resourcePackagePath + name); - assert inputStream != null; - return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - } - @Service @Profile("test") static class MockSecurityServicesClient extends SecurityServicesClient { From 285c880b92d42a856487c364f243bae9a2544457 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 27 Dec 2024 13:37:24 -0700 Subject: [PATCH 117/128] fix: replace outdated Date handling to remove system time variability Replaced the outdated `SimpleDateFormat` with the modern `DateTimeFormatter` to handle date formatting and parsing. This improves thread safety and code readability while ensuring alignment with Java's modern date/time APIs. Adjusted test resources to reflect updated timestamp formatting logic. --- .../services/asn1/Asn1EncodedDataRouter.java | 23 ++++++++++--------- .../asn1/expected-tim-cert-expired.json | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 798387e00..0c6af7ca7 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -22,9 +22,11 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import java.text.ParseException; -import java.text.SimpleDateFormat; import java.time.Instant; -import java.util.Date; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.json.JSONObject; @@ -259,7 +261,7 @@ private String signTimWithExpiration(String encodedTIM, JSONObject metadataJson) timWithExpiration.put("packetID", packetId); timWithExpiration.put("startDateTime", timStartDateTime); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + var dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); setExpiryDate(signedResponse, timWithExpiration, dateFormat); setRequiredExpiryDate(dateFormat, timStartDateTime, maxDurationTime, timWithExpiration); return timWithExpiration.toString(); @@ -355,13 +357,12 @@ private void sendToRsus(ServiceRequest request, String encodedMsg) { rsuDepositor.deposit(request, encodedMsg); } - private void setRequiredExpiryDate(SimpleDateFormat dateFormat, String timStartDateTime, + private void setRequiredExpiryDate(DateTimeFormatter dateFormat, String timStartDateTime, int maxDurationTime, JSONObject timWithExpiration) { try { - Date timTimestamp = dateFormat.parse(timStartDateTime); - Date requiredExpirationDate = new Date(); - requiredExpirationDate.setTime(timTimestamp.getTime() + maxDurationTime); - timWithExpiration.put("requiredExpirationDate", dateFormat.format(requiredExpirationDate)); + var timStartLocalDate = LocalDateTime.ofInstant(Instant.parse(timStartDateTime), ZoneId.of("UTC")); + var expiryDate = timStartLocalDate.plus(maxDurationTime, ChronoUnit.MILLIS); + timWithExpiration.put("requiredExpirationDate", expiryDate.format(dateFormat)); } catch (Exception e) { log.error("Unable to parse requiredExpirationDate. Setting requiredExpirationDate to 'null'", e); timWithExpiration.put("requiredExpirationDate", "null"); @@ -370,11 +371,11 @@ private void setRequiredExpiryDate(SimpleDateFormat dateFormat, String timStartD private void setExpiryDate(SignatureResultModel signedResponse, JSONObject timWithExpiration, - SimpleDateFormat dateFormat) { + DateTimeFormatter dateFormat) { try { var messageExpiryMillis = signedResponse.getResult().getMessageExpiry() * 1000; - var expiryDate = Date.from(Instant.ofEpochMilli(messageExpiryMillis)); - timWithExpiration.put("expirationDate", dateFormat.format(expiryDate)); + var expiryDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(messageExpiryMillis), ZoneId.of("UTC")); + timWithExpiration.put("expirationDate", expiryDate.format(dateFormat)); } catch (Exception e) { log.error("Unable to get expiration date from signed messages response. Setting expirationData to 'null'", e); timWithExpiration.put("expirationDate", "null"); diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json index 95c06d453..752d77467 100644 --- a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-tim-cert-expired.json @@ -1 +1 @@ -{"packetID":"03B027CF2071328E12","startDateTime":"2024-03-08T16:37:05.414Z","requiredExpirationDate":"2024-03-08T21:34:05.414Z","expirationDate":"2024-03-08T09:53:45.000Z"} \ No newline at end of file +{"packetID":"03B027CF2071328E12","startDateTime":"2024-03-08T16:37:05.414Z","requiredExpirationDate":"2024-03-08T21:34:05.414Z","expirationDate":"2024-03-08T16:53:45.000Z"} \ No newline at end of file From 1c75ab62df2f9145b7b57a7868cae8a5ce2e2d18 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Fri, 27 Dec 2024 15:34:19 -0700 Subject: [PATCH 118/128] fix: handle RestClientException during TIM signing process Add exception handling to catch RestClientException when signing TIM messages. Log detailed error messages, including specific handling for HttpClientErrorException.NotFound, to provide better debugging information and highlight potential misconfiguration of jpo-security-svcs. --- .../services/asn1/Asn1EncodedDataRouter.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 0c6af7ca7..d7632e778 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -34,6 +34,7 @@ import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; @@ -191,8 +192,7 @@ private void processUnsignedMessage(ServiceRequest request, var hexEncodedTimBytes = messageFrameJson.getString(BYTES); if (dataSigningEnabledRSU && (request.getSdw() != null || request.getRsus() != null)) { - var signedTimWithExpiration = signTimWithExpiration(hexEncodedTimBytes, metadataJson); - kafkaTemplate.send(jsonTopics.getTimCertExpiration(), signedTimWithExpiration); + depositToTimCertExpirationTopic(metadataJson, hexEncodedTimBytes); } log.debug("Encoded message - phase 1: {}", hexEncodedTimBytes); @@ -203,6 +203,22 @@ private void processUnsignedMessage(ServiceRequest request, publishForSecondEncoding(request, encodedTimWithoutHeaders); } + private void depositToTimCertExpirationTopic(JSONObject metadataJson, String hexEncodedTimBytes) { + try { + var signedTimWithExpiration = signTimWithExpiration(hexEncodedTimBytes, metadataJson); + kafkaTemplate.send(jsonTopics.getTimCertExpiration(), signedTimWithExpiration); + } catch (HttpClientErrorException.NotFound e) { + // The jpo-security-svcs module returns a 404 Not Found response when it can't reach out to its external signing service. + // the body of the response contains the unmodified value of `message` in the `result`. It may be possible to recover + // from this specific exception, but at this time we are not certain what downstream effects would be if we published an unsigned tim + // to the TimCertExpiration topic. + log.error("Unable to sign message. The jpo-security-svcs application may not be properly configured. See error message for more detail {}", + e.getMessage()); + } catch (Exception e) { + log.error("Unable to sign message. Error: {}", e.getMessage(), e); + } + } + // SDW in metadata but no ASD in body (send back for another encoding) -> sign MessageFrame // -> send to RSU -> craft ASD object -> publish back to encoder stream private void processEncodedTimUnsigned(ServiceRequest request, JSONObject metadataJson, JSONObject payloadJson) { From dc48020e69e8144418a963f0a8afd9145cf0c004 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Mon, 30 Dec 2024 10:57:48 -0700 Subject: [PATCH 119/128] chore: correct TIM header processing in Asn1EncodedDataRouter Convert TIM start flag to uppercase for consistency and improve logging to dynamically include the expected start flag value. This enhances clarity and ensures robust matching during header stripping. --- .../dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index d7632e778..1d26c65f0 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -403,9 +403,9 @@ private void setExpiryDate(SignatureResultModel signedResponse, */ private String stripHeader(String encodedUnsignedTim) { // find 001F hex value - int index = encodedUnsignedTim.indexOf(SupportedMessageType.TIM.getStartFlag()); + int index = encodedUnsignedTim.indexOf(SupportedMessageType.TIM.getStartFlag().toUpperCase()); if (index == -1) { - log.warn("No '001F' hex value found in encoded message"); + log.warn("No {} hex value found in encoded message", SupportedMessageType.TIM.getStartFlag()); return encodedUnsignedTim; } // strip everything before 001F From 70827e75db2e14291f32daf42f92b6bd6ac794c2 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 2 Jan 2025 09:41:40 -0700 Subject: [PATCH 120/128] refactor: TIM signing and certification expiration logic. Consolidated signing logic into `depositToTimCertExpirationTopic` and removed redundant `signTimWithExpiration` method. Added utility for obtaining hex-encoded signed messages in `SignatureResultModel` to streamline encoding operations. --- .../security/models/SignatureResultModel.java | 6 +++ .../services/asn1/Asn1EncodedDataRouter.java | 51 ++++++++----------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java index 3377b62ed..330c96b2f 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/models/SignatureResultModel.java @@ -2,6 +2,7 @@ import lombok.Data; import lombok.NoArgsConstructor; +import us.dot.its.jpo.ode.util.CodecUtils; /** * Represents the result of a message signing operation provided by a security @@ -32,5 +33,10 @@ public static class Result { private String messageSigned; // messageExpiry is in seconds private Long messageExpiry; + + public String getHexEncodedMessageSigned() { + return CodecUtils.toHex( + CodecUtils.fromBase64(messageSigned)); + } } } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 1d26c65f0..8448b574e 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -192,7 +192,16 @@ private void processUnsignedMessage(ServiceRequest request, var hexEncodedTimBytes = messageFrameJson.getString(BYTES); if (dataSigningEnabledRSU && (request.getSdw() != null || request.getRsus() != null)) { - depositToTimCertExpirationTopic(metadataJson, hexEncodedTimBytes); + log.debug("Signing encoded TIM message..."); + String base64EncodedTim = CodecUtils.toBase64(CodecUtils.fromHex(hexEncodedTimBytes)); + + // get max duration time and convert from minutes to milliseconds + // (unsigned integer valid 0 to 2^32-1 in units of milliseconds) from metadata + int maxDurationTime = Integer.parseInt(metadataJson.get("maxDurationTime").toString()) + * 60 * 1000; + var signedResponse = securityServicesClient.signMessage(base64EncodedTim, maxDurationTime); + depositToTimCertExpirationTopic(metadataJson, signedResponse, maxDurationTime); + hexEncodedTimBytes = signedResponse.getResult().getHexEncodedMessageSigned(); } log.debug("Encoded message - phase 1: {}", hexEncodedTimBytes); @@ -203,10 +212,19 @@ private void processUnsignedMessage(ServiceRequest request, publishForSecondEncoding(request, encodedTimWithoutHeaders); } - private void depositToTimCertExpirationTopic(JSONObject metadataJson, String hexEncodedTimBytes) { + private void depositToTimCertExpirationTopic(JSONObject metadataJson, SignatureResultModel signedResponse, int maxDurationTime) { try { - var signedTimWithExpiration = signTimWithExpiration(hexEncodedTimBytes, metadataJson); - kafkaTemplate.send(jsonTopics.getTimCertExpiration(), signedTimWithExpiration); + String packetId = metadataJson.getString("odePacketID"); + String timStartDateTime = metadataJson.getString("odeTimStartDateTime"); + JSONObject timWithExpiration = new JSONObject(); + timWithExpiration.put("packetID", packetId); + timWithExpiration.put("startDateTime", timStartDateTime); + + var dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + setExpiryDate(signedResponse, timWithExpiration, dateFormat); + setRequiredExpiryDate(dateFormat, timStartDateTime, maxDurationTime, timWithExpiration); + + kafkaTemplate.send(jsonTopics.getTimCertExpiration(), timWithExpiration.toString()); } catch (HttpClientErrorException.NotFound e) { // The jpo-security-svcs module returns a 404 Not Found response when it can't reach out to its external signing service. // the body of the response contains the unmodified value of `message` in the `result`. It may be possible to recover @@ -258,31 +276,6 @@ private void depositToSdx(ServiceRequest request, String asdBytes) { } } - private String signTimWithExpiration(String encodedTIM, JSONObject metadataJson) { - log.debug("Signing encoded TIM message..."); - String base64EncodedTim = CodecUtils.toBase64( - CodecUtils.fromHex(encodedTIM)); - - // get max duration time and convert from minutes to milliseconds (unsigned - // integer valid 0 to 2^32-1 in units of - // milliseconds.) from metadata - int maxDurationTime = Integer.parseInt(metadataJson.get("maxDurationTime").toString()) - * 60 * 1000; - String packetId = metadataJson.getString("odePacketID"); - String timStartDateTime = metadataJson.getString("odeTimStartDateTime"); - log.debug("SENDING: {}", base64EncodedTim); - var signedResponse = securityServicesClient.signMessage(base64EncodedTim, maxDurationTime); - - JSONObject timWithExpiration = new JSONObject(); - timWithExpiration.put("packetID", packetId); - timWithExpiration.put("startDateTime", timStartDateTime); - - var dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - setExpiryDate(signedResponse, timWithExpiration, dateFormat); - setRequiredExpiryDate(dateFormat, timStartDateTime, maxDurationTime, timWithExpiration); - return timWithExpiration.toString(); - } - /** * Constructs an XML representation of an Advisory Situation Data (ASD) message containing a * signed Traveler Information Message (TIM). Processes the provided service request and signed From 52209b498b50f0e4188888bdfb3d78d3f28e70c2 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 2 Jan 2025 09:44:33 -0700 Subject: [PATCH 121/128] refactor: TIM signing exception handling If we fail to sign the message, we should fail to process the message further. We don't want any unsigned messages in our system (if signing is enabled) --- .../services/asn1/Asn1EncodedDataRouter.java | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index 8448b574e..b0f5a0cbc 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -34,7 +34,6 @@ import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Component; -import org.springframework.web.client.HttpClientErrorException; import us.dot.its.jpo.ode.OdeTimJsonTopology; import us.dot.its.jpo.ode.context.AppContext; import us.dot.its.jpo.ode.kafka.topics.Asn1CoderTopics; @@ -213,28 +212,17 @@ private void processUnsignedMessage(ServiceRequest request, } private void depositToTimCertExpirationTopic(JSONObject metadataJson, SignatureResultModel signedResponse, int maxDurationTime) { - try { - String packetId = metadataJson.getString("odePacketID"); - String timStartDateTime = metadataJson.getString("odeTimStartDateTime"); - JSONObject timWithExpiration = new JSONObject(); - timWithExpiration.put("packetID", packetId); - timWithExpiration.put("startDateTime", timStartDateTime); - - var dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - setExpiryDate(signedResponse, timWithExpiration, dateFormat); - setRequiredExpiryDate(dateFormat, timStartDateTime, maxDurationTime, timWithExpiration); - - kafkaTemplate.send(jsonTopics.getTimCertExpiration(), timWithExpiration.toString()); - } catch (HttpClientErrorException.NotFound e) { - // The jpo-security-svcs module returns a 404 Not Found response when it can't reach out to its external signing service. - // the body of the response contains the unmodified value of `message` in the `result`. It may be possible to recover - // from this specific exception, but at this time we are not certain what downstream effects would be if we published an unsigned tim - // to the TimCertExpiration topic. - log.error("Unable to sign message. The jpo-security-svcs application may not be properly configured. See error message for more detail {}", - e.getMessage()); - } catch (Exception e) { - log.error("Unable to sign message. Error: {}", e.getMessage(), e); - } + String packetId = metadataJson.getString("odePacketID"); + String timStartDateTime = metadataJson.getString("odeTimStartDateTime"); + JSONObject timWithExpiration = new JSONObject(); + timWithExpiration.put("packetID", packetId); + timWithExpiration.put("startDateTime", timStartDateTime); + + var dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + setExpiryDate(signedResponse, timWithExpiration, dateFormat); + setRequiredExpiryDate(dateFormat, timStartDateTime, maxDurationTime, timWithExpiration); + + kafkaTemplate.send(jsonTopics.getTimCertExpiration(), timWithExpiration.toString()); } // SDW in metadata but no ASD in body (send back for another encoding) -> sign MessageFrame From 789acc963aab8f67562371ed5dd5c396cf228147 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 2 Jan 2025 09:46:54 -0700 Subject: [PATCH 122/128] test: add better test failure messaging --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index f006549ce..b777c5d52 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -280,7 +280,8 @@ void processUnsignedMessage() throws IOException { break; } } - assertTrue(foundValidRecordInEncoderInput); + assertTrue(foundValidRecordInEncoderInput, + "found records not containing expected value: %s".formatted(encoderInputRecords.records(asn1CoderTopics.getEncoderInput()))); container.stop(); log.debug("processUnsignedMessage container stopped"); From c63b92d08eb3035053adda8c54eedd7505ee1462 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 2 Jan 2025 09:49:59 -0700 Subject: [PATCH 123/128] test: add better test failure messaging --- .../its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index b777c5d52..cd5eaa11d 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -281,7 +281,7 @@ void processUnsignedMessage() throws IOException { } } assertTrue(foundValidRecordInEncoderInput, - "found records not containing expected value: %s".formatted(encoderInputRecords.records(asn1CoderTopics.getEncoderInput()))); + "found records not containing expected value: %s".formatted(encoderInputRecords.records(asn1CoderTopics.getEncoderInput()).toString())); container.stop(); log.debug("processUnsignedMessage container stopped"); From c8055ad6d77a4074f03d013040eedf7cda5778a9 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 2 Jan 2025 09:53:55 -0700 Subject: [PATCH 124/128] test: add better test failure messaging (actually stringify iterable) --- .../ode/services/asn1/Asn1EncodedDataRouterTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index cd5eaa11d..51d1e3aaa 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -280,8 +280,16 @@ void processUnsignedMessage() throws IOException { break; } } + var foundRecordString = ""; + if (!foundValidRecordInEncoderInput) { + StringBuilder stringBuilder = new StringBuilder(); + for (var consumerRecord : encoderInputRecords.records(asn1CoderTopics.getEncoderInput())) { + stringBuilder.append(consumerRecord.value()).append("\n"); + } + foundRecordString = stringBuilder.toString(); + } assertTrue(foundValidRecordInEncoderInput, - "found records not containing expected value: %s".formatted(encoderInputRecords.records(asn1CoderTopics.getEncoderInput()).toString())); + "found records not containing expected value: %s".formatted(foundRecordString)); container.stop(); log.debug("processUnsignedMessage container stopped"); From bd9f82024cdab8434a2646b04a85ebf2532b87cd Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 2 Jan 2025 10:33:28 -0700 Subject: [PATCH 125/128] test: simplify testing assertions for better failure reporting --- .../asn1/Asn1EncodedDataRouterTest.java | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java index 51d1e3aaa..f1fa2a6ec 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouterTest.java @@ -271,26 +271,11 @@ void processUnsignedMessage() throws IOException { asn1CoderTopics.getEncoderInput()); var expectedEncoderInput = loadResourceString("expected-asn1-encoded-router-snmp-deposit.xml"); var expectedEncoderInputWithStableFieldsOnly = stripGeneratedFields(expectedEncoderInput); - var foundValidRecordInEncoderInput = false; var encoderInputRecords = KafkaTestUtils.getRecords(encoderInputConsumer); for (var consumerRecord : encoderInputRecords.records(asn1CoderTopics.getEncoderInput())) { var encoderInputWithStableFieldsOnly = stripGeneratedFields(consumerRecord.value()); - if (expectedEncoderInputWithStableFieldsOnly.equals(encoderInputWithStableFieldsOnly)) { - foundValidRecordInEncoderInput = true; - break; - } - } - var foundRecordString = ""; - if (!foundValidRecordInEncoderInput) { - StringBuilder stringBuilder = new StringBuilder(); - for (var consumerRecord : encoderInputRecords.records(asn1CoderTopics.getEncoderInput())) { - stringBuilder.append(consumerRecord.value()).append("\n"); - } - foundRecordString = stringBuilder.toString(); + assertEquals(expectedEncoderInputWithStableFieldsOnly, encoderInputWithStableFieldsOnly); } - assertTrue(foundValidRecordInEncoderInput, - "found records not containing expected value: %s".formatted(foundRecordString)); - container.stop(); log.debug("processUnsignedMessage container stopped"); } From 95f154fed03b8b8d8a89f216dfc6cbcfaf2fcb4a Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 2 Jan 2025 10:38:28 -0700 Subject: [PATCH 126/128] test: update expected-asn1-encoded-router-snmp-deposit.xml with schemaVersion 8 --- .../services/asn1/expected-asn1-encoded-router-snmp-deposit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml index 2e25d4f21..ce6323ff7 100644 --- a/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml +++ b/jpo-ode-svcs/src/test/resources/us/dot/its/jpo/ode/services/asn1/expected-asn1-encoded-router-snmp-deposit.xml @@ -1 +1 @@ -us.dot.its.jpo.ode.model.OdeAsdPayloaddb2e2c93-c87a-488c-b54b-e0dfcbb08fca10002024-12-18T23:59:55.081Z70false3POST38.98721843900006-104.7676706949999938.96666515900006-104.74048299899994oneday6B573067AdvisorySituationDataAdvisorySituationDataUPERus.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData156500000000B673BA356B5730672389872184-1047676707389666652-1047404830B673BA3520200031600003160001F808470114F3703B027CF2071328E120F775D9B0301C2670F374166BC3027FFF93F40BE628129A007F93937E1CF5AC98DFA706A5F6D09AB766C1B3780000000099C3CDD059AF0C084E2302000844033611407401733CDC2D6203FDA1756398358ED27557C6856FBAAC1C3A0F8D9C221DD858A60E0ADA596124567F2B182001002009EEEBB3600 \ No newline at end of file +us.dot.its.jpo.ode.model.OdeAsdPayloaddb2e2c93-c87a-488c-b54b-e0dfcbb08fca10002024-12-18T23:59:55.081Z80false3POST38.98721843900006-104.7676706949999938.96666515900006-104.74048299899994oneday6B573067AdvisorySituationDataAdvisorySituationDataUPERus.dot.its.jpo.ode.plugin.j2735.DdsAdvisorySituationData156500000000B673BA356B5730672389872184-1047676707389666652-1047404830B673BA3520200031600003160001F808470114F3703B027CF2071328E120F775D9B0301C2670F374166BC3027FFF93F40BE628129A007F93937E1CF5AC98DFA706A5F6D09AB766C1B3780000000099C3CDD059AF0C084E2302000844033611407401733CDC2D6203FDA1756398358ED27557C6856FBAAC1C3A0F8D9C221DD858A60E0ADA596124567F2B182001002009EEEBB3600 \ No newline at end of file From e47f528343f746242233c6bd4da0c3c8d428053c Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 2 Jan 2025 16:36:18 -0700 Subject: [PATCH 127/128] fix: correctly handle data signing flow and log clear exception messaging --- .../jpo/ode/security/SecurityServicesClient.java | 11 ++++++++--- .../ode/services/asn1/Asn1EncodedDataRouter.java | 14 +++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java index de64cd550..381d48492 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/security/SecurityServicesClient.java @@ -51,9 +51,14 @@ public SignatureResultModel signMessage(String message, int sigValidityOverride) HttpEntity entity = new HttpEntity<>(requestBody, headers); log.debug("Data to be signed: {}", entity); - ResponseEntity respEntity = restTemplate.postForEntity(signatureUri, entity, SignatureResultModel.class); - log.debug("Security services module response: {}", respEntity); + try { + ResponseEntity respEntity = restTemplate.postForEntity(signatureUri, entity, SignatureResultModel.class); + log.debug("Security services module response: {}", respEntity); - return respEntity.getBody(); + return respEntity.getBody(); + } catch (RestClientException e) { + log.error("Error sending data to security services module: {}", e.getMessage()); + throw e; + } } } diff --git a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java index b0f5a0cbc..53b9bcc7b 100644 --- a/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java +++ b/jpo-ode-svcs/src/main/java/us/dot/its/jpo/ode/services/asn1/Asn1EncodedDataRouter.java @@ -155,6 +155,7 @@ public void listen(ConsumerRecord consumerRecord) JSONObject payloadData = consumedObj.getJSONObject(AppContext.PAYLOAD_STRING).getJSONObject(AppContext.DATA_STRING); JSONObject metadataJson = consumedObj.getJSONObject(AppContext.METADATA_STRING); ServiceRequest request = getServiceRequest(metadataJson); + log.debug("Mapped to object ServiceRequest: {}", request); if (!payloadData.has(ADVISORY_SITUATION_DATA_STRING)) { processUnsignedMessage(request, metadataJson, payloadData); @@ -189,8 +190,8 @@ private void processUnsignedMessage(ServiceRequest request, log.info("Processing unsigned message."); JSONObject messageFrameJson = payloadJson.getJSONObject(MESSAGE_FRAME); var hexEncodedTimBytes = messageFrameJson.getString(BYTES); - - if (dataSigningEnabledRSU && (request.getSdw() != null || request.getRsus() != null)) { + String bytesToSend; + if ((dataSigningEnabledRSU || dataSigningEnabledSDW) && (request.getSdw() != null || request.getRsus() != null)) { log.debug("Signing encoded TIM message..."); String base64EncodedTim = CodecUtils.toBase64(CodecUtils.fromHex(hexEncodedTimBytes)); @@ -200,11 +201,14 @@ private void processUnsignedMessage(ServiceRequest request, * 60 * 1000; var signedResponse = securityServicesClient.signMessage(base64EncodedTim, maxDurationTime); depositToTimCertExpirationTopic(metadataJson, signedResponse, maxDurationTime); - hexEncodedTimBytes = signedResponse.getResult().getHexEncodedMessageSigned(); + bytesToSend = signedResponse.getResult().getHexEncodedMessageSigned(); + } else { + log.debug("Signing not enabled or no SDW or RSU data detected. Sending encoded TIM message without signing..."); + bytesToSend = hexEncodedTimBytes; } - log.debug("Encoded message - phase 1: {}", hexEncodedTimBytes); - var encodedTimWithoutHeaders = stripHeader(hexEncodedTimBytes); + log.debug("Encoded message - phase 1: {}", bytesToSend); + var encodedTimWithoutHeaders = stripHeader(bytesToSend); sendToRsus(request, encodedTimWithoutHeaders); depositToFilteredTopic(metadataJson, encodedTimWithoutHeaders); From 4a8f4f96173a8c4966bb702d1bf86b773541eb55 Mon Sep 17 00:00:00 2001 From: Matt Cook Date: Thu, 2 Jan 2025 16:45:23 -0700 Subject: [PATCH 128/128] test: add unit test for handling server error in signMessage --- .../ode/security/SecurityServicesClientTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java index d965f9aeb..ea01b0ea3 100644 --- a/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java +++ b/jpo-ode-svcs/src/test/java/us/dot/its/jpo/ode/security/SecurityServicesClientTest.java @@ -2,7 +2,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import com.fasterxml.jackson.core.JsonProcessingException; @@ -20,6 +22,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.client.ExpectedCount; import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import us.dot.its.jpo.ode.config.SerializationConfig; import us.dot.its.jpo.ode.http.WebClientConfig; @@ -90,4 +93,15 @@ void testSignMessage_WithNullResponse() { // Assert assertNull(result); } + + @Test + void testSignMessage_WithErrorResponse() { + String message = "ErrorResponseTest"; + var expiryTimeInSeconds = (int) clock.instant().plusSeconds(3600).getEpochSecond(); + + mockServer.expect(ExpectedCount.once(), requestTo(securityServicesProperties.getSignatureEndpoint())) + .andRespond(withServerError()); + + assertThrows(RestClientException.class, () -> securityServicesClient.signMessage(message, expiryTimeInSeconds)); + } } \ No newline at end of file