diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cc01fd4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.9.10 + +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/README.md b/README.md index d157159..4c016ad 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,74 @@ +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 + +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 the [examples](./examples/) folder. +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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6c77dcd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: "3.3" + +services: + rfcat_image: + build: . + volumes: + - /dev/bus/usb:/dev/bus/usb + privileged: true + 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" diff --git a/examples/kaiju_analysis.py b/examples/kaiju_analysis.py new file mode 100644 index 0000000..f3234f8 --- /dev/null +++ b/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/examples/kaiju_generate_rolling_code.py b/examples/kaiju_generate_rolling_code.py new file mode 100644 index 0000000..47a30cc --- /dev/null +++ b/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": 1440, + "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() diff --git a/examples/kaiju_generate_rolling_code_2.py b/examples/kaiju_generate_rolling_code_2.py new file mode 100644 index 0000000..2e9e663 --- /dev/null +++ b/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() diff --git a/examples/simple_bruteforce.py b/examples/simple_bruteforce.py new file mode 100644 index 0000000..ce90bf1 --- /dev/null +++ b/examples/simple_bruteforce.py @@ -0,0 +1,32 @@ +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 + + symbolLength = 3 + 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, symbolLength, encSymbolZero, encSymbolOne, encSymbolTwo, encSymbolThree, syncWordSize, syncWord, tailWordSize, tailWord, functionSize, functionMask, functionValue) + + d.setModeIDLE() \ No newline at end of file diff --git a/examples/simple_bruteforce_legacy.py b/examples/simple_bruteforce_legacy.py new file mode 100644 index 0000000..204e08f --- /dev/null +++ b/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 diff --git a/examples/simple_datarate_measurement.py b/examples/simple_datarate_measurement.py new file mode 100644 index 0000000..04e07e0 --- /dev/null +++ b/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/examples/simple_freqfinder.py b/examples/simple_freqfinder.py new file mode 100644 index 0000000..c04a13d --- /dev/null +++ b/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/examples/simple_jamming.py b/examples/simple_jamming.py new file mode 100644 index 0000000..c9bd66c --- /dev/null +++ b/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/examples/simple_rx.py b/examples/simple_rx.py new file mode 100644 index 0000000..2033fad --- /dev/null +++ b/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/examples/simple_tx.py b/examples/simple_tx.py new file mode 100644 index 0000000..7c6ef52 --- /dev/null +++ b/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() 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 diff --git a/rfcat b/rfcat old mode 100755 new mode 100644 index 1a56272..af4825a --- 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 * @@ -15,21 +18,42 @@ logger = logging.getLogger(__name__) intro = """'RfCat, the greatest thing since Frequency Hopping!' - 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(b"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() """ +def getDongleClass(idx: int): + 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") + return PandwaRFRogue + elif dongle.idProduct == PANDWARF_PRODUCT_ID: + print("PandwaRF (non Rogue) Detected") + return PandwaRF + + return RfCat + + if __name__ == "__main__": import argparse @@ -46,23 +70,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_gollum.py b/rflib/chipcon_gollum.py new file mode 100644 index 0000000..280b109 --- /dev/null +++ b/rflib/chipcon_gollum.py @@ -0,0 +1,779 @@ +#!/usr/bin/env ipython +import struct +from typing import Optional +from . import RFCAT_START_SPECAN, RFCAT_STOP_SPECAN, 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, USB_TX_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 + + 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 + + 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) + + def _stopSpecAn(self): + # Overload because it is wrongly implemented in rfcat + self._sendRfcatStopSpecan() + + + # 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 _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") + + + 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() + + + +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(" 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 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 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