Improve Wiegand PACS encode/decode and verbose output

This commit is contained in:
CinderSocket
2026-03-10 12:25:31 -07:00
parent d5dc045221
commit 08cd96c5a9
4 changed files with 210 additions and 55 deletions

View File

@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
## [unreleased][unreleased]
- Changed `wiegand encode` / `wiegand decode` - added support for encoding and decoding the new 96-bit ASN.1 encoded format, `--bin` encoding, verbose PACS encoding output, and explicit rejection of raw/binary decodes above 96 bits (@cindersocket)
- Added `hf felica discnodes` command (@kormax)
- Added `hf mfp dump` command (@apply-science)
- Added `hf felica seacinfo` command (@kormax)

View File

@@ -27,6 +27,7 @@
#include "protocols.h"
#include "parity.h" // oddparity
#include "cmdhflist.h" // annotations
#include "commonutil.h" // ARRAYLEN
#include "wiegand_formats.h"
#include "wiegand_formatutils.h"
#include "util.h"
@@ -34,45 +35,104 @@
static int CmdHelp(const char *Cmd);
#define PACS_EXTRA_LONG_FORMAT 18 // 144 bits
#define PACS_LONG_FORMAT 12 // 96 bits
#define PACS_LONG_FORMAT 13 // 96 bits + 1 byte pad
#define PACS_FORMAT 6 // 44 bits
static int wiegand_new_pacs(uint8_t *padded_pacs, uint8_t plen) {
#define PACS_MAX_WIEGAND_BITS 96
#define WIEGAND_MAX_ENCODED_BITS (PACS_MAX_WIEGAND_BITS + 8)
uint8_t d[PACS_EXTRA_LONG_FORMAT] = {0};
memcpy(d, padded_pacs, plen);
uint8_t pad = d[0];
char *binstr = (char *)calloc((PACS_EXTRA_LONG_FORMAT * 8) + 1, sizeof(uint8_t));
if (binstr == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
static void wiegand_packed_to_binstr(const wiegand_message_t *packed, char *binstr) {
for (uint8_t i = 0; i < packed->Length; i++) {
binstr[i] = get_bit_by_position((wiegand_message_t *)packed, i) ? '1' : '0';
}
binstr[packed->Length] = '\0';
}
uint8_t n = plen - 1;
static int wiegand_print_new_pacs_verbose(const wiegand_message_t *packed, const uint8_t *pacs, size_t pacs_len) {
char binstr[PACS_MAX_WIEGAND_BITS + 1] = {0};
char rawbin[WIEGAND_MAX_ENCODED_BITS + 1] = {0};
uint8_t raw[(WIEGAND_MAX_ENCODED_BITS + 7) / 8] = {0};
size_t raw_len = 0;
bytes_2_binstr(binstr, d + 1, n);
wiegand_packed_to_binstr(packed, binstr);
rawbin[0] = '1';
memcpy(rawbin + 1, binstr, packed->Length);
binstr_2_bytes(raw, &raw_len, rawbin);
bytes_2_binstr(rawbin, raw, raw_len);
binstr[strlen(binstr) - pad] = '\0';
PrintAndLogEx(INFO, "----------------------- " _CYAN_("PACS Encoding") " ------------------------");
PrintAndLogEx(SUCCESS, "New PACS......... " _GREEN_("0x %s"), sprint_hex_inrow(pacs, pacs_len));
PrintAndLogEx(INFO, "With Sentinel.... " _GREEN_("0b %s") " (%zu-bit)", rawbin, strlen(rawbin));
PrintAndLogEx(SUCCESS, "Wiegand --raw.... " _YELLOW_("0x %s"), sprint_hex_inrow(raw, raw_len));
PrintAndLogEx(INFO, "Without Sentinel. " _GREEN_("0b %s") " (%zu-bit)", binstr, strlen(binstr));
return PM3_SUCCESS;
}
size_t tlen = 0;
uint8_t tmp[16] = {0};
binstr_2_bytes(tmp, &tlen, binstr);
PrintAndLogEx(SUCCESS, "Wiegand raw.... " _YELLOW_("%s"), sprint_hex_inrow(tmp, tlen));
static int wiegand_encode_new_pacs(const wiegand_message_t *packed, bool verbose) {
uint32_t top = 0, mid = 0, bot = 0;
if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) {
PrintAndLogEx(ERR, "Binary string contains none <0|1> chars");
free(binstr);
if (packed->Length == 0) {
PrintAndLogEx(ERR, "Empty Wiegand input");
return PM3_EINVARG;
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "------------------------- " _CYAN_("SIO - Wiegand") " ---------------------------");
decode_wiegand(top, mid, bot, strlen(binstr));
free(binstr);
if (packed->Length > PACS_MAX_WIEGAND_BITS) {
PrintAndLogEx(ERR, "New PACS encoding supports up to %u Wiegand bits", PACS_MAX_WIEGAND_BITS);
return PM3_EINVARG;
}
uint8_t padded_bits = (uint8_t)(((packed->Length + 7) / 8) * 8);
uint8_t pad = padded_bits - packed->Length;
char binstr[PACS_MAX_WIEGAND_BITS + 1] = {0};
wiegand_packed_to_binstr(packed, binstr);
memset(binstr + packed->Length, '0', pad);
binstr[padded_bits] = '\0';
size_t pacs_len = 0;
uint8_t pacs[PACS_LONG_FORMAT] = {0};
binstr_2_bytes(pacs + 1, &pacs_len, binstr);
pacs[0] = pad;
PrintAndLogEx(SUCCESS, "New PACS......... " _GREEN_("0x %s"), sprint_hex_inrow(pacs, pacs_len + 1));
if (verbose) {
PrintAndLogEx(NORMAL, "");
return wiegand_print_new_pacs_verbose(packed, pacs, pacs_len + 1);
}
return PM3_SUCCESS;
}
static int wiegand_new_pacs(const uint8_t *padded_pacs, uint8_t plen) {
return HIDDumpPACSBits(padded_pacs, plen, false);
}
static int wiegand_print_raw_from_bin(const uint8_t *binarr, int blen) {
uint8_t out[(WIEGAND_MAX_ENCODED_BITS + 7) / 8] = {0};
char binstr[WIEGAND_MAX_ENCODED_BITS + 1] = {0};
binstr[0] = '1';
for (int i = 0; i < blen; i++) {
binstr[i + 1] = binarr[i] ? '1' : '0';
}
size_t out_len = 0;
binstr_2_bytes(out, &out_len, binstr);
PrintAndLogEx(SUCCESS, "Wiegand raw.... " _YELLOW_("%s"), sprint_hex_inrow(out, out_len));
return PM3_SUCCESS;
}
static int wiegand_encode_new_pacs_from_bin(const uint8_t *binarr, int blen, bool verbose) {
wiegand_message_t packed;
memset(&packed, 0, sizeof(packed));
packed.Length = blen;
for (int i = 0; i < blen; i++) {
if (set_bit_by_position(&packed, binarr[i], i) == false) {
PrintAndLogEx(ERR, "Binary string must be less than or equal to %u bits", PACS_MAX_WIEGAND_BITS);
return PM3_EINVARG;
}
}
return wiegand_encode_new_pacs(&packed, verbose);
}
int CmdWiegandList(const char *Cmd) {
CLIParserContext *ctx;
@@ -98,17 +158,22 @@ int CmdWiegandEncode(const char *Cmd) {
CLIParserInit(&ctx, "wiegand encode",
"Encode wiegand formatted number to raw hex",
"wiegand encode --fc 101 --cn 1337 -> show all formats\n"
"wiegand encode -w H10301 --fc 101 --cn 1337 -> H10301 format "
"wiegand encode -w H10301 --fc 101 --cn 1337 -> H10301 format\n"
"wiegand encode --bin 1 -> raw wiegand hex with sentinel\n"
"wiegand encode -w H10301 --fc 123 --cn 4567 --new -> new ASN.1 encoded format"
);
void *argtable[] = {
arg_param_begin,
arg_str0("b", "bin", "<bin>", "binary string to be encoded"),
arg_u64_0(NULL, "fc", "<dec>", "facility number"),
arg_u64_1(NULL, "cn", "<dec>", "card number"),
arg_u64_0(NULL, "cn", "<dec>", "card number"),
arg_u64_0(NULL, "issue", "<dec>", "issue level"),
arg_u64_0(NULL, "oem", "<dec>", "OEM code"),
arg_str0("w", "wiegand", "<format>", "see `wiegand list` for available formats"),
arg_lit0("n", "new", "encode to new ASN.1 encoded format"),
arg_lit0(NULL, "pre", "add HID ProxII preamble to wiegand output"),
arg_lit0("v", "verbose", "verbose output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
@@ -116,17 +181,44 @@ int CmdWiegandEncode(const char *Cmd) {
wiegand_card_t data;
memset(&data, 0, sizeof(wiegand_card_t));
data.FacilityCode = arg_get_u32_def(ctx, 1, 0);
data.CardNumber = arg_get_u64_def(ctx, 2, 0);
data.IssueLevel = arg_get_u32_def(ctx, 3, 0);
data.OEM = arg_get_u32_def(ctx, 4, 0);
uint8_t binarr[PACS_MAX_WIEGAND_BITS] = {0};
int blen = 0;
int res = CLIParamBinToBuf(arg_get_str(ctx, 1), binarr, ARRAYLEN(binarr), &blen);
data.FacilityCode = arg_get_u32_def(ctx, 2, 0);
data.CardNumber = arg_get_u64_def(ctx, 3, 0);
data.IssueLevel = arg_get_u32_def(ctx, 4, 0);
data.OEM = arg_get_u32_def(ctx, 5, 0);
int len = 0;
char format[16] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)format, sizeof(format), &len);
bool preamble = arg_get_lit(ctx, 6);
CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)format, sizeof(format), &len);
bool new_pacs = arg_get_lit(ctx, 7);
bool preamble = arg_get_lit(ctx, 8);
bool verbose = arg_get_lit(ctx, 9);
CLIParserFree(ctx);
if (res) {
PrintAndLogEx(FAILED, "Error parsing binary string");
return PM3_EINVARG;
}
if (new_pacs && preamble) {
PrintAndLogEx(ERR, "`--new` and `--pre` can't be combined");
return PM3_EINVARG;
}
if (blen && (len || data.FacilityCode || data.CardNumber || data.IssueLevel || data.OEM || preamble)) {
PrintAndLogEx(ERR, "`--bin` can't be combined with format, field, or preamble options");
return PM3_EINVARG;
}
if (blen == 0 && len == 0 && data.CardNumber == 0 && data.FacilityCode == 0 && data.IssueLevel == 0 && data.OEM == 0) {
PrintAndLogEx(ERR, "Must provide either card data, a specific format, or `--bin`");
return PM3_EINVARG;
}
int idx = -1;
if (len) {
idx = HIDFindCardFormat(format);
@@ -136,13 +228,26 @@ int CmdWiegandEncode(const char *Cmd) {
}
}
if (idx != -1) {
if (new_pacs && idx == -1 && blen == 0) {
PrintAndLogEx(ERR, "`--new` requires either `--bin` or a specific wiegand format");
return PM3_EINVARG;
}
if (blen) {
if (new_pacs) {
return wiegand_encode_new_pacs_from_bin(binarr, blen, verbose);
}
return wiegand_print_raw_from_bin(binarr, blen);
} else if (idx != -1) {
wiegand_message_t packed;
memset(&packed, 0, sizeof(wiegand_message_t));
if (HIDPack(idx, &data, &packed, preamble) == false) {
PrintAndLogEx(WARNING, "The card data could not be encoded in the selected format.");
return PM3_ESOFT;
}
if (new_pacs) {
return wiegand_encode_new_pacs(&packed, verbose);
}
print_wiegand_code(&packed);
} else {
// try all formats and print only the ones that work.
@@ -157,14 +262,14 @@ int CmdWiegandDecode(const char *Cmd) {
CLIParserInit(&ctx, "wiegand decode",
"Decode raw hex or binary to wiegand format",
"wiegand decode --raw 2006F623AE\n"
"wiegand decode --new 06BD88EB80 -> 4..8 bytes, new padded format "
"wiegand decode --new 06BD88EB80 -> 4..13 bytes, new ASN.1 encoded format "
);
void *argtable[] = {
arg_param_begin,
arg_str0("r", "raw", "<hex>", "raw hex to be decoded"),
arg_str0("b", "bin", "<bin>", "binary string to be decoded"),
arg_str0("n", "new", "<hex>", "new padded pacs as raw hex to be decoded"),
arg_str0("n", "new", "<hex>", "new ASN.1 encoded data as raw hex to be decoded"),
arg_lit0("f", "force", "skip preabmle checking, brute force all possible lengths for raw hex input"),
arg_param_end
};
@@ -174,11 +279,11 @@ int CmdWiegandDecode(const char *Cmd) {
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)hex, sizeof(hex), &hlen);
int blen = 0;
uint8_t binarr[100] = {0x00};
uint8_t binarr[WIEGAND_MAX_ENCODED_BITS] = {0x00};
int res = CLIParamBinToBuf(arg_get_str(ctx, 2), binarr, sizeof(binarr), &blen);
int plen = 0;
uint8_t phex[8] = {0};
uint8_t phex[PACS_LONG_FORMAT] = {0};
res = CLIParamHexToBuf(arg_get_str(ctx, 3), phex, sizeof(phex), &plen);
bool no_preamble = arg_get_lit(ctx, 4);
@@ -193,6 +298,10 @@ int CmdWiegandDecode(const char *Cmd) {
uint32_t top = 0, mid = 0, bot = 0;
if (hlen) {
if ((hlen * 4) > PACS_MAX_WIEGAND_BITS) {
PrintAndLogEx(ERR, "Raw hex decode supports up to %u Wiegand bits", PACS_MAX_WIEGAND_BITS);
return PM3_EINVARG;
}
res = hexstring_to_u96(&top, &mid, &bot, hex);
if (res != hlen) {
PrintAndLogEx(ERR, "Hex string contains none hex chars");
@@ -200,11 +309,15 @@ int CmdWiegandDecode(const char *Cmd) {
}
if (no_preamble) {
// pass hex input length as is and brute force all possible lengths
// Pass the input hex length through so decode_wiegand() brute-forces
// the possible bit lengths instead of assuming a preamble-encoded value.
blen = -hlen;
}
} else if (blen) {
if (blen > PACS_MAX_WIEGAND_BITS) {
PrintAndLogEx(ERR, "Binary decode supports up to %u Wiegand bits", PACS_MAX_WIEGAND_BITS);
return PM3_EINVARG;
}
int n = binarray_to_u96(&top, &mid, &bot, binarr, blen);
if (n != blen) {
PrintAndLogEx(ERR, "Binary string contains none <0|1> chars");

View File

@@ -1796,6 +1796,14 @@ bool decode_wiegand(uint32_t top, uint32_t mid, uint32_t bot, int n) {
}
int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbose) {
// PACS is encoded as a 1-byte pad count followed by at least 1 byte of payload.
// Reject malformed inputs here so trimming the pad bits below cannot index before
// the start of the binary buffer.
if (length < 2 || data[0] > 0x07) {
PrintAndLogEx(ERR, "Invalid PACS value");
return PM3_EINVARG;
}
uint8_t n = length - 1;
uint8_t pad = data[0];
char *binstr = (char *)calloc((length * 8) + 1, sizeof(uint8_t));
@@ -1815,13 +1823,19 @@ int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbos
PrintAndLogEx(DEBUG, "bin.............. " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr));
size_t hexlen = 0;
uint8_t hex[16] = {0};
uint8_t *hex = calloc(n ? n : 1, sizeof(uint8_t));
if (hex == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
free(binstr);
return PM3_EMALLOC;
}
binstr_2_bytes(hex, &hexlen, binstr);
PrintAndLogEx(SUCCESS, "hex.............. " _GREEN_("%s"), sprint_hex_inrow(hex, hexlen));
uint32_t top = 0, mid = 0, bot = 0;
if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) {
PrintAndLogEx(ERR, "Binary string contains none <0|1> chars");
free(hex);
free(binstr);
return PM3_EINVARG;
}
@@ -1850,25 +1864,36 @@ int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbos
// MIFARE DESFire
// MIFARE Classic
char mfcbin[28] = {0};
mfcbin[0] = '1';
memcpy(mfcbin + 1, binstr, strlen(binstr));
binstr_2_bytes(hex, &hexlen, mfcbin);
size_t binstrlen = strlen(binstr);
// Match hf mf encodehid: prepend the sentinel bit, pack to bytes, then right-align
// the result in the 15-byte payload area after the leading 0x02 marker in block 5.
// Fixed-size local buffers are enough for the current 96-bit Wiegand limit:
// 96 data bits + 1 sentinel + NUL = 98 chars, packed into at most 13 bytes.
if (binstrlen <= 96) {
char mfcbin[98] = {0};
uint8_t mfcpayload[15] = {0};
size_t mfchexlen = 0;
PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("MIFARE Classic") " (Pm3 simulation)");
PrintAndLogEx(SUCCESS, " hf mf eclr;");
PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 0 -d 049DBA42A23E80884400C82000000000;");
PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 1 -d 1B014D48000000000000000000000000;");
PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 3 -d A0A1A2A3A4A5787788C189ECA97F8C2A;");
PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 5 -d 020000000000000000000000%s;", sprint_hex_inrow(hex, hexlen));
PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 7 -d 484944204953787788AA204752454154;");
PrintAndLogEx(SUCCESS, " hf mf sim --1k -i;");
PrintAndLogEx(NORMAL, "");
mfcbin[0] = '1';
memcpy(mfcbin + 1, binstr, binstrlen);
binstr_2_bytes(mfcpayload + (sizeof(mfcpayload) - ((binstrlen + 1 + 7) / 8)), &mfchexlen, mfcbin);
PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("MIFARE Classic") " (Pm3 simulation)");
PrintAndLogEx(SUCCESS, " hf mf eclr;");
PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 0 -d 049DBA42A23E80884400C82000000000;");
PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 1 -d 1B014D48000000000000000000000000;");
PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 3 -d A0A1A2A3A4A5787788C189ECA97F8C2A;");
PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 5 -d 02%s;", sprint_hex_inrow(mfcpayload, sizeof(mfcpayload)));
PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 7 -d 484944204953787788AA204752454154;");
PrintAndLogEx(SUCCESS, " hf mf sim --1k -i;");
PrintAndLogEx(NORMAL, "");
}
PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("MIFARE Classic 1K"));
PrintAndLogEx(SUCCESS, " hf mf encodehid --bin %s", binstr);
PrintAndLogEx(NORMAL, "");
}
free(hex);
free(binstr);
return PM3_SUCCESS;
}

View File

@@ -474,8 +474,24 @@ while true; do
if ! CheckExecute "nfc decode test - signature" "$CLIENTBIN -c 'nfc decode -d 03FF010194113870696C65742E65653A656B616172743A3266195F26063132303832325904202020205F28033233335F2701316E1B5A13333038363439303039303030323636343030355304EBF2CE704103000000AC536967010200803A2448FCA7D354A654A81BD021150D1A152D1DF4D7A55D2B771F12F094EAB6E5E10F2617A2F8DAD4FD38AFF8EA39B71C19BD42618CDA86EE7E144636C8E0E7CFC4096E19C3680E09C78A0CDBC05DA2D698E551D5D709717655E56FE3676880B897D2C70DF5F06ECE07C71435255144F8EE41AF110E7B180DA0E6C22FB8FDEF61800025687474703A2F2F70696C65742E65652F6372742F33303836343930302D303030312E637274FE'" "30864900-0001.crt"; then break; fi
if ! CheckExecute "nfc decode test - openprinter tag" "$CLIENTBIN -c 'nfc decode -d 03FF012F91013A55046E756D616B6572732E636F6D2F70726F64756374732F6162732D66696C616D656E743F76617269616E743D3436393434323937333836323932521CD26170706C69636174696F6E2F766E642E6F70656E7072696E74746167A10218AFBF041B000007D0FCAB45F9080009020A70414253204C656D6F6E2059656C6C6F770B684E756D616B6572730E1A69094200101903E81119041A1218F01343F9A800181DF93C29182218F01823190104182418AA1825185A18261864FF00'" "application/vnd.openprinttag"; then break; fi
if ! CheckExecute "wiegand encode test - new" "$CLIENTBIN -c 'wiegand encode -w H10301 --fc 123 --cn 4567 --new'" "New PACS\\.{9} 0x 06BD88EB80"; then break; fi
if ! CheckExecute "wiegand encode test - bin" "$CLIENTBIN -c 'wiegand encode --bin 1'" "Wiegand raw\\.{4} 03"; then break; fi
if ! CheckExecute "wiegand encode test - new bin" "$CLIENTBIN -c 'wiegand encode --bin 1 --new'" "New PACS\\.{9} 0x 0780"; then break; fi
if ! CheckExecute "wiegand encode test - new bin verbose hdr" "$CLIENTBIN -c 'wiegand encode --bin 1 --new --verbose'" "New PACS"; then break; fi
if ! CheckExecute "wiegand encode test - new bin verbose pad" "$CLIENTBIN -c 'wiegand encode --bin 1 --new --verbose'" "With Sentinel\\.{4} 0b 00000011 \\(8-bit\\)"; then break; fi
if ! CheckExecute "wiegand encode test - new bin verbose raw" "$CLIENTBIN -c 'wiegand encode --bin 1 --new --verbose'" "Wiegand --raw\\.{4} 0x 03"; then break; fi
if ! CheckExecute "wiegand encode test - new bin verbose bin" "$CLIENTBIN -c 'wiegand encode --bin 1 --new --verbose'" "Without Sentinel\\. 0b 1 \\(1-bit\\)"; then break; fi
if ! CheckExecute "wiegand encode test - bin 96-bit" "PAT=\$(printf '01%.0s' {1..48}); $CLIENTBIN -c \"wiegand encode --bin \$PAT\"" "Wiegand raw\\.{4} 01555555555555555555555555"; then break; fi
if ! CheckExecute "wiegand encode test - new bin 96-bit" "PAT=\$(printf '01%.0s' {1..48}); $CLIENTBIN -c \"wiegand encode --bin \$PAT --new\"" "New PACS\\.{9} 0x 00555555555555555555555555"; then break; fi
if ! CheckExecute "wiegand encode test - new bin 96-bit verbose pad" "PAT=\$(printf '01%.0s' {1..48}); $CLIENTBIN -c \"wiegand encode --bin \$PAT --new --verbose\"" "With Sentinel\\.{4} 0b 00000001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101 \\(104-bit\\)"; then break; fi
if ! CheckExecute "wiegand encode test - new bin 96-bit verbose raw" "PAT=\$(printf '01%.0s' {1..48}); $CLIENTBIN -c \"wiegand encode --bin \$PAT --new --verbose\"" "Wiegand --raw\\.{4} 0x 01555555555555555555555555"; then break; fi
if ! CheckExecute "wiegand encode test - new 48-bit" "$CLIENTBIN -c 'wiegand encode -w C1k48s --fc 42069 --cn 42069 --new'" "New PACS\\.{9} 0x 0000A4550148AB"; then break; fi
if ! CheckExecute "wiegand decode test - raw over 96-bit" "$CLIENTBIN -c 'wiegand decode --raw 01555555555555555555555555' 2>&1" "Raw hex decode supports up to 96 Wiegand bits"; then break; fi
if ! CheckExecute "wiegand decode test - raw" "$CLIENTBIN -c 'wiegand decode --raw 2006F623AE'" "FC: 123 CN: 4567 parity \( ok \)"; then break; fi
if ! CheckExecute "wiegand decode test - bin over 96-bit" "PAT=\$(printf '01%.0s' {1..49}); $CLIENTBIN -c \"wiegand decode --bin \$PAT\" 2>&1" "Binary decode supports up to 96 Wiegand bits"; then break; fi
if ! CheckExecute "wiegand decode test - new" "$CLIENTBIN -c 'wiegand decode --new 06BD88EB80'" "FC: 123 CN: 4567 parity \( ok \)"; then break; fi
if ! CheckExecute "wiegand decode test - new 96-bit" "$CLIENTBIN -c 'wiegand decode --new 00555555555555555555555555'" "hex\\.{14} 555555555555555555555555"; then break; fi
if ! CheckExecute "wiegand decode test - new 48-bit" "$CLIENTBIN -c 'wiegand decode --new 0000A4550148AB'" "C1k48s.*FC: 42069 CN: 42069 parity \( ok \)"; then break; fi
if ! CheckExecute "wiegand Verkada40 encode test 1" "$CLIENTBIN -c 'wiegand encode -w Verkada40 --fc 50 --cn 1001'" "86400007D3"; then break; fi
if ! CheckExecute "wiegand Verkada40 decode test 1" "$CLIENTBIN -c 'wiegand decode --raw 86400007D3'" "Verkada40.*FC: 50 CN: 1001 parity \( ok \)"; then break; fi
if ! CheckExecute "wiegand Verkada40 encode test 2" "$CLIENTBIN -c 'wiegand encode -w Verkada40 --fc 50 --cn 1004'" "86400007D9"; then break; fi