├── rf4ce ├── autognuradio │ ├── __init__.py │ └── ieee802_15_4_oqpsk_phy.py ├── packetprocessor.py ├── __init__.py ├── linkconfig.py ├── rf4ce.py └── radio.py ├── LICENSE ├── .gitignore ├── README.md ├── sniffer.py ├── pairing_sniffer.py └── injector.py /rf4ce/autognuradio/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rf4ce/packetprocessor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Packet processor tread. Used to process the incoming RF4CE packets. 4 | """ 5 | 6 | import threading 7 | import Queue 8 | 9 | from linkconfig import LinkConfig 10 | from rf4ce import Rf4ceNode, Rf4ceFrame 11 | 12 | 13 | class PacketProcessor(threading.Thread): 14 | 15 | """Packet processor thread""" 16 | 17 | def __init__(self): 18 | threading.Thread.__init__(self) 19 | self.q = Queue.Queue() 20 | self.stopped = False 21 | 22 | def stop(self): 23 | self.stopped = True 24 | 25 | def run(self): 26 | while not self.stopped: 27 | try: 28 | data = self.q.get(timeout=1) 29 | except Queue.Empty: 30 | continue 31 | self.process(data) 32 | 33 | def feed(self, data): 34 | """Adds packets to the queue""" 35 | self.q.put(data) 36 | 37 | def process(self, data): 38 | """This should process the incoming data""" 39 | pass 40 | -------------------------------------------------------------------------------- /rf4ce/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from rf4ce import Rf4ceNode, Rf4ceFrame, Rf4ceConstants, Rf4ceException 4 | from linkconfig import LinkConfig 5 | 6 | from scapy.all import Dot15d4FCS, Dot15d4Data, Raw 7 | 8 | # Keep scapy from parsing the RF4CE payload 9 | Dot15d4Data.payload_guess = [({}, Raw)] 10 | 11 | import scapy.config 12 | from scapy.themes import ColorOnBlackTheme 13 | scapy.config.conf.color_theme = ColorOnBlackTheme() 14 | 15 | 16 | # https://github.com/zeroSteiner/scapy-com/blob/79f55855a4d55b4eaba5370cb4c7b094ab956c79/scapy/layers/dot15d4.py#L856C1-L865C1 17 | def makeFCS(data): 18 | crc = 0 19 | for i in range(0, len(data)): 20 | c = ord(data[i]) 21 | q = (crc ^ c) & 15 #Do low-order 4 bits 22 | crc = (crc // 16) ^ (q * 4225) 23 | q = (crc ^ (c // 16)) & 15 #And high 4 bits 24 | crc = (crc // 16) ^ (q * 4225) 25 | return struct.pack('H", len(a)) + a) 97 | auth_data += pad128(plain_text) 98 | 99 | B = b'\x49' + nonce + struct.pack(">H", len(plain_text)) 100 | X = b'\x00' * 16 101 | X = self.E(strxor(X, B)) 102 | for i in range(1, len(auth_data) // 16 + 1): 103 | B = auth_data[(i-1)*16:i*16] 104 | X = self.E(strxor(X, B)) 105 | 106 | return X[:self.M] 107 | 108 | def cipher(self, plain_text, frame_control_value, frame_counter_value): 109 | nonce = self.gen_nonce(frame_counter_value) 110 | 111 | T = self.gen_auth(plain_text, frame_control_value, frame_counter_value) 112 | 113 | padded_data = pad128(plain_text) 114 | 115 | flags = b'\x01' 116 | A = [flags + nonce + struct.pack(">H", counter) for counter in range(len(padded_data)//16 + 1)] 117 | 118 | U = strxor(T, self.E(A[0])[:self.M]) 119 | 120 | ciphered_data = b'' 121 | for i in range(1, len(padded_data)//16 + 1): 122 | data_chunck = padded_data[(i-1)*16:i*16] 123 | ciphered_data += strxor(self.E(A[i]), data_chunck) 124 | 125 | ciphered_data = ciphered_data[:len(plain_text)] 126 | 127 | return ciphered_data + U 128 | 129 | def decipher(self, data, frame_control_value, frame_counter_value): 130 | 131 | nonce = self.gen_nonce(frame_counter_value) 132 | 133 | padded_C = pad128(data[:len(data)-self.M]) 134 | U = data[len(data)-self.M:] 135 | 136 | flags = b'\x01' 137 | A = [flags + nonce + struct.pack(">H", counter) for counter in range(len(padded_C)//16 + 1)] 138 | 139 | T = strxor(U, self.E(A[0])[:self.M]) 140 | 141 | plain_text = b'' 142 | for i in range(1, len(padded_C)//16 + 1): 143 | data_chunck = padded_C[(i-1)*16:i*16] 144 | plain_text += strxor(self.E(A[i]), data_chunck) 145 | 146 | plain_text = plain_text[:len(data)-self.M] 147 | 148 | if self.gen_auth(plain_text, frame_control_value, frame_counter_value) != T: 149 | raise Rf4ceException("Frame authentification error") 150 | 151 | return plain_text 152 | 153 | 154 | class Rf4ceFrame(object): 155 | 156 | """Describes a RF4CE frame""" 157 | 158 | def __init__(self): 159 | self.source = None 160 | self.destination = None 161 | self.frame_type = Rf4ceConstants.FRAME_TYPE_RESERVED 162 | self.frame_ciphered = False 163 | self.protocol_version = 1 164 | self.channel_designator = 0 165 | self.frame_counter = 0 166 | self.payload = None 167 | self.profile_indentifier = 0x1 168 | self.key = None 169 | 170 | def get_frame_control(self): 171 | """Generates the frame control byte from the frame's parameters""" 172 | frame_control = self.frame_type 173 | frame_control |= self.frame_ciphered << 2 174 | frame_control |= self.protocol_version << 3 175 | frame_control |= 1 << 5 176 | frame_control |= self.channel_designator << 6 177 | 178 | return frame_control 179 | 180 | def pack(self): 181 | """Returns a string representation of the RF4CE frame 182 | 183 | The string representation returned by this function can be 184 | encapsulated into a 802.15.4 packet. 185 | """ 186 | result = struct.pack("B", self.get_frame_control()) 187 | result += struct.pack("I", self.frame_counter) 188 | 189 | if self.frame_type == Rf4ceConstants.FRAME_TYPE_COMMAND: 190 | data = struct.pack("B", self.command) + self.payload 191 | if self.frame_ciphered: 192 | cipher = Rf4ceAES(self.key, self.source, self.destination) 193 | result += cipher.cipher(data, self.get_frame_control(), self.frame_counter) 194 | else: 195 | result += data 196 | 197 | elif self.frame_type == Rf4ceConstants.FRAME_TYPE_DATA: 198 | result += struct.pack("B", self.profile_indentifier) 199 | data = self.payload 200 | if self.frame_ciphered: 201 | cipher = Rf4ceAES(self.key, self.source, self.destination) 202 | result += cipher.cipher(data, self.get_frame_control(), self.frame_counter) 203 | else: 204 | result += data 205 | 206 | elif self.frame_type == Rf4ceConstants.FRAME_TYPE_VENDOR: 207 | result += struct.pack("B", self.profile_indentifier) 208 | result += struct.pack("H", self.vendor_indentifier) 209 | 210 | data = self.payload 211 | if self.frame_ciphered: 212 | cipher = Rf4ceAES(self.key, self.source, self.destination) 213 | result += cipher.cipher(data, self.get_frame_control(), self.frame_counter) 214 | else: 215 | result += data 216 | 217 | return result 218 | 219 | def parse_from_string(self, data, source, destination, key=None): 220 | """Parses a string representation of a RF4CE frame""" 221 | if key: 222 | self.key = binascii.unhexlify(key) 223 | 224 | self.source = source 225 | self.destination = destination 226 | 227 | frame_control = data[0] 228 | 229 | self.frame_type = frame_control & 0b11 230 | 231 | if self.frame_type == Rf4ceConstants.FRAME_TYPE_RESERVED: 232 | raise Rf4ceException("Unknown frame type") 233 | 234 | if (data[0] & (1 << 2)): 235 | self.frame_ciphered = True 236 | else: 237 | self.frame_ciphered = False 238 | 239 | self.protocol_version = (frame_control >> 3) & 0b11 240 | 241 | self.channel_designator = (frame_control >> 6) & 0b11 242 | 243 | self.frame_counter = struct.unpack("I", data[1:5])[0] 244 | 245 | if self.frame_type == Rf4ceConstants.FRAME_TYPE_DATA: 246 | self.data_frame_from_string(data) 247 | elif self.frame_type == Rf4ceConstants.FRAME_TYPE_COMMAND: 248 | self.command_frame_from_string(data) 249 | elif self.frame_type == Rf4ceConstants.FRAME_TYPE_VENDOR: 250 | self.data_frame_from_string(data, True) 251 | 252 | def data_frame_from_string(self, data, vendor_specific=False): 253 | """Parses a RF4CE data pyload from a string""" 254 | self.profile_indentifier = data[5] 255 | if vendor_specific: 256 | self.vendor_indentifier = struct.unpack("H", data[6:8])[0] 257 | raw_payload = data[8:] 258 | else: 259 | raw_payload = data[6:] 260 | 261 | if self.frame_ciphered: 262 | if not self.key: 263 | raise Rf4ceException("Missing key") 264 | cipher = Rf4ceAES(self.key, self.source, self.destination) 265 | self.payload = cipher.decipher(raw_payload, self.get_frame_control(), self.frame_counter) 266 | else: 267 | self.payload = raw_payload 268 | 269 | def command_frame_from_string(self, data): 270 | """Parses a RF4CE command pyload from a string""" 271 | raw_payload = data[5:] 272 | if self.frame_ciphered: 273 | if not self.key: 274 | raise Rf4ceException("Missing key") 275 | cipher = Rf4ceAES(self.key, self.source, self.destination) 276 | command_data = cipher.decipher(raw_payload, self.get_frame_control(), self.frame_counter) 277 | else: 278 | command_data = raw_payload 279 | 280 | self.command = bytes(command_data)[0] 281 | self.payload = command_data[1:] 282 | 283 | def __repr__(self): 284 | if self.frame_type == Rf4ceConstants.FRAME_TYPE_DATA: 285 | type = hue.lightblue("DATA") + " - " 286 | type += "profile:" + hue.lightblue("0x{:x}".format(self.profile_indentifier)) 287 | elif self.frame_type == Rf4ceConstants.FRAME_TYPE_COMMAND: 288 | type = hue.lightblue("COMMAND") + " - " 289 | type += "cmd:" + hue.lightblue("0x{:x}".format(self.command)) 290 | elif self.frame_type == Rf4ceConstants.FRAME_TYPE_VENDOR: 291 | type = hue.lightblue("VENDOR") + " - " 292 | type += "profile:" + hue.lightblue("0x{:x}").format(self.profile_indentifier) 293 | type += " - vendor:" + hue.lightblue("0x{:x}".format(self.vendor_indentifier)) 294 | 295 | data = hue.bold(binascii.hexlify(self.payload).decode()) 296 | counter = hue.lightblue("0x{:x}".format(self.frame_counter)) 297 | 298 | result = "({}) -> ({}) : ".format(self.source, self.destination) 299 | result += "[{} - counter:{}] : {}".format(type, counter, data) 300 | 301 | return result 302 | -------------------------------------------------------------------------------- /injector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Injects arbitrary RF4CE packets. Support encryption. 5 | """ 6 | 7 | from __future__ import (absolute_import, 8 | print_function, unicode_literals) 9 | from builtins import * 10 | 11 | import argparse 12 | import time 13 | from datetime import datetime 14 | import binascii 15 | import readline 16 | 17 | from rf4ce import Dot15d4FCS, Dot15d4Data, Raw, makeFCS 18 | from rf4ce import LinkConfig, Rf4ceFrame, Rf4ceConstants 19 | from rf4ce.radio import TxFlow 20 | from rf4ce.packetprocessor import PacketProcessor 21 | import huepy as hue 22 | 23 | 24 | class AckProcessor(PacketProcessor): 25 | 26 | """ACK processor thread 27 | 28 | Thread processing incomming ACK 29 | Full-duplex SDR is needed for this to work 30 | """ 31 | 32 | def __init__(self): 33 | PacketProcessor.__init__(self) 34 | self.last_ack = -1 35 | 36 | def process(self, data): 37 | """Parses a 802.15.4 ACK and extract the seqnum""" 38 | 39 | # Check if the 802.15.4 packet is valid 40 | if makeFCS(data[:-2]) != data[-2:]: 41 | print(hue.bad("Received invalid packet")) 42 | return 43 | 44 | packet = Dot15d4FCS(data) 45 | 46 | if packet.fcf_frametype == 2: # ACK 47 | self.last_ack = packet.seqnum 48 | 49 | def get_last_ack(self): 50 | """Returns the seqnum of the last received ACK""" 51 | return self.last_ack 52 | 53 | 54 | class InjectorCmd(object): 55 | 56 | """Injector command object 57 | 58 | Contains the command & arguments of all possible 59 | injector commands 60 | """ 61 | 62 | PACKET = 1 63 | PROFILE = 2 64 | COUNTER = 3 65 | DELAY = 4 66 | CIPHERED = 5 67 | HELP = 6 68 | 69 | def to_int(self, n): 70 | if type(n) == int: 71 | return n 72 | if n.startswith("0x"): 73 | return int(n, 16) 74 | else: 75 | return int(n) 76 | 77 | def to_bool(self, r): 78 | if type(r) == bool: 79 | return r 80 | n = self.to_int(r) 81 | if not n in (0, 1): 82 | raise ValueError() 83 | return n == 1 84 | 85 | def __init__(self, cmd, arg): 86 | self.action = cmd 87 | 88 | if self.action == self.PROFILE: 89 | self.arg = self.to_int(arg) 90 | elif self.action == self.COUNTER: 91 | self.arg = self.to_int(arg) 92 | elif self.action == self.DELAY: 93 | self.arg = float(arg) 94 | elif self.action == self.CIPHERED: 95 | self.arg = self.to_bool(arg) 96 | else: 97 | self.arg = arg 98 | 99 | 100 | class Injector(object): 101 | 102 | """Injector util main class""" 103 | 104 | def __init__(self, link_config, channel, sdr_device): 105 | self.link_config = link_config 106 | self.sdr_device = sdr_device 107 | 108 | # Build a RF4CE data frame based on the link configuration 109 | self.rf4ce_frame = Rf4ceFrame() 110 | self.rf4ce_frame.source = self.link_config.source 111 | self.rf4ce_frame.destination = self.link_config.destination 112 | self.rf4ce_frame.frame_type = Rf4ceConstants.FRAME_TYPE_DATA 113 | if self.link_config.key: 114 | self.rf4ce_frame.frame_ciphered = True 115 | self.rf4ce_frame.key = binascii.unhexlify(self.link_config.key) 116 | else: 117 | self.rf4ce_frame.frame_ciphered = False 118 | self.rf4ce_frame.frame_counter = self.link_config.frame_counter 119 | 120 | self.seqnum = 0 121 | 122 | # inter-packet delay 123 | self.packet_delay = 0.1 124 | 125 | # Pluto-sdr support full duplex 126 | # ACK can be received 127 | if self.sdr_device == "pluto-sdr": 128 | self.ack_processor = AckProcessor() 129 | else: 130 | self.ack_processor = None 131 | 132 | self.tb = TxFlow(channel, self.ack_processor, self.sdr_device) 133 | 134 | def run(self): 135 | self.log("SRC:({}) -> DST:({})".format(self.link_config.source, 136 | self.link_config.destination), hue.info) 137 | if not self.link_config.key: 138 | self.log("No secured configuration provided. Will only send plaintext packets.", hue.info) 139 | self.log("Loading last frame counter: {}".format(self.link_config.frame_counter), hue.info) 140 | self.help() 141 | 142 | self.tb.start() 143 | if self.ack_processor: 144 | self.ack_processor.start() 145 | 146 | # Main loop, iterate through user-supplied commands 147 | for cmd in self.prompt(): 148 | if cmd.action == InjectorCmd.PACKET: 149 | self.seqnum = (self.seqnum + 1) % 255 150 | self.rf4ce_frame.frame_counter += 1 151 | self.rf4ce_frame.payload = cmd.arg 152 | data = self.gen_ieee_packet(self.rf4ce_frame.pack()) 153 | 154 | self.log("Transmitting {}".format(binascii.hexlify(data)), hue.info) 155 | 156 | if self.sdr_device == "pluto-sdr": 157 | self.ack_transmit(data) 158 | else: 159 | self.tb.transmit(data) 160 | 161 | time.sleep(self.packet_delay) 162 | 163 | elif cmd.action == InjectorCmd.PROFILE: 164 | self.log("Set profile to 0x{:02x}".format(cmd.arg), hue.info) 165 | self.rf4ce_frame.profile_indentifier = cmd.arg 166 | 167 | elif cmd.action == InjectorCmd.COUNTER: 168 | self.log("Set counter to {}".format(cmd.arg), hue.info) 169 | self.rf4ce_frame.frame_counter = cmd.arg 170 | 171 | elif cmd.action == InjectorCmd.DELAY: 172 | self.log("Set delay to {}".format(cmd.arg), hue.info) 173 | self.packet_delay = cmd.arg 174 | 175 | elif cmd.action == InjectorCmd.CIPHERED: 176 | self.log("Set ciphered to {}".format(cmd.arg), hue.info) 177 | if cmd.arg: 178 | if not link_config.key: 179 | self.log("No key provided. Cannot send ciphered packets.", hue.bad) 180 | else: 181 | self.rf4ce_frame.frame_ciphered = True 182 | else: 183 | self.rf4ce_frame.frame_ciphered = False 184 | 185 | elif cmd.action == InjectorCmd.HELP: 186 | self.help() 187 | 188 | self.link_config.frame_counter = self.rf4ce_frame.frame_counter 189 | self.log("Saving last frame counter: {}".format(self.link_config.frame_counter), hue.info) 190 | self.link_config.save() 191 | 192 | if self.sdr_device == "pluto-sdr": 193 | self.ack_processor.stop() 194 | 195 | self.tb.stop() 196 | self.tb.wait() 197 | 198 | def help(self): 199 | help_text = """ 200 | Available commands: 201 | 202 | counter Set the frame counter value 203 | 204 | delay Minimum delay between packet (seconds) 205 | 206 | ciphered [0, 1] Send AES ciphered payloads of cleartext payloads. 207 | Only possible if a key has been configured 208 | 209 | profile Select a profile number 210 | 211 | exit 212 | 213 | Other inputs will be considered as data to be sent. 214 | """ 215 | print(help_text) 216 | 217 | 218 | def prompt(self): 219 | """Generates the injector util prompt""" 220 | while True: 221 | try: 222 | if self.rf4ce_frame.frame_ciphered: 223 | ciphered_status = "ciphered" 224 | else: 225 | ciphered_status = "plain" 226 | a = hue.lightblue("{}".format(self.rf4ce_frame.frame_counter)) 227 | b = hue.lightblue("0x{:02x}".format(self.rf4ce_frame.profile_indentifier)) 228 | c = hue.lightblue(ciphered_status) 229 | cmd = raw_input("({} - {} - {})>>> ".format(a, b, c)) 230 | except KeyboardInterrupt: 231 | raise StopIteration 232 | 233 | if cmd.startswith("profile"): 234 | try: 235 | yield InjectorCmd(InjectorCmd.PROFILE, cmd.split()[1]) 236 | except: 237 | self.log("Malformed command", hue.bad) 238 | continue 239 | 240 | elif cmd.startswith("counter"): 241 | try: 242 | yield InjectorCmd(InjectorCmd.COUNTER, cmd.split()[1]) 243 | except: 244 | self.log("Malformed command", hue.bad) 245 | continue 246 | 247 | elif cmd.startswith("delay"): 248 | try: 249 | yield InjectorCmd(InjectorCmd.DELAY, cmd.split()[1]) 250 | except: 251 | self.log("Malformed command", hue.bad) 252 | continue 253 | 254 | elif cmd.startswith("ciphered"): 255 | try: 256 | yield InjectorCmd(InjectorCmd.CIPHERED, cmd.split()[1]) 257 | except: 258 | self.log("Malformed command", hue.bad) 259 | continue 260 | 261 | elif cmd.startswith("help"): 262 | yield InjectorCmd(InjectorCmd.HELP, None) 263 | 264 | elif cmd.startswith("exit"): 265 | raise StopIteration 266 | 267 | else: 268 | for packet in cmd.split(): 269 | try: 270 | data = binascii.unhexlify(packet) 271 | except: 272 | self.log("Malformed command", hue.bad) 273 | continue 274 | yield InjectorCmd(InjectorCmd.PACKET, data) 275 | 276 | def gen_ieee_packet(self, data): 277 | """Encapsulates data into a 802.15.4 packet""" 278 | packet = Dot15d4FCS() / Dot15d4Data() / Raw(load=data) 279 | 280 | packet.fcf_srcaddrmode = 2 281 | packet.fcf_destaddrmode = 2 282 | 283 | packet.fcf_panidcompress = True 284 | packet.fcf_ackreq = True 285 | packet.seqnum = self.seqnum 286 | 287 | packet.dest_panid = self.link_config.dest_panid 288 | 289 | packet.dest_addr = self.link_config.destination.get_short_address() 290 | packet.src_addr = self.link_config.source.get_short_address() 291 | 292 | return packet.build() 293 | 294 | def ack_transmit(self, data, max_freq_retry=5, max_tx_retry=10): 295 | """Transmit data with ACK check 296 | 297 | Tries to transmit a packet until a ACK is received 298 | Full-duplex SDR is needed for this to work 299 | """ 300 | transmit_success = False 301 | for freq_retry in range(max_freq_retry): 302 | for tx_retry in range(max_tx_retry): 303 | self.tb.transmit(data) 304 | time.sleep(0.15) 305 | if self.ack_processor.get_last_ack() != self.seqnum: 306 | self.log("Warning: no ACK received, retrying", hue.bad) 307 | else: 308 | self.log("ACK received", hue.good) 309 | transmit_success = True 310 | return True 311 | self.log("Warning: switching frequency", hue.bad) 312 | self.tb.frequency_switch() 313 | return False 314 | 315 | def log(self, data, format=None): 316 | if format: 317 | print(format("[{}] {}".format(datetime.now(), data))) 318 | else: 319 | print("[{}] {}".format(datetime.now(), data)) 320 | 321 | 322 | if __name__ == '__main__': 323 | 324 | parser = argparse.ArgumentParser() 325 | parser.add_argument("config_file", help="JSON file containing link information") 326 | parser.add_argument("-c", "--channel", help="RF4CE channel (default: 15)", type=int, 327 | choices=[15, 20, 25], default=15) 328 | parser.add_argument("-s", "--sdr", help="SDR Device to use (default: pluto-sdr)", 329 | choices=["hackrf", "pluto-sdr"], default="pluto-sdr") 330 | args = parser.parse_args() 331 | 332 | try: 333 | link_config = LinkConfig(args.config_file) 334 | except: 335 | print(hue.bad("Cannot load configuration file")) 336 | exit(-1) 337 | 338 | print(link_config) 339 | 340 | injector = Injector(link_config, args.channel, args.sdr) 341 | injector.run() 342 | -------------------------------------------------------------------------------- /rf4ce/radio.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Low level gnuradio graphs for 802.15.4. 4 | """ 5 | 6 | from math import pi, sin 7 | import numpy 8 | 9 | from gnuradio import blocks 10 | from gnuradio import digital 11 | from gnuradio import eng_notation 12 | from gnuradio import gr 13 | from gnuradio import iio 14 | from gnuradio.eng_option import eng_option 15 | from gnuradio.filter import firdes 16 | import es 17 | import foo 18 | import ieee802_15_4 19 | import osmosdr 20 | import pmt 21 | 22 | from autognuradio.ieee802_15_4_oqpsk_phy import ieee802_15_4_oqpsk_phy 23 | 24 | 25 | class TxFlow(gr.top_block): 26 | 27 | def __init__(self, channel, processor, sdr_device="pluto-sdr"): 28 | gr.top_block.__init__(self, "Tx Flow") 29 | 30 | ################################################## 31 | # Variables 32 | ################################################## 33 | self.channel = channel 34 | self.sdr_device = sdr_device 35 | self.processor = processor 36 | 37 | ################################################## 38 | # Blocks 39 | ################################################## 40 | if self.sdr_device == "hackrf": 41 | self.sdr_sink = osmosdr.sink(args="numchan=1") 42 | self.sdr_sink.set_sample_rate(4e6) 43 | self.sdr_sink.set_center_freq(self.get_center_freq(), 0) 44 | self.sdr_sink.set_freq_corr(0, 0) 45 | self.sdr_sink.set_gain(10, 0) 46 | self.sdr_sink.set_if_gain(20, 0) 47 | self.sdr_sink.set_bb_gain(20, 0) 48 | self.sdr_sink.set_antenna('', 0) 49 | self.sdr_sink.set_bandwidth(0, 0) 50 | elif self.sdr_device == "pluto-sdr": 51 | self.sdr_sink = iio.pluto_sink('192.168.2.1', self.get_center_freq(), 52 | int(4e6), int(4e6), 0x8000, False, 0, '', True) 53 | self.sdr_source = iio.pluto_source('192.168.2.1', self.get_center_freq(), 54 | int(4e6), int(4e6), 0x8000, True, True, True, "manual", 50, '', True) 55 | 56 | self.ieee802_15_4_access_code_prefixer_0 = ieee802_15_4.access_code_prefixer() 57 | self.es_source_0 = es.source(1*[gr.sizeof_gr_complex], 1, 2, 0) 58 | self.digital_chunks_to_symbols_xx_0 = digital.chunks_to_symbols_bc(([(1+1j), (-1+1j), (1-1j), (-1+1j), (1+1j), (-1-1j), (-1-1j), (1+1j), (-1+1j), (-1+1j), (-1-1j), (1-1j), (-1-1j), (1-1j), (1+1j), (1-1j), (1-1j), (-1-1j), (1+1j), (-1-1j), (1-1j), (-1+1j), (-1+1j), (1-1j), (-1-1j), (-1-1j), (-1+1j), (1+1j), (-1+1j), (1+1j), (1-1j), (1+1j), (-1+1j), (-1+1j), (-1-1j), (1-1j), (-1-1j), (1-1j), (1+1j), (1-1j), (1+1j), (-1+1j), (1-1j), (-1+1j), (1+1j), (-1-1j), (-1-1j), (1+1j), (-1-1j), (-1-1j), (-1+1j), (1+1j), (-1+1j), (1+1j), (1-1j), (1+1j), (1-1j), (-1-1j), (1+1j), (-1-1j), (1-1j), (-1+1j), (-1+1j), (1-1j), (-1-1j), (1-1j), (1+1j), (1-1j), (1+1j), (-1+1j), (1-1j), (-1+1j), (1+1j), (-1-1j), (-1-1j), (1+1j), (-1+1j), (-1+1j), (-1-1j), (1-1j), (-1+1j), (1+1j), (1-1j), (1+1j), (1-1j), (-1-1j), (1+1j), (-1-1j), (1-1j), (-1+1j), (-1+1j), (1-1j), (-1-1j), (-1-1j), (-1+1j), (1+1j), (1+1j), (-1-1j), (-1-1j), (1+1j), (-1+1j), (-1+1j), (-1-1j), (1-1j), (-1-1j), (1-1j), (1+1j), (1-1j), (1+1j), (-1+1j), (1-1j), (-1+1j), (1-1j), (-1+1j), (-1+1j), (1-1j), (-1-1j), (-1-1j), (-1+1j), (1+1j), (-1+1j), (1+1j), (1-1j), (1+1j), (1-1j), (-1-1j), (1+1j), (-1-1j), (1+1j), (1-1j), (1+1j), (-1+1j), (1-1j), (-1+1j), (1+1j), (-1-1j), (-1-1j), (1+1j), (-1+1j), (-1+1j), (-1-1j), (1-1j), (-1-1j), (1-1j), (1-1j), (1+1j), (1-1j), (-1-1j), (1+1j), (-1-1j), (1-1j), (-1+1j), (-1+1j), (1-1j), (-1-1j), (-1-1j), (-1+1j), (1+1j), (-1+1j), (1+1j), (-1-1j), (1+1j), (-1+1j), (-1+1j), (-1-1j), (1-1j), (-1-1j), (1-1j), (1+1j), (1-1j), (1+1j), (-1+1j), (1-1j), (-1+1j), (1+1j), (-1-1j), (-1+1j), (1-1j), (-1-1j), (-1-1j), (-1+1j), (1+1j), (-1+1j), (1+1j), (1-1j), (1+1j), (1-1j), (-1-1j), (1+1j), (-1-1j), (1-1j), (-1+1j), (-1-1j), (1-1j), (-1-1j), (1-1j), (1+1j), (1-1j), (1+1j), (-1+1j), (1-1j), (-1+1j), (1+1j), (-1-1j), (-1-1j), (1+1j), (-1+1j), (-1+1j), (-1+1j), (1+1j), (-1+1j), (1+1j), (1-1j), (1+1j), (1-1j), (-1-1j), (1+1j), (-1-1j), (1-1j), (-1+1j), (-1+1j), (1-1j), (-1-1j), (-1-1j), (1-1j), (-1+1j), (1+1j), (-1-1j), (-1-1j), (1+1j), (-1+1j), (-1+1j), (-1-1j), (1-1j), (-1-1j), (1-1j), (1+1j), (1-1j), (1+1j), (-1+1j), (1+1j), (-1-1j), (1-1j), (-1+1j), (-1+1j), (1-1j), (-1-1j), (-1-1j), (-1+1j), (1+1j), (-1+1j), (1+1j), (1-1j), (1+1j), (1-1j), (-1-1j)]), 16) 59 | self.blocks_vector_source_x_0 = blocks.vector_source_c([0, sin(pi/4), 1, sin(3*pi/4)], True, 1, []) 60 | self.blocks_tagged_stream_to_pdu_0 = blocks.tagged_stream_to_pdu(blocks.complex_t, 'pdu_length') 61 | self.blocks_tagged_stream_multiply_length_0 = blocks.tagged_stream_multiply_length(gr.sizeof_gr_complex*1, 'pdu_length', 128) 62 | self.blocks_tagged_stream_multiply_length_0.set_min_output_buffer(20000) 63 | self.blocks_repeat_0 = blocks.repeat(gr.sizeof_gr_complex*1, 4) 64 | self.blocks_pdu_to_tagged_stream_0_0_0 = blocks.pdu_to_tagged_stream(blocks.byte_t, 'pdu_length') 65 | self.blocks_packed_to_unpacked_xx_0 = blocks.packed_to_unpacked_bb(4, gr.GR_LSB_FIRST) 66 | self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) 67 | self.blocks_float_to_complex_0 = blocks.float_to_complex(1) 68 | self.blocks_delay_0 = blocks.delay(gr.sizeof_float*1, 2) 69 | self.blocks_complex_to_float_0 = blocks.complex_to_float(1) 70 | 71 | self.msg_in_0 = msg_block_source() 72 | 73 | if self.sdr_device == "pluto-sdr": 74 | self.ieee802_15_4_oqpsk_phy_0 = ieee802_15_4_oqpsk_phy() 75 | self.blocks_null_sink_0 = blocks.null_sink(gr.sizeof_gr_complex*1) 76 | 77 | self.msg_out_0 = msg_sink_block(self.processor) 78 | 79 | ################################################## 80 | # Connections 81 | ################################################## 82 | self.msg_connect((self.blocks_tagged_stream_to_pdu_0, 'pdus'), (self.es_source_0, 'schedule_event')) 83 | self.msg_connect((self.ieee802_15_4_access_code_prefixer_0, 'out'), (self.blocks_pdu_to_tagged_stream_0_0_0, 'pdus')) 84 | self.msg_connect((self.msg_in_0, 'msg_out'), (self.ieee802_15_4_access_code_prefixer_0, 'in')) 85 | self.connect((self.blocks_complex_to_float_0, 1), (self.blocks_delay_0, 0)) 86 | self.connect((self.blocks_complex_to_float_0, 0), (self.blocks_float_to_complex_0, 0)) 87 | self.connect((self.blocks_delay_0, 0), (self.blocks_float_to_complex_0, 1)) 88 | self.connect((self.blocks_float_to_complex_0, 0), (self.sdr_sink, 0)) 89 | self.connect((self.blocks_multiply_xx_0, 0), (self.blocks_tagged_stream_multiply_length_0, 0)) 90 | self.connect((self.blocks_packed_to_unpacked_xx_0, 0), (self.digital_chunks_to_symbols_xx_0, 0)) 91 | self.connect((self.blocks_pdu_to_tagged_stream_0_0_0, 0), (self.blocks_packed_to_unpacked_xx_0, 0)) 92 | self.connect((self.blocks_repeat_0, 0), (self.blocks_multiply_xx_0, 1)) 93 | self.connect((self.blocks_tagged_stream_multiply_length_0, 0), (self.blocks_tagged_stream_to_pdu_0, 0)) 94 | self.connect((self.blocks_vector_source_x_0, 0), (self.blocks_multiply_xx_0, 0)) 95 | self.connect((self.digital_chunks_to_symbols_xx_0, 0), (self.blocks_repeat_0, 0)) 96 | self.connect((self.es_source_0, 0), (self.blocks_complex_to_float_0, 0)) 97 | 98 | if self.sdr_device == "pluto-sdr": 99 | self.msg_connect((self.ieee802_15_4_oqpsk_phy_0, 'rxout'), (self.msg_out_0, 'msg_in')) 100 | self.connect((self.ieee802_15_4_oqpsk_phy_0, 0), (self.blocks_null_sink_0, 0)) 101 | self.connect((self.sdr_source, 0), (self.ieee802_15_4_oqpsk_phy_0, 0)) 102 | 103 | 104 | def get_channel(self): 105 | return self.channel 106 | 107 | def set_channel(self, channel): 108 | self.channel = channel 109 | if self.sdr_device == "hackrf": 110 | self.sdr_source.set_center_freq(self.get_center_freq()) 111 | elif self.sdr_device == "pluto-sdr": 112 | self.sdr_source.set_params(self.get_center_freq(), 113 | int(4e6), int(20e6), True, True, True, "manual", 50, '', True) 114 | self.sdr_sink.set_params(self.get_center_freq(), int(4e6), int(20e6), 0, '', True) 115 | 116 | def frequency_switch(self): 117 | channels = [15, 20, 25] 118 | i = channels.index(self.get_channel()) 119 | self.set_channel(channels[(i+1)%3]) 120 | 121 | def get_center_freq(self): 122 | return 1000000 * (2400 + 5 * (self.channel - 10)) 123 | 124 | 125 | def transmit(self, data): 126 | self.msg_in_0.transmit(data) 127 | 128 | 129 | class RxFlow(gr.top_block): 130 | 131 | def __init__(self, channel, processor, device="pluto-sdr"): 132 | gr.top_block.__init__(self, "Sniffer Flow") 133 | 134 | self.processor = processor 135 | 136 | ################################################## 137 | # Variables 138 | ################################################## 139 | self.channel = channel 140 | self.device = device 141 | 142 | ################################################## 143 | # Blocks 144 | ################################################## 145 | if self.device == "hackrf": 146 | self.sdr_source = osmosdr.source(args="numchan=1") 147 | self.sdr_source.set_sample_rate(4e6) 148 | self.sdr_source.set_center_freq(self.get_center_freq(), 0) 149 | self.sdr_source.set_freq_corr(0, 0) 150 | self.sdr_source.set_dc_offset_mode(0, 0) 151 | self.sdr_source.set_iq_balance_mode(0, 0) 152 | self.sdr_source.set_gain_mode(False, 0) 153 | self.sdr_source.set_gain(14, 0) 154 | self.sdr_source.set_if_gain(16, 0) 155 | self.sdr_source.set_bb_gain(16, 0) 156 | self.sdr_source.set_antenna('', 0) 157 | self.sdr_source.set_bandwidth(0, 0) 158 | elif self.device == "pluto-sdr": 159 | self.sdr_source = iio.pluto_source('192.168.2.1', self.get_center_freq(), 160 | int(4e6), int(20e6), 0x8000, True, True, True, "manual", 50, '', True) 161 | 162 | 163 | self.ieee802_15_4_oqpsk_phy_0 = ieee802_15_4_oqpsk_phy() 164 | self.blocks_null_sink_0 = blocks.null_sink(gr.sizeof_gr_complex*1) 165 | 166 | self.msg_out_0 = msg_sink_block(self.processor) 167 | 168 | ################################################## 169 | # Connections 170 | ################################################## 171 | self.msg_connect((self.ieee802_15_4_oqpsk_phy_0, 'rxout'), (self.msg_out_0, 'msg_in')) 172 | self.connect((self.ieee802_15_4_oqpsk_phy_0, 0), (self.blocks_null_sink_0, 0)) 173 | self.connect((self.sdr_source, 0), (self.ieee802_15_4_oqpsk_phy_0, 0)) 174 | 175 | def get_channel(self): 176 | return self.channel 177 | 178 | def set_channel(self, channel): 179 | self.channel = channel 180 | if self.device == "hackrf": 181 | self.sdr_source.set_center_freq(self.get_center_freq()) 182 | elif self.device == "pluto-sdr": 183 | self.sdr_source.set_params('192.168.2.1', self.get_center_freq(), 184 | int(4e6), int(20e6), 0x8000, True, True, True, "manual", 50, '', True) 185 | 186 | def get_center_freq(self): 187 | return 1000000 * (2400 + 5 * (self.channel - 10)) 188 | 189 | 190 | 191 | class msg_sink_block(gr.basic_block): 192 | 193 | def __init__(self, processor): 194 | 195 | gr.basic_block.__init__( 196 | self, 197 | name="msg_block", 198 | in_sig=None, 199 | out_sig=None) 200 | 201 | self.processor = processor 202 | self.message_port_register_in(pmt.intern('msg_in')) 203 | self.set_msg_handler(pmt.intern('msg_in'), self.handle_msg) 204 | 205 | def handle_msg(self, msg): 206 | messages = pmt.to_python(msg) 207 | for message in messages: 208 | if type(message) == numpy.ndarray: 209 | self.processor.feed(message.tostring()) 210 | 211 | 212 | class msg_block_source(gr.basic_block): 213 | 214 | def __init__(self): 215 | 216 | gr.basic_block.__init__( 217 | self, 218 | name="msg_block", 219 | in_sig=None, 220 | out_sig=None) 221 | 222 | self.message_port_register_out(pmt.intern('msg_out')) 223 | 224 | def transmit(self, data): 225 | vector = pmt.make_u8vector(len(data), 0) 226 | for i, c in enumerate(data): 227 | pmt.u8vector_set(vector, i, ord(data[i])) 228 | pdu = pmt.cons(pmt.make_dict(), vector) 229 | self.message_port_pub(pmt.intern('msg_out'), pdu) 230 | --------------------------------------------------------------------------------