Skip to content

Commit

Permalink
AVRO-3918: enforce network byte order
Browse files Browse the repository at this point in the history
As stated in RFC 4122 section 4.1.2, UUIDs are in network byte order.

Also added a test for string based UUID conversion.
  • Loading branch information
opwvhk committed Jan 24, 2024
1 parent c9f4dea commit 992e3c2
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 48 deletions.
44 changes: 8 additions & 36 deletions lang/java/avro/src/main/java/org/apache/avro/Conversions.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,6 @@ public class Conversions {

public static class UUIDConversion extends Conversion<UUID> {

private final boolean isBigEndian;

public UUIDConversion() {
this(ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN);
}

public UUIDConversion(final boolean isBigEndian) {
this.isBigEndian = isBigEndian;
}

@Override
public Class<UUID> getConvertedType() {
return UUID.class;
Expand Down Expand Up @@ -82,36 +72,18 @@ public CharSequence toCharSequence(UUID value, Schema schema, LogicalType type)

@Override
public UUID fromFixed(final GenericFixed value, final Schema schema, final LogicalType type) {
long mostSigBits = 0;
long leastSigBits = 0;
byte[] bytes = value.bytes();
for (int i = 0; i < Long.BYTES; i++) {
mostSigBits |= ((long) (bytes[i] & 255)) << (Byte.SIZE * i);
leastSigBits |= ((long) (bytes[i + Long.BYTES] & 255)) << (Byte.SIZE * i);
}

return new UUID(this.convert(mostSigBits), this.convert(leastSigBits));
}

private long convert(long value) {
if (this.isBigEndian) {
return value;
} else {
return Long.reverseBytes(value);
}
ByteBuffer buffer = ByteBuffer.wrap(value.bytes());
long mostSigBits = buffer.getLong();
long leastSigBits = buffer.getLong();
return new UUID(mostSigBits, leastSigBits);
}

@Override
public GenericFixed toFixed(final UUID value, final Schema schema, final LogicalType type) {
final long mostSigBits = this.convert(value.getMostSignificantBits());
final long leastSigBits = this.convert(value.getLeastSignificantBits());
byte[] result = new byte[2 * Long.BYTES];
for (int i = 0; i < Long.BYTES; i++) {
result[i] = (byte) ((mostSigBits >> (i * Byte.SIZE)) & 255);
result[i + Long.BYTES] = (byte) ((leastSigBits >> (i * Byte.SIZE)) & 255);
}

return new GenericData.Fixed(schema, result);
ByteBuffer buffer = ByteBuffer.allocate(2 * Long.BYTES);
buffer.putLong(value.getMostSignificantBits());
buffer.putLong(value.getLeastSignificantBits());
return new GenericData.Fixed(schema, buffer.array());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.math.BigInteger;
import java.util.UUID;
import java.util.stream.Stream;

Expand All @@ -32,29 +33,34 @@ public class TestUuidConversions {
private Conversions.UUIDConversion uuidConversion = new Conversions.UUIDConversion();

private Schema fixed = Schema.createFixed("fixed", "doc", "", Long.BYTES * 2);
private Schema fixedUUid = LogicalTypes.uuid().addToSchema(fixed);
private Schema fixedUuid = LogicalTypes.uuid().addToSchema(fixed);

private Schema string = Schema.createFixed("fixed", "doc", "", Long.BYTES * 2);
private Schema stringUuid = LogicalTypes.uuid().addToSchema(string);

@ParameterizedTest
@MethodSource("uuidData")
void uuidFixed(UUID uuid) {
GenericFixed value = uuidConversion.toFixed(uuid, fixedUUid, LogicalTypes.uuid());
UUID uuid1 = uuidConversion.fromFixed(value, fixedUUid, LogicalTypes.uuid());
GenericFixed value = uuidConversion.toFixed(uuid, fixedUuid, LogicalTypes.uuid());

byte[] b = new byte[Long.BYTES];
System.arraycopy(value.bytes(), 0, b, 0, b.length);
Assertions.assertEquals(uuid.getMostSignificantBits(), new BigInteger(b).longValue());
System.arraycopy(value.bytes(), Long.BYTES, b, 0, b.length);
Assertions.assertEquals(uuid.getLeastSignificantBits(), new BigInteger(b).longValue());

UUID uuid1 = uuidConversion.fromFixed(value, fixedUuid, LogicalTypes.uuid());
Assertions.assertEquals(uuid, uuid1);
}

@ParameterizedTest
@MethodSource("uuidData")
void uuidFixedReverse(UUID uuid) {
Conversions.UUIDConversion bigEndianConversion = new Conversions.UUIDConversion(true);
Conversions.UUIDConversion littleEndianConversion = new Conversions.UUIDConversion(false);
void uuidCharSequence(UUID uuid) {
CharSequence value = uuidConversion.toCharSequence(uuid, stringUuid, LogicalTypes.uuid());

// simulate uuid from other endian source, assume it's big endian (nevermind if
// current jvm is big endian, as we simulate).
UUID reverserUUID = new UUID(Long.reverseBytes(uuid.getMostSignificantBits()),
Long.reverseBytes(uuid.getLeastSignificantBits()));
GenericFixed value = bigEndianConversion.toFixed(reverserUUID, fixedUUid, LogicalTypes.uuid());
Assertions.assertEquals(uuid.toString(), value.toString());

UUID uuid1 = littleEndianConversion.fromFixed(value, fixedUUid, LogicalTypes.uuid());
UUID uuid1 = uuidConversion.fromCharSequence(value, stringUuid, LogicalTypes.uuid());
Assertions.assertEquals(uuid, uuid1);
}

Expand Down

0 comments on commit 992e3c2

Please sign in to comment.