From d9be4ba4beffb50ffcda13166cc89b6b9b577ac8 Mon Sep 17 00:00:00 2001 From: Gerben Kroes Date: Wed, 25 Oct 2023 15:14:58 +0200 Subject: [PATCH 1/3] UDP alarm from DSMR 2.2 should be considered as a AlarmDecoder.PUSH_SMS_TRIGGER which triggers a notify of ip-address Handle 17 char length device identifications Signed-off-by: Gerben Kroes --- .../infra/networking/ConnectionProtocol.java | 12 +++ .../DlmsPushNotificationDecoder.java | 6 +- .../DlmsPushNotificationReplayingDecoder.java | 3 +- .../infra/networking/Mx382AlarmDecoder.java | 75 ++++++++++++++++--- .../networking/UdpInboundMessageHandler.java | 3 +- .../networking/Mx382AlarmDecoderTest.java | 68 +++++++++++++++-- 6 files changed, 144 insertions(+), 23 deletions(-) create mode 100644 osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/ConnectionProtocol.java diff --git a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/ConnectionProtocol.java b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/ConnectionProtocol.java new file mode 100644 index 00000000000..25d9b7a3b42 --- /dev/null +++ b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/ConnectionProtocol.java @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: Copyright Contributors to the GXF project + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensmartgridplatform.adapter.protocol.dlms.infra.networking; + +public enum ConnectionProtocol { + TCP, + UDP +} diff --git a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/DlmsPushNotificationDecoder.java b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/DlmsPushNotificationDecoder.java index 940f2150451..3b903d72e4c 100644 --- a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/DlmsPushNotificationDecoder.java +++ b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/DlmsPushNotificationDecoder.java @@ -20,7 +20,9 @@ public class DlmsPushNotificationDecoder { private static final byte DATA_NOTIFICATION = 0x0F; private static final byte EVENT_NOTIFICATION_REQUEST = (byte) 0xC2; - public DlmsPushNotification decode(final byte[] message) throws UnrecognizedMessageDataException { + public DlmsPushNotification decode( + final byte[] message, final ConnectionProtocol connectionProtocol) + throws UnrecognizedMessageDataException { /** * MX382 alarm examples (in HEX bytes): * @@ -83,7 +85,7 @@ public DlmsPushNotification decode(final byte[] message) throws UnrecognizedMess pushNotification = alarmDecoder.decodeSmr5alarm(inputStream); } else if (mx382alarm) { final Mx382AlarmDecoder alarmDecoder = new Mx382AlarmDecoder(); - pushNotification = alarmDecoder.decodeMx382alarm(inputStream); + pushNotification = alarmDecoder.decodeMx382alarm(inputStream, connectionProtocol); } else { final Dsmr4AlarmDecoder alarmDecoder = new Dsmr4AlarmDecoder(); pushNotification = alarmDecoder.decodeDsmr4alarm(inputStream); diff --git a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/DlmsPushNotificationReplayingDecoder.java b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/DlmsPushNotificationReplayingDecoder.java index 37c13e61f68..41ef9bd7337 100644 --- a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/DlmsPushNotificationReplayingDecoder.java +++ b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/DlmsPushNotificationReplayingDecoder.java @@ -45,7 +45,8 @@ protected void decode( final byte[] byteArray = new byte[byteBuf.readableBytes()]; byteBuf.readBytes(byteArray); - final DlmsPushNotification dlmsPushNotification = this.decoder.decode(byteArray); + final DlmsPushNotification dlmsPushNotification = + this.decoder.decode(byteArray, ConnectionProtocol.TCP); LOGGER.info("Decoded push notification: {}", dlmsPushNotification); out.add(dlmsPushNotification); diff --git a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/Mx382AlarmDecoder.java b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/Mx382AlarmDecoder.java index d63d8a66fc3..6752125d883 100644 --- a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/Mx382AlarmDecoder.java +++ b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/Mx382AlarmDecoder.java @@ -4,7 +4,9 @@ package org.opensmartgridplatform.adapter.protocol.dlms.infra.networking; +import java.io.IOException; import java.io.InputStream; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashSet; @@ -17,9 +19,7 @@ public class Mx382AlarmDecoder extends AlarmDecoder { private static final byte EVENT_NOTIFICATION_REQUEST = (byte) 0xC2; private static final byte[] WPDU_HEADER = - new byte[] {0x00, 0x01, 0x00, 0x67, 0x00, 0x66, 0x00, 0x1d}; - private static final byte[] WPDU_HEADER_WITH_DATE = - new byte[] {0x00, 0x01, 0x00, 0x67, 0x00, 0x66, 0x00, 0x2a}; + new byte[] {0x00, 0x01, 0x00, 0x67, 0x00, 0x66}; // , 0x00, 0x1d // , 0x00, 0x2a private static final byte[] AMM_FORWARDED_ALARM_VERSION_0 = new byte[] {(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x00}; static final byte[] CLASS_ID_1 = new byte[] {0x00, 0x01}; @@ -31,9 +31,18 @@ public class Mx382AlarmDecoder extends AlarmDecoder { private final DlmsPushNotification.Builder builder = new DlmsPushNotification.Builder(); - public DlmsPushNotification decodeMx382alarm(final InputStream inputStream) + public DlmsPushNotification decodeMx382alarm( + final InputStream inputStream, final ConnectionProtocol connectionProtocol) throws UnrecognizedMessageDataException { + if (connectionProtocol == ConnectionProtocol.UDP) { + return this.decodeMx382alarmUdp(inputStream); + } + return this.decodeMx382alarmTcp(inputStream); + } + + private DlmsPushNotification decodeMx382alarmTcp(final InputStream inputStream) + throws UnrecognizedMessageDataException { this.handleWpduHeaderBytes(inputStream, "WPDU-header"); this.checkByte(inputStream, EVENT_NOTIFICATION_REQUEST, "event-notification-request"); @@ -43,9 +52,25 @@ public DlmsPushNotification decodeMx382alarm(final InputStream inputStream) this.handleCosemAttributeDescriptor(inputStream); // Get equipment identifier and add it to the new push notification this.handleEquipmentIdentifier(inputStream); + // Add alarmbits to the new push notification + this.addTriggerType(PUSH_ALARM_TRIGGER); + this.addAlarm(AlarmTypeDto.LAST_GASP); + + return this.builder.build(); + } + + private DlmsPushNotification decodeMx382alarmUdp(final InputStream inputStream) + throws UnrecognizedMessageDataException { + this.handleWpduHeaderBytes(inputStream, "WPDU-header"); + this.checkByte(inputStream, EVENT_NOTIFICATION_REQUEST, "event-notification-request"); + + // Datetime is read and skipped + this.handleDateTime(inputStream); + // Get equipment identifier and add it to the new push notification + this.readEquipmentIdentifierAtEndOfMessage(inputStream); // Add alarmbits to the new push notification - this.addAlarm(); + this.addTriggerType(PUSH_SMS_TRIGGER); return this.builder.build(); } @@ -77,6 +102,22 @@ private void handleEquipmentIdentifier(final InputStream inputStream) new String(equipmentIdentifierBytes, StandardCharsets.US_ASCII)); } + private void readEquipmentIdentifierAtEndOfMessage(final InputStream inputStream) + throws UnrecognizedMessageDataException { + try { + this.readBytes(inputStream, inputStream.available() - 17); + // Read the equipment identifier from incoming mx382 message + final byte[] equipmentIdentifierBytes = this.readBytes(inputStream, 17); + final String equipmentIdentifier = + new String(equipmentIdentifierBytes, StandardCharsets.US_ASCII).trim(); + if (equipmentIdentifier.matches("[a-zA-Z0-9]+")) { + this.builder.withEquipmentIdentifier(equipmentIdentifier); + } + } catch (final IOException io) { + throw new UnrecognizedMessageDataException(io.getMessage(), io); + } + } + private void handleCosemAttributeDescriptor(final InputStream inputStream) throws UnrecognizedMessageDataException { // Check bytes of incoming mx382 message @@ -85,10 +126,13 @@ private void handleCosemAttributeDescriptor(final InputStream inputStream) this.checkByte(inputStream, ATTRIBUTE_ID_2, "attribute-id"); } - private void addAlarm() { - this.builder.withTriggerType(PUSH_ALARM_TRIGGER); + private void addTriggerType(final String triggerType) { + this.builder.withTriggerType(triggerType); + } + + private void addAlarm(final AlarmTypeDto alarmType) { final Set alarmSet = new HashSet<>(); - alarmSet.add(AlarmTypeDto.LAST_GASP); + alarmSet.add(alarmType); this.builder.addAlarms(alarmSet); } @@ -110,16 +154,23 @@ private void handleWpduHeaderBytes(final InputStream inputStream, final String n throws UnrecognizedMessageDataException { final byte[] readBytes = this.readBytes(inputStream, WPDU_HEADER.length); this.builder.appendBytes(readBytes); - if (!Arrays.equals(readBytes, WPDU_HEADER) - && !Arrays.equals(readBytes, WPDU_HEADER_WITH_DATE)) { - + if (!Arrays.equals(readBytes, WPDU_HEADER)) { throw new UnrecognizedMessageDataException( String.format( EXPECTED_MESSAGE_TEMPLATE, - toHexString(WPDU_HEADER) + " or " + toHexString(WPDU_HEADER_WITH_DATE), + toHexString(WPDU_HEADER), name, toHexString(this.resetAndReadAllBytes(inputStream)))); } + + final byte[] apduLengthBytes = this.readBytes(inputStream, 2); + this.builder.appendBytes(apduLengthBytes); + final int adpuLength = new BigInteger(apduLengthBytes).intValue(); + if (adpuLength != 29 && adpuLength != 30 && adpuLength != 42 && adpuLength != 43) { + throw new UnrecognizedMessageDataException( + String.format( + "Expected length value of 29,30,42 or 43, but found length of %d", adpuLength)); + } } private void checkBytes( diff --git a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/UdpInboundMessageHandler.java b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/UdpInboundMessageHandler.java index bb46cec6396..b2b998a0bc8 100644 --- a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/UdpInboundMessageHandler.java +++ b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/UdpInboundMessageHandler.java @@ -30,7 +30,8 @@ public void handeMessage(final Message message, @Headers final Map { + decoder.decodeMx382alarm(is, ConnectionProtocol.TCP); + }); + assertThat(exception.getMessage()) + .contains( + "Data in DLMS Push Notification cannot be decoded. Reason: Expected length value of 29,30,42 or 43, but found length of 19"); + } + @Test void testCheckOnObiscode() { @@ -127,7 +180,8 @@ private static void assertDecodingException(final byte[] mx382message) { final Mx382AlarmDecoder decoder = new Mx382AlarmDecoder(); final InputStream is = new ByteArrayInputStream(mx382message); - final Throwable actual = catchThrowable(() -> decoder.decodeMx382alarm(is)); + final Throwable actual = + catchThrowable(() -> decoder.decodeMx382alarm(is, ConnectionProtocol.TCP)); assertThat(actual).isInstanceOf(UnrecognizedMessageDataException.class); } From 5afd5aae021ce350a62a2ce76701411a01f36468 Mon Sep 17 00:00:00 2001 From: Gerben Kroes Date: Wed, 25 Oct 2023 15:49:56 +0200 Subject: [PATCH 2/3] Remove comment Signed-off-by: Gerben Kroes --- .../protocol/dlms/infra/networking/Mx382AlarmDecoder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/Mx382AlarmDecoder.java b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/Mx382AlarmDecoder.java index 6752125d883..60e7819badd 100644 --- a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/Mx382AlarmDecoder.java +++ b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/Mx382AlarmDecoder.java @@ -18,8 +18,7 @@ public class Mx382AlarmDecoder extends AlarmDecoder { private static final byte EVENT_NOTIFICATION_REQUEST = (byte) 0xC2; - private static final byte[] WPDU_HEADER = - new byte[] {0x00, 0x01, 0x00, 0x67, 0x00, 0x66}; // , 0x00, 0x1d // , 0x00, 0x2a + private static final byte[] WPDU_HEADER = new byte[] {0x00, 0x01, 0x00, 0x67, 0x00, 0x66}; private static final byte[] AMM_FORWARDED_ALARM_VERSION_0 = new byte[] {(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x00}; static final byte[] CLASS_ID_1 = new byte[] {0x00, 0x01}; From 1c9d2369f434dd72a5f666a661d7ac128134801a Mon Sep 17 00:00:00 2001 From: Gerben Kroes Date: Wed, 25 Oct 2023 16:52:39 +0200 Subject: [PATCH 3/3] Add logging of hex representation Signed-off-by: Gerben Kroes --- .../dlms/infra/networking/UdpInboundMessageHandler.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/UdpInboundMessageHandler.java b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/UdpInboundMessageHandler.java index b2b998a0bc8..8dd5a799238 100644 --- a/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/UdpInboundMessageHandler.java +++ b/osgp/protocol-adapter-dlms/osgp-protocol-adapter-dlms/src/main/java/org/opensmartgridplatform/adapter/protocol/dlms/infra/networking/UdpInboundMessageHandler.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.UUID; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Hex; import org.opensmartgridplatform.dlms.DlmsPushNotification; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.annotation.MessageEndpoint; @@ -28,7 +29,10 @@ public class UdpInboundMessageHandler { public void handeMessage(final Message message, @Headers final Map headerMap) throws UnrecognizedMessageDataException { final byte[] payload = (byte[]) message.getPayload(); - log.info("Received UDP message: {}", new String(payload)); + log.info( + "Received UDP message: {}, hex-representation: {}", + new String(payload), + Hex.encodeHexString(payload)); final DlmsPushNotification dlmsPushNotification = this.decoder.decode(payload, ConnectionProtocol.UDP);