1

I am trying to write an NRPE interpreter for a Netduino board. This is an Arduino-type board running .NET Micro Framework 4.3. I'm having trouble calculating the CRC that the protocol calls for, which looks like this (original C++ header file snippet):

typedef struct packet_struct {
int16_t packet_version;
int16_t packet_type;
uint32_t crc32_value;
int16_t result_code;
char buffer[1024];
} packet;

There are definitely byte ordering problems because I'm moving from big-endian (Network) to little endian (Netduino/.Net). I have been trying to be careful to reverse and re-reverse the Int16 and Uint32s as they come in and out of my structure. When I re-output a packet I've read in from the wire it is identical, so I believe that much is handled properly. But the CRC I calculate for it is not. The routine I'm calling is Utility.ComputeCRC from the Micro framework

Others have had similar problems in this area, so I'm fortunate enough to have some clues what the problem might be:

The NRPE Protocol Explained

Stack Overflow post about CRC'ing NRPE posts in Python

CRC implementations for Micro

For example, it seems clear the original message is 1034 bytes, padded to 1036. Where I'm not so fortunate is that I'm on the constrained Micro environment, and all the example code for CRC I can find generally involves templates, Linq, or other libraries I don't have access to.

All help appreciated. Here's some sample code where I attempt to re-compute a CRC from an existing valid packet unsuccessfully.

Code Output:

Original 1036 bytes: 0002000174D13FD5426E5F4E5250455F434845434B0000000000000000...
Original CRC: 3FD574D1
1036 bytes with zeroed out checksum:    0002000100000000426E5F4E5250455F434845434B00000000000000....
Re-computed checksum (0xFFFF seed): F5B1C55A

Actual Code:

    using System;
    using System.Text;
    // .NET Micro Framework 4.3
    using Microsoft.SPOT;
    using Microsoft.SPOT.Hardware;

    namespace TestApp
    {
        public class Program
        {
            /// <summary>
            ///     These are the bytes as-received from the wire, hex encoded for readability here.
            /// </summary>
            private const string OriginalNetworkBytes = "0002000174D13FD5426E5F4E5250455F434845434B00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
            /// <summary>
            /// Index the CRC starts at in the original message
            /// </summary>
            private const int CrcIndex = 4;

            public static void Main()
            {
                byte[] bufferBytes = StringToByteArrayFastest(OriginalNetworkBytes);
                PrintBytesInHex("Original " + bufferBytes.Length + " bytes: ", bufferBytes);
                UInt32 originalCrc = ParseSwappedUInt32(bufferBytes, CrcIndex);
                Debug.Print("Original CRC: " + originalCrc.ToString("X"));

                // Zero out CRC, then attempt to recompute the CRC
                ZeroOutChecksum(bufferBytes);
                PrintBytesInHex(bufferBytes.Length + " bytes with zeroed out checksum: ", bufferBytes);
                uint computedCrc = Utility.ComputeCRC(bufferBytes, 0, bufferBytes.Length, 0xFFFF);
                Debug.Print("Re-computed checksum (0xFFFF seed): " + computedCrc.ToString("X"));
            }

            /// <summary>
            ///     From this fine Stack Overflow post:
            ///     https://stackoverflow.com/questions/321370/convert-hex-string-to-byte-array
            ///     Because as the author points out, "also works on .NET Micro Framework where (in SDK4.3) byte.Parse(string) only
            ///     permits integer formats."
            /// </summary>
            /// <param name="hex"></param>
            /// <returns></returns>
            public static byte[] StringToByteArrayFastest(string hex)
            {
                if (hex.Length%2 == 1)
                    throw new Exception("The binary key cannot have an odd number of digits");

                var arr = new byte[hex.Length >> 1];

                for (int i = 0; i < hex.Length >> 1; ++i)
                {
                    arr[i] = (byte) ((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1])));
                }

                return arr;
            }

            public static int GetHexVal(char hex)
            {
                int val = hex;
                //For uppercase A-F letters:
                return val - (val < 58 ? 48 : 55);
                //For lowercase a-f letters:
                //return val - (val < 58 ? 48 : 87);
                //Or the two combined, but a bit slower:
                //return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
            }


            public static UInt32 ParseSwappedUInt32(byte[] byteArray, int arrayIndex)
            {
                byte[] swappedBytes = ByteSwapper(byteArray, arrayIndex, 4);
                return BitConverter.ToUInt32(swappedBytes, 0);
            }

            public static byte[] ByteSwapper(byte[] array, int incomingArrayIndex, int countOfBytesToSwap)
            {
                if (countOfBytesToSwap%2 != 0)
                {
                    throw new Exception("Bytes to be swapped must be divisible by 2; you requested " + countOfBytesToSwap);
                }

                int outgoingArrayIndex = 0;
                byte lastByte = 0;
                var arrayToReturn = new byte[countOfBytesToSwap];
                int finalArrayIndex = incomingArrayIndex + countOfBytesToSwap;
                for (int arrayIndex = incomingArrayIndex; arrayIndex < finalArrayIndex; arrayIndex++)
                {
                    bool isEvenIndex = arrayIndex%2 == 0 || arrayIndex == 0;
                    byte currentByte = array[arrayIndex];
                    if (isEvenIndex)
                    {
                        // Store current byte for next pass through
                        lastByte = currentByte;
                    }
                    else
                    {
                        // Swap two bytes, put into outgoing array
                        arrayToReturn[outgoingArrayIndex] = currentByte;
                        arrayToReturn[outgoingArrayIndex + 1] = lastByte;
                        outgoingArrayIndex += 2;
                    }
                }

                return arrayToReturn;
            }

            private static void ZeroOutChecksum(byte[] messageBytesToClear)
            {
                messageBytesToClear[CrcIndex] = 0;
                messageBytesToClear[CrcIndex + 1] = 0;
                messageBytesToClear[CrcIndex + 2] = 0;
                messageBytesToClear[CrcIndex + 3] = 0;
            }

            /// <summary>
            ///     Debug function to output the message as a hex string
            /// </summary>
            public static void PrintBytesInHex(string messageLabel, byte[] messageBytes)
            {
                string hexString = BytesToHexString(messageBytes);
                Debug.Print(messageLabel + hexString);
            }

            private static string BytesToHexString(byte[] messageBytes)
            {
                var sb = new StringBuilder();
                foreach (byte b in messageBytes)
                {
                    sb.Append(b.ToString("X2"));
                }
                string hexString = sb.ToString();
                return hexString;
            }


        }
    }
Community
  • 1
  • 1
StewLG
  • 81
  • 1
  • 5

1 Answers1

1

I eventually worked out a solution.

The full thing is documented here:

http://www.skyscratch.com/2014/04/02/rats-ate-the-washing-machine-or-a-nagios-nrpe-environmental-monitor-for-netduino/

The relevant CRC code goes like this:

using System;

namespace FloodSensor
{
/// <summary>
/// Ported from https://github.com/KristianLyng/nrpe/blob/master/src/utils.c
/// I am not sure if this was strictly necessary, but then I could not seem to get Utility.ComputeCRC
/// (http://msdn.microsoft.com/query/dev11.query?appId=Dev11IDEF1&l=EN-US&k=k%28Microsoft.SPOT.Hardware.Utility.ComputeCRC%29;k%28TargetFrameworkMoniker-.NETMicroFramework)
/// to return the same result as this function, no matter what seed I tried with it.
/// </summary>
class NrpeCrc
{
    private const int CrcTableLength = 256;
    static private readonly UInt32[] Crc32Table = new UInt32[CrcTableLength];

    public NrpeCrc()
    {
        generateCrc32Table();
    }

    // Build the crc table - must be called before calculating the crc value
    private void generateCrc32Table()
    {
        const uint poly = 0xEDB88320;
        for (int i = 0; i < 256; i++)
        {
            var crc = (UInt32)i;
            for (int j = 8; j > 0; j--)
            {
                if ((crc & (UInt32)1) > 0)
                {
                    crc = (crc >> 1) ^ poly;
                }
                else
                {
                    crc >>= 1;
                }
            }
            Crc32Table[i] = crc;
        }
    }

    /// <summary>
    /// Calculates the CRC 32 value for a buffer
    /// </summary>
    public UInt32 CalculateCrc32(byte[] buffer, int bufferSize)
    {
        int currentIndex;
        uint crc = 0xFFFFFFFF;

        for (currentIndex = 0; currentIndex < bufferSize; currentIndex++)
        {
            int thisChar = buffer[currentIndex];
            crc = ((crc >> 8) & 0x00FFFFFF) ^ Crc32Table[(crc ^ thisChar) & 0xFF];
        }

        return (crc ^ 0xFFFFFFFF);
    }
}
}

See also https://github.com/StewLG/NetduinoNrpe/blob/master/FloodSensor/NrpeServer/NrpeCrc.cs

StewLG
  • 81
  • 1
  • 5