Apollo3zehn / FluentModbus

Lightweight and fast client and server implementation of the Modbus protocol (TCP/RTU).
MIT License
216 stars 74 forks source link

Mid Little/Big endian #45

Open Adhara3 opened 3 years ago

Adhara3 commented 3 years ago

Hi,

I just wanted to let you know that the server we are working on in MidLittleEndian. No problem, I just read bytes and then interpret byte order myself, I just wanted to let you know that there are these scenarios that are currently uncovered by the lib.

Cheers A

Apollo3zehn commented 3 years ago

Thanks for that hint. I have no experience with any non-Little endian systems. Only big-endian is sometimes required because network procotols often demand it.

Could you please give a short example of how those numbers would look like? With the new intrinsics support of .NET Core for Intel CPUs, we can now hardware accelerate the byte shuffling. I have already done it in another project (e.g. here for AVX2: https://github.com/Apollo3zehn/HDF5.NET/blob/master/src/HDF5.NET/Filters/EndiannessConverterAvx2.cs), so it won't take too much time to implement it here.

Adhara3 commented 3 years ago

Hi,

I had myself to look at the raw bytes I was receiving from the server, then inserted them in here: https://www.scadacore.com/tools/programming-calculators/online-hex-converter/

I knew I was expecting a particular uint value of 141... so I just found out which was the endianness that was giving the right number. That's how I discovered the existence of mid-little endian. The byte layout is indicated above the boxes.

In your code you are inheriting from BinaryReader and you explicitly call the Reverse methods, which works fine for 2 cases scenario. In my case I did implement a converter interface

public interface IBitConverter
{
    float ReadFloat(byte[] data, ref int index);
    uint ReadUint32(byte[] data, ref int index);
}

with 4 implementations, like the following

internal class MidLittleEndianBitConverter : IBitConverter
{
    public float ReadFloat(byte[] data, ref int index)
    {
        return new Int32SingleUnion(ReadUint32(data, ref index)).AsSingle;
    }

    public uint ReadUint32(byte[] data, ref int index)
    {
        return (uint)(data[index++] << 8 | data[index++] | data[index++] << 24 | data[index++] << 16);
    }
}

which had (for my case) the advantage that once set up, the caller had no clue about the endianness I know that is very raw, may be not span<T> compliant and may have other disadvantages depending o the context. I usually prefer interface --> implementation over enums because of this flexibility.

Last but not least, in my case I had to read 120bytes from holding registers and then go through them and interpret the types (they were mixed: 4 floats, then 5 uint, then 3 float, etc...) so instead of calling N holding registers strongly typed reads, I preferred one "huge" raw read (it was also much faster). This explains the weird ref int index: I needed to navigate the full data and that way the "pointer" was auto updating based on the reads.

Just for reference, I also used this Jon Skeet old lib and this is where the Int32SingleUnion comes from A

Apollo3zehn commented 3 years ago

Thanks for your input, I will go through to it soon and see if I can improvide the code accordingly!