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 };