How to read a .NET Guid into a Java UUID
Asked Answered
V

9

27

I need to communicate a Guid that was generated in .NET to a Java application. I use Guid.ToByteArray() to store it on disk as a byte[], then read it into Java and convert it to a UUID. For this purpose I copied the implementation of the (private) constructor of UUID that takes a byte[]:

private UUID(byte[] data) {
    long msb = 0;
    long lsb = 0;
    assert data.length == 16;
    for (int i=0; i<8; i++)
        msb = (msb << 8) | (data[i] & 0xff);
    for (int i=8; i<16; i++)
        lsb = (lsb << 8) | (data[i] & 0xff);
    this.mostSigBits = msb;
    this.leastSigBits = lsb;
}

However, when I inspect the UUID using toString(), the Java UUID is different from the .NET Guid.

For example, the .NET Guid

888794c2-65ce-4de1-aa15-75a11342bc63

turns into the Java UUID

c2948788-ce65-e14d-aa15-75a11342bc63

It seems that the byte ordering of the first three groups is reversed, while the ordering in the last two groups is the same.

Since I would expect the toString() of both the Guid and the UUID to yield the same result, does anyone know how I should correctly read the .NET Guid into a Java UUID?

Edit: To clarify, the implementation is not my own. It is the private constructor of the java.util.UUID class that takes a byte[], which I copied to use for the purpose of reading a byte[] from disk into a UUID.

I do not want to use strings to store the Guids as I'm storing a lot of them and it seems like a waste of space.

Russell Troywest's link at least clarifies why the first couple of groups of the Guid come out reversed, while the second half stays in the same order. The question is, can I depend on .NET always generating these bytes in the same order?

Viscountcy answered 21/4, 2011 at 14:31 Comment(3)
It looks like you are shifting the bits the wrong way. Why try and be cute with it? Read the bytes and make the appropriate assignments first (using an index first) and then use a shift operator to optimize (if necessary). The point is to have easy-to-understand code.Attractive
Java stores data strictly Big Endian, while C# doesn't specify an "endianness" but USUALLY stores data as Little Endian. Like @Attractive said, you're shifting the wrong way.Choline
I've been trying to reverse engineer a framework that uses this construction. Had been staring at the strange bit shift for over two hours until I found this thread.Descent
C
7

In response to your edit, no, you cannot consistently depend on the bytes being generated in the same order. The runtime determines the endianness. C# does however offer BitConverter.isLittleEndian for this very reason.

I know you can't change the endianness of the Java implementation and the bit shifting. But you can shift the bits on the C# end after storing and before sending them to Java.

Update:

MSDN Article on IsLittleEndian

Edit: To be practical, you can PROBABLY count on it always being little endian in its layout of the first chunk of bytes, but technically you can't.

Choline answered 21/4, 2011 at 15:12 Comment(0)
K
13

Could you not just store the .Net Guid as a string and read it into Java? That way you don't need to worry about byte order or anything.

If not then This explains how the bytes are laid out in C#

http://msdn.microsoft.com/en-us/library/fx22893a.aspx

Katy answered 21/4, 2011 at 14:38 Comment(0)
C
12

Edit 2017-08-30: Swapped array elements 6 and 7 per comments.

I have to read & write Guids from/to MySQL (stored as binary(16)) in a C# app, but the database is also used by Java apps. Here are the extension methods I use for converting between .NET little-endian and Java big-endian byte order:

public static class GuidExtensions
{
    /// <summary>
    /// A CLSCompliant method to convert a Java big-endian Guid to a .NET 
    /// little-endian Guid.
    /// The Guid Constructor (UInt32, UInt16, UInt16, Byte, Byte, Byte, Byte,
    ///  Byte, Byte, Byte, Byte) is not CLSCompliant.
    /// </summary>
    [CLSCompliant(true)]
    public static Guid ToLittleEndian(this Guid javaGuid) {
        byte[] net = new byte[16];
        byte[] java = javaGuid.ToByteArray();
        for (int i = 8; i < 16; i++) {
            net[i] = java[i];
        }
        net[3] = java[0];
        net[2] = java[1];
        net[1] = java[2];
        net[0] = java[3];
        net[5] = java[4];
        net[4] = java[5];
        net[6] = java[7];
        net[7] = java[6];
        return new Guid(net);
    }

    /// <summary>
    /// Converts little-endian .NET guids to big-endian Java guids:
    /// </summary>
    [CLSCompliant(true)]
    public static Guid ToBigEndian(this Guid netGuid) {
        byte[] java = new byte[16];
        byte[] net = netGuid.ToByteArray();
        for (int i = 8; i < 16; i++) {
            java[i] = net[i];
        }
        java[0] = net[3];
        java[1] = net[2];
        java[2] = net[1];
        java[3] = net[0];
        java[4] = net[5];
        java[5] = net[4];
        java[6] = net[7];
        java[7] = net[6];
        return new Guid(java);
    }
}
Cavite answered 21/4, 2011 at 14:31 Comment(6)
I'm not sure if this is a java specific thing but I needed to flip endianess for active directory guid / nativeGuid and using this code produces error. The fix is in last two byte swaps that isn't in code: java[6]=net[7]; java[7]=net[6]; Also note that the two methods are identical and you can you can reduce it to one flip function.Overcasting
So you're saying that instead of net[6] = java[6] and net[7] = java[7] it should be swapping these two bytes? I know this code worked for me handling Guids in MySQL (don't have that DB handy right now), but @Russell Troywest's answer below might be the safer option after all.Cavite
that's how Active Directory appears to work. Strange stuff! I'm basically switching between DirectoryEntry.Guid & DirectoryEntry.NativeGuid using your code with that modification. According to documentation of Guids, that last byte should be swapping as per my basic understanding of that textOvercasting
there seems to be a typo (or a bug) in your code of net[6] = java[6];, should it be net[6] = java[7];?Moderate
I had to shift net[7] to 6 and net[6] to 7 in ToBigEndianSod
@Alexander, If that works, then shouldn't ToLittleEndian swap java[6] and java[7] similarly? Assuming so, I'll swap the bytes in both. For the time being, I won't combine the methods into a SwapEndianness method, but perhaps I will later.Cavite
A
12

As already noted, the binary encoding of GUID in .NET has bytes in the first three groups placed in the little-endian order (reversed) – see Guid.ToByteArray Method. To create java.util.UUID from it you can use the following code:

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.UUID;

public UUID toUUID(byte[] binaryEncoding) {
    ByteBuffer source = ByteBuffer.wrap(binaryEncoding);
    ByteBuffer target = ByteBuffer.allocate(16).
        order(ByteOrder.LITTLE_ENDIAN).
        putInt(source.getInt()).
        putShort(source.getShort()).
        putShort(source.getShort()).
        order(ByteOrder.BIG_ENDIAN).
        putLong(source.getLong());
    target.rewind();
    return new UUID(target.getLong(), target.getLong());
}
Airstrip answered 20/2, 2015 at 11:45 Comment(0)
C
7

In response to your edit, no, you cannot consistently depend on the bytes being generated in the same order. The runtime determines the endianness. C# does however offer BitConverter.isLittleEndian for this very reason.

I know you can't change the endianness of the Java implementation and the bit shifting. But you can shift the bits on the C# end after storing and before sending them to Java.

Update:

MSDN Article on IsLittleEndian

Edit: To be practical, you can PROBABLY count on it always being little endian in its layout of the first chunk of bytes, but technically you can't.

Choline answered 21/4, 2011 at 15:12 Comment(0)
W
5

The GUID.toByteArray is pretty odd in C#. The first half are in little-endian and the second half are in big-endia.

A comment on this page notes this fact: http://msdn.microsoft.com/en-us/library/system.guid.tobytearray.aspx

the order of bytes in the returned byte array is different from the string representation of a Guid value. The order of the beginning four-byte group and the next two two-byte groups is reversed, whereas the order of the last two-byte group and the closing six-byte group is the same.

Whitewing answered 3/12, 2011 at 0:19 Comment(0)
Y
3

I think your problem here is that .NET is little-endian but JAVA is big-endian, so when you read a 128 bits integer (a GUID) written by a C# app from a JAVA app you have to do de conversion from little-endian to big-endian.

Yasmeen answered 21/4, 2011 at 14:54 Comment(1)
I think that the C# endianness is technically determined by the runtime and just so happens to be little endian on most CLR implementations. To be really thorough there is always IsLittleEndian and the like. But this is also probably where the problem is coming from. At least that'd be my guess.Choline
P
1

The codecs DotNetGuid1Codec and DotNetGuid4Codec can encode UUIDs to .Net Guids.

// Convert time-based (version 1) to .Net Guid
UuidCodec<UUID> codec = new DotNetGuid1Codec();
UUID guid = codec.encode(timeUuid);
// Convert random-based (version 4) to .Net Guid
UuidCodec<UUID> codec = new DotNetGuid4Codec();
UUID guid = codec.encode(randomUuid);

See: uuid-creator

Pileous answered 23/9, 2019 at 5:59 Comment(0)
W
0

This code works for me.

var msb: Long = 0
var lsb: Long = 0
for(i <- Seq(3, 2, 1, 0, 5, 4, 7, 6)) {
  msb = (msb << 8) | (data(i) & 0xFF)
}
for(i <- 8 until 16) {
  lsb = (lsb << 8) | (data(i) & 0xFF)
}
new UUID(msb, lsb)
Wherewith answered 11/2, 2015 at 14:38 Comment(0)
S
0

Use simd accelerated version

public static Guid ToGuid(this JavaUUID self)
{
    var vec = Vector128.Shuffle(
        Vector128.Create(self.MostSigBits, self.LeastSigBits).AsByte(),
        Vector128.Create((byte)4, 5, 6, 7, 2, 3, 0, 1, 15, 14, 13, 12, 11, 10, 9, 8)
    );
    return Unsafe.As<Vector128<byte>, Guid>(ref vec);
}

public static JavaUUID ToJavaUUID(this Guid self)
{
    var vec = Vector128.Shuffle(
        Unsafe.As<Guid, Vector128<byte>>(ref self).AsByte(),
        Vector128.Create((byte)6, 7, 4, 5, 0, 1, 2, 3, 15, 14, 13, 12, 11, 10, 9, 8)
    ).AsUInt64();
    return new JavaUUID { MostSigBits = vec.GetElement(0), LeastSigBits = vec.GetElement(1) };
}
Surmullet answered 4/2, 2024 at 9:31 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.