From e657721c270833df7a24c67e21c3369fedbdd26d Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Mon, 31 Jan 2022 14:09:22 +0100 Subject: [PATCH 01/31] Docker: Add configs and script to access the rfcat device using docker Docker: Now copy files from folder instead of cloning repo --- Dockerfile | 11 +++++++++++ docker-compose.yml | 6 ++++++ run_rfcat_docker.sh | 1 + 3 files changed, 18 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 run_rfcat_docker.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..aede72f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.9.10 + +WORKDIR /pandwarf + +COPY . ./rfcat + +RUN apt update && apt install -y usbutils + +RUN cd rfcat && python setup.py install + +CMD [ "bash" ] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..88c260b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,6 @@ +version: "3.3" + +services: + rfcat_image: + build: . + privileged: true diff --git a/run_rfcat_docker.sh b/run_rfcat_docker.sh new file mode 100644 index 0000000..398b9b0 --- /dev/null +++ b/run_rfcat_docker.sh @@ -0,0 +1 @@ +docker-compose run rfcat_image bash \ No newline at end of file From 8d90b2118e3aea4f73c0247dc78fe465d2a97e3d Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Mon, 31 Jan 2022 15:08:08 +0100 Subject: [PATCH 02/31] Docker: Now unpluging and replugging usb does not break access to the dongle --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 88c260b..6c77dcd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,4 +3,7 @@ version: "3.3" services: rfcat_image: build: . + volumes: + - /dev/bus/usb:/dev/bus/usb privileged: true + From ad43e4f6d519cfd458c2c6d7428dc5f9006901be Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Mon, 7 Feb 2022 11:33:47 +0100 Subject: [PATCH 03/31] RfCat: Implement a first version of PandwaRF and PandwaRF Rogue --- rflib/chipcon_gollum.py | 736 ++++++++++++++++++++++++++++++++++++++++ rflib/const_gollum.py | 246 ++++++++++++++ rflib/utils_gollum.py | 26 ++ 3 files changed, 1008 insertions(+) create mode 100644 rflib/chipcon_gollum.py create mode 100644 rflib/const_gollum.py create mode 100644 rflib/utils_gollum.py diff --git a/rflib/chipcon_gollum.py b/rflib/chipcon_gollum.py new file mode 100644 index 0000000..11612a4 --- /dev/null +++ b/rflib/chipcon_gollum.py @@ -0,0 +1,736 @@ +#!/usr/bin/env ipython +import struct +from typing import Optional +from . import RfCat + +from rflib.ccspecan import SPECAN_QUEUE + +from .utils_gollum import Tools + +from .chipcon_usb import ChipconUsbTimeoutException, keystop +from .chipcondefs import RFST_SIDLE +from .const import MOD_ASK_OOK, SYNCM_CARRIER, USB_RX_WAIT +from .const_gollum import * + +# PandwaRF/PandwaRF Rogue specific methods +class PandwaRF(RfCat): + + def __init__(self, idx=0, debug=False, copyDongle=None, RfMode=RFST_SIDLE, safemode=False): + super().__init__(idx, debug, copyDongle, RfMode, safemode) + self.endec = None + self._initPandwaRf() + + def recv(self, app, cmd=None, wait=USB_RX_WAIT): + # Override recv to replace some commands (e.g specan) + if app == APP_SPECAN: + if cmd == SPECAN_QUEUE: + cmd = CMD_SPECAN_QUEUE + + r, t = super().recv(app, cmd, wait) + return r, t + + # RECV / SEND COMMANDS (Low Level) + + # APP_SYSTEM_GOLLUM + + def _sendAppSystemGollum(self, cmd: int): + return self.send(APP_SYSTEM_GOLLUM, cmd, b'') + + def _sendGetFwVersion(self): + return self._sendAppSystemGollum(CMD_GET_FW_VERSION) + + def _sendGetSerialNumber(self): + return self._sendAppSystemGollum(CMD_DEVICE_SERIAL_NUMBER_GOLLUM) + + # APP_NIC + + def _sendAppNic(self, cmd: int, payload: bytes): + return self.send(APP_NIC, cmd, payload) + + def _recvAppNic(self, cmd: int, wait=USB_RX_WAIT): + return self.recv(APP_NIC, cmd, wait) + + def _sendEncodingMode(self, compressionMode: bool): + _data = struct.pack("B", compressionMode) + return self._sendAppNic(CMD_NIC_ENCODING_MODE, _data) + + def _sendRecvInfiniteMode(self, rxInfiniteMode: bool): + _data = struct.pack("B", rxInfiniteMode) + return self._sendAppNic(CMD_NIC_RECV_INFINITE_MODE, _data) + + def _recv(self): + return self._recvAppNic(CMD_NIC_RECV) + + def _recvRle(self): + return self._recvAppNic(CMD_NIC_RECV_RLE) + + def _sendRequestRemainingData(self): + return self._sendAppNic(CMD_NIC_ENCODING_MODE, b"") + + def _recvRemainingData(self): + return self._recvAppNic(CMD_NIC_RECV_REMAINING_DATA) + + def _recvRemainingDataRle(self): + return self._recvAppNic(CMD_NIC_RECV_REMAINING_DATA_RLE) + + def _sendXmitInfiniteMode(self, enqueueMode: bool, expectedBlockSize: int): + _data = struct.pack(" bytes: + r, t = self._sendGetFwVersion() + return r + + def _getSerialNumber(self) -> bytes: + r, t = self._sendGetSerialNumber() + return r + + def _initPandwaRf(self) -> None: + self.setAmpMode(RF_POWER_AMPLIFIERS_ACTION_ALL_OFF) + + + # Overloading functions + + def getPartNum(self) -> int: + return 0x11 + + def reprHardwareConfig(self): + output= [] + hardware = self.getBuildInfo() + output.append("Dongle: %s" % hardware.split(b' ')[0]) + + fwVersion = self._getFirmwareVersion() + output.append("Firmware rev: %s" % fwVersion) + + # see if we have a bootloader by loooking for it's recognition semaphores + # in SFR I2SCLKF0 & I2SCLKF1 + if(self.peek(0xDF46,1) == b'\xF0' and self.peek(0xDF47,1) == b'\x0D'): + output.append("Bootloader: CC-Bootloader") + elif(self.peek(0xDF46,1) == b'\xCC' and self.peek(0xDF47,1) == b'\x01'): + output.append("Bootloader: Gollum CCTL v1") + elif(self.peek(0xDF46,1) == b'\xCC' and self.peek(0xDF47,1) == b'\x02'): + output.append("Bootloader: GollumCCTL v2") + elif(b"CCtl" in hardware): + output.append("Bootloader: Gollum CCTL") + else: + output.append("Bootloader: Not installed") + return "\n".join(output) + + def reprRadioConfig(self) -> str: + output = [] + output.append(super().reprRadioConfig()) + + output.append("\n== PandwaRF Specific ==") + output.append(f"Amplification Mode: {self.reprAmpMode()}") + + return '\n'.join(output) + + + # User functions + + def setFreq(self, freq: int) -> None: + """ + Modify carrier's frequency. + + :param freq: New frequency + :return: + """ + if type(freq) is float: + freq = int(freq) # Because the specan calls setFreq with a float + self._sendRfSetFreq(freq) + + def setPower(self, power: int, pset: bool = True, invert: bool = False) -> None: + """ + Modify output power. + + :param power: New output power + :param pset: If false, OOK modulation is used, the logic 0 and logic 1 power levels shall be + programmed to index 0 and 1 respectively, i.e. PA_TABLE0 and PA_TABLE1. + :param invert: Invert parameter for power + :return: + """ + self._sendRfSetPower(invert, pset, power) + + def setMdmModulation(self, modulation: int, invert: bool = False) -> None: + """ + Modify modulation format. + + :param modulation: Modulation type + :param invert: Invert parameter for modulation + :return: + """ + self._sendRfSetModulation(invert, modulation) + + def setMdmDRate(self, datarate: int) -> None: + """ + Modify data rate. + + :param datarate: New data rate + :return: + """ + self._sendRfSetDatarate(datarate) + + def setMaxPower(self, invert: bool = False) -> None: + """ + Set power to max. + + :param invert: Invert parameter + :return: + """ + self._sendRfSetMaxPower(invert) + + def setRxFilterBw(self, bandwidth: int) -> None: + """ + Modify RX filter bandwidth. + + :param bandwidth: New RX filter bandwidth + :return: + """ + self._sendRfSetRxFilterBw(bandwidth) + + def makePktFLEN(self, packetLength: int) -> None: + """ + Modify packet length. + + :param packetLength: New packet length + :return: + """ + self._sendRfSetPacketFlen(packetLength) + + def setMdmSyncMode(self, syncMode: int) -> None: + """ + Modify sync mode. + + :param syncMode: New sync mode + :return: + """ + self._sendRfSetSyncmode(syncMode) + + + def setEnablePktCRC(self, crcMode: bool) -> None: + """ + Modify packet CRC state. + + :param crcMode: New packet CRC state + :return: + """ + self._sendRfSetPktCrc(crcMode) + + def setMdmChanSpc(self, channelSpacing: int) -> None: + """ + Modify channel spacing. + + :param channelSpacing: New channel spacing + :return: + """ + if type(channelSpacing) is float: + channelSpacing = int(channelSpacing) # Because the specan calls setFreq with a float + self._sendRfSetChanSpc(channelSpacing) + + def stopAllRf(self) -> None: + """ + Stop all RF activity by setting Mac state Idle directly. + """ + self._sendRfStopAll() + + def getAmpMode(self) -> bytes: + ''' + get the amplifier mode (RF amp external to CC1111) + ''' + r, t = self._sendRfGetTxRxPowerAmpMode() + return r + + def reprAmpMode(self) -> str: + ampMode = int.from_bytes(self.getAmpMode(), 'little') + if ampMode == RF_POWER_AMPLIFIERS_ACTION_ALL_OFF: + return "Amplifiers OFF" + elif ampMode == RF_TX_POWER_AMPLIFIER_ACTION_ON: + return "TX Amplifier only" + elif ampMode == RF_RX_POWER_AMPLIFIER_ACTION_ON: + return "RX Amplifier only" + elif ampMode == RF_TX_RX_POWER_AMPLIFIER_ACTION_ON: + return "TX & RX Amplifiers ON" + elif ampMode == RF_TX_POWER_AMPLIFIER_ACTION_ON_TX: + return "TX Amplifier only when transmitting" + elif ampMode == RF_RX_POWER_AMPLIFIER_ACTION_ON_RX: + return "RX Amplifier only when receiving" + elif ampMode == RF_TX_RX_POWER_AMPLIFIER_ACTION_ON_TX_RX: + return "TX & RX Amplifiers only when transmitting & receiving" + elif ampMode == RF_ANT_POWER_ENABLE: + return "Antenna power enabled" + elif ampMode == RF_ANT_POWER_DISABLE: + return "Antenna power disabled" + else: + return "Unknown amplification mode" + + def rxSetup(self, + frequency: int, + modulation: int, + datarate: int, + packetLength: int = 200, + filterBandwidth: int = 0) -> None: + """ + Initial configuration for Rx. + + :param frequency: Frequency + :param modulation: Modulation + :param datarate: Data rate + :param packetLength: Packet length + :param filterBandwidth: Rx filter bandwidth + :return: + """ + + self.setMdmModulation(modulation) + self.setFreq(frequency) + self.setMdmDRate(datarate) + self.setMdmSyncMode(RfUtils.SYNC_MODE_NO_PRE_CS) + + # This command writes to the register PKTCTRL1, setting to zero APPEND_STATUS and ADR_CHK + self.setPktPQT(0) + self.makePktFLEN(packetLength) + # This command writes to the register PKTCTRL0, setting to zero PKT_FORMAT, CRC_EN and LENGTH_CONFIG + self.setEnablePktDataWhitening(False) + # This command writes to the register MDMCFG1, setting to zero NUM_PREAMBLE and CHANSPC_E + self.setEnableMdmFEC(False) + self.setEnablePktCRC(False) + + if not filterBandwidth: + filterBandwidth = Tools.get_minimum_rx_filter_bandwidth(frequency, datarate) + + self.setRxFilterBw(filterBandwidth) + + def txSetup(self, + frequency: int, + modulation: int, + datarate: int,) -> None: + """ + Initial configuration for Tx. + + :param frequency: Frequency + :param modulation: Modulation + :param datarate: Data rate + :return: + """ + + self.setMdmModulation(modulation) + self.setFreq(frequency) + self.setMdmDRate(datarate) + self.setMaxPower() + + def configSpecan(self, freq: int, channel_spacing: int, rx_bandwidth) -> None: + """ + Initial configure before starting SpecAn. + + :param freq: Frequency + :param channel_spacing: Space between channels + :param rx_bandwidth: Rx Bandwidth + :return: + """ + + self.setFreq(freq) + self.setMdmChanSpc(channel_spacing) + self.setRxFilterBw(rx_bandwidth) + + def doFreqFinder(self) -> Optional[int]: + self._sendFreqfinderStart() + freq = None + print("Waiting for signal....") + print("(press Enter to stop)") + while not keystop() and not freq: + try: + r, t = self._recvFreqfinderResult() + freq = int.from_bytes(r, 'little') + except: + pass + return freq + + def RFxmitRle(self, data: bytes, repeat: int, offset: int) -> None: + """ + Transmit frame (RLE encoded). + + :param data: Data to transmit + :param repeat: Times to repeat + :param offset: Offset + :return: + """ + dataLength = len(data) + self._sendXmitRle(dataLength, repeat, offset, data) + + def setAmpMode(self, amplifierMode: int = 0) -> None: + ''' + set the amplifier mode (RF amp external to CC1111) + 0x00 turn off amplifiers + 0x01 turn on TX amplifier only + 0x02 turn on RX amplifier only + 0x05 turn on TX & RX amplifiers (not supported by rev. E) + 0x06 turn on TX amplifier only when transmitting + 0x07 turn on RX amplifier only when receiving + 0x08 turn on TX & RX amplifiers only when transmitting & receiving + 0x03 enable antenna power + 0x04 disable antenna power + ''' + self._sendRfSetTxRxPowerAmpMode(amplifierMode) + + def doDataRateDetect(self, freq: int, modulation: int, deviation: int = 0, occurenceThreshold=DATARATE_MEAS_OCC_THRESHOLD_DEFAULT): + ''' + starts the Data rate measurement procedure. Frequency needs to be setup first. + ''' + print("Entering data rate measurement mode... measured data rates arriving will be displayed on the screen") + print("(press Enter to stop)") + + self.setFreq(freq) + self.setMdmModulation(modulation) + if deviation > 0: + self.setMdmDeviatn(deviation) + self.setMdmDRate(100000) + self.setMdmSyncMode(SYNCM_CARRIER) + self.setPktPQT(0) + self.makePktFLEN(250) + self.setEnablePktDataWhitening(0) + self.setEnableMdmFEC(0) + self.setEnablePktCRC(False) + self.setMdmChanBW(125000) + self.setAmpMode(RF_POWER_AMPLIFIERS_ACTION_ALL_OFF) + self._sendStartDatarateDetection(occurenceThreshold) + + while not keystop(): + # check for SYS_CMD_NIC_DATARATE_DETECTED + try: + (y, t) = self._recvDatarateDetected() + dr, = struct.unpack(" None: + """ + Performs a jamming, either around one frequency (startFrequency) or + between 2 frequencies (startFrequency and stopFrequency) + + :param startFrequency: Center Frequency to Jam or start frequency if stopFrequency is set + :type startFrequency: int + :param dataRate: Datarate is equivalent to "Spectrum Wideness" here: More datarate => wider is the jamming spectrum + :type dataRate: int + :param stopFrequency: Stop frequency, defaults to -1 + :type stopFrequency: int, optional + """ + print("Entering RF jamming mode...") + if not stopFrequency: + stopFrequency = startFrequency + + self._sendRfStartJamming(startFrequency, dataRate, MOD_ASK_OOK, stopFrequency) + + input("press Enter to stop") + + self._sendRfStopJamming() + + def doBruteForceLegacy(self): + pass + + + +class PandwaRFRogue(PandwaRF): + + # RECV / SEND COMMANDS (Low Level) + + # APP_RF + + def _sendRfBruteForceSetupLongSymbol(self, + delay: int, + symbolLength: int, + encSymbol0: int, + encSymbol1: int, + encSymbol2: int, + encSymbol3: int): + _data = struct.pack("BB", delay, symbolLength) + endianess = 'big' + _data += int.to_bytes(encSymbol0, symbolLength, endianess) + _data += int.to_bytes(encSymbol1, symbolLength, endianess) + _data += int.to_bytes(encSymbol2, symbolLength, endianess) + _data += int.to_bytes(encSymbol3, symbolLength, endianess) + return self._sendAppRf(CMD_RF_BRUTE_FORCE_SETUP_LONG_SYMBOL, _data) + + def _sendRfBruteForceStartSyncCodeTail(self, + syncWordSize: int, + tailWordSize: int, + codeLength: int, + startValue: int, + stopValue: int, + frameRepeat: int, + littleEndian: bool, + syncWord: int, + tailWord: int): + _data = struct.pack(" to stop") + + self._sendRfBruteForceStop() diff --git a/rflib/const_gollum.py b/rflib/const_gollum.py new file mode 100644 index 0000000..59893ec --- /dev/null +++ b/rflib/const_gollum.py @@ -0,0 +1,246 @@ +APP_SYSTEM_GOLLUM = 0xF1 +APP_NIC = 0x42 +APP_SPECAN = 0x43 +APP_FREQFINDER = 0x44 +APP_RF = 0xBF + +# APP_SYSTEM_GOLLUM +CMD_PM_SLEEP = 0x8A +CMD_GET_FW_VERSION = 0x90 +CMD_DEVICE_SERIAL_NUMBER_GOLLUM = 0x92 + +# APP_NIC +CMD_NIC_ENCODING_MODE = 0x13 +CMD_NIC_RECV_INFINITE_MODE = 0x0E +CMD_NIC_RECV = 0x01 +CMD_NIC_RECV_RLE = 0x01 +CMD_NIC_REQUEST_REMAINING_DATA = 0x20 +CMD_NIC_RECV_REMAINING_DATA = 0x21 +CMD_NIC_RECV_REMAINING_DATA_RLE = 0x22 +CMD_NIC_XMIT_INFINITE_MODE = 0x12 +CMD_NIC_XMIT = 0x02 +CMD_NIC_XMIT_RLE = 0x04 +CMD_NIC_START_DATARATE_DETECTION = 0x0A +CMD_NIC_STOP_DATARATE_DETECTION = 0x0B +CMD_NIC_DATARATE_DETECTED = 0x0C +CMD_NIC_DATARATE_DETECTED_END = 0x0D +CMD_NIC_RECV_ASYNC_MODE = 0x0F +CMD_NIC_RECV_ASYNC_PROCESSING_ENABLED = 0x10 +CMD_NIC_RECV_ASYNC_DATA_NORDIC = 0xF1 +CMD_NIC_RECV_ASYNC_DATA_CC1111 = 0xF2 + +# APP_RF +CMD_RF_SEND_CONFIG = 0x50 +CMD_RF_TRANSMIT_DATA = 0x51 +CMD_RF_SET_FREQ = 0x52 +CMD_RF_SET_POWER = 0x53 +CMD_RF_SET_MODULATION = 0x54 +CMD_RF_SET_DATARATE = 0x55 +CMD_RF_SET_MAXPOWER = 0x56 +CMD_RF_SET_RXFILTERBW = 0x57 +CMD_RF_SET_PACKETFLEN = 0x58 +CMD_RF_SET_SYNCMODE = 0x59 +CMD_RF_SET_PKTCRC = 0x60 +CMD_RF_SET_CHANSPC = 0x61 +CMD_RF_START_JAMMING = 0x62 +CMD_RF_STOP_JAMMING = 0x63 +CMD_RF_BRUTE_FORCE_SETUP = 0x68 # Gouv custom +CMD_RF_BRUTE_FORCE_SETUP_LONG_SYMBOL = 0x7B +CMD_RF_BRUTE_FORCE_SETUP_FUNCTION = 0x7A +CMD_RF_BRUTE_FORCE_START = 0x64 +CMD_RF_BRUTE_FORCE_START_SYNC_CODE_TAIL = 0x69 # Gouv custom +CMD_RF_BRUTE_FORCE_STOP = 0x65 +CMD_RF_BRUTE_FORCE_STATUS_UPDATE = 0x66 +CMD_RF_SET_TXRX_POWER_AMP_MODE = 0x67 +CMD_RF_GET_TXRX_POWER_AMP_MODE = 0x70 +CMD_RF_SET_RX_MODE_AUTO = 0x5A +CMD_RF_STOP_ALL = 0x71 + +# APP_SPECAN +CMD_RFCAT_START_SPECAN = 0x40 +CMD_RFCAT_STOP_SPECAN = 0x41 +CMD_SPECAN_PKT_DELAY = 0x42 +CMD_SPECAN_QUEUE = 0x43 + +# APP_FREQFINDER + +CMD_FREQFINDER_START = 0xA0 +CMD_FREQFINDER_RESULT = 0xA1 +CMD_FREQFINDER_STOP = 0xA2 + +# RF power amplifiers +RF_POWER_AMPLIFIERS_ACTION_ALL_OFF = 0x00 # action turn off amplifiers +RF_TX_POWER_AMPLIFIER_ACTION_ON = 0x01 # action turn on TX amplifier +RF_RX_POWER_AMPLIFIER_ACTION_ON = 0x02 # action turn on RX amplifier +RF_TX_RX_POWER_AMPLIFIER_ACTION_ON = 0x05 # action turn on TX & RX amplifiers (not supported by rev. E) +RF_TX_POWER_AMPLIFIER_ACTION_ON_TX = 0x06 # action turn on TX amplifier only when transmitting +RF_RX_POWER_AMPLIFIER_ACTION_ON_RX = 0x07 # action turn on RX amplifier only when receiving +RF_TX_RX_POWER_AMPLIFIER_ACTION_ON_TX_RX = 0x08 # action turn on TX & RX amplifiers only when transmitting & receiving +RF_ANT_POWER_ENABLE = 0x03 # action enable antenna power +RF_ANT_POWER_DISABLE = 0x04 # action disable antenna power + +# Data rate measurement +USB_DATARATE_MEAS_WAIT_MS = 100 +DATARATE_MEAS_OCC_THRESHOLD_DEFAULT = 100 + +# Brute Force +USB_BRUTEFORCE_STATUS_WAIT_MS = 5000 +STATE_BRUTEFORCE_UNKNOWN = 0 +STATE_BRUTEFORCE_NOT_STARTED = 1 +STATE_BRUTEFORCE_ONGOING = 2 +STATE_BRUTEFORCE_FINISHED = 3 + + +# Registers + +class Registers: + """ + CC1111 common registers defines. + """ + + # Frequency registers + FREQ2 = 0xDF09 + FREQ1 = 0xDF0A + FREQ0 = 0xDF0B + + # Deviation register + DEVIATN = 0xDF11 + + # Sync Word registers + SYNC_H = 0xDF00 + SYNC_L = 0xDF01 + + # Packet Length register + PKTLEN = 0xDF02 + + # Packet Automation Control registers + PKTCTRL0 = 0xDF04 + PKTCTRL1 = 0xDF03 + + # Modem Configuration register + MDMCFG1 = 0xDF0F + MDMCFG2 = 0xDF0E + +class RfUtils: + """ + Utils defines for RF configuration. + """ + + # Frequency + FREQ_LOW_MIN = 300000000 + FREQ_315 = 315000000 + FREQ_LOW_MAX = 348000000 + FREQ_MID_MIN = 391000000 + FREQ_434 = 434000000 + FREQ_MID_MAX = 464000000 + FREQ_HIGH_MIN = 782000000 + FREQ_867 = 867000000 + FREQ_868 = 868000000 + FREQ_869 = 869000000 + FREQ_HIGH_MAX = 928000000 + FREQ_DEFAULT = 433000000 + FREQ_REMOTE_1 = 433875000 + FREQ_REMOTE_2 = 868200000 + FREQ_915 = 915000000 + PLATFORM_CLOCK_FREQ = 24 + FREQ_REGISTERS_TO_REAL_FREQ_CONST = 1000000 / 0x10000 + + FREQUENCY_VALUES = { + #FREQ_LOW_MIN: FREQ_LOW_MIN, + FREQ_315: FREQ_315, + FREQ_DEFAULT: FREQ_DEFAULT, + #FREQ_LOW_MAX: FREQ_LOW_MAX, + #FREQ_MID_MIN: FREQ_MID_MIN, + FREQ_434: FREQ_434, + #FREQ_MID_MAX: FREQ_MID_MAX, + #FREQ_HIGH_MIN: FREQ_HIGH_MIN, + FREQ_867: FREQ_867, + FREQ_868: FREQ_868, + FREQ_869: FREQ_869, + FREQ_915: FREQ_915, + #FREQ_HIGH_MAX: FREQ_HIGH_MAX, + FREQ_REMOTE_1: FREQ_REMOTE_1, + FREQ_REMOTE_2: FREQ_REMOTE_2 + } + + Frequency_list = [FREQ_315, FREQ_434, FREQ_DEFAULT, FREQ_867, FREQ_868, FREQ_869, FREQ_915] + + # Max and min data rate in BAUD + DATA_RATE_MAX = 500000 + DATA_RATE_MIN = 23 + + # Max and min Rx filter bandwidth in Hz + RX_FILTER_BANDWIDTH_MAX = 750000 + RX_FILTER_BANDWIDTH_MIN = 54000 + RX_DEFAULT_FILTER_BANDWIDTH = 150000 + + RX_FILTER_BANDWIDTH_VALUES = { + RX_FILTER_BANDWIDTH_MIN, + 63000, 75000, 94000, + 107000, 125000, 150000, + 188000, 214000, 250000, + 300000, 375000, 429000, + 500000, 600000, RX_FILTER_BANDWIDTH_MAX, + RX_DEFAULT_FILTER_BANDWIDTH + } + + # Max and min channel spacing in Hz + CHANSPC_MAX = 374268 + CHANSPC_MIN = 23436 + + # Power Amplifier Modes + RF_POWER_AMPLIFIERS_ACTION_ALL_OFF = 0x00 + RF_TX_POWER_AMPLIFIER_ACTION_ON = 0x01 + RF_RX_POWER_AMPLIFIER_ACTION_ON = 0x02 + RF_TX_RX_POWER_AMPLIFIER_ACTION_ON = 0x05 + RF_TX_RX_POWER_AMPLIFIER_ACTION_ON_TX_RX = 0x08 + RF_ANT_POWER_ENABLE = 0x80 + RF_ANT_POWER_DISABLE = 0x00 + + amp_modes = [ + RF_POWER_AMPLIFIERS_ACTION_ALL_OFF, + RF_TX_POWER_AMPLIFIER_ACTION_ON, + RF_RX_POWER_AMPLIFIER_ACTION_ON + ] + + # Deviation + CC1111_DEVIATION_MAX = 374268 + CC1111_DEVIATION_MIN = 23436 + + # Modulation + # MSK is only supported for data rates above 26 kBaud and cannot be used with Manchester enc/dec enabled. + # GFSK, ASK and OOK is only supported for data rate up until 250 kBaud + MOD_FORMAT_2_FSK = 0x00 + MOD_FORMAT_GFSK = 0x10 + MOD_FORMAT_ASK_OOK = 0x30 + MOD_FORMAT_MSK = 0x70 + MOD_INVERT = True + MOD_NOT_INVERT = False + + mod_to_set = [ + MOD_FORMAT_2_FSK, + MOD_FORMAT_ASK_OOK, + MOD_FORMAT_GFSK, + MOD_FORMAT_MSK + ] + + # Sync Mode + SYNC_MODE_NO_PRE = 0x00 + SYNC_MODE_15_16 = 0x01 + SYNC_MODE_16_16 = 0x02 + SYNC_MODE_30_32 = 0x03 + SYNC_MODE_NO_PRE_CS = 0x04 + SYNC_MODE_15_16_CS = 0x05 + SYNC_MODE_16_16_CS = 0x06 + SYNC_MODE_30_32_CS = 0x07 + + sync_mode_to_set = [ + SYNC_MODE_NO_PRE, + SYNC_MODE_NO_PRE_CS, + SYNC_MODE_15_16, + SYNC_MODE_15_16_CS, + SYNC_MODE_16_16, + SYNC_MODE_16_16_CS, + SYNC_MODE_30_32, + SYNC_MODE_30_32_CS + ] \ No newline at end of file diff --git a/rflib/utils_gollum.py b/rflib/utils_gollum.py new file mode 100644 index 0000000..6c0b32d --- /dev/null +++ b/rflib/utils_gollum.py @@ -0,0 +1,26 @@ +from .const_gollum import RfUtils + +class Tools: + """ + General tools for Gollum CC1111. + """ + + @classmethod + def get_minimum_rx_filter_bandwidth(cls, freq: int, data_rate: int) -> int: + """ + Calculate the minimum Rx filter bandwidth for a given frequency and data rate value. + + :param freq: Frequency + :param data_rate: Data rate + :return: Minimum filter bandwidth + """ + + freq_uncertainty = 20e-6 * freq * 2 + min_bw = data_rate + freq_uncertainty + + for bw_val in RfUtils.RX_FILTER_BANDWIDTH_VALUES: + if bw_val > min_bw: + return bw_val + +if __name__ == '__main__': + pass From 31e22342b02ee48c79a3374f918771275f7d2f36 Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Mon, 7 Feb 2022 11:35:10 +0100 Subject: [PATCH 04/31] RfCat: Add PandwaRF example scripts --- rflib/gollum_examples/simple_bruteforce.py | 31 +++++++++++++++++++ .../simple_datarate_measurement.py | 8 +++++ rflib/gollum_examples/simple_freqfinder.py | 8 +++++ rflib/gollum_examples/simple_jamming.py | 9 ++++++ rflib/gollum_examples/simple_rx.py | 17 ++++++++++ rflib/gollum_examples/simple_tx.py | 12 +++++++ 6 files changed, 85 insertions(+) create mode 100644 rflib/gollum_examples/simple_bruteforce.py create mode 100644 rflib/gollum_examples/simple_datarate_measurement.py create mode 100644 rflib/gollum_examples/simple_freqfinder.py create mode 100644 rflib/gollum_examples/simple_jamming.py create mode 100644 rflib/gollum_examples/simple_rx.py create mode 100644 rflib/gollum_examples/simple_tx.py diff --git a/rflib/gollum_examples/simple_bruteforce.py b/rflib/gollum_examples/simple_bruteforce.py new file mode 100644 index 0000000..b41f386 --- /dev/null +++ b/rflib/gollum_examples/simple_bruteforce.py @@ -0,0 +1,31 @@ +from rflib.chipcon_gollum import * + +if __name__ == "__main__": + # This bruteforce is only available on PandwaRF Rogue + # See bruteforce legacy for the PandwaRF (non Rogue) + d = PandwaRFRogue() + d.setModeIDLE() + FREQ = 433_920_000 + DATARATE = 24_536 + MOD = MOD_ASK_OOK + codeLength = 12 + startValue = 3190 + stopValue = 3300 + repeat = 6 + delayMs = 30 + + encSymbolZero = 0xFF0000 + encSymbolOne = 0xFF00FF + encSymbolTwo = 0x000000 + encSymbolThree = 0x000000 + syncWordSize = 40 + syncWord = 0 + tailWordSize = 8 + tailWord = 0xFF00000000000000 + functionSize = 12 + functionMask = 0xFFFFFFFFFFFFFFFFFFFF0000 + functionValue= 0x000000000000000000000001 + + d.doBruteForce(FREQ, MOD, DATARATE, startValue, stopValue, codeLength, repeat, delayMs, encSymbolZero, encSymbolOne, encSymbolTwo, encSymbolThree, syncWordSize, syncWord, tailWordSize, tailWord, functionSize, functionMask, functionValue) + + d.setModeIDLE() \ No newline at end of file diff --git a/rflib/gollum_examples/simple_datarate_measurement.py b/rflib/gollum_examples/simple_datarate_measurement.py new file mode 100644 index 0000000..04e07e0 --- /dev/null +++ b/rflib/gollum_examples/simple_datarate_measurement.py @@ -0,0 +1,8 @@ +from rflib.chipcon_gollum import * + +if __name__ == "__main__": + d = PandwaRF() + d.setModeIDLE() + FREQ = 433920000 + d.doDataRateDetect(FREQ, MOD_ASK_OOK) + d.setModeIDLE() diff --git a/rflib/gollum_examples/simple_freqfinder.py b/rflib/gollum_examples/simple_freqfinder.py new file mode 100644 index 0000000..c04a13d --- /dev/null +++ b/rflib/gollum_examples/simple_freqfinder.py @@ -0,0 +1,8 @@ +from rflib.chipcon_gollum import * + +if __name__ == "__main__": + d = PandwaRF() + d.setModeIDLE() + freq = d.doFreqFinder() + print(f"Found frequency : {freq} Hz") + d.setModeIDLE() diff --git a/rflib/gollum_examples/simple_jamming.py b/rflib/gollum_examples/simple_jamming.py new file mode 100644 index 0000000..c9bd66c --- /dev/null +++ b/rflib/gollum_examples/simple_jamming.py @@ -0,0 +1,9 @@ +from rflib.chipcon_gollum import * + +if __name__ == "__main__": + d = PandwaRF() + d.setModeIDLE() + FREQ = 433_920_000 + DATARATE = 10_000 # Equivalent to spectrum wideness of the jamming + d.doJamming(FREQ, DATARATE) + d.setModeIDLE() diff --git a/rflib/gollum_examples/simple_rx.py b/rflib/gollum_examples/simple_rx.py new file mode 100644 index 0000000..2033fad --- /dev/null +++ b/rflib/gollum_examples/simple_rx.py @@ -0,0 +1,17 @@ +from rflib.chipcon_gollum import * +import time + +if __name__ == "__main__": + d = PandwaRF() + FREQ = 433920000 + DATARATE = 2500 + d.setModeIDLE() + d.rxSetup(FREQ, MOD_ASK_OOK, DATARATE) + d.setAmpMode(RF_RX_POWER_AMPLIFIER_ACTION_ON) + d.setModeRX() + print("Please send some data...") + time.sleep(5) + print("Data received :") + print(d.RFrecv()) + d.setModeIDLE() + \ No newline at end of file diff --git a/rflib/gollum_examples/simple_tx.py b/rflib/gollum_examples/simple_tx.py new file mode 100644 index 0000000..7c6ef52 --- /dev/null +++ b/rflib/gollum_examples/simple_tx.py @@ -0,0 +1,12 @@ +from rflib.chipcon_gollum import * + +if __name__ == "__main__": + d = PandwaRF() + FREQ = 433920000 + DATARATE = 2500 + d.setModeIDLE() + d.txSetup(FREQ, MOD_ASK_OOK, DATARATE) + d.setAmpMode(RF_TX_POWER_AMPLIFIER_ACTION_ON) + d.setModeTX() + d.RFxmit(b"HALLO") + d.setModeIDLE() From fb117698a3df43d62d5c28332ad9bf7b330ddb96 Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Mon, 7 Feb 2022 12:23:30 +0100 Subject: [PATCH 05/31] RfCat: Fix specan --- rflib/chipcon_gollum.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/rflib/chipcon_gollum.py b/rflib/chipcon_gollum.py index 11612a4..4fa2cea 100644 --- a/rflib/chipcon_gollum.py +++ b/rflib/chipcon_gollum.py @@ -1,7 +1,7 @@ #!/usr/bin/env ipython import struct from typing import Optional -from . import RfCat +from . import RFCAT_START_SPECAN, RFCAT_STOP_SPECAN, RfCat from rflib.ccspecan import SPECAN_QUEUE @@ -9,7 +9,7 @@ from .chipcon_usb import ChipconUsbTimeoutException, keystop from .chipcondefs import RFST_SIDLE -from .const import MOD_ASK_OOK, SYNCM_CARRIER, USB_RX_WAIT +from .const import MOD_ASK_OOK, SYNCM_CARRIER, USB_RX_WAIT, USB_TX_WAIT from .const_gollum import * # PandwaRF/PandwaRF Rogue specific methods @@ -29,6 +29,15 @@ def recv(self, app, cmd=None, wait=USB_RX_WAIT): r, t = super().recv(app, cmd, wait) return r, t + def send(self, app, cmd, buf, wait=USB_TX_WAIT): + # Override send to replace some commands (e.g specan) + if app == APP_NIC: + if cmd in [RFCAT_START_SPECAN, RFCAT_STOP_SPECAN]: + app = APP_SPECAN + + r, t = super().send(app, cmd, buf, wait) + return r, t + # RECV / SEND COMMANDS (Low Level) # APP_SYSTEM_GOLLUM @@ -209,7 +218,7 @@ def _sendRfSetPktCrc(self, crcMode: bool): return self._sendAppRf(CMD_RF_SET_PKTCRC, _data) def _sendRfSetChanSpc(self, channelSpacing: int): - _data = struct.pack("B", channelSpacing) + _data = struct.pack(" str: return '\n'.join(output) + def _stopSpecAn(self): + # Overload because it is wrongly implemented in rfcat + self._sendRfcatStopSpecan() + # User functions From b76c6008f3b70127438d490807fd4acbc58b0890 Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Mon, 7 Feb 2022 13:09:47 +0100 Subject: [PATCH 06/31] USB Device List: Add the PandwaRF and PandwaRF Rogue product ids and auto detect device when doing rfcat -r --- rfcat | 27 +++++++++++++++++++++++---- rflib/chipcon_usb.py | 10 +++++++++- rflib/const_gollum.py | 5 +++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/rfcat b/rfcat index 7f2bcbb..3b14736 100755 --- a/rfcat +++ b/rfcat @@ -6,6 +6,9 @@ import sys import logging import readline import rlcompleter + +from rflib.chipcon_gollum import PandwaRF, PandwaRFRogue +from rflib.const_gollum import GOLLUM_VENDOR_ID readline.parse_and_bind("tab: complete") from rflib import * @@ -30,6 +33,20 @@ you interact with the rfcat dongle: """ +def getDongleClass(idx: int): + dongle = getRfCatDevices()[idx] + if dongle.idVendor == GOLLUM_VENDOR_ID: + if dongle.idProduct == PANDWARF_ROGUE_PRODUCT_ID: + print("PandwaRF Rogue Detected") + return PandwaRFRogue + elif dongle.idProduct == PANDWARF_PRODUCT_ID: + print("PandwaRF (non Rogue) Detected") + return PandwaRF + + print("Dongle is not a PandwaRF") + return RfCat + + if __name__ == "__main__": import argparse @@ -46,23 +63,25 @@ if __name__ == "__main__": ifo = parser.parse_args() + DongleClass = getDongleClass(ifo.index) + if ifo.bootloader: if not ifo.force: print("Protecting you from yourself. If you want to trigger Bootloader mode (you will then *have* to flash a new RfCat image on it) use the --force argument as well") exit(-1) print("Entering RfCat Bootloader mode, ready for new image...") - RfCat(ifo.index, safemode=ifo.safemode).bootloader() + DongleClass(ifo.index, safemode=ifo.safemode).bootloader() exit(0) elif ifo.specan: - RfCat(ifo.index).specan(ifo.centfreq,ifo.inc,ifo.specchans) + DongleClass(ifo.index).specan(ifo.centfreq,ifo.inc,ifo.specchans) elif ifo.research: - interactive(ifo.index, DongleClass=RfCat, intro=intro, safemode=ifo.safemode) + interactive(ifo.index, DongleClass=DongleClass, intro=intro, safemode=ifo.safemode) else: # do the full-rfcat thing - d = RfCat(ifo.index, debug=False) + d = DongleClass(ifo.index, debug=False) d.rf_redirection((sys.stdin, sys.stdout)) diff --git a/rflib/chipcon_usb.py b/rflib/chipcon_usb.py index 38bc015..e8a693c 100644 --- a/rflib/chipcon_usb.py +++ b/rflib/chipcon_usb.py @@ -19,6 +19,8 @@ import threading from binascii import hexlify +from rflib.const_gollum import PANDWARF_PRODUCT_ID, PANDWARF_ROGUE_PRODUCT_ID + from . import bits from .bits import correctbytes, ord23 from .const import * @@ -41,7 +43,13 @@ def getRfCatDevices(): for bus in usb.busses(): for dev in bus.devices: # OpenMoko assigned or Legacy TI - if (dev.idVendor == 0x0451 and dev.idProduct == 0x4715) or (dev.idVendor == 0x1d50 and (dev.idProduct == 0x6047 or dev.idProduct == 0x6048 or dev.idProduct == 0x605b or dev.idProduct == 0xecc1)): + if (dev.idVendor == 0x0451 and dev.idProduct == 0x4715) or \ + (dev.idVendor == 0x1d50 and (dev.idProduct == 0x6047 or + dev.idProduct == 0x6048 or + dev.idProduct == 0x605b or + dev.idProduct == 0xecc1 or + dev.idProduct == PANDWARF_PRODUCT_ID or + dev.idProduct == PANDWARF_ROGUE_PRODUCT_ID)): rfcats.append(dev) elif (dev.idVendor == 0x1d50 and (dev.idProduct == 0x6049 or dev.idProduct == 0x604a or dev.idProduct == 0xecc0)): diff --git a/rflib/const_gollum.py b/rflib/const_gollum.py index 59893ec..1349096 100644 --- a/rflib/const_gollum.py +++ b/rflib/const_gollum.py @@ -90,6 +90,11 @@ STATE_BRUTEFORCE_ONGOING = 2 STATE_BRUTEFORCE_FINISHED = 3 +# VENDOR / PRODUCT ID + +GOLLUM_VENDOR_ID = 0x1d50 +PANDWARF_PRODUCT_ID = 0x60FF +PANDWARF_ROGUE_PRODUCT_ID = 0x60FE # Registers From b069590bde506653e977ac7207314e08d73b4d9c Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Mon, 7 Feb 2022 13:27:17 +0100 Subject: [PATCH 07/31] USB non-root: Add PandwaRF vendor/device id to udev rules --- etc/udev/rules.d/20-rfcat.rules | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/etc/udev/rules.d/20-rfcat.rules b/etc/udev/rules.d/20-rfcat.rules index 9aa71e3..25e7d4d 100644 --- a/etc/udev/rules.d/20-rfcat.rules +++ b/etc/udev/rules.d/20-rfcat.rules @@ -12,3 +12,8 @@ SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}== SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="604a", MODE:="0660", SYMLINK+="RFCAT_BL_D", ENV{ID_MM_DEVICE_IGNORE}="1", GROUP="dialout" SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="605c", MODE:="0660", SYMLINK+="RFCAT_BL_YS1", ENV{ID_MM_DEVICE_IGNORE}="1", GROUP="dialout" SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="ecc0", MODE:="0660", SYMLINK+="RFCAT_BL_SRF", ENV{ID_MM_DEVICE_IGNORE}="1", GROUP="dialout" + +# PandwaRF devices + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="60fe", MODE:="0660", SYMLINK+="RFCAT%n", GROUP="dialout" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="60ff", MODE:="0660", SYMLINK+="RFCAT%n", GROUP="dialout" From a4badec9ba3a2b27036dde5fe7c1757918b8f754 Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Mon, 7 Feb 2022 14:45:05 +0100 Subject: [PATCH 08/31] RfCat: Update bruteforce method and example script --- rflib/chipcon_gollum.py | 48 +++++++++++----------- rflib/gollum_examples/simple_bruteforce.py | 3 +- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/rflib/chipcon_gollum.py b/rflib/chipcon_gollum.py index 4fa2cea..94c2267 100644 --- a/rflib/chipcon_gollum.py +++ b/rflib/chipcon_gollum.py @@ -651,8 +651,29 @@ def doJamming(self, startFrequency: int, dataRate: int, stopFrequency: int = Non self._sendRfStopJamming() - def doBruteForceLegacy(self): + def _doBruteForceReceive(self, stopValue: int): + status = 0 + while (not keystop() and (status <= stopValue)): + # check for SYS_CMD_RF_BRUTE_FORCE_STATUS_UPDATE + try: + (y, t) = self._recvRfBruteForceStatusUpdate() + status, state = struct.unpack(" to stop") + @@ -704,6 +725,7 @@ def doBruteForce(self, codeLength: int, repeat: int, delay: int, + symbolLength: int, encSymbol0: int, encSymbol1: int, encSymbol2: int, @@ -720,30 +742,10 @@ def doBruteForce(self, print("(press Enter to stop)") self.txSetup(frequency, modulation, datarate) - self._sendRfBruteForceSetupLongSymbol(delay, 3, encSymbol0, encSymbol1, encSymbol2, encSymbol3) + self._sendRfBruteForceSetupLongSymbol(delay, symbolLength, encSymbol0, encSymbol1, encSymbol2, encSymbol3) self._sendRfBruteForceSetupFunction(functionSize, functionMask, functionValue) self._sendRfBruteForceStartSyncCodeTail(syncWordSize, tailWordSize, codeLength, startValue, stopValue, repeat, littleEndian, syncWord, tailWord) - status = 0 - while (not keystop() and (status <= stopValue)): - # check for SYS_CMD_RF_BRUTE_FORCE_STATUS_UPDATE - try: - (y, t) = self._recvRfBruteForceStatusUpdate() - status, state = struct.unpack(" to stop") + self._doBruteForceReceive(stopValue) self._sendRfBruteForceStop() diff --git a/rflib/gollum_examples/simple_bruteforce.py b/rflib/gollum_examples/simple_bruteforce.py index b41f386..ce90bf1 100644 --- a/rflib/gollum_examples/simple_bruteforce.py +++ b/rflib/gollum_examples/simple_bruteforce.py @@ -14,6 +14,7 @@ repeat = 6 delayMs = 30 + symbolLength = 3 encSymbolZero = 0xFF0000 encSymbolOne = 0xFF00FF encSymbolTwo = 0x000000 @@ -26,6 +27,6 @@ functionMask = 0xFFFFFFFFFFFFFFFFFFFF0000 functionValue= 0x000000000000000000000001 - d.doBruteForce(FREQ, MOD, DATARATE, startValue, stopValue, codeLength, repeat, delayMs, encSymbolZero, encSymbolOne, encSymbolTwo, encSymbolThree, syncWordSize, syncWord, tailWordSize, tailWord, functionSize, functionMask, functionValue) + d.doBruteForce(FREQ, MOD, DATARATE, startValue, stopValue, codeLength, repeat, delayMs, symbolLength, encSymbolZero, encSymbolOne, encSymbolTwo, encSymbolThree, syncWordSize, syncWord, tailWordSize, tailWord, functionSize, functionMask, functionValue) d.setModeIDLE() \ No newline at end of file From 43eea214abe8a2eca161566c190ff95614d93ecc Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Mon, 7 Feb 2022 17:34:18 +0100 Subject: [PATCH 09/31] RfCat: Add bruteforce legacy with example script --- rflib/chipcon_gollum.py | 30 ++++++++++++++++++- .../simple_bruteforce_legacy.py | 30 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 rflib/gollum_examples/simple_bruteforce_legacy.py diff --git a/rflib/chipcon_gollum.py b/rflib/chipcon_gollum.py index 94c2267..a729731 100644 --- a/rflib/chipcon_gollum.py +++ b/rflib/chipcon_gollum.py @@ -264,7 +264,7 @@ def _sendRfBruteForceStart(self, syncWordSize: int, syncWord: int): _data = struct.pack(" to stop") + + def doBruteForceLegacy(self, + frequency: int, + modulation: int, + datarate: int, + startValue: int, + stopValue: int, + codeLength: int, + repeat: int, + delay: int, + encSymbol0: int, + encSymbol1: int, + encSymbol2: int, + encSymbol3: int, + syncWordSize: int, + syncWord: int, + functionSize: int, + functionMask: int, + functionValue: int, + littleEndian=False): + print("Entering brute force mode... status arriving will be displayed on the screen") + print("(press Enter to stop)") + + self.txSetup(frequency, modulation, datarate) + self._sendRfBruteForceSetupFunction(functionSize, functionMask, functionValue) + self._sendRfBruteForceStart(codeLength, startValue, stopValue, repeat, littleEndian, delay, encSymbol0, encSymbol1, encSymbol2, encSymbol3, syncWordSize, syncWord) + self._doBruteForceReceive(stopValue) + self._sendRfBruteForceStop() diff --git a/rflib/gollum_examples/simple_bruteforce_legacy.py b/rflib/gollum_examples/simple_bruteforce_legacy.py new file mode 100644 index 0000000..204e08f --- /dev/null +++ b/rflib/gollum_examples/simple_bruteforce_legacy.py @@ -0,0 +1,30 @@ +from rflib.chipcon_gollum import * + +if __name__ == "__main__": + # If you have a PandwaRF Rogue, it is better to use the + # other version of bruteforce (non-legacy) + d = PandwaRF() + d.setModeIDLE() + FREQ = 433_890_000 + DATARATE = 5_320 + MOD = MOD_ASK_OOK + codeLength = 8 + startValue = 0 + stopValue = 6560 + repeat = 10 + delayMs = 0x64 + + encSymbolZero = 0x8E + encSymbolOne = 0xEE + encSymbolTwo = 0xE8 + encSymbolThree = 0x00 + syncWordSize = 2 + syncWord = 0x0008 + + functionSize = 8 + functionMask = 0xFFFFFF0000FFFFFF + functionValue= 0x000000EEE8000000 + + d.doBruteForceLegacy(FREQ, MOD, DATARATE, startValue, stopValue, codeLength, repeat, delayMs, encSymbolZero, encSymbolOne, encSymbolTwo, encSymbolThree, syncWordSize, syncWord, functionSize, functionMask, functionValue) + + d.setModeIDLE() \ No newline at end of file From 7d2a37bebcc872e14a225f01d9f5a22bb37a086e Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Tue, 8 Feb 2022 11:09:26 +0100 Subject: [PATCH 10/31] Doc: Update Readme on how to install and use rfcat (with/without docker) --- README.md | 84 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 80e820b..2d9508a 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,75 @@ +Welcome to the official rfcat project that works for `PandwaRF` and `PandwaRF Rogue`, modified to also work with legacy rfcat dongles such as `Yard Stick One`. +See the legacy readme [here](#legacy-readme). + +## How to install RfCat python + +There are multiple ways to install rfcat, including a docker way, but for now, if you are using docker, you won't be able to use the `rfcat -s` feature (the Spectrum Analyser) since it require a GUI (Graphical User Interface). + +### On Linux + +- (Recommended) Install using docker and our custom image by simply running `./run_rfcat_docker.sh` +- Install using the [legacy install](#installing-client) + +### On Windows + +On windows you can use rfcat with `WSL 2` and the `Ubuntu distribution for WSL 2`. You can : +- (Recommended) Install using `Docker Desktop` by simply running `run_rfcat_docker.bat` +- Install using the [legacy install](#installing-client) of rfcat inside `WSL 2`. + +In both cases, after installation, you will need to follow this [guide](https://devblogs.microsoft.com/commandline/connecting-usb-devices-to-wsl/) to expose your PandwaRF / Yard Stick One USB Dongle to the `WSL 2 / Docker instance`. The guide is working on `Windows 10` even if it says you need `Windows 11`. + +## How to use RfCat python without root permissions + +(You don't need to do this if you are using the docker method) + +You may have tried to run `rfcat -r` and saw a `Permission Denied` error, and then simply did `sudo rfcat -r` to make it work, but you can avoid the `sudo` by allowing user access to the RfCat USB Dongles by doing the following: + +```bash +sudo cp etc/udev/rules.d/20-rfcat.rules /etc/udev/rules.d +sudo udevadm control --reload-rules +# Then you may need to reboot to apply changes +``` + +## How to use RfCat python + +After having followed the above guide on how to install the rfcat python client, you can simply type `rfcat -r` to interact with the Dongle. You can also create scripts to use the Dongle, and some example script for the `PandwaRF` are provided in [rflib/gollum_examples](./rflib/gollum_examples). +You can also check the [legacy Using rfcat](#using-rfcat) for additional info. + + + + +# Legacy Readme + Welcome to the rfcat project ## Table of Contents -* [Goals](#goals) -* [Requirements](#requirements) - * [Other requirements](#other-requirements) - * [Build requirements](#build-requirements) -* [Development](#development) - * ["Gotchas"](#gotchas) -* [Installing on hardware](#installing-on-hardware) - * [Allowing non-root dongle access](#allowing-non-root-dongle-access) - * [Supported dongles](#supported-dongles) - * [Your build environment](#your-build-environment) -* [Installing with bootloader](#installing-with-bootloader) - * [To install](#to-install) -* [Installing client](#installing-client) -* [Using RfCat](#using-rfcat) -* [Cool Projects Using RfCat](#external-projects) -* [Epilogue](#epilogue) +- [Legacy Readme](#legacy-readme) + - [Table of Contents](#table-of-contents) + - [GOALS](#goals) + - [REQUIREMENTS](#requirements) + - [Other requirements](#other-requirements) + - [Build requirements](#build-requirements) + - [DEVELOPMENT](#development) + - [Gotchas](#gotchas) + - [INSTALLING ON HARDWARE](#installing-on-hardware) + - [allowing non-root dongle access](#allowing-non-root-dongle-access) + - [supported dongles](#supported-dongles) + - [GoodFET](#goodfet) + - [Chronos Dongle](#chronos-dongle) + - [EMK Dongle](#emk-dongle) + - [YARD Stick One](#yard-stick-one) + - [INSTALLING WITH BOOTLOADER](#installing-with-bootloader) + - [Steps required for all firmware installs and updates](#steps-required-for-all-firmware-installs-and-updates) + - [Steps for bootloader + firmware installs via hardware debugger](#steps-for-bootloader--firmware-installs-via-hardware-debugger) + - [Steps for firmware updates via USB port](#steps-for-firmware-updates-via-usb-port) + - [Installing client](#installing-client) + - [Dependencies](#dependencies) + - [Installation](#installation) + - [Installation with pip](#installation-with-pip) + - [Using rfcat](#using-rfcat) + - [External Projects](#external-projects) + - [Epilogue](#epilogue) ## GOALS From f938577d7a265e1f89236871328e5a5c661ecb33 Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Tue, 8 Feb 2022 11:27:12 +0100 Subject: [PATCH 11/31] RfCat: Minor fixes --- rfcat | 5 ++++- rflib/chipcon_gollum.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rfcat b/rfcat index 3b14736..befa9cf 100755 --- a/rfcat +++ b/rfcat @@ -34,7 +34,10 @@ you interact with the rfcat dongle: """ def getDongleClass(idx: int): - dongle = getRfCatDevices()[idx] + dongles = getRfCatDevices() + if idx >= len(dongles): + return RfCat + dongle = dongles[idx] if dongle.idVendor == GOLLUM_VENDOR_ID: if dongle.idProduct == PANDWARF_ROGUE_PRODUCT_ID: print("PandwaRF Rogue Detected") diff --git a/rflib/chipcon_gollum.py b/rflib/chipcon_gollum.py index a729731..280b109 100644 --- a/rflib/chipcon_gollum.py +++ b/rflib/chipcon_gollum.py @@ -670,7 +670,7 @@ def _doBruteForceReceive(self, stopValue: int): except ChipconUsbTimeoutException: # print "Timeout Brute force status update" - pass + pass except KeyboardInterrupt: print("Please press to stop") From cc2550d83fb14722196518cb0125414ae3c5bbfb Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Tue, 8 Feb 2022 11:27:37 +0100 Subject: [PATCH 12/31] Docker: Make it work on windows --- Dockerfile | 5 ++--- run_rfcat_docker.bat | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 run_rfcat_docker.bat diff --git a/Dockerfile b/Dockerfile index aede72f..c4248d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,9 @@ FROM python:3.9.10 WORKDIR /pandwarf -COPY . ./rfcat - -RUN apt update && apt install -y usbutils +RUN apt update && apt install -y usbutils ffmpeg && pip install pyside2 +COPY . ./rfcat RUN cd rfcat && python setup.py install CMD [ "bash" ] diff --git a/run_rfcat_docker.bat b/run_rfcat_docker.bat new file mode 100644 index 0000000..398b9b0 --- /dev/null +++ b/run_rfcat_docker.bat @@ -0,0 +1 @@ +docker-compose run rfcat_image bash \ No newline at end of file From 4e3274a3693aee04291521b9c69f5661d0775260 Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Tue, 8 Feb 2022 17:23:47 +0100 Subject: [PATCH 13/31] RfCat: Add example script to interact with Kaiju and PandwaRF --- rflib/gollum_examples/kaiju_analysis.py | 55 ++++++++++++++ .../kaiju_generate_rolling_code.py | 74 +++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 rflib/gollum_examples/kaiju_analysis.py create mode 100644 rflib/gollum_examples/kaiju_generate_rolling_code.py diff --git a/rflib/gollum_examples/kaiju_analysis.py b/rflib/gollum_examples/kaiju_analysis.py new file mode 100644 index 0000000..f3234f8 --- /dev/null +++ b/rflib/gollum_examples/kaiju_analysis.py @@ -0,0 +1,55 @@ +import json +from rflib.chipcon_gollum import * +import time, requests + +if __name__ == "__main__": + d = PandwaRF() + FREQ = 433_920_000 + DATARATE = 10_000 + # Your token can be found on https://rolling.pandwarf.com/profile-user + # It is the 'API token' + KAIJU_API_TOKEN = "YOUR_KAIJU_API_TOKEN_HERE" + + d.setModeIDLE() + d.rxSetup(FREQ, MOD_ASK_OOK, DATARATE) + d.setAmpMode(RF_RX_POWER_AMPLIFIER_ACTION_ON) + d.setModeRX() + print("Please send some data...") + time.sleep(5) + data, _ = d.RFrecv() + print(f"Data received : {data.hex()}") + d.setModeIDLE() + print("Analysing data with Kaiju...") + + base_url = "https://rolling.pandwarf.com/api/v1" + s = requests.Session() + s.headers.update({ + 'Authorization': f'Token {KAIJU_API_TOKEN}' + }) + + payload = { + "rawHexStream": data.hex() + } + payload = json.dumps(payload) + response = s.post(f"{base_url}/analyze/detailed", data=payload) + + result = response.json() + if "progress" not in result.keys(): + print(json.dumps(result, indent=2)) + d.setModeIDLE() + exit() + # Now polling server every 1 seconds until processing is finished + progress = result["progress"] + taskId = result["id"] + while progress != 100: + r = s.get(f"{base_url}/task/{taskId}") + result = r.json() + progress = result["progress"] + print(f"Progress: {progress}/100") + time.sleep(1) + + print("Processing done, now retrieving the result :") + r = s.get(f"{base_url}/remote/{taskId}") + result = r.json() + remoteData = result["remoteData"] + print(json.dumps(remoteData, indent=2)) diff --git a/rflib/gollum_examples/kaiju_generate_rolling_code.py b/rflib/gollum_examples/kaiju_generate_rolling_code.py new file mode 100644 index 0000000..127499d --- /dev/null +++ b/rflib/gollum_examples/kaiju_generate_rolling_code.py @@ -0,0 +1,74 @@ +import json +from rflib.chipcon_gollum import * +import time, requests + +if __name__ == "__main__": + d = PandwaRF() + FREQ = 433_920_000 + DATARATE = 2_500 + # Your token can be found on https://rolling.pandwarf.com/profile-user + # It is the 'API token' + KAIJU_API_TOKEN = "YOUR_KAIJU_API_TOKEN_HERE" + REMOTE_INFO = { + "rollingCodeRequest": { + "type": "Gate Opener", + "brand": "MHouse", + "model": "GTX", + "serialNumberHex": "0x041170E", + "syncCounter": 1435, + "button": 1, + "seed": "", + "numCodesRequested": 2 + } + } + # We will generate 2 rolling codes and send them using the PandwaRF + base_url = "https://rolling.pandwarf.com/api/v1" + s = requests.Session() + s.headers.update({ + 'Authorization': f'Token {KAIJU_API_TOKEN}' + }) + + payload = json.dumps(REMOTE_INFO) + print("Asking Kaiju for rolling codes") + response = s.post(f"{base_url}/generate/param", data=payload) + result = response.json() + if "progress" not in result.keys(): + print(json.dumps(result, indent=2)) + d.setModeIDLE() + exit() + # Now polling server every 1 seconds until processing is finished + progress = result["progress"] + taskId = result["id"] + while progress != 100: + r = s.get(f"{base_url}/task/{taskId}") + result = r.json() + progress = result["progress"] + print(f"Progress: {progress}/100") + time.sleep(1) + + print("Processing done, now retrieving the rolling codes :") + r = s.get(f"{base_url}/remote/{taskId}") + result = r.json() + rollingCodes = result["remoteData"]["rollingCodes"] + + print("Preparing PandwaRF for TX") + d.setModeIDLE() + d.txSetup(FREQ, MOD_ASK_OOK, DATARATE) + d.setAmpMode(RF_TX_POWER_AMPLIFIER_ACTION_ON) + d.setModeTX() + + for rollingCode in rollingCodes: + syncCounter = rollingCode["syncCounter"] + button = rollingCode["button"] + dataHex = rollingCode["dataHex"] + fullMsgHex = rollingCode["fullMsgHex"] + data = dataHex + if not data: + data = fullMsgHex + + data = bytes.fromhex(data) + print(f"Sending rolling code {syncCounter} of button {button}") + d.RFxmit(data) + time.sleep(2) + + d.setModeIDLE() From f46e2f691a0a29890fd77e4d73a026a28b532185 Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Tue, 8 Feb 2022 17:37:15 +0100 Subject: [PATCH 14/31] RfCat: Add another example script to generate and send rolling code --- .../kaiju_generate_rolling_code_2.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 rflib/gollum_examples/kaiju_generate_rolling_code_2.py diff --git a/rflib/gollum_examples/kaiju_generate_rolling_code_2.py b/rflib/gollum_examples/kaiju_generate_rolling_code_2.py new file mode 100644 index 0000000..2e9e663 --- /dev/null +++ b/rflib/gollum_examples/kaiju_generate_rolling_code_2.py @@ -0,0 +1,72 @@ +import json +from rflib.chipcon_gollum import * +import time, requests + +if __name__ == "__main__": + d = PandwaRF() + FREQ = 433_920_000 + DATARATE = 2_500 + # Your token can be found on https://rolling.pandwarf.com/profile-user + # It is the 'API token' + KAIJU_API_TOKEN = "YOUR_KAIJU_API_TOKEN_HERE" + ROLLING_INFO = { + "numCodesRequested": 2, + "rxTxConfig": { + "rawBitStream": "10101010101010101010101000000000001101001001001001001001101101101001001001101001001101101001101101101001101101101001101001001101101101001001001101101101101001001001101001101101101001101101101101101001101101101101101101001101101101" + } + } + # We will generate 2 rolling codes and send them using the PandwaRF + base_url = "https://rolling.pandwarf.com/api/v1" + s = requests.Session() + s.headers.update({ + 'Authorization': f'Token {KAIJU_API_TOKEN}' + }) + + payload = json.dumps(ROLLING_INFO) + print("Asking Kaiju for rolling codes") + response = s.post(f"{base_url}/generate/capture", data=payload) + result = response.json() + if "progress" not in result.keys(): + print(json.dumps(result, indent=2)) + d.setModeIDLE() + exit() + # Now polling server every 1 seconds until processing is finished + progress = result["progress"] + taskId = result["id"] + while progress != 100: + r = s.get(f"{base_url}/task/{taskId}") + result = r.json() + progress = result["progress"] + print(f"Progress: {progress}/100") + time.sleep(1) + + print("Processing done, now retrieving the rolling codes :") + r = s.get(f"{base_url}/remote/{taskId}") + result = r.json() + + rollingCodes = result["remoteData"]["rollingCodes"] + + print("Preparing PandwaRF for TX") + d.setModeIDLE() + d.txSetup(FREQ, MOD_ASK_OOK, DATARATE) + d.setAmpMode(RF_TX_POWER_AMPLIFIER_ACTION_ON) + d.setModeTX() + + for rollingCode in rollingCodes: + syncCounter = rollingCode["syncCounter"] + button = rollingCode["button"] + dataHex = rollingCode["dataHex"] + fullMsgHex = rollingCode["fullMsgHex"] + data = dataHex + if not data: + data = fullMsgHex + + data = bytes.fromhex(data) + print(f"Sending rolling code {syncCounter} of button {button}") + d.RFxmit(data) + time.sleep(2) + + if rollingCodes == []: + print("Unable to generate rolling code") + + d.setModeIDLE() From a7e989124ba3b204be21bc6d996fdcc7580187dc Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Tue, 8 Feb 2022 18:01:58 +0100 Subject: [PATCH 15/31] Docker: Move dependencies to requirements.txt --- Dockerfile | 10 ++++------ requirements.txt | 2 ++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index c4248d8..cc01fd4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,8 @@ FROM python:3.9.10 -WORKDIR /pandwarf - -RUN apt update && apt install -y usbutils ffmpeg && pip install pyside2 - -COPY . ./rfcat -RUN cd rfcat && python setup.py install +WORKDIR /pandwarf/rfcat +COPY . . +RUN apt update && apt install -y usbutils ffmpeg && pip install -r requirements.txt +RUN python setup.py install CMD [ "bash" ] diff --git a/requirements.txt b/requirements.txt index 1a10931..291cd06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,5 @@ future ipython pyserial numpy +requests +pyside2 \ No newline at end of file From 7da98574a2816967ea6b8dd63eebefd72f3632c7 Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Wed, 9 Feb 2022 09:55:46 +0100 Subject: [PATCH 16/31] Move example scripts --- README.md | 2 +- {rflib/gollum_examples => examples}/kaiju_analysis.py | 0 .../gollum_examples => examples}/kaiju_generate_rolling_code.py | 2 +- .../kaiju_generate_rolling_code_2.py | 0 {rflib/gollum_examples => examples}/simple_bruteforce.py | 0 {rflib/gollum_examples => examples}/simple_bruteforce_legacy.py | 0 .../gollum_examples => examples}/simple_datarate_measurement.py | 0 {rflib/gollum_examples => examples}/simple_freqfinder.py | 0 {rflib/gollum_examples => examples}/simple_jamming.py | 0 {rflib/gollum_examples => examples}/simple_rx.py | 0 {rflib/gollum_examples => examples}/simple_tx.py | 0 11 files changed, 2 insertions(+), 2 deletions(-) rename {rflib/gollum_examples => examples}/kaiju_analysis.py (100%) rename {rflib/gollum_examples => examples}/kaiju_generate_rolling_code.py (98%) rename {rflib/gollum_examples => examples}/kaiju_generate_rolling_code_2.py (100%) rename {rflib/gollum_examples => examples}/simple_bruteforce.py (100%) rename {rflib/gollum_examples => examples}/simple_bruteforce_legacy.py (100%) rename {rflib/gollum_examples => examples}/simple_datarate_measurement.py (100%) rename {rflib/gollum_examples => examples}/simple_freqfinder.py (100%) rename {rflib/gollum_examples => examples}/simple_jamming.py (100%) rename {rflib/gollum_examples => examples}/simple_rx.py (100%) rename {rflib/gollum_examples => examples}/simple_tx.py (100%) diff --git a/README.md b/README.md index 2d9508a..b4c25d2 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ sudo udevadm control --reload-rules ## How to use RfCat python -After having followed the above guide on how to install the rfcat python client, you can simply type `rfcat -r` to interact with the Dongle. You can also create scripts to use the Dongle, and some example script for the `PandwaRF` are provided in [rflib/gollum_examples](./rflib/gollum_examples). +After having followed the above guide on how to install the rfcat python client, you can simply type `rfcat -r` to interact with the Dongle. You can also create scripts to use the Dongle, and some example script for the `PandwaRF` are provided in the [examples](./examples/) folder. You can also check the [legacy Using rfcat](#using-rfcat) for additional info. diff --git a/rflib/gollum_examples/kaiju_analysis.py b/examples/kaiju_analysis.py similarity index 100% rename from rflib/gollum_examples/kaiju_analysis.py rename to examples/kaiju_analysis.py diff --git a/rflib/gollum_examples/kaiju_generate_rolling_code.py b/examples/kaiju_generate_rolling_code.py similarity index 98% rename from rflib/gollum_examples/kaiju_generate_rolling_code.py rename to examples/kaiju_generate_rolling_code.py index 127499d..47a30cc 100644 --- a/rflib/gollum_examples/kaiju_generate_rolling_code.py +++ b/examples/kaiju_generate_rolling_code.py @@ -15,7 +15,7 @@ "brand": "MHouse", "model": "GTX", "serialNumberHex": "0x041170E", - "syncCounter": 1435, + "syncCounter": 1440, "button": 1, "seed": "", "numCodesRequested": 2 diff --git a/rflib/gollum_examples/kaiju_generate_rolling_code_2.py b/examples/kaiju_generate_rolling_code_2.py similarity index 100% rename from rflib/gollum_examples/kaiju_generate_rolling_code_2.py rename to examples/kaiju_generate_rolling_code_2.py diff --git a/rflib/gollum_examples/simple_bruteforce.py b/examples/simple_bruteforce.py similarity index 100% rename from rflib/gollum_examples/simple_bruteforce.py rename to examples/simple_bruteforce.py diff --git a/rflib/gollum_examples/simple_bruteforce_legacy.py b/examples/simple_bruteforce_legacy.py similarity index 100% rename from rflib/gollum_examples/simple_bruteforce_legacy.py rename to examples/simple_bruteforce_legacy.py diff --git a/rflib/gollum_examples/simple_datarate_measurement.py b/examples/simple_datarate_measurement.py similarity index 100% rename from rflib/gollum_examples/simple_datarate_measurement.py rename to examples/simple_datarate_measurement.py diff --git a/rflib/gollum_examples/simple_freqfinder.py b/examples/simple_freqfinder.py similarity index 100% rename from rflib/gollum_examples/simple_freqfinder.py rename to examples/simple_freqfinder.py diff --git a/rflib/gollum_examples/simple_jamming.py b/examples/simple_jamming.py similarity index 100% rename from rflib/gollum_examples/simple_jamming.py rename to examples/simple_jamming.py diff --git a/rflib/gollum_examples/simple_rx.py b/examples/simple_rx.py similarity index 100% rename from rflib/gollum_examples/simple_rx.py rename to examples/simple_rx.py diff --git a/rflib/gollum_examples/simple_tx.py b/examples/simple_tx.py similarity index 100% rename from rflib/gollum_examples/simple_tx.py rename to examples/simple_tx.py From a9338c07c77332cc1fc63bdedce7b7b2c2f3d6f3 Mon Sep 17 00:00:00 2001 From: Charlie Bailly Date: Wed, 9 Feb 2022 10:36:38 +0100 Subject: [PATCH 17/31] RfCat: Update init message --- rfcat | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/rfcat b/rfcat index befa9cf..5a846c2 100755 --- a/rfcat +++ b/rfcat @@ -18,18 +18,24 @@ logger = logging.getLogger(__name__) intro = """'RfCat, the greatest thing since Frequency Hopping!' - +Modified version for PandwaRF ! Research Mode: enjoy the raw power of rflib currently your environment has an object called "d" for dongle. this is how you interact with the rfcat dongle: - >>> d.ping() - >>> d.setFreq(433000000) - >>> d.setMdmModulation(MOD_ASK_OOK) - >>> d.makePktFLEN(250) - >>> d.RFxmit("HALLO") - >>> d.RFrecv() - >>> print(d.reprRadioConfig()) + +d.ping() +d.setModeIDLE() +d.setFreq(433920000) +d.setMdmModulation(MOD_ASK_OOK) +d.setMdmDRate(10_000) +d.makePktFLEN(250) +d.setModeTX() +d.RFxmit(b"HALLO") +d.setModeRX() +d.RFrecv() +print(d.reprRadioConfig()) +d.setModeIDLE() """ From 9eae4bc9824eb2d867a46e75160fd61d675c911a Mon Sep 17 00:00:00 2001 From: Clayton Smith Date: Sat, 16 Apr 2022 00:29:53 -0400 Subject: [PATCH 18/31] Update scripts to execute in Python 3 by default (#122) thanks @argilo ! --- firmware/bootloader_serial.py | 2 +- firmware/new_serial.py | 2 +- rfcat | 2 +- rflib/__init__.py | 4 ++-- rflib/cc111Xhparser.py | 2 +- rflib/ccspecan.py | 10 +++++----- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/firmware/bootloader_serial.py b/firmware/bootloader_serial.py index 69c9d28..9d00a15 100755 --- a/firmware/bootloader_serial.py +++ b/firmware/bootloader_serial.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 from __future__ import print_function diff --git a/firmware/new_serial.py b/firmware/new_serial.py index a9dc8f9..e134c86 100755 --- a/firmware/new_serial.py +++ b/firmware/new_serial.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from __future__ import print_function import sys diff --git a/rfcat b/rfcat index 5a846c2..04de040 100755 --- a/rfcat +++ b/rfcat @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from __future__ import print_function diff --git a/rflib/__init__.py b/rflib/__init__.py index 351f777..e468af9 100755 --- a/rflib/__init__.py +++ b/rflib/__init__.py @@ -1,4 +1,4 @@ -#!/usr/bin/env ipython -i --no-banner +#!/usr/bin/env ipython3 -i --no-banner from __future__ import print_function from __future__ import absolute_import @@ -94,7 +94,7 @@ def _stopSpecAn(self): ''' stop sending rfdata and return radio to original config ''' - self.send(APP_NIC, RFCAT_STOP_SPECAN, '') + self.send(APP_NIC, RFCAT_STOP_SPECAN, b'') self.radiocfg = self._specan_backup_radiocfg self.setRadioConfig() diff --git a/rflib/cc111Xhparser.py b/rflib/cc111Xhparser.py index ada88a6..93f3e75 100644 --- a/rflib/cc111Xhparser.py +++ b/rflib/cc111Xhparser.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ #include /* ------------------------------------------------------------------------------------------------ diff --git a/rflib/ccspecan.py b/rflib/ccspecan.py index a088399..c364eb3 100755 --- a/rflib/ccspecan.py +++ b/rflib/ccspecan.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Copyright 2012 atlas # @@ -64,7 +64,7 @@ def __init__(self, data, low_frequency, high_frequency, freq_step, delay, new_fr self._high_frequency = high_frequency self._freq_step = freq_step self._new_frame_callback = new_frame_callback - self._stop = False + self._stopping = False self._stopped = False def run(self): @@ -81,10 +81,10 @@ def run(self): frequency_axis = numpy.linspace(self._low_frequency, self._high_frequency, num=len(rssi_values), endpoint=True) self._new_frame_callback(numpy.copy(frequency_axis), numpy.copy(rssi_values)) - if self._stop: + if self._stopping: break else: - while not self._stop: + while not self._stopping: try: rssi_values, timestamp = self._data.recv(APP_SPECAN, SPECAN_QUEUE, 10000) rssi_values = [ (old_div((ord23(x)^0x80),2))-88 for x in rssi_values ] @@ -96,7 +96,7 @@ def run(self): self._data._stopSpecAn() def stop(self): - self._stop = True + self._stopping = True self.join(3.0) self._stopped = True From d7e1550433e5b8474324a81506c18fa6d14a8195 Mon Sep 17 00:00:00 2001 From: Clayton Smith Date: Sat, 16 Apr 2022 22:50:09 -0400 Subject: [PATCH 19/31] Work around a suspected SDCC bug (#123) SDCC >= 3.8.0 appears to have a bug which causes incorrect compilation of the "ep5.OUTlen += len;" line in chipcon_usb.c, corrupting the value of ep5.OUTlen. To work around the problem, we can mark the OUTlen field as volatile to prevent optimizations from being applied to it. --- README.md | 6 +++--- firmware/Makefile | 6 ------ firmware/include/chipcon_usb.h | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b4c25d2..ea3d62a 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ RfCat currently requires Python 2.7. the only suspected incompatibilities with ### Build requirements * Make -* SDCC (no later than 3.5.0, newer versions do not work) +* SDCC ## DEVELOPMENT @@ -230,10 +230,10 @@ You will also need to install the build requirements of python-usb, libusb-1.0.0 * python-usb * libusb-1.0.0 * make -* sdcc (no later than version 3.5.0, newer versions will not work) +* sdcc ``` -sudo apt install python-usb libusb-1.0.0 make sdcc=3.5.0 +sudo apt install python-usb libusb-1.0.0 make sdcc ``` For sdcc and its dependency, sdcc-libraries, you may need to download it from a earlier release's repository if you are on a newer version of Debian or Ubuntu such as: diff --git a/firmware/Makefile b/firmware/Makefile index 1e48f81..cfeef86 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -4,18 +4,12 @@ BOOTLOADER_SIZE = 0x1400 LDFLAGS_FLASH = --code-loc $(BOOTLOADER_SIZE) USB_DEVICE_SERIAL_NUMBER="`./new_serial.py`" -SDCCVEROK=$(shell expr `sdcc --version|grep SDCC|cut -d' ' -f 4` \<= 3.5.0) - CC=sdcc RFLIB_VERSION=`../revision.sh` CFLAGS=-Iinclude -DBUILD_VERSION=$(RFLIB_VERSION) CFLAGSold=--no-pack-iram $(CF) LFLAGS=--xram-loc 0xF000 -ifeq "$(SDCCVEROK)" "0" - CC=FUCKNO_use_SDCC_3.5.0_instead -endif - apps2531 = global.rel apps1111 = cc1111rf.rel global.rel cc1111_aes.rel appsimme = $(apps1111) immeio.rel immekeys.rel immefont.rel chipcon_dma.rel # immedisplay.rel immeterm.rel diff --git a/firmware/include/chipcon_usb.h b/firmware/include/chipcon_usb.h index 03df38d..a10932d 100644 --- a/firmware/include/chipcon_usb.h +++ b/firmware/include/chipcon_usb.h @@ -271,7 +271,7 @@ typedef struct { u8* INbuf; u16 INbytesleft; u8* OUTbuf; - u16 OUTlen; + volatile u16 OUTlen; u8 OUTapp; u8 OUTcmd; u16 OUTbytesleft; From dcc348afcb162df18c218e9d63e295c5fb35c298 Mon Sep 17 00:00:00 2001 From: Jimi Sanchez Date: Wed, 20 Apr 2022 11:01:50 -0400 Subject: [PATCH 20/31] Fixes bug caused by future import statement not being at the top (#124) --- rfcat_server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcat_server b/rfcat_server index 9408d18..b9bbf56 100644 --- a/rfcat_server +++ b/rfcat_server @@ -1,5 +1,6 @@ #!/usr/bin/python +from __future__ import print_function import re import os import sys @@ -8,7 +9,6 @@ import socket import threading from rflib import * -from __future__ import print_function DATA_START_IDX = 4 # without the app/cmd/len bytes, the data starts at byte 4 From 581ce04272be91177662eee5532c249852b858b3 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 16 Apr 2022 23:00:45 -0400 Subject: [PATCH 21/31] description_file --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 11e9ec4..ddb7da9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [metadata] -description-file = README.rst \ No newline at end of file +description_file = README.rst From f9632c3dca3697296ae78b2fc357a1b83ba3751a Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 20 Apr 2022 21:43:11 -0400 Subject: [PATCH 22/31] working on bits.py --- rflib/bits.py | 170 +++++++++++++++++++++++---------------------- tests/test_bits.py | 74 ++++++++++++++++++++ 2 files changed, 161 insertions(+), 83 deletions(-) create mode 100644 tests/test_bits.py diff --git a/rflib/bits.py b/rflib/bits.py index 70df54a..7eacd2c 100644 --- a/rflib/bits.py +++ b/rflib/bits.py @@ -8,8 +8,8 @@ import sys import struct -fmtsLSB = [None, "B", "H", ">I", ">I", ">Q", ">Q", ">Q", ">Q"] +fmtsLSB = [None, b"B", b"H", b">I", b">I", b">Q", b">Q", b">Q", b">Q"] sizes = [ 0, 1, 2, 4, 4, 8, 8, 8, 8] masks = [ (1<<(8*i))-1 for i in range(9) ] @@ -41,7 +41,7 @@ def ord23(thing): return thing -def strBitReverse(string): +def strBitReverse(string:bytes): # FIXME: this is really dependent upon python's number system. large strings will not convert well. # FIXME: break up array of 8-bit numbers and bit-swap in the array num = 0 @@ -49,9 +49,9 @@ def strBitReverse(string): # convert to MSB number for x in range(len(string)): ch = string[x] - #num |= (ord(ch)<<(8*x)) # this is LSB + #num |= (ord(ch:ch+1)<<(8*x)) # this is LSB num <<= 8 - num |= ord(ch) + num |= ch print(hex(num)) rnum = bitReverse(num, bits) @@ -63,8 +63,8 @@ def strBitReverse(string): out.append(correctbytes(rnum&0xff)) rnum >>= 8 out.reverse() - print(''.join(out).encode('hex')) - return ''.join(out) + print(b''.join(out)) + return b''.join(out) def strXorMSB(string, xorval, size): ''' @@ -73,7 +73,7 @@ def strXorMSB(string, xorval, size): ''' out = [] strlen = len(string) - string += "\x00" * sizes[size] + string += b"\x00" * sizes[size] for idx in range(0, strlen, size): tempstr = string[idx:idx+sizes[size]] @@ -82,7 +82,7 @@ def strXorMSB(string, xorval, size): temp &= masks[size] tempstr = struct.pack( fmtsMSB[size], temp )[-size:] out.append(tempstr) - return ''.join(out) + return b''.join(out) @@ -96,14 +96,17 @@ def bitReverse(num, bitcnt): return newnum def shiftString(string, bits): + ''' + In Python3, use bytes instead of a str + ''' carry = 0 news = [] for x in range(len(string)-1): - newc = ((ord(string[x]) << bits) + (ord(string[x+1]) >> (8-bits))) & 0xff - news.append("%c"%newc) - newc = (ord(string[-1])<> (8-bits))) & 0xff + news.append(b"%c"%newc) + newc = (ord23(string[-1])<2): + while (sbyts[0] == b'\xaa' and len(sbyts)>2): sbyts = sbyts[1:] - #print("sbyts: %s" % repr(sbyts)) + print("sbyts: %s" % repr(sbyts)) # now we look at the next 16 bits to narrow the possibilities to 8 # at this point we have no hints at bit-alignment aside from 0xaa vs 0x55 - dwbits, = struct.unpack(">H", sbyts[:2]) - #print("sbyts: %s" % repr(sbyts)) - #print("dwbits: %s" % repr(dwbits)) + dwbits, = struct.unpack(b">H", sbyts[:2]) + print("sbyts: %s" % repr(sbyts)) + print("dwbits: %s" % repr(dwbits)) if len(sbyts)>=3: bitcnt = 0 # bits1 = aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb # bits2 = bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - bits1, = struct.unpack(">H", sbyts[:2]) - bits1 = bits1 | (ord('\xaa') << 16) - bits1 = bits1 | (ord('\xaa') << 24) + bits1, = struct.unpack(b">H", sbyts[:2]) + bits1 = bits1 | (ord(b'\xaa') << 16) + bits1 = bits1 | (ord(b'\xaa') << 24) bits1 <<= 8 - bits1 |= (ord(sbyts[2]) ) - #print("bits: %x" % (bits1)) + bits1 |= sbyts[2] + print("bits: %x" % (bits1)) - bit = (5 * 8) - 2 # bytes times bits/byte #FIXME: MAGIC NUMBERS!? - while (bits1 & (3< 16: bit -= 2 - #print("bit = %d" % bit) + print("bit = %d" % bit) bits1 >>= (bit-16) #while (bits1 & 0x30000 != 0x20000): # now we align the end of the 101010 pattern with the beginning of the dword # bits1 >>= 2 @@ -216,9 +220,9 @@ def findSyncWordDoubled(byts): possDwords = [] # find the preamble (if any) bitoff = 0 - pidx = byts.find("\xaa\xaa") + pidx = byts.find(b"\xaa\xaa") if pidx == -1: - pidx = byts.find("\55\x55") + pidx = byts.find(b"\55\x55") bitoff = 1 if pidx == -1: return [] @@ -227,26 +231,26 @@ def findSyncWordDoubled(byts): byts = byts[pidx:] # find the definite end of the preamble (ie. it may be sooner, but we know this is the end) - while (byts[0] == ('\xaa', '\x55')[bitoff] and len(byts)>2): + while (byts[0] == (b'\xaa', b'\x55')[bitoff] and len(byts)>2): byts = byts[1:] # now we look at the next 16 bits to narrow the possibilities to 8 # at this point we have no hints at bit-alignment - dwbits, = struct.unpack(">H", byts[:2]) + dwbits, = struct.unpack(b">H", byts[:2]) if len(byts)>=5: bitcnt = 0 # bits1 = aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb # bits2 = bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - bits1, = struct.unpack(">H", byts[:2]) - bits1 = bits1 | (ord(('\xaa','\x55')[bitoff]) << 16) - bits1 = bits1 | (ord(('\xaa','\x55')[bitoff]) << 24) + bits1, = struct.unpack(b">H", byts[:2]) + bits1 = bits1 | (ord((b'\xaa',b'\x55')[bitoff]) << 16) + bits1 = bits1 | (ord((b'\xaa',b'\x55')[bitoff]) << 24) bits1 <<= 8 - bits1 |= (ord(byts[2]) ) + bits1 |= byts[2] bits1 >>= bitoff - bits2, = struct.unpack(">L", byts[:4]) + bits2, = struct.unpack(b">L", byts[:4]) bits2 <<= 8 - bits2 |= (ord(byts[4]) ) + bits2 |= byts[4] bits2 >>= bitoff @@ -295,7 +299,7 @@ def getBit(data, bit): idx = old_div(bit, 8) bidx = bit % 8 char = data[idx] - return (ord(char)>>(7-bidx)) & 1 + return (char>>(7-bidx)) & 1 @@ -376,7 +380,7 @@ def bitSectString(string, startbit, endbit): zeros = 0 entropy = [zeros, ones] - s = '' + s = b'' bit = startbit Bidx = old_div(bit, 8) @@ -384,9 +388,9 @@ def bitSectString(string, startbit, endbit): while bit < endbit: - byte1 = ord( string[Bidx] ) + byte1 = string[Bidx] try: - byte2 = ord( string[Bidx+1] ) + byte2 = string[Bidx+1] except IndexError: byte2 = 0 @@ -422,7 +426,7 @@ def genBitArray(string, startbit, endbit): s = [] for byte in binStr: - byte = ord(byte) + byte = byte for bitx in range(7, -1, -1): bit = (byte>>bitx) & 1 s.append(bit) @@ -431,36 +435,36 @@ def genBitArray(string, startbit, endbit): chars_top = [ - " ", #000 - " ", #001 - "^", #010 - "/", #011 - " ", #100 - " ", #101 - "\\",#110 - "-", #111 + b" ", #000 + b" ", #001 + b"^", #010 + b"/", #011 + b" ", #100 + b" ", #101 + b"\\",#110 + b"-", #111 ] chars_mid = [ - " ", #000 - "|", #001 - "#", #010 - " ", #011 - "|", #100 - "#", #101 - " ", #110 - " ", #110 + b" ", #000 + b"|", #001 + b"#", #010 + b" ", #011 + b"|", #100 + b"#", #101 + b" ", #110 + b" ", #110 ] chars_bot = [ - "-", #000 - "/", #001 - " ", #010 - " ", #011 - "\\",#100 - "V", #101 - " ", #110 - " ", #110 + b"-", #000 + b"/", #001 + b" ", #010 + b" ", #011 + b"\\",#100 + b"V", #101 + b" ", #110 + b" ", #110 ] @@ -479,7 +483,7 @@ def reprBitArray(bitAry, width=194): bits = 0 if bindex>0: bits += (expand[bindex-1]) << (2) - bits += (expand[bindex]) << (1) + bits += (expand[bindex]) << 1 if bindex < width-1: bits += (expand[bindex+1]) @@ -487,10 +491,10 @@ def reprBitArray(bitAry, width=194): mid.append( chars_mid[ bits ] ) bot.append( chars_bot[ bits ] ) - tops = "".join(top) - mids = "".join(mid) - bots = "".join(bot) - return "\n".join([tops, mids, bots]) + tops = b"".join(top) + mids = b"".join(mid) + bots = b"".join(bot) + return b"\n".join([tops, mids, bots]) def invertBits(data): output = [] @@ -498,7 +502,7 @@ def invertBits(data): off = 0 if ldata&1: - output.append( correctbytes( ord( data[0] ) ^ 0xff) ) + output.append( correctbytes( ord( data[0:1] ) ^ 0xff) ) off = 1 if ldata&2: @@ -536,7 +540,7 @@ def diff_manchester_decode(data, align=False): last = 0 obyte = 0 for bidx in range(len(data)): - byte = ord(data[bidx]) + byte = ord(data[bidx:bidx+1]) for y in range(6, -1, -2): if not syncd: diff = last & 1 @@ -579,7 +583,7 @@ def biphase_mark_coding_encode(data): out = [] last = 0 for bidx in range(len(data)): - byte = ord(data[bidx]) + byte = ord(data[bidx:bidx+1]) obyte = 0 for y in range(7, -1, -1): bit = (byte >> y) & 1 @@ -604,7 +608,7 @@ def manchester_decode(data, hilo=1): last = 0 obyte = 0 for bidx in range(len(data)): - byte = ord(data[bidx]) + byte = ord(data[bidx:bidx+1]) for y in range(7, -1, -1): bit = (byte >> y) & 1 @@ -639,7 +643,7 @@ def manchester_encode(data, hilo=1): out = [] for bidx in range(len(data)): - byte = ord(data[bidx]) + byte = ord(data[bidx:bidx+1]) obyte = 0 for bitx in range(7,-1,-1): bit = (byte>>bitx) & 1 @@ -670,7 +674,7 @@ def findManchester(data, minbytes=10): minbits = minbytes * 8 for bidx in range(len(data)): - byt = ord(data[bidx]) + byt = ord(data[bidx:bidx+1]) for btidx in range(0, 8, 2): # compare every other bits bit = (byt>>(8-btidx)) & 1 diff --git a/tests/test_bits.py b/tests/test_bits.py new file mode 100644 index 0000000..a3fa140 --- /dev/null +++ b/tests/test_bits.py @@ -0,0 +1,74 @@ +import unittest +import rflib.bits as rfbits + +class BitsTest(unittest.TestCase): + def test_bits(self): + # test correctbytes + rfbits.correctbytes(24) + + + + self.assertEqual( + rfbits.genBitArray(b'asdfasdfasdfaf', 20,23), + ([0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], 0.6666666666666666) + ) + + self.assertEqual( + rfbits.strBitReverse(b'asdf'), + b'f&\xce\x86' + ) + + self.assertEqual( + rfbits.strXorMSB(b'asdfasdfadfasfdsa', 0x1234, 2), + b'sGvRsGvRsPtUaRvGs4' + ) + + self.assertEqual( + rfbits.bitReverse(0x45, 16), + 0xa200 + ) + + self.assertEqual( + rfbits.shiftString(b'asdfasdf', 1), + b'\xc2\xe6\xc8\xcc\xc2\xe6\xc8\xcc' + ) + + self.assertEqual( + rfbits.shiftString(b'asdfasdf', 0), + b'asdfasdf' + ) + + self.assertEqual( + rfbits.shiftString(b'asdfasdf', 7), + b'\xb9\xb230\xb9\xb23\x00' + ) + + self.assertEqual( + rfbits.shiftString(b'asdfasdf', 8), + b'sdfasdf\x00' + ) + + self.assertEqual( + rfbits.whitenData(b'asdfasdfasdfasdf'), + b'\x9fn\x81\xf4e?9\nx\xda\xab\x0e4\x87\xc7' + ) + + + ''' + 128:def getNextByte_feedbackRegister7bitsLSB(): + 158:def findSyncWord(byts, sensitivity=4, minpreamble=2): + 218:def findSyncWordDoubled(byts): + 292:def visBits(data): + 297:def getBit(data, bit): + 305:def detectRepeatPatterns(data, size=64, minEntropy=.07): + 373:def bitSectString(string, startbit, endbit): + 419:def genBitArray(string, startbit, endbit): + 470:def reprBitArray(bitAry, width=194): + 498:def invertBits(data): + 525:def diff_manchester_decode(data, align=False): + 579:def biphase_mark_coding_encode(data): + 605:def manchester_decode(data, hilo=1): + 633:def manchester_encode(data, hilo=1): + 655:def findManchesterData(data, hilo=1): + 666:def findManchester(data, minbytes=10): + ''' From 9e70c361151eb61fb6aa9ba14aa6e7bfae690300 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 20 Apr 2022 22:57:42 -0400 Subject: [PATCH 23/31] lots of bits.py improvements and unittests --- rflib/bits.py | 102 ++++++++++++++++++++++++++------------------- tests/test_bits.py | 66 ++++++++++++++++++++++++----- 2 files changed, 114 insertions(+), 54 deletions(-) diff --git a/rflib/bits.py b/rflib/bits.py index 7eacd2c..294af13 100644 --- a/rflib/bits.py +++ b/rflib/bits.py @@ -1,10 +1,4 @@ -from __future__ import print_function -from __future__ import division - -from builtins import hex -from builtins import range -from builtins import bytes -from past.utils import old_div +from binascii import hexlify import sys import struct @@ -164,10 +158,14 @@ def findSyncWord(byts, sensitivity=4, minpreamble=2): # find the preamble (if any) while True: # keep searching through string until we don't find any more preamble bits to pick on sbyts = byts - pidx = byts.find(b"\xaa"*minpreamble) - # FIXME: what about alternating \x55\x55 and \xaa\xaa preambles (for diff signals?) - if pidx == -1: - pidx = byts.find(b"\x55"*minpreamble) + pidx_aa = byts.find(b"\xaa"*minpreamble) + pidx_55 = byts.find(b"\x55"*minpreamble) + if -1 in (pidx_aa, pidx_55): + pidx = max(pidx_aa, pidx_55) + else: + pidx = min(pidx_aa, pidx_55) + + if pidx == pidx_55: byts = shiftString(byts, 1) if pidx == -1: @@ -175,18 +173,25 @@ def findSyncWord(byts, sensitivity=4, minpreamble=2): # chop off the nonsense before the preamble sbyts = byts[pidx:] - print("sbyts: %s" % repr(sbyts)) + #print("sbyts: %s" % repr(sbyts)) # find the definite end of the preamble (ie. it may be sooner, but we know this is the end) - while (sbyts[0] == b'\xaa' and len(sbyts)>2): + #print("sbyts[0]: %x" % sbyts[0]) + while (sbyts[0] == 0xaa and len(sbyts)>2): + #print("... trimming 1 byte") sbyts = sbyts[1:] + + # slow trimming + while (sbyts[0] & 0xc0 == 0xa0 and len(sbyts) > 2): + #print("... slow trimming") + byts = shiftString(byts, 2) - print("sbyts: %s" % repr(sbyts)) + #print("sbyts(post front trim): %s" % repr(sbyts)) # now we look at the next 16 bits to narrow the possibilities to 8 # at this point we have no hints at bit-alignment aside from 0xaa vs 0x55 dwbits, = struct.unpack(b">H", sbyts[:2]) - print("sbyts: %s" % repr(sbyts)) - print("dwbits: %s" % repr(dwbits)) + #print("sbyts: %s" % repr(sbyts)) + #print("dwbits: %x" % (dwbits)) if len(sbyts)>=3: bitcnt = 0 # bits1 = aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb @@ -196,12 +201,12 @@ def findSyncWord(byts, sensitivity=4, minpreamble=2): bits1 = bits1 | (ord(b'\xaa') << 24) bits1 <<= 8 bits1 |= sbyts[2] - print("bits: %x" % (bits1)) + #print("bits: %x" % (bits1)) bit = (5 * 8) - 2 # bytes times bits/byte: 5bytes * 8 bits -2 (for what we're checking) while (bits1 & (3< 16: bit -= 2 - print("bit = %d" % bit) + #print("bit = %d" % bit) bits1 >>= (bit-16) #while (bits1 & 0x30000 != 0x20000): # now we align the end of the 101010 pattern with the beginning of the dword # bits1 >>= 2 @@ -210,28 +215,39 @@ def findSyncWord(byts, sensitivity=4, minpreamble=2): bitcount = min( 2 * sensitivity, 17 ) for frontbits in range( bitcount ): # with so many bit-inverted systems, let's not assume we know anything about the bit-arrangement. \x55\x55 could be a perfectly reasonable preamble. poss = (bits1 >> frontbits) & 0xffff + #print("poss (%x >> %d): %x" % (bits1, frontbits, poss)) if not poss in possDwords: possDwords.append(poss) byts = byts[pidx+1:] return possDwords -def findSyncWordDoubled(byts): - possDwords = [] +def findSyncWordDoubled(byts, sensitivity=4, minpreamble=2): + possDwords = [] + + while True: # keep searching through string until we don't find any more preamble bits to pick on # find the preamble (if any) bitoff = 0 - pidx = byts.find(b"\xaa\xaa") - if pidx == -1: - pidx = byts.find(b"\55\x55") + + pidx_aa = byts.find(b"\xaa"*minpreamble) + pidx_55 = byts.find(b"\x55"*minpreamble) + if -1 in (pidx_aa, pidx_55): + pidx = max(pidx_aa, pidx_55) + else: + pidx = min(pidx_aa, pidx_55) + + if pidx == pidx_55: bitoff = 1 - if pidx == -1: + + elif pidx == -1: return [] + # chop off the nonsense before the preamble byts = byts[pidx:] # find the definite end of the preamble (ie. it may be sooner, but we know this is the end) - while (byts[0] == (b'\xaa', b'\x55')[bitoff] and len(byts)>2): + while (byts[0] == (0xaa, 0x55)[bitoff] and len(byts)>2): byts = byts[1:] # now we look at the next 16 bits to narrow the possibilities to 8 @@ -242,8 +258,8 @@ def findSyncWordDoubled(byts): # bits1 = aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbb # bits2 = bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb bits1, = struct.unpack(b">H", byts[:2]) - bits1 = bits1 | (ord((b'\xaa',b'\x55')[bitoff]) << 16) - bits1 = bits1 | (ord((b'\xaa',b'\x55')[bitoff]) << 24) + bits1 = bits1 | (((0xaa, 0x55)[bitoff]) << 16) + bits1 = bits1 | (((0xaa, 0x55)[bitoff]) << 24) bits1 <<= 8 bits1 |= byts[2] bits1 >>= bitoff @@ -288,23 +304,18 @@ def findSyncWordDoubled(byts): possDwords.reverse() return possDwords -#def test(): - -def visBits(data): - pass - - def getBit(data, bit): - idx = old_div(bit, 8) + idx = bit // 8 bidx = bit % 8 char = data[idx] return (char>>(7-bidx)) & 1 -def detectRepeatPatterns(data, size=64, minEntropy=.07): - #FIXME: convert strings to bit arrays before comparing. +def detectRepeatPatterns(data:bytes, size=64, minEntropy=.07): + #FIXME: convert bytes to bit arrays before comparing. + patterns = [] c1 = 0 c2 = 0 d1 = 0 @@ -363,13 +374,16 @@ def detectRepeatPatterns(data, size=64, minEntropy=.07): bitSection, ent = bitSectString(data, s1, s1+length) if ent > minEntropy: print("success:") - print(" * bit idx1: %4d (%4d bits) - '%s' %s" % (s1, length, bin(d1), bitSection.encode("hex"))) + print(" * bit idx1: %4d (%4d bits) - '%s' %s" % (s1, length, bin(d1), hexlify(bitSection))) print(" * bit idx2: %4d (%4d bits) - '%s'" % (s2, length, bin(d2))) + patterns.append((s1, s2, length, d1)) #else: # print(" * idx1: %d - '%s' * idx2: %d - '%s'" % (p1, d1, p2, d2)) p2 += 1 p1 += 1 + return patterns + def bitSectString(string, startbit, endbit): ''' @@ -383,7 +397,7 @@ def bitSectString(string, startbit, endbit): s = b'' bit = startbit - Bidx = old_div(bit, 8) + Bidx = bit // 8 bidx = (bit % 8) while bit < endbit: @@ -411,7 +425,7 @@ def bitSectString(string, startbit, endbit): s += correctbytes(byte) - ent = old_div((min(entropy)+1.0), (max(entropy)+1)) + ent = (min(entropy)+1.0) / (max(entropy)+1) #print("entropy: %f" % ent) return (s, ent) @@ -514,13 +528,13 @@ def invertBits(data): # output.append( struct.pack( "= minbits: - lenbytes = (old_div(lastCount, 8)) + lenbytes = lastCount // 8 lenbits = lastCount % 8 startbyte = bidx - lenbytes if lenbits > btidx: diff --git a/tests/test_bits.py b/tests/test_bits.py index a3fa140..f8cb2b3 100644 --- a/tests/test_bits.py +++ b/tests/test_bits.py @@ -48,23 +48,69 @@ def test_bits(self): b'sdfasdf\x00' ) - self.assertEqual( + self.assertEqual( # this tests getNextByte_feedbackRegister7bitsLSB too rfbits.whitenData(b'asdfasdfasdfasdf'), b'\x9fn\x81\xf4e?9\nx\xda\xab\x0e4\x87\xc7' ) + byts = b'\x0a\xaa\xaa\xaa\x34\x35\x34\x36sdfasdfasdfasdfasdfasdfasdfasdfasdfasdf\xaa\xaa\xaa\xaa45\xc6\xdfasdfasdf' + self.assertEqual( + rfbits.findSyncWord(byts, sensitivity=4, minpreamble=2), + [0xd0d4, + 0x686a, + 0x3435, + 0x1a1a, + 0x8d0d, + 0x4686, + 0xa343, + 0x51a1, + 0xd0d7, + 0x686b] + ) + + self.assertEqual( + rfbits.findSyncWordDoubled(b'\x0a\xaa\xaa\xaa\x34\x35\x34\x35sdfasdfasdfasdfasdfasdfasdfasdfasdfasdf\xaa\xaa\xaa\xaa45\xc6\xdfasdfasdf'), + [0x34353435] + ) + + self.assertEqual( + rfbits.genBitArray(b'\x0a\xaa\xaa\xaa\x34\x35\x34\x35sdfasdfasdfasdfasdfasdfa', 3, 16), + ([0x0, + 0x1, + 0x0, + 0x1, + 0x0, + 0x1, + 0x0, + 0x1, + 0x0, + 0x1, + 0x0, + 0x1, + 0x0, + 0x0, + 0x0, + 0x0], + 1.0) + ) + + self.assertEqual( + rfbits.reprBitArray(rfbits.genBitArray(b'\x0a\xaa\xaa\xaa\x34\x35\x34\x35sdfasdfasdfasdfasdfasdfasdfasdfasdfasdf\xaa\xaa\xaa\xaa45\xc6\xdfasdfasdf', 45, 122)[0]), + b'/-\\ /-\\ /--\\ /\\ /---\\ /-\\ /-\\ /-----\\ /---\\ /---\\ /-\\ /---\\ /---\\ /---\\ /\\ /-----\\ /---\\ /---\\ /\\ /---\\ /---\\ /\\ \n || | | | | | | || || || | | || | | | | | | | | | | | | | | || | | | | | | | | | \n \\/ \\---/ \\-/ \\--------/ \\/ \\/ \\/ \\---/ \\/ \\---/ \\-----/ \\--/ \\---/ \\--------/ \\-/ \\---/ \\/ \\---/ \\-----/ \\---/ \\---/ \\------' + ) + + self.assertEqual( + rfbits.invertBits(b'\x0a\xaa\xaa\xaa'), + b'\xf5UUU' + ) + + self.assertEqual( + rfbits.detectRepeatPatterns(b'asdfasdfasdfasdfasdfasdfasdfasdf'), + [(0x0, 0x80, 0x80, 0xc2e6c8ccc2e6c8cc)] + ) ''' - 128:def getNextByte_feedbackRegister7bitsLSB(): - 158:def findSyncWord(byts, sensitivity=4, minpreamble=2): - 218:def findSyncWordDoubled(byts): - 292:def visBits(data): - 297:def getBit(data, bit): 305:def detectRepeatPatterns(data, size=64, minEntropy=.07): - 373:def bitSectString(string, startbit, endbit): - 419:def genBitArray(string, startbit, endbit): - 470:def reprBitArray(bitAry, width=194): - 498:def invertBits(data): 525:def diff_manchester_decode(data, align=False): 579:def biphase_mark_coding_encode(data): 605:def manchester_decode(data, hilo=1): From 4f0bba5463a88f1c6828974f93ae7d03c17f1654 Mon Sep 17 00:00:00 2001 From: Clayton Smith Date: Fri, 22 Apr 2022 14:07:15 -0400 Subject: [PATCH 24/31] Fix Python 3 issues in rfcat_bootloader (#125) firmware bootloader is fast again! thank you @argilo --- CC-Bootloader/rfcat_bootloader | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CC-Bootloader/rfcat_bootloader b/CC-Bootloader/rfcat_bootloader index 11cfd67..92e1fb8 100755 --- a/CC-Bootloader/rfcat_bootloader +++ b/CC-Bootloader/rfcat_bootloader @@ -20,10 +20,10 @@ def download_code(ihx_file, serial_port): for line in ihx_file.readlines(): record_type = int(line[7:9], 16) if (record_type == 0x00): - print("Writing", line[:-1], end='') + print("Writing", line[:-1].decode(), end='') serial_port.write(line) rc = serial_port.read() - print(" RC = %r" % rc, end='') + print(" RC = " + rc.decode(), end='') if rc in bootloader_error_codes: print("(%s)" % bootloader_error_codes[rc]) else: @@ -32,7 +32,7 @@ def download_code(ihx_file, serial_port): print("Error downloading code!") return False else: - print("Skipping non data record: '%s'" % line[:-1]) + print("Skipping non data record: '{}'".format(line[:-1].decode())) return True def verify_code(ihx_file, serial_port): @@ -51,7 +51,7 @@ def verify_code(ihx_file, serial_port): read_data = read_data.strip() if not read_data: continue - if not read_data == ":00000001FF": + if not read_data == b":00000001FF": can_read_any = True else: break @@ -68,7 +68,7 @@ def verify_code(ihx_file, serial_port): verify_data= b'' for read_data in serial_port: read_data= read_data.strip() - if (not data or read_data == ":00000001FF"): + if (not data or read_data == b":00000001FF"): break # strip header and checksum verify_data += read_data[9:-2] @@ -79,7 +79,7 @@ def verify_code(ihx_file, serial_port): exit(1) sys.stdout.flush() else: - print("Skipping non data record: '%s'" % line[:-1]) + print("\nSkipping non data record: '{}'".format(line[:-1].decode())) return True def run_user_code(serial_port): @@ -90,7 +90,7 @@ def run_user_code(serial_port): def reset_bootloader(serial_port): serial_port.write(b":00000022DE\n") rc = serial_port.read() - print("RC = %r" % rc, end='') + print("RC = " + rc.decode(), end='') if rc in bootloader_error_codes: print("(%s)" % bootloader_error_codes[rc]) else: @@ -103,7 +103,7 @@ def reset_bootloader(serial_port): def erase_all_user(serial_port): serial_port.write(b":00000023DD\n") rc = serial_port.read() - print("RC = %r" % rc, end='') + print("RC = " + rc.decode(), end='') if rc in bootloader_error_codes: print("(%s)" % bootloader_error_codes[rc]) else: @@ -117,7 +117,7 @@ def erase_user_page(serial_port, page): chksum = (0xDB + 0x100 - page) & 0xFF serial_port.write(b":01000024%02X%02X\n" % (page, chksum)) rc = serial_port.read() - print("RC = %r" % rc, end='') + print("RC = " + rc.decode(), end='') if rc in bootloader_error_codes: print("(%s)" % bootloader_error_codes[rc]) else: @@ -140,11 +140,11 @@ def do_flash_read(serial_port, start_addr, length): def flash_read(ihx_file, serial_port, start_addr, length): do_flash_read(serial_port, start_addr, length) for line in serial_port: - if not line == "\n": + if not line == b"\n": if(ihx_file): ihx_file.write(line) else: - print(line,) + print(line.decode(), end='') if (line == b":00000001FF\n"): break From db60441df7c9c572c1c51480be72463110305552 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 11 Aug 2022 13:16:37 -0400 Subject: [PATCH 25/31] fix a few Python3 migration oversights in bootloader and intelhex --- VERSION | 2 +- firmware/bootloader_serial.py | 7 ++++--- rflib/intelhex.py | 22 +++++++++++----------- rflib/rflib_version.py | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/VERSION b/VERSION index 158c747..7bc1c40 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.5 +1.9.6 diff --git a/firmware/bootloader_serial.py b/firmware/bootloader_serial.py index 9d00a15..ef858d3 100755 --- a/firmware/bootloader_serial.py +++ b/firmware/bootloader_serial.py @@ -11,14 +11,15 @@ ser = int(sys.argv[1]) else: try: - ser = int(file(".serial", 'rb').read(), 16) + 1 - except IOError: + serbytes = open(".serial", 'rb').read() + ser = int(serbytes, 16) + 1 + except (IOError, ValueError): ser = 0 print(("[--- new serial number: %.4x ---]" % ser), file=sys.stderr) if WRITEBACK: - file(".serial", 'wb').write("%.13x" % ser) + open(".serial", 'wb').write(b"%.13x" % ser) sertxt = '' sertmp = "%.13x" % ser diff --git a/rflib/intelhex.py b/rflib/intelhex.py index 66ae02c..184ed54 100644 --- a/rflib/intelhex.py +++ b/rflib/intelhex.py @@ -507,13 +507,13 @@ def write_hex_file(self, f, write_start_addr=True): # timeit shows that using hexstr.translate(table) # is faster than hexstr.upper(): # 0.452ms vs. 0.652ms (translate vs. upper) - table = ''.join(correctbytes(i).upper() for i in range(256)) + table = b''.join(correctbytes(i).upper() for i in range(256)) # start address record if any if self.start_addr and write_start_addr: keys = list(self.start_addr.keys()) keys.sort() - bin = array('B', '\0'*9) + bin = array('B', b'\0'*9) if keys == ['CS','IP']: # Start Segment Address Record bin[0] = 4 # reclen @@ -527,7 +527,7 @@ def write_hex_file(self, f, write_start_addr=True): bin[6] = (ip >> 8) & 0x0FF bin[7] = ip & 0x0FF bin[8] = (-sum(bin)) & 0x0FF # chksum - fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n') + fwrite(b':' + hexlify(bin.tostring()).translate(table) + b'\n') elif keys == ['EIP']: # Start Linear Address Record bin[0] = 4 # reclen @@ -540,7 +540,7 @@ def write_hex_file(self, f, write_start_addr=True): bin[6] = (eip >> 8) & 0x0FF bin[7] = eip & 0x0FF bin[8] = (-sum(bin)) & 0x0FF # chksum - fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n') + fwrite(b':' + hexlify(bin.tostring()).translate(table) + b'\n') else: if fclose: fclose() @@ -565,7 +565,7 @@ def write_hex_file(self, f, write_start_addr=True): while cur_addr <= maxaddr: if need_offset_record: - bin = array('B', '\0'*7) + bin = array('B', b'\0'*7) bin[0] = 2 # reclen bin[1] = 0 # offset msb bin[2] = 0 # offset lsb @@ -575,7 +575,7 @@ def write_hex_file(self, f, write_start_addr=True): bin[4] = bytes[0] # msb of high_ofs bin[5] = bytes[1] # lsb of high_ofs bin[6] = (-sum(bin)) & 0x0FF # chksum - fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n') + fwrite(b':' + hexlify(bin.tostring()).translate(table) + b'\n') while True: # produce one record @@ -597,7 +597,7 @@ def write_hex_file(self, f, write_start_addr=True): else: chain_len = 1 # real chain_len - bin = array('B', '\0'*(5+chain_len)) + bin = array('B', b'\0'*(5+chain_len)) bytes = divmod(low_addr, 256) bin[1] = bytes[0] # msb of low_addr bin[2] = bytes[1] # lsb of low_addr @@ -611,7 +611,7 @@ def write_hex_file(self, f, write_start_addr=True): bin = bin[:5+i] bin[0] = chain_len bin[4+chain_len] = (-sum(bin)) & 0x0FF # chksum - fwrite(':' + hexlify(bin.tostring()).translate(table) + '\n') + fwrite(':' + hexlify(bin.tostring()).translate(table).decode('latin1') + '\n') # adjust cur_addr/cur_ix cur_ix += chain_len @@ -647,7 +647,7 @@ def gets(self, addr, length): """Get string of bytes from given address. If any entries are blank from addr through addr+length, a NotEnoughDataError exception will be raised. Padding is not used.""" - a = array('B', '\0'*length) + a = array('B', b'\0'*length) try: for i in range(length): a[i] = self._buf[addr+i] @@ -659,7 +659,7 @@ def puts(self, addr, s): """Put string of bytes at given address. Will overwrite any previous entries. """ - a = array('B', s) + a = array('B', bytes(s, 'latin1')) for i in range(len(s)): self._buf[addr+i] = a[i] @@ -943,7 +943,7 @@ def _from_bytes(bytes): # calculate checksum s = (-sum(bytes)) & 0x0FF bin = array('B', bytes + [s]) - return ':' + hexlify(bin.tostring()).upper() + return b':' + hexlify(bin.tostring()).upper() _from_bytes = staticmethod(_from_bytes) def data(offset, bytes): diff --git a/rflib/rflib_version.py b/rflib/rflib_version.py index 48c21ba..63fdde8 100644 --- a/rflib/rflib_version.py +++ b/rflib/rflib_version.py @@ -1 +1 @@ -RFLIB_VERSION=606 +RFLIB_VERSION=624 From a03781fb130d02f9a3478cc8162e7d6c41feefb4 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 11 Aug 2022 13:29:25 -0400 Subject: [PATCH 26/31] touch up installer to allow for automated install of Pyside2 by choosing "pip install rfcat[specan] --- rflib/rflib_version.py | 2 +- setup.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/rflib/rflib_version.py b/rflib/rflib_version.py index 63fdde8..8bc8fd8 100644 --- a/rflib/rflib_version.py +++ b/rflib/rflib_version.py @@ -1 +1 @@ -RFLIB_VERSION=624 +RFLIB_VERSION=625 diff --git a/setup.py b/setup.py index f9d13c6..ca33747 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,11 @@ def readme(): ext_modules = mods, scripts = scripts, install_requires = requirements, + extras_require={ + 'specan': [ + 'PySide2==5.12.0', + ] + }, classifiers = [ # How mature is this project? Common values are # 3 - Alpha From 6a472e13278d185cc5c83a44cec80c6e76c4c45f Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 20 Dec 2022 14:30:50 -0500 Subject: [PATCH 27/31] rfcat_bootloader Py3 lingering bug --- CC-Bootloader/rfcat_bootloader | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CC-Bootloader/rfcat_bootloader b/CC-Bootloader/rfcat_bootloader index 92e1fb8..f66892f 100755 --- a/CC-Bootloader/rfcat_bootloader +++ b/CC-Bootloader/rfcat_bootloader @@ -61,7 +61,7 @@ def verify_code(ihx_file, serial_port): if can_read_any: block_length= length else: - block_length= ((length / 16) + 1) * 16 + block_length= ((length // 16) + 1) * 16 print("\r ", end='') print("\rVerifying %04d bytes at address: %04X" % (length, start_addr), end='') do_flash_read(serial_port, start_addr, block_length) From 246aaa4ec1d95f54b5955cffa191533109b73b12 Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 20 Dec 2022 14:34:31 -0500 Subject: [PATCH 28/31] touch version upgrade --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7bc1c40..fee0a27 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.6 +1.9.7 From 31fa5f2bc8380100acb32a6ce5504d77b9030b5e Mon Sep 17 00:00:00 2001 From: atlas0fd00m Date: Thu, 2 Nov 2023 00:36:41 -0400 Subject: [PATCH 29/31] Update rfcat --- rfcat | 1 - 1 file changed, 1 deletion(-) diff --git a/rfcat b/rfcat index 04de040..b205b98 100644 --- a/rfcat +++ b/rfcat @@ -18,7 +18,6 @@ logger = logging.getLogger(__name__) intro = """'RfCat, the greatest thing since Frequency Hopping!' -Modified version for PandwaRF ! Research Mode: enjoy the raw power of rflib currently your environment has an object called "d" for dongle. this is how From e5b734159597d36e1d7d2a8c518dd0e49217c2ad Mon Sep 17 00:00:00 2001 From: atlas0fd00m Date: Thu, 2 Nov 2023 00:36:47 -0400 Subject: [PATCH 30/31] Update rfcat --- rfcat | 1 - 1 file changed, 1 deletion(-) diff --git a/rfcat b/rfcat index b205b98..af4825a 100644 --- a/rfcat +++ b/rfcat @@ -51,7 +51,6 @@ def getDongleClass(idx: int): print("PandwaRF (non Rogue) Detected") return PandwaRF - print("Dongle is not a PandwaRF") return RfCat From 830335235a30d811f10f3155dbe78f08f2c9e614 Mon Sep 17 00:00:00 2001 From: atlas0fd00m Date: Thu, 2 Nov 2023 00:37:02 -0400 Subject: [PATCH 31/31] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index ea3d62a..4c016ad 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Welcome to the official rfcat project that works for `PandwaRF` and `PandwaRF Rogue`, modified to also work with legacy rfcat dongles such as `Yard Stick One`. -See the legacy readme [here](#legacy-readme). +Welcome to the official RfCat project! Enabling powerful RF tools such as `PandwaRF` / `PandwaRF Rogue`, `Yard Stick One`, and original Chronos and TI EMK dongles (and more) ## How to install RfCat python