You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by hu...@apache.org on 2022/06/26 07:38:51 UTC

[plc4x] 01/03: Started to clean up

This is an automated email from the ASF dual-hosted git repository.

hutcheb pushed a commit to branch eip_update
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 2f0d8c38eb7949663f64850f1f48a28753da7d4c
Author: Ben Hutcheson <be...@gmail.com>
AuthorDate: Fri Jun 24 07:55:53 2022 +1000

    Started to clean up
---
 .../java/eip/base/protocol/EipProtocolLogic.java   | 137 ++++++++++++++++++---
 .../eip/src/main/resources/protocols/eip/eip.mspec |  16 +++
 .../eip/ParserSerializerTestsuiteLittle.xml        |  95 ++++++++++++++
 3 files changed, 229 insertions(+), 19 deletions(-)

diff --git a/plc4j/drivers/eip/src/main/java/org/apache/plc4x/java/eip/base/protocol/EipProtocolLogic.java b/plc4j/drivers/eip/src/main/java/org/apache/plc4x/java/eip/base/protocol/EipProtocolLogic.java
index 1879704e9..aee0902f7 100644
--- a/plc4j/drivers/eip/src/main/java/org/apache/plc4x/java/eip/base/protocol/EipProtocolLogic.java
+++ b/plc4j/drivers/eip/src/main/java/org/apache/plc4x/java/eip/base/protocol/EipProtocolLogic.java
@@ -20,6 +20,7 @@ package org.apache.plc4x.java.eip.base.protocol;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
 import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.api.messages.*;
 import org.apache.plc4x.java.api.model.PlcField;
@@ -43,6 +44,7 @@ import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
+import java.time.temporal.TemporalUnit;
 import java.util.*;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
@@ -72,6 +74,8 @@ public class EipProtocolLogic extends Plc4xProtocolBase<EipPacket> implements Ha
 
     private boolean useConnectionManager = false;
 
+    private boolean cipEncapsulationAvailable = false;
+
     @Override
     public void setConfiguration(EIPConfiguration configuration) {
         this.configuration = configuration;
@@ -80,9 +84,34 @@ public class EipProtocolLogic extends Plc4xProtocolBase<EipPacket> implements Ha
         this.tm = new RequestTransactionManager(1);
     }
 
-    @Override
-    public void onConnect(ConversationContext<EipPacket> context) {
+    public CompletableFuture<Boolean> detectEndianness(ConversationContext<EipPacket> context) {
         logger.debug("Sending Register Session EIP Package");
+        CompletableFuture<Boolean> future = new CompletableFuture<>();
+        RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
+
+        NullLittleCommand listServicesRequest = new NullLittleCommand(
+            EMPTY_SESSION_HANDLE,
+            CIPStatus.Success.getValue(),
+            DEFAULT_SENDER_CONTEXT,
+            0L,
+            this.configuration.getByteOrder());
+        transaction.submit(() -> context.sendRequest(listServicesRequest)
+            .expectResponse(EipPacket.class, REQUEST_TIMEOUT).unwrap(p -> p)
+            .onError((p,e) -> logger.warn("No response for initial packet. Suspect device uses Big endian"))
+            .onTimeout(p -> logger.warn("No response for initial packet. Suspect device uses Big endian"))
+            .check(p -> p instanceof NullLittleCommand)
+            .handle(p -> {
+                logger.info("Device uses little endian");
+                future.complete(true);
+            })
+        );
+        return future;
+    }
+
+    private CompletableFuture<Boolean> listServices(ConversationContext<EipPacket> context) {
+        logger.debug("Sending List Services packet to confirm CIP Encapsulation is available");
+        CompletableFuture<Boolean> future = new CompletableFuture<>();
+        RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
 
         ListServicesRequest listServicesRequest = new ListServicesRequest(
             EMPTY_SESSION_HANDLE,
@@ -90,24 +119,96 @@ public class EipProtocolLogic extends Plc4xProtocolBase<EipPacket> implements Ha
             DEFAULT_SENDER_CONTEXT,
             0L,
             this.configuration.getByteOrder());
-        context.sendRequest(listServicesRequest)
+        transaction.submit(() -> context.sendRequest(listServicesRequest)
             .expectResponse(EipPacket.class, REQUEST_TIMEOUT).unwrap(p -> p)
             .check(p -> p instanceof ListServicesResponse)
             .handle(p -> {
                 if (p.getStatus() == CIPStatus.Success.getValue()) {
                     ServicesResponse listServicesResponse = (ServicesResponse) ((ListServicesResponse) p).getTypeId().get(0);
-                    this.useConnectionManager = listServicesResponse.getSupportsCIPEncapsulation();
-                    logger.debug("Device is capable of CIP over EIP encapsulation");
+                    if (listServicesResponse.getSupportsCIPEncapsulation()) {
+                        logger.debug("Device is capable of CIP over EIP encapsulation");
+                    }
+                    future.complete(listServicesResponse.getSupportsCIPEncapsulation());
                 } else {
-                    logger.warn("Got status code while polling for supported services [{}]", p.getStatus());
+                    logger.warn("Got status code while polling for supported EIP services [{}]", p.getStatus());
+                    future.complete(false);
+                }
+            })
+        );
+        return future;
+    }
+
+    private CompletableFuture<Boolean> getAllAttributes(ConversationContext<EipPacket> context) {
+        logger.debug("Requesting list of supported attributes");
+        CompletableFuture<Boolean> future = new CompletableFuture<>();
+        RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
 
+        ListServicesRequest listServicesRequest = new ListServicesRequest(
+            EMPTY_SESSION_HANDLE,
+            CIPStatus.Success.getValue(),
+            DEFAULT_SENDER_CONTEXT,
+            0L,
+            this.configuration.getByteOrder());
+        transaction.submit(() -> context.sendRequest(listServicesRequest)
+            .expectResponse(EipPacket.class, REQUEST_TIMEOUT).unwrap(p -> p)
+            .check(p -> p instanceof ListServicesResponse)
+            .handle(p -> {
+                if (p.getStatus() == CIPStatus.Success.getValue()) {
+                    ServicesResponse listServicesResponse = (ServicesResponse) ((ListServicesResponse) p).getTypeId().get(0);
+                    if (listServicesResponse.getSupportsCIPEncapsulation()) {
+                        logger.debug("Device is capable of CIP over EIP encapsulation");
+                    }
+                    future.complete(listServicesResponse.getSupportsCIPEncapsulation());
+                } else {
+                    logger.warn("Got status code while polling for supported EIP services [{}]", p.getStatus());
+                    future.complete(false);
                 }
-                onConnectRegisterSession(context);
-            });
+            })
+        );
+        return future;
     }
 
-    private void onConnectRegisterSession(ConversationContext<EipPacket> context) {
+    @Override
+    public void onConnect(ConversationContext<EipPacket> context) {
+
+        try {
+            detectEndianness(context).get(REQUEST_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            // This is where we should set the endianness, still not sure if this is correct though.
+        }
+
+        try {
+            this.cipEncapsulationAvailable = listServices(context).get(REQUEST_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+            if (!this.cipEncapsulationAvailable) {
+                logger.error("Device doesn't support EIP with Encapsulated CIP");
+                context.fireDisconnected();
+                return;
+            }
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            // This is where we should set the endianness, still not sure if this is correct though.
+        }
+
+        try {
+            onConnectRegisterSession(context).get(REQUEST_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            // This is where we should set the endianness, still not sure if this is correct though.
+        }
+
+        try {
+            getAllAttributes(context).get(REQUEST_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            // This is where we should set the endianness, still not sure if this is correct though.
+        }
+
+
+
+    }
+
+    private CompletableFuture<Boolean> onConnectRegisterSession(ConversationContext<EipPacket> context) {
         logger.debug("Sending Register Session EIP Package");
+        CompletableFuture<Boolean> future = new CompletableFuture<>();
+        RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
+
         EipConnectionRequest connectionRequest =
             new EipConnectionRequest(
                 EMPTY_SESSION_HANDLE,
@@ -115,25 +216,23 @@ public class EipProtocolLogic extends Plc4xProtocolBase<EipPacket> implements Ha
                 DEFAULT_SENDER_CONTEXT,
                 0L,
                 this.configuration.getByteOrder());
-        context.sendRequest(connectionRequest)
+
+        transaction.submit(() -> context.sendRequest(connectionRequest)
             .expectResponse(EipPacket.class, REQUEST_TIMEOUT).unwrap(p -> p)
             .check(p -> p instanceof EipConnectionRequest)
             .handle(p -> {
                 if (p.getStatus() == CIPStatus.Success.getValue()) {
                     sessionHandle = p.getSessionHandle();
                     senderContext = p.getSenderContext();
-                    logger.debug("Got assigned with Session {}", sessionHandle);
-                    if (this.useConnectionManager) {
-                        onConnectOpenConnectionManager(context, p);
-                    } else {
-                        logger.debug("Using unconnected explicit messaging");
-                        // Send an event that connection setup is complete.
-                        context.fireConnected();
-                    }
+                    logger.debug("Got assigned with Session handle {}", sessionHandle);
+                    future.complete(true);
                 } else {
                     logger.warn("Got status code [{}]", p.getStatus());
+                    future.completeExceptionally(new PlcConnectionException("Error while handling Register Session response"));
                 }
-            });
+            })
+        );
+        return future;
     }
 
     public void onConnectOpenConnectionManager(ConversationContext<EipPacket> context, EipPacket response) {
diff --git a/protocols/eip/src/main/resources/protocols/eip/eip.mspec b/protocols/eip/src/main/resources/protocols/eip/eip.mspec
index e423d04be..b1bb73b5a 100644
--- a/protocols/eip/src/main/resources/protocols/eip/eip.mspec
+++ b/protocols/eip/src/main/resources/protocols/eip/eip.mspec
@@ -29,6 +29,8 @@
     [array         byte    senderContext count '8']
     [simple        uint 32 options]
     [typeSwitch command,response
+            ['0x0001'   NullLittleCommand
+            ]
             ['0x0004','false' ListServicesRequest
             ]
             ['0x0004','true' ListServicesResponse
@@ -53,6 +55,8 @@
                 [simple     uint    16     itemCount]
                 [array      TypeId('order')         typeId   count   'itemCount']
             ]
+            ['0x0100'   NullBigCommand
+            ]
         ]
 ]
 
@@ -91,6 +95,18 @@
     [discriminator  bit     response]
     [discriminator  uint    7   service]
     [typeSwitch service,response,connected
+        ['0x01','false' GetAttributeAllRequest
+            [implicit    int     8              requestPathSize '(classSegment.lengthInBytes + instanceSegment.lengthInBytes)/2']
+            [simple      PathSegment('order')   classSegment]
+            [simple      PathSegment('order')   instanceSegment]
+        ]
+        ['0x01','true' GetAttributeAllResponse
+            [implicit      int     8         requestPathSize '(classSegment.lengthInBytes + instanceSegment.lengthInBytes)/2']
+            [simple      PathSegment('order')         classSegment]
+            [simple      PathSegment('order')         instanceSegment]
+            [implicit    uint 16                numberOfClasses 'COUNT(classId)']
+            [array       uint 16                classId count 'numberOfClasses']
+        ]
         ['0x4C','false' CipReadRequest
             [implicit   int     8   requestPathSize 'COUNT(tag) / 2']
             [array      byte   tag   count  '(requestPathSize * 2)']
diff --git a/protocols/eip/src/test/resources/protocols/eip/ParserSerializerTestsuiteLittle.xml b/protocols/eip/src/test/resources/protocols/eip/ParserSerializerTestsuiteLittle.xml
index df089e9d5..3483e13b2 100644
--- a/protocols/eip/src/test/resources/protocols/eip/ParserSerializerTestsuiteLittle.xml
+++ b/protocols/eip/src/test/resources/protocols/eip/ParserSerializerTestsuiteLittle.xml
@@ -1102,5 +1102,100 @@
   </testcase>
 
 
+  <testcase>
+    <name>EIP Get Attribute List Request - Message Router</name>
+    <raw>6f00160045000040000000001400000030145b0300000000000000002000020000000000b2000600010220022401</raw>
+    <root-type>EipPacket</root-type>
+    <parser-arguments>
+      <byteOrder>LITTLE_ENDIAN</byteOrder>
+      <response>false</response>
+    </parser-arguments>
+    <xml>
+      <EipPacket>
+        <command dataType="uint" bitLength="16">111</command>
+        <packetLength dataType="uint" bitLength="16">22</packetLength>
+        <sessionHandle dataType="uint" bitLength="32">1073741893</sessionHandle>
+        <status dataType="uint" bitLength="32">0</status>
+        <senderContext dataType="byte" bitLength="64">0x1400000030145b03</senderContext>
+        <options dataType="uint" bitLength="32">0</options>
+        <CipRRData>
+          <interfaceHandle dataType="uint" bitLength="32">0</interfaceHandle>
+          <timeout dataType="uint" bitLength="16">32</timeout>
+          <itemCount dataType="uint" bitLength="16">2</itemCount>
+          <typeId isList="true">
+            <TypeId>
+              <id dataType="uint" bitLength="16">0</id>
+              <NullAddressItem>
+                <reserved dataType="uint" bitLength="16">0</reserved>
+              </NullAddressItem>
+            </TypeId>
+            <TypeId>
+              <id dataType="uint" bitLength="16">178</id>
+              <UnConnectedDataItem>
+                <packetSize dataType="uint" bitLength="16">6</packetSize>
+                <service>
+                  <CipService>
+                    <response dataType="bit" bitLength="1">false</response>
+                    <service dataType="uint" bitLength="7">1</service>
+                    <GetAttributeAllRequest>
+                      <requestPathSize dataType="int" bitLength="8">2</requestPathSize>
+                      <classSegment>
+                        <PathSegment>
+                          <pathSegment dataType="uint" bitLength="3">1</pathSegment>
+                          <LogicalSegment>
+                            <segmentType>
+                              <LogicalSegmentType>
+                                <logicalSegmentType dataType="uint" bitLength="3">0</logicalSegmentType>
+                                <ClassID>
+                                  <format dataType="uint" bitLength="2">0</format>
+                                  <segmentClass dataType="uint" bitLength="8">2</segmentClass>
+                                </ClassID>
+                              </LogicalSegmentType>
+                            </segmentType>
+                          </LogicalSegment>
+                        </PathSegment>
+                      </classSegment>
+                      <instanceSegment>
+                        <PathSegment>
+                          <pathSegment dataType="uint" bitLength="3">1</pathSegment>
+                          <LogicalSegment>
+                            <segmentType>
+                              <LogicalSegmentType>
+                                <logicalSegmentType dataType="uint" bitLength="3">1</logicalSegmentType>
+                                <InstanceID>
+                                  <format dataType="uint" bitLength="2">0</format>
+                                  <instance dataType="uint" bitLength="8">1</instance>
+                                </InstanceID>
+                              </LogicalSegmentType>
+                            </segmentType>
+                          </LogicalSegment>
+                        </PathSegment>
+                      </instanceSegment>
+                    </GetAttributeAllRequest>
+                  </CipService>
+                </service>
+              </UnConnectedDataItem>
+            </TypeId>
+          </typeId>
+        </CipRRData>
+      </EipPacket>
+    </xml>
+  </testcase>
+
+  <testcase>
+    <name>EIP Get Attribute List Response - Message Router</name>
+    <raw>6f00b60045000040000000001400000030145b0300000000000000000000020000000000b200a600810000004e003a03770066004300f6003700f500ac035f005d005e000003ab033703a503040348004203a4038b002f031204b603b203b303b003b10330034f004e00aa03a803a703a6036e037003320331032d031703b20049033503710072007803ac00b0002b03b100730067006b0068007d038d008c006d006a0038031a0369004500f20074006e008e0070006c0002006a036400a100f4000100c100c000060000010500</raw>
+    <root-type>EipPacket</root-type>
+    <parser-arguments>
+      <byteOrder>LITTLE_ENDIAN</byteOrder>
+      <response>true</response>
+    </parser-arguments>
+    <xml>
+      <EipPacket>
+
+
+      </EipPacket>
+    </xml>
+  </testcase>
 
 </test:testsuite>
\ No newline at end of file