From 60936a45aec54c5b36299e40085f8aa1faea1073 Mon Sep 17 00:00:00 2001 From: Dollarhyde Date: Thu, 23 Jul 2026 13:10:15 -0400 Subject: [PATCH 1/9] goTenna Pro core files --- README.md | 1 + challenges/gotenna_packet/__init__.py | 5 + challenges/gotenna_packet/gotenna_packet.py | 807 ++++++++++++++++++ challenges/gotenna_packet/proto/__init__.py | 0 .../gotenna_packet/proto/base_message_pb2.py | 27 + .../gotenna_packet/proto/data_type_pb2.py | 36 + .../gotenna_packet/proto/frequency_pb2.py | 29 + challenges/gotenna_packet/proto/header_pb2.py | 27 + .../gotenna_packet/proto/location_pb2.py | 39 + .../gotenna_packet/proto/message_pb2.py | 44 + challenges/gotenna_pro_bladerf.py | 459 ++++++++++ flags.txt.ci | 1 + 12 files changed, 1475 insertions(+) create mode 100644 challenges/gotenna_packet/__init__.py create mode 100755 challenges/gotenna_packet/gotenna_packet.py create mode 100644 challenges/gotenna_packet/proto/__init__.py create mode 100644 challenges/gotenna_packet/proto/base_message_pb2.py create mode 100644 challenges/gotenna_packet/proto/data_type_pb2.py create mode 100644 challenges/gotenna_packet/proto/frequency_pb2.py create mode 100644 challenges/gotenna_packet/proto/header_pb2.py create mode 100644 challenges/gotenna_packet/proto/location_pb2.py create mode 100644 challenges/gotenna_packet/proto/message_pb2.py create mode 100644 challenges/gotenna_pro_bladerf.py diff --git a/README.md b/README.md index 6fbb034..0cd3ef2 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,4 @@ Any transmit capable device that can be used to transmit using a GNURadio gr-osm **lrs_pager.py** Generates a pager.bin file to be transmitted by lrs_tx.py. This code is based on [github.com/tony-tiger/lrs](https://github.com/tony-tiger/lrs)
**lrs_tx.py** Transmits an LRS signal, based on a pager.bin file generated by lrs_pager.py
**pocsagtx_osmocom.py** Transmits POCSAG messages using [gr-mixalot](https://github.com/unsynchronized/gr-mixalot)
+**gotenna_pro_bladerf.py** Transmits goTenna Pro broadcast messages using bladeRF and packet encoding from [gr-tenna](https://github.com/argilo/gr-tenna)
diff --git a/challenges/gotenna_packet/__init__.py b/challenges/gotenna_packet/__init__.py new file mode 100644 index 0000000..ed0bf15 --- /dev/null +++ b/challenges/gotenna_packet/__init__.py @@ -0,0 +1,5 @@ +import sys + +sys.modules.setdefault("gotenna_packet", sys.modules[__name__]) + +from .gotenna_packet import * diff --git a/challenges/gotenna_packet/gotenna_packet.py b/challenges/gotenna_packet/gotenna_packet.py new file mode 100755 index 0000000..60c9d15 --- /dev/null +++ b/challenges/gotenna_packet/gotenna_packet.py @@ -0,0 +1,807 @@ +#!/usr/bin/env python3 +# +# Copyright 2018-2024 Clayton Smith (argilo@gmail.com) +# +# This file is part of gr-tenna. +# +# gr-tenna is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# gr-tenna is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with gr-tenna. If not, see . +# + +import enum +import struct +import time +import reedsolo +import json +import base64 +import csv +import os +import google +from .proto import base_message_pb2 +from .proto import data_type_pb2 +from .proto import message_pb2 +from google.protobuf import json_format +from cryptography.exceptions import InvalidTag +from cryptography.hazmat.primitives import hashes, hmac +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.ciphers.aead import AESGCM +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from cryptography.hazmat.primitives.serialization import load_der_private_key + + +CRC16_TABLE = [ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +] + +WHITENING = [ + 0x03, 0x96, 0x6a, 0x0f, 0xe7, 0xe5, 0x09, 0xde, 0xc4, 0xd8, 0x52, 0x3d, 0x76, 0x68, 0xbe, 0x2b, + 0x03, 0x8e, 0x1f, 0x09, 0x0e, 0x07, 0x6b, 0x4b, 0x61, 0x78, 0xb2, 0xfb, 0x81, 0x78, 0x36, 0x2a, + 0x1e, 0xcd, 0x8a, 0x76, 0x30, 0x85, 0x59, 0x32, 0xe1, 0x3c, 0xc9, 0xae, 0x34, 0x5c, 0x27, 0x98, + 0x34, 0x70, 0xcd, 0x88, 0x0c, 0xd4, 0x46, 0x0c, 0x62, 0x7d, 0xfc, 0x52, 0xe4, 0x03, 0x15, 0xb9, + 0x8a, 0x9f, 0x46, 0x64, 0x60, 0xe5, 0x98, 0x2c, 0x3e, 0x4b, 0x2e, 0xb7, 0x0f, 0xd9, 0xf2, 0x88, + 0xcf, 0xde, 0x8f, 0x31, 0xa2, 0x18, 0xb5, 0x0c, 0x20, 0x8f, 0x8c, 0x9f, 0x53, 0xa0, 0x6b, 0x4e, + 0x60, 0x78, 0x43, 0x88, 0x30, 0xb8, 0xd5, 0xfa, 0x13, 0xdf, 0x59, 0x41, 0xea, 0x85, 0x2a, 0xf3, + 0xc9, 0x27, 0xc2, 0x00, 0x69, 0xee, 0xb2, 0x4e, 0xd5, 0x09, 0x93, 0x93, 0x36, 0xff, 0xf5, 0xae, + 0xf2, 0xdf, 0x01, 0x1c, 0xb1, 0x9e, 0xa4, 0x73, 0x87, 0xf1, 0xa1, 0xcb, 0xae, 0x2e, 0x60, 0x1b, + 0x36, 0x0b, 0x45, 0xd0, 0xf0, 0xc8, 0x8d, 0xe9, 0x51, 0x25, 0xb4, 0x7e, 0x9b, 0x21, 0x02, 0x20, + 0xcb, 0x22, 0x46, 0x5a, 0xf2, 0x0c, 0xcb, 0x07, 0x17, 0x03, 0xde, 0x52, 0x96, 0x5a, 0x6e, 0x7d, + 0x14, 0x76, 0xc7, 0x1d, 0x01, 0xd4, 0x60, 0xf4, 0x5a, 0x12, 0xed, 0x6a, 0x7b, 0x6a, 0xd8, 0x41, + 0x93, 0xd3, 0x42, 0xfd, 0x46, 0x44, 0xaf, 0xda, 0x13, 0x3e, 0x25, 0x6a, 0x88, 0xbb, 0x4c, 0x65, + 0xac, 0x37, 0x7c, 0x6d, 0x9f, 0xd3, 0x3c, 0x17, 0xdc, 0x8a, 0x57, 0x4f, 0xcf, 0xb9, 0x0d, 0x96, + 0x18, 0xa6, 0x7b, 0xb2, 0xa6, 0x78, 0x33, 0xe1, 0x8f, 0xc4, 0xaf, 0x94, 0xd6, 0x31, 0x84, 0xb4, + 0x8c, 0xe9, 0x66, 0xb6, 0xd3, 0xd1, 0xca, 0x46, 0x3d, 0xd0, 0x62, 0x8d, 0x37, 0xc8, 0x84, 0x1c, +] + +CONTROL_CHANNELS = [903000000, 915000000, 927000000] + +DATA_CHANNELS = [ + 923500000, 923000000, 925500000, 926500000, 925000000, 908000000, 911500000, 905500000, + 903500000, 915500000, 909000000, 912000000, 922000000, 921000000, 924000000, 907500000, + 910000000, 912500000, 927500000, 910500000, 913000000, 918500000, 913500000, 914000000, + 902500000, 922500000, 907000000, 920000000, 919000000, 916500000, 919500000, 914500000, + 918000000, 911000000, 917500000, 916000000, 924500000, 909500000, 904500000, 904000000, + 917000000, 921500000, 920500000, 905000000, 908500000, 906500000, 926000000, 906000000 +] + +ONE_TO_ONE_GID = 0 +GROUP_GID = 1 +SHOUT_GID = 2 +EMERGENCY_GID = 3 + +TAG_TYPE = 1 +TAG_INITIALS = 3 +TAG_MESSAGE_CONTENT = 4 +TAG_MESSAGE = 5 +TAG_ENCRYPTION_INFO = 251 +TAG_PUBLIC_KEY = 252 + +MESH_SYNC = 0x01 +MESH_DATA = 0x02 +MESH_ACK = 0x03 +PRO_SYNC = 0x10 +PRO_DATA = 0x20 + + +class ConversationType(enum.Enum): + PRIVATE = 0 + GROUP = 1 + BROADCAST = 2 + EMERGENCY = 3 + UNKNOWN = 4 + ACTIVE_NODE = 5 + + +TEXT_MESSAGE = b"0" +MESH_KEY_EXCHANGE_REQUEST = b"14" +MESH_KEY_EXCHANGE_RESPONSE = b"15" + +type_names = { + TEXT_MESSAGE: "Text message", + MESH_KEY_EXCHANGE_REQUEST: "Mesh key exchange request", + MESH_KEY_EXCHANGE_RESPONSE: "Mesh key exchange response", +} + +PAYLOAD_SPLIT_LEN = 90 +CONTROL_PREAMBLE_LEN = 93 +DATA_PREAMBLE_LEN = 10 +HOP_TIME_1 = 33 +HOP_TIME_2 = 27 + +PRO_PAYLOAD_SPLIT_LEN = 94 +PRO_CONTROL_PREAMBLE_LEN = 58 +PRO_DATA_PREAMBLE_LEN = 30 +PRO_HOP_TIME = 33 + +public_keys = {} +private_keys = {} +group_keys = {} +broadcast_keys = {} + +key_types = { + "public": public_keys, + "private": private_keys, + "group": group_keys, + "broadcast": broadcast_keys, +} + +mesh_fragments = [] + + +def compress_point(x, y): + return bytes([2 | (y & 1)]) + x.to_bytes(48, byteorder="big") + + +def uncompress_point(data): + p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff + a = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc + b = 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef + + x = int.from_bytes(data[1:], byteorder="big") + y_squared = (pow(x, 3, p) + x * a + b) % p + y = pow(y_squared, (p + 1) // 4, p) + if (data[0] & 1) != (y & 1): + y = p - y + return x, y + + +def crc16(in_bytes): + reg = 0x0000 + for b in in_bytes: + reg = ((reg & 0xff) << 8) ^ CRC16_TABLE[(b ^ (reg >> 8)) & 0xff] + return reg + + +def encode_packet(preamble_len, in_bytes): + length = len(in_bytes) + 2 + 8 + packet = bytes([length]) + in_bytes + packet += struct.pack(">H", crc16(packet) ^ 0xabcd) + rs = reedsolo.RSCodec(nsym=8, fcr=1) + return bytes([0xaa]*preamble_len + [0x2d, 0xd4]) + rs.encode(packet) + + +def encode_tlv(type, value): + return bytes([type, len(value)]) + value + + +def encode_encryption_info(encrypted, sender_gid, timestamp, enc_counter, resend_id): + return struct.pack(">BQIHB", encrypted, sender_gid, timestamp, enc_counter, resend_id) + + +def encode_control_packet(channel, num_data_packets): + return encode_packet(CONTROL_PREAMBLE_LEN, bytes([9, channel, num_data_packets, 1, 1])) + + +def encode_data_packet(seq_no, segment): + return encode_packet(DATA_PREAMBLE_LEN, bytes([2, len(segment), seq_no]) + segment) + + +def encode_dest_gid(app_id, type, gid=None): + data = struct.pack(">BH", type, app_id) + if gid: + data += struct.pack(">Q", gid)[2:] + return data + + +def encode_shout_message(sender_gid, initials, message): + packet = encode_tlv(TAG_TYPE, TEXT_MESSAGE) + packet += encode_tlv(TAG_INITIALS, initials.encode("utf-8")) + packet += encode_tlv(TAG_MESSAGE_CONTENT, message.encode("utf-8")) + packet += struct.pack(">H", crc16(packet)) + + encryption_info = encode_encryption_info(False, sender_gid, int(time.time()), 0, 0) + return encode_tlv(TAG_ENCRYPTION_INFO, encryption_info) + packet + + +def encode_key_exchange_response(sender_gid, initials, public_key): + packet = encode_tlv(TAG_TYPE, MESH_KEY_EXCHANGE_RESPONSE) + packet += encode_tlv(TAG_INITIALS, initials.encode("utf-8")) + packet += encode_tlv(TAG_PUBLIC_KEY, public_key) + packet += struct.pack(">H", crc16(packet)) + + encryption_info = encode_encryption_info(False, sender_gid, int(time.time()), 0, 0) + return encode_tlv(TAG_ENCRYPTION_INFO, encryption_info) + packet + + +def encode_encrypted_payload(app_id, recipient_gid, sender_gid, counter, ciphertext): + encryption_info = encode_encryption_info(True, sender_gid, int(time.time()), counter, 0) + packet = encode_dest_gid(app_id, ONE_TO_ONE_GID, recipient_gid) + packet += bytes([0x00, 0xff, 0x00, 0x00]) + packet += encode_tlv(TAG_ENCRYPTION_INFO, encryption_info) + packet += ciphertext + return packet + + +def encode_packets(channel, payload): + packets = [] + seq_no = 0 + for offset in range(0, len(payload), PAYLOAD_SPLIT_LEN): + segment = payload[offset:offset+PAYLOAD_SPLIT_LEN] + packets.append(encode_data_packet(seq_no, segment)) + seq_no += 1 + return [encode_control_packet(channel, seq_no)] + packets + + +def encode_shout_packets(channel, app_id, sender_gid, initials, message): + payload = encode_dest_gid(app_id, SHOUT_GID) + encode_shout_message(sender_gid, initials, message) + return encode_packets(channel, payload) + + +def gfsk_bytes(packets): + data = [0]*HOP_TIME_1 + list(packets[0]) + [0]*HOP_TIME_1 + for packet in packets[1:]: + data += list(packet) + [0]*HOP_TIME_2 + return data + + +def envelope(packets): + envelope = [0]*HOP_TIME_1 + [1]*(len(packets[0])+1) + [0]*(HOP_TIME_1-1) + for packet in packets[1:]: + envelope += [1]*(len(packet)+1) + [0]*(HOP_TIME_2-1) + return envelope + + +def vco(center_freq, control_chan, data_chan, packets): + control_offset = CONTROL_CHANNELS[control_chan] - center_freq + vco = [control_offset]*(HOP_TIME_1-3+len(packets[0])+HOP_TIME_1) + for packet in packets[1:]: + data_offset = DATA_CHANNELS[data_chan] - center_freq + vco += [data_offset]*(len(packet)+HOP_TIME_2) + data_chan = (data_chan + 1) % len(DATA_CHANNELS) + vco += [data_offset]*3 + return vco + + +def decode_tlv(tag, value): + if tag == TAG_TYPE: + print(f"Type: {value.decode()} ({type_names[value]})") + elif tag == TAG_INITIALS: + print("Initials: " + value.decode()) + elif tag == TAG_MESSAGE_CONTENT: + if value[0] == 0xff: + print("Message content: " + value.hex()) + else: + print("Message content: " + value.decode()) + elif tag == TAG_MESSAGE: + print("Message:") + decode_tlvs(value) + elif tag == TAG_ENCRYPTION_INFO: + encrypted, sender_gid, timestamp, enc_counter, resend_id = struct.unpack(">BQIHB", value) + print("Encrypted: " + str(encrypted)) + print("Sender GID: " + str(sender_gid)) + print("Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)))) + print("Encryption Counter: " + str(enc_counter)) + print("Resend ID: " + str(resend_id)) + elif tag == TAG_PUBLIC_KEY: + print("Public key: " + value.hex()) + else: + print("Tag: " + str(tag) + " value: " + value.hex()) + + +def decode_tlvs(data): + while len(data) >= 2: + tag = data[0] + length = data[1] + if 2 + length > len(data): + break + value = data[2:2+length] + data = data[2+length:] + decode_tlv(tag, value) + + +def gid_hash(gid): + reg = 170 + output = 0 + for i in range(40, -8, -8): + reg += (gid >> i) & 0xff + reg *= 48271 + reg %= 2**32 + reg += 1 + reg %= (2**31 - 1) + output ^= reg + return (output >> 16) ^ (output & 0xffff) + + +def encode_pro_packet(preamble_len, in_bytes): + packet_type = in_bytes[0] & 0x3f + nsym = 4 if packet_type == PRO_SYNC else 8 + + length = len(in_bytes) + 2 + nsym + packet = bytes([length]) + in_bytes + packet += struct.pack(">H", crc16(packet)) + rs = reedsolo.RSCodec(nsym=nsym, fcr=1) + packet = bytes(p ^ w for p, w in zip(rs.encode(packet), WHITENING)) + symbols = [2, 0]*preamble_len + [0, 2, 3, 1, 3, 1, 1, 0] + for p in packet: + symbols.append(p >> 6) + symbols.append((p >> 4) & 0b11) + symbols.append((p >> 2) & 0b11) + symbols.append(p & 0b11) + return symbols + + +def encode_pro_control_packet(num_data_packets, transmitter_gid): + packet = bytes([(num_data_packets << 6) | PRO_SYNC, 0x00, 0x00, 0x60, 0xff, 0xff]) \ + + struct.pack("H", gid_hash(sender_gid)) \ + + base_message.SerializeToString() + + if message_type == "PUBLICKEY": + base_message.header.messageType = data_type_pb2.PUBLIC_KEY_REQUEST + publickey = message_pb2.PBPublicKeyMessageData() + publickey.public_key = base64.b64decode(publickey_data) + base_message.messageData = publickey.SerializeToString() + + payload = bytes([(ConversationType.PRIVATE.value << 2) | 2, 0x80, 0x00, mode, counter, 0x00, 0x56, 0x28]) \ + + struct.pack(">H", gid_hash(sender_gid)) \ + + struct.pack(">H", gid_hash(recipient_gid)) \ + + base_message.SerializeToString() + + + return encode_pro_packets(payload, sender_gid) + + +def pro_gfsk_symbols(packets): + data = [4]*PRO_HOP_TIME + for packet in packets: + data += packet + [4]*PRO_HOP_TIME + return data + + +def decrypt_qr_message(password, salt, iv, sender_gid, key_data): + kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=32, salt=salt, iterations=10000) + key = kdf.derive(password) + + full_iv = iv + struct.pack("= len(mesh_fragments): + print(f"Invalid fragment index: {fragIDX}") + return + + mesh_fragments[fragIDX] = data + if all(mesh_fragments): + data = b"".join(mesh_fragments) + if pro: + process_data_pro(data) + else: + process_data_mesh(data) + + +def process_data_mesh(data): + """Process a reassembled Gotenna Mesh packet""" + + offset = 0 + + conv_type = ConversationType(data[offset]) + print(f"Conversation type: {conv_type.name}") + offset += 1 + + app_id = struct.unpack(">H", data[offset:offset+2])[0] + print(f"App ID: {app_id:04x}") + offset += 2 + + if conv_type in (ConversationType.PRIVATE, ConversationType.GROUP): + gid_hi, gid_lo = struct.unpack(">HL", data[offset:offset+6]) + gid = (gid_hi << 32) | gid_lo + print(f"Dest. GID: {gid}") + offset += 6 + + print(f"Unknown: {data[offset:offset+4].hex()}") + offset += 4 + + decode_tlvs(data[offset:]) + + +def process_data_pro(data): + """Process a reassembled Gotenna Pro packet""" + + print(f"Reassembled packet: {data.hex()}") + + offset = 0 + + conv_type = ConversationType(data[offset] >> 2) + print(f"Conversation type: {conv_type.name}") + offset += 1 + + print(f"Unknown: {data[offset:offset+2].hex()}") + offset += 2 + mode = data[offset] + print(f"mode: {hex(data[offset])}") + offset += 1 + print(f"Counter: {data[offset]}") + offset += 1 + print(f"Unknown: {data[offset:offset+3].hex()}") + offset += 3 + print(f"Sender GID hash: {data[offset:offset+2].hex()}") + offset += 2 + + if conv_type in (ConversationType.PRIVATE, ConversationType.GROUP): + recipient_gid_hash = struct.unpack(">H", data[offset:offset+2])[0] + offset += 2 + print(f"Recipient GID hash: {recipient_gid_hash:04x}") + recipient_gid = None + key_store = public_keys if conv_type == ConversationType.PRIVATE else group_keys + for gid in key_store.keys(): + if gid_hash(gid) == recipient_gid_hash: + recipient_gid = gid + print(f"Found recipient GID: {recipient_gid}") + break + + if mode == 0x80: + print(f"Unknown: {data[offset:].hex()}") + return + elif mode == 0x20: + print(f"Unknown: {data[offset:].hex()}") + return + else: + base_message = base_message_pb2.PBBaseMessage() + try: + base_message.ParseFromString(data[offset:]) + except google.protobuf.message.DecodeError: + print("Error parsing base message") + return + + print(json_format.MessageToJson(base_message.header)) + + message_type = base_message.header.messageType + + if message_type == data_type_pb2.PBMessageDataType.GROUP_CREATION: + message = None + priv_key = private_keys.get(base_message.header.sender_gid) + pub_key = public_keys.get(recipient_gid) + if not (priv_key and pub_key): + priv_key = private_keys.get(recipient_gid) + pub_key = public_keys.get(base_message.header.sender_gid) + + if priv_key and pub_key: + ec_priv_key = load_der_private_key(priv_key, None) + curve = ec.SECP384R1() + ec_pub_key = ec.EllipticCurvePublicKey.from_encoded_point(curve, pub_key) + + shared_key = ec_priv_key.exchange(ec.ECDH(), ec_pub_key) + base_message.messageData = decrypt_p2p_group(base_message.messageData, + shared_key, + base_message.header.sender_gid, + base_message.header.iv) + message = message_pb2.PBGroupCreationMessageData() + elif message_type == data_type_pb2.PBMessageDataType.MAP_OBJECT: + message = message_pb2.PBMapObjectMessageData() + elif message_type == data_type_pb2.PBMessageDataType.REQUEST: + message = message_pb2.PBRequestMessageData() + elif message_type == data_type_pb2.PBMessageDataType.TEXT: + if base_message.header.iv: + message = None + if conv_type == ConversationType.BROADCAST: + uuid = base_message.header.keyUuid + if uuid in broadcast_keys: + shared_key = broadcast_keys[uuid] + base_message.messageData = decrypt_broadcast(base_message.messageData, + shared_key, + base_message.header.sender_gid, + base_message.header.iv) + message = message_pb2.PBTextMessageData() + if conv_type == ConversationType.GROUP: + shared_key = group_keys.get(recipient_gid) + if shared_key: + base_message.messageData = decrypt_p2p_group(base_message.messageData, + shared_key, + base_message.header.sender_gid, + base_message.header.iv) + message = message_pb2.PBTextMessageData() + if conv_type == ConversationType.PRIVATE: + priv_key = private_keys.get(base_message.header.sender_gid) + pub_key = public_keys.get(recipient_gid) + if not (priv_key and pub_key): + priv_key = private_keys.get(recipient_gid) + pub_key = public_keys.get(base_message.header.sender_gid) + + if priv_key and pub_key: + ec_priv_key = load_der_private_key(priv_key, None) + curve = ec.SECP384R1() + ec_pub_key = ec.EllipticCurvePublicKey.from_encoded_point(curve, pub_key) + + shared_key = ec_priv_key.exchange(ec.ECDH(), ec_pub_key) + base_message.messageData = decrypt_p2p_group(base_message.messageData, + shared_key, + base_message.header.sender_gid, + base_message.header.iv) + message = message_pb2.PBTextMessageData() + else: + message = message_pb2.PBTextMessageData() + elif message_type == data_type_pb2.PBMessageDataType.FREQUENCY: + message = message_pb2.PBFrequencyMessageData() + elif message_type == data_type_pb2.PBMessageDataType.PING: + message = None + elif message_type == data_type_pb2.PBMessageDataType.EMERGENCY_BEACON: + message = None + elif message_type == data_type_pb2.PBMessageDataType.PUBLIC_KEY_REQUEST: + message = message_pb2.PBPublicKeyMessageData() + elif message_type == data_type_pb2.PBMessageDataType.PUBLIC_KEY_RESPONSE: + message = message_pb2.PBPublicKeyMessageData() + elif message_type == data_type_pb2.PBMessageDataType.BROADCAST_QR: + message = message_pb2.PBBroadcastQrMessageData() + elif message_type == data_type_pb2.PBMessageDataType.PLI: + message = None + elif message_type == data_type_pb2.PBMessageDataType.SHARED_LOCATION: + message = message_pb2.PBLocationMessageData() + elif message_type == data_type_pb2.PBMessageDataType.COMMS_CHECK: + message = None + elif message_type == data_type_pb2.PBMessageDataType.MANUAL_PLI: + message = None + + if message: + message.ParseFromString(base_message.messageData) + + if isinstance(message, message_pb2.PBLocationMessageData): + json_dict = json.loads(json_format.MessageToJson(message)) + json_dict['coordinate'] = decode_coords(message.coordinate) + print(json.dumps(json_dict, indent=2)) + elif isinstance(message, message_pb2.PBMapObjectMessageData): + json_dict = json.loads(json_format.MessageToJson(message)) + if message.HasField("pin"): + json_dict['pin']['coordinate'] = decode_coords(message.pin.coordinate) + elif message.HasField("shape"): + if message.shape.HasField("circle_data"): + json_dict['shape']['circleData']['center'] = decode_coords(message.shape.circle_data.center) + elif message.shape.HasField("rectangle_data"): + json_dict['shape']['rectangleData']['cornerOne'] = decode_coords(message.shape.rectangle_data.cornerOne) + json_dict['shape']['rectangleData']['cornerTwo'] = decode_coords(message.shape.rectangle_data.cornerTwo) + json_dict['shape']['rectangleData']['depth'] = decode_coords(message.shape.rectangle_data.depth) + elif message.shape.HasField("perimeter_data"): + json_dict['shape']['perimeterData']['dataPoints'] = decode_coords_list(message.shape.perimeter_data.data_points) + elif message.shape.HasField("route_data"): + json_dict['shape']['routeData']['dataPoints'] = decode_coords_list(message.shape.route_data.data_points) + print(json.dumps(json_dict, indent=2)) + elif isinstance(message, message_pb2.PBPublicKeyMessageData): + curve = ec.SECP384R1() + public_key = ec.EllipticCurvePublicKey.from_encoded_point(curve, message.public_key) + print(public_key.public_numbers()) + print(json_format.MessageToJson(message)) + else: + print(json_format.MessageToJson(message)) + + # Store any observed keys + if isinstance(message, message_pb2.PBGroupCreationMessageData): + group_keys[message.group_gid] = message.group_shared_key + save_keys() + elif isinstance(message, message_pb2.PBPublicKeyMessageData): + public_keys[base_message.header.sender_gid] = message.public_key + save_keys() + + else: + print(f"Encrypted/unknown message: {base_message.messageData.hex()}") + + +def correct_packet(data): + data = bytes(data) + packet_type = data[1] & 0x3f + + # Reed-Solomon error correction + nsym = 4 if packet_type == PRO_SYNC else 8 + rs = reedsolo.RSCodec(nsym=nsym, fcr=1) + try: + data, _, _ = rs.decode(data) + except reedsolo.ReedSolomonError: + raise Exception("Too many byte errors") + + # CRC check + packetCRC, = struct.unpack(">H", data[-2:]) + diff = packetCRC ^ crc16(data[:-2]) + if diff not in (0x0000, 0xabcd): + raise Exception("Invalid CRC") + + # Strip length header & CRC + return data[1:-2] + + +def ingest_packet(data): + global mesh_fragments + + print() + print(data.hex()) + packet_type = data[0] & 0x3f + + if packet_type == MESH_SYNC: + # Gotenna Mesh "SYNC" packet - contains data channel index and TTL data + chIDX, frags, iniTTL, curTTL = struct.unpack("BBBB", data[1:5]) + print(f"RX SYNC (0x{packet_type:02x}): chIDX={chIDX}, frags={frags}, iniTTL={iniTTL}, curTTL={curTTL}") + mesh_fragments = [None] * frags + # in real life, hop to index ++chIDX in datachan map + # to receive first data packet of this transmission + + elif packet_type == MESH_DATA: + # Gotenna Mesh "DATA" packet - contains message data, may be fragmented + datalen, fragIDX = struct.unpack("BB", data[1:3]) + print(f"RX DATA (0x{packet_type:02x}): len={datalen}, fragIDX={fragIDX}") + # strip first 4 bytes and send to reassembly + process_data_frag(data[3:], fragIDX) + # in real life, hop to index ++chIDX in datachan map + # to receive *next* data packet of this transmission + + elif packet_type == MESH_ACK: + # Gotenna Mesh "ACK" packet - contains message hash ID and number of hops + hashID, hopTTL, curTTL = struct.unpack(">HBB", data[1:5]) + print(f"RX ACK (0x{packet_type:02x}): hash=0x{hashID:04x}, TTL/hop(?)=0x{hopTTL:02x}, curTTL={curTTL}") + + elif packet_type == PRO_SYNC: + # Gotenna Pro "SYNC" packet + frags = data[0] >> 6 + print(f"RX SYNC (0x{packet_type:02x}): frags={frags} unknown={data[1:].hex()}") + mesh_fragments = [None] * frags + + elif packet_type == PRO_DATA: + # Gotenna Pro "DATA" packet + fragIDX = data[0] >> 6 + print(f"RX DATA (0x{packet_type:02x}): fragIDX={fragIDX}") + process_data_frag(data[1:], fragIDX, pro=True) + + else: + print(f"RX TYPE 0x{packet_type:02x} UNKNOWN") + + +xdg_data = os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) +data_dir = os.path.join(xdg_data, "gr-tenna") +os.makedirs(data_dir, exist_ok=True) +keys_filename = os.path.join(data_dir, "keys.csv") + + +def save_keys(filename=keys_filename): + with open(filename, "w", newline="") as csv_file: + keys_writer = csv.DictWriter(csv_file, fieldnames=["key_type", "name", "value_base64"]) + keys_writer.writeheader() + for key_type, key_store in key_types.items(): + for name, value in key_store.items(): + keys_writer.writerow({"key_type": key_type, "name": name, "value_base64": base64.b64encode(value).decode("ASCII")}) + + +def load_keys(filename=keys_filename): + if os.path.exists(keys_filename): + with open(filename, newline="") as csv_file: + keys_reader = csv.DictReader(csv_file) + for row in keys_reader: + key_type = row["key_type"] + name = row["name"] if key_type == "broadcast" else int(row["name"]) + value = base64.b64decode(row["value_base64"]) + + key_store = key_types[key_type] + key_store[name] = value + + +load_keys() diff --git a/challenges/gotenna_packet/proto/__init__.py b/challenges/gotenna_packet/proto/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/challenges/gotenna_packet/proto/base_message_pb2.py b/challenges/gotenna_packet/proto/base_message_pb2.py new file mode 100644 index 0000000..5825dd2 --- /dev/null +++ b/challenges/gotenna_packet/proto/base_message_pb2.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: gotenna_packet/proto/base_message.proto +# Protobuf Python Version: 4.25.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import header_pb2 as gotenna__packet_dot_proto_dot_header__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'gotenna_packet/proto/base_message.proto\x1a!gotenna_packet/proto/header.proto\"C\n\rPBBaseMessage\x12\x1d\n\x06header\x18\x01 \x01(\x0b\x32\r.PBBaseHeader\x12\x13\n\x0bmessageData\x18\x02 \x01(\x0c\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.base_message_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_PBBASEMESSAGE']._serialized_start=78 + _globals['_PBBASEMESSAGE']._serialized_end=145 +# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_packet/proto/data_type_pb2.py b/challenges/gotenna_packet/proto/data_type_pb2.py new file mode 100644 index 0000000..803d920 --- /dev/null +++ b/challenges/gotenna_packet/proto/data_type_pb2.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: gotenna_packet/proto/data_type.proto +# Protobuf Python Version: 4.25.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$gotenna_packet/proto/data_type.proto*\xff\x01\n\x11PBMessageDataType\x12\x12\n\x0eGROUP_CREATION\x10\x00\x12\x0e\n\nMAP_OBJECT\x10\x01\x12\x0b\n\x07REQUEST\x10\x02\x12\x08\n\x04TEXT\x10\x03\x12\r\n\tFREQUENCY\x10\x04\x12\x08\n\x04PING\x10\x05\x12\x14\n\x10\x45MERGENCY_BEACON\x10\x06\x12\x16\n\x12PUBLIC_KEY_REQUEST\x10\x07\x12\x17\n\x13PUBLIC_KEY_RESPONSE\x10\x08\x12\x10\n\x0c\x42ROADCAST_QR\x10\t\x12\x07\n\x03PLI\x10\n\x12\x13\n\x0fSHARED_LOCATION\x10\x0b\x12\x0f\n\x0b\x43OMMS_CHECK\x10\x0c\x12\x0e\n\nMANUAL_PLI\x10\r*e\n\x0ePBGeofenceType\x12\x13\n\x0fGEOFENCING_NONE\x10\x00\x12\x14\n\x10GEOFENCING_ENTRY\x10\x01\x12\x13\n\x0fGEOFENCING_EXIT\x10\x02\x12\x13\n\x0fGEOFENCING_BOTH\x10\x03*V\n\x0bPBPowerType\x12\r\n\tHALF_WATT\x10\x00\x12\x0c\n\x08ONE_WATT\x10\x01\x12\x0c\n\x08TWO_WATT\x10\x02\x12\r\n\tFOUR_WATT\x10\x03\x12\r\n\tFIVE_WATT\x10\x04*B\n\x0fPBBandwidthType\x12\x0e\n\nBW_4_84KHZ\x10\x00\x12\x0e\n\nBW_7_28KHZ\x10\x01\x12\x0f\n\x0b\x42W_11_80KHZ\x10\x02*4\n\tPBPinType\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\r\n\tEMERGENCY\x10\x01\x12\x0b\n\x07WARNING\x10\x02*.\n\x12PBConversationType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0b\n\x07\x43OMMAND\x10\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.data_type_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_PBMESSAGEDATATYPE']._serialized_start=41 + _globals['_PBMESSAGEDATATYPE']._serialized_end=296 + _globals['_PBGEOFENCETYPE']._serialized_start=298 + _globals['_PBGEOFENCETYPE']._serialized_end=399 + _globals['_PBPOWERTYPE']._serialized_start=401 + _globals['_PBPOWERTYPE']._serialized_end=487 + _globals['_PBBANDWIDTHTYPE']._serialized_start=489 + _globals['_PBBANDWIDTHTYPE']._serialized_end=555 + _globals['_PBPINTYPE']._serialized_start=557 + _globals['_PBPINTYPE']._serialized_end=609 + _globals['_PBCONVERSATIONTYPE']._serialized_start=611 + _globals['_PBCONVERSATIONTYPE']._serialized_end=657 +# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_packet/proto/frequency_pb2.py b/challenges/gotenna_packet/proto/frequency_pb2.py new file mode 100644 index 0000000..6b00ebe --- /dev/null +++ b/challenges/gotenna_packet/proto/frequency_pb2.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: gotenna_packet/proto/frequency.proto +# Protobuf Python Version: 4.25.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import data_type_pb2 as gotenna__packet_dot_proto_dot_data__type__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$gotenna_packet/proto/frequency.proto\x1a$gotenna_packet/proto/data_type.proto\".\n\tPBChannel\x12\r\n\x05hertz\x18\x01 \x01(\r\x12\x12\n\nis_control\x18\x02 \x01(\x08\"\xad\x01\n\x0bPBFrequency\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x11\n\tcall_sign\x18\x03 \x01(\t\x12\x1b\n\x05power\x18\x04 \x01(\x0e\x32\x0c.PBPowerType\x12#\n\tbandwidth\x18\x05 \x01(\x0e\x32\x10.PBBandwidthType\x12\x10\n\x08use_only\x18\x06 \x01(\x08\x12\x1c\n\x08\x63hannels\x18\x07 \x03(\x0b\x32\n.PBChannelb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.frequency_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_PBCHANNEL']._serialized_start=78 + _globals['_PBCHANNEL']._serialized_end=124 + _globals['_PBFREQUENCY']._serialized_start=127 + _globals['_PBFREQUENCY']._serialized_end=300 +# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_packet/proto/header_pb2.py b/challenges/gotenna_packet/proto/header_pb2.py new file mode 100644 index 0000000..790158d --- /dev/null +++ b/challenges/gotenna_packet/proto/header_pb2.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: gotenna_packet/proto/header.proto +# Protobuf Python Version: 4.25.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import data_type_pb2 as gotenna__packet_dot_proto_dot_data__type__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!gotenna_packet/proto/header.proto\x1a$gotenna_packet/proto/data_type.proto\"\xbc\x01\n\x0cPBBaseHeader\x12\x12\n\nsender_gid\x18\x01 \x01(\x04\x12\x10\n\x08\x63\x61llsign\x18\x02 \x01(\t\x12\'\n\x0bmessageType\x18\x03 \x01(\x0e\x32\x12.PBMessageDataType\x12\x0f\n\x07keyUuid\x18\x04 \x01(\t\x12\n\n\x02iv\x18\x05 \x01(\x0c\x12-\n\x10\x63onversationType\x18\x06 \x01(\x0e\x32\x13.PBConversationType\x12\x11\n\ttimestamp\x18\x07 \x01(\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.header_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_PBBASEHEADER']._serialized_start=76 + _globals['_PBBASEHEADER']._serialized_end=264 +# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_packet/proto/location_pb2.py b/challenges/gotenna_packet/proto/location_pb2.py new file mode 100644 index 0000000..51355f3 --- /dev/null +++ b/challenges/gotenna_packet/proto/location_pb2.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: gotenna_packet/proto/location.proto +# Protobuf Python Version: 4.25.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import data_type_pb2 as gotenna__packet_dot_proto_dot_data__type__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#gotenna_packet/proto/location.proto\x1a$gotenna_packet/proto/data_type.proto\"\x90\x02\n\x0bPBShapeData\x12\r\n\x05\x63olor\x18\x01 \x01(\r\x12&\n\rgeofence_type\x18\x02 \x01(\x0e\x32\x0f.PBGeofenceType\x12)\n\nroute_data\x18\x04 \x01(\x0b\x32\x13.PBRouteMessageDataH\x00\x12\x31\n\x0eperimeter_data\x18\x05 \x01(\x0b\x32\x17.PBPerimeterMessageDataH\x00\x12+\n\x0b\x63ircle_data\x18\x06 \x01(\x0b\x32\x14.PBCircleMessageDataH\x00\x12\x31\n\x0erectangle_data\x18\x07 \x01(\x0b\x32\x17.PBRectangleMessageDataH\x00\x42\x0c\n\nmap_object\"=\n\tPBPinData\x12\x12\n\ncoordinate\x18\x01 \x01(\x0c\x12\x1c\n\x08pin_type\x18\x02 \x01(\x0e\x32\n.PBPinType\")\n\x12PBRouteMessageData\x12\x13\n\x0b\x64\x61ta_points\x18\x01 \x01(\x0c\"-\n\x16PBPerimeterMessageData\x12\x13\n\x0b\x64\x61ta_points\x18\x01 \x01(\x0c\"5\n\x13PBCircleMessageData\x12\x0e\n\x06\x63\x65nter\x18\x01 \x01(\x0c\x12\x0e\n\x06radius\x18\x02 \x01(\x04\"M\n\x16PBRectangleMessageData\x12\x11\n\tcornerOne\x18\x01 \x01(\x0c\x12\x11\n\tcornerTwo\x18\x02 \x01(\x0c\x12\r\n\x05\x64\x65pth\x18\x03 \x01(\x0c\"3\n\x0cPBCoordinate\x12\x10\n\x08latitude\x18\x01 \x01(\x01\x12\x11\n\tlongitude\x18\x02 \x01(\x01\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.location_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_PBSHAPEDATA']._serialized_start=78 + _globals['_PBSHAPEDATA']._serialized_end=350 + _globals['_PBPINDATA']._serialized_start=352 + _globals['_PBPINDATA']._serialized_end=413 + _globals['_PBROUTEMESSAGEDATA']._serialized_start=415 + _globals['_PBROUTEMESSAGEDATA']._serialized_end=456 + _globals['_PBPERIMETERMESSAGEDATA']._serialized_start=458 + _globals['_PBPERIMETERMESSAGEDATA']._serialized_end=503 + _globals['_PBCIRCLEMESSAGEDATA']._serialized_start=505 + _globals['_PBCIRCLEMESSAGEDATA']._serialized_end=558 + _globals['_PBRECTANGLEMESSAGEDATA']._serialized_start=560 + _globals['_PBRECTANGLEMESSAGEDATA']._serialized_end=637 + _globals['_PBCOORDINATE']._serialized_start=639 + _globals['_PBCOORDINATE']._serialized_end=690 +# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_packet/proto/message_pb2.py b/challenges/gotenna_packet/proto/message_pb2.py new file mode 100644 index 0000000..00a361d --- /dev/null +++ b/challenges/gotenna_packet/proto/message_pb2.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: gotenna_packet/proto/message.proto +# Protobuf Python Version: 4.25.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from . import location_pb2 as gotenna__packet_dot_proto_dot_location__pb2 +from . import frequency_pb2 as gotenna__packet_dot_proto_dot_frequency__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"gotenna_packet/proto/message.proto\x1a#gotenna_packet/proto/location.proto\x1a$gotenna_packet/proto/frequency.proto\"\x90\x01\n\x16PBMapObjectMessageData\x12\x11\n\ttimestamp\x18\x01 \x01(\x01\x12\x0c\n\x04uuid\x18\x02 \x01(\x04\x12\r\n\x05title\x18\x03 \x01(\t\x12\x1d\n\x05shape\x18\x04 \x01(\x0b\x32\x0c.PBShapeDataH\x00\x12\x19\n\x03pin\x18\x05 \x01(\x0b\x32\n.PBPinDataH\x00\x42\x0c\n\nmap_object\"\x90\x01\n\x15PBLocationMessageData\x12\x12\n\ncoordinate\x18\x01 \x01(\x0c\x12#\n\x1bpli_sharing_frequency_index\x18\x02 \x01(\r\x12\x1d\n\x15pli_location_accuracy\x18\x03 \x01(\r\x12\x11\n\ttimestamp\x18\x04 \x01(\x01\x12\x0c\n\x04text\x18\x05 \x01(\t\"$\n\x14PBRequestMessageData\x12\x0c\n\x04text\x18\x01 \x01(\t\"/\n\x11PBTextMessageData\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x0c\n\x04uuid\x18\x02 \x01(\t\"G\n\x16PBFrequencyMessageData\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x1f\n\tfrequency\x18\x02 \x01(\x0b\x32\x0c.PBFrequency\"\xa9\x01\n\x1aPBGroupCreationMessageData\x12@\n\rgroup_members\x18\x01 \x03(\x0b\x32).PBGroupCreationMessageData.PBGroupMember\x12\x18\n\x10group_shared_key\x18\x02 \x01(\x0c\x12\x11\n\tgroup_gid\x18\x03 \x01(\x04\x1a\x1c\n\rPBGroupMember\x12\x0b\n\x03gid\x18\x01 \x01(\x04\",\n\x16PBPublicKeyMessageData\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\"a\n\x18PBBroadcastQrMessageData\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x0c\n\x04uuid\x18\x02 \x01(\x0c\x12\x0c\n\x04salt\x18\x03 \x01(\x0c\x12\n\n\x02iv\x18\x04 \x01(\x0c\x12\x0f\n\x07keyData\x18\x05 \x01(\x0c\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.message_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_PBMAPOBJECTMESSAGEDATA']._serialized_start=114 + _globals['_PBMAPOBJECTMESSAGEDATA']._serialized_end=258 + _globals['_PBLOCATIONMESSAGEDATA']._serialized_start=261 + _globals['_PBLOCATIONMESSAGEDATA']._serialized_end=405 + _globals['_PBREQUESTMESSAGEDATA']._serialized_start=407 + _globals['_PBREQUESTMESSAGEDATA']._serialized_end=443 + _globals['_PBTEXTMESSAGEDATA']._serialized_start=445 + _globals['_PBTEXTMESSAGEDATA']._serialized_end=492 + _globals['_PBFREQUENCYMESSAGEDATA']._serialized_start=494 + _globals['_PBFREQUENCYMESSAGEDATA']._serialized_end=565 + _globals['_PBGROUPCREATIONMESSAGEDATA']._serialized_start=568 + _globals['_PBGROUPCREATIONMESSAGEDATA']._serialized_end=737 + _globals['_PBGROUPCREATIONMESSAGEDATA_PBGROUPMEMBER']._serialized_start=709 + _globals['_PBGROUPCREATIONMESSAGEDATA_PBGROUPMEMBER']._serialized_end=737 + _globals['_PBPUBLICKEYMESSAGEDATA']._serialized_start=739 + _globals['_PBPUBLICKEYMESSAGEDATA']._serialized_end=783 + _globals['_PBBROADCASTQRMESSAGEDATA']._serialized_start=785 + _globals['_PBBROADCASTQRMESSAGEDATA']._serialized_end=882 +# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_pro_bladerf.py b/challenges/gotenna_pro_bladerf.py new file mode 100644 index 0000000..7b99f58 --- /dev/null +++ b/challenges/gotenna_pro_bladerf.py @@ -0,0 +1,459 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# +# SPDX-License-Identifier: GPL-3.0 +# +# GNU Radio Python Flow Graph +# Title: Gotenna Pro Tx Bladerf +# GNU Radio version: 3.10.7.0 + +from gnuradio import blocks +from gnuradio import digital +from gnuradio import filter +from gnuradio.filter import firdes +from gnuradio import gr +import osmosdr +import sys +import signal +from argparse import ArgumentParser +from gnuradio.eng_arg import eng_float, intx +from gnuradio import eng_notation +import math + +try: + from . import gotenna_packet +except ImportError: + import gotenna_packet + + +def validate_options(options): + packets = gotenna_packet.encode_pro_broadcast_packets( + options.message_type, + options.counter_num, + options.sender_gid, + options.recipient_gid, + options.callsign, + options.message, + options.publickey_data) + gotenna_packet.pro_gfsk_symbols(packets) + return packets + + +class gotenna_pro_tx_bladerf(gr.top_block): + + def __init__( + self, + baud_rate=9600, + bandwidth=1000000, + device_args="bladerf=0,biastee=1", + antenna='', + callsign='VE3IRR', + counter_num=0, + frequency=450000000, + if_gain=0, + message='Hello world!', + message_type='BROADCAST', + publickey_data='base64key', + recipient_gid=1234567891, + rf_gain=60, + sender_gid=1234567890, + silence_time=4000, + bb_gain=0): + try: + gr.top_block.__init__(self, "Gotenna Pro Tx Bladerf", catch_exceptions=True) + except TypeError: + gr.top_block.__init__(self, "Gotenna Pro Tx Bladerf") + + if baud_rate not in (2400, 4800, 9600): + raise ValueError("goTenna Pro baud_rate must be one of: 2400, 4800, 9600") + + self.baud_rate = baud_rate + self.bandwidth = bandwidth + self.bb_gain = bb_gain + self.device_args = device_args + self.antenna = antenna + self.callsign = callsign + self.counter_num = counter_num + self.frequency = frequency + self.if_gain = if_gain + self.message = message + self.message_type = message_type + self.publickey_data = publickey_data + self.recipient_gid = recipient_gid + self.rf_gain = rf_gain + self.sender_gid = sender_gid + self.silence_time = silence_time + + self.samp_per_sym = samp_per_sym = 4 + self.offset = offset = 250000 + self.interp = interp = 480000 // baud_rate + self.taps = taps = firdes.gaussian(1.0, samp_per_sym, 0.5, 64) + self.samp_rate = samp_rate = baud_rate * samp_per_sym * interp + self.packets = packets = validate_options(self) + self.hw_rate = hw_rate = 5760000 + self.fsk_deviation_hz = fsk_deviation_hz = {2400: 400, 4800: 750, 9600: 1100}[baud_rate] + self.data_chan = data_chan = 2 + self.control_chan = control_chan = 2 + self.center_freq = center_freq = frequency - offset + + self.rational_resampler_xxx_0 = filter.rational_resampler_ccc( + interpolation=(hw_rate // (baud_rate * samp_per_sym)), + decimation=1, + taps=[], + fractional_bw=0) + self.osmosdr_sink_0 = osmosdr.sink( + args="numchan=" + str(1) + " " + device_args + ) + self.osmosdr_sink_0.set_time_unknown_pps(osmosdr.time_spec_t()) + self.osmosdr_sink_0.set_sample_rate(hw_rate) + self.osmosdr_sink_0.set_center_freq(center_freq, 0) + self.osmosdr_sink_0.set_freq_corr(0, 0) + self.osmosdr_sink_0.set_gain(rf_gain, 0) + self.osmosdr_sink_0.set_if_gain(if_gain, 0) + self.osmosdr_sink_0.set_bb_gain(bb_gain, 0) + self.osmosdr_sink_0.set_antenna(antenna, 0) + self.osmosdr_sink_0.set_bandwidth(bandwidth, 0) + self.interp_fir_filter_xxx_0 = filter.interp_fir_filter_fff(1, taps) + self.interp_fir_filter_xxx_0.declare_sample_delay(0) + self.digital_map_bb_0 = digital.map_bb([-3, -1, 3, 1, 0]) + self.blocks_vector_source_x_2 = blocks.vector_source_f([offset], True, 1, []) + self.blocks_vector_source_x_1 = blocks.vector_source_c([1], True, 1, []) + self.blocks_vector_source_x_0 = blocks.vector_source_b( + [0] * silence_time + gotenna_packet.pro_gfsk_symbols(packets) + [0] * silence_time, + False, 1, []) + self.blocks_vco_c_1 = blocks.vco_c((baud_rate * samp_per_sym), (2 * math.pi * fsk_deviation_hz), 1.0) + self.blocks_vco_c_0 = blocks.vco_c(hw_rate, (2 * math.pi), 0.9) + self.blocks_repeat_2 = blocks.repeat(gr.sizeof_float * 1, samp_per_sym) + self.blocks_repeat_1 = blocks.repeat( + gr.sizeof_float * 1, + (8 * samp_per_sym * (hw_rate // (baud_rate * samp_per_sym)))) + self.blocks_repeat_0 = blocks.repeat(gr.sizeof_gr_complex * 1, (8 * samp_per_sym)) + self.blocks_multiply_xx_1 = blocks.multiply_vcc(1) + self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) + self.blocks_char_to_float_0 = blocks.char_to_float(1, 1) + + self.connect((self.blocks_char_to_float_0, 0), (self.blocks_repeat_2, 0)) + self.connect((self.blocks_multiply_xx_0, 0), (self.rational_resampler_xxx_0, 0)) + self.connect((self.blocks_multiply_xx_1, 0), (self.osmosdr_sink_0, 0)) + self.connect((self.blocks_repeat_0, 0), (self.blocks_multiply_xx_0, 1)) + self.connect((self.blocks_repeat_1, 0), (self.blocks_vco_c_0, 0)) + self.connect((self.blocks_repeat_2, 0), (self.interp_fir_filter_xxx_0, 0)) + self.connect((self.blocks_vco_c_0, 0), (self.blocks_multiply_xx_1, 1)) + self.connect((self.blocks_vco_c_1, 0), (self.blocks_multiply_xx_0, 0)) + self.connect((self.blocks_vector_source_x_0, 0), (self.digital_map_bb_0, 0)) + self.connect((self.blocks_vector_source_x_1, 0), (self.blocks_repeat_0, 0)) + self.connect((self.blocks_vector_source_x_2, 0), (self.blocks_repeat_1, 0)) + self.connect((self.digital_map_bb_0, 0), (self.blocks_char_to_float_0, 0)) + self.connect((self.interp_fir_filter_xxx_0, 0), (self.blocks_vco_c_1, 0)) + self.connect((self.rational_resampler_xxx_0, 0), (self.blocks_multiply_xx_1, 0)) + + def get_baud_rate(self): + return self.baud_rate + + def set_baud_rate(self, baud_rate): + if baud_rate not in (2400, 4800, 9600): + raise ValueError("goTenna Pro baud_rate must be one of: 2400, 4800, 9600") + self.baud_rate = baud_rate + self.set_fsk_deviation_hz({2400: 400, 4800: 750, 9600: 1100}[self.baud_rate]) + self.set_interp(480000 // self.baud_rate) + self.set_samp_rate(self.baud_rate * self.samp_per_sym * self.interp) + self.blocks_repeat_1.set_interpolation((8 * self.samp_per_sym * (self.hw_rate // (self.baud_rate * self.samp_per_sym)))) + + def get_bandwidth(self): + return self.bandwidth + + def set_bandwidth(self, bandwidth): + self.bandwidth = bandwidth + self.osmosdr_sink_0.set_bandwidth(self.bandwidth, 0) + + def get_bb_gain(self): + return self.bb_gain + + def set_bb_gain(self, bb_gain): + self.bb_gain = bb_gain + self.osmosdr_sink_0.set_bb_gain(self.bb_gain, 0) + + def get_device_args(self): + return self.device_args + + def set_device_args(self, device_args): + self.device_args = device_args + + def get_antenna(self): + return self.antenna + + def set_antenna(self, antenna): + self.antenna = antenna + self.osmosdr_sink_0.set_antenna(self.antenna, 0) + + def get_callsign(self): + return self.callsign + + def set_callsign(self, callsign): + self.callsign = callsign + self.set_packets(gotenna_packet.encode_pro_broadcast_packets( + self.message_type, self.counter_num, self.sender_gid, + self.recipient_gid, self.callsign, self.message, + self.publickey_data)) + + def get_counter_num(self): + return self.counter_num + + def set_counter_num(self, counter_num): + self.counter_num = counter_num + self.set_packets(gotenna_packet.encode_pro_broadcast_packets( + self.message_type, self.counter_num, self.sender_gid, + self.recipient_gid, self.callsign, self.message, + self.publickey_data)) + + def get_frequency(self): + return self.frequency + + def set_frequency(self, frequency): + self.frequency = frequency + self.set_center_freq(self.frequency - self.offset) + + def get_if_gain(self): + return self.if_gain + + def set_if_gain(self, if_gain): + self.if_gain = if_gain + self.osmosdr_sink_0.set_if_gain(self.if_gain, 0) + + def get_message(self): + return self.message + + def set_message(self, message): + self.message = message + self.set_packets(gotenna_packet.encode_pro_broadcast_packets( + self.message_type, self.counter_num, self.sender_gid, + self.recipient_gid, self.callsign, self.message, + self.publickey_data)) + + def get_message_type(self): + return self.message_type + + def set_message_type(self, message_type): + self.message_type = message_type + self.set_packets(gotenna_packet.encode_pro_broadcast_packets( + self.message_type, self.counter_num, self.sender_gid, + self.recipient_gid, self.callsign, self.message, + self.publickey_data)) + + def get_publickey_data(self): + return self.publickey_data + + def set_publickey_data(self, publickey_data): + self.publickey_data = publickey_data + self.set_packets(gotenna_packet.encode_pro_broadcast_packets( + self.message_type, self.counter_num, self.sender_gid, + self.recipient_gid, self.callsign, self.message, + self.publickey_data)) + + def get_recipient_gid(self): + return self.recipient_gid + + def set_recipient_gid(self, recipient_gid): + self.recipient_gid = recipient_gid + self.set_packets(gotenna_packet.encode_pro_broadcast_packets( + self.message_type, self.counter_num, self.sender_gid, + self.recipient_gid, self.callsign, self.message, + self.publickey_data)) + + def get_rf_gain(self): + return self.rf_gain + + def set_rf_gain(self, rf_gain): + self.rf_gain = rf_gain + self.osmosdr_sink_0.set_gain(self.rf_gain, 0) + + def get_sender_gid(self): + return self.sender_gid + + def set_sender_gid(self, sender_gid): + self.sender_gid = sender_gid + self.set_packets(gotenna_packet.encode_pro_broadcast_packets( + self.message_type, self.counter_num, self.sender_gid, + self.recipient_gid, self.callsign, self.message, + self.publickey_data)) + + def get_samp_per_sym(self): + return self.samp_per_sym + + def set_samp_per_sym(self, samp_per_sym): + self.samp_per_sym = samp_per_sym + self.set_samp_rate(self.baud_rate * self.samp_per_sym * self.interp) + self.set_taps(firdes.gaussian(1.0, self.samp_per_sym, 0.5, 64)) + self.blocks_repeat_0.set_interpolation((8 * self.samp_per_sym)) + self.blocks_repeat_1.set_interpolation((8 * self.samp_per_sym * (self.hw_rate // (self.baud_rate * self.samp_per_sym)))) + self.blocks_repeat_2.set_interpolation(self.samp_per_sym) + + def get_offset(self): + return self.offset + + def set_offset(self, offset): + self.offset = offset + self.set_center_freq(self.frequency - self.offset) + self.blocks_vector_source_x_2.set_data([self.offset], []) + + def get_interp(self): + return self.interp + + def set_interp(self, interp): + self.interp = interp + self.set_samp_rate(self.baud_rate * self.samp_per_sym * self.interp) + + def get_taps(self): + return self.taps + + def set_taps(self, taps): + self.taps = taps + self.interp_fir_filter_xxx_0.set_taps(self.taps) + + def get_silence_time(self): + return self.silence_time + + def set_silence_time(self, silence_time): + self.silence_time = silence_time + self.blocks_vector_source_x_0.set_data( + [0] * self.silence_time + gotenna_packet.pro_gfsk_symbols(self.packets) + [0] * self.silence_time, + []) + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + + def get_packets(self): + return self.packets + + def set_packets(self, packets): + self.packets = packets + self.blocks_vector_source_x_0.set_data( + [0] * self.silence_time + gotenna_packet.pro_gfsk_symbols(self.packets) + [0] * self.silence_time, + []) + + def get_hw_rate(self): + return self.hw_rate + + def set_hw_rate(self, hw_rate): + self.hw_rate = hw_rate + self.blocks_repeat_1.set_interpolation((8 * self.samp_per_sym * (self.hw_rate // (self.baud_rate * self.samp_per_sym)))) + self.osmosdr_sink_0.set_sample_rate(self.hw_rate) + + def get_fsk_deviation_hz(self): + return self.fsk_deviation_hz + + def set_fsk_deviation_hz(self, fsk_deviation_hz): + self.fsk_deviation_hz = fsk_deviation_hz + + def get_data_chan(self): + return self.data_chan + + def set_data_chan(self, data_chan): + self.data_chan = data_chan + + def get_control_chan(self): + return self.control_chan + + def set_control_chan(self, control_chan): + self.control_chan = control_chan + + def get_center_freq(self): + return self.center_freq + + def set_center_freq(self, center_freq): + self.center_freq = center_freq + self.osmosdr_sink_0.set_center_freq(self.center_freq, 0) + + +def argument_parser(): + parser = ArgumentParser() + parser.add_argument( + "--baud-rate", dest="baud_rate", type=intx, default=9600, + help="Set Baud rate [default=%(default)r]") + parser.add_argument( + "--bandwidth", dest="bandwidth", type=eng_float, default=eng_notation.num_to_str(float(1000000)), + help="Set transmit bandwidth [default=%(default)r]") + parser.add_argument( + "--bb-gain", dest="bb_gain", type=eng_float, default="0.0", + help="Set BB gain [default=%(default)r]") + parser.add_argument( + "--device-args", dest="device_args", type=str, default='bladerf=0,biastee=1', + help="Set complete Osmocom device arguments [default=%(default)r]") + parser.add_argument( + "--antenna", dest="antenna", type=str, default='', + help="Set transmit antenna; empty string selects backend default [default=%(default)r]") + parser.add_argument( + "--callsign", dest="callsign", type=str, default='VE3IRR', + help="Set Sender callsign [default=%(default)r]") + parser.add_argument( + "--counter-num", dest="counter_num", type=intx, default=0, + help="Set Counter [default=%(default)r]") + parser.add_argument( + "--frequency", dest="frequency", type=eng_float, default=eng_notation.num_to_str(float(450000000)), + help="Set Frequency [default=%(default)r]") + parser.add_argument( + "--if-gain", dest="if_gain", type=eng_float, default="0.0", + help="Set IF gain [default=%(default)r]") + parser.add_argument( + "--message", dest="message", type=str, default='Hello world!', + help="Set Message [default=%(default)r]") + parser.add_argument( + "--message-type", dest="message_type", type=str, default='BROADCAST', + help="Set type [default=%(default)r]") + parser.add_argument( + "--publickey-data", dest="publickey_data", type=str, default='base64key', + help="Set Sender Public Key [default=%(default)r]") + parser.add_argument( + "--recipient-gid", dest="recipient_gid", type=intx, default=1234567891, + help="Set Recipient GID [default=%(default)r]") + parser.add_argument( + "--rf-gain", dest="rf_gain", type=eng_float, default="60.0", + help="Set RF gain [default=%(default)r]") + parser.add_argument( + "--sender-gid", dest="sender_gid", type=intx, default=1234567890, + help="Set Sender GID [default=%(default)r]") + parser.add_argument( + "--silence-time", dest="silence_time", type=intx, default=4000, + help="Set silence padding [default=%(default)r]") + return parser + + +def main(top_block_cls=gotenna_pro_tx_bladerf, options=None): + if options is None: + options = argument_parser().parse_args() + tb = top_block_cls( + baud_rate=options.baud_rate, + bandwidth=options.bandwidth, + bb_gain=options.bb_gain, + device_args=options.device_args, + antenna=options.antenna, + callsign=options.callsign, + counter_num=options.counter_num, + frequency=options.frequency, + if_gain=options.if_gain, + message=options.message, + message_type=options.message_type, + publickey_data=options.publickey_data, + recipient_gid=options.recipient_gid, + rf_gain=options.rf_gain, + sender_gid=options.sender_gid, + silence_time=options.silence_time) + + def sig_handler(sig=None, frame=None): + tb.stop() + tb.wait() + sys.exit(0) + + signal.signal(signal.SIGINT, sig_handler) + signal.signal(signal.SIGTERM, sig_handler) + + tb.start() + tb.wait() + + +if __name__ == '__main__': + main() diff --git a/flags.txt.ci b/flags.txt.ci index 6a87785..2fcf093 100644 --- a/flags.txt.ci +++ b/flags.txt.ci @@ -5,3 +5,4 @@ testflagsfile,1660312800,1660496400 3,SDR_ASK_1,The Silence of the Lambs was filmed in Pittsburgh Pennsylvania.,ask,,,60,90,ham_440,, 4,POCSAG_RX,The Clock That Went Backward,pocsag,198468,,60,90,444500,, 5,LRS_RX,-s 20 -p 238 -pf 1,lrs,,,60,90,467750,, +6,GOTENNA_PRO,RFCTF{gotenna_pro_test},gotenna_pro,"--callsign RFCTF --sender-gid 1234567890 --recipient-gid 1234567891 --baud-rate 9600 --message-type BROADCAST --counter-num 0",,60,90,450000,, From c647d69f783bb740ba710d52c25b67345b21ad71 Mon Sep 17 00:00:00 2001 From: Dollarhyde Date: Thu, 23 Jul 2026 19:28:58 -0400 Subject: [PATCH 2/9] Updated challengectl with gotenna pro --- challengectl.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/challengectl.py b/challengectl.py index 7966968..6221725 100755 --- a/challengectl.py +++ b/challengectl.py @@ -14,7 +14,7 @@ import argparse import subprocess -from challenges import ask, cw, usb_tx, nbfm, spectrum_paint, pocsagtx_osmocom, lrs_pager, lrs_tx +from challenges import ask, cw, usb_tx, nbfm, spectrum_paint, pocsagtx_osmocom, lrs_pager, lrs_tx, gotenna_pro_bladerf def build_database(flagfile, devicefile): """Create sqlite database based on flags file and devices file. Database file name will be based on @@ -232,6 +232,32 @@ def fire_lrs(self, device_string, flag_q, device_q, *flag_args): flag_q.put(flag_args[0]) print("Returned flag to pool") + def fire_gotenna_pro(self, device_string, flag_q, device_q, *flag_args): + print("\nTransmitting goTenna Pro\n") + flag_args = flag_args[0] + flag = flag_args[1] + modopts = flag_args[2] + mintime = flag_args[4] + maxtime = flag_args[5] + freq = int(flag_args[6]) * 1000 + norandsleep = flag_args[8] + + gotennaopts = gotenna_pro_bladerf.argument_parser().parse_args(modopts.split()) + gotennaopts.message = flag + gotennaopts.frequency = freq + gotennaopts.device_args = device_string + + gotenna_pro_bladerf.main(options=gotennaopts) + sleep(3) + disable_amp(device_string) + device_q.put(device_string) + + if(norandsleep == False): + sleep(randint(mintime, maxtime)) + replaceinqueue = flag_args[7] + if(replaceinqueue != False): + flag_q.put(flag_args[0]) + def select_freq(band): """Read from frequencies text file, select row that starts with band argument. From 33bd8ece34afca77fc641339e60e4804a62c5b8d Mon Sep 17 00:00:00 2001 From: Dollarhyde Date: Thu, 23 Jul 2026 19:46:12 -0400 Subject: [PATCH 3/9] Fix import --- challenges/gotenna_pro_bladerf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/challenges/gotenna_pro_bladerf.py b/challenges/gotenna_pro_bladerf.py index 7b99f58..0fab309 100644 --- a/challenges/gotenna_pro_bladerf.py +++ b/challenges/gotenna_pro_bladerf.py @@ -22,7 +22,7 @@ import math try: - from . import gotenna_packet + from .gotenna_packet import gotenna_packet except ImportError: import gotenna_packet From 657debd79ae248894694940a199ae826b6c554c6 Mon Sep 17 00:00:00 2001 From: Dollarhyde Date: Thu, 23 Jul 2026 20:18:33 -0400 Subject: [PATCH 4/9] Fix import --- challenges/gotenna_pro_bladerf.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/challenges/gotenna_pro_bladerf.py b/challenges/gotenna_pro_bladerf.py index 0fab309..b5a3086 100644 --- a/challenges/gotenna_pro_bladerf.py +++ b/challenges/gotenna_pro_bladerf.py @@ -21,10 +21,7 @@ from gnuradio import eng_notation import math -try: - from .gotenna_packet import gotenna_packet -except ImportError: - import gotenna_packet +import challenges.gotenna_packet.gotenna_packet as gotenna_packet def validate_options(options): From 1fc6b1e31ed911afa86cea81d5fc8323eba0dc70 Mon Sep 17 00:00:00 2001 From: Dollarhyde Date: Thu, 23 Jul 2026 21:24:21 -0400 Subject: [PATCH 5/9] Fix import by removing google --- challenges/gotenna_packet/gotenna_packet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/challenges/gotenna_packet/gotenna_packet.py b/challenges/gotenna_packet/gotenna_packet.py index 60c9d15..f0ddee2 100755 --- a/challenges/gotenna_packet/gotenna_packet.py +++ b/challenges/gotenna_packet/gotenna_packet.py @@ -26,7 +26,6 @@ import base64 import csv import os -import google from .proto import base_message_pb2 from .proto import data_type_pb2 from .proto import message_pb2 From ada2638dc45b4fc3b1b34993871bc1a2dab334ac Mon Sep 17 00:00:00 2001 From: Dollarhyde Date: Mon, 27 Jul 2026 02:19:47 -0400 Subject: [PATCH 6/9] Update with multiple flowgraphs --- challengectl.py | 35 +- challenges/gotenna/__init__.py | 4 + challenges/gotenna/gotenna_pro_tx_bladerf.grc | 932 +++++++++++++++++ .../gotenna_pro_tx_bladerf.py} | 242 ++--- challenges/gotenna/gotenna_pro_tx_usrp.grc | 947 ++++++++++++++++++ challenges/gotenna/gotenna_pro_tx_usrp.py | 322 ++++++ devices.txt.format | 1 + 7 files changed, 2299 insertions(+), 184 deletions(-) create mode 100644 challenges/gotenna/__init__.py create mode 100644 challenges/gotenna/gotenna_pro_tx_bladerf.grc rename challenges/{gotenna_pro_bladerf.py => gotenna/gotenna_pro_tx_bladerf.py} (64%) mode change 100644 => 100755 create mode 100644 challenges/gotenna/gotenna_pro_tx_usrp.grc create mode 100755 challenges/gotenna/gotenna_pro_tx_usrp.py diff --git a/challengectl.py b/challengectl.py index 6221725..06937e8 100755 --- a/challengectl.py +++ b/challengectl.py @@ -14,7 +14,8 @@ import argparse import subprocess -from challenges import ask, cw, usb_tx, nbfm, spectrum_paint, pocsagtx_osmocom, lrs_pager, lrs_tx, gotenna_pro_bladerf +from challenges import ask, cw, usb_tx, nbfm, spectrum_paint, pocsagtx_osmocom, lrs_pager, lrs_tx +from challenges.gotenna import gotenna_pro_tx_bladerf, gotenna_pro_tx_usrp def build_database(flagfile, devicefile): """Create sqlite database based on flags file and devices file. Database file name will be based on @@ -242,13 +243,31 @@ def fire_gotenna_pro(self, device_string, flag_q, device_q, *flag_args): freq = int(flag_args[6]) * 1000 norandsleep = flag_args[8] - gotennaopts = gotenna_pro_bladerf.argument_parser().parse_args(modopts.split()) - gotennaopts.message = flag - gotennaopts.frequency = freq - gotennaopts.device_args = device_string - - gotenna_pro_bladerf.main(options=gotennaopts) - sleep(3) + devargs = [arg.strip() for arg in device_string.split(",") if arg.strip() != ""] + devtypes = [arg.lower() for arg in devargs] + gotenna_module = None + if("uhd" in devtypes): + gotenna_module = gotenna_pro_tx_usrp + gotennaopts = gotenna_module.argument_parser().parse_args(modopts.split()) + gotennaopts.device_addr = ",".join([arg for arg in devargs if arg.lower() != "uhd"]) + elif(any(devtype.startswith("hackrf") for devtype in devtypes)): + print("goTenna Pro is not supported on HackRF, skipping device {}".format(device_string)) + elif(any(devtype.startswith("bladerf") for devtype in devtypes)): + gotenna_module = gotenna_pro_tx_bladerf + gotennaopts = gotenna_module.argument_parser().parse_args(modopts.split()) + gotennaopts.device_args = device_string + antenna = get_antenna_port(device_string) + if(antenna != ""): + gotennaopts.antenna = antenna + else: + print("goTenna Pro is not supported on device {}, skipping".format(device_string)) + + if(gotenna_module != None): + gotennaopts.message = flag + gotennaopts.frequency = freq + + gotenna_module.main(options=gotennaopts) + sleep(3) disable_amp(device_string) device_q.put(device_string) diff --git a/challenges/gotenna/__init__.py b/challenges/gotenna/__init__.py new file mode 100644 index 0000000..ca25703 --- /dev/null +++ b/challenges/gotenna/__init__.py @@ -0,0 +1,4 @@ +# The flowgraphs in this package are generated by GNU Radio Companion from the +# .grc files beside them, so they import the packet library under its bare name. +# Importing challenges.gotenna_packet first registers it under that name. +from challenges import gotenna_packet # noqa: F401 diff --git a/challenges/gotenna/gotenna_pro_tx_bladerf.grc b/challenges/gotenna/gotenna_pro_tx_bladerf.grc new file mode 100644 index 0000000..4d93c25 --- /dev/null +++ b/challenges/gotenna/gotenna_pro_tx_bladerf.grc @@ -0,0 +1,932 @@ +options: + parameters: + author: '' + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: '' + description: '' + gen_cmake: 'On' + gen_linking: dynamic + generate_options: no_gui + hier_block_src_path: '.:' + id: gotenna_pro_tx_bladerf + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: run + sizing_mode: fixed + thread_safe_setters: '' + title: '' + window_size: '' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 8] + rotation: 0 + state: enabled + +blocks: +- name: center_freq + id: variable + parameters: + comment: '' + value: frequency - offset + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [80, 460] + rotation: 0 + state: enabled +- name: control_chan + id: variable + parameters: + comment: '' + value: '2' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [120, 524] + rotation: 0 + state: enabled +- name: data_chan + id: variable + parameters: + comment: '' + value: '2' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [224, 524] + rotation: 0 + state: enabled +- name: fsk_deviation_hz + id: variable + parameters: + comment: '' + value: '{2400: 400, 4800: 750, 9600: 1100}[baud_rate]' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 332.0] + rotation: 0 + state: enabled +- name: hw_rate + id: variable + parameters: + comment: '' + value: '5760000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [184, 460.0] + rotation: 0 + state: enabled +- name: interp + id: variable + parameters: + comment: '' + value: 480000 // baud_rate + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 460] + rotation: 0 + state: enabled +- name: offset + id: variable + parameters: + comment: '' + value: '250000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 268.0] + rotation: 0 + state: enabled +- name: packets + id: variable + parameters: + comment: '' + value: gotenna_packet.encode_pro_broadcast_packets(message_type, counter_num, + sender_gid, recipient_gid, callsign, message, publickey_data) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 612.0] + rotation: 0 + state: enabled +- name: samp_per_sym + id: variable + parameters: + comment: '' + value: '4' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 524] + rotation: 0 + state: enabled +- name: samp_rate + id: variable + parameters: + comment: '' + value: baud_rate * samp_per_sym * interp + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 396] + rotation: 0 + state: enabled +- name: silence_time + id: variable + parameters: + comment: '' + value: '4000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [160, 612.0] + rotation: 0 + state: enabled +- name: taps + id: variable + parameters: + comment: '' + value: firdes.gaussian(1.0, samp_per_sym, 0.5, 64) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 188.0] + rotation: 0 + state: enabled +- name: antenna + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Transmit antenna + short_id: '' + type: str + value: '' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 44.0] + rotation: 0 + state: true +- name: baud_rate + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Baud rate + short_id: '' + type: intx + value: '9600' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [768, 44.0] + rotation: 0 + state: enabled +- name: blocks_char_to_float_0 + id: blocks_char_to_float + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + scale: '1' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [432, 196.0] + rotation: 180 + state: enabled +- name: blocks_multiply_xx_0 + id: blocks_multiply_xx + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + num_inputs: '2' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [632, 328.0] + rotation: 0 + state: enabled +- name: blocks_multiply_xx_1 + id: blocks_multiply_xx + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + num_inputs: '2' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [960, 408.0] + rotation: 0 + state: enabled +- name: blocks_repeat_0 + id: blocks_repeat + parameters: + affinity: '' + alias: '' + comment: '' + interp: 8 * samp_per_sym + maxoutbuf: '0' + minoutbuf: '0' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [448, 388.0] + rotation: 0 + state: enabled +- name: blocks_repeat_1 + id: blocks_repeat + parameters: + affinity: '' + alias: '' + comment: '' + interp: 8 * samp_per_sym * (hw_rate // (baud_rate * samp_per_sym)) + maxoutbuf: '0' + minoutbuf: '0' + type: float + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [592, 516.0] + rotation: 0 + state: enabled +- name: blocks_repeat_2 + id: blocks_repeat + parameters: + affinity: '' + alias: '' + comment: '' + interp: samp_per_sym + maxoutbuf: '0' + minoutbuf: '0' + type: float + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [256, 196.0] + rotation: 180 + state: enabled +- name: blocks_vco_c_0 + id: blocks_vco_c + parameters: + affinity: '' + alias: '' + amplitude: '0.9' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + samp_rate: hw_rate + sensitivity: 2*math.pi + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [768, 500.0] + rotation: 0 + state: enabled +- name: blocks_vco_c_1 + id: blocks_vco_c + parameters: + affinity: '' + alias: '' + amplitude: '1.0' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + samp_rate: baud_rate * samp_per_sym + sensitivity: 2 * math.pi * fsk_deviation_hz + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [440, 276.0] + rotation: 0 + state: enabled +- name: blocks_vector_source_x_0 + id: blocks_vector_source_x + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + repeat: 'False' + tags: '[]' + type: byte + vector: '[0]*silence_time+gotenna_packet.pro_gfsk_symbols(packets)+[0]*silence_time' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [776, 180.0] + rotation: 180 + state: enabled +- name: blocks_vector_source_x_1 + id: blocks_vector_source_x + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + repeat: 'True' + tags: '[]' + type: complex + vector: '[1]' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [216, 372.0] + rotation: 0 + state: enabled +- name: blocks_vector_source_x_2 + id: blocks_vector_source_x + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + repeat: 'True' + tags: '[]' + type: float + vector: '[offset]' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [360, 500.0] + rotation: 0 + state: enabled +- name: callsign + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Sender callsign + short_id: '' + type: str + value: VE3IRR + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [512, 44.0] + rotation: 0 + state: true +- name: counter_num + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Counter + short_id: '' + type: intx + value: '0' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1248, 44.0] + rotation: 0 + state: true +- name: device_args + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Osmocom device arguments + short_id: '' + type: str + value: bladerf=0,biastee=1 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [136, 44.0] + rotation: 0 + state: true +- name: digital_map_bb_0 + id: digital_map_bb + parameters: + affinity: '' + alias: '' + comment: '' + map: '[-3, -1, 3, 1, 0]' + maxoutbuf: '0' + minoutbuf: '0' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [600, 196.0] + rotation: 180 + state: enabled +- name: frequency + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Frequency + short_id: '' + type: eng_float + value: '450000000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [872, 44.0] + rotation: 0 + state: enabled +- name: import_0 + id: import + parameters: + alias: '' + comment: '' + imports: import gotenna_packet + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 92.0] + rotation: 0 + state: enabled +- name: import_1 + id: import + parameters: + alias: '' + comment: '' + imports: import math + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 140] + rotation: 0 + state: enabled +- name: interp_fir_filter_xxx_0 + id: interp_fir_filter_xxx + parameters: + affinity: '' + alias: '' + comment: '' + interp: '1' + maxoutbuf: '0' + minoutbuf: '0' + samp_delay: '0' + taps: taps + type: fff + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [216, 284.0] + rotation: 0 + state: enabled +- name: message + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Message + short_id: '' + type: str + value: Hello world! + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [648, 44.0] + rotation: 0 + state: true +- name: message_type + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: type + short_id: '' + type: str + value: BROADCAST + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [984, 44.0] + rotation: 0 + state: true +- name: osmosdr_sink_0 + id: osmosdr_sink + parameters: + affinity: '' + alias: '' + ant0: antenna + ant1: '' + ant10: '' + ant11: '' + ant12: '' + ant13: '' + ant14: '' + ant15: '' + ant16: '' + ant17: '' + ant18: '' + ant19: '' + ant2: '' + ant20: '' + ant21: '' + ant22: '' + ant23: '' + ant24: '' + ant25: '' + ant26: '' + ant27: '' + ant28: '' + ant29: '' + ant3: '' + ant30: '' + ant31: '' + ant4: '' + ant5: '' + ant6: '' + ant7: '' + ant8: '' + ant9: '' + args: device_args + bb_gain0: '0' + bb_gain1: '20' + bb_gain10: '20' + bb_gain11: '20' + bb_gain12: '20' + bb_gain13: '20' + bb_gain14: '20' + bb_gain15: '20' + bb_gain16: '20' + bb_gain17: '20' + bb_gain18: '20' + bb_gain19: '20' + bb_gain2: '20' + bb_gain20: '20' + bb_gain21: '20' + bb_gain22: '20' + bb_gain23: '20' + bb_gain24: '20' + bb_gain25: '20' + bb_gain26: '20' + bb_gain27: '20' + bb_gain28: '20' + bb_gain29: '20' + bb_gain3: '20' + bb_gain30: '20' + bb_gain31: '20' + bb_gain4: '20' + bb_gain5: '20' + bb_gain6: '20' + bb_gain7: '20' + bb_gain8: '20' + bb_gain9: '20' + bw0: '1000000' + bw1: '0' + bw10: '0' + bw11: '0' + bw12: '0' + bw13: '0' + bw14: '0' + bw15: '0' + bw16: '0' + bw17: '0' + bw18: '0' + bw19: '0' + bw2: '0' + bw20: '0' + bw21: '0' + bw22: '0' + bw23: '0' + bw24: '0' + bw25: '0' + bw26: '0' + bw27: '0' + bw28: '0' + bw29: '0' + bw3: '0' + bw30: '0' + bw31: '0' + bw4: '0' + bw5: '0' + bw6: '0' + bw7: '0' + bw8: '0' + bw9: '0' + clock_source0: '' + clock_source1: '' + clock_source2: '' + clock_source3: '' + clock_source4: '' + clock_source5: '' + clock_source6: '' + clock_source7: '' + comment: '' + corr0: '0' + corr1: '0' + corr10: '0' + corr11: '0' + corr12: '0' + corr13: '0' + corr14: '0' + corr15: '0' + corr16: '0' + corr17: '0' + corr18: '0' + corr19: '0' + corr2: '0' + corr20: '0' + corr21: '0' + corr22: '0' + corr23: '0' + corr24: '0' + corr25: '0' + corr26: '0' + corr27: '0' + corr28: '0' + corr29: '0' + corr3: '0' + corr30: '0' + corr31: '0' + corr4: '0' + corr5: '0' + corr6: '0' + corr7: '0' + corr8: '0' + corr9: '0' + freq0: center_freq + freq1: 100e6 + freq10: 100e6 + freq11: 100e6 + freq12: 100e6 + freq13: 100e6 + freq14: 100e6 + freq15: 100e6 + freq16: 100e6 + freq17: 100e6 + freq18: 100e6 + freq19: 100e6 + freq2: 100e6 + freq20: 100e6 + freq21: 100e6 + freq22: 100e6 + freq23: 100e6 + freq24: 100e6 + freq25: 100e6 + freq26: 100e6 + freq27: 100e6 + freq28: 100e6 + freq29: 100e6 + freq3: 100e6 + freq30: 100e6 + freq31: 100e6 + freq4: 100e6 + freq5: 100e6 + freq6: 100e6 + freq7: 100e6 + freq8: 100e6 + freq9: 100e6 + gain0: '60' + gain1: '10' + gain10: '10' + gain11: '10' + gain12: '10' + gain13: '10' + gain14: '10' + gain15: '10' + gain16: '10' + gain17: '10' + gain18: '10' + gain19: '10' + gain2: '10' + gain20: '10' + gain21: '10' + gain22: '10' + gain23: '10' + gain24: '10' + gain25: '10' + gain26: '10' + gain27: '10' + gain28: '10' + gain29: '10' + gain3: '10' + gain30: '10' + gain31: '10' + gain4: '10' + gain5: '10' + gain6: '10' + gain7: '10' + gain8: '10' + gain9: '10' + if_gain0: '0' + if_gain1: '20' + if_gain10: '20' + if_gain11: '20' + if_gain12: '20' + if_gain13: '20' + if_gain14: '20' + if_gain15: '20' + if_gain16: '20' + if_gain17: '20' + if_gain18: '20' + if_gain19: '20' + if_gain2: '20' + if_gain20: '20' + if_gain21: '20' + if_gain22: '20' + if_gain23: '20' + if_gain24: '20' + if_gain25: '20' + if_gain26: '20' + if_gain27: '20' + if_gain28: '20' + if_gain29: '20' + if_gain3: '20' + if_gain30: '20' + if_gain31: '20' + if_gain4: '20' + if_gain5: '20' + if_gain6: '20' + if_gain7: '20' + if_gain8: '20' + if_gain9: '20' + maxoutbuf: '0' + minoutbuf: '0' + nchan: '1' + num_mboards: '1' + sample_rate: hw_rate + sync: sync + time_source0: '' + time_source1: '' + time_source2: '' + time_source3: '' + time_source4: '' + time_source5: '' + time_source6: '' + time_source7: '' + type: fc32 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1184, 332.0] + rotation: 0 + state: enabled +- name: publickey_data + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Sender Public Key + short_id: '' + type: str + value: base64key + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1104, 44.0] + rotation: 0 + state: true +- name: rational_resampler_xxx_0 + id: rational_resampler_xxx + parameters: + affinity: '' + alias: '' + comment: '' + decim: '1' + fbw: '0' + interp: hw_rate // (baud_rate * samp_per_sym) + maxoutbuf: '0' + minoutbuf: '0' + taps: '' + type: ccc + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [760, 316.0] + rotation: 0 + state: enabled +- name: recipient_gid + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Recipient GID + short_id: '' + type: intx + value: '1234567891' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [272, 44.0] + rotation: 0 + state: true +- name: sender_gid + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Sender GID + short_id: '' + type: intx + value: '1234567890' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [400, 44.0] + rotation: 0 + state: true + +connections: +- [blocks_char_to_float_0, '0', blocks_repeat_2, '0'] +- [blocks_multiply_xx_0, '0', rational_resampler_xxx_0, '0'] +- [blocks_multiply_xx_1, '0', osmosdr_sink_0, '0'] +- [blocks_repeat_0, '0', blocks_multiply_xx_0, '1'] +- [blocks_repeat_1, '0', blocks_vco_c_0, '0'] +- [blocks_repeat_2, '0', interp_fir_filter_xxx_0, '0'] +- [blocks_vco_c_0, '0', blocks_multiply_xx_1, '1'] +- [blocks_vco_c_1, '0', blocks_multiply_xx_0, '0'] +- [blocks_vector_source_x_0, '0', digital_map_bb_0, '0'] +- [blocks_vector_source_x_1, '0', blocks_repeat_0, '0'] +- [blocks_vector_source_x_2, '0', blocks_repeat_1, '0'] +- [digital_map_bb_0, '0', blocks_char_to_float_0, '0'] +- [interp_fir_filter_xxx_0, '0', blocks_vco_c_1, '0'] +- [rational_resampler_xxx_0, '0', blocks_multiply_xx_1, '0'] + +metadata: + file_format: 1 + grc_version: 3.10.7.0 diff --git a/challenges/gotenna_pro_bladerf.py b/challenges/gotenna/gotenna_pro_tx_bladerf.py old mode 100644 new mode 100755 similarity index 64% rename from challenges/gotenna_pro_bladerf.py rename to challenges/gotenna/gotenna_pro_tx_bladerf.py index b5a3086..c58601f --- a/challenges/gotenna_pro_bladerf.py +++ b/challenges/gotenna/gotenna_pro_tx_bladerf.py @@ -6,94 +6,66 @@ # # GNU Radio Python Flow Graph # Title: Gotenna Pro Tx Bladerf -# GNU Radio version: 3.10.7.0 +# GNU Radio version: 3.10.5.0 from gnuradio import blocks from gnuradio import digital from gnuradio import filter from gnuradio.filter import firdes from gnuradio import gr -import osmosdr +from gnuradio.fft import window import sys import signal from argparse import ArgumentParser from gnuradio.eng_arg import eng_float, intx from gnuradio import eng_notation +import gotenna_packet import math - -import challenges.gotenna_packet.gotenna_packet as gotenna_packet +import osmosdr +import time -def validate_options(options): - packets = gotenna_packet.encode_pro_broadcast_packets( - options.message_type, - options.counter_num, - options.sender_gid, - options.recipient_gid, - options.callsign, - options.message, - options.publickey_data) - gotenna_packet.pro_gfsk_symbols(packets) - return packets class gotenna_pro_tx_bladerf(gr.top_block): - def __init__( - self, - baud_rate=9600, - bandwidth=1000000, - device_args="bladerf=0,biastee=1", - antenna='', - callsign='VE3IRR', - counter_num=0, - frequency=450000000, - if_gain=0, - message='Hello world!', - message_type='BROADCAST', - publickey_data='base64key', - recipient_gid=1234567891, - rf_gain=60, - sender_gid=1234567890, - silence_time=4000, - bb_gain=0): - try: - gr.top_block.__init__(self, "Gotenna Pro Tx Bladerf", catch_exceptions=True) - except TypeError: - gr.top_block.__init__(self, "Gotenna Pro Tx Bladerf") - - if baud_rate not in (2400, 4800, 9600): - raise ValueError("goTenna Pro baud_rate must be one of: 2400, 4800, 9600") + def __init__(self, antenna='', baud_rate=9600, callsign='VE3IRR', counter_num=0, device_args='bladerf=0,biastee=1', frequency=450000000, message='Hello world!', message_type='BROADCAST', publickey_data='base64key', recipient_gid=1234567891, sender_gid=1234567890): + gr.top_block.__init__(self, "Gotenna Pro Tx Bladerf", catch_exceptions=True) - self.baud_rate = baud_rate - self.bandwidth = bandwidth - self.bb_gain = bb_gain - self.device_args = device_args + ################################################## + # Parameters + ################################################## self.antenna = antenna + self.baud_rate = baud_rate self.callsign = callsign self.counter_num = counter_num + self.device_args = device_args self.frequency = frequency - self.if_gain = if_gain self.message = message self.message_type = message_type self.publickey_data = publickey_data self.recipient_gid = recipient_gid - self.rf_gain = rf_gain self.sender_gid = sender_gid - self.silence_time = silence_time + ################################################## + # Variables + ################################################## self.samp_per_sym = samp_per_sym = 4 self.offset = offset = 250000 self.interp = interp = 480000 // baud_rate self.taps = taps = firdes.gaussian(1.0, samp_per_sym, 0.5, 64) + self.silence_time = silence_time = 4000 self.samp_rate = samp_rate = baud_rate * samp_per_sym * interp - self.packets = packets = validate_options(self) + self.packets = packets = gotenna_packet.encode_pro_broadcast_packets(message_type, counter_num, sender_gid, recipient_gid, callsign, message, publickey_data) self.hw_rate = hw_rate = 5760000 self.fsk_deviation_hz = fsk_deviation_hz = {2400: 400, 4800: 750, 9600: 1100}[baud_rate] self.data_chan = data_chan = 2 self.control_chan = control_chan = 2 self.center_freq = center_freq = frequency - offset + ################################################## + # Blocks + ################################################## self.rational_resampler_xxx_0 = filter.rational_resampler_ccc( interpolation=(hw_rate // (baud_rate * samp_per_sym)), decimation=1, @@ -106,30 +78,30 @@ def __init__( self.osmosdr_sink_0.set_sample_rate(hw_rate) self.osmosdr_sink_0.set_center_freq(center_freq, 0) self.osmosdr_sink_0.set_freq_corr(0, 0) - self.osmosdr_sink_0.set_gain(rf_gain, 0) - self.osmosdr_sink_0.set_if_gain(if_gain, 0) - self.osmosdr_sink_0.set_bb_gain(bb_gain, 0) + self.osmosdr_sink_0.set_gain(60, 0) + self.osmosdr_sink_0.set_if_gain(0, 0) + self.osmosdr_sink_0.set_bb_gain(0, 0) self.osmosdr_sink_0.set_antenna(antenna, 0) - self.osmosdr_sink_0.set_bandwidth(bandwidth, 0) + self.osmosdr_sink_0.set_bandwidth(1000000, 0) self.interp_fir_filter_xxx_0 = filter.interp_fir_filter_fff(1, taps) self.interp_fir_filter_xxx_0.declare_sample_delay(0) self.digital_map_bb_0 = digital.map_bb([-3, -1, 3, 1, 0]) self.blocks_vector_source_x_2 = blocks.vector_source_f([offset], True, 1, []) self.blocks_vector_source_x_1 = blocks.vector_source_c([1], True, 1, []) - self.blocks_vector_source_x_0 = blocks.vector_source_b( - [0] * silence_time + gotenna_packet.pro_gfsk_symbols(packets) + [0] * silence_time, - False, 1, []) + self.blocks_vector_source_x_0 = blocks.vector_source_b([0]*silence_time+gotenna_packet.pro_gfsk_symbols(packets)+[0]*silence_time, False, 1, []) self.blocks_vco_c_1 = blocks.vco_c((baud_rate * samp_per_sym), (2 * math.pi * fsk_deviation_hz), 1.0) - self.blocks_vco_c_0 = blocks.vco_c(hw_rate, (2 * math.pi), 0.9) - self.blocks_repeat_2 = blocks.repeat(gr.sizeof_float * 1, samp_per_sym) - self.blocks_repeat_1 = blocks.repeat( - gr.sizeof_float * 1, - (8 * samp_per_sym * (hw_rate // (baud_rate * samp_per_sym)))) - self.blocks_repeat_0 = blocks.repeat(gr.sizeof_gr_complex * 1, (8 * samp_per_sym)) + self.blocks_vco_c_0 = blocks.vco_c(hw_rate, (2*math.pi), 0.9) + self.blocks_repeat_2 = blocks.repeat(gr.sizeof_float*1, samp_per_sym) + self.blocks_repeat_1 = blocks.repeat(gr.sizeof_float*1, (8 * samp_per_sym * (hw_rate // (baud_rate * samp_per_sym)))) + self.blocks_repeat_0 = blocks.repeat(gr.sizeof_gr_complex*1, (8 * samp_per_sym)) self.blocks_multiply_xx_1 = blocks.multiply_vcc(1) self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) self.blocks_char_to_float_0 = blocks.char_to_float(1, 1) + + ################################################## + # Connections + ################################################## self.connect((self.blocks_char_to_float_0, 0), (self.blocks_repeat_2, 0)) self.connect((self.blocks_multiply_xx_0, 0), (self.rational_resampler_xxx_0, 0)) self.connect((self.blocks_multiply_xx_1, 0), (self.osmosdr_sink_0, 0)) @@ -145,64 +117,43 @@ def __init__( self.connect((self.interp_fir_filter_xxx_0, 0), (self.blocks_vco_c_1, 0)) self.connect((self.rational_resampler_xxx_0, 0), (self.blocks_multiply_xx_1, 0)) + + def get_antenna(self): + return self.antenna + + def set_antenna(self, antenna): + self.antenna = antenna + self.osmosdr_sink_0.set_antenna(self.antenna, 0) + def get_baud_rate(self): return self.baud_rate def set_baud_rate(self, baud_rate): - if baud_rate not in (2400, 4800, 9600): - raise ValueError("goTenna Pro baud_rate must be one of: 2400, 4800, 9600") self.baud_rate = baud_rate self.set_fsk_deviation_hz({2400: 400, 4800: 750, 9600: 1100}[self.baud_rate]) self.set_interp(480000 // self.baud_rate) self.set_samp_rate(self.baud_rate * self.samp_per_sym * self.interp) self.blocks_repeat_1.set_interpolation((8 * self.samp_per_sym * (self.hw_rate // (self.baud_rate * self.samp_per_sym)))) - def get_bandwidth(self): - return self.bandwidth - - def set_bandwidth(self, bandwidth): - self.bandwidth = bandwidth - self.osmosdr_sink_0.set_bandwidth(self.bandwidth, 0) - - def get_bb_gain(self): - return self.bb_gain - - def set_bb_gain(self, bb_gain): - self.bb_gain = bb_gain - self.osmosdr_sink_0.set_bb_gain(self.bb_gain, 0) - - def get_device_args(self): - return self.device_args - - def set_device_args(self, device_args): - self.device_args = device_args - - def get_antenna(self): - return self.antenna - - def set_antenna(self, antenna): - self.antenna = antenna - self.osmosdr_sink_0.set_antenna(self.antenna, 0) - def get_callsign(self): return self.callsign def set_callsign(self, callsign): self.callsign = callsign - self.set_packets(gotenna_packet.encode_pro_broadcast_packets( - self.message_type, self.counter_num, self.sender_gid, - self.recipient_gid, self.callsign, self.message, - self.publickey_data)) + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) def get_counter_num(self): return self.counter_num def set_counter_num(self, counter_num): self.counter_num = counter_num - self.set_packets(gotenna_packet.encode_pro_broadcast_packets( - self.message_type, self.counter_num, self.sender_gid, - self.recipient_gid, self.callsign, self.message, - self.publickey_data)) + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) + + def get_device_args(self): + return self.device_args + + def set_device_args(self, device_args): + self.device_args = device_args def get_frequency(self): return self.frequency @@ -211,69 +162,40 @@ def set_frequency(self, frequency): self.frequency = frequency self.set_center_freq(self.frequency - self.offset) - def get_if_gain(self): - return self.if_gain - - def set_if_gain(self, if_gain): - self.if_gain = if_gain - self.osmosdr_sink_0.set_if_gain(self.if_gain, 0) - def get_message(self): return self.message def set_message(self, message): self.message = message - self.set_packets(gotenna_packet.encode_pro_broadcast_packets( - self.message_type, self.counter_num, self.sender_gid, - self.recipient_gid, self.callsign, self.message, - self.publickey_data)) + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) def get_message_type(self): return self.message_type def set_message_type(self, message_type): self.message_type = message_type - self.set_packets(gotenna_packet.encode_pro_broadcast_packets( - self.message_type, self.counter_num, self.sender_gid, - self.recipient_gid, self.callsign, self.message, - self.publickey_data)) + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) def get_publickey_data(self): return self.publickey_data def set_publickey_data(self, publickey_data): self.publickey_data = publickey_data - self.set_packets(gotenna_packet.encode_pro_broadcast_packets( - self.message_type, self.counter_num, self.sender_gid, - self.recipient_gid, self.callsign, self.message, - self.publickey_data)) + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) def get_recipient_gid(self): return self.recipient_gid def set_recipient_gid(self, recipient_gid): self.recipient_gid = recipient_gid - self.set_packets(gotenna_packet.encode_pro_broadcast_packets( - self.message_type, self.counter_num, self.sender_gid, - self.recipient_gid, self.callsign, self.message, - self.publickey_data)) - - def get_rf_gain(self): - return self.rf_gain - - def set_rf_gain(self, rf_gain): - self.rf_gain = rf_gain - self.osmosdr_sink_0.set_gain(self.rf_gain, 0) + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) def get_sender_gid(self): return self.sender_gid def set_sender_gid(self, sender_gid): self.sender_gid = sender_gid - self.set_packets(gotenna_packet.encode_pro_broadcast_packets( - self.message_type, self.counter_num, self.sender_gid, - self.recipient_gid, self.callsign, self.message, - self.publickey_data)) + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) def get_samp_per_sym(self): return self.samp_per_sym @@ -313,9 +235,7 @@ def get_silence_time(self): def set_silence_time(self, silence_time): self.silence_time = silence_time - self.blocks_vector_source_x_0.set_data( - [0] * self.silence_time + gotenna_packet.pro_gfsk_symbols(self.packets) + [0] * self.silence_time, - []) + self.blocks_vector_source_x_0.set_data([0]*self.silence_time+gotenna_packet.pro_gfsk_symbols(self.packets)+[0]*self.silence_time, []) def get_samp_rate(self): return self.samp_rate @@ -328,9 +248,7 @@ def get_packets(self): def set_packets(self, packets): self.packets = packets - self.blocks_vector_source_x_0.set_data( - [0] * self.silence_time + gotenna_packet.pro_gfsk_symbols(self.packets) + [0] * self.silence_time, - []) + self.blocks_vector_source_x_0.set_data([0]*self.silence_time+gotenna_packet.pro_gfsk_symbols(self.packets)+[0]*self.silence_time, []) def get_hw_rate(self): return self.hw_rate @@ -366,35 +284,27 @@ def set_center_freq(self, center_freq): self.osmosdr_sink_0.set_center_freq(self.center_freq, 0) + def argument_parser(): parser = ArgumentParser() + parser.add_argument( + "--antenna", dest="antenna", type=str, default='', + help="Set Transmit antenna [default=%(default)r]") parser.add_argument( "--baud-rate", dest="baud_rate", type=intx, default=9600, help="Set Baud rate [default=%(default)r]") - parser.add_argument( - "--bandwidth", dest="bandwidth", type=eng_float, default=eng_notation.num_to_str(float(1000000)), - help="Set transmit bandwidth [default=%(default)r]") - parser.add_argument( - "--bb-gain", dest="bb_gain", type=eng_float, default="0.0", - help="Set BB gain [default=%(default)r]") - parser.add_argument( - "--device-args", dest="device_args", type=str, default='bladerf=0,biastee=1', - help="Set complete Osmocom device arguments [default=%(default)r]") - parser.add_argument( - "--antenna", dest="antenna", type=str, default='', - help="Set transmit antenna; empty string selects backend default [default=%(default)r]") parser.add_argument( "--callsign", dest="callsign", type=str, default='VE3IRR', help="Set Sender callsign [default=%(default)r]") parser.add_argument( "--counter-num", dest="counter_num", type=intx, default=0, help="Set Counter [default=%(default)r]") + parser.add_argument( + "--device-args", dest="device_args", type=str, default='bladerf=0,biastee=1', + help="Set Osmocom device arguments [default=%(default)r]") parser.add_argument( "--frequency", dest="frequency", type=eng_float, default=eng_notation.num_to_str(float(450000000)), help="Set Frequency [default=%(default)r]") - parser.add_argument( - "--if-gain", dest="if_gain", type=eng_float, default="0.0", - help="Set IF gain [default=%(default)r]") parser.add_argument( "--message", dest="message", type=str, default='Hello world!', help="Set Message [default=%(default)r]") @@ -407,48 +317,28 @@ def argument_parser(): parser.add_argument( "--recipient-gid", dest="recipient_gid", type=intx, default=1234567891, help="Set Recipient GID [default=%(default)r]") - parser.add_argument( - "--rf-gain", dest="rf_gain", type=eng_float, default="60.0", - help="Set RF gain [default=%(default)r]") parser.add_argument( "--sender-gid", dest="sender_gid", type=intx, default=1234567890, help="Set Sender GID [default=%(default)r]") - parser.add_argument( - "--silence-time", dest="silence_time", type=intx, default=4000, - help="Set silence padding [default=%(default)r]") return parser def main(top_block_cls=gotenna_pro_tx_bladerf, options=None): if options is None: options = argument_parser().parse_args() - tb = top_block_cls( - baud_rate=options.baud_rate, - bandwidth=options.bandwidth, - bb_gain=options.bb_gain, - device_args=options.device_args, - antenna=options.antenna, - callsign=options.callsign, - counter_num=options.counter_num, - frequency=options.frequency, - if_gain=options.if_gain, - message=options.message, - message_type=options.message_type, - publickey_data=options.publickey_data, - recipient_gid=options.recipient_gid, - rf_gain=options.rf_gain, - sender_gid=options.sender_gid, - silence_time=options.silence_time) + tb = top_block_cls(antenna=options.antenna, baud_rate=options.baud_rate, callsign=options.callsign, counter_num=options.counter_num, device_args=options.device_args, frequency=options.frequency, message=options.message, message_type=options.message_type, publickey_data=options.publickey_data, recipient_gid=options.recipient_gid, sender_gid=options.sender_gid) def sig_handler(sig=None, frame=None): tb.stop() tb.wait() + sys.exit(0) signal.signal(signal.SIGINT, sig_handler) signal.signal(signal.SIGTERM, sig_handler) tb.start() + tb.wait() diff --git a/challenges/gotenna/gotenna_pro_tx_usrp.grc b/challenges/gotenna/gotenna_pro_tx_usrp.grc new file mode 100644 index 0000000..43de67d --- /dev/null +++ b/challenges/gotenna/gotenna_pro_tx_usrp.grc @@ -0,0 +1,947 @@ +options: + parameters: + author: '' + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: '' + description: '' + gen_cmake: 'On' + gen_linking: dynamic + generate_options: no_gui + hier_block_src_path: '.:' + id: gotenna_pro_tx_usrp + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: run + sizing_mode: fixed + thread_safe_setters: '' + title: '' + window_size: '' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 8] + rotation: 0 + state: enabled + +blocks: +- name: center_freq + id: variable + parameters: + comment: '' + value: frequency - offset + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [80, 460] + rotation: 0 + state: enabled +- name: control_chan + id: variable + parameters: + comment: '' + value: '2' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [120, 524] + rotation: 0 + state: enabled +- name: data_chan + id: variable + parameters: + comment: '' + value: '2' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [224, 524] + rotation: 0 + state: enabled +- name: fsk_deviation_hz + id: variable + parameters: + comment: '' + value: '{2400: 400, 4800: 750, 9600: 1100}[baud_rate]' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 332.0] + rotation: 0 + state: enabled +- name: interp + id: variable + parameters: + comment: '' + value: 480000 // baud_rate + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 460] + rotation: 0 + state: enabled +- name: offset + id: variable + parameters: + comment: '' + value: '250000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 268.0] + rotation: 0 + state: enabled +- name: packets + id: variable + parameters: + comment: '' + value: gotenna_packet.encode_pro_broadcast_packets(message_type, counter_num, + sender_gid, recipient_gid, callsign, message, publickey_data) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 612.0] + rotation: 0 + state: enabled +- name: samp_per_sym + id: variable + parameters: + comment: '' + value: '4' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 524] + rotation: 0 + state: enabled +- name: samp_rate + id: variable + parameters: + comment: '' + value: baud_rate * samp_per_sym * interp + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 396] + rotation: 0 + state: enabled +- name: taps + id: variable + parameters: + comment: '' + value: firdes.gaussian(1.0, samp_per_sym, 0.5, 64) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 188.0] + rotation: 0 + state: enabled +- name: baud_rate + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Baud rate + short_id: '' + type: intx + value: '9600' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [768, 44.0] + rotation: 0 + state: enabled +- name: blocks_char_to_float_0 + id: blocks_char_to_float + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + scale: '1' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [432, 196.0] + rotation: 180 + state: enabled +- name: blocks_file_sink_0 + id: blocks_file_sink + parameters: + affinity: '' + alias: '' + append: 'False' + comment: '' + file: pro_synth.cf32 + type: complex + unbuffered: 'False' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1136, 492.0] + rotation: 0 + state: disabled +- name: blocks_multiply_xx_0 + id: blocks_multiply_xx + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + num_inputs: '2' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [632, 328.0] + rotation: 0 + state: enabled +- name: blocks_multiply_xx_1 + id: blocks_multiply_xx + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + num_inputs: '2' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [960, 408.0] + rotation: 0 + state: enabled +- name: blocks_repeat_0 + id: blocks_repeat + parameters: + affinity: '' + alias: '' + comment: '' + interp: 8 * samp_per_sym + maxoutbuf: '0' + minoutbuf: '0' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [448, 388.0] + rotation: 0 + state: enabled +- name: blocks_repeat_1 + id: blocks_repeat + parameters: + affinity: '' + alias: '' + comment: '' + interp: 8 * samp_per_sym * interp + maxoutbuf: '0' + minoutbuf: '0' + type: float + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [592, 516.0] + rotation: 0 + state: enabled +- name: blocks_repeat_2 + id: blocks_repeat + parameters: + affinity: '' + alias: '' + comment: '' + interp: samp_per_sym + maxoutbuf: '0' + minoutbuf: '0' + type: float + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [256, 196.0] + rotation: 180 + state: enabled +- name: blocks_vco_c_0 + id: blocks_vco_c + parameters: + affinity: '' + alias: '' + amplitude: '0.9' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + samp_rate: samp_rate + sensitivity: 2*math.pi + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [768, 500.0] + rotation: 0 + state: enabled +- name: blocks_vco_c_1 + id: blocks_vco_c + parameters: + affinity: '' + alias: '' + amplitude: '1.0' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + samp_rate: baud_rate * samp_per_sym + sensitivity: 2 * math.pi * fsk_deviation_hz + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [440, 276.0] + rotation: 0 + state: enabled +- name: blocks_vector_source_x_0 + id: blocks_vector_source_x + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + repeat: 'False' + tags: '[]' + type: byte + vector: gotenna_packet.pro_gfsk_symbols(packets) + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [776, 180.0] + rotation: 180 + state: enabled +- name: blocks_vector_source_x_1 + id: blocks_vector_source_x + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + repeat: 'True' + tags: '[]' + type: complex + vector: '[1]' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [216, 372.0] + rotation: 0 + state: enabled +- name: blocks_vector_source_x_2 + id: blocks_vector_source_x + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + repeat: 'True' + tags: '[]' + type: float + vector: '[offset]' + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [360, 500.0] + rotation: 0 + state: enabled +- name: callsign + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Sender callsign + short_id: '' + type: str + value: VE3IRR + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [512, 44.0] + rotation: 0 + state: true +- name: counter_num + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Counter + short_id: '' + type: intx + value: '0' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1288, 44.0] + rotation: 0 + state: true +- name: device_addr + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: UHD device address + short_id: '' + type: str + value: '' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 44.0] + rotation: 0 + state: true +- name: digital_map_bb_0 + id: digital_map_bb + parameters: + affinity: '' + alias: '' + comment: '' + map: '[-3, -1, 3, 1, 0]' + maxoutbuf: '0' + minoutbuf: '0' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [600, 196.0] + rotation: 180 + state: enabled +- name: frequency + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Frequency + short_id: '' + type: eng_float + value: '450000000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [872, 44.0] + rotation: 0 + state: enabled +- name: import_0 + id: import + parameters: + alias: '' + comment: '' + imports: import gotenna_packet + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 92.0] + rotation: 0 + state: enabled +- name: import_1 + id: import + parameters: + alias: '' + comment: '' + imports: import math + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 140] + rotation: 0 + state: enabled +- name: interp_fir_filter_xxx_0 + id: interp_fir_filter_xxx + parameters: + affinity: '' + alias: '' + comment: '' + interp: '1' + maxoutbuf: '0' + minoutbuf: '0' + samp_delay: '0' + taps: taps + type: fff + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [216, 284.0] + rotation: 0 + state: enabled +- name: message + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Message + short_id: '' + type: str + value: Hello world! + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [648, 44.0] + rotation: 0 + state: true +- name: message_type + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: type + short_id: '' + type: str + value: BROADCAST + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [984, 44.0] + rotation: 0 + state: true +- name: publickey_data + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Sender Public Key + short_id: '' + type: str + value: base64key + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1104, 44.0] + rotation: 0 + state: true +- name: rational_resampler_xxx_0 + id: rational_resampler_xxx + parameters: + affinity: '' + alias: '' + comment: '' + decim: '1' + fbw: '0' + interp: interp + maxoutbuf: '0' + minoutbuf: '0' + taps: '' + type: ccc + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [760, 316.0] + rotation: 0 + state: enabled +- name: rational_resampler_xxx_1 + id: rational_resampler_xxx + parameters: + affinity: '' + alias: '' + comment: '' + decim: '48' + fbw: '0' + interp: '25' + maxoutbuf: '0' + minoutbuf: '0' + taps: '[]' + type: ccc + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [992, 580.0] + rotation: 0 + state: disabled +- name: recipient_gid + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Recipient GID + short_id: '' + type: intx + value: '1234567891' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [272, 44.0] + rotation: 0 + state: true +- name: sender_gid + id: parameter + parameters: + alias: '' + comment: '' + hide: none + label: Sender GID + short_id: '' + type: intx + value: '1234567890' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [400, 44.0] + rotation: 0 + state: true +- name: uhd_usrp_sink_0 + id: uhd_usrp_sink + parameters: + affinity: '' + alias: '' + ant0: '"TX/RX"' + ant1: '' + ant10: '' + ant11: '' + ant12: '' + ant13: '' + ant14: '' + ant15: '' + ant16: '' + ant17: '' + ant18: '' + ant19: '' + ant2: '' + ant20: '' + ant21: '' + ant22: '' + ant23: '' + ant24: '' + ant25: '' + ant26: '' + ant27: '' + ant28: '' + ant29: '' + ant3: '' + ant30: '' + ant31: '' + ant4: '' + ant5: '' + ant6: '' + ant7: '' + ant8: '' + ant9: '' + bw0: '0' + bw1: '0' + bw10: '0' + bw11: '0' + bw12: '0' + bw13: '0' + bw14: '0' + bw15: '0' + bw16: '0' + bw17: '0' + bw18: '0' + bw19: '0' + bw2: '0' + bw20: '0' + bw21: '0' + bw22: '0' + bw23: '0' + bw24: '0' + bw25: '0' + bw26: '0' + bw27: '0' + bw28: '0' + bw29: '0' + bw3: '0' + bw30: '0' + bw31: '0' + bw4: '0' + bw5: '0' + bw6: '0' + bw7: '0' + bw8: '0' + bw9: '0' + center_freq0: center_freq + center_freq1: '0' + center_freq10: '0' + center_freq11: '0' + center_freq12: '0' + center_freq13: '0' + center_freq14: '0' + center_freq15: '0' + center_freq16: '0' + center_freq17: '0' + center_freq18: '0' + center_freq19: '0' + center_freq2: '0' + center_freq20: '0' + center_freq21: '0' + center_freq22: '0' + center_freq23: '0' + center_freq24: '0' + center_freq25: '0' + center_freq26: '0' + center_freq27: '0' + center_freq28: '0' + center_freq29: '0' + center_freq3: '0' + center_freq30: '0' + center_freq31: '0' + center_freq4: '0' + center_freq5: '0' + center_freq6: '0' + center_freq7: '0' + center_freq8: '0' + center_freq9: '0' + clock_rate: '0.0' + clock_source0: '' + clock_source1: '' + clock_source2: '' + clock_source3: '' + clock_source4: '' + clock_source5: '' + clock_source6: '' + clock_source7: '' + comment: '' + dev_addr: device_addr + dev_args: '""' + gain0: '70' + gain1: '0' + gain10: '0' + gain11: '0' + gain12: '0' + gain13: '0' + gain14: '0' + gain15: '0' + gain16: '0' + gain17: '0' + gain18: '0' + gain19: '0' + gain2: '0' + gain20: '0' + gain21: '0' + gain22: '0' + gain23: '0' + gain24: '0' + gain25: '0' + gain26: '0' + gain27: '0' + gain28: '0' + gain29: '0' + gain3: '0' + gain30: '0' + gain31: '0' + gain4: '0' + gain5: '0' + gain6: '0' + gain7: '0' + gain8: '0' + gain9: '0' + gain_type0: default + gain_type1: default + gain_type10: default + gain_type11: default + gain_type12: default + gain_type13: default + gain_type14: default + gain_type15: default + gain_type16: default + gain_type17: default + gain_type18: default + gain_type19: default + gain_type2: default + gain_type20: default + gain_type21: default + gain_type22: default + gain_type23: default + gain_type24: default + gain_type25: default + gain_type26: default + gain_type27: default + gain_type28: default + gain_type29: default + gain_type3: default + gain_type30: default + gain_type31: default + gain_type4: default + gain_type5: default + gain_type6: default + gain_type7: default + gain_type8: default + gain_type9: default + len_tag_name: '' + lo_export0: 'False' + lo_export1: 'False' + lo_export10: 'False' + lo_export11: 'False' + lo_export12: 'False' + lo_export13: 'False' + lo_export14: 'False' + lo_export15: 'False' + lo_export16: 'False' + lo_export17: 'False' + lo_export18: 'False' + lo_export19: 'False' + lo_export2: 'False' + lo_export20: 'False' + lo_export21: 'False' + lo_export22: 'False' + lo_export23: 'False' + lo_export24: 'False' + lo_export25: 'False' + lo_export26: 'False' + lo_export27: 'False' + lo_export28: 'False' + lo_export29: 'False' + lo_export3: 'False' + lo_export30: 'False' + lo_export31: 'False' + lo_export4: 'False' + lo_export5: 'False' + lo_export6: 'False' + lo_export7: 'False' + lo_export8: 'False' + lo_export9: 'False' + lo_source0: internal + lo_source1: internal + lo_source10: internal + lo_source11: internal + lo_source12: internal + lo_source13: internal + lo_source14: internal + lo_source15: internal + lo_source16: internal + lo_source17: internal + lo_source18: internal + lo_source19: internal + lo_source2: internal + lo_source20: internal + lo_source21: internal + lo_source22: internal + lo_source23: internal + lo_source24: internal + lo_source25: internal + lo_source26: internal + lo_source27: internal + lo_source28: internal + lo_source29: internal + lo_source3: internal + lo_source30: internal + lo_source31: internal + lo_source4: internal + lo_source5: internal + lo_source6: internal + lo_source7: internal + lo_source8: internal + lo_source9: internal + maxoutbuf: '0' + minoutbuf: '0' + nchan: '1' + num_mboards: '1' + otw: '' + samp_rate: samp_rate + sd_spec0: '' + sd_spec1: '' + sd_spec2: '' + sd_spec3: '' + sd_spec4: '' + sd_spec5: '' + sd_spec6: '' + sd_spec7: '' + show_lo_controls: 'False' + start_time: '-1.0' + stream_args: '' + stream_chans: '[]' + sync: sync + time_source0: '' + time_source1: '' + time_source2: '' + time_source3: '' + time_source4: '' + time_source5: '' + time_source6: '' + time_source7: '' + type: fc32 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1144, 380.0] + rotation: 0 + state: enabled + +connections: +- [blocks_char_to_float_0, '0', blocks_repeat_2, '0'] +- [blocks_multiply_xx_0, '0', rational_resampler_xxx_0, '0'] +- [blocks_multiply_xx_1, '0', rational_resampler_xxx_1, '0'] +- [blocks_multiply_xx_1, '0', uhd_usrp_sink_0, '0'] +- [blocks_repeat_0, '0', blocks_multiply_xx_0, '1'] +- [blocks_repeat_1, '0', blocks_vco_c_0, '0'] +- [blocks_repeat_2, '0', interp_fir_filter_xxx_0, '0'] +- [blocks_vco_c_0, '0', blocks_multiply_xx_1, '1'] +- [blocks_vco_c_1, '0', blocks_multiply_xx_0, '0'] +- [blocks_vector_source_x_0, '0', digital_map_bb_0, '0'] +- [blocks_vector_source_x_1, '0', blocks_repeat_0, '0'] +- [blocks_vector_source_x_2, '0', blocks_repeat_1, '0'] +- [digital_map_bb_0, '0', blocks_char_to_float_0, '0'] +- [interp_fir_filter_xxx_0, '0', blocks_vco_c_1, '0'] +- [rational_resampler_xxx_0, '0', blocks_multiply_xx_1, '0'] +- [rational_resampler_xxx_1, '0', blocks_file_sink_0, '0'] + +metadata: + file_format: 1 + grc_version: 3.10.9.2 diff --git a/challenges/gotenna/gotenna_pro_tx_usrp.py b/challenges/gotenna/gotenna_pro_tx_usrp.py new file mode 100755 index 0000000..e00263a --- /dev/null +++ b/challenges/gotenna/gotenna_pro_tx_usrp.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# +# SPDX-License-Identifier: GPL-3.0 +# +# GNU Radio Python Flow Graph +# Title: Gotenna Pro Tx Usrp +# GNU Radio version: 3.10.5.0 + +from gnuradio import blocks +from gnuradio import digital +from gnuradio import filter +from gnuradio.filter import firdes +from gnuradio import gr +from gnuradio.fft import window +import sys +import signal +from argparse import ArgumentParser +from gnuradio.eng_arg import eng_float, intx +from gnuradio import eng_notation +from gnuradio import uhd +import time +import gotenna_packet +import math + + + + +class gotenna_pro_tx_usrp(gr.top_block): + + def __init__(self, baud_rate=9600, callsign='VE3IRR', counter_num=0, device_addr='', frequency=450000000, message='Hello world!', message_type='BROADCAST', publickey_data='base64key', recipient_gid=1234567891, sender_gid=1234567890): + gr.top_block.__init__(self, "Gotenna Pro Tx Usrp", catch_exceptions=True) + + ################################################## + # Parameters + ################################################## + self.baud_rate = baud_rate + self.callsign = callsign + self.counter_num = counter_num + self.device_addr = device_addr + self.frequency = frequency + self.message = message + self.message_type = message_type + self.publickey_data = publickey_data + self.recipient_gid = recipient_gid + self.sender_gid = sender_gid + + ################################################## + # Variables + ################################################## + self.samp_per_sym = samp_per_sym = 4 + self.offset = offset = 250000 + self.interp = interp = 480000 // baud_rate + self.taps = taps = firdes.gaussian(1.0, samp_per_sym, 0.5, 64) + self.samp_rate = samp_rate = baud_rate * samp_per_sym * interp + self.packets = packets = gotenna_packet.encode_pro_broadcast_packets(message_type, counter_num, sender_gid, recipient_gid, callsign, message, publickey_data) + self.fsk_deviation_hz = fsk_deviation_hz = {2400: 400, 4800: 750, 9600: 1100}[baud_rate] + self.data_chan = data_chan = 2 + self.control_chan = control_chan = 2 + self.center_freq = center_freq = frequency - offset + + ################################################## + # Blocks + ################################################## + self.uhd_usrp_sink_0 = uhd.usrp_sink( + ",".join((device_addr, "")), + uhd.stream_args( + cpu_format="fc32", + args='', + channels=list(range(0,1)), + ), + '', + ) + self.uhd_usrp_sink_0.set_samp_rate(samp_rate) + self.uhd_usrp_sink_0.set_time_unknown_pps(uhd.time_spec(0)) + + self.uhd_usrp_sink_0.set_center_freq(center_freq, 0) + self.uhd_usrp_sink_0.set_antenna("TX/RX", 0) + self.uhd_usrp_sink_0.set_gain(70, 0) + self.rational_resampler_xxx_0 = filter.rational_resampler_ccc( + interpolation=interp, + decimation=1, + taps=[], + fractional_bw=0) + self.interp_fir_filter_xxx_0 = filter.interp_fir_filter_fff(1, taps) + self.interp_fir_filter_xxx_0.declare_sample_delay(0) + self.digital_map_bb_0 = digital.map_bb([-3, -1, 3, 1, 0]) + self.blocks_vector_source_x_2 = blocks.vector_source_f([offset], True, 1, []) + self.blocks_vector_source_x_1 = blocks.vector_source_c([1], True, 1, []) + self.blocks_vector_source_x_0 = blocks.vector_source_b(gotenna_packet.pro_gfsk_symbols(packets), False, 1, []) + self.blocks_vco_c_1 = blocks.vco_c((baud_rate * samp_per_sym), (2 * math.pi * fsk_deviation_hz), 1.0) + self.blocks_vco_c_0 = blocks.vco_c(samp_rate, (2*math.pi), 0.9) + self.blocks_repeat_2 = blocks.repeat(gr.sizeof_float*1, samp_per_sym) + self.blocks_repeat_1 = blocks.repeat(gr.sizeof_float*1, (8 * samp_per_sym * interp)) + self.blocks_repeat_0 = blocks.repeat(gr.sizeof_gr_complex*1, (8 * samp_per_sym)) + self.blocks_multiply_xx_1 = blocks.multiply_vcc(1) + self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) + self.blocks_char_to_float_0 = blocks.char_to_float(1, 1) + + + ################################################## + # Connections + ################################################## + self.connect((self.blocks_char_to_float_0, 0), (self.blocks_repeat_2, 0)) + self.connect((self.blocks_multiply_xx_0, 0), (self.rational_resampler_xxx_0, 0)) + self.connect((self.blocks_multiply_xx_1, 0), (self.uhd_usrp_sink_0, 0)) + self.connect((self.blocks_repeat_0, 0), (self.blocks_multiply_xx_0, 1)) + self.connect((self.blocks_repeat_1, 0), (self.blocks_vco_c_0, 0)) + self.connect((self.blocks_repeat_2, 0), (self.interp_fir_filter_xxx_0, 0)) + self.connect((self.blocks_vco_c_0, 0), (self.blocks_multiply_xx_1, 1)) + self.connect((self.blocks_vco_c_1, 0), (self.blocks_multiply_xx_0, 0)) + self.connect((self.blocks_vector_source_x_0, 0), (self.digital_map_bb_0, 0)) + self.connect((self.blocks_vector_source_x_1, 0), (self.blocks_repeat_0, 0)) + self.connect((self.blocks_vector_source_x_2, 0), (self.blocks_repeat_1, 0)) + self.connect((self.digital_map_bb_0, 0), (self.blocks_char_to_float_0, 0)) + self.connect((self.interp_fir_filter_xxx_0, 0), (self.blocks_vco_c_1, 0)) + self.connect((self.rational_resampler_xxx_0, 0), (self.blocks_multiply_xx_1, 0)) + + + def get_baud_rate(self): + return self.baud_rate + + def set_baud_rate(self, baud_rate): + self.baud_rate = baud_rate + self.set_fsk_deviation_hz({2400: 400, 4800: 750, 9600: 1100}[self.baud_rate]) + self.set_interp(480000 // self.baud_rate) + self.set_samp_rate(self.baud_rate * self.samp_per_sym * self.interp) + + def get_callsign(self): + return self.callsign + + def set_callsign(self, callsign): + self.callsign = callsign + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) + + def get_counter_num(self): + return self.counter_num + + def set_counter_num(self, counter_num): + self.counter_num = counter_num + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) + + def get_device_addr(self): + return self.device_addr + + def set_device_addr(self, device_addr): + self.device_addr = device_addr + + def get_frequency(self): + return self.frequency + + def set_frequency(self, frequency): + self.frequency = frequency + self.set_center_freq(self.frequency - self.offset) + + def get_message(self): + return self.message + + def set_message(self, message): + self.message = message + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) + + def get_message_type(self): + return self.message_type + + def set_message_type(self, message_type): + self.message_type = message_type + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) + + def get_publickey_data(self): + return self.publickey_data + + def set_publickey_data(self, publickey_data): + self.publickey_data = publickey_data + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) + + def get_recipient_gid(self): + return self.recipient_gid + + def set_recipient_gid(self, recipient_gid): + self.recipient_gid = recipient_gid + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) + + def get_sender_gid(self): + return self.sender_gid + + def set_sender_gid(self, sender_gid): + self.sender_gid = sender_gid + self.set_packets(gotenna_packet.encode_pro_broadcast_packets(self.message_type, self.counter_num, self.sender_gid, self.recipient_gid, self.callsign, self.message, self.publickey_data)) + + def get_samp_per_sym(self): + return self.samp_per_sym + + def set_samp_per_sym(self, samp_per_sym): + self.samp_per_sym = samp_per_sym + self.set_samp_rate(self.baud_rate * self.samp_per_sym * self.interp) + self.set_taps(firdes.gaussian(1.0, self.samp_per_sym, 0.5, 64)) + self.blocks_repeat_0.set_interpolation((8 * self.samp_per_sym)) + self.blocks_repeat_1.set_interpolation((8 * self.samp_per_sym * self.interp)) + self.blocks_repeat_2.set_interpolation(self.samp_per_sym) + + def get_offset(self): + return self.offset + + def set_offset(self, offset): + self.offset = offset + self.set_center_freq(self.frequency - self.offset) + self.blocks_vector_source_x_2.set_data([self.offset], []) + + def get_interp(self): + return self.interp + + def set_interp(self, interp): + self.interp = interp + self.set_samp_rate(self.baud_rate * self.samp_per_sym * self.interp) + self.blocks_repeat_1.set_interpolation((8 * self.samp_per_sym * self.interp)) + + def get_taps(self): + return self.taps + + def set_taps(self, taps): + self.taps = taps + self.interp_fir_filter_xxx_0.set_taps(self.taps) + + def get_samp_rate(self): + return self.samp_rate + + def set_samp_rate(self, samp_rate): + self.samp_rate = samp_rate + self.uhd_usrp_sink_0.set_samp_rate(self.samp_rate) + + def get_packets(self): + return self.packets + + def set_packets(self, packets): + self.packets = packets + self.blocks_vector_source_x_0.set_data(gotenna_packet.pro_gfsk_symbols(self.packets), []) + + def get_fsk_deviation_hz(self): + return self.fsk_deviation_hz + + def set_fsk_deviation_hz(self, fsk_deviation_hz): + self.fsk_deviation_hz = fsk_deviation_hz + + def get_data_chan(self): + return self.data_chan + + def set_data_chan(self, data_chan): + self.data_chan = data_chan + + def get_control_chan(self): + return self.control_chan + + def set_control_chan(self, control_chan): + self.control_chan = control_chan + + def get_center_freq(self): + return self.center_freq + + def set_center_freq(self, center_freq): + self.center_freq = center_freq + self.uhd_usrp_sink_0.set_center_freq(self.center_freq, 0) + + + +def argument_parser(): + parser = ArgumentParser() + parser.add_argument( + "--baud-rate", dest="baud_rate", type=intx, default=9600, + help="Set Baud rate [default=%(default)r]") + parser.add_argument( + "--callsign", dest="callsign", type=str, default='VE3IRR', + help="Set Sender callsign [default=%(default)r]") + parser.add_argument( + "--counter-num", dest="counter_num", type=intx, default=0, + help="Set Counter [default=%(default)r]") + parser.add_argument( + "--device-addr", dest="device_addr", type=str, default='', + help="Set UHD device address [default=%(default)r]") + parser.add_argument( + "--frequency", dest="frequency", type=eng_float, default=eng_notation.num_to_str(float(450000000)), + help="Set Frequency [default=%(default)r]") + parser.add_argument( + "--message", dest="message", type=str, default='Hello world!', + help="Set Message [default=%(default)r]") + parser.add_argument( + "--message-type", dest="message_type", type=str, default='BROADCAST', + help="Set type [default=%(default)r]") + parser.add_argument( + "--publickey-data", dest="publickey_data", type=str, default='base64key', + help="Set Sender Public Key [default=%(default)r]") + parser.add_argument( + "--recipient-gid", dest="recipient_gid", type=intx, default=1234567891, + help="Set Recipient GID [default=%(default)r]") + parser.add_argument( + "--sender-gid", dest="sender_gid", type=intx, default=1234567890, + help="Set Sender GID [default=%(default)r]") + return parser + + +def main(top_block_cls=gotenna_pro_tx_usrp, options=None): + if options is None: + options = argument_parser().parse_args() + tb = top_block_cls(baud_rate=options.baud_rate, callsign=options.callsign, counter_num=options.counter_num, device_addr=options.device_addr, frequency=options.frequency, message=options.message, message_type=options.message_type, publickey_data=options.publickey_data, recipient_gid=options.recipient_gid, sender_gid=options.sender_gid) + + def sig_handler(sig=None, frame=None): + tb.stop() + tb.wait() + + sys.exit(0) + + signal.signal(signal.SIGINT, sig_handler) + signal.signal(signal.SIGTERM, sig_handler) + + tb.start() + + tb.wait() + + +if __name__ == '__main__': + main() diff --git a/devices.txt.format b/devices.txt.format index 3da464d..75c3380 100644 --- a/devices.txt.format +++ b/devices.txt.format @@ -1,2 +1,3 @@ dev_id,osmosdr_device_string 0,"bladerf=longhexserialnumberhere,biastee=1" +1,"uhd,serial=usrpserialnumberhere" From 6f4bb1fcafded1499fe0486985fb3d4ce979395f Mon Sep 17 00:00:00 2001 From: Dollarhyde Date: Mon, 27 Jul 2026 10:49:08 -0400 Subject: [PATCH 7/9] cleanup --- challenges/gotenna/__init__.py | 2 +- challenges/gotenna_packet/__init__.py | 5 - challenges/gotenna_packet/gotenna_packet.py | 806 ------------------ challenges/gotenna_packet/proto/__init__.py | 0 .../gotenna_packet/proto/base_message_pb2.py | 27 - .../gotenna_packet/proto/data_type_pb2.py | 36 - .../gotenna_packet/proto/frequency_pb2.py | 29 - challenges/gotenna_packet/proto/header_pb2.py | 27 - .../gotenna_packet/proto/location_pb2.py | 39 - .../gotenna_packet/proto/message_pb2.py | 44 - 10 files changed, 1 insertion(+), 1014 deletions(-) delete mode 100644 challenges/gotenna_packet/__init__.py delete mode 100755 challenges/gotenna_packet/gotenna_packet.py delete mode 100644 challenges/gotenna_packet/proto/__init__.py delete mode 100644 challenges/gotenna_packet/proto/base_message_pb2.py delete mode 100644 challenges/gotenna_packet/proto/data_type_pb2.py delete mode 100644 challenges/gotenna_packet/proto/frequency_pb2.py delete mode 100644 challenges/gotenna_packet/proto/header_pb2.py delete mode 100644 challenges/gotenna_packet/proto/location_pb2.py delete mode 100644 challenges/gotenna_packet/proto/message_pb2.py diff --git a/challenges/gotenna/__init__.py b/challenges/gotenna/__init__.py index ca25703..98476f2 100644 --- a/challenges/gotenna/__init__.py +++ b/challenges/gotenna/__init__.py @@ -1,4 +1,4 @@ # The flowgraphs in this package are generated by GNU Radio Companion from the # .grc files beside them, so they import the packet library under its bare name. # Importing challenges.gotenna_packet first registers it under that name. -from challenges import gotenna_packet # noqa: F401 +#from challenges import gotenna_packet # noqa: F401 diff --git a/challenges/gotenna_packet/__init__.py b/challenges/gotenna_packet/__init__.py deleted file mode 100644 index ed0bf15..0000000 --- a/challenges/gotenna_packet/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys - -sys.modules.setdefault("gotenna_packet", sys.modules[__name__]) - -from .gotenna_packet import * diff --git a/challenges/gotenna_packet/gotenna_packet.py b/challenges/gotenna_packet/gotenna_packet.py deleted file mode 100755 index f0ddee2..0000000 --- a/challenges/gotenna_packet/gotenna_packet.py +++ /dev/null @@ -1,806 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018-2024 Clayton Smith (argilo@gmail.com) -# -# This file is part of gr-tenna. -# -# gr-tenna is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# gr-tenna is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with gr-tenna. If not, see . -# - -import enum -import struct -import time -import reedsolo -import json -import base64 -import csv -import os -from .proto import base_message_pb2 -from .proto import data_type_pb2 -from .proto import message_pb2 -from google.protobuf import json_format -from cryptography.exceptions import InvalidTag -from cryptography.hazmat.primitives import hashes, hmac -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.primitives.ciphers.aead import AESGCM -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -from cryptography.hazmat.primitives.serialization import load_der_private_key - - -CRC16_TABLE = [ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, - 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, - 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, - 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, - 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, - 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, - 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, - 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, - 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, - 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, - 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, - 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, - 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, - 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, - 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, - 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, - 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, - 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, - 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, - 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, - 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, - 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, - 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 -] - -WHITENING = [ - 0x03, 0x96, 0x6a, 0x0f, 0xe7, 0xe5, 0x09, 0xde, 0xc4, 0xd8, 0x52, 0x3d, 0x76, 0x68, 0xbe, 0x2b, - 0x03, 0x8e, 0x1f, 0x09, 0x0e, 0x07, 0x6b, 0x4b, 0x61, 0x78, 0xb2, 0xfb, 0x81, 0x78, 0x36, 0x2a, - 0x1e, 0xcd, 0x8a, 0x76, 0x30, 0x85, 0x59, 0x32, 0xe1, 0x3c, 0xc9, 0xae, 0x34, 0x5c, 0x27, 0x98, - 0x34, 0x70, 0xcd, 0x88, 0x0c, 0xd4, 0x46, 0x0c, 0x62, 0x7d, 0xfc, 0x52, 0xe4, 0x03, 0x15, 0xb9, - 0x8a, 0x9f, 0x46, 0x64, 0x60, 0xe5, 0x98, 0x2c, 0x3e, 0x4b, 0x2e, 0xb7, 0x0f, 0xd9, 0xf2, 0x88, - 0xcf, 0xde, 0x8f, 0x31, 0xa2, 0x18, 0xb5, 0x0c, 0x20, 0x8f, 0x8c, 0x9f, 0x53, 0xa0, 0x6b, 0x4e, - 0x60, 0x78, 0x43, 0x88, 0x30, 0xb8, 0xd5, 0xfa, 0x13, 0xdf, 0x59, 0x41, 0xea, 0x85, 0x2a, 0xf3, - 0xc9, 0x27, 0xc2, 0x00, 0x69, 0xee, 0xb2, 0x4e, 0xd5, 0x09, 0x93, 0x93, 0x36, 0xff, 0xf5, 0xae, - 0xf2, 0xdf, 0x01, 0x1c, 0xb1, 0x9e, 0xa4, 0x73, 0x87, 0xf1, 0xa1, 0xcb, 0xae, 0x2e, 0x60, 0x1b, - 0x36, 0x0b, 0x45, 0xd0, 0xf0, 0xc8, 0x8d, 0xe9, 0x51, 0x25, 0xb4, 0x7e, 0x9b, 0x21, 0x02, 0x20, - 0xcb, 0x22, 0x46, 0x5a, 0xf2, 0x0c, 0xcb, 0x07, 0x17, 0x03, 0xde, 0x52, 0x96, 0x5a, 0x6e, 0x7d, - 0x14, 0x76, 0xc7, 0x1d, 0x01, 0xd4, 0x60, 0xf4, 0x5a, 0x12, 0xed, 0x6a, 0x7b, 0x6a, 0xd8, 0x41, - 0x93, 0xd3, 0x42, 0xfd, 0x46, 0x44, 0xaf, 0xda, 0x13, 0x3e, 0x25, 0x6a, 0x88, 0xbb, 0x4c, 0x65, - 0xac, 0x37, 0x7c, 0x6d, 0x9f, 0xd3, 0x3c, 0x17, 0xdc, 0x8a, 0x57, 0x4f, 0xcf, 0xb9, 0x0d, 0x96, - 0x18, 0xa6, 0x7b, 0xb2, 0xa6, 0x78, 0x33, 0xe1, 0x8f, 0xc4, 0xaf, 0x94, 0xd6, 0x31, 0x84, 0xb4, - 0x8c, 0xe9, 0x66, 0xb6, 0xd3, 0xd1, 0xca, 0x46, 0x3d, 0xd0, 0x62, 0x8d, 0x37, 0xc8, 0x84, 0x1c, -] - -CONTROL_CHANNELS = [903000000, 915000000, 927000000] - -DATA_CHANNELS = [ - 923500000, 923000000, 925500000, 926500000, 925000000, 908000000, 911500000, 905500000, - 903500000, 915500000, 909000000, 912000000, 922000000, 921000000, 924000000, 907500000, - 910000000, 912500000, 927500000, 910500000, 913000000, 918500000, 913500000, 914000000, - 902500000, 922500000, 907000000, 920000000, 919000000, 916500000, 919500000, 914500000, - 918000000, 911000000, 917500000, 916000000, 924500000, 909500000, 904500000, 904000000, - 917000000, 921500000, 920500000, 905000000, 908500000, 906500000, 926000000, 906000000 -] - -ONE_TO_ONE_GID = 0 -GROUP_GID = 1 -SHOUT_GID = 2 -EMERGENCY_GID = 3 - -TAG_TYPE = 1 -TAG_INITIALS = 3 -TAG_MESSAGE_CONTENT = 4 -TAG_MESSAGE = 5 -TAG_ENCRYPTION_INFO = 251 -TAG_PUBLIC_KEY = 252 - -MESH_SYNC = 0x01 -MESH_DATA = 0x02 -MESH_ACK = 0x03 -PRO_SYNC = 0x10 -PRO_DATA = 0x20 - - -class ConversationType(enum.Enum): - PRIVATE = 0 - GROUP = 1 - BROADCAST = 2 - EMERGENCY = 3 - UNKNOWN = 4 - ACTIVE_NODE = 5 - - -TEXT_MESSAGE = b"0" -MESH_KEY_EXCHANGE_REQUEST = b"14" -MESH_KEY_EXCHANGE_RESPONSE = b"15" - -type_names = { - TEXT_MESSAGE: "Text message", - MESH_KEY_EXCHANGE_REQUEST: "Mesh key exchange request", - MESH_KEY_EXCHANGE_RESPONSE: "Mesh key exchange response", -} - -PAYLOAD_SPLIT_LEN = 90 -CONTROL_PREAMBLE_LEN = 93 -DATA_PREAMBLE_LEN = 10 -HOP_TIME_1 = 33 -HOP_TIME_2 = 27 - -PRO_PAYLOAD_SPLIT_LEN = 94 -PRO_CONTROL_PREAMBLE_LEN = 58 -PRO_DATA_PREAMBLE_LEN = 30 -PRO_HOP_TIME = 33 - -public_keys = {} -private_keys = {} -group_keys = {} -broadcast_keys = {} - -key_types = { - "public": public_keys, - "private": private_keys, - "group": group_keys, - "broadcast": broadcast_keys, -} - -mesh_fragments = [] - - -def compress_point(x, y): - return bytes([2 | (y & 1)]) + x.to_bytes(48, byteorder="big") - - -def uncompress_point(data): - p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff - a = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc - b = 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef - - x = int.from_bytes(data[1:], byteorder="big") - y_squared = (pow(x, 3, p) + x * a + b) % p - y = pow(y_squared, (p + 1) // 4, p) - if (data[0] & 1) != (y & 1): - y = p - y - return x, y - - -def crc16(in_bytes): - reg = 0x0000 - for b in in_bytes: - reg = ((reg & 0xff) << 8) ^ CRC16_TABLE[(b ^ (reg >> 8)) & 0xff] - return reg - - -def encode_packet(preamble_len, in_bytes): - length = len(in_bytes) + 2 + 8 - packet = bytes([length]) + in_bytes - packet += struct.pack(">H", crc16(packet) ^ 0xabcd) - rs = reedsolo.RSCodec(nsym=8, fcr=1) - return bytes([0xaa]*preamble_len + [0x2d, 0xd4]) + rs.encode(packet) - - -def encode_tlv(type, value): - return bytes([type, len(value)]) + value - - -def encode_encryption_info(encrypted, sender_gid, timestamp, enc_counter, resend_id): - return struct.pack(">BQIHB", encrypted, sender_gid, timestamp, enc_counter, resend_id) - - -def encode_control_packet(channel, num_data_packets): - return encode_packet(CONTROL_PREAMBLE_LEN, bytes([9, channel, num_data_packets, 1, 1])) - - -def encode_data_packet(seq_no, segment): - return encode_packet(DATA_PREAMBLE_LEN, bytes([2, len(segment), seq_no]) + segment) - - -def encode_dest_gid(app_id, type, gid=None): - data = struct.pack(">BH", type, app_id) - if gid: - data += struct.pack(">Q", gid)[2:] - return data - - -def encode_shout_message(sender_gid, initials, message): - packet = encode_tlv(TAG_TYPE, TEXT_MESSAGE) - packet += encode_tlv(TAG_INITIALS, initials.encode("utf-8")) - packet += encode_tlv(TAG_MESSAGE_CONTENT, message.encode("utf-8")) - packet += struct.pack(">H", crc16(packet)) - - encryption_info = encode_encryption_info(False, sender_gid, int(time.time()), 0, 0) - return encode_tlv(TAG_ENCRYPTION_INFO, encryption_info) + packet - - -def encode_key_exchange_response(sender_gid, initials, public_key): - packet = encode_tlv(TAG_TYPE, MESH_KEY_EXCHANGE_RESPONSE) - packet += encode_tlv(TAG_INITIALS, initials.encode("utf-8")) - packet += encode_tlv(TAG_PUBLIC_KEY, public_key) - packet += struct.pack(">H", crc16(packet)) - - encryption_info = encode_encryption_info(False, sender_gid, int(time.time()), 0, 0) - return encode_tlv(TAG_ENCRYPTION_INFO, encryption_info) + packet - - -def encode_encrypted_payload(app_id, recipient_gid, sender_gid, counter, ciphertext): - encryption_info = encode_encryption_info(True, sender_gid, int(time.time()), counter, 0) - packet = encode_dest_gid(app_id, ONE_TO_ONE_GID, recipient_gid) - packet += bytes([0x00, 0xff, 0x00, 0x00]) - packet += encode_tlv(TAG_ENCRYPTION_INFO, encryption_info) - packet += ciphertext - return packet - - -def encode_packets(channel, payload): - packets = [] - seq_no = 0 - for offset in range(0, len(payload), PAYLOAD_SPLIT_LEN): - segment = payload[offset:offset+PAYLOAD_SPLIT_LEN] - packets.append(encode_data_packet(seq_no, segment)) - seq_no += 1 - return [encode_control_packet(channel, seq_no)] + packets - - -def encode_shout_packets(channel, app_id, sender_gid, initials, message): - payload = encode_dest_gid(app_id, SHOUT_GID) + encode_shout_message(sender_gid, initials, message) - return encode_packets(channel, payload) - - -def gfsk_bytes(packets): - data = [0]*HOP_TIME_1 + list(packets[0]) + [0]*HOP_TIME_1 - for packet in packets[1:]: - data += list(packet) + [0]*HOP_TIME_2 - return data - - -def envelope(packets): - envelope = [0]*HOP_TIME_1 + [1]*(len(packets[0])+1) + [0]*(HOP_TIME_1-1) - for packet in packets[1:]: - envelope += [1]*(len(packet)+1) + [0]*(HOP_TIME_2-1) - return envelope - - -def vco(center_freq, control_chan, data_chan, packets): - control_offset = CONTROL_CHANNELS[control_chan] - center_freq - vco = [control_offset]*(HOP_TIME_1-3+len(packets[0])+HOP_TIME_1) - for packet in packets[1:]: - data_offset = DATA_CHANNELS[data_chan] - center_freq - vco += [data_offset]*(len(packet)+HOP_TIME_2) - data_chan = (data_chan + 1) % len(DATA_CHANNELS) - vco += [data_offset]*3 - return vco - - -def decode_tlv(tag, value): - if tag == TAG_TYPE: - print(f"Type: {value.decode()} ({type_names[value]})") - elif tag == TAG_INITIALS: - print("Initials: " + value.decode()) - elif tag == TAG_MESSAGE_CONTENT: - if value[0] == 0xff: - print("Message content: " + value.hex()) - else: - print("Message content: " + value.decode()) - elif tag == TAG_MESSAGE: - print("Message:") - decode_tlvs(value) - elif tag == TAG_ENCRYPTION_INFO: - encrypted, sender_gid, timestamp, enc_counter, resend_id = struct.unpack(">BQIHB", value) - print("Encrypted: " + str(encrypted)) - print("Sender GID: " + str(sender_gid)) - print("Time: " + str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)))) - print("Encryption Counter: " + str(enc_counter)) - print("Resend ID: " + str(resend_id)) - elif tag == TAG_PUBLIC_KEY: - print("Public key: " + value.hex()) - else: - print("Tag: " + str(tag) + " value: " + value.hex()) - - -def decode_tlvs(data): - while len(data) >= 2: - tag = data[0] - length = data[1] - if 2 + length > len(data): - break - value = data[2:2+length] - data = data[2+length:] - decode_tlv(tag, value) - - -def gid_hash(gid): - reg = 170 - output = 0 - for i in range(40, -8, -8): - reg += (gid >> i) & 0xff - reg *= 48271 - reg %= 2**32 - reg += 1 - reg %= (2**31 - 1) - output ^= reg - return (output >> 16) ^ (output & 0xffff) - - -def encode_pro_packet(preamble_len, in_bytes): - packet_type = in_bytes[0] & 0x3f - nsym = 4 if packet_type == PRO_SYNC else 8 - - length = len(in_bytes) + 2 + nsym - packet = bytes([length]) + in_bytes - packet += struct.pack(">H", crc16(packet)) - rs = reedsolo.RSCodec(nsym=nsym, fcr=1) - packet = bytes(p ^ w for p, w in zip(rs.encode(packet), WHITENING)) - symbols = [2, 0]*preamble_len + [0, 2, 3, 1, 3, 1, 1, 0] - for p in packet: - symbols.append(p >> 6) - symbols.append((p >> 4) & 0b11) - symbols.append((p >> 2) & 0b11) - symbols.append(p & 0b11) - return symbols - - -def encode_pro_control_packet(num_data_packets, transmitter_gid): - packet = bytes([(num_data_packets << 6) | PRO_SYNC, 0x00, 0x00, 0x60, 0xff, 0xff]) \ - + struct.pack("H", gid_hash(sender_gid)) \ - + base_message.SerializeToString() - - if message_type == "PUBLICKEY": - base_message.header.messageType = data_type_pb2.PUBLIC_KEY_REQUEST - publickey = message_pb2.PBPublicKeyMessageData() - publickey.public_key = base64.b64decode(publickey_data) - base_message.messageData = publickey.SerializeToString() - - payload = bytes([(ConversationType.PRIVATE.value << 2) | 2, 0x80, 0x00, mode, counter, 0x00, 0x56, 0x28]) \ - + struct.pack(">H", gid_hash(sender_gid)) \ - + struct.pack(">H", gid_hash(recipient_gid)) \ - + base_message.SerializeToString() - - - return encode_pro_packets(payload, sender_gid) - - -def pro_gfsk_symbols(packets): - data = [4]*PRO_HOP_TIME - for packet in packets: - data += packet + [4]*PRO_HOP_TIME - return data - - -def decrypt_qr_message(password, salt, iv, sender_gid, key_data): - kdf = PBKDF2HMAC(algorithm=hashes.SHA1(), length=32, salt=salt, iterations=10000) - key = kdf.derive(password) - - full_iv = iv + struct.pack("= len(mesh_fragments): - print(f"Invalid fragment index: {fragIDX}") - return - - mesh_fragments[fragIDX] = data - if all(mesh_fragments): - data = b"".join(mesh_fragments) - if pro: - process_data_pro(data) - else: - process_data_mesh(data) - - -def process_data_mesh(data): - """Process a reassembled Gotenna Mesh packet""" - - offset = 0 - - conv_type = ConversationType(data[offset]) - print(f"Conversation type: {conv_type.name}") - offset += 1 - - app_id = struct.unpack(">H", data[offset:offset+2])[0] - print(f"App ID: {app_id:04x}") - offset += 2 - - if conv_type in (ConversationType.PRIVATE, ConversationType.GROUP): - gid_hi, gid_lo = struct.unpack(">HL", data[offset:offset+6]) - gid = (gid_hi << 32) | gid_lo - print(f"Dest. GID: {gid}") - offset += 6 - - print(f"Unknown: {data[offset:offset+4].hex()}") - offset += 4 - - decode_tlvs(data[offset:]) - - -def process_data_pro(data): - """Process a reassembled Gotenna Pro packet""" - - print(f"Reassembled packet: {data.hex()}") - - offset = 0 - - conv_type = ConversationType(data[offset] >> 2) - print(f"Conversation type: {conv_type.name}") - offset += 1 - - print(f"Unknown: {data[offset:offset+2].hex()}") - offset += 2 - mode = data[offset] - print(f"mode: {hex(data[offset])}") - offset += 1 - print(f"Counter: {data[offset]}") - offset += 1 - print(f"Unknown: {data[offset:offset+3].hex()}") - offset += 3 - print(f"Sender GID hash: {data[offset:offset+2].hex()}") - offset += 2 - - if conv_type in (ConversationType.PRIVATE, ConversationType.GROUP): - recipient_gid_hash = struct.unpack(">H", data[offset:offset+2])[0] - offset += 2 - print(f"Recipient GID hash: {recipient_gid_hash:04x}") - recipient_gid = None - key_store = public_keys if conv_type == ConversationType.PRIVATE else group_keys - for gid in key_store.keys(): - if gid_hash(gid) == recipient_gid_hash: - recipient_gid = gid - print(f"Found recipient GID: {recipient_gid}") - break - - if mode == 0x80: - print(f"Unknown: {data[offset:].hex()}") - return - elif mode == 0x20: - print(f"Unknown: {data[offset:].hex()}") - return - else: - base_message = base_message_pb2.PBBaseMessage() - try: - base_message.ParseFromString(data[offset:]) - except google.protobuf.message.DecodeError: - print("Error parsing base message") - return - - print(json_format.MessageToJson(base_message.header)) - - message_type = base_message.header.messageType - - if message_type == data_type_pb2.PBMessageDataType.GROUP_CREATION: - message = None - priv_key = private_keys.get(base_message.header.sender_gid) - pub_key = public_keys.get(recipient_gid) - if not (priv_key and pub_key): - priv_key = private_keys.get(recipient_gid) - pub_key = public_keys.get(base_message.header.sender_gid) - - if priv_key and pub_key: - ec_priv_key = load_der_private_key(priv_key, None) - curve = ec.SECP384R1() - ec_pub_key = ec.EllipticCurvePublicKey.from_encoded_point(curve, pub_key) - - shared_key = ec_priv_key.exchange(ec.ECDH(), ec_pub_key) - base_message.messageData = decrypt_p2p_group(base_message.messageData, - shared_key, - base_message.header.sender_gid, - base_message.header.iv) - message = message_pb2.PBGroupCreationMessageData() - elif message_type == data_type_pb2.PBMessageDataType.MAP_OBJECT: - message = message_pb2.PBMapObjectMessageData() - elif message_type == data_type_pb2.PBMessageDataType.REQUEST: - message = message_pb2.PBRequestMessageData() - elif message_type == data_type_pb2.PBMessageDataType.TEXT: - if base_message.header.iv: - message = None - if conv_type == ConversationType.BROADCAST: - uuid = base_message.header.keyUuid - if uuid in broadcast_keys: - shared_key = broadcast_keys[uuid] - base_message.messageData = decrypt_broadcast(base_message.messageData, - shared_key, - base_message.header.sender_gid, - base_message.header.iv) - message = message_pb2.PBTextMessageData() - if conv_type == ConversationType.GROUP: - shared_key = group_keys.get(recipient_gid) - if shared_key: - base_message.messageData = decrypt_p2p_group(base_message.messageData, - shared_key, - base_message.header.sender_gid, - base_message.header.iv) - message = message_pb2.PBTextMessageData() - if conv_type == ConversationType.PRIVATE: - priv_key = private_keys.get(base_message.header.sender_gid) - pub_key = public_keys.get(recipient_gid) - if not (priv_key and pub_key): - priv_key = private_keys.get(recipient_gid) - pub_key = public_keys.get(base_message.header.sender_gid) - - if priv_key and pub_key: - ec_priv_key = load_der_private_key(priv_key, None) - curve = ec.SECP384R1() - ec_pub_key = ec.EllipticCurvePublicKey.from_encoded_point(curve, pub_key) - - shared_key = ec_priv_key.exchange(ec.ECDH(), ec_pub_key) - base_message.messageData = decrypt_p2p_group(base_message.messageData, - shared_key, - base_message.header.sender_gid, - base_message.header.iv) - message = message_pb2.PBTextMessageData() - else: - message = message_pb2.PBTextMessageData() - elif message_type == data_type_pb2.PBMessageDataType.FREQUENCY: - message = message_pb2.PBFrequencyMessageData() - elif message_type == data_type_pb2.PBMessageDataType.PING: - message = None - elif message_type == data_type_pb2.PBMessageDataType.EMERGENCY_BEACON: - message = None - elif message_type == data_type_pb2.PBMessageDataType.PUBLIC_KEY_REQUEST: - message = message_pb2.PBPublicKeyMessageData() - elif message_type == data_type_pb2.PBMessageDataType.PUBLIC_KEY_RESPONSE: - message = message_pb2.PBPublicKeyMessageData() - elif message_type == data_type_pb2.PBMessageDataType.BROADCAST_QR: - message = message_pb2.PBBroadcastQrMessageData() - elif message_type == data_type_pb2.PBMessageDataType.PLI: - message = None - elif message_type == data_type_pb2.PBMessageDataType.SHARED_LOCATION: - message = message_pb2.PBLocationMessageData() - elif message_type == data_type_pb2.PBMessageDataType.COMMS_CHECK: - message = None - elif message_type == data_type_pb2.PBMessageDataType.MANUAL_PLI: - message = None - - if message: - message.ParseFromString(base_message.messageData) - - if isinstance(message, message_pb2.PBLocationMessageData): - json_dict = json.loads(json_format.MessageToJson(message)) - json_dict['coordinate'] = decode_coords(message.coordinate) - print(json.dumps(json_dict, indent=2)) - elif isinstance(message, message_pb2.PBMapObjectMessageData): - json_dict = json.loads(json_format.MessageToJson(message)) - if message.HasField("pin"): - json_dict['pin']['coordinate'] = decode_coords(message.pin.coordinate) - elif message.HasField("shape"): - if message.shape.HasField("circle_data"): - json_dict['shape']['circleData']['center'] = decode_coords(message.shape.circle_data.center) - elif message.shape.HasField("rectangle_data"): - json_dict['shape']['rectangleData']['cornerOne'] = decode_coords(message.shape.rectangle_data.cornerOne) - json_dict['shape']['rectangleData']['cornerTwo'] = decode_coords(message.shape.rectangle_data.cornerTwo) - json_dict['shape']['rectangleData']['depth'] = decode_coords(message.shape.rectangle_data.depth) - elif message.shape.HasField("perimeter_data"): - json_dict['shape']['perimeterData']['dataPoints'] = decode_coords_list(message.shape.perimeter_data.data_points) - elif message.shape.HasField("route_data"): - json_dict['shape']['routeData']['dataPoints'] = decode_coords_list(message.shape.route_data.data_points) - print(json.dumps(json_dict, indent=2)) - elif isinstance(message, message_pb2.PBPublicKeyMessageData): - curve = ec.SECP384R1() - public_key = ec.EllipticCurvePublicKey.from_encoded_point(curve, message.public_key) - print(public_key.public_numbers()) - print(json_format.MessageToJson(message)) - else: - print(json_format.MessageToJson(message)) - - # Store any observed keys - if isinstance(message, message_pb2.PBGroupCreationMessageData): - group_keys[message.group_gid] = message.group_shared_key - save_keys() - elif isinstance(message, message_pb2.PBPublicKeyMessageData): - public_keys[base_message.header.sender_gid] = message.public_key - save_keys() - - else: - print(f"Encrypted/unknown message: {base_message.messageData.hex()}") - - -def correct_packet(data): - data = bytes(data) - packet_type = data[1] & 0x3f - - # Reed-Solomon error correction - nsym = 4 if packet_type == PRO_SYNC else 8 - rs = reedsolo.RSCodec(nsym=nsym, fcr=1) - try: - data, _, _ = rs.decode(data) - except reedsolo.ReedSolomonError: - raise Exception("Too many byte errors") - - # CRC check - packetCRC, = struct.unpack(">H", data[-2:]) - diff = packetCRC ^ crc16(data[:-2]) - if diff not in (0x0000, 0xabcd): - raise Exception("Invalid CRC") - - # Strip length header & CRC - return data[1:-2] - - -def ingest_packet(data): - global mesh_fragments - - print() - print(data.hex()) - packet_type = data[0] & 0x3f - - if packet_type == MESH_SYNC: - # Gotenna Mesh "SYNC" packet - contains data channel index and TTL data - chIDX, frags, iniTTL, curTTL = struct.unpack("BBBB", data[1:5]) - print(f"RX SYNC (0x{packet_type:02x}): chIDX={chIDX}, frags={frags}, iniTTL={iniTTL}, curTTL={curTTL}") - mesh_fragments = [None] * frags - # in real life, hop to index ++chIDX in datachan map - # to receive first data packet of this transmission - - elif packet_type == MESH_DATA: - # Gotenna Mesh "DATA" packet - contains message data, may be fragmented - datalen, fragIDX = struct.unpack("BB", data[1:3]) - print(f"RX DATA (0x{packet_type:02x}): len={datalen}, fragIDX={fragIDX}") - # strip first 4 bytes and send to reassembly - process_data_frag(data[3:], fragIDX) - # in real life, hop to index ++chIDX in datachan map - # to receive *next* data packet of this transmission - - elif packet_type == MESH_ACK: - # Gotenna Mesh "ACK" packet - contains message hash ID and number of hops - hashID, hopTTL, curTTL = struct.unpack(">HBB", data[1:5]) - print(f"RX ACK (0x{packet_type:02x}): hash=0x{hashID:04x}, TTL/hop(?)=0x{hopTTL:02x}, curTTL={curTTL}") - - elif packet_type == PRO_SYNC: - # Gotenna Pro "SYNC" packet - frags = data[0] >> 6 - print(f"RX SYNC (0x{packet_type:02x}): frags={frags} unknown={data[1:].hex()}") - mesh_fragments = [None] * frags - - elif packet_type == PRO_DATA: - # Gotenna Pro "DATA" packet - fragIDX = data[0] >> 6 - print(f"RX DATA (0x{packet_type:02x}): fragIDX={fragIDX}") - process_data_frag(data[1:], fragIDX, pro=True) - - else: - print(f"RX TYPE 0x{packet_type:02x} UNKNOWN") - - -xdg_data = os.getenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share")) -data_dir = os.path.join(xdg_data, "gr-tenna") -os.makedirs(data_dir, exist_ok=True) -keys_filename = os.path.join(data_dir, "keys.csv") - - -def save_keys(filename=keys_filename): - with open(filename, "w", newline="") as csv_file: - keys_writer = csv.DictWriter(csv_file, fieldnames=["key_type", "name", "value_base64"]) - keys_writer.writeheader() - for key_type, key_store in key_types.items(): - for name, value in key_store.items(): - keys_writer.writerow({"key_type": key_type, "name": name, "value_base64": base64.b64encode(value).decode("ASCII")}) - - -def load_keys(filename=keys_filename): - if os.path.exists(keys_filename): - with open(filename, newline="") as csv_file: - keys_reader = csv.DictReader(csv_file) - for row in keys_reader: - key_type = row["key_type"] - name = row["name"] if key_type == "broadcast" else int(row["name"]) - value = base64.b64decode(row["value_base64"]) - - key_store = key_types[key_type] - key_store[name] = value - - -load_keys() diff --git a/challenges/gotenna_packet/proto/__init__.py b/challenges/gotenna_packet/proto/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/challenges/gotenna_packet/proto/base_message_pb2.py b/challenges/gotenna_packet/proto/base_message_pb2.py deleted file mode 100644 index 5825dd2..0000000 --- a/challenges/gotenna_packet/proto/base_message_pb2.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: gotenna_packet/proto/base_message.proto -# Protobuf Python Version: 4.25.2 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from . import header_pb2 as gotenna__packet_dot_proto_dot_header__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'gotenna_packet/proto/base_message.proto\x1a!gotenna_packet/proto/header.proto\"C\n\rPBBaseMessage\x12\x1d\n\x06header\x18\x01 \x01(\x0b\x32\r.PBBaseHeader\x12\x13\n\x0bmessageData\x18\x02 \x01(\x0c\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.base_message_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _globals['_PBBASEMESSAGE']._serialized_start=78 - _globals['_PBBASEMESSAGE']._serialized_end=145 -# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_packet/proto/data_type_pb2.py b/challenges/gotenna_packet/proto/data_type_pb2.py deleted file mode 100644 index 803d920..0000000 --- a/challenges/gotenna_packet/proto/data_type_pb2.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: gotenna_packet/proto/data_type.proto -# Protobuf Python Version: 4.25.2 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$gotenna_packet/proto/data_type.proto*\xff\x01\n\x11PBMessageDataType\x12\x12\n\x0eGROUP_CREATION\x10\x00\x12\x0e\n\nMAP_OBJECT\x10\x01\x12\x0b\n\x07REQUEST\x10\x02\x12\x08\n\x04TEXT\x10\x03\x12\r\n\tFREQUENCY\x10\x04\x12\x08\n\x04PING\x10\x05\x12\x14\n\x10\x45MERGENCY_BEACON\x10\x06\x12\x16\n\x12PUBLIC_KEY_REQUEST\x10\x07\x12\x17\n\x13PUBLIC_KEY_RESPONSE\x10\x08\x12\x10\n\x0c\x42ROADCAST_QR\x10\t\x12\x07\n\x03PLI\x10\n\x12\x13\n\x0fSHARED_LOCATION\x10\x0b\x12\x0f\n\x0b\x43OMMS_CHECK\x10\x0c\x12\x0e\n\nMANUAL_PLI\x10\r*e\n\x0ePBGeofenceType\x12\x13\n\x0fGEOFENCING_NONE\x10\x00\x12\x14\n\x10GEOFENCING_ENTRY\x10\x01\x12\x13\n\x0fGEOFENCING_EXIT\x10\x02\x12\x13\n\x0fGEOFENCING_BOTH\x10\x03*V\n\x0bPBPowerType\x12\r\n\tHALF_WATT\x10\x00\x12\x0c\n\x08ONE_WATT\x10\x01\x12\x0c\n\x08TWO_WATT\x10\x02\x12\r\n\tFOUR_WATT\x10\x03\x12\r\n\tFIVE_WATT\x10\x04*B\n\x0fPBBandwidthType\x12\x0e\n\nBW_4_84KHZ\x10\x00\x12\x0e\n\nBW_7_28KHZ\x10\x01\x12\x0f\n\x0b\x42W_11_80KHZ\x10\x02*4\n\tPBPinType\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\r\n\tEMERGENCY\x10\x01\x12\x0b\n\x07WARNING\x10\x02*.\n\x12PBConversationType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0b\n\x07\x43OMMAND\x10\x01\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.data_type_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _globals['_PBMESSAGEDATATYPE']._serialized_start=41 - _globals['_PBMESSAGEDATATYPE']._serialized_end=296 - _globals['_PBGEOFENCETYPE']._serialized_start=298 - _globals['_PBGEOFENCETYPE']._serialized_end=399 - _globals['_PBPOWERTYPE']._serialized_start=401 - _globals['_PBPOWERTYPE']._serialized_end=487 - _globals['_PBBANDWIDTHTYPE']._serialized_start=489 - _globals['_PBBANDWIDTHTYPE']._serialized_end=555 - _globals['_PBPINTYPE']._serialized_start=557 - _globals['_PBPINTYPE']._serialized_end=609 - _globals['_PBCONVERSATIONTYPE']._serialized_start=611 - _globals['_PBCONVERSATIONTYPE']._serialized_end=657 -# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_packet/proto/frequency_pb2.py b/challenges/gotenna_packet/proto/frequency_pb2.py deleted file mode 100644 index 6b00ebe..0000000 --- a/challenges/gotenna_packet/proto/frequency_pb2.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: gotenna_packet/proto/frequency.proto -# Protobuf Python Version: 4.25.2 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from . import data_type_pb2 as gotenna__packet_dot_proto_dot_data__type__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$gotenna_packet/proto/frequency.proto\x1a$gotenna_packet/proto/data_type.proto\".\n\tPBChannel\x12\r\n\x05hertz\x18\x01 \x01(\r\x12\x12\n\nis_control\x18\x02 \x01(\x08\"\xad\x01\n\x0bPBFrequency\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x11\n\tcall_sign\x18\x03 \x01(\t\x12\x1b\n\x05power\x18\x04 \x01(\x0e\x32\x0c.PBPowerType\x12#\n\tbandwidth\x18\x05 \x01(\x0e\x32\x10.PBBandwidthType\x12\x10\n\x08use_only\x18\x06 \x01(\x08\x12\x1c\n\x08\x63hannels\x18\x07 \x03(\x0b\x32\n.PBChannelb\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.frequency_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _globals['_PBCHANNEL']._serialized_start=78 - _globals['_PBCHANNEL']._serialized_end=124 - _globals['_PBFREQUENCY']._serialized_start=127 - _globals['_PBFREQUENCY']._serialized_end=300 -# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_packet/proto/header_pb2.py b/challenges/gotenna_packet/proto/header_pb2.py deleted file mode 100644 index 790158d..0000000 --- a/challenges/gotenna_packet/proto/header_pb2.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: gotenna_packet/proto/header.proto -# Protobuf Python Version: 4.25.2 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from . import data_type_pb2 as gotenna__packet_dot_proto_dot_data__type__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!gotenna_packet/proto/header.proto\x1a$gotenna_packet/proto/data_type.proto\"\xbc\x01\n\x0cPBBaseHeader\x12\x12\n\nsender_gid\x18\x01 \x01(\x04\x12\x10\n\x08\x63\x61llsign\x18\x02 \x01(\t\x12\'\n\x0bmessageType\x18\x03 \x01(\x0e\x32\x12.PBMessageDataType\x12\x0f\n\x07keyUuid\x18\x04 \x01(\t\x12\n\n\x02iv\x18\x05 \x01(\x0c\x12-\n\x10\x63onversationType\x18\x06 \x01(\x0e\x32\x13.PBConversationType\x12\x11\n\ttimestamp\x18\x07 \x01(\x01\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.header_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _globals['_PBBASEHEADER']._serialized_start=76 - _globals['_PBBASEHEADER']._serialized_end=264 -# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_packet/proto/location_pb2.py b/challenges/gotenna_packet/proto/location_pb2.py deleted file mode 100644 index 51355f3..0000000 --- a/challenges/gotenna_packet/proto/location_pb2.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: gotenna_packet/proto/location.proto -# Protobuf Python Version: 4.25.2 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from . import data_type_pb2 as gotenna__packet_dot_proto_dot_data__type__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#gotenna_packet/proto/location.proto\x1a$gotenna_packet/proto/data_type.proto\"\x90\x02\n\x0bPBShapeData\x12\r\n\x05\x63olor\x18\x01 \x01(\r\x12&\n\rgeofence_type\x18\x02 \x01(\x0e\x32\x0f.PBGeofenceType\x12)\n\nroute_data\x18\x04 \x01(\x0b\x32\x13.PBRouteMessageDataH\x00\x12\x31\n\x0eperimeter_data\x18\x05 \x01(\x0b\x32\x17.PBPerimeterMessageDataH\x00\x12+\n\x0b\x63ircle_data\x18\x06 \x01(\x0b\x32\x14.PBCircleMessageDataH\x00\x12\x31\n\x0erectangle_data\x18\x07 \x01(\x0b\x32\x17.PBRectangleMessageDataH\x00\x42\x0c\n\nmap_object\"=\n\tPBPinData\x12\x12\n\ncoordinate\x18\x01 \x01(\x0c\x12\x1c\n\x08pin_type\x18\x02 \x01(\x0e\x32\n.PBPinType\")\n\x12PBRouteMessageData\x12\x13\n\x0b\x64\x61ta_points\x18\x01 \x01(\x0c\"-\n\x16PBPerimeterMessageData\x12\x13\n\x0b\x64\x61ta_points\x18\x01 \x01(\x0c\"5\n\x13PBCircleMessageData\x12\x0e\n\x06\x63\x65nter\x18\x01 \x01(\x0c\x12\x0e\n\x06radius\x18\x02 \x01(\x04\"M\n\x16PBRectangleMessageData\x12\x11\n\tcornerOne\x18\x01 \x01(\x0c\x12\x11\n\tcornerTwo\x18\x02 \x01(\x0c\x12\r\n\x05\x64\x65pth\x18\x03 \x01(\x0c\"3\n\x0cPBCoordinate\x12\x10\n\x08latitude\x18\x01 \x01(\x01\x12\x11\n\tlongitude\x18\x02 \x01(\x01\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.location_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _globals['_PBSHAPEDATA']._serialized_start=78 - _globals['_PBSHAPEDATA']._serialized_end=350 - _globals['_PBPINDATA']._serialized_start=352 - _globals['_PBPINDATA']._serialized_end=413 - _globals['_PBROUTEMESSAGEDATA']._serialized_start=415 - _globals['_PBROUTEMESSAGEDATA']._serialized_end=456 - _globals['_PBPERIMETERMESSAGEDATA']._serialized_start=458 - _globals['_PBPERIMETERMESSAGEDATA']._serialized_end=503 - _globals['_PBCIRCLEMESSAGEDATA']._serialized_start=505 - _globals['_PBCIRCLEMESSAGEDATA']._serialized_end=558 - _globals['_PBRECTANGLEMESSAGEDATA']._serialized_start=560 - _globals['_PBRECTANGLEMESSAGEDATA']._serialized_end=637 - _globals['_PBCOORDINATE']._serialized_start=639 - _globals['_PBCOORDINATE']._serialized_end=690 -# @@protoc_insertion_point(module_scope) diff --git a/challenges/gotenna_packet/proto/message_pb2.py b/challenges/gotenna_packet/proto/message_pb2.py deleted file mode 100644 index 00a361d..0000000 --- a/challenges/gotenna_packet/proto/message_pb2.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: gotenna_packet/proto/message.proto -# Protobuf Python Version: 4.25.2 -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from . import location_pb2 as gotenna__packet_dot_proto_dot_location__pb2 -from . import frequency_pb2 as gotenna__packet_dot_proto_dot_frequency__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"gotenna_packet/proto/message.proto\x1a#gotenna_packet/proto/location.proto\x1a$gotenna_packet/proto/frequency.proto\"\x90\x01\n\x16PBMapObjectMessageData\x12\x11\n\ttimestamp\x18\x01 \x01(\x01\x12\x0c\n\x04uuid\x18\x02 \x01(\x04\x12\r\n\x05title\x18\x03 \x01(\t\x12\x1d\n\x05shape\x18\x04 \x01(\x0b\x32\x0c.PBShapeDataH\x00\x12\x19\n\x03pin\x18\x05 \x01(\x0b\x32\n.PBPinDataH\x00\x42\x0c\n\nmap_object\"\x90\x01\n\x15PBLocationMessageData\x12\x12\n\ncoordinate\x18\x01 \x01(\x0c\x12#\n\x1bpli_sharing_frequency_index\x18\x02 \x01(\r\x12\x1d\n\x15pli_location_accuracy\x18\x03 \x01(\r\x12\x11\n\ttimestamp\x18\x04 \x01(\x01\x12\x0c\n\x04text\x18\x05 \x01(\t\"$\n\x14PBRequestMessageData\x12\x0c\n\x04text\x18\x01 \x01(\t\"/\n\x11PBTextMessageData\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x0c\n\x04uuid\x18\x02 \x01(\t\"G\n\x16PBFrequencyMessageData\x12\x0c\n\x04text\x18\x01 \x01(\t\x12\x1f\n\tfrequency\x18\x02 \x01(\x0b\x32\x0c.PBFrequency\"\xa9\x01\n\x1aPBGroupCreationMessageData\x12@\n\rgroup_members\x18\x01 \x03(\x0b\x32).PBGroupCreationMessageData.PBGroupMember\x12\x18\n\x10group_shared_key\x18\x02 \x01(\x0c\x12\x11\n\tgroup_gid\x18\x03 \x01(\x04\x1a\x1c\n\rPBGroupMember\x12\x0b\n\x03gid\x18\x01 \x01(\x04\",\n\x16PBPublicKeyMessageData\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\"a\n\x18PBBroadcastQrMessageData\x12\x0c\n\x04name\x18\x01 \x01(\x0c\x12\x0c\n\x04uuid\x18\x02 \x01(\x0c\x12\x0c\n\x04salt\x18\x03 \x01(\x0c\x12\n\n\x02iv\x18\x04 \x01(\x0c\x12\x0f\n\x07keyData\x18\x05 \x01(\x0c\x62\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gotenna_packet.proto.message_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _globals['_PBMAPOBJECTMESSAGEDATA']._serialized_start=114 - _globals['_PBMAPOBJECTMESSAGEDATA']._serialized_end=258 - _globals['_PBLOCATIONMESSAGEDATA']._serialized_start=261 - _globals['_PBLOCATIONMESSAGEDATA']._serialized_end=405 - _globals['_PBREQUESTMESSAGEDATA']._serialized_start=407 - _globals['_PBREQUESTMESSAGEDATA']._serialized_end=443 - _globals['_PBTEXTMESSAGEDATA']._serialized_start=445 - _globals['_PBTEXTMESSAGEDATA']._serialized_end=492 - _globals['_PBFREQUENCYMESSAGEDATA']._serialized_start=494 - _globals['_PBFREQUENCYMESSAGEDATA']._serialized_end=565 - _globals['_PBGROUPCREATIONMESSAGEDATA']._serialized_start=568 - _globals['_PBGROUPCREATIONMESSAGEDATA']._serialized_end=737 - _globals['_PBGROUPCREATIONMESSAGEDATA_PBGROUPMEMBER']._serialized_start=709 - _globals['_PBGROUPCREATIONMESSAGEDATA_PBGROUPMEMBER']._serialized_end=737 - _globals['_PBPUBLICKEYMESSAGEDATA']._serialized_start=739 - _globals['_PBPUBLICKEYMESSAGEDATA']._serialized_end=783 - _globals['_PBBROADCASTQRMESSAGEDATA']._serialized_start=785 - _globals['_PBBROADCASTQRMESSAGEDATA']._serialized_end=882 -# @@protoc_insertion_point(module_scope) From 46ba557d5f0cd35194291a2659acec1fc0467c22 Mon Sep 17 00:00:00 2001 From: Dollarhyde Date: Mon, 27 Jul 2026 12:04:31 -0400 Subject: [PATCH 8/9] Removed uhd device --- devices.txt.format | 1 - 1 file changed, 1 deletion(-) diff --git a/devices.txt.format b/devices.txt.format index 75c3380..3da464d 100644 --- a/devices.txt.format +++ b/devices.txt.format @@ -1,3 +1,2 @@ dev_id,osmosdr_device_string 0,"bladerf=longhexserialnumberhere,biastee=1" -1,"uhd,serial=usrpserialnumberhere" From 890de8c47efec88da2ac38d035f93666754a0229 Mon Sep 17 00:00:00 2001 From: Dan Perret Date: Tue, 28 Jul 2026 03:22:23 -0400 Subject: [PATCH 9/9] Remove signal handler function from gotenna flowgraphs. --- challenges/gotenna/gotenna_pro_tx_bladerf.py | 12 ++++++------ challenges/gotenna/gotenna_pro_tx_usrp.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/challenges/gotenna/gotenna_pro_tx_bladerf.py b/challenges/gotenna/gotenna_pro_tx_bladerf.py index c58601f..5a8d886 100755 --- a/challenges/gotenna/gotenna_pro_tx_bladerf.py +++ b/challenges/gotenna/gotenna_pro_tx_bladerf.py @@ -328,14 +328,14 @@ def main(top_block_cls=gotenna_pro_tx_bladerf, options=None): options = argument_parser().parse_args() tb = top_block_cls(antenna=options.antenna, baud_rate=options.baud_rate, callsign=options.callsign, counter_num=options.counter_num, device_args=options.device_args, frequency=options.frequency, message=options.message, message_type=options.message_type, publickey_data=options.publickey_data, recipient_gid=options.recipient_gid, sender_gid=options.sender_gid) - def sig_handler(sig=None, frame=None): - tb.stop() - tb.wait() + #def sig_handler(sig=None, frame=None): + # tb.stop() + # tb.wait() - sys.exit(0) + # sys.exit(0) - signal.signal(signal.SIGINT, sig_handler) - signal.signal(signal.SIGTERM, sig_handler) + #signal.signal(signal.SIGINT, sig_handler) + #signal.signal(signal.SIGTERM, sig_handler) tb.start() diff --git a/challenges/gotenna/gotenna_pro_tx_usrp.py b/challenges/gotenna/gotenna_pro_tx_usrp.py index e00263a..06982dd 100755 --- a/challenges/gotenna/gotenna_pro_tx_usrp.py +++ b/challenges/gotenna/gotenna_pro_tx_usrp.py @@ -304,14 +304,14 @@ def main(top_block_cls=gotenna_pro_tx_usrp, options=None): options = argument_parser().parse_args() tb = top_block_cls(baud_rate=options.baud_rate, callsign=options.callsign, counter_num=options.counter_num, device_addr=options.device_addr, frequency=options.frequency, message=options.message, message_type=options.message_type, publickey_data=options.publickey_data, recipient_gid=options.recipient_gid, sender_gid=options.sender_gid) - def sig_handler(sig=None, frame=None): - tb.stop() - tb.wait() + #def sig_handler(sig=None, frame=None): + # tb.stop() + # tb.wait() - sys.exit(0) + # sys.exit(0) - signal.signal(signal.SIGINT, sig_handler) - signal.signal(signal.SIGTERM, sig_handler) + #signal.signal(signal.SIGINT, sig_handler) + #signal.signal(signal.SIGTERM, sig_handler) tb.start()