tags:

views:

2494

answers:

6

I copied and pasted this binary data out of sql server, which I am unable to query at this time.

0xBAC893CAB8B7FE03C927417A2A3F6A60BD30FF35E250011CB25507EBFCD5223B

How do I convert it back to a byte array in c#?

+1  A: 

You will need to modify this a little bit (for example, skip over the first two characters), but it does handle spaces in the string:

    /// <summary>
    /// Decodes a hex string, ignoring all non-hex characters, and stores
    /// the decodes series of bytes into the shared buffer. This returns
    /// the number of bytes that were decoded.
    /// <para>Hex characters are [0-9, a-f, A-F].</para>
    /// </summary>
    /// <param name="hexString">String to parse into bytes.</param>
    /// <param name="buffer">Buffer into which to store the decoded binary data.</param>
    /// <returns>The number of bytes decoded.</returns>
    private static int DecodeHexIntoBuffer(string hexString, byte[] buffer)
    {
        int count = 0;

        bool haveFirst = false;
        bool haveSecond = false;
        char first = '0';
        char second = '0';

        for (int i = 0; i < hexString.Length; i++)
        {
            if (!haveFirst)
            {
                first = hexString[i];
                haveFirst = char.IsLetterOrDigit(first);

                // we have to continue to the next iteration
                // or we will miss characters
                continue;
            }

            if (!haveSecond)
            {
                second = hexString[i];
                haveSecond = char.IsLetterOrDigit(second);
            }

            if (haveFirst && haveSecond)
            {
                string hex = "" + first + second;

                byte nextByte;
                if (byte.TryParse(hex, NumberStyles.HexNumber, null, out nextByte))
                {
                    // store the decoded byte into the next slot of the buffer
                    buffer[count++] = nextByte;
                }

                // reset the flags
                haveFirst = haveSecond = false;
            }
        }

        return count;
    }
Erich Mirabal
+3  A: 

Simple:

string hexnum = "0000000F"; // Represents 15
int value = int.Parse(hexnum, System.Globalization.NumberStyles.HexNumber);

All you have to remember to do is for an int to divide the hex number up into groups of 8 hex digits (hex are 4 bits each, and CLR int type is 32 bits, hence 8 digits per int). There's also a byte.Parse() that works the same, but pass in two hex digits at a time.

Kevin
You still need to convert to byte[]. BitConverter would do it. What if the sequence is not a multiple of sizeof(int)?
Erich Mirabal
@Erich - Ya, that's why in my last sentence I mentioned the byte.Parse() method too.
Kevin
+7  A: 

Something like this:

using System;

public static class Parser
{    
    static void Main()
    {
        string hex = "0xBAC893CAB8B7FE03C927417A2A3F6A6"
                     + "0BD30FF35E250011CB25507EBFCD5223B";
        byte[] parsed = ParseHex(hex);
        // Just for confirmation...
        Console.WriteLine(BitConverter.ToString(parsed));
    }

    public static byte[] ParseHex(string hex)
    {
        int offset = hex.StartsWith("0x") ? 2 : 0;
        if ((hex.Length % 2) != 0)
        {
            throw new ArgumentException("Invalid length: " + hex.Length);
        }
        byte[] ret = new byte[(hex.Length-offset)/2];

        for (int i=0; i < ret.Length; i++)
        {
            ret[i] = (byte) ((ParseNybble(hex[offset]) << 4) 
                             | ParseNybble(hex[offset+1]));
            offset += 2;
        }
        return ret;
    }        

    static int ParseNybble(char c)
    {
        if (c >= '0' && c <= '9')
        {
            return c-'0';
        }
        if (c >= 'A' && c <= 'F')
        {
            return c-'A'+10;
        }
        if (c >= 'a' && c <= 'f')
        {
            return c-'a'+10;
        }
        throw new ArgumentException("Invalid hex digit: " + c);
    }
}

(EDIT: Now slightly more efficient - no substrings required...)

It's possible that ParseNybble could be more efficient. For example, a switch/case may be more efficient:

    static int ParseNybble(char c)
    {
        switch (c)
        {
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                return c-'0';
            case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
                return c-'A'+10;
            case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
                return c-'a'+10;
        }
        throw new ArgumentException("Invalid hex digit: " + c);
    }

or possibly a lookup array:

    // Omitted for brevity... I'm sure you get the gist
    private static readonly int[] NybbleLookup = BuildLookup();

    private int ParseNybble(char c)
    {
        if (c > 'f')
        {
            throw new ArgumentException("Invalid hex digit: " + c);
        }
        int ret = NybbleLookup[c];
        if (ret == -1)
        {
            throw new ArgumentException("Invalid hex digit: " + c);
        }
        return ret;
    }

I haven't benchmarked any of these, and I've no idea which would be the fastest. The current solution is probably the simplest though.

Jon Skeet
why do you prefer parsing each nybble instead of one byte at a time?is it because you are more efficient by not creating new strings each time?
Erich Mirabal
Yes. Why not? It's efficient, and this is the kind of code you only need once in a library somewhere. Creating n*2 strings to create a byte array of length n seems pretty wasteful when the alternative is so easy.
Jon Skeet
(Btw, downvoters: comments are appreciated...)
Jon Skeet
Sorry, creating n strings, not n*2. But each of those strings will be about 20 bytes or so, which seems rather a large bloat factor. All gen0 of course, but easily avoidable.
Jon Skeet
+1. I made mine to be a little more "straight-forward" but I think yours is much more efficient. Maybe I'll incorporate the ParseNybble. It would be pretty easy to merge that into my exisiting code.
Erich Mirabal
Also, instead of doing the substring, you could just do an offset into the start value of the loop (i = offset...) and the same for length (length = (hex/2) - 1) if hex starts with "0x"
Erich Mirabal
@Erich: Absolutely. It would just complicate things a little... I wanted to keep this *quite* efficient but still simple. Might see what I can do though :)
Jon Skeet
Okay, so I couldn't resist doing a bit of tweaking :)
Jon Skeet
A: 

Something like this:

    public byte[] ParseHexString(string text)
    {
        if ((text.Length % 2) != 0)
        {
            throw new ArgumentException("Invalid length: " + text.Length);
        }

        if (text.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase))
        {
            text = text.Substring(2);
        }

        int arrayLength = text.Length / 2;
        byte[] byteArray = new byte[arrayLength];
        for (int i = 0; i < arrayLength; i++)
        {
            byteArray[i] = byte.Parse(text.Substring(i*2, 2), NumberStyles.HexNumber);
        }

        return byteArray;
    }
MatteKarla
A: 

This answer is not yet finished, but I have to leave now...

Assuming ASCII encoding and c in [0-9A-Fa-f] ...

Int32 nibble = (c & 0x4F) % 0x37;


Int32 h = c & 0x40;
Int32 nibble = ((c & 0x0F) | (h >> 3)) + (h >> 6)
Daniel Brückner
+2  A: 

Consider leveraging a Framework class that already exposes the ability to perform hex conversion, XmlReader for example:

public static byte[] HexToBytes(this string hexEncodedBytes, int start, int end)
{
    int length = end - start;
    const string tagName = "hex";
    string fakeXmlDocument = String.Format("<{1}>{0}</{1}>",
               hexEncodedBytes.Substring(start, length),
               tagName);
    var stream = new MemoryStream(Encoding.ASCII.GetBytes(fakeXmlDocument));
    XmlReader reader = XmlReader.Create(stream, new XmlReaderSettings());
    int hexLength = length / 2;
    byte[] result = new byte[hexLength];
    reader.ReadStartElement(tagName);
    reader.ReadContentAsBinHex(result, 0, hexLength);
    return result;
}

usage:

string input = "0xBAC893CAB8B7FE03C927417A2A3F6A60BD30FF35E250011CB255";
byte[] bytes = input.HexToBytes(2, input.Length);
Handcraftsman