summaryrefslogtreecommitdiffstats
path: root/libfreerdp/emu/scard
diff options
context:
space:
mode:
Diffstat (limited to 'libfreerdp/emu/scard')
-rw-r--r--libfreerdp/emu/scard/FreeRDP.ico.c458
-rw-r--r--libfreerdp/emu/scard/FreeRDP.ico.h15
-rw-r--r--libfreerdp/emu/scard/smartcard_emulate.c2712
-rw-r--r--libfreerdp/emu/scard/smartcard_virtual_gids.c1632
-rw-r--r--libfreerdp/emu/scard/smartcard_virtual_gids.h57
5 files changed, 4874 insertions, 0 deletions
diff --git a/libfreerdp/emu/scard/FreeRDP.ico.c b/libfreerdp/emu/scard/FreeRDP.ico.c
new file mode 100644
index 0000000..aab0c41
--- /dev/null
+++ b/libfreerdp/emu/scard/FreeRDP.ico.c
@@ -0,0 +1,458 @@
+#include "FreeRDP.ico.h"
+
+const unsigned char resources_FreeRDP_ico[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x32, 0x1c,
+ 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
+ 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x06,
+ 0x00, 0x00, 0x00, 0x5c, 0x72, 0xa8, 0x66, 0x00, 0x00, 0x1b, 0xf9, 0x49, 0x44, 0x41, 0x54, 0x78,
+ 0xda, 0xed, 0xdd, 0x6f, 0x6c, 0x14, 0xe7, 0x9d, 0x07, 0xf0, 0x6f, 0x0c, 0x78, 0x6d, 0xcc, 0xe2,
+ 0x01, 0x83, 0x63, 0x5b, 0x38, 0x1e, 0x9a, 0x60, 0x72, 0x05, 0xea, 0x05, 0x5a, 0xe4, 0x52, 0x09,
+ 0x96, 0x9e, 0x9a, 0xca, 0x70, 0x4d, 0x8d, 0x7a, 0x49, 0xa9, 0x4e, 0xbe, 0x18, 0x89, 0xbc, 0x48,
+ 0x93, 0x55, 0xa0, 0xd0, 0xaa, 0x2f, 0x92, 0x90, 0xa4, 0x48, 0x77, 0xad, 0x40, 0xa4, 0x72, 0x68,
+ 0xa4, 0x96, 0xca, 0xe4, 0x56, 0xba, 0xd0, 0x5c, 0x2b, 0x53, 0x52, 0xb3, 0x4a, 0x4f, 0xed, 0x1a,
+ 0x4b, 0xc0, 0x8a, 0x62, 0xbc, 0xe6, 0x8f, 0xe2, 0x3f, 0xd1, 0x79, 0x89, 0x25, 0x6c, 0x99, 0x58,
+ 0x59, 0x77, 0x21, 0xb1, 0x9d, 0x2a, 0xbe, 0x17, 0x3b, 0x6b, 0x6c, 0xb3, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x33, 0xbf, 0x79, 0x66, 0x7e, 0x9f, 0x57, 0x80, 0xbd, 0xeb, 0xdf, 0x2c, 0x7e, 0xbe, 0xf3, 0x3c,
+ 0xcf, 0x3c, 0xf3, 0xcc, 0x43, 0x60, 0xae, 0xb4, 0xf3, 0x58, 0x50, 0x06, 0xe0, 0x07, 0x20, 0x03,
+ 0xa8, 0x03, 0x20, 0xcd, 0xfa, 0x72, 0x1c, 0x40, 0x0f, 0x80, 0x8e, 0x73, 0x07, 0x9b, 0x3a, 0xa8,
+ 0x6b, 0x65, 0xe6, 0x79, 0x88, 0xba, 0x00, 0x66, 0x1d, 0xa5, 0xd1, 0x37, 0x03, 0x78, 0x06, 0xc9,
+ 0x86, 0xaf, 0x45, 0x0c, 0xc0, 0x2f, 0xcf, 0x1d, 0x6c, 0x7a, 0x83, 0xba, 0x7e, 0x66, 0x3c, 0x0e,
+ 0x00, 0x17, 0x50, 0x1a, 0xfe, 0x61, 0x24, 0x1b, 0xbf, 0x5e, 0x67, 0xce, 0x1d, 0x6c, 0xda, 0x4d,
+ 0x7d, 0x2c, 0xcc, 0x58, 0x1c, 0x00, 0x0e, 0x66, 0x50, 0xc3, 0x9f, 0xed, 0xd4, 0xb9, 0x83, 0x4d,
+ 0x7b, 0xa9, 0x8f, 0x8b, 0x19, 0x87, 0x03, 0xc0, 0xa1, 0x76, 0x1e, 0x0b, 0xbe, 0x0a, 0xe0, 0x45,
+ 0xcc, 0x1d, 0xdb, 0x1b, 0x61, 0x07, 0xcf, 0x0b, 0x38, 0xc7, 0x42, 0xea, 0x02, 0x98, 0xb1, 0x76,
+ 0x1e, 0x0b, 0xfa, 0x00, 0xb4, 0x02, 0xf0, 0x99, 0xf4, 0x23, 0x5e, 0x04, 0xd0, 0x41, 0x7d, 0x9c,
+ 0xcc, 0x18, 0x05, 0xd4, 0x05, 0x30, 0xe3, 0x28, 0x67, 0xfd, 0x6e, 0x98, 0xd7, 0xf8, 0x81, 0xe4,
+ 0x95, 0x03, 0xe6, 0x10, 0x3c, 0x04, 0x70, 0x80, 0x9d, 0xc7, 0x82, 0x12, 0x80, 0x36, 0x58, 0xd7,
+ 0x38, 0x37, 0x9e, 0x3b, 0xd8, 0x14, 0xa5, 0x3e, 0x6e, 0x96, 0x3f, 0x1e, 0x02, 0x08, 0x4e, 0xe9,
+ 0xf2, 0x87, 0x61, 0xfc, 0x58, 0x5f, 0x8d, 0x95, 0x3f, 0x8b, 0x99, 0x88, 0x87, 0x00, 0x02, 0xdb,
+ 0x79, 0x2c, 0xd8, 0x8c, 0x64, 0x97, 0x5f, 0xa2, 0xae, 0x85, 0x89, 0x89, 0x03, 0x40, 0x50, 0x3b,
+ 0x8f, 0x05, 0x8f, 0x23, 0x39, 0xd9, 0xc7, 0x98, 0x6e, 0x3c, 0x04, 0x10, 0xd0, 0xce, 0x63, 0xc1,
+ 0x56, 0x18, 0x77, 0x6d, 0x9f, 0xb9, 0x18, 0x07, 0x80, 0x40, 0x94, 0xc9, 0xbe, 0x30, 0xcc, 0x9d,
+ 0xe5, 0x67, 0x2e, 0xc2, 0x43, 0x00, 0x41, 0xd8, 0xac, 0xf1, 0xc7, 0xa8, 0x0b, 0x60, 0xc6, 0xe0,
+ 0xcb, 0x80, 0x02, 0xb0, 0x59, 0xe3, 0xc7, 0xb9, 0x83, 0x4d, 0xfc, 0x7b, 0xe3, 0x10, 0xdc, 0x03,
+ 0xb0, 0x39, 0xbb, 0x35, 0x7e, 0xf0, 0x2a, 0x40, 0x47, 0xe1, 0x00, 0xb0, 0x31, 0x1b, 0x36, 0x7e,
+ 0x00, 0x88, 0x52, 0x17, 0xc0, 0x8c, 0xc3, 0x01, 0x60, 0x53, 0x36, 0x6d, 0xfc, 0x40, 0x72, 0xa3,
+ 0x10, 0xe6, 0x10, 0x1c, 0x00, 0x36, 0x64, 0xe3, 0xc6, 0x0f, 0xf0, 0x10, 0xc0, 0x51, 0x38, 0x00,
+ 0x6c, 0xc6, 0xe6, 0x8d, 0x3f, 0x7a, 0xee, 0x60, 0x53, 0x8c, 0xba, 0x08, 0x66, 0x1c, 0x0e, 0x00,
+ 0x1b, 0xb1, 0x79, 0xe3, 0x07, 0xf8, 0xec, 0xef, 0x38, 0x1c, 0x00, 0xf6, 0x62, 0xe6, 0x7d, 0xfc,
+ 0x46, 0x78, 0x9b, 0xba, 0x00, 0x66, 0x2c, 0x0e, 0x00, 0x9b, 0x50, 0x96, 0xf7, 0x36, 0x52, 0xd7,
+ 0xa1, 0x22, 0xc6, 0xb7, 0x00, 0x3b, 0x0f, 0x07, 0x80, 0x0d, 0x28, 0x37, 0xf6, 0x34, 0x53, 0xd7,
+ 0x91, 0xc5, 0x19, 0xea, 0x02, 0x98, 0xf1, 0x38, 0x00, 0x88, 0x29, 0xb7, 0xf4, 0xee, 0xa7, 0xae,
+ 0x43, 0x03, 0xee, 0xfe, 0x3b, 0x10, 0x2f, 0xe9, 0x24, 0xb4, 0xf3, 0x58, 0xb0, 0x11, 0xc9, 0x9d,
+ 0x7c, 0xec, 0x2e, 0x76, 0xee, 0x60, 0xd3, 0x6a, 0xea, 0x22, 0x98, 0xf1, 0xb8, 0x07, 0x40, 0x64,
+ 0xd6, 0xe6, 0x9d, 0x22, 0xf8, 0x25, 0x75, 0x01, 0xcc, 0x1c, 0x1c, 0x00, 0x04, 0x66, 0xed, 0xe1,
+ 0x27, 0x51, 0xd7, 0xa2, 0xd1, 0x29, 0xea, 0x02, 0x98, 0x39, 0x38, 0x00, 0x68, 0x84, 0xa1, 0xfd,
+ 0xd1, 0x5c, 0xd4, 0x4e, 0x9d, 0x3b, 0xd8, 0x14, 0xa7, 0x2e, 0x82, 0x99, 0x83, 0x03, 0xc0, 0x62,
+ 0xca, 0xe5, 0x3e, 0x1f, 0x75, 0x1d, 0x39, 0xe0, 0xc9, 0x3f, 0x07, 0xe3, 0x49, 0x40, 0x0b, 0xed,
+ 0x3c, 0x16, 0xdc, 0x0f, 0xe0, 0x38, 0x75, 0x1d, 0x39, 0xe0, 0xc9, 0x3f, 0x87, 0xe3, 0x1e, 0x80,
+ 0x45, 0x76, 0x1e, 0x0b, 0xfa, 0x21, 0x56, 0xe3, 0x07, 0x80, 0xd7, 0xa8, 0x0b, 0x60, 0xe6, 0xe2,
+ 0x00, 0xb0, 0x80, 0xf2, 0x90, 0x4e, 0x11, 0x2e, 0xf7, 0xcd, 0x16, 0x07, 0x2f, 0xfe, 0x71, 0x3c,
+ 0x0e, 0x00, 0x6b, 0x88, 0x34, 0xe3, 0x9f, 0xf2, 0x4b, 0x9e, 0xfc, 0x73, 0x3e, 0x0e, 0x00, 0x93,
+ 0x09, 0x38, 0xe9, 0x97, 0xf2, 0x06, 0x75, 0x01, 0xcc, 0x7c, 0x1c, 0x00, 0x26, 0x52, 0x96, 0xf9,
+ 0x36, 0x53, 0xd7, 0xa1, 0x03, 0x5f, 0xfa, 0x73, 0x09, 0x0e, 0x00, 0x93, 0x28, 0x2b, 0xfd, 0x44,
+ 0x9b, 0xf4, 0x4b, 0xe1, 0x95, 0x7f, 0x2e, 0xc1, 0x01, 0x60, 0x02, 0x65, 0xa5, 0x5f, 0x2b, 0xc4,
+ 0x1b, 0xf7, 0x03, 0xc9, 0x5d, 0x7f, 0xa2, 0xd4, 0x45, 0x30, 0x6b, 0x70, 0x00, 0x98, 0xe3, 0x38,
+ 0xc4, 0x1c, 0xf7, 0x03, 0x7c, 0xf6, 0x77, 0x15, 0x0e, 0x00, 0x83, 0x09, 0x3c, 0xee, 0x4f, 0x39,
+ 0x43, 0x5d, 0x00, 0xb3, 0x0e, 0x07, 0x80, 0x81, 0x94, 0xeb, 0xfd, 0xa2, 0x8e, 0xfb, 0x01, 0xe0,
+ 0x0c, 0x4f, 0xfe, 0xb9, 0x0b, 0x07, 0x80, 0xb1, 0x44, 0xbc, 0xde, 0x3f, 0x1b, 0xaf, 0xfb, 0x77,
+ 0x19, 0x0e, 0x00, 0x83, 0x28, 0xdb, 0x7a, 0xf9, 0xa8, 0xeb, 0xc8, 0x53, 0x07, 0x75, 0x01, 0xcc,
+ 0x5a, 0x1c, 0x00, 0x06, 0x50, 0xd6, 0xf9, 0xef, 0xa7, 0xae, 0x23, 0x4f, 0xdc, 0xfd, 0x77, 0x21,
+ 0x0e, 0x80, 0x3c, 0xcd, 0xda, 0xdc, 0x43, 0x74, 0x7f, 0xa4, 0x2e, 0x80, 0x59, 0x8f, 0x03, 0x20,
+ 0x7f, 0xa2, 0x5e, 0xef, 0x9f, 0xaf, 0x83, 0xba, 0x00, 0x66, 0x3d, 0x0e, 0x80, 0x3c, 0x28, 0x97,
+ 0xfc, 0x1a, 0xa9, 0xeb, 0x30, 0x00, 0x3f, 0xf2, 0xcb, 0xa5, 0x38, 0x00, 0x74, 0x72, 0xc0, 0x25,
+ 0xbf, 0xd9, 0x3a, 0xa8, 0x0b, 0x60, 0x34, 0x38, 0x00, 0xf4, 0x73, 0x4a, 0xd7, 0x1f, 0x00, 0xce,
+ 0x53, 0x17, 0xc0, 0x68, 0x70, 0x00, 0xe8, 0xa0, 0x6c, 0xed, 0xe5, 0xa7, 0xae, 0xc3, 0x40, 0x1d,
+ 0xd4, 0x05, 0x30, 0x1a, 0xbc, 0x27, 0x60, 0x8e, 0x94, 0xae, 0x7f, 0x37, 0x9c, 0x73, 0xf6, 0x8f,
+ 0x9e, 0x3b, 0xd8, 0xb4, 0x91, 0xba, 0x08, 0x46, 0x83, 0x7b, 0x00, 0xb9, 0x73, 0x52, 0xd7, 0x1f,
+ 0xe0, 0xb3, 0xbf, 0xab, 0x71, 0x00, 0xe4, 0xc0, 0x81, 0x5d, 0x7f, 0x80, 0xc7, 0xff, 0xae, 0xc6,
+ 0x01, 0xa0, 0x91, 0xd2, 0xf5, 0x3f, 0x4c, 0x5d, 0x87, 0x09, 0xa2, 0xd4, 0x05, 0x30, 0x3a, 0x1c,
+ 0x00, 0xda, 0x39, 0xad, 0xeb, 0x0f, 0x24, 0xf7, 0xfd, 0x8f, 0x51, 0x17, 0xc1, 0xe8, 0x70, 0x00,
+ 0x68, 0xa0, 0x2c, 0xf8, 0xf1, 0x53, 0xd7, 0x61, 0x82, 0x28, 0x75, 0x01, 0x8c, 0x16, 0x07, 0x40,
+ 0x16, 0xca, 0x5a, 0x7f, 0xa7, 0x2c, 0xf8, 0x99, 0xaf, 0x87, 0xba, 0x00, 0x46, 0x8b, 0x03, 0x20,
+ 0x3b, 0x27, 0x76, 0xfd, 0x53, 0x3a, 0xa8, 0x0b, 0x60, 0xb4, 0x38, 0x00, 0x54, 0x28, 0xb7, 0xf9,
+ 0x36, 0x52, 0xd7, 0x61, 0xa2, 0x28, 0x75, 0x01, 0x8c, 0x16, 0x2f, 0x04, 0xca, 0x40, 0xe9, 0xfa,
+ 0x77, 0x43, 0x9c, 0xc7, 0x78, 0xe7, 0x2a, 0x7e, 0xee, 0x60, 0xd3, 0x32, 0xea, 0x22, 0x18, 0x2d,
+ 0xee, 0x01, 0x64, 0xb6, 0x1f, 0xce, 0x6d, 0xfc, 0x00, 0x9f, 0xfd, 0x19, 0x38, 0x00, 0xd2, 0x52,
+ 0x1e, 0xea, 0xe1, 0xc4, 0x6b, 0xfe, 0xb3, 0x45, 0xa9, 0x0b, 0x60, 0xf4, 0x38, 0x00, 0xd2, 0x73,
+ 0xea, 0xac, 0xff, 0x6c, 0xb7, 0xa8, 0x0b, 0x60, 0xf4, 0x38, 0x00, 0xe6, 0x71, 0xf0, 0x35, 0xff,
+ 0xf9, 0xa2, 0xd4, 0x05, 0x30, 0x7a, 0x1c, 0x00, 0xb3, 0x38, 0xfc, 0x9a, 0xff, 0x7c, 0x51, 0xea,
+ 0x02, 0x18, 0xbd, 0x85, 0xd4, 0x05, 0xd8, 0xcc, 0x61, 0x38, 0xf7, 0x9a, 0xff, 0x1c, 0x8f, 0x7e,
+ 0x79, 0x4d, 0x5b, 0x20, 0x14, 0x49, 0xfd, 0xf5, 0x3c, 0x80, 0x37, 0x5a, 0x1a, 0xea, 0xe3, 0xd4,
+ 0x75, 0x31, 0x6b, 0xf1, 0x65, 0x40, 0x85, 0x32, 0xf1, 0xd7, 0x4d, 0x5d, 0x87, 0x15, 0x8a, 0x4b,
+ 0x8a, 0x51, 0x55, 0xb3, 0x6a, 0xfe, 0x3f, 0xc7, 0x01, 0x1c, 0x68, 0x69, 0xa8, 0x3f, 0x45, 0x5d,
+ 0x1f, 0xb3, 0x0e, 0x0f, 0x01, 0xee, 0x73, 0x4b, 0xd7, 0x1f, 0x05, 0x05, 0x0b, 0xd2, 0xfd, 0xb3,
+ 0x04, 0xa0, 0x35, 0x10, 0x8a, 0x34, 0x52, 0xd7, 0xc7, 0xac, 0xc3, 0x01, 0x00, 0x60, 0xe7, 0xb1,
+ 0x60, 0x23, 0xdc, 0x31, 0xf1, 0x07, 0x00, 0xf0, 0x14, 0x79, 0xd4, 0xbe, 0xdc, 0x1a, 0x08, 0x45,
+ 0x24, 0xea, 0x1a, 0x99, 0x35, 0x38, 0x00, 0x92, 0x5c, 0x73, 0xf6, 0x07, 0x80, 0x82, 0x05, 0xaa,
+ 0xff, 0xed, 0x12, 0xc4, 0x7f, 0xca, 0x11, 0xd3, 0xc8, 0xf5, 0x01, 0xb0, 0xf3, 0x58, 0xf0, 0x55,
+ 0x38, 0x7b, 0xc5, 0xdf, 0x03, 0x0a, 0xd5, 0x7b, 0x00, 0x00, 0xf0, 0x62, 0x20, 0x14, 0x91, 0xa9,
+ 0xeb, 0x64, 0xe6, 0x73, 0x75, 0x00, 0x28, 0x97, 0xfd, 0x5e, 0xa4, 0xae, 0xc3, 0x6a, 0x05, 0x05,
+ 0x59, 0xff, 0xdb, 0x25, 0x38, 0x7f, 0x25, 0x24, 0x83, 0xcb, 0x03, 0x00, 0xc9, 0xae, 0xbf, 0x44,
+ 0x5d, 0x84, 0xd5, 0x3c, 0xd9, 0x7b, 0x00, 0x00, 0xd0, 0xcc, 0xbd, 0x00, 0xe7, 0x73, 0x6d, 0x00,
+ 0x28, 0x7b, 0xfc, 0x35, 0x53, 0xd7, 0x61, 0x73, 0xad, 0xd4, 0x05, 0x30, 0x73, 0xb9, 0x36, 0x00,
+ 0xe0, 0xd2, 0x5f, 0x6e, 0x8d, 0x67, 0xff, 0x14, 0x7f, 0x20, 0x14, 0xf1, 0x53, 0xd7, 0xcc, 0xcc,
+ 0xe3, 0xca, 0x00, 0x50, 0x36, 0xfa, 0xf0, 0x53, 0xd7, 0x41, 0x21, 0xcb, 0x15, 0x80, 0x74, 0x5c,
+ 0x19, 0x94, 0x6e, 0xe1, 0xca, 0x00, 0x80, 0x8b, 0x27, 0xb8, 0x32, 0x2c, 0x02, 0x52, 0x23, 0x07,
+ 0x42, 0x91, 0x57, 0xa9, 0xeb, 0x66, 0xe6, 0x70, 0x5d, 0x00, 0xb8, 0x6d, 0xd1, 0xcf, 0x83, 0xa6,
+ 0xf5, 0xbc, 0xe8, 0x45, 0x5e, 0x1c, 0xe4, 0x4c, 0xae, 0x0b, 0x00, 0xb8, 0x6c, 0xd1, 0xcf, 0x7c,
+ 0xd7, 0x23, 0x5d, 0x48, 0xc4, 0xc7, 0x73, 0x7d, 0x99, 0x04, 0x97, 0x7f, 0x6e, 0x4e, 0xe5, 0xaa,
+ 0x00, 0x50, 0xee, 0xf5, 0x97, 0xa9, 0xeb, 0xa0, 0xf4, 0x8f, 0xa9, 0xcf, 0xd1, 0xd1, 0x16, 0xd2,
+ 0xf3, 0xd2, 0x66, 0x9e, 0x10, 0x74, 0x1e, 0x57, 0x05, 0x00, 0x5c, 0x3c, 0xf6, 0x4f, 0x49, 0xc4,
+ 0xc7, 0x71, 0x3b, 0x36, 0x84, 0xeb, 0x91, 0x2e, 0x3d, 0x2f, 0xe7, 0x5e, 0x80, 0xc3, 0xb8, 0x26,
+ 0x00, 0xf8, 0xec, 0x3f, 0xd7, 0x95, 0xf0, 0x05, 0x3d, 0x43, 0x01, 0x5f, 0x20, 0x14, 0xd9, 0x4f,
+ 0x5d, 0x3b, 0x33, 0x8e, 0x6b, 0x02, 0x00, 0x7c, 0xf6, 0x9f, 0x63, 0x6a, 0x62, 0x52, 0xef, 0x50,
+ 0xe0, 0x30, 0x4f, 0x08, 0x3a, 0x87, 0x2b, 0x02, 0x80, 0xcf, 0xfe, 0xf7, 0x25, 0x3e, 0xb9, 0x7f,
+ 0xd6, 0xbf, 0x1d, 0x1b, 0x42, 0xac, 0x77, 0x20, 0xd7, 0xb7, 0x90, 0xc0, 0x43, 0x01, 0xc7, 0x70,
+ 0x45, 0x00, 0x80, 0xcf, 0xfe, 0x33, 0xfe, 0xf1, 0xf9, 0xe7, 0x73, 0xfe, 0x1e, 0x6e, 0x0b, 0x61,
+ 0x6a, 0x62, 0x32, 0xd7, 0xb7, 0xe1, 0x09, 0x41, 0x87, 0x70, 0x7c, 0x00, 0xf0, 0xd9, 0x5f, 0xdd,
+ 0xd4, 0xc4, 0x24, 0x2e, 0x86, 0xfe, 0xaa, 0xe7, 0xa5, 0xbc, 0x42, 0xd0, 0x01, 0x1c, 0x1f, 0x00,
+ 0xe0, 0xb3, 0x7f, 0x56, 0x7d, 0xd1, 0x1b, 0xb8, 0x1d, 0x1b, 0xca, 0xf5, 0x65, 0xbc, 0x42, 0xd0,
+ 0x01, 0x1c, 0x1d, 0x00, 0x7c, 0xf6, 0xd7, 0xae, 0x43, 0xdf, 0x50, 0x80, 0x37, 0x0e, 0x11, 0x9c,
+ 0xa3, 0x03, 0x00, 0x7c, 0xf6, 0x9f, 0x43, 0xed, 0xb2, 0x5f, 0x22, 0x3e, 0xae, 0x67, 0x6d, 0x80,
+ 0x04, 0x9e, 0x10, 0x14, 0x9a, 0x63, 0x03, 0x40, 0xb9, 0xe3, 0x4f, 0xa6, 0xae, 0xc3, 0x4e, 0xfe,
+ 0x31, 0xf5, 0xb9, 0xea, 0xd7, 0xaf, 0x84, 0x2f, 0x60, 0x6c, 0x64, 0x34, 0xd7, 0xb7, 0x6d, 0xe4,
+ 0x9d, 0x84, 0xc5, 0xe5, 0xd8, 0x00, 0x00, 0x9f, 0xfd, 0x75, 0xd1, 0x39, 0x21, 0x78, 0x9c, 0xd7,
+ 0x06, 0x88, 0xc9, 0x91, 0x01, 0xa0, 0x3c, 0xe4, 0xc3, 0x4f, 0x5d, 0x87, 0x88, 0x74, 0x2e, 0x13,
+ 0x96, 0xc1, 0x3b, 0x09, 0x0b, 0xc9, 0x91, 0x01, 0x00, 0x17, 0x6e, 0xf4, 0x69, 0xa4, 0x2b, 0xe1,
+ 0x0b, 0x7a, 0x26, 0x04, 0x0f, 0x07, 0x42, 0x11, 0x1f, 0x75, 0xed, 0x2c, 0x37, 0x8e, 0x0b, 0x00,
+ 0xde, 0xeb, 0x2f, 0x7f, 0x53, 0x13, 0x93, 0x08, 0xeb, 0x5b, 0x26, 0xcc, 0x13, 0x82, 0x82, 0x71,
+ 0x5c, 0x00, 0x80, 0x1b, 0xbf, 0x21, 0x62, 0xbd, 0x03, 0x7a, 0xd6, 0x06, 0xf8, 0x03, 0xa1, 0x48,
+ 0x33, 0x75, 0xed, 0x4c, 0x3b, 0x27, 0x06, 0xc0, 0x33, 0xd4, 0x05, 0x38, 0x85, 0xce, 0x9b, 0x85,
+ 0x78, 0x42, 0x50, 0x20, 0x8e, 0x0a, 0x00, 0x5e, 0xf8, 0x63, 0xac, 0x44, 0x7c, 0x1c, 0x5d, 0x1d,
+ 0x17, 0x73, 0x7d, 0x99, 0x04, 0x1e, 0x0a, 0x08, 0xc3, 0x51, 0x01, 0x00, 0x3e, 0xfb, 0x1b, 0xee,
+ 0xda, 0xa5, 0x2b, 0x7a, 0xf6, 0x0d, 0xe0, 0x9b, 0x85, 0x04, 0xe1, 0x98, 0x00, 0x50, 0x26, 0xff,
+ 0xfc, 0xd4, 0x75, 0x38, 0x4d, 0x1e, 0xfb, 0x06, 0x70, 0x2f, 0x40, 0x00, 0x8e, 0x09, 0x00, 0xf0,
+ 0xe4, 0x9f, 0x69, 0x6e, 0xc7, 0x86, 0xf4, 0x4c, 0x08, 0xfa, 0xf8, 0x66, 0x21, 0xfb, 0x73, 0x52,
+ 0x00, 0x70, 0xf7, 0xdf, 0x44, 0x3a, 0x7b, 0x01, 0x7c, 0xb3, 0x90, 0xcd, 0x39, 0x22, 0x00, 0x78,
+ 0xdd, 0xbf, 0xf9, 0xf2, 0x98, 0x10, 0xe4, 0x7d, 0x03, 0x6c, 0xcc, 0x11, 0x01, 0x00, 0x3e, 0xfb,
+ 0x5b, 0xe2, 0xda, 0xa5, 0x2b, 0x7a, 0x56, 0x08, 0xfa, 0xf9, 0x66, 0x21, 0xfb, 0x72, 0x4a, 0x00,
+ 0x34, 0x52, 0x17, 0xe0, 0x06, 0x79, 0xec, 0x1e, 0xc4, 0x6b, 0x03, 0x6c, 0x4a, 0xf8, 0x00, 0x50,
+ 0x1e, 0xf5, 0x25, 0x51, 0xd7, 0xe1, 0x16, 0x7d, 0xd1, 0x1b, 0x7a, 0x2e, 0x0b, 0xca, 0xe0, 0x9b,
+ 0x85, 0x6c, 0x49, 0xf8, 0x00, 0x00, 0xb0, 0x9d, 0xba, 0x00, 0xb7, 0xc9, 0x63, 0x3b, 0x71, 0x99,
+ 0xba, 0x76, 0x36, 0x97, 0x13, 0x02, 0xa0, 0x91, 0xba, 0x00, 0xb7, 0xd1, 0x79, 0x59, 0x10, 0xe0,
+ 0x09, 0x41, 0xdb, 0x11, 0x3a, 0x00, 0x94, 0xfb, 0xfe, 0x65, 0xea, 0x3a, 0xdc, 0xa8, 0x2b, 0x7c,
+ 0x41, 0xcf, 0xcb, 0x78, 0x42, 0xd0, 0x66, 0x84, 0x0e, 0x00, 0xf0, 0xca, 0xbf, 0x9c, 0x2c, 0x2c,
+ 0x5c, 0x64, 0xd8, 0x7b, 0xdd, 0x8e, 0x0d, 0xe9, 0xd9, 0x3e, 0x0c, 0xe0, 0x09, 0x41, 0x5b, 0x11,
+ 0x3d, 0x00, 0x78, 0xfc, 0x9f, 0x03, 0xaf, 0x54, 0x6a, 0xe8, 0xfb, 0x5d, 0xbf, 0xa4, 0xeb, 0x01,
+ 0xa3, 0x32, 0x78, 0x42, 0xd0, 0x36, 0x44, 0x0f, 0x00, 0x3f, 0x75, 0x01, 0x6e, 0xa6, 0xf3, 0x8a,
+ 0x00, 0x90, 0x5c, 0x21, 0x28, 0x51, 0xd7, 0xcf, 0x04, 0x0e, 0x00, 0x65, 0xfc, 0x2f, 0x51, 0xd7,
+ 0xe1, 0x76, 0xfd, 0xd1, 0x9b, 0x7a, 0x5e, 0x26, 0x81, 0x7b, 0x01, 0xb6, 0x20, 0x6c, 0x00, 0x00,
+ 0xf0, 0x51, 0x17, 0xc0, 0x80, 0xbe, 0xee, 0x1b, 0x7a, 0x5f, 0xca, 0xbd, 0x00, 0x1b, 0x10, 0x39,
+ 0x00, 0x78, 0xfc, 0xaf, 0xc3, 0xc2, 0x45, 0xc6, 0x4d, 0x04, 0x02, 0xc9, 0x7b, 0x04, 0x74, 0x3c,
+ 0x61, 0x18, 0x48, 0xf6, 0x02, 0x9a, 0xa9, 0x3f, 0x0f, 0xb7, 0x13, 0x39, 0x00, 0x7c, 0xd4, 0x05,
+ 0x88, 0xc8, 0xbb, 0xcc, 0xd8, 0x89, 0x40, 0x00, 0xe8, 0xeb, 0xd6, 0x35, 0x0c, 0x00, 0x78, 0xf7,
+ 0x66, 0x72, 0x1c, 0x00, 0x2c, 0x6f, 0xb1, 0xde, 0x01, 0x3d, 0x37, 0x09, 0x01, 0xc9, 0x07, 0x8c,
+ 0xfa, 0xa9, 0xeb, 0x77, 0x33, 0x21, 0x03, 0x40, 0xb9, 0xfd, 0x97, 0xd9, 0xc8, 0xa0, 0xbe, 0x61,
+ 0x00, 0xc0, 0x77, 0x72, 0x92, 0x12, 0x32, 0x00, 0xc0, 0xab, 0xff, 0x74, 0x2b, 0x2e, 0x59, 0x6c,
+ 0xca, 0xfb, 0xc6, 0x3e, 0xf8, 0x50, 0xef, 0x4b, 0x1b, 0xa9, 0x3e, 0x0b, 0x26, 0x6e, 0x00, 0xd4,
+ 0x51, 0x17, 0x20, 0xaa, 0x22, 0xb3, 0x02, 0x40, 0xff, 0x30, 0x40, 0xe2, 0x61, 0x00, 0x1d, 0x51,
+ 0x03, 0xc0, 0x47, 0x5d, 0x00, 0x7b, 0x50, 0x1e, 0xc3, 0x80, 0xef, 0x52, 0xd7, 0xee, 0x56, 0xa2,
+ 0x06, 0x80, 0x4c, 0x5d, 0x80, 0xa8, 0x8a, 0x17, 0x9b, 0xd3, 0x03, 0x00, 0x80, 0xe1, 0x41, 0x5d,
+ 0x77, 0x08, 0x02, 0xbc, 0xa2, 0x93, 0x0c, 0x07, 0x80, 0xcb, 0x98, 0x35, 0x04, 0x00, 0xf2, 0xea,
+ 0x01, 0xf8, 0x28, 0x3e, 0x0b, 0x26, 0x60, 0x00, 0xf0, 0x15, 0x00, 0xfb, 0x9a, 0x9a, 0x98, 0xd4,
+ 0x7b, 0x87, 0x20, 0x78, 0x1e, 0x80, 0x86, 0x70, 0x01, 0x00, 0x5e, 0xff, 0x9f, 0x17, 0x33, 0x16,
+ 0x02, 0xcd, 0xa6, 0x73, 0xa3, 0x10, 0x80, 0x7b, 0x01, 0x24, 0x44, 0x0c, 0x00, 0x1f, 0x75, 0x01,
+ 0x22, 0x33, 0x7a, 0x29, 0xf0, 0x7c, 0x63, 0xc3, 0xfa, 0x7a, 0x00, 0x00, 0x6a, 0x2c, 0xff, 0x30,
+ 0x98, 0x90, 0x01, 0x60, 0xee, 0x29, 0xcc, 0x05, 0xcc, 0x0c, 0x81, 0x8f, 0x75, 0x0e, 0x01, 0xc0,
+ 0xc1, 0x4e, 0x42, 0xc4, 0x00, 0xf0, 0x51, 0x17, 0x20, 0x3a, 0x33, 0x87, 0x01, 0x7a, 0xe7, 0x00,
+ 0xc0, 0x43, 0x3b, 0x12, 0x22, 0x06, 0x80, 0x44, 0x5d, 0x00, 0x53, 0xa7, 0x33, 0x04, 0x7c, 0xd4,
+ 0x75, 0xbb, 0x91, 0x88, 0x01, 0xe0, 0xa3, 0x2e, 0x40, 0x74, 0xcb, 0xca, 0x57, 0x98, 0xfa, 0xfe,
+ 0x93, 0xfa, 0x56, 0x04, 0x32, 0x02, 0x22, 0x06, 0x00, 0xb3, 0xb9, 0x61, 0xfd, 0x57, 0x02, 0x98,
+ 0xc5, 0x84, 0x0a, 0x00, 0x5e, 0x03, 0x60, 0x8c, 0x65, 0x2b, 0xcd, 0xed, 0x01, 0x30, 0x71, 0x08,
+ 0x15, 0x00, 0x8c, 0x31, 0x63, 0x89, 0x16, 0x00, 0x12, 0x75, 0x01, 0x4e, 0x60, 0xf6, 0x1c, 0xc0,
+ 0xed, 0xc1, 0x8f, 0xa8, 0x0f, 0x91, 0x69, 0x24, 0x5a, 0x00, 0xf8, 0xa8, 0x0b, 0x70, 0x0a, 0xb3,
+ 0x17, 0x04, 0x31, 0x31, 0x88, 0x16, 0x00, 0xcc, 0x20, 0x66, 0x2f, 0x09, 0x66, 0x62, 0x10, 0x2d,
+ 0x00, 0xf8, 0xb7, 0xd6, 0x20, 0x66, 0xed, 0x0c, 0xc4, 0xc4, 0xb2, 0x90, 0xba, 0x80, 0x1c, 0xf9,
+ 0xa8, 0x0b, 0xb0, 0x9b, 0x82, 0x05, 0x05, 0xf0, 0x14, 0x79, 0x50, 0x50, 0xb0, 0x00, 0x9e, 0x22,
+ 0x0f, 0x00, 0x60, 0xe1, 0xa2, 0x85, 0x0f, 0x3c, 0x07, 0x70, 0xd1, 0xa2, 0x85, 0x73, 0xba, 0xfd,
+ 0x8f, 0x7e, 0x79, 0x0d, 0x12, 0xf1, 0x71, 0x24, 0xe2, 0x7f, 0x9f, 0xf3, 0x7d, 0x63, 0x23, 0xa3,
+ 0x33, 0x3b, 0xfb, 0xa4, 0xc6, 0xf2, 0xb9, 0xde, 0xe0, 0xa3, 0xf3, 0x11, 0x64, 0x1d, 0xb4, 0x9f,
+ 0xa4, 0x3b, 0x89, 0x16, 0x00, 0xae, 0xe5, 0x29, 0xf2, 0x60, 0xe1, 0xa2, 0x45, 0xf0, 0x14, 0x79,
+ 0x50, 0x58, 0x54, 0x38, 0xf3, 0xe7, 0x7c, 0x78, 0xa5, 0xd2, 0x07, 0x1a, 0x6b, 0x95, 0x5c, 0x3d,
+ 0xf3, 0xe7, 0xcd, 0xfe, 0xad, 0x33, 0x7f, 0x9e, 0x9a, 0x98, 0xc4, 0xc7, 0x23, 0xa3, 0x18, 0x1b,
+ 0x19, 0x45, 0xe2, 0x93, 0x71, 0x8c, 0x8d, 0x8c, 0x66, 0x0c, 0x06, 0x9d, 0xc3, 0x8b, 0xb8, 0x55,
+ 0x9f, 0x25, 0xbb, 0x8f, 0x03, 0xc0, 0x86, 0x0a, 0x16, 0x14, 0xa0, 0x78, 0xf1, 0x62, 0x14, 0x97,
+ 0x14, 0xa3, 0xb0, 0xc8, 0x83, 0xe2, 0xc5, 0xc5, 0xd4, 0x25, 0xa1, 0xb0, 0xc8, 0x83, 0x2a, 0xb9,
+ 0x7a, 0x4e, 0x40, 0x00, 0x98, 0x09, 0x82, 0xdb, 0x83, 0x43, 0xb8, 0x1d, 0xfb, 0x48, 0xef, 0xbe,
+ 0x80, 0x00, 0xd0, 0x43, 0x7d, 0x8c, 0x6e, 0x24, 0x5a, 0x00, 0xf8, 0xa9, 0x0b, 0x30, 0xc3, 0xec,
+ 0x06, 0x5f, 0xe2, 0x2d, 0x11, 0x6a, 0x86, 0xbe, 0xac, 0xa2, 0x1c, 0x65, 0x15, 0xe5, 0xd8, 0x50,
+ 0xbf, 0x19, 0x40, 0x32, 0x10, 0x0a, 0xf5, 0xf5, 0x4c, 0xa2, 0xd4, 0xc7, 0xe2, 0x46, 0xa2, 0x05,
+ 0x80, 0x63, 0x2c, 0x5c, 0xb4, 0x08, 0x25, 0xde, 0x12, 0x94, 0x2c, 0x5d, 0x62, 0x8b, 0x33, 0xbc,
+ 0x51, 0xca, 0x2a, 0xca, 0xf5, 0xbe, 0x34, 0x4a, 0x5d, 0xbb, 0x1b, 0x71, 0x00, 0x58, 0xc8, 0x53,
+ 0xe4, 0x81, 0x57, 0x5a, 0x2a, 0xdc, 0x59, 0xde, 0x02, 0xb1, 0x96, 0x86, 0xfa, 0x18, 0x75, 0x11,
+ 0x6e, 0xc4, 0x01, 0x60, 0xb2, 0xd4, 0x99, 0xde, 0x2b, 0x2d, 0xcd, 0x7b, 0xd2, 0xce, 0xc1, 0x3a,
+ 0xa8, 0x0b, 0x70, 0x2b, 0x61, 0x02, 0x60, 0xe7, 0xb1, 0xa0, 0x4c, 0x5d, 0x43, 0x2e, 0x4a, 0xbc,
+ 0x4b, 0xe0, 0x95, 0xbc, 0x28, 0xf1, 0x2e, 0xa1, 0x2e, 0x45, 0x04, 0x7f, 0xa4, 0x2e, 0xc0, 0xad,
+ 0x44, 0x5a, 0x08, 0x24, 0x53, 0x17, 0x90, 0x4d, 0xc1, 0x82, 0x02, 0x2c, 0x5f, 0x59, 0x86, 0x9a,
+ 0x35, 0x32, 0x2a, 0xaa, 0x2b, 0xb9, 0xf1, 0x6b, 0x77, 0x3c, 0x10, 0x8a, 0x34, 0x53, 0x17, 0xe1,
+ 0x46, 0x22, 0x05, 0x80, 0x6d, 0x2d, 0x5c, 0xb4, 0x08, 0xe5, 0x55, 0x0f, 0x63, 0xf5, 0xda, 0x47,
+ 0xb1, 0x6c, 0xe5, 0x72, 0x1e, 0xdf, 0xe7, 0x4e, 0x06, 0xd0, 0x1a, 0x08, 0x45, 0x06, 0x39, 0x08,
+ 0xac, 0xc5, 0x01, 0x90, 0x87, 0xe2, 0x92, 0x62, 0x54, 0xc9, 0xab, 0x50, 0xb3, 0x46, 0x86, 0x57,
+ 0x5a, 0x4a, 0x5d, 0x8e, 0x13, 0xc8, 0x48, 0x06, 0x41, 0x98, 0x9f, 0x13, 0x60, 0x0d, 0x61, 0xe6,
+ 0x00, 0xec, 0xa4, 0xb8, 0xa4, 0x18, 0xcb, 0x56, 0x96, 0x39, 0xea, 0xf2, 0x9d, 0xcd, 0xf8, 0x01,
+ 0xf8, 0x03, 0xa1, 0xc8, 0x29, 0x00, 0x07, 0x5a, 0x1a, 0xea, 0xe3, 0xd4, 0x05, 0x39, 0x15, 0xf7,
+ 0x00, 0x72, 0x90, 0x3a, 0xe3, 0x57, 0xd5, 0xac, 0xe2, 0xc6, 0x6f, 0x8d, 0x66, 0x00, 0x83, 0x81,
+ 0x50, 0xa4, 0x91, 0xba, 0x10, 0xa7, 0x12, 0x29, 0x00, 0x64, 0xaa, 0x1f, 0x9c, 0x1a, 0xe3, 0x73,
+ 0xc3, 0x27, 0x21, 0x01, 0x68, 0x0b, 0x84, 0x22, 0x6d, 0x81, 0x50, 0x44, 0xa2, 0x2e, 0xc6, 0x69,
+ 0x38, 0x00, 0x54, 0xcc, 0x9e, 0xd5, 0xe7, 0x31, 0x3e, 0xb9, 0x46, 0x70, 0x6f, 0xc0, 0x70, 0x22,
+ 0x05, 0x80, 0xa5, 0x4a, 0xbc, 0x4b, 0x50, 0xfd, 0xa5, 0x47, 0xb0, 0x6c, 0xe5, 0x72, 0xea, 0x52,
+ 0xd8, 0x7d, 0x12, 0x92, 0xbd, 0x81, 0xe3, 0xd4, 0x85, 0x38, 0x05, 0x07, 0xc0, 0x3c, 0x0b, 0x17,
+ 0x2d, 0x42, 0x95, 0xbc, 0x0a, 0x15, 0xd5, 0x95, 0x7c, 0x39, 0xcf, 0xbe, 0xf6, 0x07, 0x42, 0x91,
+ 0x6e, 0x1e, 0x12, 0xe4, 0x8f, 0x03, 0x60, 0x96, 0xe5, 0x2b, 0xcb, 0x50, 0xfd, 0xa5, 0x47, 0x78,
+ 0x9c, 0x2f, 0x06, 0x1f, 0x92, 0x43, 0x02, 0x1f, 0x75, 0x21, 0x22, 0xe3, 0x00, 0x40, 0xf2, 0x26,
+ 0x9d, 0x55, 0x4a, 0x77, 0xbf, 0x60, 0x01, 0x7f, 0x24, 0x02, 0x91, 0x00, 0x84, 0x79, 0xf1, 0x90,
+ 0x7e, 0xae, 0xff, 0x6d, 0x2f, 0x5d, 0x2e, 0xa1, 0xaa, 0x66, 0x15, 0xdf, 0xa8, 0x23, 0x2e, 0x09,
+ 0xc9, 0xc5, 0x43, 0xcd, 0xd4, 0x85, 0x88, 0xc8, 0xb5, 0x01, 0x50, 0xb0, 0xa0, 0x00, 0x55, 0xf2,
+ 0x2a, 0xac, 0xa8, 0x58, 0xc9, 0x67, 0x7d, 0x67, 0x68, 0x0d, 0x84, 0x22, 0xad, 0xd4, 0x45, 0x88,
+ 0xc6, 0x95, 0xbf, 0xf9, 0xc5, 0x25, 0xc5, 0xa8, 0x79, 0x6c, 0x35, 0x8f, 0xf5, 0x9d, 0xa7, 0x99,
+ 0x43, 0x20, 0x37, 0xae, 0x0b, 0x80, 0xe5, 0x2b, 0xcb, 0x50, 0x55, 0xb3, 0x8a, 0xcf, 0xfa, 0xce,
+ 0xc5, 0x21, 0x90, 0x03, 0xd7, 0xb4, 0x82, 0x82, 0x05, 0x05, 0xa8, 0xa8, 0xae, 0xe4, 0xeb, 0xfa,
+ 0xee, 0xc0, 0x21, 0xa0, 0x91, 0x2b, 0x02, 0xc0, 0x53, 0xe4, 0x41, 0x55, 0xcd, 0x2a, 0xbe, 0x3f,
+ 0xdf, 0x5d, 0x9a, 0x03, 0xa1, 0xc8, 0xab, 0xd4, 0x45, 0xd8, 0x9d, 0xe3, 0x03, 0xa0, 0xb8, 0xa4,
+ 0x98, 0x67, 0xf9, 0xdd, 0xeb, 0x30, 0x5f, 0x1d, 0x50, 0xe7, 0xe8, 0x00, 0xf0, 0x4a, 0x4b, 0x79,
+ 0xbc, 0xcf, 0x5a, 0x79, 0x6f, 0x81, 0xcc, 0x1c, 0xdb, 0x32, 0x96, 0xaf, 0x2c, 0x43, 0x79, 0xd5,
+ 0xc3, 0xd4, 0x65, 0x30, 0x7b, 0x68, 0xe3, 0x15, 0x83, 0xe9, 0x39, 0x32, 0x00, 0xca, 0xab, 0x1e,
+ 0xe6, 0xc9, 0x3e, 0x36, 0x9b, 0x84, 0x64, 0x4f, 0x40, 0xa2, 0x2e, 0xc4, 0x6e, 0x1c, 0x17, 0x00,
+ 0xe5, 0x55, 0x0f, 0xf3, 0xad, 0xbb, 0x2c, 0x1d, 0x1f, 0x00, 0xbe, 0x32, 0x30, 0x8f, 0xa3, 0x02,
+ 0x80, 0x1b, 0x3f, 0xcb, 0xa2, 0x31, 0x10, 0x8a, 0xec, 0xa7, 0x2e, 0xc2, 0x4e, 0x1c, 0x13, 0x00,
+ 0xdc, 0xf8, 0x99, 0x46, 0xc7, 0x79, 0x3e, 0xe0, 0x3e, 0x47, 0x04, 0x00, 0x37, 0x7e, 0x96, 0x23,
+ 0xde, 0x5e, 0x4c, 0x21, 0x7c, 0x00, 0x70, 0xe3, 0x67, 0x3a, 0xc8, 0x00, 0x0e, 0x53, 0x17, 0x61,
+ 0x07, 0x42, 0x07, 0x00, 0x37, 0x7e, 0x96, 0x87, 0xfd, 0xbc, 0x3e, 0x40, 0xe0, 0x00, 0xf0, 0x4a,
+ 0x4b, 0xb9, 0xf1, 0xb3, 0x7c, 0xb9, 0xfe, 0xd2, 0xa0, 0x90, 0x01, 0xe0, 0x95, 0x96, 0xf2, 0x22,
+ 0x1f, 0x66, 0x04, 0x19, 0xc0, 0x7e, 0xea, 0x22, 0x28, 0x09, 0x17, 0x00, 0x9e, 0x22, 0x0f, 0x56,
+ 0x3c, 0xbc, 0x92, 0xba, 0x0c, 0xe6, 0x1c, 0x87, 0x03, 0xa1, 0x88, 0x4c, 0x5d, 0x04, 0x15, 0x91,
+ 0x02, 0xa0, 0xa3, 0x60, 0x41, 0x01, 0xaf, 0xed, 0x67, 0x66, 0x70, 0xed, 0x02, 0x21, 0xa1, 0x5a,
+ 0x12, 0x37, 0x7e, 0x66, 0x12, 0xbf, 0x5b, 0x27, 0x04, 0x85, 0x69, 0x4d, 0x8f, 0x7e, 0x79, 0xcd,
+ 0x77, 0xf9, 0x96, 0x5e, 0x66, 0x22, 0x57, 0xf6, 0x02, 0x84, 0x08, 0x00, 0xe5, 0x9e, 0xee, 0xfd,
+ 0xd4, 0x75, 0x30, 0x47, 0x93, 0xdd, 0xb8, 0x77, 0x80, 0xed, 0x03, 0x40, 0x99, 0xa0, 0xe1, 0x47,
+ 0x41, 0x31, 0x2b, 0xb8, 0x6e, 0x71, 0x90, 0xed, 0x03, 0x00, 0x40, 0x1b, 0x92, 0xb7, 0x73, 0x32,
+ 0x66, 0x36, 0xd7, 0xf5, 0x02, 0x6c, 0x1d, 0x00, 0xca, 0x9e, 0x6e, 0x3e, 0xea, 0x3a, 0x98, 0xab,
+ 0xb8, 0xaa, 0x17, 0xf0, 0x10, 0x75, 0x01, 0x99, 0x28, 0x77, 0x6c, 0x75, 0x53, 0xd7, 0xc1, 0x5c,
+ 0x69, 0x6f, 0x4b, 0x43, 0xfd, 0x29, 0xea, 0x22, 0xac, 0x60, 0xe7, 0x1e, 0x80, 0x2b, 0x67, 0x65,
+ 0x99, 0x2d, 0xbc, 0x48, 0x5d, 0x80, 0x55, 0x6c, 0x19, 0x00, 0xdc, 0xf5, 0x67, 0xc4, 0x7c, 0x6e,
+ 0x59, 0x17, 0x60, 0xbb, 0x00, 0x50, 0x66, 0xfd, 0x5d, 0x35, 0x0e, 0x63, 0xb6, 0xf4, 0x0c, 0x75,
+ 0x01, 0x56, 0xb0, 0xdd, 0x1c, 0x40, 0x20, 0x14, 0x09, 0x03, 0xf0, 0x53, 0xd7, 0xc1, 0x9c, 0x65,
+ 0x6a, 0x62, 0x12, 0xd7, 0x23, 0x5d, 0x98, 0xfc, 0x6c, 0x02, 0x63, 0x23, 0xa3, 0xaa, 0xdf, 0x5b,
+ 0xb5, 0xfa, 0x11, 0x14, 0x16, 0x79, 0x30, 0x35, 0x31, 0xb9, 0xfa, 0xe2, 0xcf, 0x7f, 0x14, 0xa3,
+ 0xae, 0xdd, 0x4c, 0xb6, 0x0a, 0x80, 0x40, 0x28, 0xd2, 0x88, 0xe4, 0x65, 0x3f, 0xc6, 0x0c, 0xf5,
+ 0xfe, 0x3b, 0x67, 0x10, 0xeb, 0x1d, 0xc8, 0xf5, 0x65, 0x71, 0x00, 0x3b, 0xa6, 0x2e, 0xff, 0x21,
+ 0x4a, 0x5d, 0xbf, 0x59, 0xec, 0x36, 0x04, 0xe0, 0x05, 0x3f, 0xcc, 0x14, 0x53, 0x13, 0x13, 0x7a,
+ 0x5e, 0x26, 0xc1, 0xe1, 0x93, 0xd1, 0xb6, 0x09, 0x00, 0x65, 0xe2, 0x4f, 0xa6, 0xae, 0x83, 0xb1,
+ 0x79, 0x7c, 0x85, 0x5b, 0xbe, 0xd7, 0x4c, 0x5d, 0x84, 0x59, 0x6c, 0x11, 0x00, 0xca, 0xae, 0x2c,
+ 0xae, 0xb9, 0xf4, 0xc2, 0x84, 0xe3, 0xd8, 0x09, 0x41, 0x5b, 0x04, 0x00, 0x92, 0x37, 0xfa, 0x48,
+ 0xd4, 0x45, 0x30, 0xe7, 0xf2, 0x4a, 0xa5, 0xf9, 0xbc, 0xdc, 0x5f, 0xb8, 0xe5, 0x7b, 0x12, 0xf5,
+ 0x31, 0x98, 0x81, 0x3c, 0x00, 0xf8, 0xec, 0xcf, 0xac, 0x50, 0xb9, 0xba, 0x3a, 0xdf, 0xb7, 0xf0,
+ 0x53, 0x1f, 0x83, 0x19, 0xc8, 0x03, 0x00, 0x7c, 0xf6, 0x67, 0x16, 0x58, 0xeb, 0x5b, 0x8f, 0xc2,
+ 0xfc, 0xf6, 0x93, 0xf0, 0x51, 0x1f, 0x83, 0x19, 0x48, 0x03, 0x80, 0xcf, 0xfe, 0xcc, 0x4a, 0x3b,
+ 0x76, 0x37, 0xe4, 0xf3, 0xf2, 0x1a, 0xea, 0xfa, 0xcd, 0xb0, 0x90, 0xf8, 0xe7, 0xef, 0x87, 0x4b,
+ 0xce, 0xfe, 0x53, 0x13, 0x93, 0xf8, 0x78, 0x64, 0x14, 0x89, 0xf8, 0x38, 0xee, 0xc6, 0xff, 0x9e,
+ 0xf1, 0xfb, 0x96, 0x48, 0x4b, 0xe1, 0x95, 0x4a, 0xb1, 0xa2, 0xa2, 0x3c, 0xdf, 0x33, 0x16, 0x9b,
+ 0x47, 0x7e, 0x7c, 0x0d, 0xbe, 0xba, 0xe3, 0x1b, 0xb8, 0x12, 0xbe, 0xa0, 0xeb, 0xe5, 0xd4, 0xf5,
+ 0x9b, 0x81, 0x3a, 0x00, 0x1c, 0x3b, 0xbb, 0x3a, 0x35, 0x31, 0x89, 0xc1, 0xde, 0x01, 0x0c, 0x0f,
+ 0x0e, 0xe1, 0x76, 0x6c, 0x08, 0x89, 0xf8, 0x78, 0xce, 0xef, 0x51, 0x58, 0xe4, 0x41, 0x95, 0xfc,
+ 0x08, 0xaa, 0x56, 0x57, 0x43, 0x7e, 0xfc, 0xb1, 0x7c, 0x27, 0xb2, 0x18, 0x80, 0xcd, 0xfe, 0xad,
+ 0xa8, 0xf5, 0xad, 0x43, 0x57, 0xf8, 0xa2, 0xe6, 0xff, 0x97, 0xc2, 0x22, 0x0f, 0x1e, 0xdf, 0xb8,
+ 0x21, 0x7a, 0xe5, 0xf2, 0x1f, 0xa8, 0xcb, 0x37, 0x1c, 0xd9, 0x4a, 0x40, 0x65, 0xe3, 0x05, 0x47,
+ 0x2d, 0xb2, 0x48, 0x35, 0xfa, 0xfe, 0xee, 0x1b, 0xb8, 0x1d, 0x1b, 0x32, 0xfc, 0xfd, 0xcb, 0x2a,
+ 0xca, 0xb1, 0x76, 0xe3, 0x7a, 0x23, 0xc6, 0xb3, 0x4c, 0x91, 0xea, 0x99, 0x4d, 0x4d, 0x4c, 0x60,
+ 0x6c, 0xe4, 0xce, 0x9c, 0xaf, 0x55, 0xca, 0xd5, 0xf0, 0x14, 0x79, 0x50, 0x56, 0x51, 0x0e, 0x00,
+ 0xb1, 0x96, 0x86, 0xfa, 0xd5, 0xd4, 0xf5, 0x1a, 0x8d, 0x32, 0x00, 0x1c, 0xb3, 0xe6, 0x3f, 0x11,
+ 0x1f, 0x47, 0x7f, 0xf4, 0x26, 0xae, 0x5d, 0xba, 0x82, 0xa9, 0x89, 0x49, 0x4b, 0x7e, 0xe6, 0x5a,
+ 0xdf, 0x7a, 0x6c, 0xde, 0xb1, 0x95, 0x7b, 0x05, 0xd6, 0x5a, 0xdd, 0xd2, 0x50, 0x1f, 0xa3, 0x2e,
+ 0xc2, 0x48, 0x24, 0x01, 0xa0, 0xdc, 0xf1, 0x37, 0x48, 0x7d, 0xf0, 0xf9, 0x4a, 0xdd, 0x60, 0x62,
+ 0x65, 0xc3, 0x9f, 0x8f, 0x83, 0xc0, 0x52, 0x07, 0x5a, 0x1a, 0xea, 0xdf, 0xa0, 0x2e, 0xc2, 0x48,
+ 0x54, 0x73, 0x00, 0xc2, 0xcf, 0xfc, 0xc7, 0x7a, 0x07, 0x10, 0x6e, 0x0b, 0x91, 0x35, 0xfc, 0x94,
+ 0xbe, 0xe8, 0x0d, 0x0c, 0xf6, 0x0e, 0xe0, 0x2b, 0x5f, 0xff, 0x2a, 0x36, 0xfb, 0xb7, 0x52, 0x7f,
+ 0x2c, 0x4e, 0xb7, 0x1d, 0xc0, 0x1b, 0xd4, 0x45, 0x18, 0x89, 0x2a, 0x00, 0x9a, 0xa9, 0x0f, 0x5c,
+ 0xaf, 0xa9, 0x89, 0x49, 0x84, 0xdb, 0x42, 0x7a, 0xee, 0x2c, 0x33, 0xb5, 0xa6, 0x2b, 0xe1, 0x0b,
+ 0x18, 0xfc, 0x60, 0x00, 0xdf, 0xfe, 0x41, 0x23, 0xf7, 0x06, 0xcc, 0xe3, 0xa7, 0x2e, 0xc0, 0x68,
+ 0x96, 0x0f, 0x01, 0x44, 0xbe, 0xe5, 0x77, 0x6c, 0x64, 0x14, 0x67, 0x5b, 0x4f, 0x93, 0x9f, 0xf5,
+ 0xd5, 0x14, 0x16, 0x79, 0xb0, 0x63, 0x77, 0x03, 0xe4, 0xc7, 0xd7, 0x50, 0x97, 0xe2, 0x54, 0x1b,
+ 0x5b, 0x1a, 0xea, 0xa3, 0xd4, 0x45, 0x18, 0x85, 0x62, 0x21, 0xd0, 0x77, 0xa9, 0x0f, 0x5a, 0x8f,
+ 0xbe, 0xe8, 0x0d, 0xfc, 0xfe, 0xad, 0xb7, 0x6d, 0xdd, 0xf8, 0x81, 0x64, 0x6f, 0xe0, 0xfd, 0x77,
+ 0xce, 0xa0, 0xab, 0xe3, 0x22, 0x75, 0x29, 0x4e, 0xe5, 0xa3, 0x2e, 0xc0, 0x48, 0x14, 0x43, 0x80,
+ 0x46, 0xea, 0x83, 0xce, 0x55, 0x5f, 0xf4, 0x06, 0x3a, 0xda, 0x42, 0x86, 0xbc, 0xd7, 0xb6, 0x4d,
+ 0xeb, 0x00, 0x00, 0x35, 0x95, 0xe5, 0x90, 0xab, 0x92, 0x4f, 0x39, 0xee, 0xe9, 0x8f, 0x21, 0x9e,
+ 0xb8, 0x07, 0x00, 0xe8, 0xbc, 0x7a, 0xd3, 0x90, 0x9f, 0x73, 0x25, 0x7c, 0x01, 0x89, 0x4f, 0xc6,
+ 0xe1, 0xcf, 0x6f, 0xf5, 0x1b, 0x7b, 0xd0, 0x76, 0x00, 0xa7, 0xa8, 0x8b, 0x30, 0x8a, 0xa5, 0x01,
+ 0xa0, 0x74, 0xff, 0x25, 0xea, 0x83, 0xce, 0x45, 0x3e, 0x8d, 0x5f, 0xf2, 0x96, 0xe0, 0x3b, 0xdb,
+ 0xb6, 0x60, 0xfb, 0xe6, 0x75, 0xd8, 0xb6, 0x69, 0x1d, 0x6a, 0x2a, 0xb5, 0x3d, 0xd6, 0xfc, 0xd6,
+ 0xf0, 0x1d, 0x74, 0x5e, 0xbd, 0x89, 0xf3, 0x5d, 0x37, 0xf1, 0x5e, 0xe7, 0xe5, 0x99, 0x70, 0xd0,
+ 0x53, 0x3b, 0x00, 0x0e, 0x01, 0x63, 0xf9, 0xa8, 0x0b, 0x30, 0x92, 0xa5, 0x73, 0x00, 0x81, 0x50,
+ 0xa4, 0x15, 0x02, 0x4d, 0x00, 0xea, 0x6d, 0xfc, 0x4f, 0x6e, 0xdf, 0x82, 0xa6, 0x5d, 0x7e, 0x3c,
+ 0xb9, 0x7d, 0x8b, 0x21, 0x75, 0x9c, 0x3d, 0x7f, 0x19, 0x2d, 0xa7, 0xdb, 0x75, 0xf7, 0x0e, 0x36,
+ 0xd4, 0x6f, 0xc6, 0xd6, 0x86, 0x6f, 0x9a, 0xfa, 0x59, 0xb9, 0x49, 0x4b, 0x43, 0xbd, 0xad, 0xb6,
+ 0xd2, 0xcb, 0x87, 0xd5, 0x01, 0x30, 0x08, 0x41, 0xd6, 0x54, 0xdf, 0x8e, 0x0d, 0xe1, 0xbd, 0xd6,
+ 0xd3, 0x39, 0xbd, 0xa6, 0x69, 0xd7, 0x0e, 0xbc, 0xfc, 0xec, 0xd3, 0x9a, 0xcf, 0xf4, 0x59, 0x4d,
+ 0x4f, 0x61, 0x7a, 0x7a, 0x12, 0x98, 0xfe, 0x1c, 0xc0, 0x34, 0x6e, 0x8d, 0x7c, 0x82, 0x23, 0x27,
+ 0xff, 0x88, 0xe0, 0xb9, 0x4b, 0x39, 0xbf, 0x95, 0x7f, 0x77, 0x03, 0xd6, 0xfa, 0xd6, 0x5b, 0xf0,
+ 0xc9, 0xb9, 0xc2, 0x8e, 0x96, 0x86, 0xfa, 0x0e, 0xea, 0x22, 0x8c, 0x60, 0xd9, 0x24, 0xa0, 0xf2,
+ 0xa4, 0x1f, 0x99, 0xfa, 0x80, 0xb5, 0x48, 0x4e, 0xa4, 0x69, 0xbf, 0x50, 0x51, 0x57, 0x2b, 0xe3,
+ 0x7f, 0xdf, 0x7a, 0x0d, 0x27, 0x5f, 0x79, 0xde, 0xb0, 0xc6, 0x3f, 0xfd, 0xc5, 0x5d, 0x4c, 0x7f,
+ 0x91, 0x00, 0xa6, 0xa7, 0x00, 0x4c, 0x03, 0x00, 0x6a, 0x2a, 0x96, 0xe1, 0x37, 0x2f, 0x35, 0xe3,
+ 0xf2, 0xdb, 0x2f, 0xa1, 0xa6, 0x72, 0x45, 0x4e, 0xef, 0xd7, 0xd1, 0x16, 0xca, 0xba, 0x1b, 0x2e,
+ 0xd3, 0xcc, 0x47, 0x5d, 0x80, 0x51, 0xac, 0xbc, 0x0a, 0xe0, 0xa7, 0x3e, 0x58, 0xad, 0xde, 0x7f,
+ 0xa7, 0x4d, 0xf3, 0x6c, 0x7f, 0x60, 0xcf, 0x2e, 0x5c, 0x0e, 0x1e, 0x9d, 0x99, 0xdc, 0x33, 0xc4,
+ 0xf4, 0x04, 0x30, 0x9d, 0xf9, 0xe7, 0x7f, 0x65, 0x4d, 0x35, 0x2e, 0xbf, 0x7d, 0x18, 0x81, 0x3d,
+ 0xbb, 0x72, 0x3c, 0xae, 0x33, 0xb6, 0xbf, 0x8a, 0x21, 0x88, 0x3a, 0xea, 0x02, 0x8c, 0x62, 0x65,
+ 0x00, 0x6c, 0xa7, 0x3e, 0x58, 0x2d, 0xae, 0x47, 0xba, 0x34, 0xdd, 0xc8, 0x23, 0x79, 0x4b, 0xf0,
+ 0x3f, 0xbf, 0xf8, 0x09, 0x8e, 0x1e, 0xd8, 0x6b, 0x78, 0x0d, 0xd3, 0x5f, 0x7c, 0x96, 0xf5, 0x7b,
+ 0x4a, 0x97, 0x14, 0xe2, 0xe8, 0xfe, 0x7f, 0xc3, 0xc9, 0x57, 0x5e, 0x80, 0xe4, 0x2d, 0xd1, 0xf4,
+ 0xbe, 0x89, 0xf8, 0xb8, 0xde, 0x5b, 0x61, 0xd9, 0x5c, 0x32, 0x75, 0x01, 0x46, 0xe1, 0x1e, 0xc0,
+ 0x2c, 0x5a, 0x1b, 0x88, 0xe4, 0x2d, 0xc1, 0x9f, 0x7f, 0xf5, 0x9a, 0x61, 0x93, 0x7c, 0x29, 0xc1,
+ 0xf6, 0x0e, 0x3c, 0xf5, 0xe3, 0xff, 0x00, 0xf0, 0x85, 0xa6, 0xef, 0x9f, 0x9e, 0x9e, 0x42, 0xd3,
+ 0x2e, 0x3f, 0xfe, 0xfc, 0xab, 0xd7, 0x34, 0x87, 0x80, 0xd6, 0x80, 0x63, 0xaa, 0xfc, 0xd4, 0x05,
+ 0x18, 0xc5, 0x92, 0x00, 0x50, 0xc6, 0xff, 0x12, 0xf5, 0xc1, 0x66, 0xd3, 0x15, 0xbe, 0x98, 0xb5,
+ 0x8b, 0x9c, 0x6a, 0xfc, 0x75, 0xb5, 0xb2, 0x61, 0x3f, 0xf7, 0xd6, 0xf0, 0x1d, 0x7c, 0xeb, 0xb9,
+ 0xc3, 0xd8, 0xf7, 0xfa, 0x9b, 0x88, 0xdf, 0x4d, 0x68, 0x7e, 0xdd, 0xb5, 0xfe, 0xff, 0x43, 0xb0,
+ 0xbd, 0x03, 0x75, 0xb5, 0x72, 0x4e, 0x21, 0x70, 0x31, 0xf4, 0x57, 0x53, 0x3e, 0x3f, 0x37, 0x51,
+ 0x6e, 0x68, 0x13, 0x9e, 0x55, 0x3d, 0x00, 0x1f, 0xf5, 0x81, 0x66, 0x93, 0x88, 0x8f, 0xcf, 0x5c,
+ 0x37, 0xcf, 0xc4, 0x8c, 0xc6, 0xdf, 0xd3, 0x1f, 0xc3, 0x96, 0xa6, 0x43, 0x33, 0x97, 0xf8, 0x6e,
+ 0x0d, 0x8f, 0x69, 0x7e, 0x6d, 0xfc, 0xee, 0x3d, 0xec, 0x7b, 0xfd, 0x4d, 0xec, 0x7b, 0xfd, 0x44,
+ 0x4e, 0x21, 0x30, 0x36, 0x32, 0x9a, 0xf5, 0x58, 0x59, 0x56, 0x32, 0x75, 0x01, 0x46, 0xb0, 0x2a,
+ 0x00, 0x6c, 0x3f, 0xfe, 0xef, 0x0a, 0x67, 0x5f, 0x3a, 0xfb, 0x9b, 0x97, 0x9f, 0x37, 0xbc, 0xf1,
+ 0x3f, 0xf1, 0xc3, 0xc3, 0x73, 0x16, 0xfa, 0xdc, 0x1a, 0x1e, 0xc3, 0xb5, 0x01, 0x6d, 0x5d, 0xf4,
+ 0x6b, 0xfd, 0xc9, 0xef, 0x0b, 0xb6, 0x87, 0x67, 0x42, 0x40, 0xeb, 0x9c, 0x84, 0x96, 0xe3, 0x65,
+ 0xaa, 0xfc, 0xd4, 0x05, 0x18, 0xc1, 0xaa, 0x00, 0x90, 0xa9, 0x0f, 0x54, 0x8d, 0x96, 0xb3, 0x7f,
+ 0x60, 0xcf, 0x2e, 0x43, 0xc7, 0xfc, 0xf1, 0xc4, 0xbd, 0x07, 0x1a, 0x7f, 0xca, 0x91, 0xdf, 0xfe,
+ 0x49, 0xd3, 0x7b, 0xb4, 0xbc, 0xfb, 0x97, 0x99, 0x3f, 0x07, 0xdb, 0xc3, 0x08, 0xb6, 0x77, 0xa0,
+ 0x69, 0x97, 0x5f, 0xd3, 0xd5, 0x81, 0x44, 0x7c, 0x9c, 0xe7, 0x02, 0xf2, 0xe3, 0x88, 0x4d, 0x42,
+ 0xad, 0x0a, 0x00, 0x3f, 0xf5, 0x81, 0xaa, 0xe9, 0x8f, 0xaa, 0xaf, 0xb0, 0xcb, 0xe5, 0xcc, 0xaa,
+ 0xd5, 0xa1, 0xe3, 0xa7, 0x32, 0x2e, 0xf1, 0x3d, 0xdb, 0x19, 0xcd, 0x1a, 0x02, 0xcf, 0x1e, 0x39,
+ 0xf5, 0xc0, 0x70, 0xe1, 0xd0, 0xf1, 0x56, 0xc4, 0x13, 0xf7, 0xf0, 0xd2, 0xbe, 0xa7, 0x51, 0x53,
+ 0x59, 0x9e, 0xb5, 0x86, 0xeb, 0x97, 0xba, 0x0c, 0x3d, 0x26, 0x97, 0x91, 0xa9, 0x0b, 0x30, 0x82,
+ 0xe9, 0x01, 0xa0, 0x4c, 0x00, 0xda, 0x5a, 0x5f, 0xb7, 0xfa, 0xd9, 0xdf, 0xe8, 0xc6, 0x7f, 0x6b,
+ 0xf8, 0x0e, 0x82, 0xed, 0x61, 0xd5, 0xef, 0x39, 0xf2, 0xdb, 0xf7, 0xf0, 0xf4, 0x4f, 0xdf, 0x7a,
+ 0x60, 0x38, 0xd0, 0xd9, 0xdd, 0x8f, 0x27, 0x5e, 0x38, 0x96, 0x76, 0x35, 0x60, 0x3c, 0x71, 0x0f,
+ 0x47, 0x4e, 0xbe, 0x0b, 0xc9, 0x5b, 0x82, 0x93, 0xaf, 0x3c, 0x9f, 0xb5, 0x8e, 0x58, 0xef, 0x00,
+ 0xaf, 0x0b, 0xd0, 0xcf, 0x47, 0x5d, 0x80, 0x11, 0xac, 0xb8, 0x19, 0x48, 0xa2, 0x3e, 0x48, 0x35,
+ 0x63, 0xca, 0x56, 0xdd, 0x99, 0x3c, 0xb9, 0x7d, 0x8b, 0xb1, 0x8b, 0x7c, 0x00, 0xb4, 0x9c, 0xd6,
+ 0xd6, 0xc5, 0x3f, 0xdb, 0x19, 0xc5, 0xd9, 0xce, 0x28, 0x00, 0x60, 0xdb, 0xa6, 0x5a, 0x74, 0x5e,
+ 0xed, 0xcf, 0xfa, 0x9a, 0x60, 0x7b, 0x07, 0x8e, 0x1e, 0xd8, 0x8b, 0x6d, 0x9b, 0x92, 0x37, 0x20,
+ 0x65, 0xbb, 0x7f, 0x60, 0xb0, 0x77, 0x80, 0x97, 0x08, 0xeb, 0x23, 0x51, 0x17, 0x60, 0x04, 0x2b,
+ 0x86, 0x00, 0x7e, 0xea, 0x83, 0x54, 0x13, 0xeb, 0xfd, 0x50, 0xf5, 0xeb, 0x2f, 0xed, 0x7b, 0xda,
+ 0xf0, 0x9f, 0x79, 0xf6, 0xfc, 0xdf, 0x72, 0x7e, 0x8d, 0x96, 0xc6, 0x0f, 0x24, 0x7b, 0x01, 0xa9,
+ 0x46, 0xff, 0xf2, 0xb3, 0xd9, 0x6b, 0x8f, 0x7d, 0xf0, 0x61, 0xd6, 0xef, 0x61, 0xe9, 0x05, 0x42,
+ 0x11, 0x3f, 0x75, 0x0d, 0xf9, 0xb2, 0x22, 0x00, 0x6c, 0xbd, 0x3f, 0xd5, 0xed, 0xc1, 0x8f, 0x32,
+ 0x7e, 0x6d, 0xdb, 0xa6, 0x75, 0x86, 0xce, 0xfa, 0x03, 0xc9, 0x06, 0x7a, 0x6b, 0xd8, 0xdc, 0x35,
+ 0xf9, 0xa9, 0x00, 0x48, 0xf5, 0x02, 0x54, 0x8f, 0x3f, 0xf6, 0x91, 0x96, 0xb7, 0x64, 0xe9, 0xc9,
+ 0xd4, 0x05, 0xe4, 0xcb, 0x8a, 0x00, 0xf0, 0x51, 0x1f, 0xa4, 0x1a, 0xb5, 0x99, 0xf0, 0x7f, 0xff,
+ 0x97, 0x1d, 0x86, 0xff, 0xbc, 0x6b, 0x03, 0x31, 0xd3, 0x8f, 0x29, 0x76, 0xfb, 0xfe, 0xfe, 0xf6,
+ 0xd9, 0x8e, 0x61, 0x6a, 0x62, 0x92, 0x6f, 0x12, 0xd2, 0x4f, 0xa6, 0x2e, 0x20, 0x5f, 0x76, 0x78,
+ 0x38, 0x28, 0x19, 0xb5, 0xc6, 0x2f, 0x79, 0x4b, 0xd0, 0xb4, 0xcb, 0x4f, 0x5d, 0xa2, 0x2e, 0xb3,
+ 0x7b, 0x18, 0xdf, 0xd9, 0xf6, 0xb5, 0xac, 0xdf, 0xff, 0x31, 0x07, 0x80, 0x5e, 0xc2, 0x5f, 0x0a,
+ 0x74, 0xf5, 0x1c, 0x80, 0xda, 0xe4, 0x9f, 0xd1, 0x13, 0x7f, 0x56, 0x9a, 0xbd, 0x1a, 0x50, 0xf2,
+ 0x96, 0x64, 0x5d, 0xbf, 0xa0, 0xf6, 0xac, 0x42, 0xa6, 0x4a, 0xa6, 0x2e, 0x20, 0x5f, 0xae, 0xee,
+ 0x01, 0xa8, 0xfd, 0xe2, 0x9b, 0x15, 0x00, 0xa5, 0x4b, 0xb4, 0xad, 0xd7, 0xcf, 0xc7, 0xfc, 0x79,
+ 0x8b, 0xac, 0xf3, 0x00, 0x83, 0x3c, 0x0f, 0xa0, 0x93, 0x8f, 0xba, 0x80, 0x7c, 0xb9, 0x3a, 0x00,
+ 0x26, 0x3f, 0x9b, 0xc8, 0xf8, 0x35, 0xb3, 0x02, 0xa0, 0xae, 0x56, 0xd6, 0x7c, 0xd3, 0x8e, 0x5e,
+ 0xf3, 0x17, 0x01, 0x19, 0x3d, 0x91, 0xc9, 0x66, 0x48, 0xd4, 0x05, 0xe4, 0xcb, 0xd4, 0x00, 0xb0,
+ 0xfb, 0x65, 0x12, 0xb5, 0xc9, 0x2f, 0x33, 0x1b, 0x8d, 0xd9, 0xc3, 0x8b, 0xf9, 0xef, 0x2f, 0xf2,
+ 0x70, 0xc6, 0xee, 0x44, 0x58, 0xe8, 0xa6, 0xc6, 0xd5, 0x3d, 0x80, 0x4c, 0xb4, 0x2c, 0xa3, 0xcd,
+ 0x87, 0xd1, 0xfb, 0x08, 0xcc, 0x96, 0xcb, 0xee, 0xc3, 0x29, 0x3c, 0x09, 0x98, 0x17, 0x89, 0xba,
+ 0x80, 0x7c, 0x70, 0x00, 0xa4, 0x61, 0xd8, 0xa6, 0x9e, 0x19, 0x34, 0xed, 0xf2, 0x9b, 0x16, 0x32,
+ 0x99, 0x16, 0xff, 0xa8, 0xf5, 0x02, 0x78, 0x39, 0x70, 0x5e, 0x64, 0xea, 0x02, 0xf2, 0xc1, 0x01,
+ 0x40, 0x44, 0xcb, 0x5a, 0xfd, 0x5c, 0xe9, 0x5d, 0xb6, 0x5c, 0x58, 0xe4, 0xa1, 0xfe, 0x38, 0x44,
+ 0x26, 0x53, 0x17, 0x90, 0x0f, 0x0e, 0x00, 0x22, 0xdb, 0x36, 0xad, 0xcb, 0x79, 0x53, 0x4f, 0x35,
+ 0x35, 0x95, 0xe5, 0xf8, 0xcd, 0xcb, 0xfa, 0x42, 0x65, 0x45, 0x85, 0xb9, 0x43, 0x1e, 0x66, 0x5f,
+ 0xae, 0x0e, 0x80, 0x32, 0xe2, 0x5f, 0xfc, 0xa3, 0x07, 0xf6, 0xa2, 0x69, 0x57, 0xfe, 0xab, 0x0d,
+ 0x53, 0x1b, 0x94, 0x9a, 0x7d, 0x75, 0x81, 0xa5, 0x65, 0xfb, 0xcd, 0x6e, 0xd4, 0xb8, 0x3a, 0x00,
+ 0x3c, 0xc5, 0x45, 0xd4, 0x25, 0xe0, 0xe4, 0x2b, 0xcf, 0x6b, 0xba, 0x69, 0x27, 0x93, 0xba, 0x5a,
+ 0x19, 0x7d, 0x6d, 0xbf, 0xca, 0xeb, 0xaa, 0x45, 0x61, 0x11, 0xfd, 0xe7, 0xc0, 0x68, 0xb8, 0x3a,
+ 0x00, 0x96, 0x48, 0x4b, 0xa9, 0x4b, 0x00, 0x90, 0xbc, 0xe3, 0x30, 0xd7, 0x67, 0x0b, 0x48, 0xde,
+ 0x12, 0x1c, 0x3d, 0xb0, 0x17, 0x97, 0x83, 0x47, 0x35, 0x9d, 0xf9, 0x6f, 0x0d, 0xdf, 0xc9, 0xf8,
+ 0xb5, 0x15, 0x26, 0x5f, 0xf5, 0x70, 0x38, 0x99, 0xba, 0x80, 0x7c, 0x50, 0x3c, 0x1d, 0xd8, 0x36,
+ 0xbc, 0x52, 0xfa, 0x1b, 0x15, 0x8d, 0x7a, 0x42, 0x6f, 0x2e, 0x52, 0x4f, 0x17, 0xea, 0xe9, 0x8f,
+ 0x21, 0xd8, 0x1e, 0x46, 0xe7, 0xd5, 0x9b, 0xe8, 0xe9, 0x8f, 0xcd, 0xf9, 0x9e, 0x9a, 0xca, 0x72,
+ 0xd4, 0xd5, 0xca, 0x33, 0xcf, 0x1e, 0xcc, 0x85, 0xda, 0x1d, 0x88, 0x76, 0x09, 0x42, 0x41, 0xc9,
+ 0xd4, 0x05, 0xe4, 0xc3, 0xd5, 0x01, 0x50, 0x25, 0x57, 0x67, 0xfc, 0xda, 0xad, 0xe1, 0x3b, 0xa6,
+ 0x5f, 0x0e, 0x4c, 0xa7, 0xae, 0x56, 0x46, 0x5d, 0xad, 0xb1, 0x3b, 0x10, 0xcd, 0x0f, 0x92, 0xf9,
+ 0x78, 0x12, 0xd0, 0xbd, 0x5c, 0x1d, 0x00, 0x40, 0x32, 0x04, 0xd2, 0xdd, 0x15, 0xd8, 0xd3, 0x3f,
+ 0x48, 0x12, 0x00, 0x66, 0x50, 0xbb, 0x05, 0xb9, 0xb0, 0xc8, 0x43, 0x3e, 0x19, 0x6a, 0xb4, 0xd4,
+ 0xff, 0xe7, 0xb0, 0x86, 0x4d, 0x4f, 0x67, 0x1f, 0xbf, 0xda, 0x09, 0x41, 0x4d, 0x20, 0x14, 0x91,
+ 0x5b, 0x1a, 0xea, 0x63, 0xd4, 0xc7, 0xad, 0x07, 0x07, 0xc0, 0xea, 0x47, 0xd2, 0x06, 0x40, 0xe7,
+ 0xd5, 0x9b, 0xa6, 0xae, 0xd8, 0xb3, 0xd2, 0xf9, 0xae, 0xcc, 0x43, 0x9a, 0x2a, 0xf9, 0x11, 0xea,
+ 0xf2, 0xf2, 0x92, 0xda, 0xdd, 0x78, 0x78, 0x70, 0x08, 0x1f, 0x8f, 0x8c, 0x3e, 0xb0, 0xbc, 0xbb,
+ 0xa6, 0xb2, 0x0c, 0x35, 0x95, 0x65, 0xd8, 0xb6, 0x71, 0xed, 0x9c, 0x7f, 0x1f, 0xbf, 0xfb, 0x29,
+ 0x7a, 0x06, 0x86, 0xd0, 0x19, 0x9e, 0xbb, 0xd3, 0x92, 0x57, 0x2a, 0x45, 0x95, 0x5c, 0x8d, 0xad,
+ 0x0d, 0xdf, 0xcc, 0x65, 0x7d, 0x84, 0x0c, 0x20, 0x46, 0xfd, 0x59, 0xe8, 0x61, 0x76, 0x00, 0xc4,
+ 0xa9, 0x0f, 0x30, 0x1b, 0xf9, 0xf1, 0xc7, 0xd2, 0x3e, 0x0e, 0xec, 0xec, 0xf9, 0xbf, 0x99, 0xf2,
+ 0xdc, 0x3f, 0x0a, 0x6a, 0x73, 0x1a, 0xf2, 0x3f, 0x3d, 0x46, 0x5d, 0x5e, 0xce, 0xa6, 0x26, 0x26,
+ 0xd1, 0x17, 0xbd, 0x81, 0xbe, 0xee, 0x1b, 0x69, 0xef, 0xe7, 0x90, 0x96, 0x2c, 0xc6, 0x0b, 0xdf,
+ 0xff, 0x67, 0x34, 0xed, 0xfc, 0x3a, 0x6a, 0x2a, 0xcb, 0x54, 0xdf, 0x6b, 0xfc, 0xee, 0xa7, 0x08,
+ 0xb6, 0x5f, 0xc2, 0x91, 0xdf, 0xfe, 0x09, 0xf1, 0xbb, 0x9f, 0x2a, 0x5b, 0xc4, 0x8f, 0xe3, 0xe3,
+ 0x91, 0x51, 0xfc, 0xeb, 0x73, 0xcf, 0x50, 0x1f, 0xaa, 0xe9, 0x4c, 0x0d, 0x80, 0x96, 0x86, 0xfa,
+ 0x68, 0x20, 0x14, 0xa1, 0x3e, 0x46, 0x55, 0x65, 0x15, 0xe5, 0xf0, 0x4a, 0xa5, 0x0f, 0xec, 0x0d,
+ 0x70, 0x6b, 0x78, 0x14, 0x3d, 0xfd, 0x31, 0xe1, 0xef, 0xa4, 0xeb, 0xe9, 0x8f, 0xa9, 0x4e, 0x00,
+ 0xae, 0x7e, 0x7c, 0x0d, 0x75, 0x89, 0x9a, 0x25, 0xe2, 0xe3, 0xe8, 0x0a, 0x5f, 0x54, 0x7d, 0x86,
+ 0x43, 0xdd, 0x9a, 0x6a, 0xfc, 0xf9, 0xc4, 0x8f, 0x50, 0xba, 0x64, 0xb1, 0xa6, 0xf7, 0x2c, 0x55,
+ 0xc2, 0xa2, 0xa6, 0xb2, 0x0c, 0x4f, 0xfd, 0xf4, 0xad, 0x99, 0x7f, 0x1f, 0x1b, 0x19, 0x45, 0xac,
+ 0x77, 0x00, 0xb2, 0x40, 0x9f, 0x8f, 0x1e, 0xae, 0xbe, 0x0c, 0x98, 0xb2, 0x76, 0x63, 0xfa, 0x5d,
+ 0x71, 0xb3, 0x6d, 0xdd, 0x2d, 0x82, 0x96, 0xd3, 0xed, 0x99, 0x8f, 0xdb, 0xb7, 0x5e, 0x88, 0x65,
+ 0xc0, 0x89, 0xf8, 0x38, 0x3a, 0xda, 0x42, 0xf8, 0xef, 0xe3, 0xbf, 0xce, 0xfa, 0x00, 0x97, 0x77,
+ 0xff, 0xf3, 0x39, 0xcd, 0x8d, 0x7f, 0xb6, 0x52, 0xef, 0x83, 0xaf, 0x19, 0x1b, 0xb9, 0xa3, 0xf5,
+ 0xe5, 0x32, 0xf5, 0x67, 0xa4, 0x97, 0x15, 0x01, 0x10, 0xa7, 0x3e, 0xc8, 0x6c, 0x6a, 0x7d, 0xe9,
+ 0xaf, 0xbf, 0x07, 0xdb, 0x3b, 0x32, 0x3e, 0xbc, 0x43, 0x04, 0xf1, 0xc4, 0x3d, 0xd5, 0x10, 0xab,
+ 0xdd, 0x68, 0xff, 0xed, 0xc0, 0xbb, 0x3a, 0x2e, 0xe2, 0xf7, 0x6f, 0xbd, 0x6d, 0xea, 0xb3, 0x0c,
+ 0xc7, 0xef, 0x7e, 0x8a, 0x37, 0x7f, 0xf7, 0x97, 0x07, 0xfe, 0x3d, 0x87, 0xcb, 0xa3, 0x32, 0xc9,
+ 0x87, 0x63, 0x00, 0x2b, 0x02, 0x20, 0x4a, 0x7d, 0x90, 0xd9, 0x78, 0xa5, 0xd2, 0xb4, 0x7b, 0xe3,
+ 0xc7, 0x13, 0xf7, 0xf0, 0xe6, 0xef, 0xda, 0x75, 0xbc, 0xa3, 0x3d, 0xa8, 0xd5, 0x5e, 0x56, 0x51,
+ 0xae, 0x7b, 0xd6, 0xdb, 0x0a, 0x89, 0xf8, 0x38, 0xde, 0x6b, 0x3d, 0x8d, 0x2b, 0xe1, 0x0b, 0x39,
+ 0xdd, 0xad, 0x98, 0xee, 0x61, 0x2a, 0x99, 0x8c, 0xdf, 0xfd, 0x14, 0xc1, 0x73, 0x97, 0xb0, 0xe5,
+ 0x99, 0x23, 0x33, 0xcf, 0x5f, 0x98, 0xcd, 0xce, 0x9f, 0x8f, 0x51, 0x5c, 0x7f, 0x15, 0x20, 0x65,
+ 0xf3, 0x8e, 0xad, 0x69, 0xcf, 0x32, 0x2d, 0xa7, 0xdb, 0xf1, 0xc2, 0xf7, 0x77, 0x09, 0xb7, 0xce,
+ 0xfe, 0xd6, 0xf0, 0x1d, 0xd5, 0xee, 0xff, 0x86, 0xaf, 0x6f, 0xa6, 0x2e, 0x31, 0xa3, 0xb1, 0x91,
+ 0x51, 0x9c, 0x6d, 0x3d, 0xad, 0xeb, 0x36, 0xe5, 0x9e, 0x81, 0x21, 0x6c, 0x79, 0xe6, 0x08, 0xea,
+ 0xd6, 0x54, 0x63, 0xdb, 0xa6, 0x5a, 0xd4, 0x54, 0x94, 0xe1, 0x2b, 0xb5, 0xf7, 0x1b, 0xf2, 0xb5,
+ 0xfe, 0x21, 0xc4, 0xef, 0x7e, 0x86, 0xce, 0xee, 0x3e, 0xd5, 0x67, 0x2d, 0xa4, 0xe6, 0x86, 0x9c,
+ 0xce, 0x8a, 0x00, 0x38, 0x0f, 0x1b, 0x6f, 0x0c, 0x9a, 0x92, 0xea, 0x05, 0xcc, 0x0f, 0x81, 0x78,
+ 0xe2, 0x1e, 0x0e, 0x1d, 0x3f, 0x65, 0xca, 0xed, 0xbb, 0x66, 0x4a, 0x3d, 0x27, 0x30, 0x9d, 0x2a,
+ 0xb9, 0xda, 0xb6, 0x4f, 0x03, 0xea, 0x8b, 0xde, 0xc0, 0xc5, 0xd0, 0x5f, 0xf3, 0xde, 0xa3, 0xa0,
+ 0x67, 0x60, 0x08, 0x3d, 0x1a, 0x7b, 0x02, 0xe9, 0xd8, 0x39, 0x20, 0x8d, 0xc4, 0x93, 0x80, 0xb3,
+ 0x64, 0xba, 0xf6, 0x9b, 0x5a, 0x9a, 0x2b, 0x8a, 0xb3, 0xe7, 0x2f, 0xe3, 0xec, 0xf9, 0xcb, 0x19,
+ 0xbf, 0xbe, 0x79, 0xc7, 0x37, 0xa8, 0x4b, 0x4c, 0x6b, 0x6c, 0x64, 0x14, 0x1d, 0x6d, 0x21, 0xf2,
+ 0x0d, 0x4a, 0xd6, 0xfa, 0xd6, 0xdb, 0x36, 0x20, 0x8d, 0x66, 0x45, 0x00, 0x74, 0x50, 0x1f, 0xa4,
+ 0x56, 0x85, 0x45, 0x1e, 0x7c, 0x35, 0x43, 0xe3, 0x78, 0xea, 0x27, 0xbf, 0x10, 0x62, 0x42, 0xf0,
+ 0xd6, 0xf0, 0x1d, 0x3c, 0xfb, 0xb3, 0x13, 0x19, 0xbf, 0xbe, 0xa1, 0x7e, 0xb3, 0x2d, 0xc7, 0xb6,
+ 0x89, 0xf8, 0x38, 0xce, 0xb6, 0x9e, 0xa6, 0x2e, 0x03, 0x65, 0x15, 0xe5, 0xd8, 0xda, 0xf0, 0x4d,
+ 0xea, 0x32, 0x2c, 0xc3, 0x57, 0x01, 0xe6, 0xc9, 0xd4, 0x40, 0xe2, 0x89, 0x7b, 0x78, 0xe2, 0x87,
+ 0x87, 0x6d, 0x1d, 0x02, 0xf1, 0xc4, 0x3d, 0x3c, 0xf5, 0x93, 0x9f, 0x67, 0xac, 0xd1, 0x2b, 0x95,
+ 0x66, 0x0c, 0x38, 0x6a, 0xd7, 0x2f, 0x75, 0x91, 0x9f, 0xf9, 0xcb, 0x2a, 0xca, 0xf1, 0xe4, 0xde,
+ 0x3d, 0x7a, 0x2e, 0x8d, 0xd6, 0x91, 0x16, 0x9e, 0x07, 0xd3, 0x03, 0xa0, 0xa5, 0xa1, 0x3e, 0x4a,
+ 0x7d, 0x90, 0xb9, 0xfa, 0xf6, 0x0f, 0x76, 0xa7, 0xfd, 0x25, 0xe8, 0xe9, 0x8f, 0xe1, 0xd0, 0xf1,
+ 0x53, 0xd4, 0xe5, 0x65, 0xf4, 0xec, 0xcf, 0x4e, 0xa8, 0xde, 0xf8, 0xf3, 0xed, 0x1f, 0x34, 0xda,
+ 0xf6, 0xba, 0x3f, 0xf5, 0xe3, 0xc9, 0xf2, 0x68, 0xfc, 0x80, 0xc0, 0x1b, 0x83, 0x5a, 0x35, 0x07,
+ 0x10, 0xa5, 0x3e, 0xd0, 0x5c, 0x14, 0x16, 0x79, 0xf0, 0xe4, 0xde, 0x3d, 0x69, 0xbf, 0x16, 0x6c,
+ 0x0f, 0x63, 0xdf, 0xeb, 0x27, 0x72, 0x7c, 0x47, 0xf3, 0xed, 0x7b, 0xfd, 0x84, 0xea, 0xb8, 0xdf,
+ 0xbf, 0xbb, 0xc1, 0x71, 0x37, 0xfd, 0x18, 0xc5, 0x2b, 0x95, 0xe6, 0xd3, 0xf8, 0x85, 0xc6, 0x01,
+ 0x90, 0x41, 0x59, 0x45, 0x39, 0xfc, 0xbb, 0x1b, 0xd2, 0x7e, 0x2d, 0xd8, 0x1e, 0xc6, 0xb7, 0x9e,
+ 0xb3, 0xc7, 0x70, 0x20, 0x9e, 0xb8, 0x87, 0x2d, 0x4d, 0x87, 0x54, 0x17, 0xfc, 0x6c, 0xa8, 0xdf,
+ 0xec, 0x9a, 0x49, 0xad, 0x5c, 0x15, 0x16, 0x79, 0x6c, 0xdd, 0x33, 0x32, 0x9b, 0x55, 0x01, 0xd0,
+ 0x43, 0x7d, 0xa0, 0x7a, 0xac, 0xf5, 0xad, 0xcf, 0x18, 0x02, 0x9d, 0x57, 0x6f, 0xe2, 0x89, 0x1f,
+ 0x1e, 0xce, 0x7a, 0xaf, 0xbd, 0x99, 0x7a, 0xfa, 0x63, 0xd8, 0xd2, 0xf4, 0x63, 0xd5, 0x1a, 0xd6,
+ 0xfa, 0xd6, 0x0b, 0x31, 0xa9, 0x55, 0xb5, 0xda, 0xfa, 0xbb, 0x12, 0x53, 0x3d, 0x3d, 0x37, 0xf7,
+ 0x8c, 0xb8, 0x07, 0x90, 0x85, 0x5a, 0x08, 0x24, 0x1b, 0xe0, 0x21, 0x1c, 0x39, 0xf9, 0xae, 0xe5,
+ 0x75, 0x1d, 0x39, 0xf9, 0x2e, 0xb6, 0x34, 0x1d, 0x52, 0xbd, 0xd1, 0x47, 0xad, 0x76, 0xbb, 0xd9,
+ 0xec, 0xdf, 0x6a, 0xe9, 0xd5, 0x09, 0x83, 0x1b, 0x7f, 0xd4, 0xb2, 0xc2, 0x0d, 0xf6, 0x90, 0x55,
+ 0x3f, 0x28, 0x10, 0x8a, 0x4c, 0x53, 0x1f, 0x6c, 0x3e, 0xb2, 0x2d, 0x50, 0xa9, 0xab, 0x95, 0x71,
+ 0xf4, 0xc0, 0x5e, 0xd3, 0x1f, 0xc3, 0xd5, 0x79, 0xf5, 0x26, 0xf6, 0xbd, 0x7e, 0x42, 0xb5, 0xe1,
+ 0x03, 0xc9, 0x35, 0x0d, 0x1b, 0xea, 0xc5, 0x5b, 0xcc, 0xd2, 0x17, 0xbd, 0x81, 0xae, 0xf0, 0x45,
+ 0xd5, 0x27, 0x37, 0x1b, 0xe1, 0x3b, 0x7b, 0xf7, 0x18, 0x19, 0x38, 0xaf, 0xb5, 0x34, 0xd4, 0xbf,
+ 0x6a, 0xf6, 0x67, 0x63, 0x06, 0x2b, 0x03, 0xa0, 0x1b, 0x82, 0x3f, 0x4d, 0x75, 0x6c, 0x64, 0x14,
+ 0xef, 0xbf, 0x73, 0x26, 0xeb, 0x63, 0xc5, 0x03, 0x7b, 0x76, 0x19, 0xbe, 0x99, 0x48, 0xb0, 0xbd,
+ 0x03, 0xff, 0xf5, 0xa7, 0xec, 0x0b, 0x92, 0x0a, 0x8b, 0x3c, 0xd8, 0xb1, 0xbb, 0x41, 0xf8, 0xdb,
+ 0x58, 0xc7, 0x46, 0x46, 0x71, 0xfd, 0x52, 0x17, 0x06, 0x7b, 0x07, 0x0c, 0xbf, 0x3c, 0xe8, 0xdf,
+ 0xdd, 0x60, 0xf4, 0x9c, 0x08, 0x07, 0x40, 0x36, 0x81, 0x50, 0xe4, 0x38, 0x80, 0xfd, 0xd4, 0x07,
+ 0x9c, 0xaf, 0xa9, 0x89, 0x49, 0x84, 0xdb, 0x42, 0x88, 0xf5, 0x0e, 0xa8, 0x7e, 0x5f, 0x4d, 0x65,
+ 0x39, 0x9e, 0xdc, 0xfe, 0x35, 0xdd, 0x4f, 0xeb, 0x01, 0x92, 0x67, 0xfb, 0xb3, 0xe7, 0x2f, 0x6b,
+ 0xbe, 0x2b, 0xb1, 0x4a, 0xae, 0x86, 0x7f, 0x77, 0x83, 0xe3, 0xd6, 0xb0, 0xc7, 0x7a, 0x07, 0x10,
+ 0xfb, 0xe0, 0x43, 0x43, 0xc2, 0xc0, 0x84, 0xc6, 0x0f, 0x70, 0x00, 0x64, 0x17, 0x08, 0x45, 0x9a,
+ 0x01, 0xb4, 0x52, 0x1f, 0xb0, 0x51, 0x62, 0xbd, 0x03, 0xb8, 0x18, 0x0a, 0x6b, 0xee, 0xaa, 0x6e,
+ 0xdb, 0xb4, 0x0e, 0x75, 0xb5, 0xf2, 0xcc, 0xce, 0xbe, 0xe9, 0x74, 0x5e, 0xbd, 0x89, 0x78, 0xe2,
+ 0x1e, 0x7a, 0xfa, 0x63, 0x39, 0x2d, 0x3d, 0x4e, 0xad, 0x60, 0x14, 0xb1, 0xcb, 0x9f, 0xab, 0xb1,
+ 0x91, 0x51, 0xf4, 0x75, 0xdf, 0x40, 0xac, 0xf7, 0xc3, 0x9c, 0x87, 0x09, 0x26, 0x35, 0x7e, 0x40,
+ 0xe0, 0x00, 0xf8, 0x7f, 0x93, 0x99, 0x6d, 0x99, 0x94, 0x53, 0x36, 0xc5, 0x00, 0x00, 0x00, 0x00,
+ 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+const unsigned int resources_FreeRDP_ico_len = 7240;
diff --git a/libfreerdp/emu/scard/FreeRDP.ico.h b/libfreerdp/emu/scard/FreeRDP.ico.h
new file mode 100644
index 0000000..ce6f97c
--- /dev/null
+++ b/libfreerdp/emu/scard/FreeRDP.ico.h
@@ -0,0 +1,15 @@
+/* Generated from resources/FreeRDP.ico with xxd -i
+ *
+ * The icon must have the following properties:
+ * - resolution of 256x256
+ * - no alpha
+ * - no alternate resolutions
+ */
+
+#ifndef FREERDP_ICO_INTERNAL_
+#define FREERDP_ICO_INTERNAL_
+
+extern const unsigned char resources_FreeRDP_ico[];
+extern const unsigned int resources_FreeRDP_ico_len;
+
+#endif /* FREERDP_ICO_INTERNAL_ */
diff --git a/libfreerdp/emu/scard/smartcard_emulate.c b/libfreerdp/emu/scard/smartcard_emulate.c
new file mode 100644
index 0000000..b2809c3
--- /dev/null
+++ b/libfreerdp/emu/scard/smartcard_emulate.c
@@ -0,0 +1,2712 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Smart Card API emulation
+ *
+ * Copyright 2021 Armin Novak <armin.novak@thincast.com>
+ * Copyright 2021 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+#include <winpr/file.h>
+#include <winpr/path.h>
+#include <winpr/library.h>
+#include <winpr/smartcard.h>
+#include <winpr/collections.h>
+#include <winpr/crypto.h>
+
+#include <freerdp/emulate/scard/smartcard_emulate.h>
+#include "FreeRDP.ico.h"
+
+#include "smartcard_virtual_gids.h"
+
+#define MAX_CACHE_ITEM_SIZE 4096
+#define MAX_CACHE_ITEM_VALUES 4096
+
+static const CHAR g_ReaderNameA[] = { 'F', 'r', 'e', 'e', 'R', 'D', 'P', ' ', 'E',
+ 'm', 'u', 'l', 'a', 't', 'o', 'r', '\0', '\0' };
+static INIT_ONCE g_ReaderNameWGuard = INIT_ONCE_STATIC_INIT;
+static WCHAR g_ReaderNameW[32] = { 0 };
+static size_t g_ReaderNameWLen = 0;
+
+static BOOL CALLBACK g_ReaderNameWInit(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context)
+{
+ WINPR_UNUSED(InitOnce);
+ WINPR_UNUSED(Parameter);
+ WINPR_UNUSED(Context);
+ InitializeConstWCharFromUtf8(g_ReaderNameA, g_ReaderNameW, ARRAYSIZE(g_ReaderNameW));
+ g_ReaderNameWLen = _wcsnlen(g_ReaderNameW, ARRAYSIZE(g_ReaderNameW) - 2) + 2;
+ return TRUE;
+}
+
+struct smartcard_emulation_context
+{
+ const rdpSettings* settings;
+ DWORD log_default_level;
+ wLog* log;
+ wHashTable* contexts;
+ wHashTable* handles;
+ BOOL configured;
+ const char* pem;
+ const char* key;
+ const char* pin;
+};
+
+#define MAX_EMULATED_READERS 1
+typedef struct
+{
+ ULONG readerState;
+ SCARD_READERSTATEA readerStateA[MAX_EMULATED_READERS];
+ SCARD_READERSTATEW readerStateW[MAX_EMULATED_READERS];
+ wHashTable* cards;
+ wArrayList* strings;
+ wHashTable* cacheA;
+ wHashTable* cacheW;
+ BOOL canceled;
+} SCardContext;
+
+typedef struct
+{
+ union
+ {
+ void* pv;
+ CHAR* pc;
+ WCHAR* pw;
+ } szReader;
+ BOOL unicode;
+ BOOL transaction;
+ DWORD transmitcount;
+ DWORD dwShareMode;
+ DWORD dwActiveProtocol;
+ SCARDCONTEXT hContext;
+ SCARDHANDLE card;
+ vgidsContext* vgids;
+ size_t referencecount;
+} SCardHandle;
+
+typedef struct
+{
+ DWORD freshness;
+ DWORD size;
+ char data[MAX_CACHE_ITEM_SIZE];
+} SCardCacheItem;
+
+static SCardHandle* find_reader(SmartcardEmulationContext* smartcard, const void* szReader,
+ BOOL unicode);
+
+static const BYTE ATR[] = { 0x3b, 0xf7, 0x18, 0x00, 0x00, 0x80, 0x31, 0xfe, 0x45,
+ 0x73, 0x66, 0x74, 0x65, 0x2d, 0x6e, 0x66, 0xc4 };
+
+static BOOL scard_status_transition(SCardContext* context)
+{
+ WINPR_ASSERT(context);
+
+ switch (context->readerState)
+ {
+ default:
+ case 0:
+ {
+ SCARD_READERSTATEA* reader = &context->readerStateA[0];
+ reader->szReader = g_ReaderNameA;
+ reader->dwEventState = SCARD_STATE_PRESENT;
+ reader->cbAtr = sizeof(ATR);
+ memcpy(reader->rgbAtr, ATR, sizeof(ATR));
+ }
+ {
+ InitOnceExecuteOnce(&g_ReaderNameWGuard, g_ReaderNameWInit, NULL, NULL);
+ SCARD_READERSTATEW* reader = &context->readerStateW[0];
+ reader->szReader = g_ReaderNameW;
+ reader->dwEventState = SCARD_STATE_PRESENT;
+ reader->cbAtr = sizeof(ATR);
+ memcpy(reader->rgbAtr, ATR, sizeof(ATR));
+ }
+ context->readerState = 42;
+ break;
+ }
+
+ return TRUE;
+}
+
+static UINT32 scard_copy_strings(SCardContext* ctx, void* dst, UINT32 dstSize, const void* src,
+ UINT32 srcSize)
+{
+ WINPR_ASSERT(ctx);
+ WINPR_ASSERT(dst);
+
+ if (dstSize == SCARD_AUTOALLOCATE)
+ {
+ void* tmp = malloc(srcSize);
+ memcpy(tmp, src, srcSize);
+ ArrayList_Append(ctx->strings, tmp);
+ *((void**)dst) = tmp;
+ return srcSize;
+ }
+ else
+ {
+ UINT32 min = MIN(dstSize, srcSize);
+ memcpy(dst, src, min);
+ return min;
+ }
+}
+
+static void scard_context_free(void* context)
+{
+ SCardContext* ctx = context;
+ if (ctx)
+ {
+ HashTable_Free(ctx->cards);
+ ArrayList_Free(ctx->strings);
+ HashTable_Free(ctx->cacheA);
+ HashTable_Free(ctx->cacheW);
+ }
+ free(ctx);
+}
+
+static BOOL char_compare(const void* a, const void* b)
+{
+ const CHAR* wa = a;
+ const CHAR* wb = b;
+
+ if (!a && !b)
+ return TRUE;
+ if (!a || !b)
+ return FALSE;
+ return strcmp(wa, wb) == 0;
+}
+
+static BOOL wchar_compare(const void* a, const void* b)
+{
+ const WCHAR* wa = a;
+ const WCHAR* wb = b;
+
+ if (!a && !b)
+ return TRUE;
+ if (!a || !b)
+ return FALSE;
+ return _wcscmp(wa, wb) == 0;
+}
+
+static SCardContext* scard_context_new(void)
+{
+ SCardContext* ctx = calloc(1, sizeof(SCardContext));
+ if (!ctx)
+ return NULL;
+
+ ctx->strings = ArrayList_New(FALSE);
+ if (!ctx->strings)
+ goto fail;
+ else
+ {
+ wObject* obj = ArrayList_Object(ctx->strings);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = free;
+ }
+
+ ctx->cacheA = HashTable_New(FALSE);
+ if (!ctx->cacheA)
+ goto fail;
+ else
+ {
+ wObject* key = HashTable_KeyObject(ctx->cacheA);
+ wObject* val = HashTable_ValueObject(ctx->cacheA);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(val);
+
+ key->fnObjectEquals = char_compare;
+ key->fnObjectNew = winpr_ObjectStringClone;
+ key->fnObjectFree = winpr_ObjectStringFree;
+
+ val->fnObjectFree = free;
+ }
+
+ ctx->cacheW = HashTable_New(FALSE);
+ if (!ctx->cacheW)
+ goto fail;
+ else
+ {
+ wObject* key = HashTable_KeyObject(ctx->cacheW);
+ wObject* val = HashTable_ValueObject(ctx->cacheW);
+ WINPR_ASSERT(key);
+ WINPR_ASSERT(val);
+
+ key->fnObjectEquals = wchar_compare;
+ key->fnObjectNew = winpr_ObjectWStringClone;
+ key->fnObjectFree = winpr_ObjectStringFree;
+
+ val->fnObjectFree = free;
+ }
+
+ scard_status_transition(ctx);
+ return ctx;
+fail:
+ scard_context_free(ctx);
+ return NULL;
+}
+
+static void scard_handle_free(void* handle)
+{
+ SCardHandle* hdl = handle;
+ if (hdl)
+ {
+ free(hdl->szReader.pv);
+ vgids_free(hdl->vgids);
+ }
+ free(hdl);
+}
+
+static SCardHandle* scard_handle_new(SmartcardEmulationContext* smartcard, SCARDCONTEXT context,
+ const void* name, BOOL unicode)
+{
+ SCardHandle* hdl = NULL;
+
+ WINPR_ASSERT(smartcard);
+
+ hdl = calloc(1, sizeof(SCardHandle));
+ if (!hdl)
+ goto fail;
+
+ /* ATTENTION: Do not use _strdup or _wcsdup!
+ * These strings are required to be double NULL terminated!
+ */
+ if (unicode)
+ {
+ size_t s = _wcslen(name);
+
+ hdl->szReader.pw = calloc(s + 2, sizeof(WCHAR));
+ if (!hdl->szReader.pw)
+ goto fail;
+ memcpy(hdl->szReader.pv, name, s * sizeof(WCHAR));
+ }
+ else
+ {
+ size_t s = strlen(name);
+
+ hdl->szReader.pc = calloc(s + 2, sizeof(CHAR));
+ if (!hdl->szReader.pc)
+ goto fail;
+ memcpy(hdl->szReader.pv, name, s * sizeof(CHAR));
+ }
+
+ if (!hdl->szReader.pv)
+ goto fail;
+
+ hdl->vgids = vgids_new();
+ if (!hdl->vgids)
+ goto fail;
+
+ {
+ const char* pem =
+ freerdp_settings_get_string(smartcard->settings, FreeRDP_SmartcardCertificate);
+ const char* key =
+ freerdp_settings_get_string(smartcard->settings, FreeRDP_SmartcardPrivateKey);
+
+ const char* pin = freerdp_settings_get_string(smartcard->settings, FreeRDP_Password);
+
+ if (!vgids_init(hdl->vgids, pem, key, pin))
+ goto fail;
+ }
+
+ hdl->unicode = unicode;
+ hdl->hContext = context;
+ return hdl;
+
+fail:
+ scard_handle_free(hdl);
+ return NULL;
+}
+
+static LONG scard_handle_valid(SmartcardEmulationContext* smartcard, SCARDHANDLE handle)
+{
+ SCardHandle* ctx = NULL;
+
+ WINPR_ASSERT(smartcard);
+
+ ctx = HashTable_GetItemValue(smartcard->handles, (const void*)handle);
+ if (!ctx)
+ return SCARD_E_INVALID_HANDLE;
+
+ return SCARD_S_SUCCESS;
+}
+
+static LONG scard_reader_name_valid_a(SmartcardEmulationContext* smartcard, SCARDCONTEXT context,
+ const char* name)
+{
+ SCardContext* ctx = NULL;
+
+ WINPR_ASSERT(smartcard);
+ ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)context);
+
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(ctx);
+
+ for (size_t x = 0; x < MAX_EMULATED_READERS; x++)
+ {
+ const SCARD_READERSTATEA* reader = &ctx->readerStateA[x];
+ if (strcmp(reader->szReader, name) == 0)
+ return SCARD_S_SUCCESS;
+ }
+
+ return SCARD_E_UNKNOWN_READER;
+}
+
+static LONG scard_reader_name_valid_w(SmartcardEmulationContext* smartcard, SCARDCONTEXT context,
+ const WCHAR* name)
+{
+ SCardContext* ctx = NULL;
+
+ WINPR_ASSERT(smartcard);
+ ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)context);
+
+ WINPR_ASSERT(name);
+ WINPR_ASSERT(ctx);
+
+ for (size_t x = 0; x < MAX_EMULATED_READERS; x++)
+ {
+ const SCARD_READERSTATEW* reader = &ctx->readerStateW[x];
+ if (_wcscmp(reader->szReader, name) == 0)
+ return SCARD_S_SUCCESS;
+ }
+
+ return SCARD_E_UNKNOWN_READER;
+}
+
+/**
+ * Standard Windows Smart Card API
+ */
+
+LONG WINAPI Emulate_SCardEstablishContext(SmartcardEmulationContext* smartcard, DWORD dwScope,
+ LPCVOID pvReserved1, LPCVOID pvReserved2,
+ LPSCARDCONTEXT phContext)
+{
+ LONG status = SCARD_E_NO_MEMORY;
+ SCardContext* ctx = NULL;
+
+ WINPR_ASSERT(smartcard);
+
+ ctx = scard_context_new();
+
+ WINPR_UNUSED(pvReserved1);
+ WINPR_UNUSED(pvReserved2);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardEstablishContext { dwScope: %s (0x%08" PRIX32 ")",
+ SCardGetScopeString(dwScope), dwScope);
+
+ if (ctx)
+ {
+ SCARDCONTEXT context = { 0 };
+
+ winpr_RAND(&context, sizeof(SCARDCONTEXT));
+ if (HashTable_Insert(smartcard->contexts, (const void*)context, ctx))
+ {
+ *phContext = context;
+ status = SCARD_S_SUCCESS;
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardEstablishContext } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ if (status != SCARD_S_SUCCESS)
+ scard_context_free(ctx);
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of ctx
+ return status;
+}
+
+LONG WINAPI Emulate_SCardReleaseContext(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext)
+{
+ LONG status = 0;
+ SCardContext* value = NULL;
+
+ WINPR_ASSERT(smartcard);
+
+ value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReleaseContext { hContext: %p",
+ (void*)hContext);
+
+ if (value)
+ HashTable_Remove(smartcard->contexts, (const void*)hContext);
+
+ status = SCARD_S_SUCCESS;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardReleaseContext } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIsValidContext(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardIsValidContext { hContext: %p",
+ (void*)hContext);
+
+ status = HashTable_Contains(smartcard->contexts, (const void*)hContext)
+ ? SCARD_S_SUCCESS
+ : SCARD_E_INVALID_HANDLE;
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIsValidContext } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReaderGroupsA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReaderGroupsA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(mszGroups);
+ WINPR_UNUSED(pcchGroups);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReaderGroupsA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReaderGroupsW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPWSTR mszGroups,
+ LPDWORD pcchGroups)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReaderGroupsW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(mszGroups);
+ WINPR_UNUSED(pcchGroups);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReaderGroupsW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReadersA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR mszGroups, LPSTR mszReaders, LPDWORD pcchReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+ if (!pcchReaders)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListReadersA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(mszGroups); /* Not required */
+
+ if (SCARD_S_SUCCESS == status)
+ {
+ SCardContext* value =
+ (SCardContext*)HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ // TODO: If emulator not ready return SCARD_E_NO_READERS_AVAILABLE
+
+ // TODO: argument mszGrous
+
+ /* Return length only */
+ if (!mszReaders)
+ *pcchReaders = ARRAYSIZE(g_ReaderNameA);
+ else
+ {
+ *pcchReaders = scard_copy_strings(value, mszReaders, *pcchReaders, g_ReaderNameA,
+ sizeof(g_ReaderNameA));
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReadersW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR mszGroups, LPWSTR mszReaders, LPDWORD pcchReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!pcchReaders)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListReadersW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(mszGroups); /* Not required */
+
+ InitOnceExecuteOnce(&g_ReaderNameWGuard, g_ReaderNameWInit, NULL, NULL);
+ if (SCARD_S_SUCCESS == status)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ // TODO: If emulator not ready return SCARD_E_NO_READERS_AVAILABLE
+
+ // TODO: argument mszGrous
+
+ /* Return length only */
+ if (!mszReaders)
+ *pcchReaders = g_ReaderNameWLen;
+ else
+ {
+ *pcchReaders = scard_copy_strings(value, mszReaders, *pcchReaders, g_ReaderNameW,
+ g_ReaderNameWLen * sizeof(WCHAR)) /
+ sizeof(WCHAR);
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListCardsA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCBYTE pbAtr, LPCGUID rgquidInterfaces,
+ DWORD cguidInterfaceCount, CHAR* mszCards, LPDWORD pcchCards)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListCardsA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(pbAtr);
+ WINPR_UNUSED(rgquidInterfaces);
+ WINPR_UNUSED(cguidInterfaceCount);
+ WINPR_UNUSED(mszCards);
+ WINPR_UNUSED(pcchCards);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListCardsA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListCardsW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCBYTE pbAtr, LPCGUID rgquidInterfaces,
+ DWORD cguidInterfaceCount, WCHAR* mszCards, LPDWORD pcchCards)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListCardsW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(pbAtr);
+ WINPR_UNUSED(rgquidInterfaces);
+ WINPR_UNUSED(cguidInterfaceCount);
+ WINPR_UNUSED(mszCards);
+ WINPR_UNUSED(pcchCards);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListCardsW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListInterfacesA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListInterfacesA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidInterfaces);
+ WINPR_UNUSED(pcguidInterfaces);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListInterfacesA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListInterfacesW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCard,
+ LPGUID pguidInterfaces, LPDWORD pcguidInterfaces)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardListInterfacesW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidInterfaces);
+ WINPR_UNUSED(pcguidInterfaces);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListInterfacesW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetProviderIdA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR szCard, LPGUID pguidProviderId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetProviderIdA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidProviderId);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetProviderIdA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetProviderIdW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR szCard, LPGUID pguidProviderId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetProviderIdW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCard);
+ WINPR_UNUSED(pguidProviderId);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetProviderIdW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetCardTypeProviderNameA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, CHAR* szProvider,
+ LPDWORD pcchProvider)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetCardTypeProviderNameA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+ WINPR_UNUSED(pcchProvider);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetCardTypeProviderNameA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetCardTypeProviderNameW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, WCHAR* szProvider,
+ LPDWORD pcchProvider)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetCardTypeProviderNameW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+ WINPR_UNUSED(pcchProvider);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetCardTypeProviderNameW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceReaderGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderGroupA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderGroupA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceReaderGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderGroupW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderGroupW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetReaderGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderGroupA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderGroupA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetReaderGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderGroupW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderGroupW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceReaderA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szDeviceName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardIntroduceReaderA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szDeviceName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceReaderW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szDeviceName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardIntroduceReaderW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szDeviceName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceReaderW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetReaderA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR szReaderName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardForgetReaderA { hContext: %p",
+ (void*)hContext);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetReaderW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR szReaderName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardForgetReaderW { hContext: %p",
+ (void*)hContext);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetReaderW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardAddReaderToGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardAddReaderToGroupA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardAddReaderToGroupA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardAddReaderToGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardAddReaderToGroupW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardAddReaderToGroupW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardRemoveReaderFromGroupA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPCSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardRemoveReaderFromGroupA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardRemoveReaderFromGroupA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardRemoveReaderFromGroupW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPCWSTR szGroupName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardRemoveReaderFromGroupW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szGroupName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardRemoveReaderFromGroupW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceCardTypeA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCardName,
+ LPCGUID pguidPrimaryProvider, LPCGUID rgguidInterfaces,
+ DWORD dwInterfaceCount, LPCBYTE pbAtr,
+ LPCBYTE pbAtrMask, DWORD cbAtrLen)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceCardTypeA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(pguidPrimaryProvider);
+ WINPR_UNUSED(rgguidInterfaces);
+ WINPR_UNUSED(dwInterfaceCount);
+ WINPR_UNUSED(pbAtr);
+ WINPR_UNUSED(pbAtrMask);
+ WINPR_UNUSED(cbAtrLen);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceCardTypeA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardIntroduceCardTypeW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCardName,
+ LPCGUID pguidPrimaryProvider, LPCGUID rgguidInterfaces,
+ DWORD dwInterfaceCount, LPCBYTE pbAtr,
+ LPCBYTE pbAtrMask, DWORD cbAtrLen)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceCardTypeW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(pguidPrimaryProvider);
+ WINPR_UNUSED(rgguidInterfaces);
+ WINPR_UNUSED(dwInterfaceCount);
+ WINPR_UNUSED(pbAtr);
+ WINPR_UNUSED(pbAtrMask);
+ WINPR_UNUSED(cbAtrLen);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardIntroduceCardTypeW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardSetCardTypeProviderNameA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCardName,
+ DWORD dwProviderId, LPCSTR szProvider)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardSetCardTypeProviderNameA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardSetCardTypeProviderNameA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardSetCardTypeProviderNameW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCardName,
+ DWORD dwProviderId, LPCWSTR szProvider)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardSetCardTypeProviderNameA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+ WINPR_UNUSED(dwProviderId);
+ WINPR_UNUSED(szProvider);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardSetCardTypeProviderNameW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetCardTypeA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szCardName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardForgetCardTypeA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetCardTypeA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardForgetCardTypeW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szCardName)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardForgetCardTypeW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(szCardName);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardForgetCardTypeW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardFreeMemory(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPVOID pvMem)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardFreeMemory { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ ArrayList_Remove(value->strings, pvMem);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardFreeMemory } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+HANDLE WINAPI Emulate_SCardAccessStartedEvent(SmartcardEmulationContext* smartcard)
+{
+ HANDLE hEvent = NULL;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardAccessStartedEvent {");
+
+ /* Not required, return random */
+ winpr_RAND(&hEvent, sizeof(hEvent));
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardAccessStartedEvent } hEvent: %p",
+ hEvent);
+
+ return hEvent;
+}
+
+void WINAPI Emulate_SCardReleaseStartedEvent(SmartcardEmulationContext* smartcard)
+{
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReleaseStartedEvent {");
+
+ /* Not required, return not supported */
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReleaseStartedEvent }");
+}
+
+LONG WINAPI Emulate_SCardLocateCardsA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR mszCards, LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardLocateCardsA { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(mszCards);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardLocateCardsW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR mszCards, LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardLocateCardsW { hContext: %p",
+ (void*)hContext);
+
+ WINPR_UNUSED(mszCards);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardLocateCardsByATRA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEA rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsByATRA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(rgAtrMasks);
+ WINPR_UNUSED(cAtrs);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsByATRA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardLocateCardsByATRW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks,
+ DWORD cAtrs, LPSCARD_READERSTATEW rgReaderStates,
+ DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsByATRW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(rgAtrMasks);
+ WINPR_UNUSED(cAtrs);
+ WINPR_UNUSED(rgReaderStates);
+ WINPR_UNUSED(cReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardLocateCardsByATRW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetStatusChangeA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetStatusChangeA { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ const DWORD diff = 100;
+ size_t eventCount = 0;
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ status = SCARD_E_TIMEOUT;
+ do
+ {
+ for (size_t x = 0; x < cReaders; x++)
+ {
+ LPSCARD_READERSTATEA out = &rgReaderStates[x];
+
+ for (size_t y = 0; y < MAX_EMULATED_READERS; y++)
+ {
+ const LPSCARD_READERSTATEA in = &value->readerStateA[y];
+ if (strcmp(out->szReader, in->szReader) == 0)
+ {
+ const SCardHandle* hdl = find_reader(smartcard, in->szReader, FALSE);
+ out->dwEventState = in->dwEventState;
+ if (hdl)
+ {
+ out->dwEventState |= SCARD_STATE_INUSE;
+ if (hdl->dwShareMode == SCARD_SHARE_EXCLUSIVE)
+ out->dwEventState |= SCARD_STATE_EXCLUSIVE;
+ }
+
+ if ((out->dwEventState & SCARD_STATE_EMPTY) !=
+ (out->dwCurrentState & SCARD_STATE_EMPTY))
+ out->dwEventState |= SCARD_STATE_CHANGED;
+ if ((out->dwEventState & SCARD_STATE_PRESENT) !=
+ (out->dwCurrentState & SCARD_STATE_PRESENT))
+ out->dwEventState |= SCARD_STATE_CHANGED;
+
+ out->cbAtr = in->cbAtr;
+ memcpy(out->rgbAtr, in->rgbAtr, out->cbAtr);
+ if (out->dwEventState & SCARD_STATE_CHANGED)
+ eventCount++;
+ }
+ }
+ }
+ if (value->canceled)
+ {
+ status = SCARD_E_CANCELLED;
+ break;
+ }
+ if (eventCount != 0)
+ {
+ status = SCARD_S_SUCCESS;
+ break;
+ }
+ Sleep(diff);
+ if (dwTimeout != INFINITE)
+ dwTimeout -= MIN(dwTimeout, diff);
+ } while (dwTimeout > 0);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetStatusChangeA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetStatusChangeW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, DWORD dwTimeout,
+ LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetStatusChangeW { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ const DWORD diff = 100;
+ size_t eventCount = 0;
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ status = SCARD_E_TIMEOUT;
+ do
+ {
+ for (size_t x = 0; x < cReaders; x++)
+ {
+ LPSCARD_READERSTATEW out = &rgReaderStates[x];
+
+ for (size_t y = 0; y < MAX_EMULATED_READERS; y++)
+ {
+ const LPSCARD_READERSTATEW in = &value->readerStateW[y];
+ if (_wcscmp(out->szReader, in->szReader) == 0)
+ {
+ const SCardHandle* hdl = find_reader(smartcard, in->szReader, TRUE);
+ out->dwEventState = in->dwEventState;
+ if (hdl)
+ {
+ out->dwEventState |= SCARD_STATE_INUSE;
+ if (hdl->dwShareMode == SCARD_SHARE_EXCLUSIVE)
+ out->dwEventState |= SCARD_STATE_EXCLUSIVE;
+ }
+ if ((out->dwEventState & SCARD_STATE_EMPTY) !=
+ (out->dwCurrentState & SCARD_STATE_EMPTY))
+ out->dwEventState |= SCARD_STATE_CHANGED;
+ if ((out->dwEventState & SCARD_STATE_PRESENT) !=
+ (out->dwCurrentState & SCARD_STATE_PRESENT))
+ out->dwEventState |= SCARD_STATE_CHANGED;
+ out->cbAtr = in->cbAtr;
+ memcpy(out->rgbAtr, in->rgbAtr, out->cbAtr);
+
+ if (out->dwEventState & SCARD_STATE_CHANGED)
+ eventCount++;
+ }
+ }
+ }
+ if (value->canceled)
+ {
+ status = SCARD_E_CANCELLED;
+ break;
+ }
+ if (eventCount != 0)
+ {
+ status = SCARD_S_SUCCESS;
+ break;
+ }
+ Sleep(diff);
+ if (dwTimeout != INFINITE)
+ dwTimeout -= MIN(dwTimeout, diff);
+ } while (dwTimeout > 0);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetStatusChangeW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardCancel(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardCancel { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value);
+ value->canceled = TRUE;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardCancel } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+SCardHandle* find_reader(SmartcardEmulationContext* smartcard, const void* szReader, BOOL unicode)
+{
+ SCardHandle* hdl = NULL;
+ UINT_PTR* keys = NULL;
+ size_t count = 0;
+
+ WINPR_ASSERT(smartcard);
+ count = HashTable_GetKeys(smartcard->handles, &keys);
+ for (size_t x = 0; x < count; x++)
+ {
+ SCardHandle* cur = HashTable_GetItemValue(smartcard->handles, (const void*)keys[x]);
+ WINPR_ASSERT(cur);
+
+ if (cur->unicode != unicode)
+ continue;
+ if (!unicode && (strcmp(cur->szReader.pc, szReader) != 0))
+ continue;
+ if (unicode && (_wcscmp(cur->szReader.pw, szReader) != 0))
+ continue;
+ hdl = cur;
+ break;
+ }
+ free(keys);
+ return hdl;
+}
+
+static SCardHandle* reader2handle(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ const void* szReader, BOOL unicode, DWORD dwShareMode,
+ SCARDHANDLE* phCard, DWORD dwPreferredProtocols,
+ LPDWORD pdwActiveProtocol)
+{
+ SCardHandle* hdl = NULL;
+
+ WINPR_ASSERT(phCard);
+
+ *phCard = 0;
+ if (Emulate_SCardIsValidContext(smartcard, hContext) != SCARD_S_SUCCESS)
+ return NULL;
+
+ hdl = scard_handle_new(smartcard, hContext, szReader, unicode);
+ if (hdl)
+ {
+ winpr_RAND(&hdl->card, sizeof(hdl->card));
+ hdl->dwActiveProtocol = SCARD_PROTOCOL_T1;
+ hdl->dwShareMode = dwShareMode;
+
+ if (!HashTable_Insert(smartcard->handles, (const void*)hdl->card, hdl))
+ {
+ scard_handle_free(hdl);
+ hdl = NULL;
+ }
+ else
+ {
+ if (pdwActiveProtocol)
+ {
+ if ((hdl->dwActiveProtocol & dwPreferredProtocols) == 0)
+ {
+ scard_handle_free(hdl);
+ hdl = NULL;
+ }
+ else
+ *pdwActiveProtocol = hdl->dwActiveProtocol;
+ }
+ if (hdl)
+ {
+ hdl->referencecount++;
+ *phCard = hdl->card;
+ }
+ }
+ }
+ WLog_Print(smartcard->log, smartcard->log_default_level, "{ %p }", (void*)*phCard);
+ return hdl;
+}
+
+LONG WINAPI Emulate_SCardConnectA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR szReader, DWORD dwShareMode, DWORD dwPreferredProtocols,
+ LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!phCard || !pdwActiveProtocol)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardConnectA { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ if (!reader2handle(smartcard, hContext, szReader, FALSE, dwShareMode, phCard,
+ dwPreferredProtocols, pdwActiveProtocol))
+ status = SCARD_E_NO_MEMORY;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardConnectA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardConnectW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR szReader, DWORD dwShareMode, DWORD dwPreferredProtocols,
+ LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!phCard || !pdwActiveProtocol)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardConnectW { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ if (!reader2handle(smartcard, hContext, szReader, TRUE, dwShareMode, phCard,
+ dwPreferredProtocols, pdwActiveProtocol))
+ status = SCARD_E_NO_MEMORY;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardConnectW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardReconnect(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwShareMode, DWORD dwPreferredProtocols,
+ DWORD dwInitialization, LPDWORD pdwActiveProtocol)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ if (!pdwActiveProtocol)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReconnect { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ // TODO: Implement
+ hdl->dwShareMode = dwShareMode;
+ hdl->transaction = FALSE;
+
+ *pdwActiveProtocol = hdl->dwActiveProtocol;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardReconnect } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardDisconnect(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwDisposition)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardDisconnect { hCard: %p",
+ (void*)hCard);
+
+ WINPR_UNUSED(dwDisposition); /* We just ignore this. All return values are static anyway */
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ hdl->referencecount--;
+ if (hdl->referencecount == 0)
+ HashTable_Remove(smartcard->handles, (const void*)hCard);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardDisconnect } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardBeginTransaction(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardBeginTransaction { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+ if (hdl->transaction)
+ status = SCARD_E_INVALID_VALUE;
+ else
+ hdl->transaction = TRUE;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardBeginTransaction } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardEndTransaction(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwDisposition)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardEndTransaction { hCard: %p",
+ (void*)hCard);
+
+ WINPR_UNUSED(dwDisposition); /* We just ignore this. All return values are static anyway */
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+ if (!hdl->transaction)
+ status = SCARD_E_NOT_TRANSACTED;
+ else
+ hdl->transaction = FALSE;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardEndTransaction } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardCancelTransaction(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardCancelTransaction { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+ if (!hdl->transaction)
+ status = SCARD_E_NOT_TRANSACTED;
+ else
+ hdl->transaction = FALSE;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardCancelTransaction } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardState(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ LPDWORD pdwState, LPDWORD pdwProtocol, LPBYTE pbAtr,
+ LPDWORD pcbAtrLen)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ if (!pdwState || !pdwProtocol)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardState { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ if (pdwState)
+ *pdwState = SCARD_SPECIFIC;
+ if (pdwProtocol)
+ *pdwProtocol = SCARD_PROTOCOL_T1;
+
+ if (pcbAtrLen)
+ {
+ SCardContext* ctx =
+ HashTable_GetItemValue(smartcard->contexts, (const void*)hdl->hContext);
+ WINPR_ASSERT(ctx);
+
+ for (size_t x = 0; x < MAX_EMULATED_READERS; x++)
+ {
+ const SCARD_READERSTATEA* readerA = &ctx->readerStateA[x];
+ const SCARD_READERSTATEW* readerW = &ctx->readerStateW[x];
+ if (hdl->unicode)
+ {
+ if (_wcscmp(readerW->szReader, hdl->szReader.pw) == 0)
+ {
+ *pcbAtrLen = scard_copy_strings(ctx, pbAtr, *pcbAtrLen, readerW->rgbAtr,
+ readerW->cbAtr);
+ }
+ }
+ else
+ {
+ if (strcmp(readerA->szReader, hdl->szReader.pc) == 0)
+ {
+ *pcbAtrLen = scard_copy_strings(ctx, pbAtr, *pcbAtrLen, readerA->rgbAtr,
+ readerA->cbAtr);
+ }
+ }
+ }
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardState } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardStatusA(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ LPSTR mszReaderNames, LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardStatusA { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* ctx = NULL;
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)hdl->hContext);
+ WINPR_ASSERT(ctx);
+
+ if (pcchReaderLen)
+ *pcchReaderLen =
+ scard_copy_strings(ctx, mszReaderNames, *pcchReaderLen, hdl->szReader.pc,
+ (UINT32)strlen(hdl->szReader.pc) + 2);
+
+ if (pdwState)
+ *pdwState = SCARD_SPECIFIC;
+ if (pdwProtocol)
+ *pdwProtocol = SCARD_PROTOCOL_T1;
+
+ if (pcbAtrLen)
+ {
+ for (size_t x = 0; x < MAX_EMULATED_READERS; x++)
+ {
+ const SCARD_READERSTATEA* reader = &ctx->readerStateA[x];
+ if (strcmp(reader->szReader, hdl->szReader.pc) == 0)
+ {
+ *pcbAtrLen =
+ scard_copy_strings(ctx, pbAtr, *pcbAtrLen, reader->rgbAtr, reader->cbAtr);
+ }
+ }
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardStatusA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardStatusW(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ LPWSTR mszReaderNames, LPDWORD pcchReaderLen, LPDWORD pdwState,
+ LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardStatusW { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* ctx = NULL;
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)hdl->hContext);
+ WINPR_ASSERT(ctx);
+
+ if (pcchReaderLen)
+ *pcchReaderLen =
+ scard_copy_strings(ctx, mszReaderNames, *pcchReaderLen, hdl->szReader.pw,
+ (UINT32)(_wcslen(hdl->szReader.pw) + 2) * sizeof(WCHAR)) /
+ sizeof(WCHAR);
+
+ if (pdwState)
+ *pdwState = SCARD_SPECIFIC;
+ if (pdwProtocol)
+ *pdwProtocol = SCARD_PROTOCOL_T1;
+
+ if (pcbAtrLen)
+ {
+ for (size_t x = 0; x < MAX_EMULATED_READERS; x++)
+ {
+ const SCARD_READERSTATEW* reader = &ctx->readerStateW[x];
+ if (_wcscmp(reader->szReader, hdl->szReader.pw) == 0)
+ *pcbAtrLen =
+ scard_copy_strings(ctx, pbAtr, *pcbAtrLen, reader->rgbAtr, reader->cbAtr);
+ }
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardStatusW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardTransmit(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ LPCSCARD_IO_REQUEST pioSendPci, LPCBYTE pbSendBuffer,
+ DWORD cbSendLength, LPSCARD_IO_REQUEST pioRecvPci,
+ LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ if (!pioSendPci || !pbSendBuffer || !pbRecvBuffer || !pcbRecvLength)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardTransmit { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ BYTE* response = NULL;
+ DWORD responseSize = 0;
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ hdl->transmitcount++;
+
+ if (!vgids_process_apdu(hdl->vgids, pbSendBuffer, cbSendLength, &response, &responseSize))
+ status = SCARD_E_NO_SMARTCARD;
+ else
+ {
+ SCardContext* ctx =
+ HashTable_GetItemValue(smartcard->contexts, (const void*)hdl->hContext);
+ WINPR_ASSERT(ctx);
+
+ *pcbRecvLength =
+ scard_copy_strings(ctx, pbRecvBuffer, *pcbRecvLength, response, responseSize);
+ free(response);
+
+ /* Required */
+ if (pioRecvPci)
+ pioRecvPci->dwProtocol = hdl->dwActiveProtocol;
+ }
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardTransmit } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetTransmitCount(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ LPDWORD pcTransmitCount)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ if (!pcTransmitCount)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetTransmitCount { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardHandle* hdl = HashTable_GetItemValue(smartcard->handles, (const void*)hCard);
+ WINPR_ASSERT(hdl);
+
+ *pcTransmitCount = hdl->transmitcount;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetTransmitCount } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardControl(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwControlCode, LPCVOID lpInBuffer, DWORD cbInBufferSize,
+ LPVOID lpOutBuffer, DWORD cbOutBufferSize, LPDWORD lpBytesReturned)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardControl { hCard: %p",
+ (void*)hCard);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ WINPR_UNUSED(dwControlCode);
+ WINPR_UNUSED(lpInBuffer);
+ WINPR_UNUSED(cbInBufferSize);
+ WINPR_UNUSED(lpOutBuffer);
+ WINPR_UNUSED(cbOutBufferSize);
+ WINPR_UNUSED(lpBytesReturned);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardControl } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetAttrib(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwAttrId, LPBYTE pbAttr, LPDWORD pcbAttrLen)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetAttrib { hCard: %p",
+ (void*)hCard);
+
+ WINPR_UNUSED(dwAttrId);
+ WINPR_UNUSED(pbAttr);
+ WINPR_UNUSED(pcbAttrLen);
+
+ /* Not required, return not supported */
+ status = SCARD_F_INTERNAL_ERROR;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetAttrib } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardSetAttrib(SmartcardEmulationContext* smartcard, SCARDHANDLE hCard,
+ DWORD dwAttrId, LPCBYTE pbAttr, DWORD cbAttrLen)
+{
+ LONG status = scard_handle_valid(smartcard, hCard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardSetAttrib { hCard: %p",
+ (void*)hCard);
+
+ WINPR_UNUSED(dwAttrId);
+ WINPR_UNUSED(pbAttr);
+ WINPR_UNUSED(cbAttrLen);
+
+ /* Not required, return not supported */
+ status = SCARD_F_INTERNAL_ERROR;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardSetAttrib } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardUIDlgSelectCardA(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEA_EX pDlgStruc)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardUIDlgSelectCardA {");
+
+ WINPR_UNUSED(pDlgStruc);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardUIDlgSelectCardA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardUIDlgSelectCardW(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEW_EX pDlgStruc)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardUIDlgSelectCardW {");
+
+ WINPR_UNUSED(pDlgStruc);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardUIDlgSelectCardW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_GetOpenCardNameA(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEA pDlgStruc)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "GetOpenCardNameA {");
+
+ WINPR_UNUSED(pDlgStruc);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "GetOpenCardNameA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_GetOpenCardNameW(SmartcardEmulationContext* smartcard,
+ LPOPENCARDNAMEW pDlgStruc)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "GetOpenCardNameW {");
+
+ WINPR_UNUSED(pDlgStruc);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "GetOpenCardNameW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardDlgExtendedError(SmartcardEmulationContext* smartcard)
+{
+ LONG status = 0;
+
+ WINPR_ASSERT(smartcard);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardDlgExtendedError {");
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardDlgExtendedError } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardReadCacheA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ UUID* CardIdentifier, DWORD FreshnessCounter, LPSTR LookupName,
+ PBYTE Data, DWORD* DataLen)
+{
+ DWORD count = 0;
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!CardIdentifier || !DataLen)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReadCacheA { hContext: %p",
+ (void*)hContext);
+
+ if (DataLen)
+ {
+ count = *DataLen;
+ *DataLen = 0;
+ }
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardCacheItem* data = NULL;
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ data = HashTable_GetItemValue(value->cacheA, LookupName);
+ if (!data)
+ status = SCARD_W_CACHE_ITEM_NOT_FOUND;
+ else if (data->freshness != FreshnessCounter)
+ status = SCARD_W_CACHE_ITEM_STALE;
+ else
+ *DataLen = scard_copy_strings(value, Data, count, data->data, data->size);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardReadCacheA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardReadCacheW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ UUID* CardIdentifier, DWORD FreshnessCounter, LPWSTR LookupName,
+ PBYTE Data, DWORD* DataLen)
+{
+ DWORD count = 0;
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!CardIdentifier || !DataLen)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardReadCacheW { hContext: %p",
+ (void*)hContext);
+
+ if (DataLen)
+ {
+ count = *DataLen;
+ *DataLen = 0;
+ }
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardCacheItem* data = NULL;
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ data = HashTable_GetItemValue(value->cacheW, LookupName);
+ if (!data)
+ status = SCARD_W_CACHE_ITEM_NOT_FOUND;
+ else if (data->freshness != FreshnessCounter)
+ status = SCARD_W_CACHE_ITEM_STALE;
+ else
+ *DataLen = scard_copy_strings(value, Data, count, data->data, data->size);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardReadCacheW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+static LONG insert_data(wHashTable* table, DWORD FreshnessCounter, const void* key,
+ const PBYTE Data, DWORD DataLen)
+{
+ BOOL rc = 0;
+ SCardCacheItem* item = NULL;
+
+ WINPR_ASSERT(table);
+ WINPR_ASSERT(key);
+
+ if (DataLen > MAX_CACHE_ITEM_SIZE)
+ return SCARD_W_CACHE_ITEM_TOO_BIG;
+
+ if (HashTable_Count(table) > MAX_CACHE_ITEM_VALUES)
+ return SCARD_E_WRITE_TOO_MANY;
+
+ item = HashTable_GetItemValue(table, key);
+ if (!item)
+ {
+ item = calloc(1, sizeof(SCardCacheItem));
+ if (!item)
+ return SCARD_E_NO_MEMORY;
+
+ rc = HashTable_Insert(table, key, item);
+ if (!rc)
+ {
+ free(item);
+ return SCARD_E_NO_MEMORY;
+ }
+ }
+
+ if (item->freshness > FreshnessCounter)
+ return SCARD_W_CACHE_ITEM_STALE;
+ item->freshness = FreshnessCounter;
+ item->size = DataLen;
+ memcpy(item->data, Data, DataLen);
+
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of item
+ return SCARD_S_SUCCESS;
+}
+
+LONG WINAPI Emulate_SCardWriteCacheA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ UUID* CardIdentifier, DWORD FreshnessCounter, LPSTR LookupName,
+ PBYTE Data, DWORD DataLen)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!CardIdentifier)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardWriteCacheA { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ status = insert_data(value->cacheA, FreshnessCounter, LookupName, Data, DataLen);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardWriteCacheA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardWriteCacheW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ UUID* CardIdentifier, DWORD FreshnessCounter,
+ LPWSTR LookupName, PBYTE Data, DWORD DataLen)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!CardIdentifier)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardWriteCacheW { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* value = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(value); /* Must be valid after Emulate_SCardIsValidContext */
+
+ status = insert_data(value->cacheW, FreshnessCounter, LookupName, Data, DataLen);
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardWriteCacheW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetReaderIconA(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCSTR szReaderName, LPBYTE pbIcon, LPDWORD pcbIcon)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!szReaderName || !pcbIcon)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetReaderIconA { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(ctx);
+
+ if (pbIcon)
+ *pcbIcon = scard_copy_strings(ctx, pbIcon, *pcbIcon, resources_FreeRDP_ico,
+ resources_FreeRDP_ico_len);
+ else
+ *pcbIcon = resources_FreeRDP_ico_len;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderIconA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetReaderIconW(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ LPCWSTR szReaderName, LPBYTE pbIcon, LPDWORD pcbIcon)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!szReaderName || !pcbIcon)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetReaderIconW { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ SCardContext* ctx = HashTable_GetItemValue(smartcard->contexts, (const void*)hContext);
+ WINPR_ASSERT(ctx);
+
+ if (pbIcon)
+ *pcbIcon = scard_copy_strings(ctx, pbIcon, *pcbIcon, resources_FreeRDP_ico,
+ resources_FreeRDP_ico_len);
+ else
+ *pcbIcon = resources_FreeRDP_ico_len;
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderIconW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetDeviceTypeIdA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPDWORD pdwDeviceTypeId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!pdwDeviceTypeId)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetDeviceTypeIdA { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ *pdwDeviceTypeId = SCARD_READER_TYPE_USB; // SCARD_READER_TYPE_TPM
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetDeviceTypeIdA } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetDeviceTypeIdW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPDWORD pdwDeviceTypeId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (!pdwDeviceTypeId)
+ status = SCARD_E_INVALID_PARAMETER;
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardGetDeviceTypeIdW { hContext: %p",
+ (void*)hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ {
+ *pdwDeviceTypeId = SCARD_READER_TYPE_USB; // SCARD_READER_TYPE_TPM
+ }
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetDeviceTypeIdW } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status),
+ status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetReaderDeviceInstanceIdA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCSTR szReaderName,
+ LPSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_a(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderDeviceInstanceIdA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(pcchDeviceInstanceId);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderDeviceInstanceIdA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardGetReaderDeviceInstanceIdW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext, LPCWSTR szReaderName,
+ LPWSTR szDeviceInstanceId,
+ LPDWORD pcchDeviceInstanceId)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ if (status == SCARD_S_SUCCESS)
+ status = scard_reader_name_valid_w(smartcard, hContext, szReaderName);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderDeviceInstanceIdW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(pcchDeviceInstanceId);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardGetReaderDeviceInstanceIdW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReadersWithDeviceInstanceIdA(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCSTR szDeviceInstanceId,
+ LPSTR mszReaders, LPDWORD pcchReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersWithDeviceInstanceIdA { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(mszReaders);
+ WINPR_UNUSED(pcchReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersWithDeviceInstanceIdA } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardListReadersWithDeviceInstanceIdW(SmartcardEmulationContext* smartcard,
+ SCARDCONTEXT hContext,
+ LPCWSTR szDeviceInstanceId,
+ LPWSTR mszReaders, LPDWORD pcchReaders)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersWithDeviceInstanceIdW { hContext: %p", (void*)hContext);
+
+ WINPR_UNUSED(szDeviceInstanceId);
+ WINPR_UNUSED(mszReaders);
+ WINPR_UNUSED(pcchReaders);
+
+ /* Not required, return not supported */
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardListReadersWithDeviceInstanceIdW } status: %s (0x%08" PRIX32 ")",
+ SCardGetErrorString(status), status);
+
+ return status;
+}
+
+LONG WINAPI Emulate_SCardAudit(SmartcardEmulationContext* smartcard, SCARDCONTEXT hContext,
+ DWORD dwEvent)
+{
+ LONG status = Emulate_SCardIsValidContext(smartcard, hContext);
+
+ WINPR_UNUSED(dwEvent);
+
+ WLog_Print(smartcard->log, smartcard->log_default_level, "SCardAudit { hContext: %p",
+ (void*)hContext);
+
+ // TODO: Implement
+ status = SCARD_E_UNSUPPORTED_FEATURE;
+
+ WLog_Print(smartcard->log, smartcard->log_default_level,
+ "SCardAudit } status: %s (0x%08" PRIX32 ")", SCardGetErrorString(status), status);
+
+ return status;
+}
+
+static BOOL context_equals(const void* pva, const void* pvb)
+{
+ const SCARDCONTEXT a = (const SCARDCONTEXT)pva;
+ const SCARDCONTEXT b = (const SCARDCONTEXT)pvb;
+ if (!a && !b)
+ return TRUE;
+ if (!a || !b)
+ return FALSE;
+
+ return a == b;
+}
+
+static BOOL handle_equals(const void* pva, const void* pvb)
+{
+ const SCARDHANDLE a = (const SCARDHANDLE)pva;
+ const SCARDHANDLE b = (const SCARDHANDLE)pvb;
+ if (!a && !b)
+ return TRUE;
+ if (!a || !b)
+ return FALSE;
+
+ return a == b;
+}
+
+SmartcardEmulationContext* Emulate_New(const rdpSettings* settings)
+{
+ SmartcardEmulationContext* smartcard = NULL;
+
+ WINPR_ASSERT(settings);
+
+ smartcard = calloc(1, sizeof(SmartcardEmulationContext));
+ if (!smartcard)
+ goto fail;
+
+ smartcard->settings = settings;
+ smartcard->log = WLog_Get("EmulateSCard");
+ if (!smartcard->log)
+ goto fail;
+ smartcard->log_default_level = WLOG_TRACE;
+
+ smartcard->contexts = HashTable_New(FALSE);
+ if (!smartcard->contexts)
+ goto fail;
+ else
+ {
+ wObject* obj = HashTable_KeyObject(smartcard->contexts);
+ WINPR_ASSERT(obj);
+ obj->fnObjectEquals = context_equals;
+ }
+ if (!smartcard->contexts)
+ goto fail;
+ else
+ {
+ wObject* obj = HashTable_ValueObject(smartcard->contexts);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = scard_context_free;
+ }
+
+ smartcard->handles = HashTable_New(FALSE);
+ if (!smartcard->handles)
+ goto fail;
+ else
+ {
+ wObject* obj = HashTable_KeyObject(smartcard->handles);
+ WINPR_ASSERT(obj);
+ obj->fnObjectEquals = handle_equals;
+ }
+ if (!smartcard->handles)
+ goto fail;
+ else
+ {
+ wObject* obj = HashTable_ValueObject(smartcard->handles);
+ WINPR_ASSERT(obj);
+ obj->fnObjectFree = scard_handle_free;
+ }
+
+ return smartcard;
+
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ Emulate_Free(smartcard);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void Emulate_Free(SmartcardEmulationContext* context)
+{
+ if (!context)
+ return;
+
+ HashTable_Free(context->handles);
+ HashTable_Free(context->contexts);
+ free(context);
+}
+
+BOOL Emulate_IsConfigured(SmartcardEmulationContext* context)
+{
+ BOOL rc = FALSE;
+ vgidsContext* vgids = NULL;
+ const char* pem = NULL;
+ const char* key = NULL;
+ const char* pin = NULL;
+
+ WINPR_ASSERT(context);
+
+ pem = freerdp_settings_get_string(context->settings, FreeRDP_SmartcardCertificate);
+ key = freerdp_settings_get_string(context->settings, FreeRDP_SmartcardPrivateKey);
+ pin = freerdp_settings_get_string(context->settings, FreeRDP_Password);
+
+ /* Cache result only, if no initialization arguments changed. */
+ if ((context->pem == pem) && (context->key == key) && (context->pin == pin))
+ return context->configured;
+
+ context->pem = pem;
+ context->key = key;
+ context->pin = pin;
+
+ vgids = vgids_new();
+ if (vgids)
+ rc = vgids_init(vgids, context->pem, context->key, context->pin);
+ vgids_free(vgids);
+
+ context->configured = rc;
+ return rc;
+}
diff --git a/libfreerdp/emu/scard/smartcard_virtual_gids.c b/libfreerdp/emu/scard/smartcard_virtual_gids.c
new file mode 100644
index 0000000..3d4dda3
--- /dev/null
+++ b/libfreerdp/emu/scard/smartcard_virtual_gids.c
@@ -0,0 +1,1632 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Virtual GIDS implementation
+ *
+ * Copyright 2021 Martin Fleisz <martin.fleisz@thincast.com>
+ * Copyright 2023 Armin Novak <anovak@thincast.com>
+ * Copyright 2021,2023 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <freerdp/config.h>
+
+#include <winpr/wlog.h>
+#include <winpr/stream.h>
+#include <winpr/collections.h>
+
+#include <freerdp/crypto/crypto.h>
+
+#include <zlib.h>
+
+#include "../../crypto/certificate.h"
+#include "../../crypto/privatekey.h"
+#include "smartcard_virtual_gids.h"
+
+#define TAG CHANNELS_TAG("smartcard.vgids")
+
+#define VGIDS_EFID_MASTER 0xA000
+#define VGIDS_EFID_COMMON 0xA010
+#define VGIDS_EFID_CARDCF VGIDS_EFID_COMMON
+#define VGIDS_EFID_CARDAPPS VGIDS_EFID_COMMON
+#define VGIDS_EFID_CMAPFILE VGIDS_EFID_COMMON
+#define VGIDS_EFID_CARDID 0xA012
+#define VGIDS_EFID_KXC00 VGIDS_EFID_COMMON
+#define VGIDS_EFID_CURRENTDF 0x3FFF
+
+#define VGIDS_DO_FILESYSTEMTABLE 0xDF1F
+#define VGIDS_DO_KEYMAP 0xDF20
+#define VGIDS_DO_CARDID 0xDF20
+#define VGIDS_DO_CARDAPPS 0xDF21
+#define VGIDS_DO_CARDCF 0xDF22
+#define VGIDS_DO_CMAPFILE 0xDF23
+#define VGIDS_DO_KXC00 0xDF24
+
+#define VGIDS_CARDID_SIZE 16
+#define VGIDS_MAX_PIN_SIZE 127
+
+#define VGIDS_DEFAULT_RETRY_COUNTER 3
+
+#define VGIDS_KEY_TYPE_KEYEXCHANGE 0x9A
+#define VGIDS_KEY_TYPE_SIGNATURE 0x9C
+
+#define VGIDS_ALGID_RSA_1024 0x06
+#define VGIDS_ALGID_RSA_2048 0x07
+#define VGIDS_ALGID_RSA_3072 0x08
+#define VGIDS_ALGID_RSA_4096 0x09
+
+#define VGIDS_SE_CRT_AUTH 0xA4
+#define VGIDS_SE_CRT_SIGN 0xB6
+#define VGIDS_SE_CRT_CONF 0xB8
+
+#define VGIDS_SE_ALGOID_CT_PAD_PKCS1 0x40
+#define VGIDS_SE_ALGOID_CT_PAD_OAEP 0x80
+#define VGIDS_SE_ALGOID_CT_RSA_1024 0x06
+#define VGIDS_SE_ALGOID_CT_RSA_2048 0x07
+#define VGIDS_SE_ALGOID_CT_RSA_3072 0x08
+#define VGIDS_SE_ALGOID_CT_RSA_4096 0x09
+
+#define VGIDS_SE_ALGOID_DST_PAD_PKCS1 0x40
+#define VGIDS_SE_ALGOID_DST_RSA_1024 0x06
+#define VGIDS_SE_ALGOID_DST_RSA_2048 0x07
+#define VGIDS_SE_ALGOID_DST_RSA_3072 0x08
+#define VGIDS_SE_ALGOID_DST_RSA_4096 0x09
+#define VGIDS_SE_ALGOID_DST_ECDSA_P192 0x0A
+#define VGIDS_SE_ALGOID_DST_ECDSA_P224 0x0B
+#define VGIDS_SE_ALGOID_DST_ECDSA_P256 0x0C
+#define VGIDS_SE_ALGOID_DST_ECDSA_P384 0x0D
+#define VGIDS_SE_ALGOID_DST_ECDSA_P512 0x0E
+
+#define VGIDS_DEFAULT_KEY_REF 0x81
+
+#define ISO_INS_SELECT 0xA4
+#define ISO_INS_GETDATA 0xCB
+#define ISO_INS_GETRESPONSE 0xC0
+#define ISO_INS_MSE 0x22
+#define ISO_INS_PSO 0x2A
+#define ISO_INS_VERIFY 0x20
+
+#define ISO_STATUS_MORE_DATA 0x6100
+#define ISO_STATUS_VERIFYFAILED 0x6300
+#define ISO_STATUS_WRONGLC 0x6700
+#define ISO_STATUS_COMMANDNOTALLOWED 0x6900
+#define ISO_STATUS_SECURITYSTATUSNOTSATISFIED 0x6982
+#define ISO_STATUS_AUTHMETHODBLOCKED 0x6983
+#define ISO_STATUS_INVALIDCOMMANDDATA 0x6A80
+#define ISO_STATUS_FILENOTFOUND 0x6A82
+#define ISO_STATUS_INVALIDP1P2 0x6A86
+#define ISO_STATUS_INVALIDLC 0x6A87
+#define ISO_STATUS_REFERENCEDATANOTFOUND 0x6A88
+#define ISO_STATUS_SUCCESS 0x9000
+
+#define ISO_AID_MAX_SIZE 16
+
+#define ISO_FID_MF 0x3F00
+
+struct vgids_ef
+{
+ UINT16 id;
+ UINT16 dirID;
+ wStream* data;
+};
+typedef struct vgids_ef vgidsEF;
+
+struct vgids_se
+{
+ BYTE crt; /* control reference template tag */
+ BYTE algoId; /* Algorithm ID */
+ BYTE keyRef; /* Key reference */
+};
+typedef struct vgids_se vgidsSE;
+
+struct vgids_context
+{
+ UINT16 currentDF;
+ char* pin;
+ UINT16 curRetryCounter;
+ UINT16 retryCounter;
+ wStream* commandData;
+ wStream* responseData;
+ BOOL pinVerified;
+ vgidsSE currentSE;
+
+ rdpCertificate* certificate;
+ rdpPrivateKey* privateKey;
+
+ wArrayList* files;
+};
+
+/* PKCS 1.5 DER encoded digest information */
+#define VGIDS_MAX_DIGEST_INFO 7
+
+static const BYTE g_PKCS1_SHA1[] = { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
+ 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
+static const BYTE g_PKCS1_SHA224[] = { 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+ 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c };
+static const BYTE g_PKCS1_SHA256[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+ 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };
+static const BYTE g_PKCS1_SHA384[] = { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+ 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30 };
+static const BYTE g_PKCS1_SHA512[] = { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+ 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 };
+static const BYTE g_PKCS1_SHA512_224[] = { 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02,
+ 0x05, 0x05, 0x00, 0x04, 0x1c };
+static const BYTE g_PKCS1_SHA512_256[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02,
+ 0x06, 0x05, 0x00, 0x04, 0x20 };
+
+/* Helper struct to map PKCS1.5 digest info to OpenSSL EVP_MD */
+struct vgids_digest_info_map
+{
+ const BYTE* info;
+ size_t infoSize;
+ const EVP_MD* digest;
+};
+typedef struct vgids_digest_info_map vgidsDigestInfoMap;
+
+/* MS GIDS AID */
+/* xx: Used by the Windows smart card framework for the GIDS version number. This byte must be set
+ * to the GIDS specification revision number which is either 0x01 or 0x02.
+ * yy: Reserved for use by the card application (set to 01)
+ */
+static const BYTE g_MsGidsAID[] = {
+ 0xA0, 0x00, 0x00, 0x03, 0x97, 0x42, 0x54, 0x46, 0x59, 0x02, 0x01
+};
+
+/* GIDS APP File Control Parameter:
+ FD-Byte (82): 38 (not shareable-DF)
+ Sec Attr (8C): 03 30 30 Create/Delete File(03) Ext/User-Auth (30)
+*/
+static const BYTE g_GidsAppFCP[] = { 0x62, 0x08, 0x82, 0x01, 0x38, 0x8C, 0x03, 0x03, 0x30, 0x30 };
+/* GIDS APP File Control Information:
+ AppID (4F, Len 0B): A0 00 00 03 97 42 54 46 59 02 01
+ Discretionary DOs (73, Len 03): 40 01 C0
+ Supported Auth Protocols (40, Len 01): C0 Mutual/External-Auth
+ */
+static const BYTE g_GidsAppFCI[] = { 0x61, 0x12, 0x4F, 0x0B, 0xA0, 0x00, 0x00, 0x03, 0x97, 0x42,
+ 0x54, 0x46, 0x59, 0x02, 0x01, 0x73, 0x03, 0x40, 0x01, 0xC0 };
+
+/*
+typedef struct
+{
+ BYTE bVersion; // Cache version
+ BYTE bPinsFreshness; // Card PIN
+ WORD wContainersFreshness;
+ WORD wFilesFreshness;
+} CARD_CACHE_FILE_FORMAT, *PCARD_CACHE_FILE_FORMAT; */
+static const BYTE g_CardCFContents[] = { 0x00, 0x00, 0x01, 0x00, 0x04, 0x00 };
+
+/* {‘mscp’,0,0,0,0} */
+static const BYTE g_CardAppsContents[] = { 0x6d, 0x73, 0x63, 0x70, 0x00, 0x00, 0x00, 0x00 };
+
+#pragma pack(push, 1)
+
+/* Type: CONTAINER_MAP_RECORD (taken from Windows Smart Card Minidriver Specification)
+
+ This structure describes the format of the Base CSP's
+ container map file, stored on the card. This is wellknown
+ logical file wszCONTAINER_MAP_FILE. The file consists of
+ zero or more of these records. */
+#define MAX_CONTAINER_NAME_LEN 39
+
+/* This flag is set in the CONTAINER_MAP_RECORD bFlags
+ member if the corresponding container is valid and currently
+ exists on the card. // If the container is deleted, its
+ bFlags field must be cleared. */
+#define CONTAINER_MAP_VALID_CONTAINER 1
+
+/* This flag is set in the CONTAINER_MAP_RECORD bFlags
+ member if the corresponding container is the default
+ container on the card. */
+#define CONTAINER_MAP_DEFAULT_CONTAINER 2
+
+struct vgids_container_map_entry
+{
+ WCHAR wszGuid[MAX_CONTAINER_NAME_LEN + 1];
+ BYTE bFlags;
+ BYTE bReserved;
+ WORD wSigKeySizeBits;
+ WORD wKeyExchangeKeySizeBits;
+};
+typedef struct vgids_container_map_entry vgidsContainerMapEntry;
+
+struct vgids_filesys_table_entry
+{
+ char directory[9];
+ char filename[9];
+ UINT16 pad0;
+ UINT16 dataObjectIdentifier;
+ UINT16 pad1;
+ UINT16 fileIdentifier;
+ UINT16 unknown;
+};
+typedef struct vgids_filesys_table_entry vgidsFilesysTableEntry;
+
+struct vgids_keymap_record
+{
+ UINT32 state;
+ BYTE algid;
+ BYTE keytype;
+ UINT16 keyref;
+ UINT16 unknownWithFFFF;
+ UINT16 unknownWith0000;
+};
+typedef struct vgids_keymap_record vgidsKeymapRecord;
+
+#pragma pack(pop)
+
+static void vgids_ef_free(void* ptr);
+
+static vgidsEF* vgids_ef_new(vgidsContext* ctx, USHORT id)
+{
+ vgidsEF* ef = calloc(1, sizeof(vgidsEF));
+
+ ef->id = id;
+ ef->data = Stream_New(NULL, 1024);
+ if (!ef->data)
+ {
+ WLog_ERR(TAG, "Failed to create file data stream");
+ goto create_failed;
+ }
+ Stream_SetLength(ef->data, 0);
+
+ if (!ArrayList_Append(ctx->files, ef))
+ {
+ WLog_ERR(TAG, "Failed to add new ef to file list");
+ goto create_failed;
+ }
+
+ return ef;
+
+create_failed:
+ vgids_ef_free(ef);
+ return NULL;
+}
+
+static BOOL vgids_write_tlv(wStream* s, UINT16 tag, const void* data, DWORD dataSize)
+{
+ /* A maximum of 5 additional bytes is needed */
+ if (!Stream_EnsureRemainingCapacity(s, dataSize + 5))
+ {
+ WLog_ERR(TAG, "Failed to ensure capacity of DO stream");
+ return FALSE;
+ }
+
+ /* BER encoding: If the most-significant bit is set (0x80) the length is encoded in the
+ * remaining bits. So lengths < 128 bytes can be set directly, all others are encoded */
+ if (tag > 0xFF)
+ Stream_Write_UINT16_BE(s, tag);
+ else
+ Stream_Write_UINT8(s, (BYTE)tag);
+ if (dataSize < 128)
+ {
+ Stream_Write_UINT8(s, (BYTE)dataSize);
+ }
+ else if (dataSize < 256)
+ {
+ Stream_Write_UINT8(s, 0x81);
+ Stream_Write_UINT8(s, (BYTE)dataSize);
+ }
+ else
+ {
+ Stream_Write_UINT8(s, 0x82);
+ Stream_Write_UINT16_BE(s, (UINT16)dataSize);
+ }
+ Stream_Write(s, data, dataSize);
+ Stream_SealLength(s);
+ return TRUE;
+}
+
+static BOOL vgids_ef_write_do(vgidsEF* ef, UINT16 doID, const void* data, DWORD dataSize)
+{
+ /* Write DO to end of file: 2-Byte ID, 1-Byte Len, Data */
+ return vgids_write_tlv(ef->data, doID, data, dataSize);
+}
+
+static BOOL vgids_ef_read_do(vgidsEF* ef, UINT16 doID, BYTE** data, DWORD* dataSize)
+{
+ /* Read the given DO from the file: 2-Byte ID, 1-Byte Len, Data */
+ if (!Stream_SetPosition(ef->data, 0))
+ {
+ WLog_ERR(TAG, "Failed to seek to front of file");
+ return FALSE;
+ }
+
+ /* Look for the requested DO */
+ while (Stream_GetRemainingLength(ef->data) > 3)
+ {
+ BYTE len = 0;
+ size_t curPos = 0;
+ UINT16 doSize = 0;
+ UINT16 nextDOID = 0;
+
+ curPos = Stream_GetPosition(ef->data);
+ Stream_Read_UINT16_BE(ef->data, nextDOID);
+ Stream_Read_UINT8(ef->data, len);
+ if ((len & 0x80))
+ {
+ BYTE lenSize = len & 0x7F;
+ if (!Stream_CheckAndLogRequiredLength(TAG, ef->data, lenSize))
+ return FALSE;
+
+ switch (lenSize)
+ {
+ case 1:
+ Stream_Read_UINT8(ef->data, doSize);
+ break;
+ case 2:
+ Stream_Read_UINT16_BE(ef->data, doSize);
+ break;
+ default:
+ WLog_ERR(TAG, "Unexpected tag length %" PRIu8, lenSize);
+ return FALSE;
+ }
+ }
+ else
+ doSize = len;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, ef->data, doSize))
+ return FALSE;
+
+ if (nextDOID == doID)
+ {
+ BYTE* outData = NULL;
+
+ /* Include Tag and length in result */
+ doSize += (UINT16)(Stream_GetPosition(ef->data) - curPos);
+ outData = malloc(doSize);
+ if (!outData)
+ {
+ WLog_ERR(TAG, "Failed to allocate output buffer");
+ return FALSE;
+ }
+
+ Stream_SetPosition(ef->data, curPos);
+ Stream_Read(ef->data, outData, doSize);
+ *data = outData;
+ *dataSize = doSize;
+ return TRUE;
+ }
+ else
+ {
+ /* Skip DO */
+ Stream_SafeSeek(ef->data, doSize);
+ }
+ }
+
+ return FALSE;
+}
+
+void vgids_ef_free(void* ptr)
+{
+ vgidsEF* ef = ptr;
+ if (ef)
+ {
+ Stream_Free(ef->data, TRUE);
+ free(ef);
+ }
+}
+
+static BOOL vgids_prepare_fstable(const vgidsFilesysTableEntry* fstable, DWORD numEntries,
+ BYTE** outData, DWORD* outDataSize)
+{
+ /* Filesystem table:
+ BYTE unkonwn: 0x01
+ Array of vgidsFilesysTableEntry
+ */
+ BYTE* data = malloc(sizeof(vgidsFilesysTableEntry) * numEntries + 1);
+ if (!data)
+ {
+ WLog_ERR(TAG, "Failed to allocate filesystem table data blob");
+ return FALSE;
+ }
+
+ *data = 0x01;
+ for (UINT32 i = 0; i < numEntries; ++i)
+ memcpy(data + 1 + (sizeof(vgidsFilesysTableEntry) * i), &fstable[i],
+ sizeof(vgidsFilesysTableEntry));
+
+ *outData = data;
+ *outDataSize = sizeof(vgidsFilesysTableEntry) * numEntries + 1;
+
+ return TRUE;
+}
+
+static BOOL vgids_prepare_certificate(const rdpCertificate* cert, BYTE** kxc, DWORD* kxcSize)
+{
+ /* Key exchange container:
+ UINT16 compression version: 0001
+ UINT16 source size
+ ZLIB compressed cert
+ */
+ uLongf destSize = 0;
+ wStream* s = NULL;
+ BYTE* comprData = NULL;
+
+ WINPR_ASSERT(cert);
+
+ size_t certSize = 0;
+ BYTE* certData = freerdp_certificate_get_der(cert, &certSize);
+ if (!certData || (certSize == 0))
+ {
+ WLog_ERR(TAG, "Failed to get certificate size");
+ goto handle_error;
+ }
+
+ comprData = malloc(certSize);
+ if (!comprData)
+ {
+ WLog_ERR(TAG, "Failed to allocate certificate buffer");
+ goto handle_error;
+ }
+
+ /* compress certificate data */
+ destSize = certSize;
+ if (compress(comprData, &destSize, certData, certSize) != Z_OK)
+ {
+ WLog_ERR(TAG, "Failed to compress certificate data");
+ goto handle_error;
+ }
+
+ /* Write container data */
+ s = Stream_New(NULL, destSize + 4);
+ Stream_Write_UINT16(s, 0x0001);
+ Stream_Write_UINT16(s, (UINT16)certSize);
+ Stream_Write(s, comprData, destSize);
+ Stream_SealLength(s);
+
+ *kxc = Stream_Buffer(s);
+ *kxcSize = (DWORD)Stream_Length(s);
+
+ Stream_Free(s, FALSE);
+ free(certData);
+ free(comprData);
+ return TRUE;
+
+handle_error:
+ Stream_Free(s, TRUE);
+ free(certData);
+ free(comprData);
+ return FALSE;
+}
+
+static int get_rsa_key_size(const rdpPrivateKey* privateKey)
+{
+ WINPR_ASSERT(privateKey);
+
+ return freerdp_key_get_bits(privateKey) / 8;
+}
+
+static BYTE vgids_get_algid(vgidsContext* p_Ctx)
+{
+ WINPR_ASSERT(p_Ctx);
+
+ switch (get_rsa_key_size(p_Ctx->privateKey))
+ {
+ case (1024 / 8):
+ return VGIDS_ALGID_RSA_1024;
+ case (2048 / 8):
+ return VGIDS_ALGID_RSA_2048;
+ case (3072 / 8):
+ return VGIDS_ALGID_RSA_3072;
+ case (4096 / 8):
+ return VGIDS_ALGID_RSA_4096;
+ default:
+ WLog_ERR(TAG, "Failed to determine algid for private key");
+ break;
+ }
+
+ return 0;
+}
+
+static BOOL vgids_prepare_keymap(vgidsContext* context, BYTE** outData, DWORD* outDataSize)
+{
+ /* Key map record table:
+ BYTE unkonwn (count?): 0x01
+ Array of vgidsKeymapRecord
+ */
+ BYTE* data = NULL;
+ vgidsKeymapRecord record = {
+ 1, /* state */
+ 0, /* algo */
+ VGIDS_KEY_TYPE_KEYEXCHANGE, /* keytpe */
+ (0xB000 | VGIDS_DEFAULT_KEY_REF), /* keyref */
+ 0xFFFF, /* unknown FFFF */
+ 0x0000 /* unknown 0000 */
+ };
+
+ /* Determine algo */
+ BYTE algid = vgids_get_algid(context);
+ if (algid == 0)
+ return FALSE;
+
+ data = malloc(sizeof(record) + 1);
+ if (!data)
+ {
+ WLog_ERR(TAG, "Failed to allocate filesystem table data blob");
+ return FALSE;
+ }
+
+ *data = 0x01;
+ record.algid = algid;
+ memcpy(data + 1, &record, sizeof(record));
+
+ *outData = data;
+ *outDataSize = sizeof(record) + 1;
+
+ return TRUE;
+}
+
+static BOOL vgids_parse_apdu_header(wStream* s, BYTE* cla, BYTE* ins, BYTE* p1, BYTE* p2, BYTE* lc,
+ BYTE* le)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return FALSE;
+
+ /* Read and verify APDU data */
+ if (cla)
+ Stream_Read_UINT8(s, *cla);
+ else
+ Stream_Seek(s, 1);
+ if (ins)
+ Stream_Read_UINT8(s, *ins);
+ else
+ Stream_Seek(s, 1);
+ if (p1)
+ Stream_Read_UINT8(s, *p1);
+ else
+ Stream_Seek(s, 1);
+ if (p2)
+ Stream_Read_UINT8(s, *p2);
+ else
+ Stream_Seek(s, 1);
+
+ /* If LC is requested - check remaining length and read as well */
+ if (lc)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+
+ Stream_Read_UINT8(s, *lc);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, *lc))
+ return FALSE;
+ }
+
+ /* read LE */
+ if (le)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ return FALSE;
+ Stream_Read_UINT8(s, *le);
+ }
+
+ return TRUE;
+}
+
+static BOOL vgids_create_response(UINT16 status, const BYTE* answer, DWORD answerSize,
+ BYTE** outData, DWORD* outDataSize)
+{
+ BYTE* out = malloc(answerSize + 2);
+ if (!out)
+ {
+ WLog_ERR(TAG, "Failed to allocate memory for response data");
+ return FALSE;
+ }
+
+ *outData = out;
+ if (answer)
+ {
+ memcpy(out, answer, answerSize);
+ out += answerSize;
+ }
+
+ *out = (BYTE)((status >> 8) & 0xFF);
+ *(out + 1) = (BYTE)(status & 0xFF);
+ *outDataSize = answerSize + 2;
+ return TRUE;
+}
+
+static BOOL vgids_read_do_fkt(void* data, size_t index, va_list ap)
+{
+ BYTE* response = NULL;
+ DWORD responseSize = 0;
+ vgidsEF* file = (vgidsEF*)data;
+ vgidsContext* context = va_arg(ap, vgidsContext*);
+ UINT16 efID = (UINT16)va_arg(ap, unsigned);
+ UINT16 doID = (UINT16)va_arg(ap, unsigned);
+ WINPR_UNUSED(index);
+
+ if (efID == 0x3FFF || efID == file->id)
+ {
+ /* If the DO was successfully read - abort file enum */
+ if (vgids_ef_read_do(file, doID, &response, &responseSize))
+ {
+ context->responseData = Stream_New(response, (size_t)responseSize);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void vgids_read_do(vgidsContext* context, UINT16 efID, UINT16 doID)
+{
+ ArrayList_ForEach(context->files, vgids_read_do_fkt, context, efID, doID);
+}
+
+static void vgids_reset_context_response(vgidsContext* context)
+{
+ Stream_Free(context->responseData, TRUE);
+ context->responseData = NULL;
+}
+
+static void vgids_reset_context_command_data(vgidsContext* context)
+{
+ Stream_Free(context->commandData, TRUE);
+ context->commandData = NULL;
+}
+
+static BOOL vgids_ins_select(vgidsContext* context, wStream* s, BYTE** response,
+ DWORD* responseSize)
+{
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE lc = 0;
+ DWORD resultDataSize = 0;
+ const BYTE* resultData = NULL;
+ UINT16 status = ISO_STATUS_SUCCESS;
+
+ /* The only select operations performed are either select by AID or select 3FFF (return
+ * information about the currently selected DF) */
+ if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, &lc, NULL))
+ return FALSE;
+
+ /* Check P1 for selection mode */
+ switch (p1)
+ {
+ /* Select by AID */
+ case 0x04:
+ {
+ /* read AID from APDU */
+ BYTE aid[ISO_AID_MAX_SIZE] = { 0 };
+ if (lc > ISO_AID_MAX_SIZE)
+ {
+ WLog_ERR(TAG, "The LC byte is greater than the maximum AID length");
+ status = ISO_STATUS_INVALIDLC;
+ break;
+ }
+
+ /* Check if we select MS GIDS App (only one we know) */
+ Stream_Read(s, aid, lc);
+ if (memcmp(aid, g_MsGidsAID, lc) != 0)
+ {
+ status = ISO_STATUS_FILENOTFOUND;
+ break;
+ }
+
+ /* Return FCI or FCP for MsGids App */
+ switch (p2)
+ {
+ /* Return FCI information */
+ case 0x00:
+ {
+ resultData = g_GidsAppFCI;
+ resultDataSize = sizeof(g_GidsAppFCI);
+ break;
+ }
+ /* Return FCP information */
+ case 0x04:
+ {
+ resultData = g_GidsAppFCP;
+ resultDataSize = sizeof(g_GidsAppFCP);
+ break;
+ }
+ default:
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+
+ if (resultData)
+ context->currentDF = ISO_FID_MF;
+ break;
+ }
+ /* Select by FID */
+ case 0x00:
+ {
+ /* read FID from APDU */
+ UINT16 fid = 0;
+ if (lc > 2)
+ {
+ WLog_ERR(TAG, "The LC byte for the file ID is greater than 2");
+ status = ISO_STATUS_INVALIDLC;
+ break;
+ }
+
+ Stream_Read_UINT16_BE(s, fid);
+ if (fid != VGIDS_EFID_CURRENTDF || context->currentDF == 0)
+ {
+ status = ISO_STATUS_FILENOTFOUND;
+ break;
+ }
+ break;
+ }
+ default:
+ {
+ /* P1 P2 combination not supported */
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+ }
+
+ return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
+}
+
+static UINT16 vgids_handle_chained_response(vgidsContext* context, const BYTE** response,
+ DWORD* responseSize)
+{
+ /* Cap to a maximum of 256 bytes and set status to more data */
+ UINT16 status = ISO_STATUS_SUCCESS;
+ DWORD remainingBytes = (DWORD)Stream_Length(context->responseData);
+ if (remainingBytes > 256)
+ {
+ status = ISO_STATUS_MORE_DATA;
+ remainingBytes = 256;
+ }
+
+ *response = Stream_Buffer(context->responseData);
+ *responseSize = remainingBytes;
+ Stream_Seek(context->responseData, remainingBytes);
+
+ /* Check if there are more than 256 bytes left or if we can already provide the remaining length
+ * in the status word */
+ remainingBytes = (DWORD)(Stream_Length(context->responseData) - remainingBytes);
+ if (remainingBytes < 256 && remainingBytes != 0)
+ status |= (remainingBytes & 0xFF);
+ return status;
+}
+
+static BOOL vgids_get_public_key(vgidsContext* context, UINT16 doTag)
+{
+ BOOL rc = FALSE;
+ wStream* pubKey = NULL;
+ wStream* response = NULL;
+
+ WINPR_ASSERT(context);
+
+ /* Get key components */
+ size_t nSize = 0;
+ size_t eSize = 0;
+
+ char* n = freerdp_certificate_get_param(context->certificate, FREERDP_CERT_RSA_N, &nSize);
+ char* e = freerdp_certificate_get_param(context->certificate, FREERDP_CERT_RSA_E, &eSize);
+
+ if (!n || !e)
+ goto handle_error;
+
+ pubKey = Stream_New(NULL, nSize + eSize + 0x10);
+ if (!pubKey)
+ {
+ WLog_ERR(TAG, "Failed to allocate public key stream");
+ goto handle_error;
+ }
+
+ response = Stream_New(NULL, Stream_Capacity(pubKey) + 0x10);
+ if (!response)
+ {
+ WLog_ERR(TAG, "Failed to allocate response stream");
+ goto handle_error;
+ }
+
+ /* write modulus and exponent DOs */
+ if (!vgids_write_tlv(pubKey, 0x81, n, nSize))
+ goto handle_error;
+
+ if (!vgids_write_tlv(pubKey, 0x82, e, eSize))
+ goto handle_error;
+
+ /* write ISO public key template */
+ if (!vgids_write_tlv(response, doTag, Stream_Buffer(pubKey), (DWORD)Stream_Length(pubKey)))
+ goto handle_error;
+
+ /* set response data */
+ Stream_SetPosition(response, 0);
+ context->responseData = response;
+
+ rc = TRUE;
+handle_error:
+ free(n);
+ free(e);
+ Stream_Free(pubKey, TRUE);
+ if (!rc)
+ Stream_Free(response, TRUE);
+ return rc;
+}
+
+static BOOL vgids_ins_getdata(vgidsContext* context, wStream* s, BYTE** response,
+ DWORD* responseSize)
+{
+ UINT16 doId = 0;
+ UINT16 fileId = 0;
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE lc = 0;
+ DWORD resultDataSize = 0;
+ const BYTE* resultData = NULL;
+ UINT16 status = ISO_STATUS_SUCCESS;
+
+ /* GetData is called a lot!
+ - To retrieve DOs from files
+ - To retrieve public key information
+ */
+ if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, &lc, NULL))
+ return FALSE;
+
+ /* free any previous queried data */
+ vgids_reset_context_response(context);
+
+ /* build up file identifier */
+ fileId = ((UINT16)p1 << 8) | p2;
+
+ /* Do we have a DO reference? */
+ switch (lc)
+ {
+ case 4:
+ {
+ BYTE tag = 0;
+ BYTE length = 0;
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ if (tag != 0x5C && length != 0x02)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ Stream_Read_UINT16_BE(s, doId);
+ vgids_read_do(context, fileId, doId);
+ break;
+ }
+ case 0xA:
+ {
+ UINT16 pubKeyDO = 0;
+ BYTE tag = 0;
+ BYTE length = 0;
+ BYTE keyRef = 0;
+
+ /* We want to retrieve the public key? */
+ if (p1 != 0x3F && p2 != 0xFF)
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+
+ /* read parent tag/length */
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ if (tag != 0x70 || length != 0x08)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ /* read key reference TLV */
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ Stream_Read_UINT8(s, keyRef);
+ if (tag != 0x84 || length != 0x01 || keyRef != VGIDS_DEFAULT_KEY_REF)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ /* read key value template TLV */
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ if (tag != 0xA5 || length != 0x03)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ Stream_Read_UINT16_BE(s, pubKeyDO);
+ Stream_Read_UINT8(s, length);
+ if (pubKeyDO != 0x7F49 || length != 0x80)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ {
+ status = ISO_STATUS_INVALIDLC;
+ break;
+ }
+
+ /* Return public key value */
+ vgids_get_public_key(context, pubKeyDO);
+ break;
+ }
+ default:
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ break;
+ }
+
+ /* If we have response data, make it ready for return */
+ if (context->responseData)
+ status = vgids_handle_chained_response(context, &resultData, &resultDataSize);
+ else if (status == ISO_STATUS_SUCCESS)
+ status = ISO_STATUS_REFERENCEDATANOTFOUND;
+
+ return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
+}
+
+static BOOL vgids_ins_manage_security_environment(vgidsContext* context, wStream* s,
+ BYTE** response, DWORD* responseSize)
+{
+ BYTE tag = 0;
+ BYTE length = 0;
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE lc = 0;
+ DWORD resultDataSize = 0;
+ const BYTE* resultData = NULL;
+ UINT16 status = ISO_STATUS_SUCCESS;
+
+ vgids_reset_context_command_data(context);
+ vgids_reset_context_response(context);
+
+ /* Manage security environment prepares the card for performing crypto operations. */
+ if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, &lc, NULL))
+ return FALSE;
+
+ /* Check APDU params */
+ /* P1: Set Computation, decipherment, Internal Auth */
+ /* P2: Digital Signature (B6), Confidentiality (B8) */
+ if (p1 != 0x41 && p2 != 0xB6 && p2 != 0xB8)
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ goto create_response;
+ }
+
+ if (lc != 6)
+ {
+ status = ISO_STATUS_WRONGLC;
+ goto create_response;
+ }
+
+ context->currentSE.crt = p2;
+
+ /* parse command buffer */
+ /* Read algo ID */
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ if (tag != 0x80 || length != 0x01)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ goto create_response;
+ }
+ Stream_Read_UINT8(s, context->currentSE.algoId);
+
+ /* Read private key reference */
+ Stream_Read_UINT8(s, tag);
+ Stream_Read_UINT8(s, length);
+ if (tag != 0x84 || length != 0x01)
+ {
+ status = ISO_STATUS_INVALIDCOMMANDDATA;
+ goto create_response;
+ }
+ Stream_Read_UINT8(s, context->currentSE.keyRef);
+
+create_response:
+ /* If an error occured reset SE */
+ if (status != ISO_STATUS_SUCCESS)
+ memset(&context->currentSE, 0, sizeof(context->currentSE));
+ return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
+}
+
+static BOOL vgids_perform_digital_signature(vgidsContext* context)
+{
+ size_t sigSize = 0;
+ size_t msgSize = 0;
+ EVP_PKEY_CTX* ctx = NULL;
+ EVP_PKEY* pk = freerdp_key_get_evp_pkey(context->privateKey);
+ const vgidsDigestInfoMap gidsDigestInfo[VGIDS_MAX_DIGEST_INFO] = {
+ { g_PKCS1_SHA1, sizeof(g_PKCS1_SHA1), EVP_sha1() },
+ { g_PKCS1_SHA224, sizeof(g_PKCS1_SHA224), EVP_sha224() },
+ { g_PKCS1_SHA256, sizeof(g_PKCS1_SHA256), EVP_sha256() },
+ { g_PKCS1_SHA384, sizeof(g_PKCS1_SHA384), EVP_sha384() },
+ { g_PKCS1_SHA512, sizeof(g_PKCS1_SHA512), EVP_sha512() },
+ { g_PKCS1_SHA512_224, sizeof(g_PKCS1_SHA512_224), EVP_sha512_224() },
+ { g_PKCS1_SHA512_256, sizeof(g_PKCS1_SHA512_256), EVP_sha512_256() }
+ };
+
+ if (!pk)
+ {
+ WLog_ERR(TAG, "Failed to create PKEY");
+ return FALSE;
+ }
+
+ vgids_reset_context_response(context);
+
+ /* for each digest info */
+ Stream_SetPosition(context->commandData, 0);
+ for (int i = 0; i < VGIDS_MAX_DIGEST_INFO; ++i)
+ {
+ /* have we found our digest? */
+ const vgidsDigestInfoMap* digest = &gidsDigestInfo[i];
+ if (Stream_Length(context->commandData) >= digest->infoSize &&
+ memcmp(Stream_Buffer(context->commandData), digest->info, digest->infoSize) == 0)
+ {
+ /* skip digest info and calculate message size */
+ Stream_Seek(context->commandData, digest->infoSize);
+ if (!Stream_CheckAndLogRequiredLength(TAG, context->commandData, 2))
+ goto sign_failed;
+ msgSize = Stream_GetRemainingLength(context->commandData);
+
+ /* setup signing context */
+ ctx = EVP_PKEY_CTX_new(pk, NULL);
+ if (!ctx)
+ {
+ WLog_ERR(TAG, "Failed to create signing context");
+ goto sign_failed;
+ }
+
+ if (EVP_PKEY_sign_init(ctx) <= 0)
+ {
+ WLog_ERR(TAG, "Failed to init signing context");
+ goto sign_failed;
+ }
+
+ /* set padding and signature algo */
+ if (context->currentSE.algoId & VGIDS_SE_ALGOID_DST_PAD_PKCS1)
+ {
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
+ {
+ WLog_ERR(TAG, "Failed to set padding mode");
+ goto sign_failed;
+ }
+ }
+
+ if (EVP_PKEY_CTX_set_signature_md(ctx, digest->digest) <= 0)
+ {
+ WLog_ERR(TAG, "Failed to set signing mode");
+ goto sign_failed;
+ }
+
+ /* Determine buffer length */
+ if (EVP_PKEY_sign(ctx, NULL, &sigSize, Stream_Pointer(context->commandData), msgSize) <=
+ 0)
+ {
+ WLog_ERR(TAG, "Failed to determine signature size");
+ goto sign_failed;
+ }
+
+ context->responseData = Stream_New(NULL, sigSize);
+ if (!context->responseData)
+ {
+ WLog_ERR(TAG, "Failed to allocate signing buffer");
+ goto sign_failed;
+ }
+
+ /* sign */
+ if (EVP_PKEY_sign(ctx, Stream_Buffer(context->responseData), &sigSize,
+ Stream_Pointer(context->commandData), msgSize) <= 0)
+ {
+ WLog_ERR(TAG, "Failed to create signature");
+ goto sign_failed;
+ }
+
+ Stream_SetLength(context->responseData, sigSize);
+ EVP_PKEY_CTX_free(ctx);
+ break;
+ }
+ }
+
+ EVP_PKEY_free(pk);
+ vgids_reset_context_command_data(context);
+ return TRUE;
+
+sign_failed:
+ vgids_reset_context_command_data(context);
+ vgids_reset_context_response(context);
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(pk);
+ return FALSE;
+}
+
+static BOOL vgids_perform_decrypt(vgidsContext* context)
+{
+ EVP_PKEY_CTX* ctx = NULL;
+ BOOL rc = FALSE;
+ int res = 0;
+ int padding = RSA_NO_PADDING;
+
+ vgids_reset_context_response(context);
+
+ /* determine padding */
+ if (context->currentSE.algoId & VGIDS_SE_ALGOID_CT_PAD_PKCS1)
+ padding = RSA_PKCS1_PADDING;
+ else if (context->currentSE.algoId & VGIDS_SE_ALGOID_CT_PAD_OAEP)
+ padding = RSA_PKCS1_OAEP_PADDING;
+
+ /* init response buffer */
+ EVP_PKEY* pkey = freerdp_key_get_evp_pkey(context->privateKey);
+ if (!pkey)
+ goto decrypt_failed;
+ ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx)
+ goto decrypt_failed;
+ if (EVP_PKEY_decrypt_init(ctx) <= 0)
+ goto decrypt_failed;
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0)
+ goto decrypt_failed;
+
+ /* Determine buffer length */
+ const size_t inlen = Stream_Length(context->commandData);
+ size_t outlen = 0;
+ res = EVP_PKEY_decrypt(ctx, NULL, &outlen, Stream_Buffer(context->commandData), inlen);
+ if (res < 0)
+ {
+ WLog_ERR(TAG, "Failed to decrypt data");
+ goto decrypt_failed;
+ }
+
+ /* Prepare output buffer */
+ context->responseData = Stream_New(NULL, outlen);
+ if (!context->responseData)
+ {
+ WLog_ERR(TAG, "Failed to create decryption buffer");
+ goto decrypt_failed;
+ }
+
+ /* Decrypt */
+ res = EVP_PKEY_decrypt(ctx, Stream_Buffer(context->responseData), &outlen,
+ Stream_Buffer(context->commandData), inlen);
+
+ if (res < 0)
+ {
+ WLog_ERR(TAG, "Failed to decrypt data");
+ goto decrypt_failed;
+ }
+
+ Stream_SetLength(context->responseData, outlen);
+ rc = TRUE;
+
+decrypt_failed:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ vgids_reset_context_command_data(context);
+ if (!rc)
+ vgids_reset_context_response(context);
+ return rc;
+}
+
+static BOOL vgids_ins_perform_security_operation(vgidsContext* context, wStream* s, BYTE** response,
+ DWORD* responseSize)
+{
+ BYTE cla = 0;
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE lc = 0;
+ DWORD resultDataSize = 0;
+ const BYTE* resultData = NULL;
+ UINT16 status = ISO_STATUS_SUCCESS;
+
+ /* Perform security operation */
+ if (!vgids_parse_apdu_header(s, &cla, NULL, &p1, &p2, &lc, NULL))
+ return FALSE;
+
+ if (lc == 0)
+ {
+ status = ISO_STATUS_WRONGLC;
+ goto create_response;
+ }
+
+ /* Is our default key referenced? */
+ if (context->currentSE.keyRef != VGIDS_DEFAULT_KEY_REF)
+ {
+ status = ISO_STATUS_SECURITYSTATUSNOTSATISFIED;
+ goto create_response;
+ }
+
+ /* is the pin protecting the key verified? */
+ if (!context->pinVerified)
+ {
+ status = ISO_STATUS_SECURITYSTATUSNOTSATISFIED;
+ goto create_response;
+ }
+
+ /* Append the data to the context command buffer (PSO might chain command data) */
+ if (!context->commandData)
+ {
+ context->commandData = Stream_New(NULL, lc);
+ if (!context->commandData)
+ return FALSE;
+ }
+ else
+ Stream_EnsureRemainingCapacity(context->commandData, lc);
+
+ Stream_Write(context->commandData, Stream_Pointer(s), lc);
+ Stream_SealLength(context->commandData);
+
+ /* Check if the correct operation is requested for our current SE */
+ switch (context->currentSE.crt)
+ {
+ case VGIDS_SE_CRT_SIGN:
+ {
+ if (p1 != 0x9E || p2 != 0x9A)
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+
+ /* If chaining is over perform op */
+ if (!(cla & 0x10))
+ vgids_perform_digital_signature(context);
+ break;
+ }
+ case VGIDS_SE_CRT_CONF:
+ {
+ if ((p1 != 0x86 || p2 != 0x80) && (p1 != 0x80 || p2 != 0x86))
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+
+ /* If chaining is over perform op */
+ if (!(cla & 0x10))
+ vgids_perform_decrypt(context);
+ break;
+ }
+ default:
+ status = ISO_STATUS_INVALIDP1P2;
+ break;
+ }
+
+ /* Do chaining of response data if necessary */
+ if (status == ISO_STATUS_SUCCESS && context->responseData)
+ status = vgids_handle_chained_response(context, &resultData, &resultDataSize);
+
+ /* Check APDU params */
+create_response:
+ return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
+}
+
+static BOOL vgids_ins_getresponse(vgidsContext* context, wStream* s, BYTE** response,
+ DWORD* responseSize)
+{
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE le = 0;
+ DWORD resultDataSize = 0;
+ const BYTE* resultData = NULL;
+ DWORD expectedLen = 0;
+ DWORD remainingSize = 0;
+ UINT16 status = ISO_STATUS_SUCCESS;
+
+ /* Get response continues data transfer after a previous get data command */
+ /* Check if there is any data to transfer left */
+ if (!context->responseData || !Stream_CheckAndLogRequiredLength(TAG, context->responseData, 1))
+ {
+ status = ISO_STATUS_COMMANDNOTALLOWED;
+ goto create_response;
+ }
+
+ if (!vgids_parse_apdu_header(s, NULL, NULL, &p1, &p2, NULL, &le))
+ return FALSE;
+
+ /* Check APDU params */
+ if (p1 != 00 || p2 != 0x00)
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ goto create_response;
+ }
+
+ /* LE = 0 means 256 bytes expected */
+ expectedLen = le;
+ if (expectedLen == 0)
+ expectedLen = 256;
+
+ /* prepare response size and update offset */
+ remainingSize = (DWORD)Stream_GetRemainingLength(context->responseData);
+ if (remainingSize < expectedLen)
+ expectedLen = remainingSize;
+
+ resultData = Stream_Pointer(context->responseData);
+ resultDataSize = expectedLen;
+ Stream_Seek(context->responseData, expectedLen);
+
+ /* If more data is left return 61XX - otherwise 9000 */
+ remainingSize = (DWORD)Stream_GetRemainingLength(context->responseData);
+ if (remainingSize > 0)
+ {
+ status = ISO_STATUS_MORE_DATA;
+ if (remainingSize < 256)
+ status |= (remainingSize & 0xFF);
+ }
+
+create_response:
+ return vgids_create_response(status, resultData, resultDataSize, response, responseSize);
+}
+
+static BOOL vgids_ins_verify(vgidsContext* context, wStream* s, BYTE** response,
+ DWORD* responseSize)
+{
+ BYTE ins = 0;
+ BYTE p1 = 0;
+ BYTE p2 = 0;
+ BYTE lc = 0;
+ UINT16 status = ISO_STATUS_SUCCESS;
+ char pin[VGIDS_MAX_PIN_SIZE + 1] = { 0 };
+
+ /* Verify is always called for the application password (PIN) P2=0x80 */
+ if (!vgids_parse_apdu_header(s, NULL, &ins, &p1, &p2, NULL, NULL))
+ return FALSE;
+
+ /* Check APDU params */
+ if (p1 != 00 && p2 != 0x80 && p2 != 0x82)
+ {
+ status = ISO_STATUS_INVALIDP1P2;
+ goto create_response;
+ }
+
+ /* shall we reset the security state? */
+ if (p2 == 0x82)
+ {
+ context->pinVerified = FALSE;
+ goto create_response;
+ }
+
+ /* Check if pin is not already blocked */
+ if (context->curRetryCounter == 0)
+ {
+ status = ISO_STATUS_AUTHMETHODBLOCKED;
+ goto create_response;
+ }
+
+ /* Read and verify LC */
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
+ {
+ status = ISO_STATUS_INVALIDLC;
+ goto create_response;
+ }
+
+ Stream_Read_UINT8(s, lc);
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, lc) || (lc > VGIDS_MAX_PIN_SIZE))
+ {
+ status = ISO_STATUS_INVALIDLC;
+ goto create_response;
+ }
+
+ /* read and verify pin */
+ Stream_Read(s, pin, lc);
+ if (strcmp(context->pin, pin) != 0)
+ {
+ /* retries are encoded in the lowest 4-bit of the status code */
+ --context->curRetryCounter;
+ context->pinVerified = FALSE;
+ status = (ISO_STATUS_VERIFYFAILED | (context->curRetryCounter & 0xFF));
+ }
+ else
+ {
+ /* reset retry counter and mark pin as verified */
+ context->curRetryCounter = context->retryCounter;
+ context->pinVerified = TRUE;
+ }
+
+create_response:
+ return vgids_create_response(status, NULL, 0, response, responseSize);
+}
+
+vgidsContext* vgids_new(void)
+{
+ wObject* obj = NULL;
+ vgidsContext* ctx = calloc(1, sizeof(vgidsContext));
+
+ ctx->files = ArrayList_New(FALSE);
+ if (!ctx->files)
+ {
+ WLog_ERR(TAG, "Failed to create files array list");
+ goto create_failed;
+ }
+
+ obj = ArrayList_Object(ctx->files);
+ obj->fnObjectFree = vgids_ef_free;
+
+ return ctx;
+
+create_failed:
+ vgids_free(ctx);
+ return NULL;
+}
+
+BOOL vgids_init(vgidsContext* ctx, const char* cert, const char* privateKey, const char* pin)
+{
+ DWORD kxcSize = 0;
+ DWORD keymapSize = 0;
+ DWORD fsTableSize = 0;
+ BOOL rc = FALSE;
+ BYTE* kxc = NULL;
+ BYTE* keymap = NULL;
+ BYTE* fsTable = NULL;
+ vgidsEF* masterEF = NULL;
+ vgidsEF* cardidEF = NULL;
+ vgidsEF* commonEF = NULL;
+ BYTE cardid[VGIDS_CARDID_SIZE] = { 0 };
+ vgidsContainerMapEntry cmrec = { { 'P', 'r', 'i', 'v', 'a', 't', 'e', ' ', 'K', 'e', 'y', ' ',
+ '0', '0' },
+ CONTAINER_MAP_VALID_CONTAINER |
+ CONTAINER_MAP_DEFAULT_CONTAINER,
+ 0,
+ 0,
+ 0x00 /* key-size in bits - filled out later */ };
+ vgidsFilesysTableEntry filesys[] = {
+ { "mscp", "", 0, 0, 0, 0xA000, 0 },
+ { "", "cardid", 0, 0xDF20, 0, 0xA012, 0 },
+ { "", "cardapps", 0, 0xDF21, 0, 0xA010, 0 },
+ { "", "cardcf", 0, 0xDF22, 0, 0xA010, 0 },
+ { "mscp", "cmapfile", 0, 0xDF23, 0, 0xA010, 0 },
+ { "mscp", "kxc00", 0, 0xDF24, 0, 0xA010, 0 },
+ };
+
+ /* Check params */
+ if (!cert || !privateKey || !pin)
+ {
+ WLog_DBG(TAG, "Passed invalid NULL argument: cert=%p, privateKey=%p, pin=%p", cert,
+ privateKey, pin);
+ goto init_failed;
+ }
+
+ /* Convert PEM input to DER certificate/public key/private key */
+ ctx->certificate = freerdp_certificate_new_from_pem(cert);
+ if (!ctx->certificate)
+ goto init_failed;
+
+ ctx->privateKey = freerdp_key_new_from_pem(privateKey);
+ if (!ctx->privateKey)
+ goto init_failed;
+
+ /* create masterfile */
+ masterEF = vgids_ef_new(ctx, VGIDS_EFID_MASTER);
+ if (!masterEF)
+ goto init_failed;
+
+ /* create cardid file with cardid DO */
+ cardidEF = vgids_ef_new(ctx, VGIDS_EFID_CARDID);
+ if (!cardidEF)
+ goto init_failed;
+ winpr_RAND(cardid, sizeof(cardid));
+ if (!vgids_ef_write_do(cardidEF, VGIDS_DO_CARDID, cardid, sizeof(cardid)))
+ goto init_failed;
+
+ /* create user common file */
+ commonEF = vgids_ef_new(ctx, VGIDS_EFID_COMMON);
+ if (!commonEF)
+ goto init_failed;
+
+ /* write card cache DO */
+ if (!vgids_ef_write_do(commonEF, VGIDS_DO_CARDCF, g_CardCFContents, sizeof(g_CardCFContents)))
+ goto init_failed;
+
+ /* write container map DO */
+ const int size = get_rsa_key_size(ctx->privateKey);
+ if (size <= 0)
+ goto init_failed;
+
+ if (size <= 0)
+ goto init_failed;
+
+ cmrec.wKeyExchangeKeySizeBits = (WORD)size * 8;
+ if (!vgids_ef_write_do(commonEF, VGIDS_DO_CMAPFILE, &cmrec, sizeof(cmrec)))
+ goto init_failed;
+
+ /* write cardapps DO */
+ if (!vgids_ef_write_do(commonEF, VGIDS_DO_CARDAPPS, g_CardAppsContents,
+ sizeof(g_CardAppsContents)))
+ goto init_failed;
+
+ /* convert and write certificate to key exchange container */
+ if (!vgids_prepare_certificate(ctx->certificate, &kxc, &kxcSize))
+ goto init_failed;
+ if (!vgids_ef_write_do(commonEF, VGIDS_DO_KXC00, kxc, kxcSize))
+ goto init_failed;
+
+ /* prepare and write file system table */
+ if (!vgids_prepare_fstable(filesys, ARRAYSIZE(filesys), &fsTable, &fsTableSize))
+ goto init_failed;
+ if (!vgids_ef_write_do(masterEF, VGIDS_DO_FILESYSTEMTABLE, fsTable, fsTableSize))
+ goto init_failed;
+
+ /* vgids_prepare_keymap and write to masterEF */
+ if (!vgids_prepare_keymap(ctx, &keymap, &keymapSize))
+ goto init_failed;
+ if (!vgids_ef_write_do(masterEF, VGIDS_DO_KEYMAP, keymap, keymapSize))
+ goto init_failed;
+
+ /* store user pin */
+ ctx->curRetryCounter = ctx->retryCounter = VGIDS_DEFAULT_RETRY_COUNTER;
+ ctx->pin = _strdup(pin);
+ if (!ctx->pin)
+ goto init_failed;
+
+ rc = TRUE;
+
+init_failed:
+ // ArrayList_Append in vgids_ef_new takes ownership
+ // of cardidEF, commonEF, masterEF
+ // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
+ free(kxc);
+ free(keymap);
+ free(fsTable);
+ return rc;
+}
+
+BOOL vgids_process_apdu(vgidsContext* context, const BYTE* data, DWORD dataSize, BYTE** response,
+ DWORD* responseSize)
+{
+ wStream s;
+ static int x = 1;
+
+ /* Check params */
+ if (!context || !data || !response || !responseSize)
+ {
+ WLog_ERR(TAG, "Invalid NULL pointer passed");
+ return FALSE;
+ }
+
+ if (dataSize < 4)
+ {
+ WLog_ERR(TAG, "APDU buffer is less than 4 bytes: %" PRIu32, dataSize);
+ return FALSE;
+ }
+
+ /* Examine INS byte */
+ Stream_StaticConstInit(&s, data, dataSize);
+ if (x++ == 0xe)
+ x = 0xe + 1;
+ switch (data[1])
+ {
+ case ISO_INS_SELECT:
+ return vgids_ins_select(context, &s, response, responseSize);
+ case ISO_INS_GETDATA:
+ return vgids_ins_getdata(context, &s, response, responseSize);
+ case ISO_INS_GETRESPONSE:
+ return vgids_ins_getresponse(context, &s, response, responseSize);
+ case ISO_INS_MSE:
+ return vgids_ins_manage_security_environment(context, &s, response, responseSize);
+ case ISO_INS_PSO:
+ return vgids_ins_perform_security_operation(context, &s, response, responseSize);
+ case ISO_INS_VERIFY:
+ return vgids_ins_verify(context, &s, response, responseSize);
+ default:
+ break;
+ }
+
+ /* return command not allowed */
+ return vgids_create_response(ISO_STATUS_COMMANDNOTALLOWED, NULL, 0, response, responseSize);
+}
+
+void vgids_free(vgidsContext* context)
+{
+ if (context)
+ {
+ freerdp_key_free(context->privateKey);
+ freerdp_certificate_free(context->certificate);
+ Stream_Free(context->commandData, TRUE);
+ Stream_Free(context->responseData, TRUE);
+ free(context->pin);
+ ArrayList_Free(context->files);
+ free(context);
+ }
+}
diff --git a/libfreerdp/emu/scard/smartcard_virtual_gids.h b/libfreerdp/emu/scard/smartcard_virtual_gids.h
new file mode 100644
index 0000000..c59b00e
--- /dev/null
+++ b/libfreerdp/emu/scard/smartcard_virtual_gids.h
@@ -0,0 +1,57 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Virtual GIDS implementation
+ *
+ * Copyright 2021 Martin Fleisz <martin.fleisz@thincast.com>
+ * Copyright 2021 Thincast Technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WINPR_SMARTCARD_VIRTUAL_GIDS_H
+#define WINPR_SMARTCARD_VIRTUAL_GIDS_H
+
+#include <winpr/winpr.h>
+#include <winpr/wtypes.h>
+#include <freerdp/channels/log.h>
+
+/* Virtual GIDS context */
+typedef struct vgids_context vgidsContext;
+
+/* Creates a new virtual gids context */
+vgidsContext* vgids_new(void);
+
+/*
+ Initializes the virtual gids context.
+ cert: PEM encoded smartcard certificate
+ privateKey: PEM encoded private key for the smartcard certificate
+ pin: Pin protecting the usage of the private key
+ Returns: TRUE on success, FALSE in case of an error
+*/
+BOOL vgids_init(vgidsContext* ctx, const char* cert, const char* privateKey, const char* pin);
+
+/*
+ Processes the provided APDU returning a response for each processed command.
+ data: APDU byte stream
+ dataSize: size of the APDU provided in data
+ response: Pointer where the response buffer is stored to. Must be freed by caller!
+ responseSize: Size of the returned data buffer
+ Returns: TRUE on success, FALSE in case of an error
+*/
+BOOL vgids_process_apdu(vgidsContext* context, const BYTE* data, DWORD dataSize, BYTE** response,
+ DWORD* responseSize);
+
+/* frees a previously created virtual gids context */
+void vgids_free(vgidsContext* context);
+
+#endif /* WINPR_SMARTCARD_VIRTUAL_GIDS_H */