hex.ha (5791B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use ascii; 5 use bytes; 6 use errors; 7 use fmt; 8 use io; 9 use memio; 10 use os; 11 use strconv; 12 use strings; 13 14 export type encoder = struct { 15 stream: io::stream, 16 out: io::handle, 17 err: (void | io::error), 18 }; 19 20 const encoder_vtable: io::vtable = io::vtable { 21 writer = &encode_writer, 22 ... 23 }; 24 25 // Creates a stream that encodes writes as lowercase hexadecimal before writing 26 // them to a secondary stream. Closing this stream will not close the underlying 27 // stream. 28 export fn newencoder(out: io::handle) encoder = { 29 return encoder { 30 stream = &encoder_vtable, 31 out = out, 32 err = void, 33 }; 34 }; 35 36 fn encode_writer(s: *io::stream, in: const []u8) (size | io::error) = { 37 const s = s: *encoder; 38 match(s.err) { 39 case let err: io::error => 40 return err; 41 case void => void; 42 }; 43 let z = 0z; 44 for (let i = 0z; i < len(in); i += 1) { 45 const r = strconv::u8tos(in[i], strconv::base::HEX_LOWER); 46 if (len(r) == 1) { 47 match(fmt::fprint(s.out, "0")) { 48 case let b: size => 49 z += b; 50 case let err: io::error => 51 s.err = err; 52 return err; 53 }; 54 }; 55 match(fmt::fprint(s.out, r)) { 56 case let b: size => 57 z += b; 58 case let err: io::error => 59 s.err = err; 60 return err; 61 }; 62 }; 63 return z; 64 }; 65 66 // Encodes a byte slice as a hexadecimal string and returns it. The caller must 67 // free the return value. 68 export fn encodestr(in: []u8) str = { 69 const out = memio::dynamic(); 70 const enc = newencoder(&out); 71 io::writeall(&enc, in)!; 72 return memio::string(&out)!; 73 }; 74 75 @test fn encodestr() void = { 76 let in: [_]u8 = [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xF0, 0x0D]; 77 let s = encodestr(in); 78 defer free(s); 79 assert(s == "cafebabedeadf00d"); 80 }; 81 82 // Encodes a byte slice as a hexadecimal string and writes it to an 83 // [[io::handle]]. 84 export fn encode(out: io::handle, in: []u8) (size | io::error) = { 85 const enc = newencoder(out); 86 return io::writeall(&enc, in); 87 }; 88 89 @test fn encode() void = { 90 const in: [_]u8 = [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xF0, 0x0D]; 91 92 let out = memio::dynamic(); 93 defer io::close(&out)!; 94 95 encode(&out, in)!; 96 assert(memio::string(&out)! == "cafebabedeadf00d"); 97 }; 98 99 export type decoder = struct { 100 stream: io::stream, 101 in: io::handle, 102 state: (void | io::EOF | io::error), 103 }; 104 105 const decoder_vtable: io::vtable = io::vtable { 106 reader = &decode_reader, 107 ... 108 }; 109 110 // Creates a stream that reads and decodes hexadecimal data from a secondary 111 // stream. This stream does not need to be closed, and closing it will not 112 // close the underlying stream. 113 export fn newdecoder(in: io::handle) decoder = { 114 return decoder { 115 stream = &decoder_vtable, 116 in = in, 117 state = void, 118 ... 119 }; 120 }; 121 122 fn decode_reader(s: *io::stream, out: []u8) (size | io::EOF | io::error) = { 123 const s = s: *decoder; 124 match(s.state) { 125 case let err: (io::EOF | io::error) => 126 return err; 127 case void => void; 128 }; 129 static let buf: [os::BUFSZ]u8 = [0...]; 130 let n = len(out) * 2; 131 if (n > os::BUFSZ) { 132 n = os::BUFSZ; 133 }; 134 let nr = 0z; 135 for (nr < n) { 136 match(io::read(s.in, buf[nr..n])) { 137 case let n: size => 138 nr += n; 139 case io::EOF => 140 s.state = io::EOF; 141 break; 142 case let err: io::error => 143 s.state = err; 144 return err; 145 }; 146 }; 147 if (nr % 2 != 0) { 148 s.state = errors::invalid; 149 return errors::invalid; 150 }; 151 const l = nr / 2; 152 for (let i = 0z; i < l; i += 1) { 153 const oct = strings::fromutf8_unsafe(buf[i * 2..i * 2 + 2]); 154 const u = match (strconv::stou8(oct, 16)) { 155 case (strconv::invalid | strconv::overflow) => 156 s.state = errors::invalid; 157 return errors::invalid; 158 case let u: u8 => 159 yield u; 160 }; 161 out[i] = u; 162 }; 163 return l; 164 }; 165 166 // Decodes a string of hexadecimal bytes into a byte slice. The caller must free 167 // the return value. 168 export fn decodestr(s: str) ([]u8 | io::error) = { 169 let s = strings::toutf8(s); 170 const in = memio::fixed(s); 171 const decoder = newdecoder(&in); 172 const out = memio::dynamic(); 173 match(io::copy(&out, &decoder)) { 174 case size => 175 return memio::buffer(&out); 176 case let err: io::error => 177 return err; 178 }; 179 }; 180 181 @test fn decode() void = { 182 let s = decodestr("cafebabedeadf00d") as []u8; 183 defer free(s); 184 assert(bytes::equal(s, [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xF0, 0x0D])); 185 186 decodestr("this is not hex") as io::error as errors::invalid: void; 187 }; 188 189 // Outputs a dump of hex data alongside the offset and an ASCII representation 190 // (if applicable). 191 // 192 // Example output: 193 // 194 // 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 195 // 00000010 03 00 3e 00 01 00 00 00 80 70 01 00 00 00 00 00 |..>......p......| 196 export fn dump(out: io::handle, data: []u8) (void | io::error) = { 197 let datalen = len(data): u32; 198 199 for (let off = 0u32; off < datalen; off += 16) { 200 fmt::fprintf(out, "{:.8x} ", off)?; 201 202 let toff = 0z; 203 for (let i = 0z; i < 16 && off + i < datalen; i += 1) { 204 let val = data[off + i]; 205 toff += fmt::fprintf(out, "{}{:.2x} ", 206 if (i == 8) " " else "", val)?; 207 }; 208 209 // Align ASCII representation, max width of hex part (48) + 210 // spacing around it 211 for (toff < 50; toff += 1) { 212 fmt::fprint(out, " ")?; 213 }; 214 215 fmt::fprint(out, "|")?; 216 for (let i = 0z; i < 16 && off + i < datalen; i += 1) { 217 let r = data[off + i]: rune; 218 219 fmt::fprint(out, if (ascii::isprint(r)) r else '.')?; 220 }; 221 fmt::fprint(out, "|\n")?; 222 }; 223 }; 224 225 @test fn dump() void = { 226 let in: [_]u8 = [ 227 0x7F, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0xCA, 0xFE, 228 0xBA, 0xBE, 0xDE, 0xAD, 0xF0, 0x0D, 0xCE, 0xFE, 0xBA, 0xBE, 229 0xDE, 0xAD, 0xF0, 0x0D 230 ]; 231 232 let sink = memio::dynamic(); 233 defer io::close(&sink)!; 234 dump(&sink, in) as void; 235 236 let s = memio::string(&sink)!; 237 assert(s == 238 "00000000 7f 45 4c 46 02 01 01 00 ca fe ba be de ad f0 0d |.ELF............|\n" 239 "00000010 ce fe ba be de ad f0 0d |........|\n"); 240 };