Skip to content

Commit 1bc538e

Browse files
nvobilisseebees
authored andcommitted
feat(hierarchical-keyring): Uuidv4 byte compression (#626)
* wrote code and added tests from MPL * explain the ranges
1 parent be914b7 commit 1bc538e

File tree

3 files changed

+183
-0
lines changed

3 files changed

+183
-0
lines changed

modules/serialize/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './identifiers'
1212
export * from './uint_util'
1313
export * from './signature_info'
1414
export * from './ecdsa_signature'
15+
export * from './uuidv4_factory'
+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { needs } from '@aws-crypto/material-management'
5+
import { validate, version } from 'uuid'
6+
7+
// function to validate a string as uuidv4
8+
const validateUuidv4 = (input: string): boolean =>
9+
validate(input) && version(input) === 4
10+
11+
// accepts user defined lambda functions to convert between a string and
12+
// compressed hex encoded
13+
// bytes. This factory is a higher order function that returns the compression
14+
// and decompression functions based on the input lambda functions
15+
export function uuidv4Factory(
16+
stringToHexBytes: (input: string) => Uint8Array,
17+
hexBytesToString: (input: Uint8Array) => string
18+
) {
19+
return { uuidv4ToCompressedBytes, decompressBytesToUuidv4 }
20+
21+
// remove the '-' chars from the uuid string and convert to hex bytes
22+
function uuidv4ToCompressedBytes(uuidString: string): Uint8Array {
23+
/* Precondition: Input string must be valid uuidv4 */
24+
needs(validateUuidv4(uuidString), 'Input must be valid uuidv4')
25+
26+
const uuidBytes = new Uint8Array(
27+
stringToHexBytes(uuidString.replace(/-/g, ''))
28+
)
29+
30+
/* Postcondition: Compressed bytes must have correct byte length */
31+
needs(
32+
uuidBytes.length === 16,
33+
'Unable to convert uuid into compressed bytes'
34+
)
35+
36+
return uuidBytes
37+
}
38+
39+
// convert the hex bytes to a string. Reconstruct the uuidv4 string with the
40+
// '-' chars
41+
function decompressBytesToUuidv4(uuidBytes: Uint8Array): string {
42+
/* Precondition: Compressed bytes must have correct byte length */
43+
needs(uuidBytes.length === 16, 'Compressed uuid has incorrect byte length')
44+
45+
const hex = hexBytesToString(uuidBytes)
46+
let uuidString: string
47+
48+
try {
49+
// These represent the ranges of the uuidv4 string that contain
50+
// alphanumeric chars. We want to rebuild the proper uuidv4 string by
51+
// joining these segments with the '-' char
52+
uuidString = [
53+
hex.slice(0, 8),
54+
hex.slice(8, 12),
55+
hex.slice(12, 16),
56+
hex.slice(16, 20),
57+
hex.slice(20),
58+
].join('-')
59+
} catch {
60+
throw new Error('Unable to decompress UUID compressed bytes')
61+
}
62+
63+
/* Postcondition: Output string must be valid uuidv4 */
64+
needs(validateUuidv4(uuidString), 'Input must represent a uuidv4')
65+
66+
return uuidString
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// tests contains MPL tests: https://github.com/aws/aws-cryptographic-material-providers-library/blob/da6812fa30315fda75d4277f814d1d0e36e22498/StandardLibrary/test/UUID.dfy
5+
6+
import { v3, v5, v1, v4 } from 'uuid'
7+
import { uuidv4Factory } from '../src/uuidv4_factory'
8+
import { expect } from 'chai'
9+
10+
const stringToHexBytes = (input: string): Uint8Array =>
11+
new Uint8Array(Buffer.from(input, 'hex'))
12+
13+
const hexBytesToString = (input: Uint8Array): string =>
14+
Buffer.from(input).toString('hex')
15+
16+
const { uuidv4ToCompressedBytes, decompressBytesToUuidv4 } = uuidv4Factory(
17+
stringToHexBytes,
18+
hexBytesToString
19+
)
20+
21+
const uuidString = '92382658-b7a0-4d97-9c49-cee4e672a3b3'
22+
const byteUuid = new Uint8Array([
23+
146, 56, 38, 88, 183, 160, 77, 151, 156, 73, 206, 228, 230, 114, 163, 179,
24+
])
25+
const wrongByteUuid = new Uint8Array([
26+
146, 56, 38, 88, 183, 160, 77, 151, 156, 73, 206, 228, 230, 114, 163, 178,
27+
])
28+
29+
describe('uuidv4Factory', () => {
30+
it('Test roundtrip string conversion', () => {
31+
const stringToBytes = uuidv4ToCompressedBytes(uuidString)
32+
expect(stringToBytes).has.lengthOf(16)
33+
const bytesToString = decompressBytesToUuidv4(stringToBytes)
34+
expect(bytesToString).to.equal(uuidString)
35+
})
36+
37+
it('Test roundtrip byte conversion', () => {
38+
const bytesToString = decompressBytesToUuidv4(byteUuid)
39+
const stringToBytes = uuidv4ToCompressedBytes(bytesToString)
40+
expect(stringToBytes).has.lengthOf(16)
41+
expect(stringToBytes).to.deep.equal(byteUuid)
42+
})
43+
44+
it('Test generate and conversion', () => {
45+
const uuid = v4()
46+
const uuidBytes = uuidv4ToCompressedBytes(uuid)
47+
const bytesToString = decompressBytesToUuidv4(uuidBytes)
48+
const stringToBytes = uuidv4ToCompressedBytes(bytesToString)
49+
50+
expect(stringToBytes).has.lengthOf(16)
51+
expect(stringToBytes).to.deep.equal(uuidBytes)
52+
53+
const uuidStringToBytes = uuidv4ToCompressedBytes(uuid)
54+
expect(uuidStringToBytes).has.lengthOf(16)
55+
const uuidBytesToString = decompressBytesToUuidv4(uuidStringToBytes)
56+
expect(uuidBytesToString).to.equal(uuid)
57+
})
58+
59+
describe('decompressBytesToUuidv4', () => {
60+
it('Precondition: Compressed bytes must have correct byte length', () => {
61+
expect(() => decompressBytesToUuidv4(new Uint8Array([0]))).to.throw(
62+
'Compressed uuid has incorrect byte length'
63+
)
64+
})
65+
66+
it('Postcondition: Output string must be valid uuidv4', () => {
67+
expect(() =>
68+
decompressBytesToUuidv4(new Uint8Array(Buffer.alloc(16)))
69+
).to.throw('Input must represent a uuidv4')
70+
})
71+
72+
it('Test success', () => {
73+
const fromBytes = decompressBytesToUuidv4(byteUuid)
74+
expect(fromBytes).to.equal(uuidString)
75+
})
76+
77+
it('Test failure', () => {
78+
const fromBytes = decompressBytesToUuidv4(wrongByteUuid)
79+
expect(fromBytes).to.not.equals(uuidString)
80+
})
81+
})
82+
83+
describe('uuidv4ToCompressedBytes', () => {
84+
it('Precondition: Input string must be valid uuidv4', () => {
85+
expect(() => uuidv4ToCompressedBytes(v1())).to.throw(
86+
'Input must be valid uuidv4'
87+
)
88+
89+
const name = 'example.com'
90+
const namespace = uuidString
91+
expect(() => uuidv4ToCompressedBytes(v3(name, namespace))).to.throw(
92+
'Input must be valid uuidv4'
93+
)
94+
95+
expect(() => uuidv4ToCompressedBytes(v5(name, namespace))).to.throw(
96+
'Input must be valid uuidv4'
97+
)
98+
})
99+
100+
it('Postcondition: Compressed bytes must have correct byte length', () => {
101+
expect(() => uuidv4ToCompressedBytes(uuidString)).to.not.throw()
102+
})
103+
104+
it('Test success', () => {
105+
const fromBytes = uuidv4ToCompressedBytes(uuidString)
106+
expect(fromBytes).to.deep.equal(byteUuid)
107+
})
108+
109+
it('Test failure', () => {
110+
const fromBytes = uuidv4ToCompressedBytes(uuidString)
111+
expect(fromBytes).to.not.deep.equals(wrongByteUuid)
112+
})
113+
})
114+
})

0 commit comments

Comments
 (0)