hare

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

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