hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

uuid.ha (4640B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bytes;
      5 use crypto::random;
      6 use endian;
      7 use fmt;
      8 use io;
      9 use memio;
     10 use strconv;
     11 use strings;
     12 
     13 // A UUID.
     14 export type uuid = [16]u8;
     15 
     16 // The length of a UUID in bytes.
     17 export def UUID_LEN: size = 16;
     18 
     19 // The length of a UUID in runes when encoded as a string.
     20 export def UUID_STRLEN: size = 36;
     21 
     22 // The length of the return value of [[uri]] in runes.
     23 export def UUID_URILEN: size = 45;
     24 
     25 // The "nil" UUID, with all bits set to zero.
     26 export const nil: uuid = [0...];
     27 
     28 // Returned from [[decode]] if an invalid UUID is observed.
     29 export type invalid = !void;
     30 
     31 // Octet offsets of various fields as defined by the RFC.
     32 def TIME_LOW: size = 0;
     33 def TIME_MID: size = 4;
     34 def TIME_HI_AND_VERSION: size = 6;
     35 def CLOCK_SEQ_HI_AND_RESERVED: size = 8;
     36 def CLOCK_SEQ_LOW: size = 9;
     37 def NODE: size = 10;
     38 
     39 // Generates a new version 4 UUID.
     40 export fn generate() uuid = {
     41 	let id: uuid = [0...];
     42 	random::buffer(id[..]);
     43 	let buf = id[CLOCK_SEQ_HI_AND_RESERVED..CLOCK_SEQ_HI_AND_RESERVED + 2];
     44 	let clock = (endian::begetu16(buf) & 0x3FFF) | 0x8000;
     45 	endian::beputu16(buf, clock);
     46 	let buf = id[TIME_HI_AND_VERSION..TIME_HI_AND_VERSION + 2];
     47 	let version = (endian::begetu16(buf) & 0x0FFF) | 0x4000;
     48 	endian::beputu16(buf, version);
     49 	return id;
     50 };
     51 
     52 // Returns true if two UUIDs are equal.
     53 export fn compare(a: uuid, b: uuid) bool = bytes::equal(a, b);
     54 
     55 // Encodes a UUID as a string and writes it to an I/O handle.
     56 export fn encode(out: io::handle, in: uuid) (size | io::error) = {
     57 	let z = 0z;
     58 	for (let i = TIME_LOW; i < TIME_LOW + 4; i += 1) {
     59 		z += fmt::fprintf(out, "{:.2x}", in[i])?;
     60 	};
     61 	z += fmt::fprintf(out, "-")?;
     62 	for (let i = TIME_MID; i < TIME_MID + 2; i += 1) {
     63 		z += fmt::fprintf(out, "{:.2x}", in[i])?;
     64 	};
     65 	z += fmt::fprintf(out, "-")?;
     66 	for (let i = TIME_HI_AND_VERSION; i < TIME_HI_AND_VERSION + 2; i += 1) {
     67 		z += fmt::fprintf(out, "{:.2x}", in[i])?;
     68 	};
     69 	z += fmt::fprintf(out, "-{:.2x}{:.2x}-",
     70 		in[CLOCK_SEQ_HI_AND_RESERVED], in[CLOCK_SEQ_LOW])?;
     71 	for (let i = NODE; i < NODE + 6; i += 1) {
     72 		z += fmt::fprintf(out, "{:.2x}", in[i])?;
     73 	};
     74 	return z;
     75 };
     76 
     77 // Encodes a UUID as a URI and writes it to an I/O handle.
     78 export fn uri(out: io::handle, in: uuid) (size | io::error) = {
     79 	return fmt::fprintf(out, "urn:uuid:")? + encode(out, in)?;
     80 };
     81 
     82 // Encodes a UUID as a string. The return value is statically allocated, the
     83 // caller must use [[strings::dup]] to extend its lifetime.
     84 export fn encodestr(in: uuid) str = {
     85 	static let buf: [UUID_STRLEN]u8 = [0...];
     86 	let sink = memio::fixed(buf);
     87 	encode(&sink, in) as size;
     88 	return memio::string(&sink)!;
     89 };
     90 
     91 // Encodes a UUID as a string. The return value is statically allocated, the
     92 // caller must use [[strings::dup]] to extend its lifetime.
     93 export fn encodeuri(in: uuid) str = {
     94 	static let buf: [UUID_URILEN]u8 = [0...];
     95 	let sink = memio::fixed(buf);
     96 	uri(&sink, in) as size;
     97 	return memio::string(&sink)!;
     98 };
     99 
    100 @test fn encode() void = {
    101 	let in: uuid = [
    102 		0x3d, 0xed, 0x91, 0x0c, 0x80, 0x80, 0x4b, 0xc8,
    103 		0xaf, 0x39, 0xb6, 0xcc, 0xce, 0xe3, 0x67, 0x41,
    104 	];
    105 	assert(encodestr(in) == "3ded910c-8080-4bc8-af39-b6cccee36741");
    106 };
    107 
    108 // Decodes a UUID as a string from an [[io::handle]].
    109 export fn decode(in: io::handle) (uuid | invalid | io::error) = {
    110 	let u: uuid = [0...];
    111 	for (let i = 0z; i < len(u); i += 1) {
    112 		let buf: [2]u8 = [0...];
    113 		match (io::readall(in, buf)?) {
    114 		case io::EOF =>
    115 			return invalid;
    116 		case let z: size => void;
    117 		};
    118 		u[i] = match (strconv::stou8(
    119 				strings::fromutf8_unsafe(buf),
    120 				strconv::base::HEX)) {
    121 		case strconv::overflow =>
    122 			abort();
    123 		case strconv::invalid =>
    124 			return invalid;
    125 		case let u: u8 =>
    126 			yield u;
    127 		};
    128 		if (i + 1 == TIME_MID
    129 				|| i + 1 == TIME_HI_AND_VERSION
    130 				|| i + 1 == CLOCK_SEQ_HI_AND_RESERVED
    131 				|| i + 1 == NODE) {
    132 			match (io::readall(in, buf[..1])?) {
    133 			case io::EOF =>
    134 				return invalid;
    135 			case let z: size => void;
    136 			};
    137 			if (buf[0] != '-') {
    138 				return invalid;
    139 			};
    140 		};
    141 	};
    142 	return u;
    143 };
    144 
    145 // Decodes a UUID from a string.
    146 export fn decodestr(in: str) (uuid | invalid) = {
    147 	let buf = memio::fixed(strings::toutf8(in));
    148 	match (decode(&buf)) {
    149 	case let err: io::error =>
    150 		abort();
    151 	case invalid =>
    152 		return invalid;
    153 	case let u: uuid =>
    154 		return u;
    155 	};
    156 };
    157 
    158 @test fn decode() void = {
    159 	let in = "3ded910c-8080-4bc8-af39-b6cccee36741";
    160 	let id = match (decodestr(in)) {
    161 	case invalid =>
    162 		abort();
    163 	case let u: uuid =>
    164 		yield u;
    165 	};
    166 	assert(compare(id, [
    167 		0x3d, 0xed, 0x91, 0x0c, 0x80, 0x80, 0x4b, 0xc8,
    168 		0xaf, 0x39, 0xb6, 0xcc, 0xce, 0xe3, 0x67, 0x41,
    169 	]));
    170 	assert(decodestr("hello world") is invalid);
    171 };