1

I'm building an C# library for applications who needs communication over TCP/IP using the CIP-protocol. (This is an industrial protocol, used by PLC's). My library is based on an open source VB.net project.

enter image description here

I found a lot of information at the Microsoft developer network to setup the socket. Next step is to register the session, so I need to receive information using the Socket.Receive method. Again, lots of useful information on the developer network.

I'm building the header like this:

private string Build_Header(byte[] Command, int Length) //Build the encapsulate message header. The header is 24 bytes fixed length and includes the command and the length of the optional data portion
    {
        string Header;
        byte[] HeaderStatus = { 0x0, 0x0, 0x0, 0x0 };
        byte[] HeaderOption = { 0x0, 0x0, 0x0, 0x0 };

        try
        {
            Header = Encoding.Unicode.GetString(Command);
            Header += Encoding.Unicode.GetString(BitConverter.GetBytes(Length));
            Header += Encoding.Unicode.GetString(BitConverter.GetBytes(SessionID));
            Header += Encoding.Unicode.GetString(HeaderStatus);
            Header += Encoding.Unicode.GetString(BitConverter.GetBytes(Context));
            Header += Encoding.Unicode.GetString(HeaderOption);

            return Header;
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Failed to create header: {0}", ex);
            return "FAILED";
        }
    }

Witch is a copy of the VB function:

Private Function _build_header(Command As Byte(), length As Short)
    'Build the encapsulate message header
    'The header is 24 bytes fixed length, and includes the command and the length of the optional data portion.
    Dim header As String
    Try
        header = Encoding.Unicode.GetString(Command)                             '# Command UINT
        header += Encoding.Unicode.GetString(BitConverter.GetBytes(length))      '# Length UINT
        header += Encoding.Unicode.GetString(BitConverter.GetBytes(_session))    '# Session Handle UDINT
        header += Encoding.Unicode.GetString({&H0, &H0, &H0, &H0})               '# Status UDINT
        header += Encoding.Unicode.GetString(BitConverter.GetBytes(_Context))    '# Sender Context 8 bytes
        header += Encoding.Unicode.GetString({&H0, &H0, &H0, &H0})               '# Option UDINT
        'If Not DisableLogFile Then Console.WriteLine("Header created: " + Bytes_To_String(Encoding.Unicode.GetBytes(header)) + "  , length: " _
        '                    + header.Length.ToString + "   (must be equal to 24 bytes)")
        Return header
    Catch ex As Exception
        Console.WriteLine("Failed to create header: " + ex.Message)
        Return ""
    End Try
End Function

But I'm receiving an error message as status "Unsupported encapsulation protocol revision." So I started debugging, line after line and found the following difference:

Original (VB):

VB

Copy (C#):

enter image description here

I'm not sure if this is the main problem, but I at least it should be the same format I guess?

14
  • 2
    That's a red herring; both strings contain the same data. The display of it is simply different. If you receive a protocol error, your client and server don't agree on what to send and receive. Commented Jan 29, 2018 at 12:49
  • Is there a way that I can get both strings in the same format, so I can check if the content is exactly the same? Commented Jan 29, 2018 at 12:50
  • @Belekz Use the Immediate Window and just enter Header, it will display the actual (unescaped) value Commented Jan 29, 2018 at 12:52
  • 1
    @Belekz he is wrong; I've found the specification, and whoever wrote that VB code has completely misunderstood binary encoding protocols. The fact that it is working at all right now is completely by chance - they're lucky the encoder didn't corrupt the data. They are doing it very wrong. This is not good code to copy, even if it works. I say this as someone with extensive experience implementing binary network protocols. Lets be 100% clear: this method should not return string. This data is not text. Commented Jan 29, 2018 at 13:03
  • 1
    What the protocol expects: an array of byte. It has a fixed length so it shouldn't be too hard. Commented Jan 29, 2018 at 13:11

1 Answer 1

3

Oof; where to start. K: I'll be frank - the VB reference material you are copying from is garbage. It is 100% wrong and anything that is working right now is completely by chance. For starters, this data is not text - it is a binary frame. Thus, you cannot return string. Returning a byte[] would be reasonable. Thus, the most important question perhaps is not "how should I get this string", but rather "once I have this string, how am I writing it to the socket"? That code isn't shown in your question, but I'd happily wager that how you're writing the string to the socket is the key difference between the two versions.

But: you shouldn't be dealing with strings in the first place. Let's say that method returned a byte[]. Then we have:

byte[] header = BuildHeader(command, length);

Now there's only one way we can write a byte[] to the stream (unless we get very imaginitive):

socket.Send(header, 0, header.Length);

(or some similar variant involving a Stream)

Now; what BuildHeader should be doing is something like:

byte[] header = new byte[24];
// .. fill in
return header;

As for what those parts should be: it depends on the endianness; it looks like UINT is 2 bytes and UDINT is 4. So to give an example of writing length (the second field, 2 bytes, offset 2 bytes), that will be either:

header[2] = (byte)length;
header[3] = (byte)(length >> 8);

or

header[2] = (byte)(length >> 8);
header[3] = (byte)length;

Unfortunately BitConverter.GetBytes doesn't help you pack a buffer, as a: it doesn't allow you to pass a buffer and offset in, and b: the endianness is defined by your CPU (and we need it to be a specific endianness). If the VB code works, it is probably little-endian (unless you have an Itanium), so the first version (little end first) should be correct.

Repeat that for all the fields, and you'll have a valid header.

Sign up to request clarification or add additional context in comments.

9 Comments

ah, update: I found this in the spec: "Multi-byte integer fields in the encapsulation messages shall be transmitted in little-endian byte order." - so yeah, I was right - little-endian
What does the (byte)(length >>8) ?
@Belekz "right shift"; we're trying to pack 16 bits of data into 2 bytes; the (byte)length takes the lowest 8 bits and just drops the rest (assuming that you haven't enabled checked mode) - so let's say our length is 814; in (big-endian) binary that is 00000011 00101110. The (byte)length gives us the 00101110 (46 as a decimal) that we shove into the first byte. >> 8 means "move all the bits 8 places to the right" (dropping any as needed) - leaving us 00000011 (3 as a decimal). So: the little-endian encoding of 814 is (in decimals): 46, 3
@Belekz worth using short instead of int for "length", since it's 2 bytes in your protocol (so int range is much bigger than what you need).
@Belekz three options there: 1: you allocate a second buffer of the size you want, and simply have two calls to Send/Write; 2: you allocate a single buffer at the start that is the right size for both - and pass it into the two methods for them to add their bits; 3: you get all complex with buffer pools etc so you never allocate anything - that's at the extreme hardcore end :)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.