diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e3a7eb..4129971 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,24 @@ cmake_minimum_required(VERSION 3.16) -project(fun VERSION 0.16.6 LANGUAGES C) +project(fun VERSION 0.17.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) include(GNUInstallDirs) +# Optional PCSC support (enabled via -DFUN_WITH_PCSC=ON from Makefile) +option(FUN_WITH_PCSC "Enable PCSC (pcsclite) support" OFF) +if(FUN_WITH_PCSC) + message(STATUS "Building with PCSC support") + add_definitions(-DFUN_WITH_PCSC) + if(APPLE) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework PCSC") + else() + include_directories(/usr/include/PCSC) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lpcsclite") + endif() +endif() + # Core VM/library sources add_library(fun_core src/bytecode.c diff --git a/Makefile b/Makefile index 4defac2..bcf34ec 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,28 @@ CMAKE ?= cmake # Default to a build with extra logging disabled CMAKE_FLAGS ?= -DFUN_DEBUG=OFF +# Enable PCSC with: make PCSC=ON +ifeq ($(PCSC),ON) + CMAKE_FLAGS += -DFUN_WITH_PCSC=ON + CMAKE_C_FLAGS += -DFUN_WITH_PCSC + CMAKE_CXX_FLAGS += -DFUN_WITH_PCSC + ifeq ($(OS),Windows_NT) + # not supported here + else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Darwin) + CMAKE_FLAGS += -DCMAKE_EXE_LINKER_FLAGS="$(CMAKE_EXE_LINKER_FLAGS) -framework PCSC" + else + CMAKE_C_FLAGS += -I/usr/include/PCSC + CMAKE_CXX_FLAGS += -I/usr/include/PCSC + CMAKE_FLAGS += -DCMAKE_EXE_LINKER_FLAGS="$(CMAKE_EXE_LINKER_FLAGS) -lpcsclite" + endif + endif + # Apply accumulated flags to CMake call + CMAKE_FLAGS += -DCMAKE_C_FLAGS="$(CMAKE_C_FLAGS)" + CMAKE_FLAGS += -DCMAKE_CXX_FLAGS="$(CMAKE_CXX_FLAGS)" +endif + # Convenience: default path to bundled stdlib FUN_LIB ?= $(CURDIR)/lib diff --git a/examples/pcsc_demo.fun b/examples/pcsc_demo.fun new file mode 100755 index 0000000..dacd9e9 --- /dev/null +++ b/examples/pcsc_demo.fun @@ -0,0 +1,44 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-02 + */ + +// PCSC demo using stdlib PCSC class. +// Establishes context, lists readers, optionally connects to the first, +// transmits a sample APDU (SELECT MF), and cleans up. + +#include + +pc = PCSC() + +ctx = pc.establish() +print("ctx=" + to_string(ctx)) + +readers = pc.list_readers(ctx) +print("readers=" + to_string(readers)) + +number h = 0 +if len(readers) > 0 + // Connect to first reader + h = pc.connect(ctx, readers[1]) + print("handle=" + to_string(h)) + + if h != 0 + // Sample APDU: SELECT MF (00 A4 00 00 02 3F 00) + resp = pc.transmit_hex(h, "00A40000023F00") + // Print hex data and SW + print("resp.data_hex=" + resp["data_hex"]) + print("resp.sw1=" + to_string(resp["sw1"]) + " sw2=" + to_string(resp["sw2"]) + " code=" + to_string(resp["code"])) + + _ = pc.disconnect(h) + +_ = pc.release(ctx) +print("done") diff --git a/examples/pcsc_example.fun b/examples/pcsc_example.fun new file mode 100755 index 0000000..4cf7384 --- /dev/null +++ b/examples/pcsc_example.fun @@ -0,0 +1,14 @@ +#!/usr/bin/env fun + +// Minimal PCSC example for Fun language +// Establish context and list readers (prints [] if none or unsupported) + +class Pcsc() + fun list_readers(this) + ctx = pcsc_establish() + readers = pcsc_list_readers(ctx) + print(readers) + _ = pcsc_release(ctx) + +p = Pcsc() +p.list_readers() diff --git a/examples/stdlib_showcase.fun b/examples/stdlib_showcase.fun old mode 100644 new mode 100755 diff --git a/lib/hex.fun b/lib/hex.fun new file mode 100644 index 0000000..fb9ab05 --- /dev/null +++ b/lib/hex.fun @@ -0,0 +1,79 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-02 + */ + +// Hex utility functions + +fun hex_val(ch) + if (ch == "0") + return 0 + else if (ch == "1") + return 1 + else if (ch == "2") + return 2 + else if (ch == "3") + return 3 + else if (ch == "4") + return 4 + else if (ch == "5") + return 5 + else if (ch == "6") + return 6 + else if (ch == "7") + return 7 + else if (ch == "8") + return 8 + else if (ch == "9") + return 9 + else if (ch == "a" || ch == "A") + return 10 + else if (ch == "b" || ch == "B") + return 11 + else if (ch == "c" || ch == "C") + return 12 + else if (ch == "d" || ch == "D") + return 13 + else if (ch == "e" || ch == "E") + return 14 + else if (ch == "f" || ch == "F") + return 15 + else + return 0 + +fun hex_to_bytes(hex) + s = to_string(hex) + out = [] + number i = 0 + number n = len(s) + while i + 1 < n + number b = hex_val(substr(s, i, 1)) * 16 + hex_val(substr(s, i + 1, 1)) + push(out, b) + i = i + 2 + return out + +fun two_hex(n) + n = n % 256 + hexd = "0123456789abcdef" + number hi = (n / 16) % 16 + number lo = n % 16 + c1 = substr(hexd, hi, 1) + c2 = substr(hexd, lo, 1) + return c1 + c2 + +fun bytes_to_hex(arr) + res = "" + number i = 0 + number N = len(arr) + while i < N + res = res + two_hex(arr[i]) + i = i + 1 + return res diff --git a/lib/io/pcsc.fun b/lib/io/pcsc.fun new file mode 100644 index 0000000..6ce4667 --- /dev/null +++ b/lib/io/pcsc.fun @@ -0,0 +1,80 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-02 + */ + +// __ns_alias__: PCSC + +// PCSC stdlib abstraction class wrapping VM opcodes. +// Provides snake_case methods and hex helpers. +// All methods are defensive and work even if PCSC is unsupported (returning []/0 or a default map). + +#include + +class PCSC() + // ---- hex helpers moved to lib/hex.fun ---- + + // ---- PCSC wrappers ---- + fun establish(this) + // returns ctx id (>0) or 0 + return pcsc_establish() + + fun release(this, ctx) + // returns 1 on success or 0 + return pcsc_release(ctx) + + fun list_readers(this, ctx) + readers = pcsc_list_readers(ctx) + if readers == nil + return [] + return readers + + fun connect(this, ctx, reader_name) + // returns handle id (>0) or 0 + return pcsc_connect(ctx, to_string(reader_name)) + + fun disconnect(this, handle) + // returns 1 on success or 0 + return pcsc_disconnect(handle) + + // Transmit raw APDU bytes (array of numbers 0..255). Returns map {"data":[], "sw1":n, "sw2":n, "code":n} + fun transmit_bytes(this, handle, bytes) + res = pcsc_transmit(handle, bytes) + if res == nil + m = {} + m["data"] = [] + m["sw1"] = -1 + m["sw2"] = -1 + m["code"] = -2 + return m + if res["data"] == nil + res["data"] = [] + return res + + // Transmit hex APDU string, return map with hex "data_hex" and numeric sw1/sw2/code for convenience + fun transmit_hex(this, handle, hex) + arr = hex_to_bytes(hex) + res = pcsc_transmit(handle, arr) + if res == nil + res = {} + res["data"] = [] + res["sw1"] = -1 + res["sw2"] = -1 + res["code"] = -2 + if res["data"] == nil + res["data"] = [] + dh = bytes_to_hex(res["data"]) + m = {} + m["data_hex"] = dh + m["sw1"] = res["sw1"] + m["sw2"] = res["sw2"] + m["code"] = res["code"] + return m diff --git a/src/bytecode.h b/src/bytecode.h index 5f7b2f7..6dd3a44 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -131,7 +131,15 @@ typedef enum { OP_SHL, // pops s, a; pushes (uint32_t)(a << (s&31)) OP_SHR, // pops s, a; pushes (uint32_t)(a >> (s&31)) logical OP_ROTL, // pops s, a; pushes rotl32(a, s) - OP_ROTR // pops s, a; pushes rotr32(a, s) + OP_ROTR, // pops s, a; pushes rotr32(a, s) + + // PCSC (smart card) opcodes + OP_PCSC_ESTABLISH, // returns context id (>0) or 0 + OP_PCSC_RELEASE, // pops ctx id; returns 1/0 + OP_PCSC_LIST_READERS, // pops ctx id; returns array of reader names (possibly empty) + OP_PCSC_CONNECT, // pops reader, ctx id; returns handle id (>0) or 0 + OP_PCSC_DISCONNECT, // pops handle id; returns 1/0 + OP_PCSC_TRANSMIT // pops apdu array, handle id; returns map {"data":[],"sw1":n,"sw2":n,"code":n} } OpCode; typedef struct { diff --git a/src/parser.c b/src/parser.c index 530faad..52dc3c6 100644 --- a/src/parser.c +++ b/src/parser.c @@ -678,6 +678,61 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + /* PCSC builtins */ + if (strcmp(name, "pcsc_establish") == 0) { + (*pos)++; /* '(' */ + /* no args */ + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "pcsc_establish expects ()"); free(name); return 0; } + bytecode_add_instruction(bc, OP_PCSC_ESTABLISH, 0); + free(name); + return 1; + } + if (strcmp(name, "pcsc_release") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_release expects 1 argument (ctx)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcsc_release arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_PCSC_RELEASE, 0); + free(name); + return 1; + } + if (strcmp(name, "pcsc_list_readers") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_list_readers expects 1 argument (ctx)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcsc_list_readers arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_PCSC_LIST_READERS, 0); + free(name); + return 1; + } + if (strcmp(name, "pcsc_connect") == 0) { + (*pos)++; /* '(' */ + /* ctx, reader */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_connect expects (ctx, reader)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcsc_connect expects (ctx, reader)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_connect expects (ctx, reader)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcsc_connect args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_PCSC_CONNECT, 0); + free(name); + return 1; + } + if (strcmp(name, "pcsc_disconnect") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_disconnect expects 1 argument (handle)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcsc_disconnect arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_PCSC_DISCONNECT, 0); + free(name); + return 1; + } + if (strcmp(name, "pcsc_transmit") == 0) { + (*pos)++; /* '(' */ + /* handle, bytes */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_transmit expects (handle, bytes)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "pcsc_transmit expects (handle, bytes)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "pcsc_transmit expects (handle, bytes)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after pcsc_transmit args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_PCSC_TRANSMIT, 0); + free(name); + return 1; + } /* string ops */ if (strcmp(name, "split") == 0) { (*pos)++; /* '(' */ diff --git a/src/pcsc.c b/src/pcsc.c new file mode 100644 index 0000000..1affa22 --- /dev/null +++ b/src/pcsc.c @@ -0,0 +1,74 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-02 + */ + + /* PCSC helpers: registries and helper functions. + * Included at file scope from vm.c. + */ +#ifdef FUN_WITH_PCSC + #if defined(__has_include) + #if __has_include() + #include + #include + #elif __has_include() + #include + #else + #error "FUN_WITH_PCSC is enabled but PCSC headers were not found" + #endif + #else + #include + #include + #endif + #include + + typedef struct { + SCARDCONTEXT ctx; + int in_use; + } pcsc_ctx_entry; + + typedef struct { + SCARDHANDLE h; + DWORD proto; + int in_use; + } pcsc_card_entry; + + static pcsc_ctx_entry g_pcsc_ctx[8]; + static pcsc_card_entry g_pcsc_card[32]; + + static int pcsc_alloc_ctx_slot(void) { + for (int i = 0; i < (int)(sizeof(g_pcsc_ctx)/sizeof(g_pcsc_ctx[0])); ++i) { + if (!g_pcsc_ctx[i].in_use) { g_pcsc_ctx[i].in_use = 1; g_pcsc_ctx[i].ctx = 0; return i + 1; } + } + return 0; + } + + static int pcsc_alloc_card_slot(void) { + for (int i = 0; i < (int)(sizeof(g_pcsc_card)/sizeof(g_pcsc_card[0])); ++i) { + if (!g_pcsc_card[i].in_use) { g_pcsc_card[i].in_use = 1; g_pcsc_card[i].h = 0; g_pcsc_card[i].proto = 0; return i + 1; } + } + return 0; + } + + static pcsc_ctx_entry* pcsc_get_ctx(int id) { + if (id <= 0) return NULL; + int idx = id - 1; + if (idx < 0 || idx >= (int)(sizeof(g_pcsc_ctx)/sizeof(g_pcsc_ctx[0]))) return NULL; + if (!g_pcsc_ctx[idx].in_use) return NULL; + return &g_pcsc_ctx[idx]; + } + + static pcsc_card_entry* pcsc_get_card(int id) { + if (id <= 0) return NULL; + int idx = id - 1; + if (idx < 0 || idx >= (int)(sizeof(g_pcsc_card)/sizeof(g_pcsc_card[0]))) return NULL; + if (!g_pcsc_card[idx].in_use) return NULL; + return &g_pcsc_card[idx]; + } +#endif /* FUN_WITH_PCSC */ diff --git a/src/vm.c b/src/vm.c index 8cd6589..2ef2537 100644 --- a/src/vm.c +++ b/src/vm.c @@ -11,6 +11,7 @@ #include "iter.c" #include "map.c" #include "string.c" +#include "pcsc.c" #include "vm.h" #include "value.h" #include @@ -321,6 +322,12 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/os/thread_spawn.c" #include "vm/os/proc_run.c" #include "vm/os/proc_system.c" + #include "vm/pcsc/establish.c" + #include "vm/pcsc/release.c" + #include "vm/pcsc/list_readers.c" + #include "vm/pcsc/connect.c" + #include "vm/pcsc/disconnect.c" + #include "vm/pcsc/transmit.c" #include "vm/strings/find.c" #include "vm/strings/split.c" diff --git a/src/vm.h b/src/vm.h index 4daaf77..2dfc3f7 100644 --- a/src/vm.h +++ b/src/vm.h @@ -35,7 +35,8 @@ static const char *opcode_names[] = { "MAKE_MAP","KEYS","VALUES","HAS_KEY", "READ_FILE","WRITE_FILE","ENV","INPUT_LINE","PROC_RUN","PROC_SYSTEM", "THREAD_SPAWN","THREAD_JOIN","SLEEP_MS", - "BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR" + "BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR", + "PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT" }; typedef struct { @@ -78,7 +79,7 @@ void vm_dump_globals(VM *vm); void vm_run(VM *vm, Bytecode *entry); static inline int opcode_is_valid(int op) { - return op >= OP_NOP && op <= OP_ROTR; // all current opcodes + return op >= OP_NOP && op <= OP_PCSC_TRANSMIT; // all current opcodes } #endif diff --git a/src/vm/pcsc/connect.c b/src/vm/pcsc/connect.c new file mode 100644 index 0000000..1d068f7 --- /dev/null +++ b/src/vm/pcsc/connect.c @@ -0,0 +1,48 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-02 + */ + +/* PCSC connect */ +case OP_PCSC_CONNECT: { +#ifdef FUN_WITH_PCSC + /* Stack: [..., ctx_id, reader_name] -> pops reader_name first, then ctx_id */ + Value vreader = pop_value(vm); + Value vctx = pop_value(vm); + + int ctx_id = (int)vctx.i; + free_value(vctx); + char *rname = value_to_string_alloc(&vreader); + free_value(vreader); + + pcsc_ctx_entry *e = pcsc_get_ctx(ctx_id); + if (!e || !rname) { if (rname) free(rname); push_value(vm, make_int(0)); break; } + + int hslot = pcsc_alloc_card_slot(); + if (!hslot) { free(rname); push_value(vm, make_int(0)); break; } + pcsc_card_entry *ce = pcsc_get_card(hslot); + DWORD dwActive = 0; + LONG rv = SCardConnect(e->ctx, rname, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, + &ce->h, &dwActive); + free(rname); + if (rv != SCARD_S_SUCCESS) { + ce->in_use = 0; + push_value(vm, make_int(0)); + break; + } + ce->proto = dwActive; + push_value(vm, make_int(hslot)); +#else + Value vreader = pop_value(vm); free_value(vreader); + Value vctx = pop_value(vm); free_value(vctx); + push_value(vm, make_int(0)); +#endif + break; +} diff --git a/src/vm/pcsc/disconnect.c b/src/vm/pcsc/disconnect.c new file mode 100644 index 0000000..f955804 --- /dev/null +++ b/src/vm/pcsc/disconnect.c @@ -0,0 +1,30 @@ +/** +* This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-02 + */ + +/* PCSC disconnect */ +case OP_PCSC_DISCONNECT: { +#ifdef FUN_WITH_PCSC + Value vh = pop_value(vm); + int hid = (int)vh.i; + free_value(vh); + pcsc_card_entry *ce = pcsc_get_card(hid); + if (!ce) { push_value(vm, make_int(0)); break; } + SCardDisconnect(ce->h, SCARD_LEAVE_CARD); + ce->in_use = 0; + ce->h = 0; + ce->proto = 0; + push_value(vm, make_int(1)); +#else + Value vh = pop_value(vm); free_value(vh); + push_value(vm, make_int(0)); +#endif + break; +} diff --git a/src/vm/pcsc/establish.c b/src/vm/pcsc/establish.c new file mode 100644 index 0000000..bbb8501 --- /dev/null +++ b/src/vm/pcsc/establish.c @@ -0,0 +1,26 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-02 + */ + +/* PCSC establish */ +case OP_PCSC_ESTABLISH: { +#ifdef FUN_WITH_PCSC + int slot = pcsc_alloc_ctx_slot(); + if (!slot) { push_value(vm, make_int(0)); break; } + pcsc_ctx_entry *e = pcsc_get_ctx(slot); + if (!e) { push_value(vm, make_int(0)); break; } + LONG rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &e->ctx); + if (rv != SCARD_S_SUCCESS) { e->in_use = 0; push_value(vm, make_int(0)); break; } + push_value(vm, make_int(slot)); +#else + push_value(vm, make_int(0)); +#endif + break; +} diff --git a/src/vm/pcsc/list_readers.c b/src/vm/pcsc/list_readers.c new file mode 100644 index 0000000..8b302d3 --- /dev/null +++ b/src/vm/pcsc/list_readers.c @@ -0,0 +1,64 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-02 + */ + +/* PCSC list_readers */ +case OP_PCSC_LIST_READERS: { +#ifdef FUN_WITH_PCSC + /* Pop context id; return [] if anything goes wrong */ + Value vid = pop_value(vm); + int id = (int)vid.i; + free_value(vid); + + pcsc_ctx_entry *e = pcsc_get_ctx(id); + if (!e) { push_value(vm, make_array_from_values(NULL, 0)); break; } + + DWORD sz = 0; + LONG rv = SCardListReaders(e->ctx, NULL, NULL, &sz); + if (rv != SCARD_S_SUCCESS || sz == 0) { + push_value(vm, make_array_from_values(NULL, 0)); + break; + } + + char *msz = (char*)malloc(sz); + if (!msz) { push_value(vm, make_array_from_values(NULL, 0)); break; } + + rv = SCardListReaders(e->ctx, NULL, msz, &sz); + if (rv != SCARD_S_SUCCESS) { + free(msz); + push_value(vm, make_array_from_values(NULL, 0)); + break; + } + + Value vals[64]; + int count = 0; + char *p = msz; + while (*p && count < (int)(sizeof(vals)/sizeof(vals[0]))) { + size_t n = strlen(p); + vals[count++] = make_string(p); + p += n + 1; + } + Value arr = make_array_from_values(vals, count); + for (int i = 0; i < count; ++i) free_value(vals[i]); + free(msz); + + if (arr.type != VAL_ARRAY) { + push_value(vm, make_array_from_values(NULL, 0)); + } else { + push_value(vm, arr); + } +#else + /* Fallback (no PCSC): consume ctx id and return [] to keep the stack consistent */ + Value vid = pop_value(vm); + free_value(vid); + push_value(vm, make_array_from_values(NULL, 0)); +#endif + break; +} diff --git a/src/vm/pcsc/release.c b/src/vm/pcsc/release.c new file mode 100644 index 0000000..602e845 --- /dev/null +++ b/src/vm/pcsc/release.c @@ -0,0 +1,29 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-02 + */ + +/* PCSC release */ +case OP_PCSC_RELEASE: { +#ifdef FUN_WITH_PCSC + Value vid = pop_value(vm); + int id = (int)vid.i; + free_value(vid); + pcsc_ctx_entry *e = pcsc_get_ctx(id); + if (!e) { push_value(vm, make_int(0)); break; } + SCardReleaseContext(e->ctx); + e->in_use = 0; + e->ctx = 0; + push_value(vm, make_int(1)); +#else + Value vid = pop_value(vm); free_value(vid); + push_value(vm, make_int(0)); +#endif + break; +} diff --git a/src/vm/pcsc/transmit.c b/src/vm/pcsc/transmit.c new file mode 100644 index 0000000..4ff198c --- /dev/null +++ b/src/vm/pcsc/transmit.c @@ -0,0 +1,95 @@ +/** + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-02 + */ + +/* PCSC transmit */ +case OP_PCSC_TRANSMIT: { +#ifdef FUN_WITH_PCSC + /* pops apdu array, handle_id */ + Value vapdu = pop_value(vm); + Value vh = pop_value(vm); + int hid = (int)vh.i; + free_value(vh); + pcsc_card_entry *ce = pcsc_get_card(hid); + + Value m = make_map_empty(); + map_set(&m, "data", make_array_from_values(NULL, 0)); + map_set(&m, "sw1", make_int(-1)); + map_set(&m, "sw2", make_int(-1)); + map_set(&m, "code", make_int(-1)); + + if (!ce || vapdu.type != VAL_ARRAY) { + free_value(vapdu); + push_value(vm, m); + break; + } + int n = array_length(&vapdu); + if (n < 0) n = 0; + unsigned char sbuf[4096]; + if (n > (int)sizeof(sbuf)) n = (int)sizeof(sbuf); + for (int i = 0; i < n; ++i) { + Value tmp; + if (array_get_copy(&vapdu, i, &tmp)) { + int64_t v = tmp.type == VAL_INT ? tmp.i : 0; + sbuf[i] = (unsigned char)(v & 0xFF); + free_value(tmp); + } else { + sbuf[i] = 0; + } + } + free_value(vapdu); + + SCARD_IO_REQUEST pio; + if (ce->proto == SCARD_PROTOCOL_T0) pio = *SCARD_PCI_T0; + else if (ce->proto == SCARD_PROTOCOL_T1) pio = *SCARD_PCI_T1; + else { pio = *SCARD_PCI_T1; } + + unsigned char rbuf[4096]; + DWORD rlen = sizeof(rbuf); + LONG rv = SCardTransmit(ce->h, &pio, sbuf, (DWORD)n, NULL, rbuf, &rlen); + + int sw1 = -1, sw2 = -1; + int datalen = 0; + if (rv == SCARD_S_SUCCESS && rlen >= 2) { + sw1 = rbuf[rlen - 2]; + sw2 = rbuf[rlen - 1]; + datalen = (int)rlen - 2; + } + + map_set(&m, "sw1", make_int(sw1)); + map_set(&m, "sw2", make_int(sw2)); + map_set(&m, "code", make_int((int)rv)); + + if (datalen > 0) { + Value *vals = (Value*)malloc(sizeof(Value) * (size_t)datalen); + if (!vals) { + map_set(&m, "data", make_array_from_values(NULL, 0)); + } else { + for (int i = 0; i < datalen; ++i) vals[i] = make_int((int64_t)rbuf[i]); + Value arr = make_array_from_values(vals, datalen); + for (int i = 0; i < datalen; ++i) free_value(vals[i]); + free(vals); + map_set(&m, "data", arr); + } + } + + push_value(vm, m); +#else + Value vapdu = pop_value(vm); free_value(vapdu); + Value vh = pop_value(vm); free_value(vh); + Value m = make_map_empty(); + map_set(&m, "data", make_array_from_values(NULL, 0)); + map_set(&m, "sw1", make_int(-1)); + map_set(&m, "sw2", make_int(-1)); + map_set(&m, "code", make_int(-2)); + push_value(vm, m); +#endif + break; +}