1
votes

To save space when passing UUIDs with Protocol Buffers, we send them using the MSB/LSB representation, two 64-bit long values.

message Uuid {
  sfixed64 msb = 1;
  sfixed64 lsb = 2;
} 

These are simple to go to and from in Java,

UUID id = UUID.fromString("eb66c416-4739-465b-9af3-9dc33ed8eef9");
long msb = id.getMostSignificantBits();
long lsb = id.getLeastSignificantBits();
System.out.println(msb + ", " + lsb);
  // -1484283427208739237, -7281302710629372167

System.out.println(new UUID(msb, lsb));
  // eb66c416-4739-465b-9af3-9dc33ed8eef9

However, since JavaScript's number only goes up to 253 - 1, I'm unable to convert the MSB/LSB format back to a string in my TypeScript client. Is this possible?

1

1 Answers

1
votes

Looking at Java UUID's toString() method for inspiration,

public String toString() {
  return (digits(mostSigBits >> 32, 8) + "-" +
    digits(mostSigBits >> 16, 4) + "-" +
    digits(mostSigBits, 4) + "-" +
    digits(leastSigBits >> 48, 4) + "-" +
    digits(leastSigBits, 12));
}

private static String digits(long val, int digits) {
  long hi = 1L << (digits * 4);
  return Long.toHexString(hi | (val & (hi - 1))).substring(1);
}

We can do the same using BigInt. This assumes Node 10.8+ (tested with 14.15.5), TypeScript targeting ES2020+, and with this browser compatibility.

Note: If you get "BigInt literals are not available..." wrap all literals ending with n with BigInt instead (e.g., instead of 32n, use BigInt(32)).

export function toUuidString(lsb: bigint, msb: bigint): string {
  return `${digits(msb >> 32n, 8n)}-${digits(msb >> 16n, 4n)}-${digits(
    msb,
    4n
  )}-${digits(lsb >> 48n, 4n)}-${digits(lsb, 12n)}`;
}

function digits(val: bigint, ds: bigint): string {
  const hi = 1n << (ds * 4n);
  return (hi | (val & (hi - 1n))).toString(16).substring(1);
}

And an example test, notice msb/lsb are passed to BigInt as strings,

it('converts UUID from msb/lsb to string', () => {
  expect(
    toUuidString(
      BigInt('-1160168401362026442'),
      BigInt('-6694969989912915968')
    )
  ).toEqual('a316b044-0157-1000-efe6-40fc5d2f0036');
});

The final piece is protocol buffers. By default, google-protobuf uses number for 64-bit float and int values, which causes overflow above Number.MAX_VALUE or 253 - 1. To avoid this, use the jstype annotation on 64-bit fields,

message Uuid {
    sfixed64 msb = 1 [jstype = JS_STRING];
    sfixed64 lsb = 2 [jstype = JS_STRING];
}