hare

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

commit 338719c951d8e8884684f94d3bc519ab017998ae
parent e9b63afe4adbc088b6c103548a50b10fdf20189f
Author: Drew DeVault <sir@cmpwn.com>
Date:   Mon,  2 May 2022 11:40:13 +0200

encoding::pem: initial commit

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Aencoding/pem/+test.ha | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aencoding/pem/pem.ha | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscripts/gen-stdlib | 15+++++++++++++++
Mstdlib.mk | 33+++++++++++++++++++++++++++++++++
4 files changed, 391 insertions(+), 0 deletions(-)

diff --git a/encoding/pem/+test.ha b/encoding/pem/+test.ha @@ -0,0 +1,147 @@ +use bufio; +use bytes; +use io; +use strings; + +@test fn read() void = { + const in = bufio::fixed(strings::toutf8(testcert_str), io::mode::READ); + const dec = newdecoder(&in); + defer finish(&dec); + + const stream = next(&dec)! as (str, pemdecoder); + assert(stream.0 == "CERTIFICATE"); + static let buf: [1024]u8 = [0...]; + assert(len(buf) >= len(testcert_bin)); + + const data = io::drain(&stream.1)!; + assert(bytes::equal(data, testcert_bin)); + + assert(next(&dec) is io::EOF); +}; + +@test fn read_many() void = { + const in = bufio::fixed(strings::toutf8(testmany), io::mode::READ); + const dec = newdecoder(&in); + defer finish(&dec); + + static let buf: [1024]u8 = [0...]; + const stream = next(&dec)! as (str, pemdecoder); + assert(stream.0 == "CERTIFICATE"); + const data = io::drain(&stream.1)!; + assert(bytes::equal(data, testcert_bin)); + + const stream = next(&dec)! as (str, pemdecoder); + assert(stream.0 == "PRIVATE KEY"); + const data = io::drain(&stream.1)!; + assert(bytes::equal(data, testprivkey_bin)); + + assert(next(&dec) is io::EOF); +}; + +const testcert_str: str = ` +garbage +garbage +garbage +-----BEGIN CERTIFICATE----- +MIICLDCCAdKgAwIBAgIBADAKBggqhkjOPQQDAjB9MQswCQYDVQQGEwJCRTEPMA0G +A1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2VydGlmaWNhdGUgYXV0aG9y +aXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdudVRMUyBjZXJ0aWZpY2F0 +ZSBhdXRob3JpdHkwHhcNMTEwNTIzMjAzODIxWhcNMTIxMjIyMDc0MTUxWjB9MQsw +CQYDVQQGEwJCRTEPMA0GA1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2Vy +dGlmaWNhdGUgYXV0aG9yaXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdu +dVRMUyBjZXJ0aWZpY2F0ZSBhdXRob3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMB +BwNCAARS2I0jiuNn14Y2sSALCX3IybqiIJUvxUpj+oNfzngvj/Niyv2394BWnW4X +uQ4RTEiywK87WRcWMGgJB5kX/t2no0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1Ud +DwEB/wQFAwMHBgAwHQYDVR0OBBYEFPC0gf6YEr+1KLlkQAPLzB9mTigDMAoGCCqG +SM49BAMCA0gAMEUCIDGuwD1KPyG+hRf88MeyMQcqOFZD0TbVleF+UsAGQ4enAiEA +l4wOuDwKQa+upc8GftXE2C//4mKANBC6It01gUaTIpo= +-----END CERTIFICATE----- +garbage +`; + +const testcert_bin: [_]u8 = [ + 0x30, 0x82, 0x02, 0x2c, 0x30, 0x82, 0x01, 0xd2, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x01, 0x00, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x02, 0x30, 0x7d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x0f, 0x30, 0x0d, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, 0x47, 0x6e, 0x75, 0x54, 0x4c, 0x53, + 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c, 0x47, + 0x6e, 0x75, 0x54, 0x4c, 0x53, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x13, 0x06, 0x4c, 0x65, 0x75, 0x76, 0x65, 0x6e, 0x31, 0x25, 0x30, 0x23, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x47, 0x6e, 0x75, 0x54, 0x4c, + 0x53, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, + 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x35, 0x32, 0x33, 0x32, 0x30, 0x33, + 0x38, 0x32, 0x31, 0x5a, 0x17, 0x0d, 0x31, 0x32, 0x31, 0x32, 0x32, 0x32, + 0x30, 0x37, 0x34, 0x31, 0x35, 0x31, 0x5a, 0x30, 0x7d, 0x31, 0x0b, 0x30, + 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x0f, + 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, 0x47, 0x6e, 0x75, + 0x54, 0x4c, 0x53, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, + 0x13, 0x1c, 0x47, 0x6e, 0x75, 0x54, 0x4c, 0x53, 0x20, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, + 0x55, 0x04, 0x08, 0x13, 0x06, 0x4c, 0x65, 0x75, 0x76, 0x65, 0x6e, 0x31, + 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x47, 0x6e, + 0x75, 0x54, 0x4c, 0x53, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x74, 0x79, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, + 0x07, 0x03, 0x42, 0x00, 0x04, 0x52, 0xd8, 0x8d, 0x23, 0x8a, 0xe3, 0x67, + 0xd7, 0x86, 0x36, 0xb1, 0x20, 0x0b, 0x09, 0x7d, 0xc8, 0xc9, 0xba, 0xa2, + 0x20, 0x95, 0x2f, 0xc5, 0x4a, 0x63, 0xfa, 0x83, 0x5f, 0xce, 0x78, 0x2f, + 0x8f, 0xf3, 0x62, 0xca, 0xfd, 0xb7, 0xf7, 0x80, 0x56, 0x9d, 0x6e, 0x17, + 0xb9, 0x0e, 0x11, 0x4c, 0x48, 0xb2, 0xc0, 0xaf, 0x3b, 0x59, 0x17, 0x16, + 0x30, 0x68, 0x09, 0x07, 0x99, 0x17, 0xfe, 0xdd, 0xa7, 0xa3, 0x43, 0x30, + 0x41, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, + 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, + 0x0f, 0x01, 0x01, 0xff, 0x04, 0x05, 0x03, 0x03, 0x07, 0x06, 0x00, 0x30, + 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xf0, 0xb4, + 0x81, 0xfe, 0x98, 0x12, 0xbf, 0xb5, 0x28, 0xb9, 0x64, 0x40, 0x03, 0xcb, + 0xcc, 0x1f, 0x66, 0x4e, 0x28, 0x03, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, + 0x20, 0x31, 0xae, 0xc0, 0x3d, 0x4a, 0x3f, 0x21, 0xbe, 0x85, 0x17, 0xfc, + 0xf0, 0xc7, 0xb2, 0x31, 0x07, 0x2a, 0x38, 0x56, 0x43, 0xd1, 0x36, 0xd5, + 0x95, 0xe1, 0x7e, 0x52, 0xc0, 0x06, 0x43, 0x87, 0xa7, 0x02, 0x21, 0x00, + 0x97, 0x8c, 0x0e, 0xb8, 0x3c, 0x0a, 0x41, 0xaf, 0xae, 0xa5, 0xcf, 0x06, + 0x7e, 0xd5, 0xc4, 0xd8, 0x2f, 0xff, 0xe2, 0x62, 0x80, 0x34, 0x10, 0xba, + 0x22, 0xdd, 0x35, 0x81, 0x46, 0x93, 0x22, 0x9a, +]; + +const testmany: str = ` +-----BEGIN CERTIFICATE----- +MIICLDCCAdKgAwIBAgIBADAKBggqhkjOPQQDAjB9MQswCQYDVQQGEwJCRTEPMA0G +A1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2VydGlmaWNhdGUgYXV0aG9y +aXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdudVRMUyBjZXJ0aWZpY2F0 +ZSBhdXRob3JpdHkwHhcNMTEwNTIzMjAzODIxWhcNMTIxMjIyMDc0MTUxWjB9MQsw +CQYDVQQGEwJCRTEPMA0GA1UEChMGR251VExTMSUwIwYDVQQLExxHbnVUTFMgY2Vy +dGlmaWNhdGUgYXV0aG9yaXR5MQ8wDQYDVQQIEwZMZXV2ZW4xJTAjBgNVBAMTHEdu +dVRMUyBjZXJ0aWZpY2F0ZSBhdXRob3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMB +BwNCAARS2I0jiuNn14Y2sSALCX3IybqiIJUvxUpj+oNfzngvj/Niyv2394BWnW4X +uQ4RTEiywK87WRcWMGgJB5kX/t2no0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1Ud +DwEB/wQFAwMHBgAwHQYDVR0OBBYEFPC0gf6YEr+1KLlkQAPLzB9mTigDMAoGCCqG +SM49BAMCA0gAMEUCIDGuwD1KPyG+hRf88MeyMQcqOFZD0TbVleF+UsAGQ4enAiEA +l4wOuDwKQa+upc8GftXE2C//4mKANBC6It01gUaTIpo= +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgVcB/UNPxalR9zDYAjQIf +jojUDiQuGnSJrFEEzZPT/92hRANCAASc7UJtgnF/abqWM60T3XNJEzBv5ez9TdwK +H0M6xpM2q+53wmsN/eYLdgtjgBd3DBmHtPilCkiFICXyaA8z9LkJ +-----END PRIVATE KEY----- +`; + +const testprivkey_bin: [_]u8 = [ + 0x30, 0x81, 0x84, 0x02, 0x01, 0x00, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a, + 0x04, 0x6d, 0x30, 0x6b, 0x02, 0x01, 0x01, 0x04, 0x20, 0x55, 0xc0, 0x7f, + 0x50, 0xd3, 0xf1, 0x6a, 0x54, 0x7d, 0xcc, 0x36, 0x00, 0x8d, 0x02, 0x1f, + 0x8e, 0x88, 0xd4, 0x0e, 0x24, 0x2e, 0x1a, 0x74, 0x89, 0xac, 0x51, 0x04, + 0xcd, 0x93, 0xd3, 0xff, 0xdd, 0xa1, 0x44, 0x03, 0x42, 0x00, 0x04, 0x9c, + 0xed, 0x42, 0x6d, 0x82, 0x71, 0x7f, 0x69, 0xba, 0x96, 0x33, 0xad, 0x13, + 0xdd, 0x73, 0x49, 0x13, 0x30, 0x6f, 0xe5, 0xec, 0xfd, 0x4d, 0xdc, 0x0a, + 0x1f, 0x43, 0x3a, 0xc6, 0x93, 0x36, 0xab, 0xee, 0x77, 0xc2, 0x6b, 0x0d, + 0xfd, 0xe6, 0x0b, 0x76, 0x0b, 0x63, 0x80, 0x17, 0x77, 0x0c, 0x19, 0x87, + 0xb4, 0xf8, 0xa5, 0x0a, 0x48, 0x85, 0x20, 0x25, 0xf2, 0x68, 0x0f, 0x33, + 0xf4, 0xb9, 0x09, +]; diff --git a/encoding/pem/pem.ha b/encoding/pem/pem.ha @@ -0,0 +1,196 @@ +use ascii; +use bufio; +use encoding::base64; +use errors; +use io; +use os; +use strings; +use strio; + +const begin: str = "-----BEGIN "; +const end: str = "-----END "; +const suffix: str = "-----"; + +export type decoder = struct { + in: bufio::bufstream, + label: strio::stream, + buf: []u8, +}; + +export type b64stream = struct { + stream: io::stream, + in: *bufio::bufstream, + readend: bool, +}; + +export type pemdecoder = struct { + stream: io::stream, + in: *bufio::bufstream, + b64_in: b64stream, + b64: base64::decoder, + // XXX: kind of dumb but it saves us some memory management problems + b64_ready: bool, +}; + +const pemdecoder_vt: io::vtable = io::vtable { + reader = &pem_read, + ... +}; + +const b64stream_r_vt: io::vtable = io::vtable { + reader = &b64_read, + ... +}; + +// Creates a new PEM decoder. The caller must either read it until it returns +// [[io::EOF]], or call [[finish]] to free state associated with the parser. +export fn newdecoder(in: io::handle) decoder = { + let buf: []u8 = alloc([0...], os::BUFSIZ); + return decoder { + in = bufio::buffered(in, buf, []), + buf = buf, + label = strio::dynamic(), + }; +}; + +// Frees state associated with this [[decoder]]. +export fn finish(dec: *decoder) void = { + io::close(&dec.label)!; + free(dec.buf); +}; + +// Converts an I/O error returned from a PEM decoder into a human-friendly +// string. +export fn strerror(err: io::error) const str = { + match (err) { + case errors::invalid => + return "Invalid PEM data"; + case => + return io::strerror(err); + }; +}; + +// Finds the next PEM boundary in the stream, ignoring any non-PEM data, and +// returns the label and a [[pemdecoder]] from which the encoded data may be +// read, or [[io::EOF]] if no further PEM boundaries are found. The user must +// completely read the pemdecoder until it returns [[io::EOF]] before calling +// [[next]] again. +// +// The label returned by this function is borrowed from the decoder state and +// does not contain "-----BEGIN " or "-----". +export fn next(dec: *decoder) ((str, pemdecoder) | io::EOF | io::error) = { + for (true) { + // XXX: This can be improved following + // https://todo.sr.ht/~sircmpwn/hare/562 + const line = match (bufio::scanline(&dec.in)?) { + case io::EOF => + return io::EOF; + case let line: []u8 => + yield match (strings::try_fromutf8(line)) { + case let s: str => + yield s; + case => + return errors::invalid; + }; + }; + defer free(line); + + if (!strings::hasprefix(line, begin) + || !strings::hassuffix(line, suffix)) { + continue; + }; + + strio::reset(&dec.label); + strio::concat(&dec.label, strings::sub(line, + len(begin), len(line) - len(suffix)))!; + + return (strio::string(&dec.label), pemdecoder { + stream = &pemdecoder_vt, + in = &dec.in, + b64_ready = false, + ... + }); + }; + + abort(); // Unreachable +}; + +fn pem_read(st: *io::stream, buf: []u8) (size | io::EOF | io::error) = { + // We need to set up two streams. This is the stream which is actually + // returned to the caller, which calls the base64 decoder against a + // special stream (b64stream) which trims out whitespace and EOF's on + // -----END. + const st = st: *pemdecoder; + assert(st.stream.reader == &pem_read); + if (!st.b64_ready) { + st.b64_in = b64stream { + stream = &b64stream_r_vt, + in = st.in, + readend = false, + }; + st.b64 = base64::newdecoder(&base64::std_encoding, &st.b64_in); + st.b64_ready = true; + }; + + match (io::read(&st.b64, buf)?) { + case let z: size => + return z; + case io::EOF => + yield; + }; + + const line = match (bufio::scanline(st.in)?) { + case io::EOF => + return io::EOF; + case let line: []u8 => + yield match (strings::try_fromutf8(line)) { + case let s: str => + yield s; + case => + return errors::invalid; + }; + }; + defer free(line); + + if (!strings::hasprefix(line, end) + || !strings::hassuffix(line, suffix)) { + return errors::invalid; + }; + + // XXX: We could verify the trailer matches but the RFC says it's + // optional. + return io::EOF; +}; + +fn b64_read(st: *io::stream, buf: []u8) (size | io::EOF | io::error) = { + const st = st: *b64stream; + assert(st.stream.reader == &b64_read); + + const z = match (io::read(st.in, buf)?) { + case let z: size => + yield z; + case io::EOF => + return errors::invalid; // Missing -----END + }; + + // Trim off whitespace and look for -----END + let sub = buf[..z]; + for (let i = 0z; i < len(sub); i += 1) { + if (sub[i] == '-') { + bufio::unread(st.in, sub[i..]); + sub = sub[..i]; + break; + }; + if (ascii::isspace(sub[i]: u32: rune)) { + static delete(sub[i]); + i -= 1; + continue; + }; + }; + + if (len(sub) == 0) { + return io::EOF; + }; + + return len(sub); +}; diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib @@ -495,6 +495,20 @@ encoding_hex() { gen_ssa encoding::hex ascii bytes fmt io strconv strio strings } +encoding_pem() { + if [ $testing -eq 0 ] + then + gen_srcs encoding::pem \ + pem.ha + else + gen_srcs encoding::pem \ + pem.ha \ + +test.ha + fi + gen_ssa encoding::pem strings bufio strio io errors bytes \ + encoding::base64 ascii os +} + encoding_utf8() { gen_srcs encoding::utf8 \ decode.ha \ @@ -1334,6 +1348,7 @@ dirs encoding::base64 encoding::base32 encoding::hex +encoding::pem encoding::utf8 endian errors diff --git a/stdlib.mk b/stdlib.mk @@ -302,6 +302,12 @@ stdlib_deps_any += $(stdlib_encoding_hex_any) stdlib_encoding_hex_linux = $(stdlib_encoding_hex_any) stdlib_encoding_hex_freebsd = $(stdlib_encoding_hex_any) +# gen_lib encoding::pem (any) +stdlib_encoding_pem_any = $(HARECACHE)/encoding/pem/encoding_pem-any.o +stdlib_deps_any += $(stdlib_encoding_pem_any) +stdlib_encoding_pem_linux = $(stdlib_encoding_pem_any) +stdlib_encoding_pem_freebsd = $(stdlib_encoding_pem_any) + # gen_lib encoding::utf8 (any) stdlib_encoding_utf8_any = $(HARECACHE)/encoding/utf8/encoding_utf8-any.o stdlib_deps_any += $(stdlib_encoding_utf8_any) @@ -1029,6 +1035,16 @@ $(HARECACHE)/encoding/hex/encoding_hex-any.ssa: $(stdlib_encoding_hex_any_srcs) @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nencoding::hex \ -t$(HARECACHE)/encoding/hex/encoding_hex.td $(stdlib_encoding_hex_any_srcs) +# encoding::pem (+any) +stdlib_encoding_pem_any_srcs = \ + $(STDLIB)/encoding/pem/pem.ha + +$(HARECACHE)/encoding/pem/encoding_pem-any.ssa: $(stdlib_encoding_pem_any_srcs) $(stdlib_rt) $(stdlib_strings_$(PLATFORM)) $(stdlib_bufio_$(PLATFORM)) $(stdlib_strio_$(PLATFORM)) $(stdlib_io_$(PLATFORM)) $(stdlib_errors_$(PLATFORM)) $(stdlib_bytes_$(PLATFORM)) $(stdlib_encoding_base64_$(PLATFORM)) $(stdlib_ascii_$(PLATFORM)) $(stdlib_os_$(PLATFORM)) + @printf 'HAREC \t$@\n' + @mkdir -p $(HARECACHE)/encoding/pem + @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nencoding::pem \ + -t$(HARECACHE)/encoding/pem/encoding_pem.td $(stdlib_encoding_pem_any_srcs) + # encoding::utf8 (+any) stdlib_encoding_utf8_any_srcs = \ $(STDLIB)/encoding/utf8/decode.ha \ @@ -2332,6 +2348,12 @@ testlib_deps_any += $(testlib_encoding_hex_any) testlib_encoding_hex_linux = $(testlib_encoding_hex_any) testlib_encoding_hex_freebsd = $(testlib_encoding_hex_any) +# gen_lib encoding::pem (any) +testlib_encoding_pem_any = $(TESTCACHE)/encoding/pem/encoding_pem-any.o +testlib_deps_any += $(testlib_encoding_pem_any) +testlib_encoding_pem_linux = $(testlib_encoding_pem_any) +testlib_encoding_pem_freebsd = $(testlib_encoding_pem_any) + # gen_lib encoding::utf8 (any) testlib_encoding_utf8_any = $(TESTCACHE)/encoding/utf8/encoding_utf8-any.o testlib_deps_any += $(testlib_encoding_utf8_any) @@ -3078,6 +3100,17 @@ $(TESTCACHE)/encoding/hex/encoding_hex-any.ssa: $(testlib_encoding_hex_any_srcs) @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nencoding::hex \ -t$(TESTCACHE)/encoding/hex/encoding_hex.td $(testlib_encoding_hex_any_srcs) +# encoding::pem (+any) +testlib_encoding_pem_any_srcs = \ + $(STDLIB)/encoding/pem/pem.ha \ + $(STDLIB)/encoding/pem/+test.ha + +$(TESTCACHE)/encoding/pem/encoding_pem-any.ssa: $(testlib_encoding_pem_any_srcs) $(testlib_rt) $(testlib_strings_$(PLATFORM)) $(testlib_bufio_$(PLATFORM)) $(testlib_strio_$(PLATFORM)) $(testlib_io_$(PLATFORM)) $(testlib_errors_$(PLATFORM)) $(testlib_bytes_$(PLATFORM)) $(testlib_encoding_base64_$(PLATFORM)) $(testlib_ascii_$(PLATFORM)) $(testlib_os_$(PLATFORM)) + @printf 'HAREC \t$@\n' + @mkdir -p $(TESTCACHE)/encoding/pem + @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nencoding::pem \ + -t$(TESTCACHE)/encoding/pem/encoding_pem.td $(testlib_encoding_pem_any_srcs) + # encoding::utf8 (+any) testlib_encoding_utf8_any_srcs = \ $(STDLIB)/encoding/utf8/decode.ha \