0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-04 13:50:12 -05:00

🐛 Fix incorrect encoding of u32 parts of uuid

This commit is contained in:
Andrey Antukh 2024-11-27 08:36:34 +01:00
parent 5b52e2a50b
commit 965d457664
3 changed files with 196 additions and 55 deletions

View file

@ -44,15 +44,15 @@
[v]
(= zero v))
#?(:clj
(defn get-word-high
[id]
(.getMostSignificantBits ^UUID id)))
(defn get-word-high
[id]
#?(:clj (.getMostSignificantBits ^UUID id)
:cljs (impl/getHi (.-uuid ^UUID id))))
#?(:clj
(defn get-word-low
[id]
(.getLeastSignificantBits ^UUID id)))
(defn get-word-low
[id]
#?(:clj (.getLeastSignificantBits ^UUID id)
:cljs (impl/getLo (.-uuid ^UUID id))))
(defn get-bytes
[^UUID o]
@ -80,12 +80,21 @@
[id]
(impl/shortV8 (dm/str id))))
#?(:cljs
(defn get-unsigned-parts
"Get a Uint32 array of length 4 that represents the UUID, needed
for interact with wasm"
[this]
(impl/getUnsignedParts (.-uuid ^UUID this))))
#?(:cljs
(defn get-u32
"A cached variant of get-unsigned-parts"
[this]
(let [buffer (unchecked-get this "__u32_buffer")]
(if (nil? buffer)
(let [buffer (impl/getUnsignedInt32Array (.-uuid ^UUID this))]
(let [buffer (get-unsigned-parts (.-uuid ^UUID this))]
(unchecked-set this "__u32_buffer" buffer)
buffer)
buffer))))
@ -97,3 +106,33 @@
b (.getLeastSignificantBits ^UUID id)]
(+ (clojure.lang.Murmur3/hashLong a)
(clojure.lang.Murmur3/hashLong b)))))
;; Commented code used for debug
;; #?(:cljs
;; (defn ^:export test-uuid
;; []
;; (let [expected #uuid "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8"]
;;
;; (js/console.log "===> to-from-bytes-roundtrip")
;; (js/console.log (uuid.impl/getBytes (str expected)))
;; (js/console.log (uuid.impl/fromBytes (uuid.impl/getBytes (str expected))))
;;
;; (js/console.log "===> HI LO roundtrip")
;; (let [hi (uuid.impl/getHi (str expected))
;; lo (uuid.impl/getLo (str expected))
;; res (uuid.impl/custom hi lo)]
;;
;; (js/console.log "HI:" hi)
;; (js/console.log "LO:" lo)
;; (js/console.log "RS:" res))
;;
;; (js/console.log "===> OTHER")
;; (let [parts (uuid.impl/getUnsignedParts (str expected))
;; res (uuid.impl/fromUnsignedParts (aget parts 0)
;; (aget parts 1)
;; (aget parts 2)
;; (aget parts 3))]
;; (js/console.log "PARTS:" parts)
;; (js/console.log "RES: " res))
;;
;; )))

View file

@ -192,6 +192,76 @@ goog.scope(function() {
}
};
const fillBytes = (uuid) => {
let rest;
int8[0] = (rest = parseInt(uuid.slice(0, 8), 16)) >>> 24;
int8[1] = (rest >>> 16) & 0xff;
int8[2] = (rest >>> 8) & 0xff;
int8[3] = rest & 0xff;
// Parse ........-####-....-....-............
int8[4] = (rest = parseInt(uuid.slice(9, 13), 16)) >>> 8;
int8[5] = rest & 0xff;
// Parse ........-....-####-....-............
int8[6] = (rest = parseInt(uuid.slice(14, 18), 16)) >>> 8;
int8[7] = rest & 0xff;
// Parse ........-....-....-####-............
int8[8] = (rest = parseInt(uuid.slice(19, 23), 16)) >>> 8;
int8[9] = rest & 0xff,
// Parse ........-....-....-....-############
// (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes)
int8[10] = ((rest = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff;
int8[11] = (rest / 0x100000000) & 0xff;
int8[12] = (rest >>> 24) & 0xff;
int8[13] = (rest >>> 16) & 0xff;
int8[14] = (rest >>> 8) & 0xff;
int8[15] = rest & 0xff;
}
const fromPair = (hi, lo) => {
view.setBigInt64(0, hi);
view.setBigInt64(8, lo);
return encoding.bufferToHex(int8, true);
}
const getHi = (uuid) => {
fillBytes(uuid);
return view.getBigInt64(0);
}
const getLo = (uuid) => {
fillBytes(uuid);
return view.getBigInt64(8);
}
const getBytes = (uuid) => {
fillBytes(uuid);
return Int8Array.from(int8);
}
const getUnsignedParts = (uuid) => {
fillBytes(uuid);
const result = new Uint32Array(4);
result[0] = view.getUint32(0)
result[1] = view.getUint32(4);
result[2] = view.getUint32(8);
result[3] = view.getUint32(12);
return result;
}
const fromUnsignedParts = (a, b, c, d) => {
view.setUint32(0, a)
view.setUint32(4, b)
view.setUint32(8, c)
view.setUint32(12, d)
return encoding.bufferToHex(int8, true);
}
const fromArray = (u8data) => {
int8.set(u8data);
return encoding.bufferToHex(int8, true);
@ -209,8 +279,14 @@ goog.scope(function() {
};
factory.create = create;
factory.setTag = setTag;
factory.fromArray = fromArray;
factory.fromPair = fromPair;
factory.fromUnsignedParts = fromUnsignedParts;
factory.getBytes = getBytes;
factory.getHi = getHi;
factory.getLo = getLo;
factory.getUnsignedParts = getUnsignedParts;
factory.setTag = setTag;
return factory;
})();
@ -220,67 +296,44 @@ goog.scope(function() {
return encoding.bufferToBase62(short);
};
self.custom = function formatAsUUID(mostSigBits, leastSigBits) {
const most = mostSigBits.toString("16").padStart(16, "0");
const least = leastSigBits.toString("16").padStart(16, "0");
return `${most.substring(0, 8)}-${most.substring(8, 12)}-${most.substring(12)}-${least.substring(0, 4)}-${least.substring(4)}`;
self.custom = function formatAsUUID(hi, lo) {
if (!(hi instanceof BigInt)) {
hi = BigInt(hi);
}
if (!(hi instanceof BigInt)) {
lo = BigInt(lo);
}
return self.v8.fromPair(hi, lo);
};
self.fromBytes = function(data) {
if (data instanceof Uint8Array) {
return self.v8.fromArray(data);
} else if (data instanceof Int8Array) {
data = Uint8Array.from(data);
return self.v8.fromArray(data);
} else {
let buffer = data?.buffer;
if (buffer instanceof ArrayBuffer) {
data = new Uint8Array(buffer);
return self.v8.fromArray(data);
} else {
throw new Error("invalid array type received");
}
throw new Error("invalid array type received");
}
};
// Code based from uuidjs/parse.ts
self.getBytes = function parse(uuid) {
const buffer = new ArrayBuffer(16);
const view = new Int8Array(buffer);
let rest;
return self.v8.getBytes(uuid);
};
// Parse ########-....-....-....-............
view[0] = (rest = parseInt(uuid.slice(0, 8), 16)) >>> 24;
view[1] = (rest >>> 16) & 0xff;
view[2] = (rest >>> 8) & 0xff;
view[3] = rest & 0xff;
self.getUnsignedParts = function (uuid) {
return self.v8.getUnsignedParts(uuid);
};
// Parse ........-####-....-....-............
view[4] = (rest = parseInt(uuid.slice(9, 13), 16)) >>> 8;
view[5] = rest & 0xff;
self.fromUnsignedParts = function(a,b,c,d) {
return self.v8.fromUnsignedParts(a,b,c,d);
};
// Parse ........-....-####-....-............
view[6] = (rest = parseInt(uuid.slice(14, 18), 16)) >>> 8;
view[7] = rest & 0xff;
// Parse ........-....-....-####-............
view[8] = (rest = parseInt(uuid.slice(19, 23), 16)) >>> 8;
view[9] = rest & 0xff,
// Parse ........-....-....-....-############
// (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes)
view[10] = ((rest = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff;
view[11] = (rest / 0x100000000) & 0xff;
view[12] = (rest >>> 24) & 0xff;
view[13] = (rest >>> 16) & 0xff;
view[14] = (rest >>> 8) & 0xff;
view[15] = rest & 0xff;
return view;
self.getHi = function (uuid) {
return self.v8.getHi(uuid);
}
self.getUnsignedInt32Array = function (uuid) {
const bytes = self.getBytes(uuid);
return new Uint32Array(bytes.buffer);
self.getLo = function (uuid) {
return self.v8.getLo(uuid);
}
});

View file

@ -43,5 +43,54 @@
(t/is (= result uuid))))))
(t/deftest bytes-roundtrip-2
(let [uuid (uuid/uuid "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8")
result-bytes (uuid/get-bytes uuid)
expected-hi #?(:clj -6799692559624781374
:cljs (js/BigInt "-6799692559624781374"))
expected-lo #?(:clj -3327364263599220776
:cljs (js/BigInt "-3327364263599220776"))
expected-bytes [-95, -94, -93, -92, -79, -78, -63, -62, -47, -46, -45, -44, -43, -42, -41, -40]]
(t/testing "get-bytes"
(let [data (uuid/get-bytes uuid)]
(t/is (= (nth expected-bytes 0) (aget data 0)))
(t/is (= (nth expected-bytes 1) (aget data 1)))
(t/is (= (nth expected-bytes 2) (aget data 2)))
(t/is (= (nth expected-bytes 3) (aget data 3)))
(t/is (= (nth expected-bytes 4) (aget data 4)))
(t/is (= (nth expected-bytes 5) (aget data 5)))
(t/is (= (nth expected-bytes 6) (aget data 6)))
(t/is (= (nth expected-bytes 7) (aget data 7)))
(t/is (= (nth expected-bytes 8) (aget data 8)))
(t/is (= (nth expected-bytes 9) (aget data 9)))
(t/is (= (nth expected-bytes 10) (aget data 10)))
(t/is (= (nth expected-bytes 11) (aget data 11)))
(t/is (= (nth expected-bytes 12) (aget data 12)))
(t/is (= (nth expected-bytes 13) (aget data 13)))
(t/is (= (nth expected-bytes 14) (aget data 14)))
(t/is (= (nth expected-bytes 15) (aget data 15)))))
(t/testing "from-bytes"
(let [data (create-array expected-bytes)
result (uuid/from-bytes data)]
(t/is (= result uuid))))
(t/testing "hi-low"
(let [hi (uuid/get-word-high uuid)
lo (uuid/get-word-low uuid)]
(t/is (= hi expected-hi))
(t/is (= lo expected-lo))))
#?(:cljs
(t/testing "unsigned-parts"
(let [parts (uuid/get-unsigned-parts uuid)
expected [2711790500, 2981282242, 3520254932, 3587626968]]
(t/is (instance? js/Uint32Array parts))
(t/is (= (nth expected 0) (aget parts 0)))
(t/is (= (nth expected 1) (aget parts 1)))
(t/is (= (nth expected 2) (aget parts 2)))
(t/is (= (nth expected 3) (aget parts 3))))))))