Skip to content

Commit

Permalink
Cleaned up large forward open, ensure all packet data is received
Browse files Browse the repository at this point in the history
  • Loading branch information
dmroeder committed May 4, 2019
1 parent c20d51a commit 44e6dad
Showing 1 changed file with 76 additions and 76 deletions.
152 changes: 76 additions & 76 deletions pylogix/eip.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import sys
import time

MAX_SOCKET_SIZE = 8192
programNames = []

class PLC:
Expand All @@ -50,7 +49,7 @@ def __init__(self):
self.SerialNumber = 0
self.OriginatorSerialNumber = 42
self.SequenceCounter = 1
self.ConnectionSize = 500
self.ConnectionSize = 511
self.Offset = 0
self.KnownTags = {}
self.TagList = []
Expand Down Expand Up @@ -184,14 +183,14 @@ def Close(self):
class LgxTag:

def __init__(self):
self.TagName = ''
self.InstanceID = 0x00
self.SymbolType = 0x00
self.DataTypeValue = 0x00
self.DataType = ''
self.Array = 0x00
self.Struct = 0x00
self.Size = 0x00
self.TagName = ''
self.InstanceID = 0x00
self.SymbolType = 0x00
self.DataTypeValue = 0x00
self.DataType = ''
self.Array = 0x00
self.Struct = 0x00
self.Size = 0x00

def _readTag(self, tag, elements, dt):
'''
Expand Down Expand Up @@ -689,7 +688,7 @@ def _discover():
s.sendto(request, ('255.255.255.255', 44818))
try:
while(1):
ret = s.recv(MAX_SOCKET_SIZE)
ret = s.recv(4096)
context = unpack_from('<Q', ret, 14)[0]
if context == 0x006d6f4d6948:
device = _parseIdentityResponse(ret)
Expand All @@ -708,7 +707,7 @@ def _discover():
s.sendto(request, ('255.255.255.255', 44818))
try:
while(1):
ret = s.recv(MAX_SOCKET_SIZE)
ret = s.recv(4096)
context = unpack_from('<Q', ret, 14)[0]
if context == 0x006d6f4d6948:
device = _parseIdentityResponse(ret)
Expand Down Expand Up @@ -753,7 +752,7 @@ def _getModuleProperties(self, slot):
eipHeader = _buildEIPSendRRDataHeader(self, len(frame)) + frame
pad = pack('<I', 0x00)
self.Socket.send(eipHeader)
retData = pad + self.Socket.recv(MAX_SOCKET_SIZE)
retData = pad + recv_data(self)
status = unpack_from('<B', retData, 46)[0]

if status == 0:
Expand All @@ -780,15 +779,15 @@ def _connect(self):
raise

self.Socket.send(_buildRegisterSession(self))
retData = self.Socket.recv(MAX_SOCKET_SIZE)
retData = recv_data(self)
if retData:
self.SessionHandle = unpack_from('<I', retData, 4)[0]
else:
self.SocketConnected = False
raise Exception("Failed to register session")

self.Socket.send(_buildForwardOpenPacket(self))
retData = self.Socket.recv(MAX_SOCKET_SIZE)
retData = recv_data(self)
sts = unpack_from('<b', retData, 42)[0]
if not sts:
self.OTNetworkConnectionID = unpack_from('<I', retData, 44)[0]
Expand All @@ -808,9 +807,9 @@ def _closeConnection(self):
unregPacket = _buildUnregisterSession(self)
try:
self.Socket.send(closePacket)
retData = self.Socket.recv(MAX_SOCKET_SIZE)
retData = recv_data(self)
self.Socket.send(unregPacket)
retData = self.Socket.recv(MAX_SOCKET_SIZE)
retData = recv_data(self)
self.Socket.close()
except:
pass
Expand All @@ -822,7 +821,7 @@ def _getBytes(self, data):
'''
try:
self.Socket.send(data)
retData = self.Socket.recv(MAX_SOCKET_SIZE)
retData = recv_data(self)
if retData:
status = unpack_from('<B', retData, 48)[0]
return status, retData
Expand Down Expand Up @@ -894,63 +893,44 @@ def _buildForwardClosePacket(self):
def _buildCIPForwardOpen(self):
'''
Forward Open happens after a connection is made,
this will setup the CIP connection parameters
'''
#might as well use 500 bytes if using standard forward open
if self.ConnectionSize < 500:
self.ConnectionSize = 500
elif self.ConnectionSize > 4002:
self.ConnectionSize = 4002
#per standard over 504 bytes is an extended (large) forward open
exForwardOpen = (self.ConnectionSize > 504)
CIPService = 0x54 if not exForwardOpen else 0x5B #forward open
CIPPathSize = 0x02
CIPClassType = 0x20

CIPClass = 0x06
CIPInstanceType = 0x24

CIPInstance = 0x01
CIPPriority = 0x0A
CIPTimeoutTicks = 0x0E
CIPOTConnectionID = 0x20000002
CIPTOConnectionID = 0x20000001
self.SerialNumber = randrange(65000)
CIPConnectionSerialNumber = self.SerialNumber
CIPVendorID = self.VendorID
CIPOriginatorSerialNumber = self.OriginatorSerialNumber
CIPMultiplier = 0x03
CIPOTRPI = 0x00201234
#3-5.5.1.1 Network Connection Parameters
#default to 500,True,0,2,True 0x43f4 0b0100001111110100
self.VariableConnectionSize = True #allow for variable connection size
self.Priority = 0 #low priority by default. 00 = Low Priority, 01 = High Priority, 10 = Scheduled, 11 = Urgent
self.ConnectionType = 2 #point to point by default 00 = Null (may be used to reconfigure a connection), 01 = Multicast, 10 = Point to Point, 11 = Reserved
self.RedundantOwner = False #multiple connections allowed

netParams = ((self.VariableConnectionSize << 9) |
(self.Priority << 10) |
(self.ConnectionType << 13) |
(self.RedundantOwner << 15))

#if using the extended forward open we need more bits for the connection size
#so add another word
if exForwardOpen:
netParams = ((self.ConnectionSize & 0xFFFF) | netParams << 16)
print 'Using extended forward open of size %d bytes' % self.ConnectionSize
else:
netParams = ((self.ConnectionSize & 0x01FF) | netParams)
self.NetworkConnectionParameters = netParams
#self.NetworkConnectionParameters = 0x43f4
#print hex(self.NetworkConnectionParameters)
CIPOTNetworkConnectionParameters = self.NetworkConnectionParameters

CIPTORPI = 0x00204001
CIPTONetworkConnectionParameters = self.NetworkConnectionParameters
this will sequp the CIP connection parameters
'''
CIPPathSize = 0x02
CIPClassType = 0x20

CIPClass = 0x06
CIPInstanceType = 0x24

CIPInstance = 0x01
CIPPriority = 0x0A
CIPTimeoutTicks = 0x0e
CIPOTConnectionID = 0x20000002
CIPTOConnectionID = 0x20000001
self.SerialNumber = randrange(65000)
CIPConnectionSerialNumber = self.SerialNumber
CIPVendorID = self.VendorID
CIPOriginatorSerialNumber = self.OriginatorSerialNumber
CIPMultiplier = 0x03
CIPOTRPI = 0x00201234
CIPConnectionParameters = 0xC200
CIPTORPI = 0x00204001
CIPTransportTrigger = 0xA3

pack_format = '<BBBBBBBBIIHHIIIIIIB' if exForwardOpen else '<BBBBBBBBIIHHIIIHIHB'

# decide whether to use the standard ForwardOpen
# or the large format
if self.ConnectionSize <= 511:
CIPService = 0x54
CIPConnectionParameters += self.ConnectionSize
pack_format = '<BBBBBBBBIIHHIIIHIHB'
else:
CIPService = 0x5B
CIPConnectionParameters = CIPConnectionParameters << 16
CIPConnectionParameters += self.ConnectionSize
pack_format = '<BBBBBBBBIIHHIIIIIIB'

CIPOTNetworkConnectionParameters = CIPConnectionParameters
CIPTONetworkConnectionParameters = CIPConnectionParameters

ForwardOpen = pack(pack_format,
CIPService,
CIPPathSize,
Expand Down Expand Up @@ -978,7 +958,7 @@ def _buildCIPForwardOpen(self):
else:
ConnectionPath = [0x01, self.ProcessorSlot, 0x20, 0x02, 0x24, 0x01]

ConnectionPathSize = int(len(ConnectionPath)/2) #add number of 16-bit words
ConnectionPathSize = int(len(ConnectionPath)/2)
pack_format = '<B' + str(len(ConnectionPath)) + 'B'
CIPConnectionPath = pack(pack_format, ConnectionPathSize, *ConnectionPath)

Expand Down Expand Up @@ -1434,10 +1414,11 @@ def _getReplyValues(self, tag, elements, data):
eipHeader = _buildEIPHeader(self, readIOI)

self.Socket.send(eipHeader)
data = self.Socket.recv(MAX_SOCKET_SIZE)
data = recv_data(self)

status = unpack_from('<B', data, 48)[0]
numbytes = len(data)-dataSize

return vals

else: # didn't nail it
Expand All @@ -1447,6 +1428,25 @@ def _getReplyValues(self, tag, elements, data):
err = 'Unknown error'
return 'Failed to read tag: {} - {}'.format(tag, err)

def recv_data(self):
'''
When receiving data from the socket, it is possible to receive
incomplete data. The initial packet that comes in contains
the length of the payload. We can use that to keep calling
recv() until the entire payload is received. This only happnens
when using LargeForwardOpen
'''
data = b''
part = self.Socket.recv(4096)
payload_len = unpack_from('<H', part, 2)[0]
data += part

while len(data)-24 < payload_len:
part = self.Socket.recv(4096)
data += part

return data

def _getBitOfWord(tag, value):
'''
Takes a tag name, gets the bit from the end of
Expand Down

0 comments on commit 44e6dad

Please sign in to comment.