1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-26 16:09:27 -05:00

feat(std/uuid): Implement uuid v5 (#4916)

This commit is contained in:
underfin 2020-04-27 20:49:34 +08:00 committed by GitHub
parent 516d970fd3
commit df0000ff0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 180 additions and 11 deletions

View file

@ -47,9 +47,9 @@ export class Sha1 {
this._finalized = this._hashed = false;
}
update(data: string | ArrayBuffer | ArrayBufferView): void {
update(data: string | ArrayBuffer | ArrayBufferView): Sha1 {
if (this._finalized) {
return;
return this;
}
let notString = true;
let message;
@ -119,6 +119,7 @@ export class Sha1 {
this._hBytes += (this._bytes / 4294967296) >>> 0;
this._bytes = this._bytes >>> 0;
}
return this;
}
finalize(): void {

View file

@ -3,20 +3,20 @@ const { test } = Deno;
import { assertEquals } from "../testing/asserts.ts";
import { Sha1 } from "./sha1.ts";
test("[ws/sha] test1", () => {
test("[util/sha] test1", () => {
const sha1 = new Sha1();
sha1.update("abcde");
assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
});
test("[ws/sha] testWithArray", () => {
test("[util/sha] testWithArray", () => {
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
const sha1 = new Sha1();
sha1.update(data);
assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
});
test("[ws/sha] testSha1WithBuffer", () => {
test("[util/sha] testSha1WithBuffer", () => {
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
const sha1 = new Sha1();
sha1.update(data.buffer);

View file

@ -13,6 +13,35 @@ export function bytesToUuid(bytes: number[] | Uint8Array): string {
"-",
...bits.slice(8, 10),
"-",
...bits.slice(10),
...bits.slice(10, 16),
].join("");
}
export function uuidToBytes(uuid: string): number[] {
const bytes: number[] = [];
uuid.replace(/[a-fA-F0-9]{2}/g, (hex: string): string => {
bytes.push(parseInt(hex, 16));
return "";
});
return bytes;
}
export function stringToBytes(str: string): number[] {
str = unescape(encodeURIComponent(str));
const bytes = new Array(str.length);
for (let i = 0; i < str.length; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
export function createBuffer(content: number[]): ArrayBuffer {
const arrayBuffer = new ArrayBuffer(content.length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < content.length; i++) {
uint8Array[i] = content[i];
}
return arrayBuffer;
}

View file

@ -2,6 +2,7 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import * as v1 from "./v1.ts";
import * as v4 from "./v4.ts";
import * as v5 from "./v5.ts";
export const NIL_UUID = "00000000-0000-0000-0000-000000000000";
@ -21,7 +22,4 @@ const NOT_IMPLEMENTED = {
// TODO Implement
export const v3 = NOT_IMPLEMENTED;
export { v1, v4 };
// TODO Implement
export const v5 = NOT_IMPLEMENTED;
export { v1, v4, v5 };

View file

@ -11,3 +11,7 @@ import "./tests/v1/generate.ts";
// V4 Tests
import "./tests/v4/validate.ts";
import "./tests/v4/generate.ts";
// V5 Tests
import "./tests/v5/validate.ts";
import "./tests/v5/generate.ts";

View file

@ -0,0 +1,66 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { assert, assertEquals } from "../../../testing/asserts.ts";
const { test } = Deno;
import { generate, validate } from "../../v5.ts";
const NAMESPACE = "1b671a64-40d5-491e-99b0-da01ff1f3341";
test({
name: "[UUID] test_uuid_v5",
fn(): void {
const u = generate({ value: "", namespace: NAMESPACE });
assertEquals(typeof u, "string", "returns a string");
assert(u !== "", "return string is not empty");
},
});
test({
name: "[UUID] test_uuid_v5_format",
fn(): void {
for (let i = 0; i < 10000; i++) {
const u = generate({ value: String(i), namespace: NAMESPACE }) as string;
assert(validate(u), `${u} is not a valid uuid v5`);
}
},
});
test({
name: "[UUID] test_uuid_v5_option",
fn(): void {
const v5Options = {
value: "Hello, World",
namespace: NAMESPACE,
};
const u = generate(v5Options);
assertEquals(u, "4b4f2adc-5b27-57b5-8e3a-c4c4bcf94f05");
},
});
test({
name: "[UUID] test_uuid_v5_buf_offset",
fn(): void {
const buf = [
75,
79,
42,
220,
91,
39,
87,
181,
142,
58,
196,
196,
188,
249,
79,
5,
];
const origin = JSON.parse(JSON.stringify(buf));
generate({ value: "Hello, World", namespace: NAMESPACE }, buf);
assertEquals(origin, buf);
generate({ value: "Hello, World", namespace: NAMESPACE }, buf, 3);
assertEquals(origin.slice(0, 3), buf.slice(0, 3));
assertEquals(origin, buf.slice(3));
},
});

View file

@ -0,0 +1,19 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { assert } from "../../../testing/asserts.ts";
import { generate, validate } from "../../v5.ts";
Deno.test({
name: "[UUID] is_valid_uuid_v5",
fn(): void {
const u = generate({
value: "Hello, World",
namespace: "1b671a64-40d5-491e-99b0-da01ff1f3341",
}) as string;
const t = "4b4f2adc-5b27-57b5-8e3a-c4c4bcf94f05";
const n = "4b4f2adc-5b27-17b5-8e3a-c4c4bcf94f05";
assert(validate(u), `generated ${u} should be valid`);
assert(validate(t), `${t} should be valid`);
assert(!validate(n), `${n} should not be valid`);
},
});

52
std/uuid/v5.ts Normal file
View file

@ -0,0 +1,52 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import {
bytesToUuid,
createBuffer,
stringToBytes,
uuidToBytes,
} from "./_common.ts";
import { Sha1 } from "../util/sha1.ts";
import { isString } from "../node/util.ts";
import { assert } from "../testing/asserts.ts";
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
export function validate(id: string): boolean {
return UUID_RE.test(id);
}
interface V5Options {
value: string | number[];
namespace: string | number[];
}
export function generate(
options: V5Options,
buf?: number[],
offset?: number
): string | number[] {
const i = (buf && offset) || 0;
let { value, namespace } = options;
if (isString(value)) value = stringToBytes(value as string);
if (isString(namespace)) namespace = uuidToBytes(namespace as string);
assert(
namespace.length === 16,
"namespace must be uuid string or an Array of 16 byte values"
);
const content = (namespace as number[]).concat(value as number[]);
const bytes = new Sha1().update(createBuffer(content)).digest();
bytes[6] = (bytes[6] & 0x0f) | 0x50;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
if (buf) {
for (let idx = 0; idx < 16; ++idx) {
buf[i + idx] = bytes[idx];
}
}
return buf || bytesToUuid(bytes);
}

View file

@ -4,7 +4,7 @@ import { decode, encode } from "../encoding/utf8.ts";
import { hasOwnProperty } from "../util/has_own_property.ts";
import { BufReader, BufWriter } from "../io/bufio.ts";
import { readLong, readShort, sliceLongToBytes } from "../io/ioutil.ts";
import { Sha1 } from "./sha1.ts";
import { Sha1 } from "../util/sha1.ts";
import { writeResponse } from "../http/io.ts";
import { TextProtoReader } from "../textproto/mod.ts";
import { Deferred, deferred } from "../util/async.ts";