Files
proxmark3/client/src/cmddata.c
T
Matthew CarrollandClaude Opus 5 782d28932c lf: restore the graph buffer length along with its contents
restore_bufferS32() returns the length it put back and every graph-buffer
caller dropped it, so g_GraphTraceLen kept whatever value the code between
save and restore had left.

In try_detect_modulation() that is the 160 sample psk antenna-settle trim:
`data modulation` gave the buffer's samples back but left it 160 short, and
a following demod lost round(160 / clk) bits off the end. check_chiptype()
loses the same way through the acquisitions its detections make, on the
`lf search` path, and for g_DemodBuffer too. demodTI() loses convLen + 16
on its error path.

save_graphbuffer() / restore_graphbuffer() pair the length and grid offset
with the contents so no call site has to remember them. The visa2000 and
zx8211 sites do not change the length, so converting them is a no-op today
and keeps the next one from being a bug.

Regression test in tools/pm3_tests.sh: `data modulation` on a saved psk1
trace must leave all 20000 samples.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 10:33:17 -07:00

5805 lines
205 KiB
C

//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Data and Graph commands
//-----------------------------------------------------------------------------
#include "cmddata.h"
#include <stdio.h>
#include <string.h>
#include <limits.h> // for CmdNorm INT_MIN && INT_MAX
#include <math.h> // pow
#include <ctype.h> // tolower
#include <locale.h> // number formatter..
#include "commonutil.h" // ARRAYLEN
#include "cmdparser.h" // for command_t
#include "ui.h" // for show graph controls
#include "proxgui.h"
#include "graph.h" // for graph data
#include "comms.h"
#include "lfdemod.h" // for demod code
#include "cmdlf.h" // for lf_getconfig
#include "loclass/cipherutils.h" // for decimating samples in getsamples
#include "cmdlfem410x.h" // askem410xdecode
#include "fileutils.h" // searchFile
#include "cliparser.h"
#include "cmdlft55xx.h" // print...
#include "crypto/asn1utils.h" // ASN1 decode / print
#include "cmdflashmemspiffs.h" // SPIFFS flash memory download
#include "mbedtls/bignum.h" // big num
#include "mbedtls/entropy.h" //
#include "mbedtls/ctr_drbg.h" // random generator
#include "atrs.h" // ATR lookup
#include "crypto/libpcrypto.h" // Cryptography
#include "qrcode/qrcode.h" // QR Code lib
#include "pm3_dsp.h" // FFT, windows, spectra
#include "pm3_fit.h" // matched filter hypothesis bank
#define FITSCORE_DEFAULT_WINDOW 16384
#define FITSCORE_MIN_SYMBOLS 128
uint8_t g_DemodBuffer[MAX_DEMOD_BUF_LEN] = { 0x00 };
size_t g_DemodBufferLen = 0;
int32_t g_DemodStartIdx = 0;
int g_DemodClock = 0;
static int CmdHelp(const char *Cmd);
// set the g_DemodBuffer with given array ofq binary (one bit per byte)
void setDemodBuff(const uint8_t *buff, size_t size, size_t start_idx) {
if (buff == NULL) {
return;
}
if (size > MAX_DEMOD_BUF_LEN - start_idx) {
size = MAX_DEMOD_BUF_LEN - start_idx;
}
for (size_t i = 0; i < size; i++) {
g_DemodBuffer[i] = buff[start_idx++];
}
g_DemodBufferLen = size;
}
bool getDemodBuff(uint8_t *buff, size_t *size) {
if (buff == NULL) return false;
if (size == NULL) return false;
if (*size == 0) return false;
*size = (*size > g_DemodBufferLen) ? g_DemodBufferLen : *size;
memcpy(buff, g_DemodBuffer, *size);
return true;
}
// function to compute mean for a series
static double compute_mean(const int *data, size_t n) {
double mean = 0.0;
for (size_t i = 0; i < n; i++)
mean += data[i];
mean /= n;
return mean;
}
// function to compute variance for a series
static double compute_variance(const int *data, size_t n) {
double variance = 0.0;
double mean = compute_mean(data, n);
for (size_t i = 0; i < n; i++)
variance += pow((data[i] - mean), 2.0);
variance /= n;
return variance;
}
static int CmdSetDebugMode(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data setdebugmode",
"Set debugging level on client side",
"data setdebugmode"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("0", NULL, "no debug messages"),
arg_lit0("1", NULL, "debug"),
arg_lit0("2", NULL, "verbose debugging"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool dg_0 = arg_get_lit(ctx, 1);
bool dg_1 = arg_get_lit(ctx, 2);
bool dg_2 = arg_get_lit(ctx, 3);
CLIParserFree(ctx);
if (dg_0 + dg_1 + dg_2 > 1) {
PrintAndLogEx(INFO, "Select only one option");
return PM3_EINVARG;
}
if (dg_0)
g_debugMode = 0;
if (dg_1)
g_debugMode = 1;
if (dg_2)
g_debugMode = 2;
switch (g_debugMode) {
case 0:
PrintAndLogEx(INFO, "client debug level... %u ( no debug messages )", g_debugMode);
break;
case 1:
PrintAndLogEx(INFO, "client debug level... %u ( debug messages )", g_debugMode);
break;
case 2:
PrintAndLogEx(INFO, "client debug level... %u ( verbose debug messages )", g_debugMode);
break;
default:
break;
}
return PM3_SUCCESS;
}
// max output to MAX_DEMODULATION_BITS bits if we have more
// doesn't take inconsideration where the demod offset or bitlen found.
int printDemodBuff(uint8_t offset, bool strip_leading, bool invert, bool print_hex) {
size_t len = g_DemodBufferLen;
if (len == 0) {
PrintAndLogEx(WARNING, "DemodBuffer is empty");
return PM3_EINVARG;
}
uint8_t *buf = calloc(len, sizeof(uint8_t));
if (buf == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
memcpy(buf, g_DemodBuffer, len);
uint8_t *p = NULL;
if (strip_leading) {
p = (buf + offset);
if (len > (g_DemodBufferLen - offset)) {
len = (g_DemodBufferLen - offset);
}
size_t i;
for (i = 0; i < len; i++) {
if (p[i] == 1) {
break;
}
}
offset += i;
}
if (len > (g_DemodBufferLen - offset)) {
len = (g_DemodBufferLen - offset);
}
if (len > MAX_DEMODULATION_BITS) {
len = MAX_DEMODULATION_BITS;
}
if (invert) {
p = (buf + offset);
for (size_t i = 0; i < len; i++) {
if (p[i] == 1) {
p[i] = 0;
} else {
if (p[i] == 0) {
p[i] = 1;
}
}
}
}
if (print_hex) {
p = (buf + offset);
char hex[MAX_DEMODULATION_BITS + 1] = { 0x00 };
int num_bits = binarray_2_hex(hex, sizeof(hex), (char *)p, len);
if (num_bits == 0) {
p = NULL;
free(buf);
return PM3_ESOFT;
}
PrintAndLogEx(SUCCESS, "DemodBuffer:\n%s", hex);
} else {
PrintAndLogEx(SUCCESS, "DemodBuffer:\n%s", sprint_bytebits_bin_break(buf + offset, len, 32));
}
p = NULL;
free(buf);
return PM3_SUCCESS;
}
int CmdPrintDemodBuff(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data print",
"Print the data in the DemodBuffer as hex or binary.\n"
"Defaults to binary output",
"data print"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("i", "inv", "invert DemodBuffer before printing"),
// arg_int0("l","len", "<dec>", "length to print in # of bits or hex characters respectively"),
arg_int0("o", "offset", "<dec>", "offset in # of bits"),
arg_lit0("s", "strip", "strip leading zeroes, i.e. set offset to first bit equal to one"),
arg_lit0("x", "hex", "output in hex (omit for binary output)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool invert = arg_get_lit(ctx, 1);
int os = arg_get_int_def(ctx, 2, 0);
bool lstrip = arg_get_lit(ctx, 3);
bool print_hex = arg_get_lit(ctx, 4);
CLIParserFree(ctx);
uint8_t offset = (os & 0xFF);
return printDemodBuff(offset, lstrip, invert, print_hex);
}
// this function strictly converts >1 to 1 and <1 to 0 for each sample in the graphbuffer
int CmdGetBitStream(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data getbitstream",
"Convert GraphBuffer's value accordingly\n"
" - larger or equal to ONE becomes ONE\n"
" - less than ONE becomes ZERO",
"data getbitstream"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
CmdHpf("");
for (uint32_t i = 0; i < g_GraphTraceLen; i++) {
g_GraphBuffer[i] = (g_GraphBuffer[i] >= 1) ? 1 : 0;
}
RepaintGraphWindow();
return PM3_SUCCESS;
}
static int CmdConvertBitStream(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data convertbitstream",
"Convert GraphBuffer's 0|1 values to 127|-127",
"data convertbitstream"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
if (isGraphBitstream()) {
convertGraphFromBitstream();
} else {
// get high, low
convertGraphFromBitstreamEx(-126, -127);
}
return PM3_SUCCESS;
}
// Cmd Args: Clock, invert, maxErr, maxLen as integers and amplify as char == 'a'
// (amp may not be needed anymore)
// verbose will print results and demoding messages
// emSearch will auto search for EM410x format in bitstream
// askType switches decode: ask/raw = 0, ask/manchester = 1
int ASKDemod_ext(int clk, int invert, int maxErr, size_t maxlen, bool amplify, bool verbose, bool emSearch, uint8_t askType, bool *stCheck) {
PrintAndLogEx(DEBUG, "DEBUG: (ASKDemod_ext) clk %i invert %i maxErr %i maxLen %zu amplify %i verbose %i emSearch %i askType %i "
, clk
, invert
, maxErr
, maxlen
, amplify
, verbose
, emSearch
, askType
);
uint8_t askamp = 0;
if (maxlen == 0)
maxlen = g_pm3_capabilities.bigbuf_size;
uint8_t *bits = calloc(MAX_GRAPH_TRACE_LEN, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t bitlen = getFromGraphBuffer(bits);
PrintAndLogEx(DEBUG, "DEBUG: (ASKDemod_ext) #samples from graphbuff: %zu", bitlen);
if (bitlen < 255) {
free(bits);
return PM3_ESOFT;
}
if (maxlen < bitlen && maxlen != 0)
bitlen = maxlen;
int foundclk = 0;
//amplify signal before ST check
if (amplify) {
askAmp(bits, bitlen);
}
size_t ststart = 0, stend = 0;
// if (*stCheck)
bool st = DetectST(bits, &bitlen, &foundclk, &ststart, &stend);
if (clk == 0) {
if (foundclk == 32 || foundclk == 64) {
clk = foundclk;
}
}
if (st) {
*stCheck = st;
g_MarkerC.pos = ststart;
g_MarkerD.pos = stend;
if (verbose)
PrintAndLogEx(DEBUG, "Found Sequence Terminator - First one is shown by orange / blue graph markers");
}
int start_idx = 0;
int errCnt = askdemod_ext(bits, &bitlen, &clk, &invert, maxErr, askamp, askType, &start_idx);
if (start_idx >= clk / 2) {
start_idx -= clk / 2;
}
if (askType == 0) { // if not Manchester, clock width is halved
clk /= 2;
}
if (errCnt < 0 || bitlen < 16) { //if fatal error (or -1)
PrintAndLogEx(DEBUG, "DEBUG: (ASKDemod_ext) No data found errors:%d, %s bitlen:%zu, clock:%i"
, errCnt
, (invert) ? "inverted," : ""
, bitlen
, clk
);
free(bits);
return PM3_ESOFT;
}
if (errCnt > maxErr) {
PrintAndLogEx(DEBUG, "DEBUG: (ASKDemod_ext) Too many errors found, errors:%d, bits:%zu, clock:%i"
, errCnt
, bitlen
, clk
);
free(bits);
return PM3_ESOFT;
}
if (verbose) {
PrintAndLogEx(DEBUG, "DEBUG: (ASKDemod_ext) using clock:%i, %sbits found:%zu, start index %d"
, clk
, (invert) ? "inverted, " : ""
, bitlen
, start_idx
);
}
//output
setDemodBuff(bits, bitlen, 0);
setClockGrid(clk, start_idx);
if (verbose) {
if (errCnt > 0)
PrintAndLogEx(DEBUG, "# Errors during demoding (shown as 7 in bit stream)... " _RED_("%d"), errCnt);
if (askType) {
PrintAndLogEx(SUCCESS, _YELLOW_("ASK/Manchester") " - clock " _YELLOW_("%i") " - decoded bitstream", clk);
PrintAndLogEx(INFO, "-----------------------------------------------");
} else {
PrintAndLogEx(SUCCESS, _YELLOW_("ASK/Raw") " - clock " _YELLOW_("%i") " - decoded bitstream", clk);
PrintAndLogEx(INFO, "----------------------------------------");
}
printDemodBuff(0, false, false, false);
}
uint64_t lo = 0;
uint32_t hi = 0;
if (emSearch)
AskEm410xDecode(true, &hi, &lo);
free(bits);
return PM3_SUCCESS;
}
int ASKDemod(int clk, int invert, int maxErr, size_t maxlen, bool amplify, bool verbose, bool emSearch, uint8_t askType) {
bool st = false;
return ASKDemod_ext(clk, invert, maxErr, maxlen, amplify, verbose, emSearch, askType, &st);
}
// takes 5 arguments - clock, invert, maxErr, maxLen as integers and amplify as char == 'a'
// attempts to demodulate ask while decoding manchester
// prints binary found and saves in graphbuffer for further commands
static int Cmdaskmandemod(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data rawdemod --am",
"ASK/MANCHESTER demodulate the data in the GraphBuffer and output binary",
"data rawdemod --am --> demod a ask/manchester tag, using autodetect\n"
"data rawdemod --am -c 32 --> demod a ask/manchester tag, using a clock of RF/32\n"
"data rawdemod --am -i --> demod a ask/manchester tag, using autodetect, invert output\n"
"data rawdemod --am -c 32 -i --> demod a ask/manchester tag, using a clock of RF/32, invert output\n"
"data rawdemod --am -c 64 -i --max 0 --> demod a ask/manchester tag, using a clock of RF/64, inverting and allowing 0 demod errors\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("a", "amp", "try attempt demod with ask amplification (def no amp)"),
arg_int0("c", "clk", "<dec>", "set clock manually (def autodetect)"),
arg_lit0("i", "inv", "invert output"),
arg_lit0("s", "st", "check for sequence terminator"),
arg_int0(NULL, "max", "<dec>", "maximum allowed errors (def 100)"),
arg_int0(NULL, "samples", "<dec>", "maximum samples to read (def 32768) [512 bits at RF/64]"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool amplify = arg_get_lit(ctx, 1);
uint16_t clk = (uint16_t)arg_get_int_def(ctx, 2, 0);
bool invert = arg_get_lit(ctx, 3);
bool st = arg_get_lit(ctx, 4);
uint8_t max_err = (uint8_t)arg_get_int_def(ctx, 5, 100) & 0xFF;
size_t max_len = (size_t)arg_get_int_def(ctx, 6, 0) & 0xFF;
CLIParserFree(ctx);
return ASKDemod_ext(clk, invert, max_err, max_len, amplify, true, true, 1, &st);
}
// manchester decode
// strictly take 10 and 01 and convert to 0 and 1
static int Cmdmandecoderaw(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data manrawdecode",
"Manchester decode binary stream in DemodBuffer\n"
"Converts 10 and 01 and converts to 0 and 1 respectively\n"
" - must have binary sequence in DemodBuffer (run `data rawdemod --ar` before)",
"data manrawdecode"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("i", "inv", "invert output"),
arg_int0(NULL, "err", "<dec>", "set max errors tolerated (def 20)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool invert = arg_get_lit(ctx, 1);
int max_err = arg_get_int_def(ctx, 2, 20);
CLIParserFree(ctx);
if (g_DemodBufferLen == 0) {
PrintAndLogEx(WARNING, "DemodBuffer empty, run " _YELLOW_("`data rawdemod --ar`"));
return PM3_ESOFT;
}
uint8_t *bits = calloc(MAX_DEMOD_BUF_LEN, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
// make sure its just binary data 0|1|7 in buffer
int high = 0, low = 0;
size_t i = 0;
for (; i < g_DemodBufferLen; ++i) {
if (g_DemodBuffer[i] > high)
high = g_DemodBuffer[i];
else if (g_DemodBuffer[i] < low)
low = g_DemodBuffer[i];
bits[i] = g_DemodBuffer[i];
}
if (high > 7 || low < 0) {
PrintAndLogEx(ERR, "Error: please first raw demod then manchester raw decode");
free(bits);
return PM3_ESOFT;
}
size_t size = i;
uint8_t offset = 0;
uint16_t err_cnt = manrawdecode(bits, &size, invert, &offset);
if (err_cnt > max_err) {
PrintAndLogEx(ERR, "Too many errors attempting to decode " _RED_("%i"), err_cnt);
free(bits);
return PM3_ESOFT;
}
if (err_cnt > 0) {
PrintAndLogEx(WARNING, "# %i errors found during demod (shown as " _YELLOW_(".")" in bit stream) ", err_cnt);
}
PrintAndLogEx(INFO, "Manchester decoded %s", (invert) ? "( inverted )" : "");
PrintAndLogEx(INFO, "%s", sprint_bytebits_bin_break(bits, size, 32));
// try decode EM410x
if (err_cnt == 0) {
uint64_t id = 0;
uint32_t hi = 0;
size_t idx = 0;
size_t tmpsize = 0;
int res = Em410xDecode(bits, &tmpsize, &idx, &hi, &id);
if (res > 0) {
//need to adjust to set bitstream back to manchester encoded data
//setDemodBuff(bits, size, idx);
printEM410x(hi, id, false, res);
size = tmpsize;
}
}
setDemodBuff(bits, size, 0);
setClockGrid(g_DemodClock * 2, g_DemodStartIdx);
free(bits);
return PM3_SUCCESS;
}
/*
* @author marshmellow
* biphase decode
* decodes 01 or 10 -> ZERO
* 11 or 00 -> ONE
* param offset adjust start position
* param invert invert output
* param masxErr maximum tolerated errors
*/
static int CmdBiphaseDecodeRaw(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data biphaserawdecode",
"Biphase decode binary stream in DemodBuffer\n"
"Converts 10 or 01 -> 1 and 11 or 00 -> 0\n"
" - must have binary sequence in DemodBuffer (run `data rawdemod --ar` before)\n"
" - invert for Conditional Dephase Encoding (CDP) AKA Differential Manchester",
"data biphaserawdecode --> decode biphase bitstream from the DemodBuffer\n"
"data biphaserawdecode -oi --> decode biphase bitstream from the DemodBuffer, adjust offset, and invert output"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("o", "offset", "set to adjust decode start position"),
arg_lit0("i", "inv", "invert output"),
arg_int0(NULL, "err", "<dec>", "set max errors tolerated (def 20)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int offset = arg_get_lit(ctx, 1);
bool invert = arg_get_lit(ctx, 2);
int max_err = arg_get_int_def(ctx, 3, 20);
CLIParserFree(ctx);
if (g_DemodBufferLen == 0) {
PrintAndLogEx(WARNING, "DemodBuffer empty, run " _YELLOW_("`data rawdemod --ar`"));
return PM3_ESOFT;
}
uint8_t *bits = calloc(MAX_DEMOD_BUF_LEN, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = MAX_DEMOD_BUF_LEN;
if (!getDemodBuff(bits, &size)) {
free(bits);
return PM3_ESOFT;
}
int err_cnt = BiphaseRawDecode(bits, &size, &offset, invert);
if (err_cnt < 0) {
PrintAndLogEx(ERR, "Error during decode " _RED_("%i"), err_cnt);
free(bits);
return PM3_ESOFT;
}
if (err_cnt > max_err) {
PrintAndLogEx(ERR, "Too many errors attempting to decode " _RED_("%i"), err_cnt);
free(bits);
return PM3_ESOFT;
}
if (err_cnt > 0) {
PrintAndLogEx(WARNING, "# %i errors found during demod (shown as " _YELLOW_(".")" in bit stream) ", err_cnt);
}
PrintAndLogEx(INFO, "Biphase decoded using offset %d%s", offset, (invert) ? "( inverted )" : "");
PrintAndLogEx(INFO, "%s", sprint_bytebits_bin_break(bits, size, 32));
setDemodBuff(bits, size, 0);
setClockGrid(g_DemodClock * 2, g_DemodStartIdx + g_DemodClock * offset);
free(bits);
return PM3_SUCCESS;
}
// ASK Demod then Biphase decode g_GraphBuffer samples
int ASKbiphaseDemod(int offset, int clk, int invert, int maxErr, bool verbose) {
//ask raw demod g_GraphBuffer first
uint8_t *bs = calloc(MAX_DEMOD_BUF_LEN, sizeof(uint8_t));
if (bs == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = getFromGraphBufferEx(bs, MAX_DEMOD_BUF_LEN);
if (size == 0) {
PrintAndLogEx(DEBUG, "DEBUG: no data in graphbuf");
free(bs);
return PM3_ESOFT;
}
int startIdx = 0;
//invert here inverts the ask raw demoded bits which has no effect on the demod, but we need the pointer
int errCnt = askdemod_ext(bs, &size, &clk, &invert, maxErr, 0, 0, &startIdx);
if (errCnt < 0 || errCnt > maxErr) {
PrintAndLogEx(DEBUG, "DEBUG: no data or error found %d, clock: %d", errCnt, clk);
free(bs);
return PM3_ESOFT;
}
//attempt to Biphase decode BitStream
errCnt = BiphaseRawDecode(bs, &size, &offset, invert);
if (errCnt < 0) {
if (g_debugMode || verbose) PrintAndLogEx(DEBUG, "DEBUG: Error BiphaseRawDecode: %d", errCnt);
free(bs);
return PM3_ESOFT;
}
if (errCnt > maxErr) {
if (g_debugMode || verbose) PrintAndLogEx(DEBUG, "DEBUG: Error BiphaseRawDecode too many errors: %d", errCnt);
free(bs);
return PM3_ESOFT;
}
if (offset >= 1) {
offset -= 1;
}
//success set g_DemodBuffer and return
setDemodBuff(bs, size, 0);
setClockGrid(clk, startIdx + clk * offset / 2);
if (g_debugMode || verbose) {
PrintAndLogEx(DEBUG, "Biphase Decoded using offset %d | clock %d | #errors %d | start index %d\ndata\n", offset, clk, errCnt, (startIdx + clk * offset / 2));
printDemodBuff(offset, false, false, false);
}
free(bs);
return PM3_SUCCESS;
}
// see ASKbiphaseDemod
static int Cmdaskbiphdemod(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data rawdemod --ab",
"ASK/BIPHASE demodulate the data in the GraphBuffer and output binary\n"
"NOTE, `--invert` for Conditional Dephase Encoding (CDP) AKA Differential Manchester\n",
"data rawdemod --ab --> demod a ask/biphase tag, using autodetect\n"
"data rawdemod --ab -c 32 --> demod a ask/biphase tag, using a clock of RF/32\n"
"data rawdemod --ab -i --> demod a ask/biphase tag, using autodetect, invert output\n"
"data rawdemod --ab -c 32 -i --> demod a ask/biphase tag, using a clock of RF/32, invert output\n"
"data rawdemod --ab -c 64 -i --max 0 --> demod a ask/biphase tag, using a clock of RF/64, inverting and allowing 0 demod errors\n"
);
void *argtable[] = {
arg_param_begin,
arg_int0("c", "clk", "<dec>", "set clock manually (def autodetect)"),
arg_lit0("i", "inv", "invert output"),
arg_int0("o", "offset", "<dec>", "offset to begin biphase (def 0)"),
arg_int0(NULL, "max", "<dec>", "maximum allowed errors (def 50)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint16_t clk = (uint16_t)arg_get_int_def(ctx, 1, 0);
bool invert = arg_get_lit(ctx, 2);
int offset = arg_get_int_def(ctx, 3, 0);
uint8_t max_err = (uint8_t)arg_get_int_def(ctx, 4, 50) & 0xFF;
CLIParserFree(ctx);
return ASKbiphaseDemod(offset, clk, invert, max_err, true);
}
// see ASKDemod
static int Cmdaskrawdemod(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data rawdemod --ar",
"ASK/RAW demodulate the data in the GraphBuffer and output binary",
"data rawdemod --ar -a --> demod a ask tag, using autodetect, amplified\n"
"data rawdemod --ar -c 32 --> demod a ask tag, using a clock of RF/32\n"
"data rawdemod --ar -i --> demod a ask tag, using autodetect, invert output\n"
"data rawdemod --ar -c 32 -i --> demod a ask tag, using a clock of RF/32, invert output\n"
"data rawdemod --ar -c 64 -i --max 0 --> demod a ask tag, using a clock of RF/64, inverting and allowing 0 demod errors\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("a", "amp", "try attempt demod with ask amplification (def no amp)"),
arg_int0("c", "clk", "<dec>", "set clock manually (def autodetect)"),
arg_lit0("i", "inv", "invert output"),
arg_lit0("s", "st", "check for sequence terminator"),
arg_int0(NULL, "max", "<dec>", "maximum allowed errors (def 100)"),
arg_int0(NULL, "samples", "<dec>", "maximum samples to read (def 32768) [512 bits at RF/64]"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool amplify = arg_get_lit(ctx, 1);
uint16_t clk = (uint16_t)arg_get_int_def(ctx, 2, 0);
bool invert = arg_get_lit(ctx, 3);
bool st = arg_get_lit(ctx, 4);
uint8_t max_err = (uint8_t)arg_get_int_def(ctx, 5, 100) & 0xFF;
size_t max_len = (size_t)arg_get_int_def(ctx, 6, 0) & 0xFF;
CLIParserFree(ctx);
return ASKDemod_ext(clk, invert, max_err, max_len, amplify, true, false, 0, &st);
}
int AutoCorrelate(const int *in, int *out, size_t len, size_t window, bool SaveGrph, bool verbose) {
// sanity check
if (window > len) {
window = len;
}
//test
double autocv = 0.0; // Autocovariance value
size_t correlation = 0;
int lastmax = 0;
// in, len, 4000
double mean = compute_mean(in, len);
// Computed variance
double variance = compute_variance(in, len);
int *correl_buf = calloc(MAX_GRAPH_TRACE_LEN, sizeof(int));
if (correl_buf == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return -1;
}
uint8_t peak_cnt = 0;
size_t peaks[10] = {0};
for (size_t i = 0; i < len - window; ++i) {
for (size_t j = 0; j < (len - i); j++) {
autocv += (in[j] - mean) * (in[j + i] - mean);
}
autocv = (1.0 / (len - i)) * autocv;
correl_buf[i] = autocv;
// Computed autocorrelation value to be returned
// Autocorrelation is autocovariance divided by variance
double ac_value = autocv / variance;
// keep track of which distance is repeating.
// A value near 1.0 or more indicates a correlation in the signal
if (ac_value > 0.95f) {
correlation = i - lastmax;
lastmax = i;
if ((correlation > 1) && peak_cnt < ARRAYLEN(peaks)) {
peaks[peak_cnt++] = correlation;
}
}
}
// Find shorts distance between peaks
int distance = -1;
for (size_t i = 0; i < ARRAYLEN(peaks); ++i) {
PrintAndLogEx(DEBUG, "%zu | %zu", i, peaks[i]);
if (peaks[i] < 128) {
continue;
}
if (distance == -1) {
distance = peaks[i];
continue;
}
if (peaks[i] < distance) {
distance = peaks[i];
}
}
if (distance > -1) {
if (verbose) {
PrintAndLogEx(SUCCESS, "Possible correlation at "_YELLOW_("%4d") " samples", distance);
}
} else {
PrintAndLogEx(HINT, "Hint: No repeating pattern found, try increasing window size");
free(correl_buf);
// return value -1, indication to increase window size
return -1;
}
if (SaveGrph) {
//g_GraphTraceLen = g_GraphTraceLen - window;
memcpy(out, correl_buf, len * sizeof(int));
setClockGrid(distance, 0);
g_DemodBufferLen = 0;
RepaintGraphWindow();
}
free(correl_buf);
return distance;
}
static int CmdAutoCorr(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data autocorr",
"Autocorrelate over window is used to detect repeating sequences.\n"
"We use it as detection of how long in bits a message inside the signal is",
"data autocorr -w 4000\n"
"data autocorr -w 4000 -g"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("g", NULL, "save back to GraphBuffer (overwrite)"),
arg_u64_0("w", "win", "<dec>", "window length for correlation. def 4000"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool updateGrph = arg_get_lit(ctx, 1);
uint32_t window = arg_get_u32_def(ctx, 2, 4000);
CLIParserFree(ctx);
PrintAndLogEx(INFO, "Using window size " _YELLOW_("%u"), window);
if (g_GraphTraceLen == 0) {
PrintAndLogEx(WARNING, "GraphBuffer is empty");
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("lf read") "` to collect samples");
return PM3_ESOFT;
}
if (window >= g_GraphTraceLen) {
PrintAndLogEx(WARNING, "window must be smaller than trace ( " _YELLOW_("%zu") " samples )", g_GraphTraceLen);
return PM3_EINVARG;
}
AutoCorrelate(g_GraphBuffer, g_GraphBuffer, g_GraphTraceLen, window, updateGrph, true);
return PM3_SUCCESS;
}
static int CmdBitsamples(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data bitsamples",
"Get raw samples from device as bitstring",
"data bitsamples"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
int cnt = 0;
uint8_t got[12288];
if (!GetFromDevice(BIG_BUF, got, sizeof(got), 0, NULL, 0, NULL, 2500, false)) {
PrintAndLogEx(WARNING, "command execution time out");
return PM3_ETIMEOUT;
}
for (size_t j = 0; j < ARRAYLEN(got); j++) {
for (uint8_t k = 0; k < 8; k++) {
if (got[j] & (1 << (7 - k)))
g_GraphBuffer[cnt++] = 1;
else
g_GraphBuffer[cnt++] = 0;
}
}
g_GraphTraceLen = cnt;
RepaintGraphWindow();
return PM3_SUCCESS;
}
static int CmdBuffClear(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data clear",
"This function clears the BigBuf on device side\n"
"and graph window ( graphbuffer )",
"data clear"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
clearCommandBuffer();
SendCommandNG(CMD_BUFF_CLEAR, NULL, 0);
ClearGraph(true);
// iceman: should clear all other new buffers getting introduced
return PM3_SUCCESS;
}
static int CmdDecimate(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data decimate",
"Performs decimation, by reducing samples N times in the grapbuf. Good for PSK\n",
"data decimate\n"
"data decimate -n 4"
);
void *argtable[] = {
arg_param_begin,
arg_int0("n", NULL, "<dec>", "factor to reduce sample set (default 2)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int n = arg_get_int_def(ctx, 1, 2);
CLIParserFree(ctx);
for (size_t i = 0; i < (g_GraphTraceLen / n); ++i)
g_GraphBuffer[i] = g_GraphBuffer[i * n];
g_GraphTraceLen /= n;
PrintAndLogEx(SUCCESS, "decimated by " _GREEN_("%u"), n);
RepaintGraphWindow();
return PM3_SUCCESS;
}
/**
* Undecimate - I'd call it 'interpolate', but we'll save that
* name until someone does an actual interpolation command, not just
* blindly repeating samples
* @param Cmd
* @return
*/
static int CmdUndecimate(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data undecimate",
"Performs un-decimation, by repeating each sample N times in the graphbuf",
"data undecimate\n"
"data undecimate -n 4\n"
);
void *argtable[] = {
arg_param_begin,
arg_int0("n", NULL, "<dec>", "factor to repeat each sample (default 2)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int factor = arg_get_int_def(ctx, 1, 2);
CLIParserFree(ctx);
//We have memory, don't we?
int *swap = calloc(MAX_GRAPH_TRACE_LEN, sizeof(int));
if (swap == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
uint32_t g_index = 0, s_index = 0;
while (g_index < g_GraphTraceLen && s_index + factor < MAX_GRAPH_TRACE_LEN) {
int count = 0;
for (count = 0; count < factor && s_index + count < MAX_GRAPH_TRACE_LEN; count++) {
swap[s_index + count] = (
(double)(factor - count) / (factor - 1)) * g_GraphBuffer[g_index] +
((double)count / factor) * g_GraphBuffer[g_index + 1]
;
}
s_index += count;
g_index++;
}
memcpy(g_GraphBuffer, swap, s_index * sizeof(int));
g_GraphTraceLen = s_index;
RepaintGraphWindow();
free(swap);
return PM3_SUCCESS;
}
// shift graph zero up or down based on input + or -
static int CmdGraphShiftZero(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data shiftgraphzero",
"Shift 0 for Graphed wave + or - shift value",
"data shiftgraphzero -n 10 --> shift 10 points\n"
"data shiftgraphzero -n -22 --> shift negative 22 points"
);
void *argtable[] = {
arg_param_begin,
arg_int1("n", NULL, "<dec>", "shift + or -"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int shift = arg_get_int_def(ctx, 1, 0);
CLIParserFree(ctx);
for (size_t i = 0; i < g_GraphTraceLen; i++) {
int shiftedVal = g_GraphBuffer[i] + shift;
if (shiftedVal > 127)
shiftedVal = 127;
else if (shiftedVal < -127)
shiftedVal = -127;
g_GraphBuffer[i] = shiftedVal;
}
CmdNorm("");
return PM3_SUCCESS;
}
int AskEdgeDetect(const int *in, int *out, int len, int threshold) {
int last = 0;
for (int i = 1; i < len; i++) {
if (in[i] - in[i - 1] >= threshold) //large jump up
last = 127;
else if (in[i] - in[i - 1] <= -1 * threshold) //large jump down
last = -127;
out[i - 1] = last;
}
return PM3_SUCCESS;
}
// use large jumps in read samples to identify edges of waves and then amplify that wave to max
// similar to dirtheshold, threshold commands
// takes a threshold length which is the measured length between two samples then determines an edge
static int CmdAskEdgeDetect(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data askedgedetect",
"Adjust Graph for manual ASK demod using the length of sample differences\n"
"to detect the edge of a wave",
"data askedgedetect -t 20"
);
void *argtable[] = {
arg_param_begin,
arg_int0("t", "thres", "<dec>", "threshold, use 20 - 45 (def 25)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int threshold = arg_get_int_def(ctx, 1, 25);
CLIParserFree(ctx);
PrintAndLogEx(INFO, "using threshold " _YELLOW_("%i"), threshold);
int res = AskEdgeDetect(g_GraphBuffer, g_GraphBuffer, g_GraphTraceLen, threshold);
RepaintGraphWindow();
return res;
}
// Clock detection for `data detectclock`.
static int detectclock_fit(int mod_mask, pm3_hyp_t *hyp) {
if (g_GraphTraceLen < 2048) {
return PM3_ESOFT;
}
const size_t count = (g_GraphTraceLen < FITSCORE_DEFAULT_WINDOW)
? g_GraphTraceLen
: FITSCORE_DEFAULT_WINDOW;
double *sig = pm3_extract(g_GraphBuffer, g_GraphTraceLen, 0, count);
if (sig == NULL) {
return PM3_EMALLOC;
}
pm3_fit_opts_t opts = {0};
opts.mod_mask = mod_mask;
pm3_fit_t fit;
int res = pm3_fit_run(sig, count, &opts, &fit);
free(sig);
if (res != PM3_SUCCESS) {
return res;
}
*hyp = fit.items[0];
pm3_fit_free(&fit);
return PM3_SUCCESS;
}
static int detectclock_print(int mod_mask, bool verbose) {
pm3_hyp_t hyp;
if (detectclock_fit(mod_mask, &hyp) != PM3_SUCCESS) {
return 0;
}
const int clk = (int)(hyp.clk_fine + 0.5);
if (clk <= 0) {
return 0;
}
if (verbose) {
if (hyp.mod == PM3_MOD_FSK) {
PrintAndLogEx(SUCCESS, "Detected Field Clocks: FC/%d, FC/%d - Bit Clock: RF/%d"
, hyp.fc_hi
, hyp.fc_lo
, clk
);
} else if (hyp.mod == PM3_MOD_ASK) {
PrintAndLogEx(SUCCESS, "Auto-detected clock rate: %d, Best Starting Position: %d", clk, hyp.phase);
} else {
PrintAndLogEx(SUCCESS, "Auto-detected clock rate: %d", clk);
}
}
setClockGrid(clk, hyp.phase);
return clk;
}
static int CmdDetectClockRate(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data detectclock",
"Detect ASK, FSK, NRZ, PSK clock rate of wave in GraphBuffer",
"data detectclock --ask\n"
"data detectclock --nzr --> detect clock of an nrz/direct wave in GraphBuffer\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0(NULL, "ask", "specify ASK modulation clock detection"),
arg_lit0(NULL, "fsk", "specify FSK modulation clock detection"),
arg_lit0(NULL, "nzr", "specify NZR/DIRECT modulation clock detection"),
arg_lit0(NULL, "psk", "specify PSK modulation clock detection"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool a = arg_get_lit(ctx, 1);
bool f = arg_get_lit(ctx, 2);
bool n = arg_get_lit(ctx, 3);
bool p = arg_get_lit(ctx, 4);
CLIParserFree(ctx);
int tmp = (a + f + n + p);
if (tmp > 1) {
PrintAndLogEx(WARNING, "Only specify one modulation");
return PM3_EINVARG;
} else if (tmp == 0) {
// no modulation asked for, so report whichever one the signal is
pm3_hyp_t hyp;
if (detectclock_fit(0, &hyp) == PM3_SUCCESS) {
const int clk = (int)(hyp.clk_fine + 0.5);
if (clk > 0) {
PrintAndLogEx(SUCCESS, "%s Clock... %d", pm3_mod_name(hyp.mod), clk);
setClockGrid(clk, hyp.phase);
return PM3_SUCCESS;
}
}
// nothing fit, fall back to what the old heuristics can offer
int clock = GetFskClock("", false);
if (clock > 0) {
PrintAndLogEx(SUCCESS, "FSK Clock... %d", clock);
}
clock = GetAskClock("", false);
if (clock > 0) {
PrintAndLogEx(SUCCESS, "ASK Clock... %d", clock);
}
clock = GetNrzClock("", false);
if (clock > 0) {
PrintAndLogEx(SUCCESS, "NRZ Clock... %d", clock);
}
clock = GetPskClock("", false);
if (clock > 0) {
PrintAndLogEx(SUCCESS, "PSK Clock... %d", clock);
}
return PM3_SUCCESS;
}
if (a && detectclock_print(1 << PM3_MOD_ASK, true) == 0) {
GetAskClock("", true);
}
if (f && detectclock_print(1 << PM3_MOD_FSK, true) == 0) {
GetFskClock("", true);
}
if (n && detectclock_print(1 << PM3_MOD_NRZ, true) == 0) {
GetNrzClock("", true);
}
if (p && detectclock_print(1 << PM3_MOD_PSK, true) == 0) {
GetPskClock("", true);
}
RepaintGraphWindow();
return PM3_SUCCESS;
}
static char *GetFSKType(uint8_t fchigh, uint8_t fclow, uint8_t invert) {
static char fType[8];
memset(fType, 0x00, 8);
char *fskType = fType;
if (fchigh == 10 && fclow == 8) {
if (invert)
memcpy(fskType, "FSK2a", 5);
else
memcpy(fskType, "FSK2", 4);
} else if (fchigh == 8 && fclow == 5) {
if (invert)
memcpy(fskType, "FSK1", 4);
else
memcpy(fskType, "FSK1a", 5);
} else {
memcpy(fskType, "FSK??", 5);
}
return fskType;
}
// fsk raw demod and print binary
// takes 4 arguments - Clock, invert, fchigh, fclow
// defaults: clock = 50, invert=1, fchigh=10, fclow=8 (RF/10 RF/8 (fsk2a))
int FSKrawDemod(uint8_t rfLen, uint8_t invert, uint8_t fchigh, uint8_t fclow, bool verbose) {
//raw fsk demod no manchester decoding no start bit finding just get binary from wave
if (getSignalProperties()->isnoise) {
if (verbose) {
PrintAndLogEx(INFO, "signal looks like noise");
}
return PM3_ESOFT;
}
uint8_t *bits = calloc(MAX_GRAPH_TRACE_LEN, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t bitlen = getFromGraphBuffer(bits);
if (bitlen == 0) {
PrintAndLogEx(DEBUG, "DEBUG: no data in graphbuf");
free(bits);
return PM3_ESOFT;
}
//get field clock lengths
if (!fchigh || !fclow) {
uint16_t fcs = countFC(bits, bitlen, true);
if (!fcs) {
fchigh = 10;
fclow = 8;
} else {
fchigh = (fcs >> 8) & 0x00FF;
fclow = fcs & 0x00FF;
}
}
//get bit clock length
if (!rfLen) {
int firstClockEdge = 0; //todo - align grid on graph with this...
rfLen = detectFSKClk(bits, bitlen, fchigh, fclow, &firstClockEdge);
if (!rfLen) rfLen = 50;
}
int start_idx = 0;
int size = fskdemod(bits, bitlen, rfLen, invert, fchigh, fclow, &start_idx);
if (size > 0) {
setDemodBuff(bits, size, 0);
setClockGrid(rfLen, start_idx);
// Now output the bitstream to the scrollback by line of 16 bits
if (verbose || g_debugMode) {
PrintAndLogEx(DEBUG, "DEBUG: (FSKrawDemod) using clock:%u, %sfc high:%u, fc low:%u"
, rfLen
, (invert) ? "inverted, " : ""
, fchigh
, fclow
);
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, _YELLOW_("%s") " decoded bitstream", GetFSKType(fchigh, fclow, invert));
PrintAndLogEx(INFO, "-----------------------");
printDemodBuff(0, false, false, false);
}
goto out;
} else {
PrintAndLogEx(DEBUG, "no FSK data found");
}
out:
free(bits);
return PM3_SUCCESS;
}
// fsk raw demod and print binary
// takes 4 arguments - Clock, invert, fchigh, fclow
// defaults: clock = 50, invert=1, fchigh=10, fclow=8 (RF/10 RF/8 (fsk2a))
static int CmdFSKrawdemod(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data rawdemod --fs",
"FSK demodulate the data in the GraphBuffer and output binary",
"data rawdemod --fs --> demod an fsk tag, using autodetect\n"
"data rawdemod --fs -c 32 --> demod an fsk tag, using a clock of RF/32, autodetect fc\n"
"data rawdemod --fs -i --> demod an fsk tag, using autodetect, invert output\n"
"data rawdemod --fs -c 32 -i --> demod an fsk tag, using a clock of RF/32, invert output, autodetect fc\n"
"data rawdemod --fs -c 64 --hi 8 --lo 5 --> demod an fsk1 RF/64 tag\n"
"data rawdemod --fs -c 50 --hi 10 --lo 8 --> demod an fsk2 RF/50 tag\n"
"data rawdemod --fs -c 50 -i --hi 10 --lo 8 --> demod an fsk2a RF/50 tag\n"
);
void *argtable[] = {
arg_param_begin,
arg_int0("c", "clk", "<dec>", "set clock manually (def: autodetect)"),
arg_lit0("i", "inv", "invert output"),
arg_int0(NULL, "hi", "<dec>", "larger field clock length (def: autodetect)"),
arg_int0(NULL, "lo", "<dec>", "small field clock length (def: autodetect)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint8_t clk = (uint8_t)arg_get_int_def(ctx, 1, 0) & 0xFF;
bool invert = arg_get_lit(ctx, 2);
uint8_t fchigh = (uint8_t)arg_get_int_def(ctx, 3, 0) & 0xFF;
uint8_t fclow = (uint8_t)arg_get_int_def(ctx, 4, 0) & 0xFF;
CLIParserFree(ctx);
return FSKrawDemod(clk, invert, fchigh, fclow, true);
}
// attempt to psk1 demod graph buffer
int PSKDemod(int clk, int invert, int maxErr, bool verbose) {
if (getSignalProperties()->isnoise) {
if (verbose) {
PrintAndLogEx(INFO, "signal looks like noise");
}
return PM3_ESOFT;
}
uint8_t *bits = calloc(MAX_GRAPH_TRACE_LEN, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t bitlen = getFromGraphBuffer(bits);
if (bitlen == 0) {
free(bits);
return PM3_ESOFT;
}
int startIdx = 0;
int errCnt = pskRawDemod_ext(bits, &bitlen, &clk, &invert, &startIdx);
if (errCnt > maxErr) {
if (g_debugMode || verbose) PrintAndLogEx(DEBUG, "DEBUG: (PSKdemod) Too many errors found, clk: %d, invert: %d, numbits: %zu, errCnt: %d", clk, invert, bitlen, errCnt);
free(bits);
return PM3_ESOFT;
}
if (errCnt < 0 || bitlen < 16) { //throw away static - allow 1 and -1 (in case of threshold command first)
if (g_debugMode || verbose) PrintAndLogEx(DEBUG, "DEBUG: (PSKdemod) no data found, clk: %d, invert: %d, numbits: %zu, errCnt: %d", clk, invert, bitlen, errCnt);
free(bits);
return PM3_ESOFT;
}
if (verbose || g_debugMode) {
PrintAndLogEx(DEBUG, "DEBUG: (PSKdemod) Using Clock:%d, invert:%d, Bits Found:%zu", clk, invert, bitlen);
if (errCnt > 0) {
PrintAndLogEx(DEBUG, "DEBUG: (PSKdemod) errors during Demoding (shown as 7 in bit stream): %d", errCnt);
}
}
//prime g_DemodBuffer for output
setDemodBuff(bits, bitlen, 0);
setClockGrid(clk, startIdx);
free(bits);
return PM3_SUCCESS;
}
// takes 3 arguments - clock, invert, maxErr as integers
// attempts to demodulate nrz only
// prints binary found and saves in g_DemodBuffer for further commands
int NRZrawDemod(int clk, int invert, int maxErr, bool verbose) {
int errCnt = 0, clkStartIdx = 0;
if (getSignalProperties()->isnoise) {
if (verbose) {
PrintAndLogEx(INFO, "signal looks like noise");
}
return PM3_ESOFT;
}
uint8_t *bits = calloc(MAX_GRAPH_TRACE_LEN, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t bitlen = getFromGraphBuffer(bits);
if (bitlen == 0) {
free(bits);
return PM3_ESOFT;
}
errCnt = nrzRawDemod(bits, &bitlen, &clk, &invert, &clkStartIdx);
if (errCnt > maxErr) {
PrintAndLogEx(DEBUG, "DEBUG: (NRZrawDemod) Too many errors found, clk: %d, invert: %d, numbits: %zu, errCnt: %d", clk, invert, bitlen, errCnt);
free(bits);
return PM3_ESOFT;
}
if (errCnt < 0 || bitlen < 16) { //throw away static - allow 1 and -1 (in case of threshold command first)
PrintAndLogEx(DEBUG, "DEBUG: (NRZrawDemod) no data found, clk: %d, invert: %d, numbits: %zu, errCnt: %d", clk, invert, bitlen, errCnt);
free(bits);
return PM3_ESOFT;
}
if (verbose || g_debugMode) PrintAndLogEx(DEBUG, "DEBUG: (NRZrawDemod) Tried NRZ Demod using Clock: %d - invert: %d - Bits Found: %zu", clk, invert, bitlen);
//prime g_DemodBuffer for output
setDemodBuff(bits, bitlen, 0);
setClockGrid(clk, clkStartIdx);
if (errCnt > 0 && (verbose || g_debugMode)) PrintAndLogEx(DEBUG, "DEBUG: (NRZrawDemod) Errors during Demoding (shown as 7 in bit stream): %d", errCnt);
if (verbose || g_debugMode) {
PrintAndLogEx(SUCCESS, "NRZ demoded bitstream");
PrintAndLogEx(INFO, "---------------------");
// Now output the bitstream to the scrollback by line of 16 bits
printDemodBuff(0, false, invert, false);
}
free(bits);
return PM3_SUCCESS;
}
static int CmdNRZrawDemod(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data rawdemod --nr",
"NRZ/DIRECT demodulate the data in the GraphBuffer and output binary",
"data rawdemod --nr --> demod a nrz/direct tag, using autodetect\n"
"data rawdemod --nr -c 32 --> demod a nrz/direct tag, using a clock of RF/32\n"
"data rawdemod --nr -i --> demod a nrz/direct tag, using autodetect, invert output\n"
"data rawdemod --nr -c 32 -i --> demod a nrz/direct tag, using a clock of RF/32, invert output\n"
"data rawdemod --nr -c 64 -i --max 0 --> demod a nrz/direct tag, using a clock of RF/64, inverting and allowing 0 demod errors\n"
);
void *argtable[] = {
arg_param_begin,
arg_int0("c", "clk", "<dec>", "set clock manually (def autodetect)"),
arg_lit0("i", "inv", "invert output"),
arg_int0(NULL, "max", "<dec>", "maximum allowed errors (def 100)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint16_t clk = (uint16_t)arg_get_int_def(ctx, 1, 0);
bool invert = arg_get_lit(ctx, 2);
uint8_t max_err = (uint8_t)arg_get_int_def(ctx, 3, 100) & 0xFF;
CLIParserFree(ctx);
return NRZrawDemod(clk, invert, max_err, true);
}
// takes 3 arguments - clock, invert, max_err as integers
// attempts to demodulate psk only
// prints binary found and saves in g_DemodBuffer for further commands
int CmdPSK1rawDemod(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data rawdemod --p1",
"PSK1 demodulate the data in the GraphBuffer and output binary",
"data rawdemod --p1 --> demod a psk1 tag, using autodetect\n"
"data rawdemod --p1 -c 32 --> demod a psk1 tag, using a clock of RF/32\n"
"data rawdemod --p1 -i --> demod a psk1 tag, using autodetect, invert output\n"
"data rawdemod --p1 -c 32 -i --> demod a psk1 tag, using a clock of RF/32, invert output\n"
"data rawdemod --p1 -c 64 -i --max 0 --> demod a psk1 tag, using a clock of RF/64, inverting and allowing 0 demod errors\n"
);
void *argtable[] = {
arg_param_begin,
arg_int0("c", "clk", "<dec>", "set clock manually (def autodetect)"),
arg_lit0("i", "inv", "invert output"),
arg_int0(NULL, "max", "<dec>", "maximum allowed errors (def 100)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint16_t clk = (uint16_t)arg_get_int_def(ctx, 1, 0);
bool invert = arg_get_lit(ctx, 2);
uint8_t max_err = (uint8_t)arg_get_int_def(ctx, 3, 100) & 0xFF;
CLIParserFree(ctx);
int ans = PSKDemod(clk, invert, max_err, true);
//output
if (ans != PM3_SUCCESS) {
if (g_debugMode) PrintAndLogEx(ERR, "Error demoding: %d", ans);
return PM3_ESOFT;
}
PrintAndLogEx(SUCCESS, _YELLOW_("PSK1") " demoded bitstream");
PrintAndLogEx(INFO, "----------------------");
// Now output the bitstream to the scrollback by line of 16 bits
printDemodBuff(0, false, invert, false);
return PM3_SUCCESS;
}
// takes same args as cmdpsk1rawdemod
static int CmdPSK2rawDemod(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data rawdemod --p2",
"PSK2 demodulate the data in the GraphBuffer and output binary",
"data rawdemod --p2 --> demod a psk2 tag, using autodetect\n"
"data rawdemod --p2 -c 32 --> demod a psk2 tag, using a clock of RF/32\n"
"data rawdemod --p2 -i --> demod a psk2 tag, using autodetect, invert output\n"
"data rawdemod --p2 -c 32 -i --> demod a psk2 tag, using a clock of RF/32, invert output\n"
"data rawdemod --p2 -c 64 -i --max 0 --> demod a psk2 tag, using a clock of RF/64, inverting and allowing 0 demod errors\n"
);
void *argtable[] = {
arg_param_begin,
arg_int0("c", "clk", "<dec>", "set clock manually (def autodetect)"),
arg_lit0("i", "inv", "invert output"),
arg_int0(NULL, "max", "<dec>", "maximum allowed errors (def 100)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
uint8_t clk = (uint8_t)arg_get_int_def(ctx, 1, 0) & 0xFF;
bool invert = arg_get_lit(ctx, 2);
uint8_t max_err = (uint8_t)arg_get_int_def(ctx, 3, 100) & 0xFF;
CLIParserFree(ctx);
int ans = PSKDemod(clk, invert, max_err, true);
if (ans != PM3_SUCCESS) {
if (g_debugMode) PrintAndLogEx(ERR, "Error demoding: %d", ans);
return PM3_ESOFT;
}
psk1TOpsk2(g_DemodBuffer, g_DemodBufferLen);
PrintAndLogEx(SUCCESS, _YELLOW_("PSK2") " demoded bitstream");
PrintAndLogEx(INFO, "----------------------");
// Now output the bitstream to the scrollback by line of 16 bits
printDemodBuff(0, false, invert, false);
return PM3_SUCCESS;
}
// combines all raw demod functions into one menu command
static int CmdRawDemod(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data rawdemod",
"Demodulate the data in the GraphBuffer and output binary",
"data rawdemod --fs --> demod FSK - autodetect\n"
"data rawdemod --ab --> demod ASK/BIPHASE - autodetect\n"
"data rawdemod --am --> demod ASK/MANCHESTER - autodetect\n"
"data rawdemod --ar --> demod ASK/RAW - autodetect\n"
"data rawdemod --nr --> demod NRZ/DIRECT - autodetect\n"
"data rawdemod --p1 --> demod PSK1 - autodetect\n"
"data rawdemod --p2 --> demod PSK2 - autodetect\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0(NULL, "ab", "ASK/Biphase demodulation"),
arg_lit0(NULL, "am", "ASK/Manchester demodulation"),
arg_lit0(NULL, "ar", "ASK/Raw demodulation"),
arg_lit0(NULL, "fs", "FSK demodulation"),
arg_lit0(NULL, "nr", "NRZ/Direct demodulation"),
arg_lit0(NULL, "p1", "PSK 1 demodulation"),
arg_lit0(NULL, "p2", "PSK 2 demodulation"),
arg_strn(NULL, NULL, "<params>", 0, 35, "params for sub command"),
arg_param_end
};
//
char tmp[5];
size_t n = MIN(strlen(Cmd), sizeof(tmp) - 1);
memset(tmp, 0, sizeof(tmp));
strncpy(tmp, Cmd, sizeof(tmp) - 1);
CLIExecWithReturn(ctx, tmp, argtable, false);
bool ab = arg_get_lit(ctx, 1);
bool am = arg_get_lit(ctx, 2);
bool ar = arg_get_lit(ctx, 3);
bool fs = arg_get_lit(ctx, 4);
bool nr = arg_get_lit(ctx, 5);
bool p1 = arg_get_lit(ctx, 6);
bool p2 = arg_get_lit(ctx, 7);
CLIParserFree(ctx);
int foo = (ab + am + ar + fs + nr + p1 + p2);
if (foo > 1) {
PrintAndLogEx(WARNING, "please, select only one modulation");
return PM3_EINVARG;
}
if (foo == 0) {
PrintAndLogEx(WARNING, "please, select a modulation");
return PM3_EINVARG;
}
int ans = 0;
const char *s = Cmd + n;
if (fs)
ans = CmdFSKrawdemod(s);
else if (ab)
ans = Cmdaskbiphdemod(s);
else if (am)
ans = Cmdaskmandemod(s);
else if (ar)
ans = Cmdaskrawdemod(s);
else if (nr)
ans = CmdNRZrawDemod(s);
else if (p1)
ans = CmdPSK1rawDemod(s);
else if (p2)
ans = CmdPSK2rawDemod(s);
return ans;
}
void setClockGrid(uint32_t clk, int offset) {
g_DemodStartIdx = offset;
g_DemodClock = clk;
if (clk == 0 && offset == 0)
PrintAndLogEx(DEBUG, "DEBUG: (setClockGrid) clear settings");
else
PrintAndLogEx(DEBUG, "DEBUG: (setClockGrid) demodoffset %d, clk %d", offset, clk);
if (offset > clk) offset %= clk;
if (offset < 0) offset += clk;
if (offset > g_GraphTraceLen || offset < 0) return;
if (clk < 8 || clk > g_GraphTraceLen) {
g_GridLocked = false;
g_GridOffset = 0;
g_PlotGridX = 0;
g_DefaultGridX = 0;
RepaintGraphWindow();
} else {
g_GridLocked = true;
g_GridOffset = offset;
g_PlotGridX = clk;
g_DefaultGridX = clk;
RepaintGraphWindow();
}
}
int CmdGrid(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data grid",
"This function overlay grid on graph plot window.\n"
"use zero value to turn off either",
"data grid --> turn off\n"
"data grid -x 64 -y 50"
);
void *argtable[] = {
arg_param_begin,
arg_dbl0("x", NULL, "<dec>", "plot grid X coord"),
arg_dbl0("y", NULL, "<dec>", "plot grid Y coord"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
g_PlotGridX = arg_get_dbl_def(ctx, 1, 0);
g_PlotGridY = arg_get_dbl_def(ctx, 2, 0);
CLIParserFree(ctx);
PrintAndLogEx(DEBUG, "Setting X %.0f Y %.0f", g_PlotGridX, g_PlotGridY);
g_DefaultGridX = g_PlotGridX;
g_DefaultGridY = g_PlotGridY;
RepaintGraphWindow();
return PM3_SUCCESS;
}
static int CmdSetGraphMarkers(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data setgraphmarkers",
"Set the locations of the markers in the graph window",
"data setgraphmarkers --> reset the markers\n"
"data setgraphmarkers -a 64 --> set A, reset the rest\n"
"data setgraphmarkers -d --keep --> set D, keep the rest"
);
void *argtable[] = {
arg_param_begin,
arg_lit0(NULL, "keep", "keep the current values of the markers"),
arg_u64_0("a", NULL, "<dec>", "yellow marker"),
arg_u64_0("b", NULL, "<dec>", "purple marker"),
arg_u64_0("c", NULL, "<dec>", "orange marker"),
arg_u64_0("d", NULL, "<dec>", "blue marker"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool keep = arg_get_lit(ctx, 1);
g_MarkerA.pos = arg_get_u32_def(ctx, 2, (keep ? g_MarkerA.pos : 0));
g_MarkerB.pos = arg_get_u32_def(ctx, 3, (keep ? g_MarkerB.pos : 0));
g_MarkerC.pos = arg_get_u32_def(ctx, 4, (keep ? g_MarkerC.pos : 0));
g_MarkerD.pos = arg_get_u32_def(ctx, 5, (keep ? g_MarkerD.pos : 0));
CLIParserFree(ctx);
PrintAndLogEx(INFO, "Setting markers " _BRIGHT_YELLOW_("A") "=%u, "_BRIGHT_MAGENTA_("B") "=%u, "_RED_("C") "=%u, "_BLUE_("D") "=%u",
g_MarkerA.pos,
g_MarkerB.pos,
g_MarkerC.pos,
g_MarkerD.pos
);
RepaintGraphWindow();
return PM3_SUCCESS;
}
static int CmdHexsamples(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data hexsamples",
"Dump big buffer as hex bytes",
"data hexsamples -n 128 --> dumps 128 bytes from offset 0"
);
void *argtable[] = {
arg_param_begin,
arg_u64_0("b", "breaks", "<dec>", "row break, def 16"),
arg_u64_0("n", NULL, "<dec>", "num of bytes to download"),
arg_u64_0("o", "offset", "<hex>", "offset in big buffer"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
uint32_t breaks = arg_get_u32_def(ctx, 1, 16);
uint32_t requested = arg_get_u32_def(ctx, 2, 8);
uint32_t offset = arg_get_u32_def(ctx, 3, 0);
CLIParserFree(ctx);
// sanity checks
if (requested > g_pm3_capabilities.bigbuf_size) {
requested = g_pm3_capabilities.bigbuf_size;
PrintAndLogEx(INFO, "n is larger than big buffer size, will use %u", requested);
}
uint8_t got[g_pm3_capabilities.bigbuf_size];
if (offset + requested > sizeof(got)) {
PrintAndLogEx(NORMAL, "Tried to read past end of buffer, <bytes %u> + <offset %u> > %d"
, requested
, offset
, g_pm3_capabilities.bigbuf_size
);
return PM3_EINVARG;
}
if (!GetFromDevice(BIG_BUF, got, requested, offset, NULL, 0, NULL, 2500, false)) {
PrintAndLogEx(WARNING, "command execution time out");
return PM3_ESOFT;
}
print_hex_break(got, requested, breaks);
return PM3_SUCCESS;
}
static int CmdHide(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data hide",
"Show graph window",
"data hide"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
HideGraphWindow();
return PM3_SUCCESS;
}
// zero mean g_GraphBuffer
int CmdHpf(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data hpf",
"Remove DC offset from trace. It should centralize around 0",
"data hpf"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
uint8_t *bits = calloc(g_GraphTraceLen, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = getFromGraphBuffer(bits);
removeSignalOffset(bits, size);
// push it back to graph
setGraphBuffer(bits, size);
// set signal properties low/high/mean/amplitude and is_noise detection
computeSignalProperties(bits, size);
RepaintGraphWindow();
free(bits);
return PM3_SUCCESS;
}
static bool _headBit(BitstreamOut_t *stream) {
int bytepos = stream->position >> 3; // divide by 8
int bitpos = (stream->position++) & 7; // mask out 00000111
return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1;
}
static uint8_t getByte(uint8_t bits_per_sample, BitstreamOut_t *b) {
uint8_t val = 0;
for (int i = 0 ; i < bits_per_sample; i++)
val |= (_headBit(b) << (7 - i));
return val;
}
int getSamples(uint32_t n, bool verbose) {
return getSamplesEx(0, n, verbose, false);
}
int getSamplesEx(uint32_t start, uint32_t end, bool verbose, bool ignore_lf_config) {
if (end < start) {
PrintAndLogEx(WARNING, "error, end (%u) is smaller than start (%u)", end, start);
return PM3_EINVARG;
}
// If we get all but the last byte in bigbuf,
// we don't have to worry about remaining trash
// in the last byte in case the bits-per-sample
// does not line up on byte boundaries
uint8_t got[g_pm3_capabilities.bigbuf_size - 1];
memset(got, 0x00, sizeof(got));
uint32_t n = end - start;
if (n == 0 || n > g_pm3_capabilities.bigbuf_size - 1) {
n = g_pm3_capabilities.bigbuf_size - 1;
}
if (verbose) {
PrintAndLogEx(INFO, "Reading " _YELLOW_("%u") " bytes from device memory", n);
}
PacketResponseNG resp;
if (GetFromDevice(BIG_BUF, got, n, start, NULL, 0, &resp, 10000, true) == false) {
PrintAndLogEx(WARNING, "timeout while waiting for reply");
return PM3_ETIMEOUT;
}
if (verbose) {
PrintAndLogEx(SUCCESS, "Data fetched");
}
uint8_t bits_per_sample = 8;
if (IfPm3Lf() && ignore_lf_config == false) {
sample_config sc;
lf_getconfig(&sc);
if (verbose) {
PrintAndLogEx(INFO, "Samples @ " _YELLOW_("%d") " bits/smpl, decimation 1:%d ", sc.bits_per_sample, sc.decimation);
}
bits_per_sample = sc.bits_per_sample;
}
return getSamplesFromBufEx(got, n, bits_per_sample, verbose);;
}
int getSamplesFromBufEx(uint8_t *data, size_t sample_num, uint8_t bits_per_sample, bool verbose) {
size_t max_num = MIN(sample_num, MAX_GRAPH_TRACE_LEN);
if (bits_per_sample < 8) {
if (verbose) PrintAndLogEx(INFO, "Unpacking...");
BitstreamOut_t bout = {data, bits_per_sample * sample_num, 0};
size_t j = 0;
for (j = 0; j < max_num; j++) {
uint8_t sample = getByte(bits_per_sample, &bout);
g_GraphBuffer[j] = ((int) sample) - 127;
}
g_GraphTraceLen = j;
if (verbose) PrintAndLogEx(INFO, "Unpacked %zu samples", j);
} else {
for (size_t j = 0; j < max_num; j++) {
g_GraphBuffer[j] = ((int)data[j]) - 127;
}
g_GraphTraceLen = max_num;
}
uint8_t *bits = calloc(g_GraphTraceLen, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = getFromGraphBuffer(bits);
// set signal properties low/high/mean/amplitude and is_noise detection
computeSignalProperties(bits, size);
free(bits);
setClockGrid(0, 0);
g_DemodBufferLen = 0;
RepaintGraphWindow();
return PM3_SUCCESS;
}
static int CmdSamples(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data samples",
"Get raw samples for graph window (GraphBuffer) from device.\n"
"If 0, then get whole big buffer from device.",
"data samples\n"
"data samples -n 10000"
);
void *argtable[] = {
arg_param_begin,
arg_int0("n", NULL, "<dec>", "num of samples (512 - 40000)"),
arg_lit0("v", "verbose", "verbose output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int n = arg_get_int_def(ctx, 1, 0);
bool verbose = arg_get_lit(ctx, 2);
CLIParserFree(ctx);
if (n < 0 || n > MIN(g_pm3_capabilities.bigbuf_size - 1, MAX_GRAPH_TRACE_LEN)) {
PrintAndLogEx(WARNING, "invalid number of samples, must be between 0 and %d", MIN(g_pm3_capabilities.bigbuf_size - 1, MAX_GRAPH_TRACE_LEN));
return PM3_EINVARG;
}
return getSamples(n, verbose);
}
static int CmdLoad(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data load",
"This command loads the contents of a pm3 file into graph window\n",
"data load -f myfilename"
);
void *argtable[] = {
arg_param_begin,
arg_str1("f", "file", "<fn>", "file to load"),
arg_lit0("b", "bin", "binary file"),
arg_lit0("n", "no-fix", "Load data from file without any transformations"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
bool is_bin = arg_get_lit(ctx, 2);
bool nofix = arg_get_lit(ctx, 3);
CLIParserFree(ctx);
char *path = NULL;
if (searchFile(&path, TRACES_SUBDIR, filename, ".pm3", true) != PM3_SUCCESS) {
if (searchFile(&path, TRACES_SUBDIR, filename, "", false) != PM3_SUCCESS) {
return PM3_EFILE;
}
}
FILE *f;
if (is_bin)
f = fopen(path, "rb");
else
f = fopen(path, "r");
if (f == NULL) {
PrintAndLogEx(WARNING, "couldn't open `" _YELLOW_("%s") "`", path);
free(path);
return PM3_EFILE;
}
free(path);
g_GraphTraceLen = 0;
if (is_bin) {
uint8_t val[2];
while (fread(val, 1, 1, f)) {
g_GraphBuffer[g_GraphTraceLen] = val[0] - 127;
g_GraphTraceLen++;
if (g_GraphTraceLen >= MAX_GRAPH_TRACE_LEN) {
break;
}
}
} else {
char line[80];
while (fgets(line, sizeof(line), f)) {
g_GraphBuffer[g_GraphTraceLen] = atoi(line);
g_GraphTraceLen++;
if (g_GraphTraceLen >= MAX_GRAPH_TRACE_LEN)
break;
}
}
fclose(f);
PrintAndLogEx(SUCCESS, "loaded " _YELLOW_("%zu") " samples", g_GraphTraceLen);
if (nofix == false) {
uint8_t *bits = calloc(g_GraphTraceLen, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = getFromGraphBuffer(bits);
removeSignalOffset(bits, size);
setGraphBuffer(bits, size);
computeSignalProperties(bits, size);
free(bits);
}
setClockGrid(0, 0);
g_DemodBufferLen = 0;
RepaintGraphWindow();
return PM3_SUCCESS;
}
// trim graph from the end
int CmdLtrim(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data ltrim",
"Trim samples from left of trace",
"data ltrim -i 300 --> remove from start 0 to index 300"
);
void *argtable[] = {
arg_param_begin,
arg_u64_1("i", "idx", "<dec>", "index in graph buffer"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
uint32_t ds = arg_get_u32(ctx, 1);
CLIParserFree(ctx);
// sanitycheck
if (g_GraphTraceLen <= ds) {
PrintAndLogEx(WARNING, "index out of bounds");
return PM3_EINVARG;
}
for (size_t i = ds; i < g_GraphTraceLen; ++i) {
g_GraphBuffer[i - ds] = g_GraphBuffer[i];
}
g_GraphTraceLen -= ds;
g_DemodStartIdx -= ds;
RepaintGraphWindow();
return PM3_SUCCESS;
}
// trim graph from the beginning
static int CmdRtrim(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data rtrim",
"Trim samples from right of trace",
"data rtrim -i 4000 --> remove from index 4000 to end of graph buffer"
);
void *argtable[] = {
arg_param_begin,
arg_u64_1("i", "idx", "<dec>", "index in graph buffer"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
uint32_t ds = arg_get_u32(ctx, 1);
CLIParserFree(ctx);
// sanitycheck
if (g_GraphTraceLen <= ds) {
PrintAndLogEx(WARNING, "index out of bounds");
return PM3_EINVARG;
}
g_GraphTraceLen = ds;
RepaintGraphWindow();
return PM3_SUCCESS;
}
// trim graph (middle) piece
static int CmdMtrim(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data mtrim",
"Trim out samples from\n"
" start 0 to `-s index`\n"
"AND\n"
" from `-e index` to end of graph buffer",
"data mtrim -s 1000 -e 2000 --> keep all between index 1000 and 2000"
);
void *argtable[] = {
arg_param_begin,
arg_u64_1("s", "start", "<dec>", "start point"),
arg_u64_1("e", "end", "<dec>", "end point"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
uint32_t start = arg_get_u32(ctx, 1);
uint32_t stop = arg_get_u32(ctx, 2);
CLIParserFree(ctx);
if (start > g_GraphTraceLen || stop > g_GraphTraceLen || start >= stop) {
PrintAndLogEx(WARNING, "start and end points doesn't align");
return PM3_EINVARG;
}
// leave start position sample
start++;
g_GraphTraceLen = stop - start;
for (uint32_t i = 0; i < g_GraphTraceLen; i++) {
g_GraphBuffer[i] = g_GraphBuffer[start + i];
}
g_DemodStartIdx = 0;
RepaintGraphWindow();
return PM3_SUCCESS;
}
int CmdNorm(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data norm",
"Normalize max/min to +/-128",
"data norm"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
int max = INT_MIN, min = INT_MAX;
// Find local min, max
for (uint32_t i = 10; i < g_GraphTraceLen; ++i) {
if (g_GraphBuffer[i] > max) max = g_GraphBuffer[i];
if (g_GraphBuffer[i] < min) min = g_GraphBuffer[i];
}
if ((g_GraphTraceLen > 10) && (max != min)) {
for (uint32_t i = 0; i < g_GraphTraceLen; ++i) {
g_GraphBuffer[i] = ((long)(g_GraphBuffer[i] - ((max + min) / 2)) * 256) / (max - min);
//marshmelow: adjusted *1000 to *256 to make +/- 128 so demod commands still work
}
}
uint8_t *bits = calloc(g_GraphTraceLen, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = getFromGraphBuffer(bits);
// set signal properties low/high/mean/amplitude and is_noise detection
computeSignalProperties(bits, size);
RepaintGraphWindow();
free(bits);
return PM3_SUCCESS;
}
int CmdPlot(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data plot",
"Show graph window \n"
"hit 'h' in window for detail keystroke help available",
"data plot"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
ShowGraphWindow();
return PM3_SUCCESS;
}
int CmdSave(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data save",
"Save signal trace from graph window , i.e. the GraphBuffer\n"
"This is a text file with number -127 to 127. With the option `w` you can save it as wave file\n"
"Filename should be without file extension",
"data save -f myfilename -> save graph buffer to file\n"
"data save --wave -f myfilename -> save graph buffer to wave file"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("w", "wave", "save as wave format (.wav)"),
arg_str1("f", "file", "<fn w/o ext>", "save file name"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
bool as_wave = arg_get_lit(ctx, 1);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
// CLIGetStrWithReturn(ctx, 2, (uint8_t *)filename, &fnlen);
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
CLIParserFree(ctx);
if (g_GraphTraceLen == 0) {
PrintAndLogEx(WARNING, "Graphbuffer is empty, nothing to save");
return PM3_SUCCESS;
}
if (as_wave)
return saveFileWAVE(filename, g_GraphBuffer, g_GraphTraceLen);
else
return saveFilePM3(filename, g_GraphBuffer, g_GraphTraceLen);
}
static int CmdTimeScale(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data timescale",
"Set cursor display timescale.\n"
"Setting the timescale makes the differential `dt` reading between the yellow and purple markers meaningful.\n"
"once the timescale is set, the differential reading between brackets can become a time duration.",
"data timescale --sr 125 -u ms -> for LF sampled at 125 kHz. Reading will be in milliseconds\n"
"data timescale --sr 1.695 -u us -> for HF sampled at 16 * fc/128. Reading will be in microseconds\n"
"data timescale --sr 16 -u ETU -> for HF with 16 samples per ETU (fc/128). Reading will be in ETUs"
);
void *argtable[] = {
arg_param_begin,
arg_dbl1(NULL, "sr", "<float>", "sets timescale factor according to sampling rate"),
arg_str0("u", "unit", "<str>", "time unit to display (max 10 chars)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
g_CursorScaleFactor = arg_get_dbl_def(ctx, 1, 1);
if (g_CursorScaleFactor <= 0) {
PrintAndLogEx(FAILED, "bad, can't have negative or zero timescale factor");
g_CursorScaleFactor = 1;
}
int len = 0;
g_CursorScaleFactorUnit[0] = '\x00';
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)g_CursorScaleFactorUnit, sizeof(g_CursorScaleFactorUnit), &len);
CLIParserFree(ctx);
RepaintGraphWindow();
return PM3_SUCCESS;
}
int directionalThreshold(const int *in, int *out, size_t len, int8_t up, int8_t down) {
int lastValue = in[0];
// Will be changed at the end, but init 0 as we adjust to last samples
// value if no threshold kicks in.
out[0] = 0;
for (size_t i = 1; i < len; ++i) {
// Apply first threshold to samples heading up
if (in[i] >= up && in[i] > lastValue) {
lastValue = out[i]; // Buffer last value as we overwrite it.
out[i] = 1;
}
// Apply second threshold to samples heading down
else if (in[i] <= down && in[i] < lastValue) {
lastValue = out[i]; // Buffer last value as we overwrite it.
out[i] = -1;
} else {
lastValue = out[i]; // Buffer last value as we overwrite it.
out[i] = out[i - 1];
}
}
// Align with first edited sample.
out[0] = out[1];
return PM3_SUCCESS;
}
static int CmdDirectionalThreshold(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data dirthreshold",
"Max rising higher up-thres/ Min falling lower down-thres, keep rest as prev.",
"data dirthreshold -u 10 -d -10"
);
void *argtable[] = {
arg_param_begin,
arg_int1("d", "down", "<dec>", "threshold down"),
arg_int1("u", "up", "<dec>", "threshold up"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int8_t down = arg_get_int(ctx, 1);
int8_t up = arg_get_int(ctx, 2);
CLIParserFree(ctx);
PrintAndLogEx(INFO, "Applying up threshold: " _YELLOW_("%i") ", down threshold: " _YELLOW_("%i") "\n", up, down);
directionalThreshold(g_GraphBuffer, g_GraphBuffer, g_GraphTraceLen, up, down);
// set signal properties low/high/mean/amplitude and isnoice detection
uint8_t *bits = calloc(g_GraphTraceLen, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = getFromGraphBuffer(bits);
// set signal properties low/high/mean/amplitude and is_noice detection
computeSignalProperties(bits, size);
RepaintGraphWindow();
free(bits);
return PM3_SUCCESS;
}
static int CmdZerocrossings(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data zerocrossings",
"Count time between zero-crossings",
"data zerocrossings"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
// Zero-crossings aren't meaningful unless the signal is zero-mean.
CmdHpf("");
int sign = 1, zc = 0, lastZc = 0;
for (uint32_t i = 0; i < g_GraphTraceLen; ++i) {
if (g_GraphBuffer[i] * sign >= 0) {
// No change in sign, reproduce the previous sample count.
zc++;
g_GraphBuffer[i] = lastZc;
} else {
// Change in sign, reset the sample count.
sign = -sign;
g_GraphBuffer[i] = lastZc;
if (sign > 0) {
lastZc = zc;
zc = 0;
}
}
}
uint8_t *bits = calloc(g_GraphTraceLen, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = getFromGraphBuffer(bits);
// set signal properties low/high/mean/amplitude and is_noise detection
computeSignalProperties(bits, size);
RepaintGraphWindow();
free(bits);
return PM3_SUCCESS;
}
static bool data_verify_hex(uint8_t *d, size_t n) {
if (d == NULL) {
return false;
}
for (size_t i = 0; i < n; i++) {
if (isxdigit(d[i]) == false) {
PrintAndLogEx(ERR, "Non hex digit found");
return false;
}
}
return true;
}
/* // example of FSK2 RF/50 Tones
static const int LowTone[] = {
1, 1, 1, 1, 1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, -1, -1, -1, -1, -1
};
static const int HighTone[] = {
1, 1, 1, 1, 1, -1, -1, -1, -1, // note one extra 1 to padd due to 50/8 remainder (1/2 the remainder)
1, 1, 1, 1, -1, -1, -1, -1,
1, 1, 1, 1, -1, -1, -1, -1,
1, 1, 1, 1, -1, -1, -1, -1,
1, 1, 1, 1, -1, -1, -1, -1,
1, 1, 1, 1, -1, -1, -1, -1, -1, // note one extra -1 to padd due to 50/8 remainder
};
*/
static void GetHiLoTone(int *LowTone, int *HighTone, int clk, int LowToneFC, int HighToneFC) {
int i, j = 0;
int Left_Modifier = ((clk % LowToneFC) % 2) + ((clk % LowToneFC) / 2);
int Right_Modifier = (clk % LowToneFC) / 2;
//int HighToneMod = clk mod HighToneFC;
int LeftHalfFCCnt = (LowToneFC % 2) + (LowToneFC / 2); //truncate
int FCs_per_clk = clk / LowToneFC;
// need to correctly split up the clock to field clocks.
// First attempt uses modifiers on each end to make up for when FCs don't evenly divide into Clk
// start with LowTone
// set extra 1 modifiers to make up for when FC doesn't divide evenly into Clk
for (i = 0; i < Left_Modifier; i++) {
LowTone[i] = 1;
}
// loop # of field clocks inside the main clock
for (i = 0; i < (FCs_per_clk); i++) {
// loop # of samples per field clock
for (j = 0; j < LowToneFC; j++) {
LowTone[(i * LowToneFC) + Left_Modifier + j] = (j < LeftHalfFCCnt) ? 1 : -1;
}
}
int k;
// add last -1 modifiers
for (k = 0; k < Right_Modifier; k++) {
LowTone[((i - 1) * LowToneFC) + Left_Modifier + j + k] = -1;
}
// now do hightone
Left_Modifier = ((clk % HighToneFC) % 2) + ((clk % HighToneFC) / 2);
Right_Modifier = (clk % HighToneFC) / 2;
LeftHalfFCCnt = (HighToneFC % 2) + (HighToneFC / 2); //truncate
FCs_per_clk = clk / HighToneFC;
for (i = 0; i < Left_Modifier; i++) {
HighTone[i] = 1;
}
// loop # of field clocks inside the main clock
for (i = 0; i < (FCs_per_clk); i++) {
// loop # of samples per field clock
for (j = 0; j < HighToneFC; j++) {
HighTone[(i * HighToneFC) + Left_Modifier + j] = (j < LeftHalfFCCnt) ? 1 : -1;
}
}
// add last -1 modifiers
for (k = 0; k < Right_Modifier; k++) {
PrintAndLogEx(NORMAL, "(i-1)*HighToneFC+lm+j+k %i", ((i - 1) * HighToneFC) + Left_Modifier + j + k);
HighTone[((i - 1) * HighToneFC) + Left_Modifier + j + k] = -1;
}
if (g_debugMode == 2) {
for (i = 0; i < clk; i++) {
PrintAndLogEx(NORMAL, "Low: %i, High: %i", LowTone[i], HighTone[i]);
}
}
}
//old CmdFSKdemod adapted by marshmellow
//converts FSK to clear NRZ style wave. (or demodulates)
static int FSKToNRZ(int *data, size_t *dataLen, uint8_t clk, uint8_t LowToneFC, uint8_t HighToneFC) {
uint8_t ans = 0;
if (clk == 0 || LowToneFC == 0 || HighToneFC == 0) {
int firstClockEdge = 0;
ans = fskClocks((uint8_t *) &LowToneFC, (uint8_t *) &HighToneFC, (uint8_t *) &clk, &firstClockEdge);
if (g_debugMode > 1) {
PrintAndLogEx(NORMAL, "DEBUG FSKtoNRZ: detected clocks: fc_low %i, fc_high %i, clk %i, firstClockEdge %i, ans %u", LowToneFC, HighToneFC, clk, firstClockEdge, ans);
}
}
// currently only know fsk modulations with field clocks < 10 samples and > 4 samples. filter out to remove false positives (and possibly destroying ask/psk modulated waves...)
if (ans == 0 || clk == 0 || LowToneFC == 0 || HighToneFC == 0 || LowToneFC > 10 || HighToneFC < 4) {
if (g_debugMode > 1) {
PrintAndLogEx(NORMAL, "DEBUG FSKtoNRZ: no fsk clocks found");
}
return PM3_ESOFT;
}
int LowTone[clk];
int HighTone[clk];
GetHiLoTone(LowTone, HighTone, clk, LowToneFC, HighToneFC);
// loop through ([all samples] - clk)
for (size_t i = 0; i < *dataLen - clk; ++i) {
int lowSum = 0, highSum = 0;
// sum all samples together starting from this sample for [clk] samples for each tone (multiply tone value with sample data)
for (size_t j = 0; j < clk; ++j) {
lowSum += LowTone[j] * data[i + j];
highSum += HighTone[j] * data[i + j];
}
// get abs( [average sample value per clk] * 100 ) (or a rolling average of sorts)
lowSum = abs(100 * lowSum / clk);
highSum = abs(100 * highSum / clk);
// save these back to buffer for later use
data[i] = (highSum << 16) | lowSum;
}
// now we have the abs( [average sample value per clk] * 100 ) for each tone
// loop through again [all samples] - clk - 16
// note why 16??? is 16 the largest FC? changed to LowToneFC as that should be the > fc
for (size_t i = 0; i < *dataLen - clk - LowToneFC; ++i) {
int lowTot = 0, highTot = 0;
// sum a field clock width of abs( [average sample values per clk] * 100) for each tone
for (size_t j = 0; j < LowToneFC; ++j) { //10 for fsk2
lowTot += (data[i + j] & 0xffff);
}
for (size_t j = 0; j < HighToneFC; j++) { //8 for fsk2
highTot += (data[i + j] >> 16);
}
// subtract the sum of lowTone averages by the sum of highTone averages as it
// and write back the new graph value
data[i] = lowTot - highTot;
}
// update dataLen to what we put back to the data sample buffer
*dataLen -= (clk + LowToneFC);
return PM3_SUCCESS;
}
static int CmdFSKToNRZ(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data fsktonrz",
"Convert fsk2 to nrz wave for alternate fsk demodulating (for weak fsk)\n"
"Omitted values are autodetect instead",
"data fsktonrz\n"
"data fsktonrz -c 32 --low 8 --hi 10");
void *argtable[] = {
arg_param_begin,
arg_int0("c", "clk", "<dec>", "clock"),
arg_int0(NULL, "low", "<dec>", "low field clock"),
arg_int0(NULL, "hi", "<dec>", "high field clock"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int clk = arg_get_int_def(ctx, 1, 0);
int fc_low = arg_get_int_def(ctx, 2, 0);
int fc_high = arg_get_int_def(ctx, 3, 0);
CLIParserFree(ctx);
setClockGrid(0, 0);
g_DemodBufferLen = 0;
int ans = FSKToNRZ(g_GraphBuffer, &g_GraphTraceLen, clk, fc_low, fc_high);
CmdNorm("");
RepaintGraphWindow();
return ans;
}
/*
// If reactivated, beware it doesn't compile on Android (DXL)
void iceIIR_Butterworth(int *data, const size_t len) {
int *output = (int *) calloc(sizeof(int) * len, sizeof(uint8_t));
if (!output) return;
// clear mem
memset(output, 0x00, len);
size_t adjustedLen = len;
float fc = 0.1125f; // center frequency
// create very simple low-pass filter to remove images (2nd-order Butterworth)
float complex iir_buf[3] = {0, 0, 0};
float b[3] = {0.003621681514929, 0.007243363029857, 0.003621681514929};
float a[3] = {1.000000000000000, -1.822694925196308, 0.837181651256023};
for (size_t i = 0; i < adjustedLen; ++i) {
float sample = data[i]; // input sample read from array
float complex x_prime = 1.0f; // save sample for estimating frequency
float complex x;
// remove DC offset and mix to complex baseband
x = (sample - 127.5f) * cexpf(_Complex_I * 2 * M_PI * fc * i);
// apply low-pass filter, removing spectral image (IIR using direct-form II)
iir_buf[2] = iir_buf[1];
iir_buf[1] = iir_buf[0];
iir_buf[0] = x - a[1] * iir_buf[1] - a[2] * iir_buf[2];
x = b[0] * iir_buf[0] +
b[1] * iir_buf[1] +
b[2] * iir_buf[2];
// compute instantaneous frequency by looking at phase difference
// between adjacent samples
float freq = cargf(x * conjf(x_prime));
x_prime = x; // retain this sample for next iteration
output[i] = (freq > 0) ? 127 : -127;
}
// show data
//memcpy(data, output, adjustedLen);
for (size_t j = 0; j < adjustedLen; ++j)
data[j] = output[j];
free(output);
}
*/
static void iceSimple_Filter(int *data, const size_t len, uint8_t k) {
// ref: http://www.edn.com/design/systems-design/4320010/A-simple-software-lowpass-filter-suits-embedded-system-applications
// parameter K
#define FILTER_SHIFT 4
int32_t filter_reg = 0;
int8_t shift = (k <= 8) ? k : FILTER_SHIFT;
for (size_t i = 0; i < len; ++i) {
// Update filter with current sample
filter_reg = filter_reg - (filter_reg >> shift) + data[i];
// Scale output for unity gain
data[i] = filter_reg >> shift;
}
}
static int CmdDataIIR(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data iir",
"Apply IIR buttersworth filter on plot data",
"data iir -n 2"
);
void *argtable[] = {
arg_param_begin,
arg_u64_1("n", NULL, "<dec>", "factor n"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
uint8_t k = (arg_get_u32_def(ctx, 1, 0) & 0xFF);
CLIParserFree(ctx);
iceSimple_Filter(g_GraphBuffer, g_GraphTraceLen, k);
uint8_t *bits = calloc(g_GraphTraceLen, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = getFromGraphBuffer(bits);
// set signal properties low/high/mean/amplitude and is_noise detection
computeSignalProperties(bits, size);
RepaintGraphWindow();
free(bits);
return PM3_SUCCESS;
}
typedef struct {
t55xx_modulation modulation;
int bitrate;
int carrier;
uint8_t fc1;
uint8_t fc2;
} lf_modulation_t;
static int print_modulation(lf_modulation_t b) {
PrintAndLogEx(INFO, " Modulation........ " _GREEN_("%s"), GetSelectedModulationStr(b.modulation));
PrintAndLogEx(INFO, " Bit clock......... " _GREEN_("RF/%d"), b.bitrate);
PrintAndLogEx(INFO, " Approx baudrate... " _GREEN_("%.f") " baud", (125000 / (float)b.bitrate));
switch (b.modulation) {
case DEMOD_PSK1:
case DEMOD_PSK2:
case DEMOD_PSK3:
PrintAndLogEx(SUCCESS, " Carrier rate...... %d", b.carrier);
break;
case DEMOD_FSK:
case DEMOD_FSK1:
case DEMOD_FSK1a:
case DEMOD_FSK2:
case DEMOD_FSK2a:
PrintAndLogEx(SUCCESS, " Field Clocks...... FC/%u, FC/%u", b.fc1, b.fc2);
break;
case DEMOD_NRZ:
case DEMOD_ASK:
case DEMOD_BI:
case DEMOD_BIa:
default:
break;
}
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int try_detect_modulation(void) {
#define LF_NUM_OF_TESTS 6
lf_modulation_t tests[LF_NUM_OF_TESTS];
for (int i = 0; i < ARRAYLEN(tests); i++) {
memset(&tests[i], 0, sizeof(lf_modulation_t));
}
int clk = 0, firstClockEdge = 0;
uint8_t hits = 0, fc1 = 0, fc2 = 0;
bool st = false;
uint8_t ans = fskClocks(&fc1, &fc2, (uint8_t *)&clk, &firstClockEdge);
if (ans && ((fc1 == 10 && fc2 == 8) || (fc1 == 8 && fc2 == 5))) {
if ((FSKrawDemod(0, 0, 0, 0, false) == PM3_SUCCESS)) {
tests[hits].modulation = DEMOD_FSK;
if (fc1 == 8 && fc2 == 5) {
tests[hits].modulation = DEMOD_FSK1a;
} else if (fc1 == 10 && fc2 == 8) {
tests[hits].modulation = DEMOD_FSK2;
}
tests[hits].bitrate = clk;
tests[hits].fc1 = fc1;
tests[hits].fc2 = fc2;
++hits;
}
} else {
clk = GetAskClock("", false);
if (clk > 0) {
// 0 = auto clock
// 0 = no invert
// 1 = maxError 1
// 0 = max len
// false = no amplify
// false = no verbose
// false = no emSearch
// 1 = Ask/Man
// st = true
if ((ASKDemod_ext(0, 0, 1, 0, false, false, false, 1, &st) == PM3_SUCCESS)) {
tests[hits].modulation = DEMOD_ASK;
tests[hits].bitrate = clk;
++hits;
}
// "0 0 1 " == clock auto, invert true, maxError 1.
// false = no verbose
// false = no emSearch
// 1 = Ask/Man
// st = true
// ASK / biphase
if ((ASKbiphaseDemod(0, 0, 0, 2, false) == PM3_SUCCESS)) {
tests[hits].modulation = DEMOD_BI;
tests[hits].bitrate = clk;
++hits;
}
// ASK / Diphase
if ((ASKbiphaseDemod(0, 0, 1, 2, false) == PM3_SUCCESS)) {
tests[hits].modulation = DEMOD_BIa;
tests[hits].bitrate = clk;
++hits;
}
}
clk = GetNrzClock("", false);
if ((NRZrawDemod(0, 0, 1, false) == PM3_SUCCESS)) {
tests[hits].modulation = DEMOD_NRZ;
tests[hits].bitrate = clk;
++hits;
}
clk = GetPskClock("", false);
if (clk > 0) {
// allow undo
buffer_savestate_t saveState = save_graphbuffer();
// skip first 160 samples to allow antenna to settle in (psk gets inverted occasionally otherwise)
CmdLtrim("-i 160");
if ((PSKDemod(0, 0, 6, false) == PM3_SUCCESS)) {
tests[hits].modulation = DEMOD_PSK1;
tests[hits].bitrate = clk;
++hits;
// get psk carrier
tests[hits].carrier = GetPskCarrier(false);
}
//undo trim samples
restore_graphbuffer(saveState);
}
}
if (hits) {
PrintAndLogEx(SUCCESS, "Found [%d] possible matches for modulation.", hits);
for (int i = 0; i < hits; ++i) {
PrintAndLogEx(INFO, "--[%d]---------------", i + 1);
print_modulation(tests[i]);
}
return PM3_SUCCESS;
} else {
PrintAndLogEx(INFO, "Signal doesn't match");
return PM3_ESOFT;
}
}
static int CmdDataModulationSearch(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data modulation",
"search LF signal after clock and modulation\n",
"data modulation"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
return try_detect_modulation();
}
static int CmdAsn1Decoder(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data asn1",
"Decode ASN1 bytearray\n"
"",
"data asn1 -d 303381050186922305a5020500a6088101010403030008a7188516eeee4facacf4fbde5e5c49d95e55bfbca74267b02407a9020500\n"
);
void *argtable[] = {
arg_param_begin,
arg_str0("d", NULL, "<hex>", "ASN1 encoded byte array"),
arg_lit0(NULL, "test", "perform self tests"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int dlen = 2048;
uint8_t data[2048];
CLIGetHexWithReturn(ctx, 1, data, &dlen);
bool selftest = arg_get_lit(ctx, 2);
CLIParserFree(ctx);
if (selftest) {
return asn1_selftest();
}
// print ASN1 decoded array in TLV view
PrintAndLogEx(INFO, "---------------- " _CYAN_("ASN1 TLV") " -----------------");
asn1_print(data, dlen, " ");
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int CmdDiff(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data diff",
"Diff takes a multitude of input data and makes a binary compare.\n"
"It accepts filenames (filesystem or RDV4 flashmem SPIFFS), emulator memory, magic gen1",
"data diff -w 4 -a hf-mfu-01020304.bin -b hf-mfu-04030201.bin\n"
"data diff -a fileA -b fileB\n"
"data diff -a fileA --eb\n"
"data diff --fa fileA -b fileB\n"
"data diff --fa fileA --fb fileB\n"
);
void *argtable[] = {
arg_param_begin,
arg_str0("a", NULL, "<fn>", "input file name A"),
arg_str0("b", NULL, "<fn>", "input file name B"),
arg_lit0(NULL, "eb", "emulator memory <hf mf esave>"),
arg_str0(NULL, "fa", "<fn>", "input spiffs file A"),
arg_str0(NULL, "fb", "<fn>", "input spiffs file B"),
arg_int0("w", NULL, "<4|8|16>", "Width of data output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int fnlenA = 0;
char filenameA[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filenameA, FILE_PATH_SIZE, &fnlenA);
int fnlenB = 0;
char filenameB[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filenameB, FILE_PATH_SIZE, &fnlenB);
bool use_e = arg_get_lit(ctx, 3);
// SPIFFS filename A
int splenA = 0;
char spnameA[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)spnameA, FILE_PATH_SIZE, &splenA);
// SPIFFS filename B
int splenB = 0;
char spnameB[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)spnameB, FILE_PATH_SIZE, &splenB);
int width = arg_get_int_def(ctx, 6, 16);
CLIParserFree(ctx);
// sanity check
if (IfPm3Flash() == false && (splenA > 0 || splenB > 0)) {
PrintAndLogEx(WARNING, "No Flashmemory available");
return PM3_EINVARG;
}
if (splenA > 32) {
PrintAndLogEx(WARNING, "SPIFFS filname A length is large than 32 bytes, got %d", splenA);
return PM3_EINVARG;
}
if (splenB > 32) {
PrintAndLogEx(WARNING, "SPIFFS filname B length is large than 32 bytes, got %d", splenB);
return PM3_EINVARG;
}
//
if (width > 16 || width < 1) {
PrintAndLogEx(INFO, "Width out of range, using default 16 bytes width");
width = 16;
}
// if user supplied dump file, time to load it
int res = PM3_SUCCESS;
uint8_t *inA = NULL, *inB = NULL;
size_t datalenA = 0, datalenB = 0;
// read file A
if (fnlenA) {
// read dump file
res = pm3_load_dump(filenameA, (void **)&inA, &datalenA, MIFARE_4K_MAX_BYTES);
if (res != PM3_SUCCESS) {
return res;
}
}
// read file B
if (fnlenB) {
// read dump file
res = pm3_load_dump(filenameB, (void **)&inB, &datalenB, MIFARE_4K_MAX_BYTES);
if (res != PM3_SUCCESS) {
return res;
}
}
// read spiffs file A
if (splenA) {
res = flashmem_spiffs_download(spnameA, splenA, (void **)&inA, &datalenA);
if (res != PM3_SUCCESS) {
return res;
}
}
// read spiffs file B
if (splenB) {
res = flashmem_spiffs_download(spnameB, splenB, (void **)&inB, &datalenB);
if (res != PM3_SUCCESS) {
return res;
}
}
// download emulator memory
if (use_e) {
uint8_t *d = calloc(MIFARE_4K_MAX_BYTES, sizeof(uint8_t));
if (d == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
PrintAndLogEx(INFO, "downloading from emulator memory");
if (GetFromDevice(BIG_BUF_EML, d, MIFARE_4K_MAX_BYTES, 0, NULL, 0, NULL, 2500, false) == false) {
PrintAndLogEx(WARNING, "Fail, transfer from device time-out");
free(inA);
free(inB);
free(d);
return PM3_ETIMEOUT;
}
if (fnlenA) {
datalenB = MIFARE_4K_MAX_BYTES;
inB = d;
} else {
datalenA = MIFARE_4K_MAX_BYTES;
inA = d;
}
}
size_t biggest = (datalenA > datalenB) ? datalenA : datalenB;
PrintAndLogEx(DEBUG, "data len: %zu A %zu B %zu", biggest, datalenA, datalenB);
if (inA == NULL) {
PrintAndLogEx(INFO, "inA null");
}
if (inB == NULL) {
PrintAndLogEx(INFO, "inB null");
}
char hdr0[400] = {0};
int hdr_sln = (width * 4) + 2;
int max_fn_space = (width * 4);
if (max_fn_space < fnlenA) {
truncate_filename(filenameA, max_fn_space);
fnlenA = strlen(filenameA);
}
if (max_fn_space < fnlenB) {
truncate_filename(filenameB, max_fn_space);
fnlenB = strlen(filenameB);
}
if (fnlenA && fnlenB) {
snprintf(hdr0, sizeof(hdr0) - 1, " # | " _CYAN_("%.*s"), max_fn_space, filenameA);
// add space if needed
int padding_len = (hdr_sln - fnlenA - 1);
if (padding_len > 0) {
memset(hdr0 + strlen(hdr0), ' ', padding_len);
}
snprintf(hdr0 + strlen(hdr0), sizeof(hdr0) - 1 - strlen(hdr0), "| " _CYAN_("%.*s"), max_fn_space, filenameB);
} else {
strcat(hdr0, " # | " _CYAN_("a"));
memset(hdr0 + strlen(hdr0), ' ', hdr_sln - 2);
strcat(hdr0 + strlen(hdr0), "| " _CYAN_("b"));
}
char hdr1[200] = "----+";
memset(hdr1 + strlen(hdr1), '-', hdr_sln);
memset(hdr1 + strlen(hdr1), '+', 1);
memset(hdr1 + strlen(hdr1), '-', hdr_sln);
PrintAndLogEx(INFO, "");
PrintAndLogEx(INFO, "%s", hdr1);
PrintAndLogEx(INFO, "%s", hdr0);
PrintAndLogEx(INFO, "%s", hdr1);
char line[880] = {0};
// print data diff loop
for (int i = 0 ; i < biggest ; i += width) {
char dlnA[240] = {0};
char dlnB[240] = {0};
char dlnAii[180] = {0};
char dlnBii[180] = {0};
memset(dlnA, 0, sizeof(dlnA));
memset(dlnB, 0, sizeof(dlnB));
memset(dlnAii, 0, sizeof(dlnAii));
memset(dlnBii, 0, sizeof(dlnBii));
for (int j = i; j < i + width; j++) {
int dlnALen = strlen(dlnA);
int dlnBLen = strlen(dlnB);
int dlnAiiLen = strlen(dlnAii);
int dlnBiiLen = strlen(dlnBii);
//both files ended
if (j >= datalenA && j >= datalenB) {
snprintf(dlnA + dlnALen, sizeof(dlnA) - dlnALen, "-- ");
snprintf(dlnAii + dlnAiiLen, sizeof(dlnAii) - dlnAiiLen, ".") ;
snprintf(dlnB + dlnBLen, sizeof(dlnB) - dlnBLen, "-- ");
snprintf(dlnBii + dlnBiiLen, sizeof(dlnBii) - dlnBiiLen, ".") ;
continue ;
}
char ca, cb;
if (j >= datalenA) {
// file A ended. print B without colors
cb = inB[j];
snprintf(dlnA + dlnALen, sizeof(dlnA) - dlnALen, "-- ");
snprintf(dlnAii + dlnAiiLen, sizeof(dlnAii) - dlnAiiLen, ".") ;
snprintf(dlnB + dlnBLen, sizeof(dlnB) - dlnBLen, "%02X ", inB[j]);
snprintf(dlnBii + dlnBiiLen, sizeof(dlnBii) - dlnBiiLen, "%c", ((cb < 32) || (cb == 127)) ? '.' : cb);
continue ;
}
ca = inA[j];
if (j >= datalenB) {
// file B ended. print A without colors
snprintf(dlnA + dlnALen, sizeof(dlnA) - dlnALen, "%02X ", inA[j]);
snprintf(dlnAii + dlnAiiLen, sizeof(dlnAii) - dlnAiiLen, "%c", ((ca < 32) || (ca == 127)) ? '.' : ca);
snprintf(dlnB + dlnBLen, sizeof(dlnB) - dlnBLen, "-- ");
snprintf(dlnBii + dlnBiiLen, sizeof(dlnBii) - dlnBiiLen, ".") ;
continue ;
}
cb = inB[j];
if (inA[j] != inB[j]) {
// diff / add colors
snprintf(dlnA + dlnALen, sizeof(dlnA) - dlnALen, _GREEN_("%02X "), inA[j]);
snprintf(dlnB + dlnBLen, sizeof(dlnB) - dlnBLen, _RED_("%02X "), inB[j]);
snprintf(dlnAii + dlnAiiLen, sizeof(dlnAii) - dlnAiiLen, _GREEN_("%c"), ((ca < 32) || (ca == 127)) ? '.' : ca);
snprintf(dlnBii + dlnBiiLen, sizeof(dlnBii) - dlnBiiLen, _RED_("%c"), ((cb < 32) || (cb == 127)) ? '.' : cb);
} else {
// normal
snprintf(dlnA + dlnALen, sizeof(dlnA) - dlnALen, "%02X ", inA[j]);
snprintf(dlnB + dlnBLen, sizeof(dlnB) - dlnBLen, "%02X ", inB[j]);
snprintf(dlnAii + dlnAiiLen, sizeof(dlnAii) - dlnAiiLen, "%c", ((ca < 32) || (ca == 127)) ? '.' : ca);
snprintf(dlnBii + dlnBiiLen, sizeof(dlnBii) - dlnBiiLen, "%c", ((cb < 32) || (cb == 127)) ? '.' : cb);
}
}
snprintf(line, sizeof(line), "%s%s | %s%s", dlnA, dlnAii, dlnB, dlnBii);
PrintAndLogEx(INFO, "%03X | %s", i, line);
}
// footer
PrintAndLogEx(INFO, "%s", hdr1);
PrintAndLogEx(NORMAL, "");
free(inB);
free(inA);
return PM3_SUCCESS;
}
/**
* @brief Utility for number conversion in client.
* @param Cmd
* @return
*/
static int CmdNumCon(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data num",
"Function takes a decimal or hexdecimal number and print it in decimal/hex/binary\n"
"Will print message if number is a prime number\n",
"data num --dec 2023\n"
"data num --hex 2A\n"
);
void *argtable[] = {
arg_param_begin,
arg_str0(NULL, "dec", "<dec>", "decimal value"),
arg_str0(NULL, "hex", "<hex>", "hexadecimal value"),
arg_str0(NULL, "bin", "<bin>", "binary value"),
arg_lit0("i", NULL, "print inverted value"),
arg_lit0("r", NULL, "print reversed value"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int dlen = 256;
char dec[256];
memset(dec, 0, sizeof(dec));
int res = CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)dec, sizeof(dec), &dlen);
int hlen = 256;
char hex[256];
memset(hex, 0, sizeof(hex));
res |= CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)hex, sizeof(hex), &hlen);
int blen = 256;
char bin[256];
memset(bin, 0, sizeof(bin));
res |= CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)bin, sizeof(bin), &blen);
bool shall_invert = arg_get_lit(ctx, 4);
bool shall_reverse = arg_get_lit(ctx, 5);
CLIParserFree(ctx);
// sanity checks
if (res) {
PrintAndLogEx(FAILED, "Error parsing bytes");
return PM3_EINVARG;
}
// results for MPI actions
bool ret = false;
(void) ret;
// container of big number
mbedtls_mpi N;
mbedtls_mpi_init(&N);
// hex
if (hlen > 0) {
if (data_verify_hex((uint8_t *)hex, hlen) == false) {
return PM3_EINVARG;
}
MBEDTLS_MPI_CHK(mbedtls_mpi_read_string(&N, 16, hex));
PrintAndLogEx(INFO, "#....... %d", hlen);
}
// decimal
if (dlen > 0) {
// should have decimal string check here too
MBEDTLS_MPI_CHK(mbedtls_mpi_read_string(&N, 10, dec));
}
// binary
if (blen > 0) {
// should have bianry string check here too
MBEDTLS_MPI_CHK(mbedtls_mpi_read_string(&N, 2, bin));
PrintAndLogEx(INFO, "#bits... %d", blen);
}
mbedtls_mpi base;
mbedtls_mpi_init(&base);
mbedtls_mpi_add_int(&base, &base, 10);
// printing
typedef struct {
const char *desc;
uint8_t radix;
} radix_t;
radix_t radix[] = {
{"dec..... ", 10},
{"hex..... ", 16},
{"bin..... ", 2}
};
char s[600] = {0};
size_t slen = 0;
const char pad[] = "00000000000000000000000000000000000000000000000000000000000000000000000000000000";
for (uint8_t i = 0; i < ARRAYLEN(radix); i++) {
MBEDTLS_MPI_CHK(mbedtls_mpi_write_string(&N, radix[i].radix, s, sizeof(s), &slen));
if (slen) {
// only pad bin string
int pn = 0;
if (i == 2) {
if (blen && slen < blen) {
pn = blen - slen + 1;
} else if (hlen && (slen < (hlen * 4))) {
pn = (hlen * 4) - slen + 1;
}
}
PrintAndLogEx(SUCCESS, "%s%.*s%s", radix[i].desc, pn, pad, s);
}
}
// ascii
MBEDTLS_MPI_CHK(mbedtls_mpi_write_string(&N, 16, s, sizeof(s), &slen));
if (slen) {
size_t n = (slen >> 1);
uint8_t *d = calloc(n, sizeof(uint8_t));
if (d != NULL) {
hexstr_to_byte_array(s, d, &n);
PrintAndLogEx(SUCCESS, "ascii... " _YELLOW_("%s"), sprint_ascii((const uint8_t *)d, n));
free(d);
}
}
// reverse
if (shall_reverse) {
PrintAndLogEx(SUCCESS, _CYAN_("Reversed"));
for (uint8_t i = 0; i < ARRAYLEN(radix); i++) {
MBEDTLS_MPI_CHK(mbedtls_mpi_write_string(&N, radix[i].radix, s, sizeof(s), &slen));
if (slen) {
// only pad bin string
char scpy[600] = {0x30};
memset(scpy, 0x30, sizeof(scpy));
int pn = 0;
if (i == 2) {
if (blen && slen < blen) {
pn = blen - slen + 1;
} else if (hlen && (slen < (hlen * 4))) {
pn = (hlen * 4) - slen + 1;
}
}
memcpy(scpy + pn, s, slen);
str_reverse(scpy, strlen(scpy));
PrintAndLogEx(SUCCESS, "%s%s", radix[i].desc, scpy);
}
}
// ascii
MBEDTLS_MPI_CHK(mbedtls_mpi_write_string(&N, 16, s, sizeof(s), &slen));
if (slen) {
str_reverse(s, strlen(s));
size_t n = (slen >> 1);
uint8_t *d = calloc(n, sizeof(uint8_t));
if (d != NULL) {
hexstr_to_byte_array(s, d, &n);
PrintAndLogEx(SUCCESS, "ascii... " _YELLOW_("%s"), sprint_ascii((const uint8_t *)d, n));
free(d);
}
}
}
// invert
if (shall_invert) {
PrintAndLogEx(SUCCESS, _CYAN_("Inverted"));
for (uint8_t i = 0; i < ARRAYLEN(radix); i++) {
MBEDTLS_MPI_CHK(mbedtls_mpi_write_string(&N, radix[i].radix, s, sizeof(s), &slen));
if (slen == 0) {
continue;
}
switch (i) {
case 0: {
// MBEDTLS_MPI_CHK(mbedtls_mpi_inv_mod(&N, &N, &base));
break;
}
case 1: {
str_inverse_hex(s, strlen(s));
PrintAndLogEx(SUCCESS, "%s%s", radix[i].desc, s);
break;
}
case 2: {
char scpy[600] = {0x30};
memset(scpy, 0x30, sizeof(scpy));
int pn = 0;
if (blen && slen < blen) {
pn = blen - slen + 1;
} else if (hlen && (slen < (hlen * 4))) {
pn = (hlen * 4) - slen + 1;
}
memcpy(scpy + pn, s, slen);
str_inverse_bin(scpy, strlen(scpy));
PrintAndLogEx(SUCCESS, "%s%s", radix[i].desc, scpy);
break;
}
default: {
break;
}
}
}
// ascii
MBEDTLS_MPI_CHK(mbedtls_mpi_write_string(&N, 16, s, sizeof(s), &slen));
if (slen) {
str_inverse_hex(s, strlen(s));
size_t n = (slen >> 1);
uint8_t *d = calloc(n, sizeof(uint8_t));
if (d != NULL) {
hexstr_to_byte_array(s, d, &n);
PrintAndLogEx(SUCCESS, "ascii... " _YELLOW_("%s"), sprint_ascii((const uint8_t *)d, n));
free(d);
}
}
}
// check if number is a prime
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
MBEDTLS_MPI_CHK(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0));
res = mbedtls_mpi_is_prime_ext(&N, 50, mbedtls_ctr_drbg_random, &ctr_drbg);
if (res == 0) {
PrintAndLogEx(INFO, "prime... " _YELLOW_("yes"));
}
cleanup:
mbedtls_mpi_free(&N);
mbedtls_mpi_free(&base);
mbedtls_entropy_free(&entropy);
mbedtls_ctr_drbg_free(&ctr_drbg);
return PM3_SUCCESS;
}
int centerThreshold(const int *in, int *out, size_t len, int8_t up, int8_t down) {
if (len < 5) {
return PM3_EINVARG;
}
for (size_t i = 0; i < len; ++i) {
if ((in[i] <= up) && (in[i] >= down)) {
out[i] = 0;
}
}
// clean out spikes.
for (size_t i = 2; i < len - 2; ++i) {
int a = out[i - 2] + out[i - 1];
int b = out[i + 2] + out[i + 1];
if (a == 0 && b == 0) {
out[i] = 0;
}
}
return PM3_SUCCESS;
}
static int CmdCenterThreshold(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data cthreshold",
"Inverse of dirty threshold command, all values between up and down will be average out",
"data cthreshold -u 10 -d -10"
);
void *argtable[] = {
arg_param_begin,
arg_int1("d", "down", "<dec>", "threshold down"),
arg_int1("u", "up", "<dec>", "threshold up"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int8_t down = arg_get_int(ctx, 1);
int8_t up = arg_get_int(ctx, 2);
CLIParserFree(ctx);
PrintAndLogEx(INFO, "Applying up threshold: " _YELLOW_("%i") ", down threshold: " _YELLOW_("%i") "\n", up, down);
centerThreshold(g_GraphBuffer, g_GraphBuffer, g_GraphTraceLen, up, down);
// set signal properties low/high/mean/amplitude and isnoice detection
uint8_t *bits = calloc(g_GraphTraceLen, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = getFromGraphBuffer(bits);
// set signal properties low/high/mean/amplitude and is_noice detection
computeSignalProperties(bits, size);
RepaintGraphWindow();
free(bits);
return PM3_SUCCESS;
}
static int envelope_square(const int *in, int *out, size_t len) {
if (len < 10) {
return PM3_EINVARG;
}
size_t i = 0;
while (i < len - 8) {
if (in[i] == 0 && in[i + 1] == 0 && in[i + 2] == 0 && in[i + 3] == 0 &&
in[i + 4] == 0 && in[i + 5] == 0 && in[i + 6] == 0 && in[i + 7] == 0) {
i += 8;
continue;
}
out[i] = 255;
i++;
}
return PM3_SUCCESS;
}
static int CmdEnvelope(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data envelope",
"Create an square envelope of the samples",
"data envelope"
);
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
envelope_square(g_GraphBuffer, g_GraphBuffer, g_GraphTraceLen);
uint8_t *bits = calloc(g_GraphTraceLen, sizeof(uint8_t));
if (bits == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
size_t size = getFromGraphBuffer(bits);
// set signal properties low/high/mean/amplitude and is_noice detection
computeSignalProperties(bits, size);
RepaintGraphWindow();
free(bits);
return PM3_SUCCESS;
}
static int CmdAtrLookup(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data atr",
"look up ATR record from bytearray\n"
"",
"data atr -d 3B6B00000031C064BE1B0100079000\n"
);
void *argtable[] = {
arg_param_begin,
arg_str0("d", NULL, "<hex>", "ASN1 encoded byte array"),
// arg_lit0("t", "test", "perform self test"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
uint8_t data[129];
int dlen = sizeof(data) - 1; // CLIGetStrWithReturn does not guarantee string to be null-terminated
CLIGetStrWithReturn(ctx, 1, data, &dlen);
// bool selftest = arg_get_lit(ctx, 2);
CLIParserFree(ctx);
// if (selftest) {
// return atr_selftest();
// }
PrintAndLogEx(INFO, "ISO7816-3 ATR... " _YELLOW_("%s"), data);
PrintAndLogEx(INFO, "Fingerprint...");
char *copy = str_dup(getAtrInfo((char *)data));
char *token = strtok(copy, "\n");
while (token != NULL) {
PrintAndLogEx(INFO, " %s", token);
token = strtok(NULL, "\n");
}
free(copy);
return PM3_SUCCESS;
}
static int CmdCryptography(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data crypto",
"This command lets you encrypt or decrypt data using DES/3DES/AES.\n"
"Supply data, key, IV (needed for des MAC or aes), and cryptography action.\n",
"To calculate a MAC for FMCOS, supply challenge as IV, data as data, and session/line protection key as key.\n"
"To calculate a MAC for FeliCa, supply first RC as IV, BLE+data as data and session key as key.\n"
"data crypto -d 04D6850E06AABB80 -k FFFFFFFFFFFFFFFF --iv 9EA0401A00000000 --des -> Calculate a MAC for FMCOS chip. The result should be ED3A0133\n"
);
void *argtable[] = {
arg_param_begin,
arg_str1("d", "data", "<hex>", "Data to process"),
arg_str1("k", "key", "<hex>", "Key to use"),
arg_lit0("r", "rev", "Decrypt, not encrypt"),
arg_lit0(NULL, "des", "Cipher with DES, not AES"),
arg_lit0(NULL, "mac", "Calculate AES CMAC/FeliCa Lite MAC"),
arg_str0(NULL, "iv", "<hex>", "IV value if needed"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
uint8_t dati[250] = {0};
uint8_t dato[250] = {0};
int datilen = 0;
CLIGetHexWithReturn(ctx, 1, dati, &datilen);
uint8_t key[33] = {0};
int keylen = 0;
CLIGetHexWithReturn(ctx, 2, key, &keylen);
uint8_t type = 0;
if (arg_get_lit(ctx, 3)) {
type ^= 0x08;
}
if (arg_get_lit(ctx, 4)) {
type ^= 0x04;
}
if (arg_get_lit(ctx, 5)) {
type ^= 0x02;
}
uint8_t iv[250] = {0};
int ivlen = 0;
CLIGetHexWithReturn(ctx, 6, iv, &ivlen);
CLIParserFree(ctx);
// Do data length check
if ((type & 0x04) == 0x04) { // Use AES(0) or DES(1)?
if (datilen % 8 != 0) {
PrintAndLogEx(ERR, "<data> length must be a multiple of 8. Got %d", datilen);
return PM3_EINVARG;
}
if (keylen != 8 && keylen != 16 && keylen != 24 && keylen != 32) {
PrintAndLogEx(ERR, "<key> must be 8, 16, 24, 32 bytes. Got %d", keylen);
return PM3_EINVARG;
}
} else {
if (datilen % 16 != 0 && ((type & 0x02) == 0)) {
PrintAndLogEx(ERR, "<data> length must be a multiple of 16. Got %d", datilen);
return PM3_EINVARG;
}
if (keylen != 16 && keylen != 32) {
PrintAndLogEx(ERR, "<key> must be 16 or 32 bytes. Got %d", keylen);
return PM3_EINVARG;
}
}
// Encrypt(0) or decrypt(1)?
if ((type & 0x08) == 0x08) {
if ((type & 0x04) == 0x04) { // AES or DES?
if (keylen > 8) {
des3_decrypt(dato, dati, key, keylen / 8);
PrintAndLogEx(INFO, "3DES decrypt... " _YELLOW_("%s"), sprint_hex_inrow(dato, datilen));
} else {
if (ivlen == 0) {
// If there's an IV, use CBC
des_decrypt_ecb(dato, dati, datilen, key);
PrintAndLogEx(INFO, "DES ECB decrypt... " _YELLOW_("%s"), sprint_hex_inrow(dato, datilen));
} else {
des_decrypt_cbc(dato, dati, datilen, key, iv);
PrintAndLogEx(INFO, "DES CBC decrypt... " _YELLOW_("%s"), sprint_hex_inrow(dato, datilen));
}
}
} else {
if (keylen == 32) {
aes256_decode(iv, key, dati, dato, datilen);
PrintAndLogEx(INFO, "AES-256 decrypt... " _YELLOW_("%s"), sprint_hex_inrow(dato, datilen));
} else {
aes_decode(iv, key, dati, dato, datilen);
PrintAndLogEx(INFO, "AES-128 decrypt... " _YELLOW_("%s"), sprint_hex_inrow(dato, datilen));
}
}
} else {
if ((type & 0x04) == 0x04) { // AES or DES?
if ((type & 0x02) == 0x02) { // If we will calculate a MAC
/*PrintAndLogEx(INFO, "Called FeliCa MAC");
// For DES all I know useful is the felica and fudan MAC algorithm.This is just des-cbc, but felica needs it in its way.
for (int i = 0; i < datilen; i+=8){ // For all 8 byte sequences
reverser(dati, &dati[i], 8, i);
if (i){ // If IV is processed
for (int n = 0; n < 8; ++n){
dato[n] ^= dati[i+n]; // XOR with Dx
}
des_encrypt_ecb(dato, dato, 8, key); // Cipher itself
} else { // If we didn't start with IV
for (int n = 0; n < 8; ++n){
dato[n] = iv[n]; // Feed data into output
dato[n] ^= dati[i+n]; // XOR with D1
}
des_encrypt_ecb(dato, dato, 8, key); // Cipher itself
}
}
PrintAndLogEx(SUCCESS, "MAC: %s", sprint_hex_inrow(dato, 8));*/
PrintAndLogEx(INFO, "Not implemented yet - feel free to contribute!");
return PM3_SUCCESS;
} else {
if (keylen > 8) {
des3_encrypt(dato, dati, key, keylen / 8);
PrintAndLogEx(INFO, "3DES encrypt keysize ( %d )... " _YELLOW_("%s"), (keylen / 8), sprint_hex_inrow(dato, datilen));
} else {
if (ivlen == 0) {
// If there's an IV, use ECB
des_encrypt_ecb(dato, dati, datilen, key);
PrintAndLogEx(INFO, "DES ECB encrypt... " _YELLOW_("%s"), sprint_hex_inrow(dato, datilen));
} else {
des_encrypt_cbc(dato, dati, datilen, key, iv);
char pad[250];
memset(pad, ' ', 4 + 8 + (datilen - 8) * 3);
pad[8 + (datilen - 8) * 3] = 0; // Make a padding to insert FMCOS macing algorithm guide
PrintAndLogEx(INFO, "%sVV VV VV VV FMCOS MAC", pad);
PrintAndLogEx(INFO, "DES CBC encrypt... " _YELLOW_("%s"), sprint_hex_inrow(dato, datilen));
}
}
}
} else {
if ((type & 0x02) == 0x02) {
// If we will calculate a MAC
aes_cmac8(iv, key, dati, dato, datilen);
PrintAndLogEx(INFO, "AES CMAC... " _YELLOW_("%s"), sprint_hex_inrow(dato, 8));
} else {
if (keylen == 32) {
aes256_encode(iv, key, dati, dato, datilen);
PrintAndLogEx(INFO, "AES-256 encrypt... " _YELLOW_("%s"), sprint_hex_inrow(dato, datilen));
} else {
aes_encode(iv, key, dati, dato, datilen);
PrintAndLogEx(INFO, "AES-128 encrypt... " _YELLOW_("%s"), sprint_hex_inrow(dato, datilen));
}
}
}
}
return PM3_SUCCESS;
}
static int CmdBinaryMap(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data bmap",
"Breaks down a hex value to binary according a template\n"
" data bmap -d 16 -m 4,4\n"
"This will give two rows each with four bits",
"data bmap -d 3B\n"
"data bmap -d 3B -m 2,5,1\n"
);
void *argtable[] = {
arg_param_begin,
arg_str0("d", NULL, "<hex>", "hex string"),
arg_str0("m", NULL, "<str>", "binary template"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
uint8_t hex[6];
int hlen = sizeof(hex) - 1; // CLIGetStrWithReturn does not guarantee string to be null-terminated
CLIGetStrWithReturn(ctx, 1, hex, &hlen);
uint8_t template[41];
int tlen = sizeof(template) - 1; // CLIGetStrWithReturn does not guarantee string to be null-terminated
CLIGetStrWithReturn(ctx, 2, template, &tlen);
CLIParserFree(ctx);
char bits[(8 * 4) + 1] = {0};
hextobinstring_n(bits, (char *)hex, hlen);
if (tlen == 0) {
template[0] = '8';
template[1] = 0;
}
char *token = strtok((char *)template, ",");
// header
PrintAndLogEx(INFO, "---+-------------------------");
PrintAndLogEx(INFO, " | b0 b1 b2 b3 b4 b5 b6 b7");
PrintAndLogEx(INFO, "---+-------------------------");
uint8_t i = 0;
uint8_t cnt = 1;
int x = 0;
while (token != NULL) {
sscanf(token, "%d", &x);
PrintAndLogEx(INFO, " %d | %.*s" NOLF, cnt, i * 3, " ");
// incease with previous offset
x += i;
for (; i < (uint8_t)x; i++) {
PrintAndLogEx(NORMAL, "%c " NOLF, bits[7 - i]);
}
PrintAndLogEx(NORMAL, "");
token = strtok(NULL, ",");
cnt++;
}
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int CmdXor(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data xor",
"takes input string and xor string. Perform xor on it.\n"
"If no xor string, try the most reoccuring value to xor against",
"data xor -d 99aabbcc8888888888\n"
"data xor -d 99aabbcc --xor 88888888\n"
);
void *argtable[] = {
arg_param_begin,
arg_str1("d", "data", "<hex>", "input hex string"),
arg_str0("x", "xor", "<str>", "input xor string"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int hlen = 128;
uint8_t hex[128 + 1];
CLIGetHexWithReturn(ctx, 1, hex, &hlen);
int xlen = 128;
uint8_t xor[128 + 1];
CLIGetHexWithReturn(ctx, 2, xor, &xlen);
CLIParserFree(ctx);
// find xor value
if (xlen == 0) {
uint8_t x = get_highest_frequency(hex, hlen);
xlen = hlen;
memset(xor, x, xlen);
}
if (hlen != xlen) {
PrintAndLogEx(FAILED, "Length mismatch, got %i != %i", hlen, xlen);
return PM3_EINVARG;
}
PrintAndLogEx(SUCCESS, "input... %s", sprint_hex_inrow(hex, hlen));
PrintAndLogEx(SUCCESS, "xor..... %s", sprint_hex_inrow(xor, xlen));
hex_xor(hex, xor, hlen);
PrintAndLogEx(SUCCESS, "plain... " _YELLOW_("%s"), sprint_hex_inrow(hex, hlen));
return PM3_SUCCESS;
}
static int CmdTestSaveState8(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data test_ss8",
"Tests the implementation of Buffer Save States (8-bit buffer)",
"data test_ss8");
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
srand(time(NULL));
size_t length = (rand() % 256);
PrintAndLogEx(DEBUG, "Testing with length = %llu", length);
uint8_t *srcBuffer = (uint8_t *)calloc(length + 1, sizeof(uint8_t));
//Set up the source buffer with random data
for (int i = 0; i < length; i++) {
srcBuffer[i] = (rand() % 256);
}
buffer_savestate_t test8 = save_buffer8(srcBuffer, length);
PrintAndLogEx(DEBUG, "Save State created, length = %llu, padding = %i, type = %i", test8.bufferSize, test8.padding, test8.type);
test8.clock = rand();
test8.offset = rand();
PrintAndLogEx(DEBUG, "Save State clock = %u, offset = %u", test8.clock, test8.offset);
uint8_t *destBuffer = (uint8_t *)calloc(length, sizeof(uint8_t));
size_t returnedLength = restore_buffer8(test8, destBuffer);
if (returnedLength != length) {
PrintAndLogEx(DEBUG, _YELLOW_("Returned length != expected length!"));
PrintAndLogEx(WARNING, "Returned Length = %llu Buffer Length = %llu Expected = %llu", returnedLength, test8.bufferSize, length);
} else {
PrintAndLogEx(DEBUG, _GREEN_("Lengths match!") "\n");
}
for (size_t i = 0; i < returnedLength; i++) {
if (srcBuffer[i] != destBuffer[i]) {
PrintAndLogEx(FAILED, "Buffers don't match at index %lu!, Expected %i, got %i", i, srcBuffer[i], destBuffer[i]);
free(srcBuffer);
free(destBuffer);
return PM3_EFAILED;
}
PrintAndLogEx(DEBUG, "Index %lu matches: %u", i, destBuffer[i]);
}
PrintAndLogEx(SUCCESS, _GREEN_("Save State (8-bit) test success!") "\n");
free(srcBuffer);
free(destBuffer);
return PM3_SUCCESS;
}
static int CmdTestSaveState32(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data test_ss32",
"Tests the implementation of Buffer Save States (32-bit buffer)",
"data test_ss32");
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
srand(time(NULL));
size_t length = 64;
uint32_t *srcBuffer = (uint32_t *)calloc(length, sizeof(uint32_t));
//Set up the source buffer with random data
for (size_t i = 0; i < length; i++) {
srcBuffer[i] = (rand());
}
buffer_savestate_t test32 = save_buffer32(srcBuffer, length);
PrintAndLogEx(DEBUG, "Save State created, length=%llu, type=%i", test32.bufferSize, test32.type);
test32.clock = rand();
test32.offset = rand();
PrintAndLogEx(DEBUG, "Save State clock=%u, offset=%u", test32.clock, test32.offset);
uint32_t *destBuffer = (uint32_t *)calloc(length, sizeof(uint32_t));
size_t returnedLength = restore_buffer32(test32, destBuffer);
if (returnedLength != length) {
PrintAndLogEx(FAILED, "Return Length != Buffer Length! Expected '%llu', got '%llu", g_DemodBufferLen, returnedLength);
free(srcBuffer);
free(destBuffer);
return PM3_EFAILED;
}
PrintAndLogEx(DEBUG, _GREEN_("Lengths match!") "\n");
for (size_t i = 0; i < length; i++) {
if (srcBuffer[i] != destBuffer[i]) {
PrintAndLogEx(FAILED, "Buffers don't match at index %lu!, Expected %i, got %i", i, srcBuffer[i], destBuffer[i]);
free(srcBuffer);
free(destBuffer);
return PM3_EFAILED;
}
PrintAndLogEx(DEBUG, "Index %lu matches: %i", i, destBuffer[i]);
}
PrintAndLogEx(SUCCESS, _GREEN_("Save State (32-bit) test success!") "\n");
free(srcBuffer);
free(destBuffer);
return PM3_SUCCESS;
}
static int CmdTestSaveState32S(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data test_ss32s",
"Tests the implementation of Buffer Save States (32-bit signed buffer)",
"data test_ss32s");
void *argtable[] = {
arg_param_begin,
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIParserFree(ctx);
srand(time(NULL));
size_t length = 64;
int32_t *srcBuffer = (int32_t *)calloc(length, sizeof(int32_t));
//Set up the source buffer with random data
for (int i = 0; i < length; i++) {
srcBuffer[i] = (rand() - 4294967296);
}
buffer_savestate_t test32 = save_bufferS32(srcBuffer, length);
PrintAndLogEx(DEBUG, "Save State created, length=%llu, type=%i", test32.bufferSize, test32.type);
test32.clock = rand();
test32.offset = rand();
PrintAndLogEx(DEBUG, "Save State clock=%u, offset=%u", test32.clock, test32.offset);
int32_t *destBuffer = (int32_t *)calloc(length, sizeof(int32_t));
size_t returnedLength = restore_bufferS32(test32, destBuffer);
if (returnedLength != length) {
PrintAndLogEx(FAILED, "Return Length != Buffer Length! Expected '%llu', got '%llu", g_DemodBufferLen, returnedLength);
free(srcBuffer);
free(destBuffer);
return PM3_EFAILED;
}
PrintAndLogEx(DEBUG, _GREEN_("Lengths match!") "\n");
for (int i = 0; i < length; i++) {
if (srcBuffer[i] != destBuffer[i]) {
PrintAndLogEx(FAILED, "Buffers don't match at index %i!, Expected %i, got %i", i, srcBuffer[i], destBuffer[i]);
free(srcBuffer);
free(destBuffer);
return PM3_EFAILED;
}
PrintAndLogEx(DEBUG, "Index %i matches: %i", i, destBuffer[i]);
}
PrintAndLogEx(SUCCESS, _GREEN_("Save State (signed 32-bit) test success!") "\n");
free(srcBuffer);
free(destBuffer);
return PM3_SUCCESS;
}
static int CmdQRcode(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data qrcode",
"Generate a QR code with the input data",
"data qrcode -f <filename>\n"
"data qrcode -d 0123456789\n"
"data qrcode -d AABBCCDD --ascii\n"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("a", "ascii", "Render with ASCII-safe characters"),
arg_str0("f", "file", "<fn>", "Specify a filename"),
arg_str0("d", "data", "<hex>", "message as a single hex byte string"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
bool ascii = arg_get_lit(ctx, 1);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = { 0 };
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
int dlen = 0;
uint8_t data[512] = { 0 };
int data_arg_len = 0;
char data_arg[1024] = { 0 };
struct arg_str *data_arg_str = arg_get_str(ctx, 3);
int data_count = data_arg_str->count;
if (data_count > 1) {
CLIParserFree(ctx);
PrintAndLogEx(ERR, "QR data accepts exactly one -d value. Use a single contiguous hex string and encode spaces as 20.");
return PM3_EINVARG;
}
if (data_count == 1) {
if (CLIParamStrToBuf(data_arg_str, (uint8_t *)data_arg, sizeof(data_arg), &data_arg_len) != 0) {
CLIParserFree(ctx);
return PM3_EINVARG;
}
if (strpbrk(data_arg, " \t") != NULL) {
CLIParserFree(ctx);
PrintAndLogEx(ERR, "QR data must be one contiguous hex string. Spaces are not supported; encode a space byte as 20.");
return PM3_EINVARG;
}
for (int i = 0; i < data_arg_len; i++) {
if (isxdigit((unsigned char)data_arg[i]) == 0) {
CLIParserFree(ctx);
PrintAndLogEx(ERR, "QR data must contain only hex characters 0-9, a-f, or A-F.");
return PM3_EINVARG;
}
}
if (data_arg_len & 1) {
CLIParserFree(ctx);
PrintAndLogEx(ERR, "QR data must contain an even number of hex digits.");
return PM3_EINVARG;
}
dlen = hex_to_bytes(data_arg, data, sizeof(data));
if (dlen < 0) {
CLIParserFree(ctx);
PrintAndLogEx(ERR, "QR data must be valid hex bytes.");
return PM3_EINVARG;
}
}
CLIParserFree(ctx);
if (fnlen && dlen) {
PrintAndLogEx(WARNING, "Please specify filename or datastring");
return PM3_EINVARG;
}
QRCode qrcode;
if (fnlen) {
uint8_t *dump = NULL;
size_t bytes_read = 0;
// read from file
int res = pm3_load_dump(filename, (void **)&dump, &bytes_read, (MFBLOCK_SIZE * MIFARE_4K_MAXBLOCK));
if (res != PM3_SUCCESS) {
return res;
}
// check file size corresponds to card size.
if (dump != NULL) {
free(dump);
return PM3_EFILE;
}
}
if (dlen) {
int8_t smallest_version = qrcode_getMinVersionForBytes(dlen, ECC_LOW);
if (smallest_version < 1) {
PrintAndLogEx(ERR, "QR data is too large to fit in a supported QR code.");
return PM3_EINVARG;
}
uint8_t qr_arr[qrcode_getBufferSize(smallest_version)];
if (qrcode_initBytes(&qrcode, qr_arr, smallest_version, ECC_LOW, data, dlen) < 0) {
PrintAndLogEx(ERR, "Failed to encode QR data.");
return PM3_ESOFT;
}
PrintAndLogEx(NORMAL, "");
if (ascii) {
qrcode_print_matrix_ascii(&qrcode);
} else {
qrcode_print_matrix_utf8(&qrcode);
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(NORMAL, "");
qrcode_print_matrix_utf8_2x2(&qrcode);
}
PrintAndLogEx(NORMAL, "");
}
return PM3_SUCCESS;
}
//-----------------------------------------------------------------------------
// Frequency domain analysis
//
// These commands all read the graph buffer, never write to it unless asked,
// The transforms themselves live in pm3_dsp.c
//-----------------------------------------------------------------------------
static size_t largest_pow2_le(size_t v) {
size_t p = 1;
while ((p << 1) <= v && (p << 1) <= PM3_DSP_MAX_FFT) {
p <<= 1;
}
return p;
}
// Shared front end for the host side DSP commands. Validates the requested
// window against the graph buffer, rejects degenerate input, and hands back a
// mean removed, unit variance copy of the samples. Caller frees *sig.
//
// `size` == 0 means "the largest power of two that fits from start". When a
// size is given it is rounded up to a power of two for the transform length,
// which is returned in *n, while *count stays at the number of real samples.
static int dsp_prepare(int start, int size, double **sig, size_t *count, size_t *n) {
*sig = NULL;
*count = 0;
*n = 0;
if (g_GraphTraceLen == 0) {
PrintAndLogEx(WARNING, "GraphBuffer is empty");
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("data load -f <file>") "` or `" _YELLOW_("lf read") "` to get samples");
return PM3_EINVARG;
}
if (start < 0) {
PrintAndLogEx(WARNING, "start must not be negative");
return PM3_EINVARG;
}
if ((size_t)start >= g_GraphTraceLen) {
PrintAndLogEx(WARNING, "start ( " _YELLOW_("%d") " ) is past the end of the trace ( " _YELLOW_("%zu") " samples )"
, start
, g_GraphTraceLen
);
return PM3_EINVARG;
}
if (size < 0) {
PrintAndLogEx(WARNING, "size must not be negative");
return PM3_EINVARG;
}
const size_t avail = g_GraphTraceLen - (size_t)start;
if (avail < 4) {
PrintAndLogEx(WARNING, "not enough samples from start ( " _YELLOW_("%zu") " ), need at least 4", avail);
return PM3_EINVARG;
}
if (size == 0) {
*n = largest_pow2_le(avail);
*count = *n;
} else {
if ((size_t)size > avail) {
PrintAndLogEx(WARNING, "size ( " _YELLOW_("%d") " ) is larger than the %zu samples available from start", size, avail);
return PM3_EINVARG;
}
if ((size_t)size > PM3_DSP_MAX_FFT) {
PrintAndLogEx(WARNING, "size ( " _YELLOW_("%d") " ) exceeds the maximum transform length of %d", size, PM3_DSP_MAX_FFT);
return PM3_EINVARG;
}
*count = (size_t)size;
*n = pm3_next_pow2(*count);
}
if (*n < 4) {
PrintAndLogEx(WARNING, "transform length ( " _YELLOW_("%zu") " ) is too short to be useful", *n);
return PM3_EINVARG;
}
// a flat window carries no information and every downstream statistic
// would divide by zero. Catch it here rather than printing NaN tables.
bool flat = true;
for (size_t i = 1; i < *count; i++) {
if (g_GraphBuffer[start + i] != g_GraphBuffer[start]) {
flat = false;
break;
}
}
if (flat) {
PrintAndLogEx(WARNING, "all %zu samples in the window are identical, nothing to analyse", *count);
return PM3_EINVARG;
}
*sig = pm3_extract(g_GraphBuffer, g_GraphTraceLen, (size_t)start, *count);
if (*sig == NULL) {
PrintAndLogEx(WARNING, "failed to allocate memory");
return PM3_EMALLOC;
}
return PM3_SUCCESS;
}
// Frequencies are reported in cycles/sample as the primary unit. Hz is only
// printed when the user tells us the sample rate with --fs: the client keeps no
// record of the divisor a trace was captured with, and assuming 125 kHz would
// silently mislabel every HF and every non default LF capture.
static int CmdFFT(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data fft",
"Fourier transform of the samples in the GraphBuffer.\n"
"Reports the magnitude spectrum of the first N/2+1 bins, the input being real.\n"
"The DC component is removed and the window is normalised to unit variance\n"
"before the transform, so magnitudes are comparable between captures.",
"data fft --> transform the whole buffer\n"
"data fft --size 4096 --win blackman --> 4096 point transform, blackman window\n"
"data fft --start 1000 --size 8192 --db --> magnitudes in dB relative to the peak\n"
"data fft --size 4096 --graph --> put the spectrum in the graph window\n"
);
void *argtable[] = {
arg_param_begin,
arg_int0(NULL, "start", "<int>", "first sample of the GraphBuffer to use (def 0)"),
arg_int0(NULL, "size", "<int>", "transform length, rounded up to a power of two (def largest that fits)"),
arg_str0(NULL, "win", "<hann|hamming|blackman|rect>", "window function (def hann)"),
arg_lit0(NULL, "db", "report magnitude in dB relative to the peak bin instead of linear"),
arg_lit0(NULL, "graph", "write the magnitude spectrum into the GraphBuffer and repaint"),
arg_int0(NULL, "fs", "<int>", "sample rate in Hz, adds a frequency column in Hz"),
arg_int0(NULL, "bins", "<int>", "how many bins to print (def 32, 0 for all)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int start = arg_get_int_def(ctx, 1, 0);
int size = arg_get_int_def(ctx, 2, 0);
char win_str[16] = {0};
int win_len = sizeof(win_str) - 1;
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)win_str, win_len, &win_len);
bool use_db = arg_get_lit(ctx, 4);
bool to_graph = arg_get_lit(ctx, 5);
int fs = arg_get_int_def(ctx, 6, 0);
int want_bins = arg_get_int_def(ctx, 7, 32);
CLIParserFree(ctx);
pm3_window_t win = PM3_WIN_HANN;
if (win_len > 0 && pm3_window_from_str(win_str, &win) == false) {
PrintAndLogEx(WARNING, "unknown window `%s`, expected hann, hamming, blackman or rect", win_str);
return PM3_EINVARG;
}
if (want_bins < 0) {
PrintAndLogEx(WARNING, "bins must not be negative");
return PM3_EINVARG;
}
double *sig = NULL;
size_t count = 0, n = 0;
int res = dsp_prepare(start, size, &sig, &count, &n);
if (res != PM3_SUCCESS) {
return res;
}
pm3_spectrum_t spec;
res = pm3_spectrum(sig, count, n, win, &spec);
free(sig);
if (res != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "failed to compute the spectrum");
return res;
}
const double bin_width = 1.0 / (double)n;
const double peak_freq = (double)spec.peak_bin * bin_width;
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Fourier transform") " ---------------------------------");
PrintAndLogEx(INFO, " samples....... " _YELLOW_("%zu") " from offset " _YELLOW_("%d"), count, start);
PrintAndLogEx(INFO, " transform..... " _YELLOW_("%zu") " point, %zu bins reported", n, spec.nbins);
PrintAndLogEx(INFO, " window........ " _YELLOW_("%s"), pm3_window_name(win));
PrintAndLogEx(INFO, " bin width..... " _YELLOW_("%.6f") " cycles/sample", bin_width);
if (fs > 0) {
PrintAndLogEx(INFO, " sample rate... " _YELLOW_("%d") " Hz ( %.2f Hz per bin )", fs, bin_width * (double)fs);
} else {
PrintAndLogEx(INFO, " sample rate... " _YELLOW_("unknown") ", use `--fs` to add a Hz column");
}
PrintAndLogEx(INFO, " peak bin...... " _GREEN_("%zu") " ( %.6f cycles/sample, clock %.2f samples/symbol )"
, spec.peak_bin
, peak_freq
, (peak_freq > 0.0) ? 1.0 / peak_freq : 0.0
);
if (fs > 0) {
PrintAndLogEx(INFO, " peak.......... " _GREEN_("%.1f") " Hz", peak_freq * (double)fs);
}
size_t print_bins = (want_bins == 0) ? spec.nbins : (size_t)want_bins;
if (print_bins > spec.nbins) {
print_bins = spec.nbins;
}
if (print_bins) {
PrintAndLogEx(NORMAL, "");
if (fs > 0) {
PrintAndLogEx(INFO, " bin cycles/sample Hz %s", use_db ? " mag (dB)" : " magnitude");
PrintAndLogEx(INFO, "------+--------------+-----------+-----------");
} else {
PrintAndLogEx(INFO, " bin cycles/sample %s", use_db ? " mag (dB)" : " magnitude");
PrintAndLogEx(INFO, "------+--------------+-----------");
}
for (size_t i = 0; i < print_bins; i++) {
const double f = (double)i * bin_width;
char val[32];
if (use_db) {
snprintf(val, sizeof(val), "%9.2f", 20.0 * log10((spec.mag[i] / (spec.peak + 1e-30)) + 1e-30));
} else {
snprintf(val, sizeof(val), "%9.3f", spec.mag[i]);
}
if (fs > 0) {
PrintAndLogEx(INFO, "%5zu | %12.6f | %9.1f | %s", i, f, f * (double)fs, val);
} else {
PrintAndLogEx(INFO, "%5zu | %12.6f | %s", i, f, val);
}
}
if (print_bins < spec.nbins) {
PrintAndLogEx(INFO, "( %zu of %zu bins shown, use `--bins 0` for all )", print_bins, spec.nbins);
}
}
if (to_graph) {
// dB against the peak keeps the low level structure visible, a linear
// plot of an LF spectrum is one spike and a flat line
for (size_t i = 0; i < spec.nbins; i++) {
double db = 20.0 * log10((spec.mag[i] / (spec.peak + 1e-30)) + 1e-30);
if (db < -96.0) {
db = -96.0;
}
g_GraphBuffer[i] = (int32_t)((db + 96.0) * (255.0 / 96.0)) - 128;
}
g_GraphTraceLen = spec.nbins;
// the x axis is now bins, not samples. Drive the existing cursor
// scale so a marker delta reads out directly in cycles/sample.
setClockGrid(0, 0);
g_CursorScaleFactor = (double)n;
snprintf(g_CursorScaleFactorUnit, sizeof(g_CursorScaleFactorUnit), "c/sample");
RepaintGraphWindow();
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(WARNING, "GraphBuffer now holds a " _YELLOW_("magnitude spectrum"));
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("data load -f <file>") "` to get the original samples back");
}
pm3_spectrum_free(&spec);
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static void print_peak_table(const char *title, const pm3_peak_t *peaks, size_t n, int fs) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "%s", title);
if (n == 0) {
PrintAndLogEx(INFO, " no peaks found above the noise floor");
return;
}
if (fs > 0) {
PrintAndLogEx(INFO, " bin cycles/sample Hz clock mag(dB) prom(dB)");
PrintAndLogEx(INFO, "------+--------------+-----------+---------+----------+----------");
} else {
PrintAndLogEx(INFO, " bin cycles/sample clock mag(dB) prom(dB)");
PrintAndLogEx(INFO, "------+--------------+---------+----------+----------");
}
for (size_t i = 0; i < n; i++) {
if (fs > 0) {
PrintAndLogEx(INFO, "%5zu | %12.6f | %9.1f | %7.2f | %8.2f | %8.2f"
, peaks[i].bin
, peaks[i].freq
, peaks[i].freq * (double)fs
, peaks[i].clk
, peaks[i].mag_db
, peaks[i].prominence
);
} else {
PrintAndLogEx(INFO, "%5zu | %12.6f | %7.2f | %8.2f | %8.2f"
, peaks[i].bin
, peaks[i].freq
, peaks[i].clk
, peaks[i].mag_db
, peaks[i].prominence
);
}
}
}
// Pick the analysis window when the user did not. A capture long enough to
// have a choice gets one cheap look at its spectrum first, because the right
// window depends on the symbol rate and nothing knows that yet. The spectral
// estimate can land on a harmonic of the true rate, so size for twice what it
// reports rather than for exactly it.
static int autodemod_window(int start, size_t avail) {
size_t want = MIN(avail, (size_t)FITSCORE_DEFAULT_WINDOW);
if (avail <= (size_t)FITSCORE_DEFAULT_WINDOW) {
return (int)want;
}
double *probe = NULL;
size_t count = 0, n = 0;
if (dsp_prepare(start, (int)want, &probe, &count, &n) != PM3_SUCCESS) {
return (int)want;
}
pm3_spec_analysis_t an;
const int res = pm3_analyse(probe, count, n, PM3_WIN_HANN, &an);
free(probe);
if (res != PM3_SUCCESS || an.have_symbol_clk == false) {
return (int)want;
}
const double need = an.symbol_clk * 2.0 * (double)FITSCORE_MIN_SYMBOLS;
if (need > (double)want) {
want = MIN(avail, (size_t)need);
want = MIN(want, (size_t)PM3_DSP_MAX_FFT);
}
return (int)want;
}
// Keep a copy of the graph buffer the first time something is about to
// overwrite it. `data autodemod` rectifies and resamples in place so the
// demodulators see what it decided on, and both have to be undoable.
static void autodemod_backup(int32_t **backup, size_t len) {
if (*backup != NULL || len == 0) {
return;
}
*backup = calloc(len, sizeof(int32_t));
if (*backup != NULL) {
memcpy(*backup, g_GraphBuffer, len * sizeof(int32_t));
}
}
// Write `len` doubles into the graph buffer, scaled to the +/-100 the
// demodulators expect from a loaded trace.
static void autodemod_store(const double *src, size_t len) {
double peak = 0.0;
for (size_t i = 0; i < len; i++) {
if (fabs(src[i]) > peak) {
peak = fabs(src[i]);
}
}
if (peak <= 0.0) {
peak = 1.0;
}
for (size_t i = 0; i < len; i++) {
g_GraphBuffer[i] = (int32_t)((src[i] / peak) * 100.0);
}
g_GraphTraceLen = len;
}
static void print_family_hint(const pm3_spec_analysis_t *an) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Modulation family") " ---------------------------------");
if (an->needs_envelope) {
PrintAndLogEx(INFO, " carrier....... rf/" _YELLOW_("%.2f") ", switched on and off ( depth " _YELLOW_("%.2f") " )"
, an->carrier_clk
, an->envelope_depth
);
PrintAndLogEx(INFO, " two samples per carrier period, so the data is in the envelope, not the phase");
PrintAndLogEx(INFO, " everything below was measured on the rectified envelope");
}
if (an->family == PM3_FAM_UNKNOWN) {
PrintAndLogEx(WARNING, "no clear spectral signature, confidence " _RED_("none"));
PrintAndLogEx(INFO, "the capture may be noise, or the symbol rate may be outside the search range");
return;
}
if (an->confidence >= PM3_CONF_MEDIUM) {
PrintAndLogEx(SUCCESS, "looks like " _GREEN_("%s") ", confidence " _GREEN_("%s")
, pm3_family_name(an->family)
, pm3_confidence_name(an->confidence)
);
} else {
PrintAndLogEx(WARNING, "looks like " _YELLOW_("%s") ", confidence " _YELLOW_("%s")
, pm3_family_name(an->family)
, pm3_confidence_name(an->confidence)
);
}
switch (an->family) {
case PM3_FAM_FSK:
PrintAndLogEx(INFO, " field clocks.. rf/" _YELLOW_("%.2f") " and rf/" _YELLOW_("%.2f") " ( ratio %.3f )"
, an->fsk_clk_hi
, an->fsk_clk_lo
, an->fsk_ratio
);
PrintAndLogEx(INFO, " two strong lines at a stable ratio is the FSK signature");
break;
case PM3_FAM_PSK:
PrintAndLogEx(INFO, " subcarrier.... rf/" _YELLOW_("%.2f"), an->carrier_clk);
PrintAndLogEx(INFO, " carrier is suppressed at the symbol rate, the line only appears after squaring");
break;
case PM3_FAM_MANCHESTER:
PrintAndLogEx(INFO, " null at DC, energy concentrated at the symbol rate");
break;
case PM3_FAM_ASK:
PrintAndLogEx(INFO, " a line at the symbol rate survives in the plain spectrum");
break;
case PM3_FAM_NRZ:
PrintAndLogEx(INFO, " maximum at DC with a sinc squared skirt");
break;
case PM3_FAM_UNKNOWN:
break;
}
if (an->have_symbol_clk) {
PrintAndLogEx(INFO, " symbol clock.. " _GREEN_("%.2f") " samples/symbol", an->symbol_clk);
const double frac = fabs(an->symbol_clk - floor(an->symbol_clk + 0.5));
if (frac > 0.05) {
PrintAndLogEx(WARNING, " clock is " _YELLOW_("%.2f") " samples off the nearest integer", frac);
PrintAndLogEx(INFO, " a fractional clock decodes cleanly at the start of a capture and drifts out later");
}
}
PrintAndLogEx(INFO, " this is a hint from spectral shape alone, no bits have been decoded");
}
// Renders the spectrogram as a character ramp. Rows are frequency bands,
// columns are time. Both are folded to fit a terminal.
static void print_heatmap(const pm3_stft_t *st) {
static const char ramp[] = " .:-=+*#%@";
const size_t nramp = sizeof(ramp) - 2;
const size_t cols = (st->nframes < 96) ? st->nframes : 96;
const size_t rows = 20;
// only the lower part of the spectrum carries the symbol rate, the top
// octave is almost always empty on an LF capture
const size_t band_hi = st->nbins;
double peak = 0.0;
for (size_t i = 0; i < st->nframes * st->nbins; i++) {
if (st->ridge[i] > peak) {
peak = st->ridge[i];
}
}
if (peak <= 0.0) {
return;
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Spectrogram") " ---------------------------------------");
PrintAndLogEx(INFO, "rows are frequency ( top = Nyquist ), columns are time, `%c` is the peak", ramp[nramp]);
char line[128];
for (size_t r = 0; r < rows; r++) {
// row 0 is the top of the spectrum
const size_t b_lo = ((rows - 1 - r) * band_hi) / rows;
size_t b_hi = ((rows - r) * band_hi) / rows;
if (b_hi <= b_lo) {
b_hi = b_lo + 1;
}
for (size_t c = 0; c < cols; c++) {
const size_t f_lo = (c * st->nframes) / cols;
size_t f_hi = ((c + 1) * st->nframes) / cols;
if (f_hi <= f_lo) {
f_hi = f_lo + 1;
}
double best = 0.0;
for (size_t f = f_lo; f < f_hi && f < st->nframes; f++) {
for (size_t b = b_lo; b < b_hi && b < st->nbins; b++) {
const double v = st->ridge[(f * st->nbins) + b];
if (v > best) {
best = v;
}
}
}
size_t idx = (size_t)((best / peak) * (double)nramp);
if (idx > nramp) {
idx = nramp;
}
line[c] = ramp[idx];
}
line[cols] = '\0';
const double f_top = (double)b_hi / (double)st->n;
PrintAndLogEx(INFO, "%8.4f |%s|", f_top, line);
}
PrintAndLogEx(INFO, " +%.*s+", (int)cols, "--------------------------------------------------------------------------------------------------");
PrintAndLogEx(INFO, " sample %-*zu%zu", (int)(cols - 8), st->frame_start[0], st->frame_start[st->nframes - 1] + st->n);
}
static void report_stft(const pm3_stft_t *st, int start) {
print_heatmap(st);
// median of the dominant rate, used as the reference the ridge must stay close to
double *sorted = calloc(st->nframes, sizeof(double));
if (sorted == NULL) {
PrintAndLogEx(WARNING, "failed to allocate memory");
return;
}
memcpy(sorted, st->peak_freq, st->nframes * sizeof(double));
for (size_t i = 1; i < st->nframes; i++) {
const double v = sorted[i];
size_t j = i;
while (j > 0 && sorted[j - 1] > v) {
sorted[j] = sorted[j - 1];
j--;
}
sorted[j] = v;
}
const double median = sorted[st->nframes / 2];
memcpy(sorted, st->peak_mag, st->nframes * sizeof(double));
for (size_t i = 1; i < st->nframes; i++) {
const double v = sorted[i];
size_t j = i;
while (j > 0 && sorted[j - 1] > v) {
sorted[j] = sorted[j - 1];
j--;
}
sorted[j] = v;
}
const double median_mag = sorted[st->nframes / 2];
free(sorted);
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Ridge") " ---------------------------------------------");
if (median <= 0.0) {
PrintAndLogEx(WARNING, "no coherent rate found in any frame");
return;
}
// longest run of frames whose dominant rate stays within 5% of the median
size_t best_lo = 0, best_len = 0, run_lo = 0, run_len = 0;
for (size_t f = 0; f < st->nframes; f++) {
// a frame counts as coherent when the clock is where we expect it and
// there is actually something there. Frames before the tag enters the
// field pass the first test on noise alone, hence the second.
const bool ok = ((fabs(st->peak_freq[f] - median) / median) < 0.10)
&& (st->peak_mag[f] > (0.25 * median_mag));
if (ok) {
if (run_len == 0) {
run_lo = f;
}
run_len++;
if (run_len > best_len) {
best_len = run_len;
best_lo = run_lo;
}
} else {
run_len = 0;
}
}
if (best_len == 0) {
PrintAndLogEx(WARNING, "the dominant rate never settles, the capture may be all noise");
return;
}
const size_t s_lo = start + st->frame_start[best_lo];
const size_t s_hi = start + st->frame_start[best_lo + best_len - 1] + st->n;
PrintAndLogEx(INFO, " coherent...... samples " _GREEN_("%zu") " .. " _GREEN_("%zu") " ( %zu of %zu frames )"
, s_lo
, s_hi
, best_len
, st->nframes
);
if (best_len < st->nframes) {
if (s_hi < g_GraphTraceLen) {
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("data rtrim -i %zu") "` to drop the incoherent tail", s_hi);
}
if (s_lo > 0) {
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("data ltrim -i %zu") "` to drop the incoherent head", s_lo);
}
} else {
PrintAndLogEx(INFO, " the whole capture holds a coherent clock, no trimming needed");
}
// drift, measured over the coherent span only
const size_t edge = (best_len / 10) ? (best_len / 10) : 1;
double f_start = 0.0, f_end = 0.0;
for (size_t i = 0; i < edge; i++) {
f_start += st->peak_freq[best_lo + i];
f_end += st->peak_freq[best_lo + best_len - 1 - i];
}
f_start /= (double)edge;
f_end /= (double)edge;
if (f_start > 0.0 && f_end > 0.0) {
const double clk_start = 1.0 / f_start;
const double clk_end = 1.0 / f_end;
const double pct = ((clk_end - clk_start) / clk_start) * 100.0;
PrintAndLogEx(INFO, " clock drift... " _YELLOW_("%.2f") " -> " _YELLOW_("%.2f") " samples/symbol ( %+.2f %% )"
, clk_start
, clk_end
, pct
);
if (fabs(pct) > 1.0) {
PrintAndLogEx(WARNING, " the clock moves across the capture, a single integer clock will not fit all of it");
}
}
// discontinuities
size_t jumps = 0;
for (size_t f = 1; f < st->nframes; f++) {
const double d = fabs(st->peak_freq[f] - st->peak_freq[f - 1]);
if ((d / median) > 0.15) {
if (jumps < 8) {
PrintAndLogEx(INFO, " rate jump..... at sample " _YELLOW_("%zu") " ( %.2f -> %.2f samples/symbol )"
, (size_t)start + st->frame_start[f]
, (st->peak_freq[f - 1] > 0.0) ? 1.0 / st->peak_freq[f - 1] : 0.0
, (st->peak_freq[f] > 0.0) ? 1.0 / st->peak_freq[f] : 0.0
);
}
jumps++;
}
}
if (jumps > 8) {
PrintAndLogEx(INFO, " ( %zu more jumps not shown )", jumps - 8);
}
if (jumps == 0) {
PrintAndLogEx(INFO, " rate jumps.... " _GREEN_("none"));
}
}
static int CmdSpectrum(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data spectrum",
"Spectral analysis of the GraphBuffer.\n"
"Reports the strongest spectral peaks with a sub bin estimate of the symbol clock,\n"
"the squaring and delay-and-multiply spectra which expose rates the plain spectrum\n"
"nulls out, and a modulation family hint derived from spectral shape alone.\n"
"Run this on a trace that will not decode.",
"data spectrum --> peak table and family hint\n"
"data spectrum --top 8 --sq --> more peaks, plus the squaring spectrum\n"
"data spectrum --stft --size 2048 --> spectrogram over the whole capture\n"
"data spectrum --stft --size 2048 --hop 256 --> finer time resolution\n"
);
void *argtable[] = {
arg_param_begin,
arg_int0(NULL, "start", "<int>", "first sample of the GraphBuffer to use (def 0)"),
arg_int0(NULL, "size", "<int>", "transform length, rounded up to a power of two (def largest that fits)"),
arg_str0(NULL, "win", "<hann|hamming|blackman|rect>", "window function (def hann)"),
arg_int0(NULL, "top", "<int>", "number of peaks to report (def 5)"),
arg_lit0(NULL, "sq", "also show the squaring and delay-and-multiply spectra"),
arg_lit0(NULL, "stft", "sliding window spectrogram, ridge tracking and drift"),
arg_int0(NULL, "hop", "<int>", "STFT hop in samples (def size/4)"),
arg_int0(NULL, "fs", "<int>", "sample rate in Hz, adds a frequency column in Hz"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int start = arg_get_int_def(ctx, 1, 0);
int size = arg_get_int_def(ctx, 2, 0);
char win_str[16] = {0};
int win_len = sizeof(win_str) - 1;
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)win_str, win_len, &win_len);
int top = arg_get_int_def(ctx, 4, 5);
bool show_sq = arg_get_lit(ctx, 5);
bool do_stft = arg_get_lit(ctx, 6);
int hop = arg_get_int_def(ctx, 7, 0);
int fs = arg_get_int_def(ctx, 8, 0);
CLIParserFree(ctx);
pm3_window_t win = PM3_WIN_HANN;
if (win_len > 0 && pm3_window_from_str(win_str, &win) == false) {
PrintAndLogEx(WARNING, "unknown window `%s`, expected hann, hamming, blackman or rect", win_str);
return PM3_EINVARG;
}
if (top < 1 || top > PM3_DSP_MAX_PEAKS) {
PrintAndLogEx(WARNING, "top must be between 1 and %d", PM3_DSP_MAX_PEAKS);
return PM3_EINVARG;
}
if (hop < 0) {
PrintAndLogEx(WARNING, "hop must not be negative");
return PM3_EINVARG;
}
double *sig = NULL;
size_t count = 0, n = 0;
int res = dsp_prepare(start, size, &sig, &count, &n);
if (res != PM3_SUCCESS) {
return res;
}
pm3_spec_analysis_t an;
res = pm3_analyse(sig, count, n, win, &an);
if (res != PM3_SUCCESS) {
free(sig);
PrintAndLogEx(WARNING, "spectral analysis failed");
return res;
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Spectrum") " ------------------------------------------");
PrintAndLogEx(INFO, " samples..... " _YELLOW_("%zu") " from offset " _YELLOW_("%d"), count, start);
PrintAndLogEx(INFO, " transform... " _YELLOW_("%zu") " point, %s window", n, pm3_window_name(win));
PrintAndLogEx(INFO, " bin width... " _YELLOW_("%.6f") " cycles/sample", 1.0 / (double)n);
print_peak_table("--- " _CYAN_("Peaks") " ---------------------------------------------", an.peaks, MIN(an.npeaks, (size_t)top), fs);
PrintAndLogEx(INFO, "clock is the reciprocal of the frequency");
if (show_sq) {
print_peak_table("--- " _CYAN_("Squaring spectrum") " ( y = x * x ) -------------", an.sq_peaks, MIN(an.nsq_peaks, (size_t)top), fs);
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Delay and multiply") " ( y[i] = x[i] * x[i-d] ) ---");
PrintAndLogEx(INFO, " lag clock prom(dB)");
PrintAndLogEx(INFO, "------+---------+----------");
for (size_t i = 0; i < an.ndelay; i++) {
if (an.delay_valid[i] == false) {
continue;
}
PrintAndLogEx(INFO, "%5zu | %7.2f | %8.2f", an.delay[i], an.delay_peak[i].clk, an.delay_peak[i].prominence);
}
PrintAndLogEx(INFO, "squaring a BPSK subcarrier collapses the +/-180 modulation into a tone at twice the carrier");
}
print_family_hint(&an);
if (do_stft) {
size_t stft_hop = (hop > 0) ? (size_t)hop : (n / 4);
if (stft_hop == 0) {
stft_hop = 1;
}
// the spectrogram wants the whole trace from start
const size_t avail = g_GraphTraceLen - (size_t)start;
double *full = pm3_extract(g_GraphBuffer, g_GraphTraceLen, (size_t)start, avail);
if (full == NULL) {
PrintAndLogEx(WARNING, "failed to allocate memory");
free(sig);
return PM3_EMALLOC;
}
// anchor the ridge tracker on the symbol rate the full length analysis found,
// so drift is measured against the clock we care about rather than against whichever bin peaked in each frame
const double anchor = an.have_symbol_clk ? (1.0 / an.symbol_clk) : 0.0;
pm3_stft_t st;
res = pm3_stft(full, avail, n, stft_hop, win, anchor, &st);
free(full);
if (res != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "cannot build a spectrogram: need at least one full %zu sample window from offset %d", n, start);
PrintAndLogEx(HINT, "Hint: Try a smaller `" _YELLOW_("--size") "`");
} else {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, " frames........ " _YELLOW_("%zu") " of %zu samples, hop " _YELLOW_("%zu"), st.nframes, st.n, st.hop);
report_stft(&st, start);
pm3_stft_free(&st);
}
}
free(sig);
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int parse_mod_mask(const char *str, int *mask) {
*mask = 0;
if (str == NULL || str[0] == '\0') {
return PM3_SUCCESS;
}
if (strcmp(str, "ask") == 0) {
*mask = 1 << PM3_MOD_ASK;
} else if (strcmp(str, "fsk") == 0) {
*mask = 1 << PM3_MOD_FSK;
} else if (strcmp(str, "psk") == 0) {
*mask = 1 << PM3_MOD_PSK;
} else if (strcmp(str, "nrz") == 0) {
*mask = 1 << PM3_MOD_NRZ;
} else {
return PM3_EINVARG;
}
return PM3_SUCCESS;
}
static void print_fit_table(const pm3_fit_t *fit, size_t rows) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "rank mod encoding clk clk fine Q phase SNR_dd eye mid margin");
PrintAndLogEx(INFO, "-----+-----+------------+-----+----------+----+-------+---------+-------+------+--------");
for (size_t i = 0; i < rows; i++) {
const pm3_hyp_t *h = &fit->items[i];
char extra[24] = {0};
if (h->tpl == PM3_TPL_FSK) {
snprintf(extra, sizeof(extra), " fc %d/%d", h->fc_hi, h->fc_lo);
} else if (h->tpl == PM3_TPL_PSK) {
snprintf(extra, sizeof(extra), " fc %d", h->fc);
}
PrintAndLogEx(INFO, "%4zu | %-3s | %-10s | %3d | %8.2f | %2d | %5d | %7.2f | %5.3f | %4.2f | %6.2f%s"
, i + 1
, pm3_mod_name(h->mod)
, pm3_enc_name(h->enc)
, h->clk
, h->clk_fine
, h->q
, h->phase
, h->snr_dd
, h->eye
, h->mid_ratio
, h->margin
, extra
);
}
}
static int CmdFitScore(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data fitscore",
"Rank matched filter hypotheses against the GraphBuffer.\n"
"A hypothesis is a modulation, a symbol shape and a clock. Each one is scored by\n"
"correlating the trace against a single symbol template and measuring how tightly\n"
"the correlator output clusters at the decision instants. Phase is not searched:\n"
"the correlation is done with an FFT, which yields every phase offset at once.\n"
"Templates are shaped by an assumed antenna Q, because correlating a bandlimited\n"
"signal against square edges biases the ranking toward short clocks.",
"data fitscore --> rank the whole bank\n"
"data fitscore --mod ask --top 5 --> ASK hypotheses only\n"
"data fitscore --clk 64 --all --> everything at clock 64\n"
"data fitscore --verbose --> also put the rank 1 correlator output in the graph\n"
);
void *argtable[] = {
arg_param_begin,
arg_int0(NULL, "start", "<int>", "first sample of the GraphBuffer to use (def 0)"),
arg_int0(NULL, "size", "<int>", "samples to analyse, rounded up to a power of two (def 16384)"),
arg_str0(NULL, "mod", "<ask|fsk|psk|nrz>", "restrict to one modulation (def all)"),
arg_int0(NULL, "clk", "<int>", "restrict to one clock (def all candidates)"),
arg_int0(NULL, "top", "<int>", "rows to print (def 10)"),
arg_lit0(NULL, "all", "print every scored hypothesis"),
arg_lit0("v", "verbose", "write the rank 1 correlator output into the GraphBuffer"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int start = arg_get_int_def(ctx, 1, 0);
int size = arg_get_int_def(ctx, 2, 0);
char mod_str[8] = {0};
int mod_len = sizeof(mod_str) - 1;
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)mod_str, mod_len, &mod_len);
int clk_only = arg_get_int_def(ctx, 4, 0);
int top = arg_get_int_def(ctx, 5, 10);
bool show_all = arg_get_lit(ctx, 6);
bool verbose = arg_get_lit(ctx, 7);
CLIParserFree(ctx);
pm3_fit_opts_t opts = {0};
if (parse_mod_mask(mod_str, &opts.mod_mask) != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "unknown modulation `%s`, expected ask, fsk, psk or nrz", mod_str);
return PM3_EINVARG;
}
if (clk_only < 0) {
PrintAndLogEx(WARNING, "clk must not be negative");
return PM3_EINVARG;
}
if (clk_only > 0) {
const int *clocks = NULL;
const size_t nclk = pm3_fit_clocks(&clocks);
bool known = false;
for (size_t i = 0; i < nclk; i++) {
if (clocks[i] == clk_only) {
known = true;
break;
}
}
if (known == false) {
PrintAndLogEx(WARNING, "clock " _YELLOW_("%d") " is not one of the candidates the detectors search", clk_only);
PrintAndLogEx(INFO, "candidates are:");
char buf[128] = {0};
for (size_t i = 0; i < nclk; i++) {
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%s%d", i ? ", " : " ", clocks[i]);
}
PrintAndLogEx(INFO, "%s", buf);
return PM3_EINVARG;
}
}
opts.clk_only = clk_only;
opts.keep_corr = verbose;
if (top < 1) {
PrintAndLogEx(WARNING, "top must be at least 1");
return PM3_EINVARG;
}
if (size == 0) {
const size_t avail = (g_GraphTraceLen > (size_t)MAX(start, 0)) ? g_GraphTraceLen - (size_t)MAX(start, 0) : 0;
size = autodemod_window(start, avail);
if (size <= 0) {
size = 0;
}
}
double *sig = NULL;
size_t count = 0, n = 0;
int res = dsp_prepare(start, size, &sig, &count, &n);
if (res != PM3_SUCCESS) {
return res;
}
pm3_fit_t fit;
res = pm3_fit_run(sig, count, &opts, &fit);
free(sig);
if (res == PM3_ESOFT) {
PrintAndLogEx(WARNING, "no hypothesis had enough symbols to score");
PrintAndLogEx(INFO, "a hypothesis needs at least %d decision instants, so a clock of C needs %d*C samples"
, PM3_FIT_MIN_SYMBOLS
, PM3_FIT_MIN_SYMBOLS
);
return res;
}
if (res != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "failed to score the hypothesis bank");
return res;
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Matched filter bank") " -------------------------------");
PrintAndLogEx(INFO, " samples....... " _YELLOW_("%zu") " from offset " _YELLOW_("%d"), count, start);
PrintAndLogEx(INFO, " hypotheses.... " _YELLOW_("%zu") " scored", fit.count);
if (fit.stats.valid) {
PrintAndLogEx(INFO, " short runs.... " _YELLOW_("%.3f") " of runs are under 2.5 chips", fit.stats.short_fraction);
PrintAndLogEx(INFO, " chip.......... " _YELLOW_("%.1f") " samples, longest run " _YELLOW_("%.1f") " ( ratio %.2f, %s )"
, fit.stats.chip
, fit.stats.long_run
, fit.stats.run_ratio
, fit.stats.transition_coded ? "transition coded" : "raw levels"
);
if (fit.stats.period > 0.0) {
PrintAndLogEx(INFO, " repeats every. " _YELLOW_("%.1f") " samples ( autocorrelation %.2f )"
, fit.stats.period
, fit.stats.period_strength
);
} else {
PrintAndLogEx(INFO, " repeats every. " _YELLOW_("no clear period"));
}
}
size_t rows = show_all ? fit.count : MIN((size_t)top, fit.count);
print_fit_table(&fit, rows);
if (rows < fit.count) {
PrintAndLogEx(INFO, "( %zu of %zu rows shown, use `--all` for everything )", rows, fit.count);
}
const pm3_hyp_t *best = &fit.items[0];
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "best fit... " _GREEN_("%s / %s") " at clock " _GREEN_("%.2f") ", phase %d, Q %d"
, pm3_mod_name(best->mod)
, pm3_enc_name(best->enc)
, best->clk_fine
, best->phase
, best->q
);
PrintAndLogEx(INFO, " SNR_dd........ " _YELLOW_("%.2f") " dB over %zu decision instants", best->snr_dd, best->nsym);
PrintAndLogEx(INFO, " eye opening... " _YELLOW_("%.3f") " ( min / mean at the decision instants )", best->eye);
PrintAndLogEx(INFO, " mid symbol.... " _YELLOW_("%.2f") " ( relative to the decision instants )", best->mid_ratio);
if (best->margin >= 6.0) {
PrintAndLogEx(SUCCESS, " confidence.... " _GREEN_("%.2f") " dB ahead of the next different decode", best->margin);
} else if (best->margin >= 3.0) {
PrintAndLogEx(INFO, " confidence.... " _YELLOW_("%.2f") " dB ahead of the next different decode", best->margin);
} else {
PrintAndLogEx(WARNING, " confidence.... " _RED_("%.2f") " dB ahead of the next different decode, this is ambiguous", best->margin);
}
PrintAndLogEx(INFO, "");
if (verbose && fit.corr != NULL) {
size_t len = MIN(fit.corr_len, (size_t)MAX_GRAPH_TRACE_LEN);
double peak = 0.0;
for (size_t i = 0; i < len; i++) {
if (fit.corr[i] > peak) {
peak = fit.corr[i];
}
}
if (peak > 0.0) {
for (size_t i = 0; i < len; i++) {
g_GraphBuffer[i] = (int32_t)((fit.corr[i] / peak) * 127.0);
}
g_GraphTraceLen = len;
setClockGrid(best->clk, best->phase);
RepaintGraphWindow();
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(WARNING, "GraphBuffer now holds the " _YELLOW_("rank 1 correlator output") ", not the original samples");
PrintAndLogEx(INFO, "the grid is set to the winning clock and phase");
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("data plot") "`");
}
}
pm3_fit_free(&fit);
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
// Developer helper.
// Fills the GraphBuffer with a synthetic waveform whose modulation, encoding and clock are known
static int CmdGenSignal(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data gensignal",
"Developer tool. Fill the GraphBuffer with a synthetic LF waveform.\n"
"Used with `data fitscore` and `data autodemod`",
"data gensignal --mod ask --enc manchester --clk 64\n"
"data gensignal --mod ask --enc manchester --clk 64.25 --noise 0.4 --len 20000\n"
);
void *argtable[] = {
arg_param_begin,
arg_str0(NULL, "mod", "<ask|fsk|psk|nrz>", "modulation (def ask)"),
arg_str0(NULL, "enc", "<raw|manchester|biphase>", "encoding (def raw)"),
arg_dbl0(NULL, "clk", "<dbl>", "clock in samples/symbol, may be fractional (def 64)"),
arg_int0(NULL, "len", "<int>", "samples to generate (def 16384)"),
arg_dbl0(NULL, "noise", "<dbl>", "gaussian noise standard deviation (def 0)"),
arg_dbl0(NULL, "drift", "<dbl>", "linear DC ramp across the capture (def 0)"),
arg_dbl0(NULL, "env", "<dbl>", "amplitude envelope swing (def 0)"),
arg_dbl0(NULL, "jitter", "<dbl>", "per symbol edge jitter, samples (def 0)"),
arg_dbl0(NULL, "clip", "<dbl>", "hard clip level, 0 for none (def 0)"),
arg_int0(NULL, "fc", "<int>", "PSK subcarrier period (def 4)"),
arg_int0(NULL, "fchigh", "<int>", "FSK long field clock (def 10)"),
arg_int0(NULL, "fclow", "<int>", "FSK short field clock (def 8)"),
arg_int0(NULL, "q", "<int>", "antenna Q to shape the waveform with (def 8)"),
arg_int0(NULL, "repeat", "<int>", "loop a message of this many bits, like a real tag (def 0, random)"),
arg_int0(NULL, "seed", "<int>", "RNG seed, for reproducible runs (def 1)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
char mod_str[8] = {0};
int mod_len = sizeof(mod_str) - 1;
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)mod_str, mod_len, &mod_len);
char enc_str[16] = {0};
int enc_len = sizeof(enc_str) - 1;
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)enc_str, enc_len, &enc_len);
pm3_gen_opts_t opts = {0};
opts.mod = PM3_MOD_ASK;
opts.enc = PM3_ENC_RAW;
opts.clk = arg_get_dbl_def(ctx, 3, 64.0);
int len = arg_get_int_def(ctx, 4, 16384);
opts.noise = arg_get_dbl_def(ctx, 5, 0.0);
opts.drift = arg_get_dbl_def(ctx, 6, 0.0);
opts.envelope = arg_get_dbl_def(ctx, 7, 0.0);
opts.jitter = arg_get_dbl_def(ctx, 8, 0.0);
opts.clip = arg_get_dbl_def(ctx, 9, 0.0);
opts.fc = arg_get_int_def(ctx, 10, 4);
opts.fc_hi = arg_get_int_def(ctx, 11, 10);
opts.fc_lo = arg_get_int_def(ctx, 12, 8);
opts.q = arg_get_int_def(ctx, 13, 8);
opts.repeat = arg_get_int_def(ctx, 14, 0);
opts.seed = (uint32_t)arg_get_int_def(ctx, 15, 1);
CLIParserFree(ctx);
if (mod_len > 0) {
if (strcmp(mod_str, "ask") == 0) {
opts.mod = PM3_MOD_ASK;
} else if (strcmp(mod_str, "fsk") == 0) {
opts.mod = PM3_MOD_FSK;
} else if (strcmp(mod_str, "psk") == 0) {
opts.mod = PM3_MOD_PSK;
} else if (strcmp(mod_str, "nrz") == 0) {
opts.mod = PM3_MOD_NRZ;
} else {
PrintAndLogEx(WARNING, "unknown modulation `%s`", mod_str);
return PM3_EINVARG;
}
}
if (enc_len > 0) {
if (strcmp(enc_str, "raw") == 0) {
opts.enc = PM3_ENC_RAW;
} else if (strcmp(enc_str, "manchester") == 0) {
opts.enc = PM3_ENC_MANCHESTER;
} else if (strcmp(enc_str, "biphase") == 0) {
opts.enc = PM3_ENC_BIPHASE;
} else {
PrintAndLogEx(WARNING, "unknown encoding `%s`", enc_str);
return PM3_EINVARG;
}
}
if (len < 256 || len > MAX_GRAPH_TRACE_LEN) {
PrintAndLogEx(WARNING, "len must be between 256 and %d", MAX_GRAPH_TRACE_LEN);
return PM3_EINVARG;
}
if (opts.clk < 4.0 || opts.clk > 1024.0) {
PrintAndLogEx(WARNING, "clk must be between 4 and 1024");
return PM3_EINVARG;
}
double *buf = calloc((size_t)len, sizeof(double));
if (buf == NULL) {
PrintAndLogEx(WARNING, "failed to allocate memory");
return PM3_EMALLOC;
}
int res = pm3_fit_generate(&opts, buf, (size_t)len);
if (res != PM3_SUCCESS) {
free(buf);
PrintAndLogEx(WARNING, "failed to generate the signal");
return res;
}
double peak = 0.0;
for (int i = 0; i < len; i++) {
if (fabs(buf[i]) > peak) {
peak = fabs(buf[i]);
}
}
if (peak <= 0.0) {
peak = 1.0;
}
for (int i = 0; i < len; i++) {
g_GraphBuffer[i] = (int32_t)((buf[i] / peak) * 100.0);
}
g_GraphTraceLen = (size_t)len;
free(buf);
setClockGrid(0, 0);
RepaintGraphWindow();
PrintAndLogEx(SUCCESS, "generated " _GREEN_("%d") " samples, %s / %s at clock " _GREEN_("%.2f")
, len
, pm3_mod_name(opts.mod)
, pm3_enc_name(opts.enc)
, opts.clk
);
return PM3_SUCCESS;
}
// Run whichever of the existing demodulators the hypothesis names. This
// command is a parameter suggester, not a decoder - the bit producing path
// stays exactly the one `data rawdemod` uses, so anything it outputs is as
// auditable as before.
static int autodemod_dispatch(const pm3_hyp_t *hyp, int clk, bool invert, bool amp, bool verbose) {
const int max_err = 100;
switch (hyp->mod) {
case PM3_MOD_ASK:
if (hyp->enc == PM3_ENC_BIPHASE) {
return ASKbiphaseDemod(0, clk, invert ? 1 : 0, max_err, verbose);
}
if (hyp->enc == PM3_ENC_MANCHESTER) {
return ASKDemod(clk, invert ? 1 : 0, max_err, 0, amp, verbose, false, 1);
}
// askdemod_ext() halves the clock again for askType 0, so the raw
// path wants the half symbol rate, not the bit rate. See the note
// on detectclock_fit().
return ASKDemod(clk * 2, invert ? 1 : 0, max_err, 0, amp, verbose, false, 0);
case PM3_MOD_FSK:
return FSKrawDemod((uint8_t)clk, invert ? 1 : 0, (uint8_t)hyp->fc_hi, (uint8_t)hyp->fc_lo, verbose);
case PM3_MOD_PSK:
return PSKDemod(clk, invert ? 1 : 0, max_err, verbose);
case PM3_MOD_NRZ:
return NRZrawDemod(clk, invert ? 1 : 0, max_err, verbose);
}
return PM3_ESOFT;
}
// Count the chips the demodulator could not call.
static size_t autodemod_markers(void) {
size_t markers = 0;
for (size_t i = 0; i < g_DemodBufferLen; i++) {
if (g_DemodBuffer[i] > 1) {
markers++;
}
}
return markers;
}
// Second opinion on an ASK/raw decode, taken at the chip centres.
//
// The shared demodulator recovers its phase from edge positions, which is the
// only option while the clock is unknown. It does mean any asymmetry in the
// waveform lands directly on that phase: an LF tank rings down slower than it
// charges, so on a COTAG capture every falling edge arrives 10 samples late
// and every rising edge 10 samples early against a chip of 384. The chip
// centres are the furthest points from either edge, so once the clock is
// settled they are the better place to decide.
//
// Only ASK/raw, because only there does the demod buffer hold chips at the
// hypothesis clock. The Manchester and biphase paths pair and decode inside
// the shared demodulator, and raw chips are not interchangeable with that.
static bool autodemod_reslice(const pm3_hyp_t *hyp, size_t before) {
if (hyp->mod != PM3_MOD_ASK || hyp->enc != PM3_ENC_RAW || before == 0) {
return false;
}
double *sig = pm3_extract(g_GraphBuffer, g_GraphTraceLen, 0, g_GraphTraceLen);
if (sig == NULL) {
return false;
}
pm3_slice_t sl;
const int res = pm3_ask_slice(sig, g_GraphTraceLen, hyp->clk_fine, &sl);
free(sig);
if (res != PM3_SUCCESS) {
return false;
}
// Keep whichever demodulation had to guess less often. Both ran at the
// same clock, so the bit counts are comparable and a lower marker count
// cannot be bought by decoding fewer chips.
if (sl.nbits == 0 || sl.nerrors >= before) {
pm3_slice_free(&sl);
return false;
}
const size_t n = MIN(sl.nbits, (size_t)MAX_DEMOD_BUF_LEN);
setDemodBuff(sl.bits, n, 0);
setClockGrid((uint32_t)(hyp->clk_fine + 0.5), (int)sl.phase);
PrintAndLogEx(INFO, " resliced...... at the chip centres, %zu demod errors instead of %zu ( eye %.3f )"
, sl.nerrors
, before
, sl.eye
);
pm3_slice_free(&sl);
return true;
}
// The equivalent hand typed command, so the tool teaches rather than replaces
// and the result can go in a script.
static void autodemod_hint(const pm3_hyp_t *hyp, int clk, bool invert, bool amp) {
const char *flag = "--ar";
switch (hyp->mod) {
case PM3_MOD_ASK:
flag = (hyp->enc == PM3_ENC_BIPHASE) ? "--ab"
: (hyp->enc == PM3_ENC_MANCHESTER) ? "--am" : "--ar";
break;
case PM3_MOD_FSK:
flag = "--fs";
break;
case PM3_MOD_PSK:
flag = "--p1";
break;
case PM3_MOD_NRZ:
flag = "--nr";
break;
}
const int shown = (hyp->mod == PM3_MOD_ASK && hyp->enc == PM3_ENC_RAW) ? (clk * 2) : clk;
PrintAndLogEx(HINT, "Hint: the same thing by hand is `" _YELLOW_("data rawdemod %s -c %d%s%s") "`"
, flag
, shown
, invert ? " -i" : ""
, amp ? " -a" : ""
);
}
// How clean the demodulation came out. This is about the demodulation, not
// about what the bits mean - putting a format to them is `lf search`'s job and
// this command deliberately stops short of it.
static void autodemod_quality(void) {
if (g_DemodBufferLen == 0) {
return;
}
size_t markers = 0;
for (size_t i = 0; i < g_DemodBufferLen; i++) {
if (g_DemodBuffer[i] > 1) {
markers++;
}
}
if (markers) {
PrintAndLogEx(WARNING, " quality....... %zu of %zu bits are demod errors", markers, g_DemodBufferLen);
} else {
PrintAndLogEx(SUCCESS, " quality....... no demod errors in %zu bits", g_DemodBufferLen);
}
}
static int CmdAutoDemod(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "data autodemod",
"Tries to work out the modulation, encoding and clock of the wave in the GraphBuffer then run the matching demodulator\n"
"Adds no signal processing of its own, the analysis is `data spectrum` and `data fitscore`\n"
"A fractional clock is resampled onto an integer grid first",
"data autodemod --> analyse and demodulate\n"
"data autodemod --dry-run --> decide and print, demodulate nothing\n"
"data autodemod --thres 6 --> insist on a 6 dB margin before trusting rank 1\n"
"data autodemod --invert --amp --> pass invert and amplify through to the demod\n"
);
void *argtable[] = {
arg_param_begin,
arg_int0(NULL, "start", "<int>", "first sample of the GraphBuffer to use (def 0)"),
arg_int0(NULL, "size", "<int>", "samples to analyse (def 16384)"),
arg_dbl0(NULL, "thres", "<dB>", "margin below which rank 1 is called ambiguous (def 3.0)"),
arg_lit0(NULL, "dry-run", "analyse and decide, but do not demodulate"),
arg_lit0("a", "amp", "amplify the signal before ASK demodulation"),
arg_lit0("i", "invert", "invert the demodulated output"),
arg_lit0("v", "verbose", "show the demodulator's own output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
int start = arg_get_int_def(ctx, 1, 0);
int size = arg_get_int_def(ctx, 2, 0);
double thres = arg_get_dbl_def(ctx, 3, 3.0);
bool dry_run = arg_get_lit(ctx, 4);
bool amp = arg_get_lit(ctx, 5);
bool invert = arg_get_lit(ctx, 6);
bool verbose = arg_get_lit(ctx, 7);
CLIParserFree(ctx);
if (size == 0) {
const size_t avail = (g_GraphTraceLen > (size_t)MAX(start, 0)) ? g_GraphTraceLen - (size_t)MAX(start, 0) : 0;
size = autodemod_window(start, avail);
}
double *sig = NULL;
size_t count = 0, n = 0;
int res = dsp_prepare(start, size, &sig, &count, &n);
if (res != PM3_SUCCESS) {
return res;
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("Auto demodulate") " -----------------------------------");
// the graph buffer is put back the way we found it unless we get bits out
int32_t *backup = NULL;
size_t backup_len = g_GraphTraceLen;
// 1 - spectral analysis, to prune the bank. Zeroed up front because the
// steps below read it whether or not the analysis had anything to say.
pm3_spec_analysis_t an = {0};
int mod_mask = 0;
if (pm3_analyse(sig, count, n, PM3_WIN_HANN, &an) == PM3_SUCCESS) {
PrintAndLogEx(INFO, " spectrum...... %s, confidence %s"
, pm3_family_name(an.family)
, pm3_confidence_name(an.confidence)
);
// only prune on a hint worth trusting, otherwise search everything
if (an.confidence >= PM3_CONF_MEDIUM) {
switch (an.family) {
case PM3_FAM_FSK:
mod_mask = 1 << PM3_MOD_FSK;
break;
case PM3_FAM_PSK:
mod_mask = 1 << PM3_MOD_PSK;
break;
case PM3_FAM_ASK:
case PM3_FAM_MANCHESTER:
mod_mask = (1 << PM3_MOD_ASK) | (1 << PM3_MOD_NRZ);
break;
case PM3_FAM_NRZ:
mod_mask = (1 << PM3_MOD_NRZ) | (1 << PM3_MOD_ASK);
break;
case PM3_FAM_UNKNOWN:
break;
}
}
}
// 1b - a switched carrier has to be rectified before anything downstream
// will work. The demodulators threshold the samples as they stand, and a
// capture with two samples per carrier period alternates sign on every
// one of them, so every symbol averages to zero and no demodulator can
// ever produce a bit. Replace the buffer with its envelope and carry on.
if (an.needs_envelope) {
PrintAndLogEx(INFO, " carrier....... rf/%.2f switched on and off ( envelope depth %.2f )"
, an.carrier_clk
, an.envelope_depth
);
double *whole = pm3_extract(g_GraphBuffer, g_GraphTraceLen, 0, g_GraphTraceLen);
double *env = (whole != NULL) ? pm3_envelope(whole, g_GraphTraceLen, 0) : NULL;
free(whole);
if (env != NULL) {
autodemod_backup(&backup, backup_len);
autodemod_store(env, g_GraphTraceLen);
free(env);
double *re = pm3_extract(g_GraphBuffer, g_GraphTraceLen, (size_t)start, count);
if (re != NULL) {
free(sig);
sig = re;
}
PrintAndLogEx(INFO, " rectified..... the data is in the envelope, demodulating that instead");
} else {
PrintAndLogEx(WARNING, " rectified..... failed to allocate, carrying on with the raw samples");
}
}
if (mod_mask) {
PrintAndLogEx(INFO, " bank.......... pruned to %s", pm3_family_name(an.family));
} else {
PrintAndLogEx(INFO, " bank.......... full, the spectral hint was not strong enough to prune on");
}
// 2 - rank the hypotheses
pm3_fit_opts_t opts = {0};
opts.mod_mask = mod_mask;
pm3_fit_t fit;
res = pm3_fit_run(sig, count, &opts, &fit);
// a pruned bank that finds nothing is worse than no pruning
if (res != PM3_SUCCESS && mod_mask != 0) {
PrintAndLogEx(INFO, " bank.......... pruned search came up empty, retrying with everything");
opts.mod_mask = 0;
res = pm3_fit_run(sig, count, &opts, &fit);
}
free(sig);
if (res != PM3_SUCCESS) {
PrintAndLogEx(WARNING, "no hypothesis fit the signal");
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("data spectrum --sq") "` to see what is actually there");
return res;
}
const pm3_hyp_t *best = &fit.items[0];
PrintAndLogEx(SUCCESS, " decision...... " _GREEN_("%s / %s") " at clock " _GREEN_("%.2f")
, pm3_mod_name(best->mod)
, pm3_enc_name(best->enc)
, best->clk_fine
);
PrintAndLogEx(INFO, " margin........ %.2f dB, SNR %.2f dB, eye %.3f", best->margin, best->snr_dd, best->eye);
// 3 - an ambiguous choice is reported, not refused.
//
// A structural promotion is not a score race, so a negative margin there
// is expected and says nothing about confidence - the run lengths settled
// it, and they are not a matter of degree.
if (fit.promoted) {
PrintAndLogEx(INFO, " chosen on..... run length evidence, not on score");
} else if (best->margin < thres) {
PrintAndLogEx(WARNING, "margin %.2f dB is below the %.2f dB threshold, this choice is ambiguous"
, best->margin
, thres
);
const size_t show = MIN((size_t)3, fit.count);
for (size_t i = 0; i < show; i++) {
PrintAndLogEx(INFO, " %zu. %s / %s at clock %.2f ( SNR %.2f dB )"
, i + 1
, pm3_mod_name(fit.items[i].mod)
, pm3_enc_name(fit.items[i].enc)
, fit.items[i].clk_fine
, fit.items[i].snr_dd
);
}
}
if (dry_run) {
autodemod_hint(best, (int)(best->clk_fine + 0.5), invert, amp);
PrintAndLogEx(INFO, "dry run, nothing was demodulated and the DemodBuffer is untouched");
pm3_fit_free(&fit);
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
// 4 - put a fractional clock onto an integer grid before dispatching.
//
// This is the whole point of the exercise. A clock of 64.19 walks a full
// symbol out of step after 340 symbols, so the demodulator gets the start
// of the capture right and turns the rest to mush - which presents to the
// user as a noisy tag rather than as a wrong clock.
const double frac = fabs(best->clk_fine - floor(best->clk_fine + 0.5));
if (frac > 0.05) {
const double target = floor(best->clk_fine + 0.5);
const double ratio = target / best->clk_fine;
double *whole = pm3_extract(g_GraphBuffer, g_GraphTraceLen, 0, g_GraphTraceLen);
if (whole != NULL) {
size_t out_len = 0;
double *rs = pm3_resample(whole, g_GraphTraceLen, ratio, &out_len);
free(whole);
if (rs != NULL) {
const size_t was = g_GraphTraceLen;
autodemod_backup(&backup, g_GraphTraceLen);
autodemod_store(rs, out_len);
free(rs);
PrintAndLogEx(INFO, " resampled..... clock %.2f -> %.0f, buffer %zu -> %zu samples"
, best->clk_fine
, target
, was
, out_len
);
PrintAndLogEx(INFO, " a fractional clock drifts out of step across a long capture, this fixes that");
}
}
}
// 5..7 - dispatch, and fall through to the next ranked hypothesis on failure
const size_t tries = MIN((size_t)3, fit.count);
bool ok = false;
for (size_t i = 0; i < tries; i++) {
const pm3_hyp_t *h = &fit.items[i];
const int clk = (int)(h->clk_fine + 0.5);
PrintAndLogEx(INFO, " attempt %zu..... %s / %s at clock %d", i + 1, pm3_mod_name(h->mod), pm3_enc_name(h->enc), clk);
if (autodemod_dispatch(h, clk, invert, amp, verbose) == PM3_SUCCESS && g_DemodBufferLen > 0) {
autodemod_reslice(h, autodemod_markers());
PrintAndLogEx(SUCCESS, " demodulated... " _GREEN_("%zu") " bits into the DemodBuffer", g_DemodBufferLen);
autodemod_quality();
autodemod_hint(h, clk, invert, amp);
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("lf search -1") "` to identify what the bits are");
ok = true;
break;
}
PrintAndLogEx(INFO, " no bits, moving on");
}
if (ok == false) {
PrintAndLogEx(WARNING, "none of the top %zu hypotheses produced bits", tries);
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("data fitscore --all") "` to see the whole ranking");
// leave the user's buffer as we found it if we changed it for nothing
if (backup != NULL) {
memcpy(g_GraphBuffer, backup, backup_len * sizeof(int32_t));
g_GraphTraceLen = backup_len;
PrintAndLogEx(INFO, "the graph buffer has been put back the way it was");
}
}
free(backup);
pm3_fit_free(&fit);
PrintAndLogEx(NORMAL, "");
return ok ? PM3_SUCCESS : PM3_ESOFT;
}
static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"-----------", CmdHelp, AlwaysAvailable, "------------------------------ " _CYAN_("General") " -------------------------------"},
{"clear", CmdBuffClear, AlwaysAvailable, "Clears various buffers used by the graph window"},
{"hide", CmdHide, AlwaysAvailable, "Hide the graph window"},
{"load", CmdLoad, AlwaysAvailable, "Load contents of file into graph window"},
{"num", CmdNumCon, AlwaysAvailable, "Converts dec/hex/bin"},
{"plot", CmdPlot, AlwaysAvailable, "Show the graph window"},
{"print", CmdPrintDemodBuff, AlwaysAvailable, "Print the data in the DemodBuffer"},
{"save", CmdSave, AlwaysAvailable, "Save signal trace data"},
{"setdebugmode", CmdSetDebugMode, AlwaysAvailable, "Set Debugging Level on client side"},
{"xor", CmdXor, AlwaysAvailable, "Xor a input string"},
{"-----------", CmdHelp, AlwaysAvailable, "----------------------------- " _CYAN_("Modulation") " -----------------------------"},
{"biphaserawdecode", CmdBiphaseDecodeRaw, AlwaysAvailable, "Biphase decode bin stream in DemodBuffer"},
{"detectclock", CmdDetectClockRate, AlwaysAvailable, "Detect ASK, FSK, NRZ, PSK clock rate of wave in GraphBuffer"},
{"fsktonrz", CmdFSKToNRZ, AlwaysAvailable, "Convert fsk2 to nrz wave for alternate fsk demodulating (for weak fsk)"},
{"manrawdecode", Cmdmandecoderaw, AlwaysAvailable, "Manchester decode binary stream in DemodBuffer"},
{"modulation", CmdDataModulationSearch, AlwaysAvailable, "Identify LF signal for clock and modulation"},
{"rawdemod", CmdRawDemod, AlwaysAvailable, "Demodulate the data in the GraphBuffer and output binary"},
{"-----------", CmdHelp, AlwaysAvailable, "-------------------------- " _CYAN_("Frequency domain") " --------------------------"},
{"autodemod", CmdAutoDemod, AlwaysAvailable, "Detect modulation, encoding and clock, then demodulate"},
{"fft", CmdFFT, AlwaysAvailable, "Fourier transform of the GraphBuffer"},
{"fitscore", CmdFitScore, AlwaysAvailable, "Rank matched filter hypotheses for modulation, encoding and clock"},
{"spectrum", CmdSpectrum, AlwaysAvailable, "Spectral peaks, symbol rate and modulation family hint"},
{"-----------", CmdHelp, AlwaysAvailable, "------------------------------- " _CYAN_("Graph") " --------------------------------"},
{"askedgedetect", CmdAskEdgeDetect, AlwaysAvailable, "Adjust Graph for manual ASK demod"},
{"autocorr", CmdAutoCorr, AlwaysAvailable, "Autocorrelation over window"},
{"convertbitstream", CmdConvertBitStream, AlwaysAvailable, "Convert GraphBuffer's 0/1 values to 127 / -127"},
{"cthreshold", CmdCenterThreshold, AlwaysAvailable, "Average out all values between"},
{"dirthreshold", CmdDirectionalThreshold, AlwaysAvailable, "Max rising higher up-thres/ Min falling lower down-thres"},
{"decimate", CmdDecimate, AlwaysAvailable, "Decimate samples"},
{"envelope", CmdEnvelope, AlwaysAvailable, "Generate square envelope of samples"},
{"grid", CmdGrid, AlwaysAvailable, "overlay grid on graph window"},
{"getbitstream", CmdGetBitStream, AlwaysAvailable, "Convert GraphBuffer's >=1 values to 1 and <1 to 0"},
{"hpf", CmdHpf, AlwaysAvailable, "Remove DC offset from trace"},
{"iir", CmdDataIIR, AlwaysAvailable, "Apply IIR buttersworth filter on plot data"},
{"ltrim", CmdLtrim, AlwaysAvailable, "Trim samples from left of trace"},
{"mtrim", CmdMtrim, AlwaysAvailable, "Trim out samples from the specified start to the specified stop"},
{"norm", CmdNorm, AlwaysAvailable, "Normalize max/min to +/-128"},
{"rtrim", CmdRtrim, AlwaysAvailable, "Trim samples from right of trace"},
{"setgraphmarkers", CmdSetGraphMarkers, AlwaysAvailable, "Set the markers in the graph window"},
{"shiftgraphzero", CmdGraphShiftZero, AlwaysAvailable, "Shift 0 for Graphed wave + or - shift value"},
{"timescale", CmdTimeScale, AlwaysAvailable, "Set cursor display timescale"},
{"undecimate", CmdUndecimate, AlwaysAvailable, "Un-decimate samples"},
{"zerocrossings", CmdZerocrossings, AlwaysAvailable, "Count time between zero-crossings"},
{"-----------", CmdHelp, AlwaysAvailable, "---------------------------- " _CYAN_("Operations") " ------------------------------"},
{"asn1", CmdAsn1Decoder, AlwaysAvailable, "ASN1 decoder"},
{"atr", CmdAtrLookup, AlwaysAvailable, "ATR lookup"},
{"bitsamples", CmdBitsamples, IfPm3Present, "Get raw samples as bitstring"},
{"bmap", CmdBinaryMap, AlwaysAvailable, "Convert hex value according a binary template"},
{"crypto", CmdCryptography, AlwaysAvailable, "Encrypt and decrypt data"},
{"diff", CmdDiff, AlwaysAvailable, "Diff of input files"},
{"hexsamples", CmdHexsamples, IfPm3Present, "Dump big buffer as hex bytes"},
{"samples", CmdSamples, IfPm3Present, "Get raw samples for graph window ( GraphBuffer )"},
{"qrcode", CmdQRcode, AlwaysAvailable, "Create a QR code"},
{"-----------", CmdHelp, IfClientDebugEnabled, "------------------------------- " _CYAN_("Debug") " --------------------------------"},
{"gensignal", CmdGenSignal, IfClientDebugEnabled, "Generate a synthetic LF waveform into the GraphBuffer"},
{"test_ss8", CmdTestSaveState8, IfClientDebugEnabled, "Test the implementation of Buffer Save States (8-bit buffer)"},
{"test_ss32", CmdTestSaveState32, IfClientDebugEnabled, "Test the implementation of Buffer Save States (32-bit buffer)"},
{"test_ss32s", CmdTestSaveState32S, IfClientDebugEnabled, "Test the implementation of Buffer Save States (32-bit signed buffer)"},
{NULL, NULL, NULL, NULL}
};
static int CmdHelp(const char *Cmd) {
(void)Cmd; // Cmd is not used so far
CmdsHelp(CommandTable);
return PM3_SUCCESS;
}
int CmdData(const char *Cmd) {
clearCommandBuffer();
return CmdsParse(CommandTable, Cmd);
}