1

Topic: Ubuntu and byte ordering...

Hi -

I've written a .NET Core 3.1 service to read data from a UPS device using HidSharp.  I am targeting 3 different OS's:  Windows, Mac and Ubuntu.

When I publish the service to Windows and Mac self-contained images, everything is working great and I'm getting good values for the various HID Feature reports.

However, when I set my Target runtime to Linux-x64 and attempt to run it on Ubuntu, the values that I'm retrieving from the HID Features are "out of whack" (great technical term!).   For example, Voltage which should be 120V and is getting reported as such on MAC and Window.  However, on Ubuntu I get a value of 4,841V.

My best guess is that it might be some byte ordering difference, though I'm running on a dual-booted 64-bit Windows 10/Ubuntu Intel  i7 machine. The Windows 10 image works, the Ubuntu does not.  Strings on Ubuntu are coming back fine, getting Serial Number, Vendor Id, etc w/o issue.

It's only when I'm trying to convert the byte array to a double (for example) that I'm seeing the issue.

When I publish, I'm targeting Linux-x64 with an Any CPU configuration to create the binary image of the Service.

Any thoughts or suggestions as to what I'm doing wrong would be greatly appreciated.

Thanks!

2

Re: Ubuntu and byte ordering...

All the systems you mentioned are x86_x64 so the byte order should be the same. 
Just to be sure, have you tried reversing the order of the bytes on Ubuntu before converting them?  (as a test only)
If the byte order is indeed reversed, the values should then be what you expected, 
changes are they might still be "out of whack"

3 (edited by johnbizios 2020-01-16 15:35:14)

Re: Ubuntu and byte ordering...

Thanks...actually think I know what is happening, but surprised that no one else is having the same issue.

I dumped out the byte array that is returned for the features that I was requesting and found something interesting.

The call to GetFeature which I invoke as follows:

         public static byte[] GetReportData(byte reportNumber, HidStream stream, int maxInputBufferSize)
        {
            Exception internalException = null;

            try
            {
                var inputReportBuffer = new byte[maxInputBufferSize];
                inputReportBuffer[0] = reportNumber;
                stream.GetFeature(inputReportBuffer);

// DUMP TO CONSOLE TO SEE OUTPUT OF ABOVE CALL
                Console.WriteLine($@"Report {reportNumber}: {BitConverter.ToString(inputReportBuffer)}");
///////////

                return inputReportBuffer;
            }
            catch (Exception e)
            {
                internalException = e;
            }

            throw new Exception($"Unable to retrieve data from UPS after {internalException}", internalException);
        }


In Windows I see this in the Console Window:

Report 0x51: 51-00-00-00-00-00-00-00
Report 0x32: 32-00-00-09-00-00-00-00
Report 0x17: 17-FF-FF-00-00-00-00-00

In Linux I see this in the Console Window:

Report 0x51: 51-51-00-00-00-00-00-00
Report 0x32: 32-32-00-09-00-00-00-00
Report 0x17: 17-17-FF-FF-00-00-00-00

The return from the call to GetFeature returns the reportID in byte 0 & byte 1.

When I took a look at the code in LinuxHidStream.cs, I noticed that the ReportId was being copied to the second byte and the call to NativeMethods.ioctl was indexing at ptr + offset + 1 for the 3rd parameter:

public unsafe override void GetFeature(byte[] buffer, int offset, int count)
        {
            Throw.If.OutOfRange(buffer, offset, count).False(count >= 2);

            HandleAcquireIfOpenOrFail();
            try
            {
                byte reportID = buffer[offset];

                fixed (byte* ptr = buffer)
                {
                    buffer[offset + 1] = reportID;
                    int bytes = NativeMethods.ioctl(_handle, NativeMethods.HIDIOCGFEATURE(count - 1), (IntPtr)(ptr + offset + 1));
                    if (bytes < 0) { throw new IOException("GetFeature failed."); }
                    Array.Clear(buffer, 1 + bytes, count - (1 + bytes));
                }
            }
            finally
            {
                HandleRelease();
            }
        }
       
I changed the code so that it didn't copy the ReportId from byte 0 to byte 1 and adjusted the offset to the call to NativeMethods.ioctl as follows:

             public unsafe override void GetFeature(byte[] buffer, int offset, int count)
        {
            Throw.If.OutOfRange(buffer, offset, count).False(count >= 2);

            HandleAcquireIfOpenOrFail();
            try
            {
            ////////////////////////////////////////////////////
            //  Not needed -- see below
            //    byte reportID = buffer[offset];
            //////////////////////////////////////////

                fixed (byte* ptr = buffer)
                {
              ///////////////////////////////////////////////////////////
              // Remove copying the report ID, since we already have it in byte 0
              //      buffer[offset + 1] = reportID;
              //      int bytes = NativeMethods.ioctl(_handle, NativeMethods.HIDIOCGFEATURE(count - 1), (IntPtr)(ptr + offset + 1));
              ///////////////////////////////////////////////////////////
              
                    int bytes = NativeMethods.ioctl(_handle, NativeMethods.HIDIOCGFEATURE(count), (IntPtr)(ptr + offset));
                    if (bytes < 0) { throw new IOException("GetFeature failed."); }
                    Array.Clear(buffer, bytes, count - bytes);
                }
            }
            finally
            {
                HandleRelease();
            }
        }
       
And everything started working correctly.

Not sure why the code was adding the ReportId twice and surprised that no one else is having a similar issue.

Hoping James might give some insight as to why it was coded this way and if there is an issue with my fix.

Thanks!!