Commit Diff


commit - ba66a49b73addf69451f8497dedffdd670d6b06b
commit + 417f792f5259aa51eb411679c35b4ad182138b59
blob - 83fd94c11f6beaf00dc41c3d95c2886c3d8df0d3
blob + 4a1b7780aef447b311cdd9df18413185c21fc2f2
--- Makefile
+++ Makefile
@@ -1,11 +1,11 @@
 TESTS = test_nfa test_regex test_pcre test_hammer
+APPS = simcard
 OBJS = nfa.o
 
 .PHONY: all clean benchmark
-all: ${OBJS} ${TESTS}
+all: ${OBJS} ${TESTS} ${APPS}
 clean:
-	rm -f ${OBJS}
-	rm -f ${TESTS}
+	rm -f ${OBJS} ${TESTS} ${APPS}
 benchmark: ${TESTS}
 	./test_nfa -b
 	./test_regex -b
@@ -22,3 +22,6 @@ test_pcre: test_pcre.c
 test_hammer: test_hammer.c
 	$(CC) $(CFLAGS) -I/usr/local/include $(LDFLAGS) -o $@ $< \
 	    -lhammer
+
+simcard: $(OBJS) simcard.c
+	$(CC) $(CFLAGS) -o $@ $(OBJS) $<
blob - /dev/null
blob + 1f779781ebc3e50890a5b96ebddc4adedcfa5684 (mode 644)
--- /dev/null
+++ select_mf.hex
@@ -0,0 +1 @@
+00 A4 00 0C
blob - /dev/null
blob + 98c532c584d5181ca916047dcb186b565aaa6450 (mode 644)
--- /dev/null
+++ simcard.c
@@ -0,0 +1,289 @@
+/* simulate a SIM card (application layer)
+ * pesco 2022
+ *
+ * ref: ETSI TS 102 221
+ * aux: ISO 7816-4
+ */
+
+#include <stdio.h>
+#include <stdint.h>		/* uint8_t */
+#include <stdbool.h>
+#include <assert.h>
+#include "nfa.h"
+
+enum gedanken {
+	SW1 = INNER, SW2	/* which status byte(s) to generate? */
+};
+
+#define ACTION	(OUTPUT + UCHAR_MAX + 1)
+enum action {
+	STDCHAN = ACTION,	/* class byte using standard channels (0-3) */
+	EXTCHAN,		/* class byte using extended channels (4-19) */
+	LENBYTE,		/* (any) length byte received */
+	LEN256,			/* length byte 0, meaning 256, received */
+	LCMD,			/* command length received */
+	LEXP,			/* expected response length received */
+
+	SELECT_MF,		/* select the MF */
+	SELECT_BYFID,		/* select DF/EF/MF by file ID */
+	SELECT_CHILDDF,		/* select child DF of current DF */
+	SELECT_PARENT,		/* select parent DF of current DF */
+	SELECT_BYNAME,		/* select by DF name (i.e. AID) */
+	SELECT_ABSPATH,		/* select by path from MF */
+	SELECT_RELPATH,		/* select by path from current DF */
+
+	TODO			/* unimplemented feature, abort match */
+};
+
+/* status words; SW1 in high byte, SW2 in low byte. */
+enum status {
+	SW_NONE		= 0,		/* command not (yet) completed */
+	SW_OK		= 0x9000,	/* normal ending of command */
+	SW_ERROR	= 0x6400	/* execution error, mem unchanged */
+};
+
+struct mind {
+	const uint8_t *input;
+	int channel;
+	int lc, le;
+	int len;		/* last seen length byte */
+	uint16_t status;	/* result to output for current command */
+	uint8_t output;		/* character choice to generate */
+};
+
+/* global NFAs created by init() */
+NFA n_todo;
+NFA n_len;
+NFA n_cla_intr, n_cla_prop;
+NFA n_lc, n_le;
+NFA n_sw;
+NFA n_fcp;
+NFA n_select, n_sel_p2non, n_sel_p2fcp;
+NFA n_app;
+
+NFA
+octet(uint8_t x)
+{
+	/* NB: if we have uint8_t, CHAR_BIT must be 8 */
+	return chr(x);
+}
+
+NFA
+header(bool prop, uint8_t ins)
+{
+	return seq(prop ? n_cla_prop : n_cla_intr, octet(ins));
+}
+
+NFA
+status(uint16_t sw)
+{
+	uint8_t sw1 = sw >> 8;
+	uint8_t sw2 = sw & 0xFF;
+
+	return seq(think(SW1), outchr(sw1), think(SW2), outchr(sw2));
+}
+
+/* helper: general pattern of the SELECT command */
+NFA
+n_sel(uint8_t p1, NFA cdata, enum action a)
+{
+	NFA act = output(a);
+
+	/* if command data is not empty, attach the Lc byte */
+	if (cdata.size > 0)
+		cdata = seq(n_lc, cdata);
+
+	NFA cont_non = seq(n_sel_p2non, cdata, act, n_sw);
+	NFA cont_fcp = seq(n_sel_p2fcp, cdata, act, n_le, n_fcp, n_sw);
+
+	return seq(octet(p1), choice(cont_non, cont_fcp));
+}
+
+/* helper: special case (for SELECT_MF): no cdata, no rdata allowed */
+NFA
+n_sel_nn(uint8_t p1, enum action a)
+{
+	return seq(octet(p1), n_sel_p2non, output(a), n_sw);
+}
+
+void
+init_select(void)
+{
+	/* response data (FCP template) */
+	n_fcp = n_todo;
+
+	/* command data (file ID, path, or DF name) */
+	NFA fileid	= n_todo;
+	NFA path	= n_todo;
+	NFA name	= n_todo;
+	NFA empty	= epsilon();
+
+	/* possible values for P2 */
+	n_sel_p2fcp	= octet(0x04);	/* return FCP template */
+	n_sel_p2non	= octet(0x0C);	/* no data returned */
+
+	/* command forms (switching on P1) */
+	NFA sel_mf	= n_sel_nn(0x00,      SELECT_MF);
+	NFA sel_byfid	= n_sel(0x00, fileid, SELECT_BYFID);
+	NFA sel_childdf	= n_sel(0x01, fileid, SELECT_CHILDDF);
+	NFA sel_parent	= n_sel(0x03, empty,  SELECT_PARENT);
+	NFA sel_byname	= n_sel(0x04, name,   SELECT_BYNAME);
+	NFA sel_abspath = n_sel(0x08, path,   SELECT_ABSPATH);
+	NFA sel_relpath = n_sel(0x09, path,   SELECT_RELPATH);
+
+	n_select = choice(sel_mf, sel_parent, sel_childdf,
+	    sel_byfid, sel_byname, sel_abspath, sel_relpath);
+}
+
+void
+init_cla(void)
+{
+	NFA cla_intr_stdchan = seq(range(0x00, 0x03), output(STDCHAN));
+	NFA cla_intr_extchan = seq(range(0x40, 0x4F), output(EXTCHAN));
+	NFA cla_prop_stdchan = seq(range(0x80, 0x83), output(STDCHAN));
+	NFA cla_prop_extchan = seq(range(0xC0, 0xCF), output(EXTCHAN));
+
+	n_cla_intr = choice(cla_intr_stdchan, cla_intr_extchan);
+	n_cla_prop = choice(cla_prop_stdchan, cla_prop_extchan);
+}
+
+void
+init(void)
+{
+	n_todo	= output(TODO);
+	n_len	= choice(seq(octet(0), output(LEN256)),
+		         seq(range(1, 255), output(LENBYTE)));
+
+	n_lc = seq(n_len, output(LCMD));
+	n_le = seq(n_len, output(LEXP));
+
+	n_sw = choice(status(SW_OK), status(SW_ERROR));
+
+	init_cla();
+	init_select();
+
+	n_app = seq(header(0, 0xA4), n_select);
+}
+
+int
+sub(int x, size_t state, int pos, void *env_)
+{
+	struct mind *env = env_;
+
+	switch(x) {
+	case SW1:
+		env->output = env->status >> 8;
+		return 1;
+	case SW2:
+		env->output = env->status & 0xFF;
+		return 1;
+	default:
+		fprintf(stderr, "pos %d: unhandled thought %d [state %zu]\n",
+		    pos, x, state);
+		return 0;
+	}
+}
+
+int
+out(struct range r, size_t state, int pos, void *env_)
+{
+	struct mind *env = env_;
+	const uint8_t *p = env->input + pos - 1;
+	int x = 0;
+
+	/* handle proper output characters */
+	if (r.base < ACTION) {
+		/*
+		 * do we have a specific output character in mind?
+		 * if so, insist on it.
+		 * otherwise, pass simple characters.
+		 */
+		if (env->output != 0) {
+			x = OUTPUT + env->output;
+			if (x >= r.base && x < r.base + r.n)
+				env->output = 0;	/* done with it */
+		} else if (r.n == 1)
+			x = r.base;
+
+		if (x != 0) {
+			assert(x >= OUTPUT);
+			assert(x <= OUTPUT + UCHAR_MAX);
+			putchar(x - OUTPUT);
+			return x;
+		}
+
+		/* reject unrecognized ranges. */
+		return -1;
+	}
+
+	/* handle semantic actions */
+	switch (r.base) {
+	case STDCHAN:
+		env->channel = *p & 0x03;
+		break;
+	case EXTCHAN:
+		env->channel = 4 + (*p & 0x0F);
+		break;
+	case LENBYTE:
+		env->len = *p;
+		break;
+	case LEN256:
+		env->len = 256;
+		break;
+	case LCMD:
+		env->lc = env->len;
+		break;
+	case LEXP:
+		env->le = env->len;
+		break;
+
+	case SELECT_MF:
+		fprintf(stderr, "action: SELECT_MF\n");
+		env->status = SW_OK;
+		break;
+
+	case TODO:
+		fprintf(stderr, "pos %d: TODO [state %zu]\n", pos, state);
+		return -1;
+	default:
+		fprintf(stderr, "pos %d: unhandled action %d [state %zu]\n",
+		    pos, r.base - ACTION, state);
+		return -1;
+	}
+	assert(r.n == 1);
+	return r.base;
+}
+
+int
+main(int argc, char *argv[])
+{
+	char buf[1024];
+	struct mind env;
+	struct prep *pr;
+	size_t n;
+	int m;
+
+	init();
+
+	//nfaprint(n_app);	// XXX debug
+
+	n = fread(buf, 1, sizeof buf, stdin);
+	if (ferror(stdin)) {
+		perror("stdin");
+		return 2;
+	}
+	assert(feof(stdin));	// XXX
+
+	env.input = buf;
+	pr = nfaprep(n_app, sub, out, &env);
+
+	m = nfarun(pr, buf, n);
+	if (m == -1) {
+		fprintf(stderr, "Protocol error.\n");
+		return 1;
+	}
+	if (m < n)
+		fprintf(stderr, "Trailing garbage (after %d bytes).\n", m);
+
+	return 0;
+}