merge branch max into master

This commit is contained in:
Max 2019-11-14 22:10:34 -05:00
commit b6b0eeb7b8
149 changed files with 15769 additions and 1548 deletions

View File

@ -1,6 +1,9 @@
cmake_minimum_required(VERSION 2.6)
project(gr-op25 CXX C)
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_FLAGS "-std=c++11")
add_subdirectory(op25/gr-op25)
add_subdirectory(op25/gr-op25_repeater)

View File

@ -12,7 +12,7 @@ fi
sudo apt-get update
sudo apt-get build-dep gnuradio
sudo apt-get install gnuradio gnuradio-dev gr-osmosdr librtlsdr-dev libuhd-dev libhackrf-dev libitpp-dev libpcap-dev cmake git swig build-essential pkg-config doxygen
sudo apt-get install gnuradio gnuradio-dev gr-osmosdr librtlsdr-dev libuhd-dev libhackrf-dev libitpp-dev libpcap-dev cmake git swig build-essential pkg-config doxygen python-numpy python-waitress python-requests
mkdir build
cd build

View File

@ -83,7 +83,6 @@ set(GRC_BLOCKS_DIR ${GR_PKG_DATA_DIR}/grc/blocks)
########################################################################
# Find gnuradio build dependencies
########################################################################
find_package(GnuradioRuntime)
find_package(CppUnit)
# To run a more advanced search for GNU Radio and it's components and
@ -93,10 +92,8 @@ find_package(CppUnit)
#
# set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER ...)
# find_package(Gnuradio "version")
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
find_package(Gnuradio)
endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
find_package(Gnuradio)
if(NOT GNURADIO_RUNTIME_FOUND)
message(FATAL_ERROR "GnuRadio Runtime required to compile op25")

View File

@ -4,17 +4,17 @@
<key>op25_decoder_ff</key>
<category>op25</category>
<import>import op25</import>
<make>op25.decoder_ff($)</make>
<make>op25.decoder_ff()</make>
<!-- Make one 'param' node for every Parameter you want settable from the GUI.
Sub-nodes:
* name
* key (makes the value accessible as $keyname, e.g. in the make node)
* type -->
<param>
<!--<param>
<name>...</name>
<key>...</key>
<type>...</type>
</param>
</param>-->
<!-- Make one 'sink' node per input. Sub-nodes:
* name (an identifier for the GUI)
@ -23,7 +23,7 @@
* optional (set to 1 for optional inputs) -->
<sink>
<name>in</name>
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
<type>float</type>
</sink>
<!-- Make one 'source' node per output. Sub-nodes:
@ -32,7 +32,7 @@
* vlen
* optional (set to 1 for optional inputs) -->
<source>
<name>out</name>
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
<name>audio</name>
<type>float</type>
</source>
</block>

View File

@ -4,7 +4,7 @@
<key>op25_fsk4_demod_ff</key>
<category>op25</category>
<import>import op25</import>
<make>op25.fsk4_demod_ff(self.auto_tune_msgq, $sample_rate, $symbol_rate)</make>
<make>op25.fsk4_demod_ff($(id)_msgq_out, $sample_rate, $symbol_rate)</make>
<param>
<name>Sample Rate</name>
@ -20,7 +20,8 @@
<type>real</type>
</param>
<param>
<!-- Must connect tune message queue as the block expects a queue as first argument! -->
<!--<param>
<name>Output Auto Tune</name>
<key>tune_out</key>
<value>True</value>
@ -34,7 +35,7 @@
<name>No</name>
<key>False</key>
</option>
</param>
</param>-->
<sink>
<name>in</name>
@ -45,4 +46,10 @@
<name>dibits</name>
<type>float</type>
</source>
<source>
<name>tune</name>
<type>msg</type>
<!--<optional>1</optional>-->
</source>
</block>

View File

@ -51,7 +51,7 @@ namespace gr {
* class. op25::decoder_bf::make is the public interface for
* creating new instances.
*/
static sptr make();
static sptr make(bool idle_silence = true, bool verbose = false);
/**
* Return a pointer to a string identifying the destination of
@ -78,6 +78,17 @@ namespace gr {
* message queue.
*/
virtual void set_msgq(gr::msg_queue::sptr msgq) = 0;
virtual void set_idle_silence(bool idle_silence = true) = 0;
virtual void set_logging(bool verbose = true) = 0;
typedef std::vector<uint8_t> key_type;
typedef std::map<uint16_t, key_type> key_map_type;
virtual void set_key(const key_type& key) = 0;
virtual void set_key_map(const key_map_type& keys) = 0;
};
} // namespace op25

View File

@ -53,6 +53,13 @@ list(APPEND op25_sources
value_string.cc
pickle.cc
pcap_source_b_impl.cc
bch.cc
ldu.cc
crypto.cc
crypto_module_du_handler.cc
deskey.c
desport.c
dessp.c
)
add_library(gnuradio-op25 SHARED ${op25_sources})

View File

@ -55,10 +55,10 @@ abstract_data_unit::correct_errors()
}
void
abstract_data_unit::decode_audio(imbe_decoder& imbe)
abstract_data_unit::decode_audio(imbe_decoder& imbe, crypto_module::sptr crypto_mod)
{
if(is_complete()) {
do_decode_audio(d_frame_body, imbe);
do_decode_audio(d_frame_body, imbe, crypto_mod);
} else {
ostringstream msg;
msg << "cannot decode audio - frame is not complete" << endl;
@ -153,7 +153,8 @@ abstract_data_unit::dump(ostream& os) const
}
abstract_data_unit::abstract_data_unit(const_bit_queue& frame_body) :
d_frame_body(frame_body.size())
d_frame_body(frame_body.size()),
d_logging_enabled(false)
{
copy(frame_body.begin(), frame_body.end(), d_frame_body.begin());
}
@ -164,7 +165,7 @@ abstract_data_unit::do_correct_errors(bit_vector& frame_body)
}
void
abstract_data_unit::do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe)
abstract_data_unit::do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe, crypto_module::sptr crypto_mod)
{
}
@ -179,3 +180,15 @@ abstract_data_unit::frame_size() const
{
return d_frame_body.size();
}
void
abstract_data_unit::set_logging(bool on)
{
d_logging_enabled = on;
}
bool
abstract_data_unit::logging_enabled() const
{
return d_logging_enabled;
}

View File

@ -26,6 +26,7 @@
#include "data_unit.h"
#include "op25_yank.h"
#include "crypto.h"
#include <string>
#include <vector>
@ -62,7 +63,7 @@ public:
* \precondition is_complete() == true.
* \param imbe The imbe_decoder to use to generate the audio.
*/
virtual void decode_audio(imbe_decoder& imbe);
virtual void decode_audio(imbe_decoder& imbe, crypto_module::sptr crypto_mod);
/**
* Decode the frame into an octet vector.
@ -117,6 +118,15 @@ public:
*/
virtual std::string snapshot() const;
/**
* Returns a string describing the Data Unit ID (DUID).
*
* \return A string identifying the DUID.
*/
virtual std::string duid_str() const = 0;
virtual void set_logging(bool on);
protected:
/**
@ -140,7 +150,7 @@ protected:
* \param frame_body The const_bit_vector to decode.
* \param imbe The imbe_decoder to use.
*/
virtual void do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe);
virtual void do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe, crypto_module::sptr crypto_mod);
/**
* Decode frame_body and write the decoded frame contents to msg.
@ -152,13 +162,6 @@ protected:
*/
virtual size_t decode_frame(const_bit_vector& frame_body, size_t msg_sz, uint8_t *msg);
/**
* Returns a string describing the Data Unit ID (DUID).
*
* \return A string identifying the DUID.
*/
virtual std::string duid_str() const = 0;
/**
* Return a reference to the frame body.
*/
@ -180,6 +183,8 @@ protected:
*/
virtual uint16_t frame_size() const;
virtual bool logging_enabled() const;
private:
/**
@ -187,6 +192,7 @@ private:
*/
bit_vector d_frame_body;
bool d_logging_enabled;
};
#endif /* INCLUDED_ABSTRACT_DATA_UNIT_H */

162
op25/gr-op25/lib/bch.cc Normal file
View File

@ -0,0 +1,162 @@
#include <stdio.h>
#include <vector>
#include "bch.h"
/*
* Copyright 2010, KA1RBI
*/
static const int bchGFexp[64] = {
1, 2, 4, 8, 16, 32, 3, 6, 12, 24, 48, 35, 5, 10, 20, 40,
19, 38, 15, 30, 60, 59, 53, 41, 17, 34, 7, 14, 28, 56, 51, 37,
9, 18, 36, 11, 22, 44, 27, 54, 47, 29, 58, 55, 45, 25, 50, 39,
13, 26, 52, 43, 21, 42, 23, 46, 31, 62, 63, 61, 57, 49, 33, 0
};
static const int bchGFlog[64] = {
-1, 0, 1, 6, 2, 12, 7, 26, 3, 32, 13, 35, 8, 48, 27, 18,
4, 24, 33, 16, 14, 52, 36, 54, 9, 45, 49, 38, 28, 41, 19, 56,
5, 62, 25, 11, 34, 31, 17, 47, 15, 23, 53, 51, 37, 44, 55, 40,
10, 61, 46, 30, 50, 22, 39, 43, 29, 60, 42, 21, 20, 59, 57, 58
};
static const int bchG[48] = {
1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0,
1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0,
1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1
};
int bchDec(bit_vector& Codeword)
{
int elp[24][ 22], S[23];
int D[23], L[24], uLu[24];
int root[11], locn[11], reg[12];
int i,j,U,q,count;
int SynError, CantDecode;
SynError = 0; CantDecode = 0;
for(i = 1; i <= 22; i++) {
S[i] = 0;
// FOR j = 0 TO 62
for(j = 0; j <= 62; j++) {
if( Codeword[j]) { S[i] = S[i] ^ bchGFexp[(i * j) % 63]; }
}
if( S[i]) { SynError = 1; }
S[i] = bchGFlog[S[i]];
// printf("S[%d] %d\n", i, S[i]);
}
if( SynError) { //if there are errors, try to correct them
L[0] = 0; uLu[0] = -1; D[0] = 0; elp[0][ 0] = 0;
L[1] = 0; uLu[1] = 0; D[1] = S[1]; elp[1][ 0] = 1;
//FOR i = 1 TO 21
for(i = 1; i <= 21; i++) {
elp[0][ i] = -1; elp[1][ i] = 0;
}
U = 0;
do {
U = U + 1;
if( D[U] == -1) {
L[U + 1] = L[U];
// FOR i = 0 TO L[U]
for(i = 0; i <= L[U]; i++) {
elp[U + 1][ i] = elp[U][ i]; elp[U][ i] = bchGFlog[elp[U][ i]];
}
} else {
//search for words with greatest uLu(q) for which d(q)!=0
q = U - 1;
while((D[q] == -1) &&(q > 0)) { q = q - 1; }
//have found first non-zero d(q)
if( q > 0) {
j = q;
do { j = j - 1; if((D[j] != -1) &&(uLu[q] < uLu[j])) { q = j; }
} while( j > 0) ;
}
//store degree of new elp polynomial
if( L[U] > L[q] + U - q) {
L[U + 1] = L[U] ;
} else {
L[U + 1] = L[q] + U - q;
}
///* form new elp(x) */
// FOR i = 0 TO 21
for(i = 0; i <= 21; i++) {
elp[U + 1][ i] = 0;
}
// FOR i = 0 TO L(q)
for(i = 0; i <= L[q]; i++) {
if( elp[q][ i] != -1) {
elp[U + 1][ i + U - q] = bchGFexp[(D[U] + 63 - D[q] + elp[q][ i]) % 63];
}
}
// FOR i = 0 TO L(U)
for(i = 0; i <= L[U]; i++) {
elp[U + 1][ i] = elp[U + 1][ i] ^ elp[U][ i];
elp[U][ i] = bchGFlog[elp[U][ i]];
}
}
uLu[U + 1] = U - L[U + 1];
//form(u+1)th discrepancy
if( U < 22) {
//no discrepancy computed on last iteration
if( S[U + 1] != -1) { D[U + 1] = bchGFexp[S[U + 1]]; } else { D[U + 1] = 0; }
// FOR i = 1 TO L(U + 1)
for(i = 1; i <= L[U + 1]; i++) {
if((S[U + 1 - i] != -1) &&(elp[U + 1][ i] != 0)) {
D[U + 1] = D[U + 1] ^ bchGFexp[(S[U + 1 - i] + bchGFlog[elp[U + 1][ i]]) % 63];
}
}
//put d(u+1) into index form */
D[U + 1] = bchGFlog[D[U + 1]];
}
} while((U < 22) &&(L[U + 1] <= 11));
U = U + 1;
if( L[U] <= 11) { // /* Can correct errors */
//put elp into index form
// FOR i = 0 TO L[U]
for(i = 0; i <= L[U]; i++) {
elp[U][ i] = bchGFlog[elp[U][ i]];
}
//Chien search: find roots of the error location polynomial
// FOR i = 1 TO L(U)
for(i = 1; i <= L[U]; i++) {
reg[i] = elp[U][ i];
}
count = 0;
// FOR i = 1 TO 63
for(i = 1; i <= 63; i++) {
q = 1;
//FOR j = 1 TO L(U)
for(j = 1; j <= L[U]; j++) {
if( reg[j] != -1) {
reg[j] =(reg[j] + j) % 63; q = q ^ bchGFexp[reg[j]];
}
}
if( q == 0) { //store root and error location number indices
root[count] = i; locn[count] = 63 - i; count = count + 1;
}
}
if( count == L[U]) {
//no. roots = degree of elp hence <= t errors
//FOR i = 0 TO L[U] - 1
for(i = 0; i <= L[U]-1; i++) {
Codeword[locn[i]] = Codeword[locn[i]] ^ 1;
}
CantDecode = count;
} else { //elp has degree >t hence cannot solve
CantDecode = -1;
}
} else {
CantDecode = -2;
}
}
return CantDecode;
}

4
op25/gr-op25/lib/bch.h Normal file
View File

@ -0,0 +1,4 @@
#include <vector>
typedef std::vector<bool> bit_vector;
int bchDec(bit_vector& Codeword);

286
op25/gr-op25/lib/crypto.cc Normal file
View File

@ -0,0 +1,286 @@
#include "crypto.h"
#include <sstream>
#include <boost/format.hpp>
#include <iostream>
#include <stdio.h>
#include <memory.h>
extern "C" {
#include "des.h"
}
static unsigned long long swap_bytes(uint64_t l)
{
unsigned long long r;
unsigned char* pL = (unsigned char*)&l;
unsigned char* pR = (unsigned char*)&r;
for (int i = 0; i < sizeof(l); ++i)
pR[i] = pL[(sizeof(l) - 1) - i];
return r;
}
///////////////////////////////////////////////////////////////////////////////
/*
class null_algorithm : public crypto_algorithm // This is an algorithm skeleton (can be used for no encryption as pass-through)
{
private:
size_t m_generated_bits;
public:
null_algorithm()
: m_generated_bits(0)
{
}
const type_id id() const
{
return crypto_algorithm::NONE;
}
bool update(const struct CryptoState& state)
{
fprintf(stderr, "NULL:\t%d bits generated\n", m_generated_bits);
m_generated_bits = 0;
return true;
}
bool set_key(const crypto_algorithm::key_type& key)
{
return true;
}
uint64_t generate(size_t n)
{
m_generated_bits += n;
return 0;
}
};
*/
///////////////////////////////////////////////////////////////////////////////
class des_ofb : public crypto_algorithm
{
public:
unsigned long long m_key_des, m_next_iv, m_ks;
int m_ks_idx;
DES_KS m_ksDES;
int m_iterations;
uint16_t m_current_kid;
key_type m_default_key;
key_map_type m_key_map;
bool m_verbose;
public:
des_ofb()
: m_current_kid(-1)
{
memset(&m_ksDES, 0, sizeof(m_ksDES));
m_key_des = 0;
m_next_iv = 0;
m_ks_idx = 0;
m_ks = 0;
m_iterations = 0;
}
void set_logging(bool on)
{
m_verbose = on;
}
const type_id id() const
{
return crypto_algorithm::DES_OFB;
}
bool update(const struct CryptoState& state)
{
if (m_current_kid != state.kid)
{
if (m_key_map.empty())
{
// Nothing to do
}
else
{
key_map_type::iterator it = m_key_map.find(state.kid);
if (it != m_key_map.end())
{
set_key(it->second);
}
else if (!m_default_key.empty())
{
/*if (m_verbose) */fprintf(stderr, "Key 0x%04x not found in key map - using default key\n", state.kid);
set_key(m_default_key);
}
else
{
/*if (m_verbose) */fprintf(stderr, "Key 0x%04x not found in key map and no default key\n", state.kid);
}
}
m_current_kid = state.kid;
}
uint64_t iv = 0;
size_t n = std::min(sizeof(iv), state.mi.size());
memcpy(&iv, &state.mi[0], n);
set_iv(iv);
return (n == 8);
}
void set_key_map(const key_map_type& key_map)
{
m_key_map = key_map;
m_current_kid = -1; // To refresh on next update if it has changed
}
bool set_key(const crypto_algorithm::key_type& key)
{
const size_t valid_key_length = 8;
if (key.size() != valid_key_length)
{
if (m_verbose) fprintf(stderr, "DES:\tIncorrect key length of %lu (should be %lu)\n", key.size(), valid_key_length);
return false;
}
m_default_key = key;
memcpy(&m_key_des, &key[0], std::min(key.size(), sizeof(m_key_des)));
if (m_verbose)
{
std::stringstream ss;
for (int i = 0; i < valid_key_length; ++i)
ss << boost::format("%02X") % (int)key[i];
std::cerr << "DES:\tKey: " << ss.str() << std::endl;
}
deskey(m_ksDES, (unsigned char*)&m_key_des, 0); // 0: encrypt (for OFB mode)
return true;
}
void set_iv(uint64_t iv)
{
if (m_iterations > 0)
{
if (m_verbose) fprintf(stderr, "DES:\t%i bits used from %i iterations\n", m_ks_idx, m_iterations);
}
m_next_iv = iv;
m_ks_idx = 0;
m_iterations = 0;
m_ks = m_next_iv;
des(m_ksDES, (unsigned char*)&m_ks); // First initialisation
++m_iterations;
des(m_ksDES, (unsigned char*)&m_ks); // Throw out first iteration & prepare for second
++m_iterations;
generate(64); // Reserved 3 + first 5 of LC (3 left)
generate(3 * 8); // Use remaining 3 bytes for LC
}
uint64_t generate(size_t count) // 1..64
{
unsigned long long ullCurrent = swap_bytes(m_ks);
const int max_len = 64;
int pos = m_ks_idx % max_len;
m_ks_idx += count;
if ((pos + count) <= max_len) // Up to 64
{
if ((m_ks_idx % max_len) == 0)
{
des(m_ksDES, (unsigned char*)&m_ks); // Prepare for next iteration
++m_iterations;
}
unsigned long long result = (ullCurrent >> (((max_len - 1) - pos) - (count-1))) & ((count == max_len) ? (unsigned long long)-1 : ((1ULL << count) - 1));
return result;
}
// Over-flow 64-bit boundary (so all of rest of current will be used)
des(m_ksDES, (unsigned char*)&m_ks); // Compute second part
++m_iterations;
unsigned long long first = ullCurrent << pos; // RHS will be zeros
ullCurrent = swap_bytes(m_ks);
int remainder = count - (max_len - pos);
first >>= (((max_len - 1) - remainder) - ((max_len - 1) - pos));
unsigned long long next = (ullCurrent >> (((max_len - 1) - 0) - (remainder-1))) & ((1ULL << remainder) - 1);
return (first | next);
}
};
///////////////////////////////////////////////////////////////////////////////
crypto_module::crypto_module(bool verbose/* = true*/)
: d_verbose(verbose)
{
}
crypto_algorithm::sptr crypto_module::algorithm(crypto_algorithm::type_id algid)
{
if ((!d_current_algorithm && (algid == crypto_algorithm::NONE)) || // This line should be commented out if 'null_algorithm' is to be tested
(d_current_algorithm && (algid == d_current_algorithm->id())))
return d_current_algorithm;
switch (algid)
{
case crypto_algorithm::DES_OFB:
d_current_algorithm = crypto_algorithm::sptr(new des_ofb());
break;
//case crypto_algorithm::NONE:
// d_current_algorithm = crypto_algorithm::sptr(new null_algorithm());
// break;
default:
d_current_algorithm = crypto_algorithm::sptr();
};
if (d_current_algorithm)
{
d_current_algorithm->set_logging(logging_enabled());
if (!d_persistent_key_map.empty())
d_current_algorithm->set_key_map(d_persistent_key_map);
if (!d_persistent_key.empty())
d_current_algorithm->set_key(d_persistent_key);
}
return d_current_algorithm;
}
void crypto_module::set_key(const crypto_algorithm::key_type& key)
{
d_persistent_key = key;
if (d_current_algorithm)
d_current_algorithm->set_key(d_persistent_key);
}
void crypto_module::set_key_map(const crypto_algorithm::key_map_type& keys)
{
d_persistent_key_map = keys;
if (d_current_algorithm)
d_current_algorithm->set_key_map(d_persistent_key_map);
}
void crypto_module::set_logging(bool on/* = true*/)
{
d_verbose = on;
if (d_current_algorithm)
d_current_algorithm->set_logging(on);
}

73
op25/gr-op25/lib/crypto.h Normal file
View File

@ -0,0 +1,73 @@
#ifndef INCLUDED_CRYPTO_H
#define INCLUDED_CRYPTO_H
#include <stdint.h>
#include <vector>
#include <map>
#include <boost/shared_ptr.hpp>
static const int MESSAGE_INDICATOR_LENGTH = 9;
class CryptoState
{
public:
CryptoState() :
kid(0), algid(0), mi(MESSAGE_INDICATOR_LENGTH)
{ }
public:
std::vector<uint8_t> mi;
uint16_t kid;
uint8_t algid;
};
class crypto_state_provider
{
public:
virtual struct CryptoState crypto_state() const=0;
};
class crypto_algorithm
{
public:
typedef boost::shared_ptr<class crypto_algorithm> sptr;
typedef std::vector<uint8_t> key_type;
typedef std::map<uint16_t, key_type > key_map_type;
typedef uint8_t type_id;
enum
{
NONE = 0x80,
DES_OFB = 0x81,
};
public:
virtual const type_id id() const=0;
virtual bool set_key(const key_type& key)=0;
virtual void set_key_map(const key_map_type& key_map)=0;
virtual bool update(const struct CryptoState& state)=0;
virtual uint64_t generate(size_t n_bits)=0; // Can request up to 64 bits of key stream at one time
virtual void set_logging(bool on)=0;
};
class crypto_module
{
public:
typedef boost::shared_ptr<class crypto_module> sptr;
public:
crypto_module(bool verbose = false);
public:
virtual crypto_algorithm::sptr algorithm(crypto_algorithm::type_id algid);
virtual void set_key(const crypto_algorithm::key_type& key);
virtual void set_key_map(const crypto_algorithm::key_map_type& keys);
virtual void set_logging(bool on = true);
protected:
crypto_algorithm::sptr d_current_algorithm;
crypto_algorithm::key_type d_persistent_key;
crypto_algorithm::key_map_type d_persistent_key_map;
bool d_verbose;
public:
virtual crypto_algorithm::sptr current_algorithm() const
{ return d_current_algorithm; }
virtual bool logging_enabled() const
{ return d_verbose; }
};
#endif // INCLUDED_CRYPTO_H

View File

@ -0,0 +1,64 @@
#include "crypto_module_du_handler.h"
#include "abstract_data_unit.h"
#include <boost/format.hpp>
#include <sstream>
#include <stdio.h>
crypto_module_du_handler::crypto_module_du_handler(data_unit_handler_sptr next, crypto_module::sptr crypto_mod)
: data_unit_handler(next)
, d_crypto_mod(crypto_mod)
{
}
void
crypto_module_du_handler::handle(data_unit_sptr du)
{
if (!d_crypto_mod)
{
data_unit_handler::handle(du);
return;
}
crypto_state_provider* p = dynamic_cast<crypto_state_provider*>(du.get());
if (p == NULL)
{
data_unit_handler::handle(du);
return;
}
CryptoState state = p->crypto_state();
///////////////////////////////////
if (d_crypto_mod->logging_enabled())
{
std::string duid_str("?");
abstract_data_unit* adu = dynamic_cast<abstract_data_unit*>(du.get());
if (adu)
duid_str = adu->duid_str();
std::stringstream ss;
for (size_t n = 0; n < state.mi.size(); ++n)
ss << (boost::format("%02x") % (int)state.mi[n]);
fprintf(stderr, "%s:\tAlgID: 0x%02x, KID: 0x%04x, MI: %s\n", duid_str.c_str(), state.algid, state.kid, ss.str().c_str());
}
///////////////////////////////////
crypto_algorithm::sptr algorithm = d_crypto_mod->algorithm(state.algid);
if (!algorithm)
{
data_unit_handler::handle(du);
return;
}
// TODO: Could do key management & selection here with 'state.kid'
// Assuming we're only using one key (ignoring 'kid')
algorithm->update(state);
data_unit_handler::handle(du);
}

View File

@ -0,0 +1,21 @@
#ifndef INCLUDED_CRYPTO_MODULE_DU_HANDLER_H
#define INCLUDED_CRYPTO_MODULE_DU_HANDLER_H
#include <boost/shared_ptr.hpp>
#include "data_unit_handler.h"
#include "crypto.h"
class crypto_module_du_handler : public data_unit_handler
{
public:
crypto_module_du_handler(data_unit_handler_sptr next, crypto_module::sptr crypto_mod);
public:
typedef boost::shared_ptr<class crypto_module_du_handler> sptr;
public:
virtual void handle(data_unit_sptr du);
private:
crypto_module::sptr d_crypto_mod;
};
#endif //INCLUDED_CRYPTO_MODULE_HANDLER_H

View File

@ -32,6 +32,8 @@
#include <iosfwd>
#include <stdint.h>
#include "crypto.h"
typedef std::deque<bool> bit_queue;
typedef const std::deque<bool> const_bit_queue;
@ -76,7 +78,7 @@ public:
* \precondition is_complete() == true.
* \param imbe The imbe_decoder to use to generate the audio.
*/
virtual void decode_audio(imbe_decoder& imbe) = 0;
virtual void decode_audio(imbe_decoder& imbe, crypto_module::sptr crypto_mod) = 0;
/**
* Decode the frame into an octet vector.
@ -132,6 +134,8 @@ public:
*/
virtual std::string snapshot() const = 0;
virtual void set_logging(bool on) = 0;
protected:
/**

View File

@ -34,6 +34,7 @@
#include "offline_imbe_decoder.h"
#include "voice_du_handler.h"
#include "op25_yank.h"
#include "bch.h"
using namespace std;
@ -41,13 +42,13 @@ namespace gr {
namespace op25 {
decoder_bf::sptr
decoder_bf::make()
decoder_bf::make(bool idle_silence /*= true*/, bool verbose /*= false*/)
{
return gnuradio::get_initial_sptr
(new decoder_bf_impl());
(new decoder_bf_impl(idle_silence, verbose));
}
decoder_bf_impl::decoder_bf_impl() :
decoder_bf_impl::decoder_bf_impl(bool idle_silence /*= true*/, bool verbose /*= false*/) :
gr::block("decoder_bf",
gr::io_signature::make(1, 1, sizeof(uint8_t)),
gr::io_signature::make(0, 1, sizeof(float))),
@ -56,14 +57,25 @@ namespace gr {
d_frame_hdr(),
d_imbe(imbe_decoder::make()),
d_state(SYNCHRONIZING),
d_p25cai_du_handler(NULL)
d_p25cai_du_handler(NULL),
d_idle_silence(idle_silence),
d_verbose(false)
{
set_logging(verbose);
d_p25cai_du_handler = new p25cai_du_handler(d_data_unit_handler,
"224.0.0.1", 23456);
d_data_unit_handler = data_unit_handler_sptr(d_p25cai_du_handler);
d_snapshot_du_handler = new snapshot_du_handler(d_data_unit_handler);
d_data_unit_handler = data_unit_handler_sptr(d_snapshot_du_handler);
d_data_unit_handler = data_unit_handler_sptr(new voice_du_handler(d_data_unit_handler, d_imbe));
d_crypto_module = crypto_module::sptr(new crypto_module(verbose));
d_crypto_module_du_handler = crypto_module_du_handler::sptr(new crypto_module_du_handler(d_data_unit_handler, d_crypto_module));
d_data_unit_handler = data_unit_handler_sptr(d_crypto_module_du_handler);
d_data_unit_handler = data_unit_handler_sptr(new voice_du_handler(d_data_unit_handler, d_imbe, d_crypto_module));
}
decoder_bf_impl::~decoder_bf_impl()
@ -104,34 +116,35 @@ namespace gr {
gr_vector_void_star &output_items)
{
try {
gr::thread::scoped_lock lock(d_mutex);
// process input
const uint8_t *in = reinterpret_cast<const uint8_t*>(input_items[0]);
for(int i = 0; i < ninput_items[0]; ++i) {
dibit d = in[i] & 0x3;
receive_symbol(d);
}
consume_each(ninput_items[0]);
// process input
const uint8_t *in = reinterpret_cast<const uint8_t*>(input_items[0]);
for(int i = 0; i < ninput_items[0]; ++i) {
dibit d = in[i] & 0x3;
receive_symbol(d);
}
consume_each(ninput_items[0]);
// produce audio
audio_samples *samples = d_imbe->audio();
float *out = reinterpret_cast<float*>(output_items[0]);
const int n = min(static_cast<int>(samples->size()), noutput_items);
if(0 < n) {
copy(samples->begin(), samples->begin() + n, out);
samples->erase(samples->begin(), samples->begin() + n);
}
if(n < noutput_items) {
fill(out + n, out + noutput_items, 0.0);
}
return noutput_items;
// produce audio
audio_samples *samples = d_imbe->audio();
float *out = reinterpret_cast<float*>(output_items[0]);
const int n = min(static_cast<int>(samples->size()), noutput_items);
if(0 < n) {
copy(samples->begin(), samples->begin() + n, out);
samples->erase(samples->begin(), samples->begin() + n);
}
if((d_idle_silence) && (n < noutput_items)) {
fill(out + n, out + noutput_items, 0.0);
}
return (d_idle_silence ? noutput_items : n);
} catch(const std::exception& x) {
cerr << x.what() << endl;
exit(1);
cerr << x.what() << endl;
exit(1);
} catch(...) {
cerr << "unhandled exception" << endl;
exit(2); }
cerr << "unhandled exception" << endl;
exit(2); }
}
const char*
@ -177,14 +190,12 @@ namespace gr {
};
size_t NID_SZ = sizeof(NID) / sizeof(NID[0]);
itpp::bvec b(63), zeroes(16);
itpp::BCH bch(63, 16, 11, "6 3 3 1 1 4 1 3 6 7 2 3 5 4 5 3", true);
bit_vector b(NID_SZ);
yank(d_frame_hdr, NID, NID_SZ, b, 0);
b = bch.decode(b);
if(b != zeroes) {
b = bch.encode(b);
if(bchDec(b) >= 0) {
yank_back(b, 0, d_frame_hdr, NID, NID_SZ);
d_data_unit = data_unit::make_data_unit(d_frame_hdr);
d_data_unit->set_logging(d_verbose);
} else {
data_unit_sptr null;
d_data_unit = null;
@ -229,5 +240,36 @@ namespace gr {
break;
}
}
void
decoder_bf_impl::set_idle_silence(bool idle_silence/* = true*/)
{
gr::thread::scoped_lock lock(d_mutex);
d_idle_silence = idle_silence;
}
void
decoder_bf_impl::set_logging(bool verbose/* = true*/)
{
if (verbose) fprintf(stderr, "[%s<%lu>] verbose logging enabled\n", name().c_str(), unique_id());
d_verbose = verbose;
if (d_crypto_module)
d_crypto_module->set_logging(verbose);
}
void
decoder_bf_impl::set_key(const key_type& key)
{
d_crypto_module->set_key(key);
}
void
decoder_bf_impl::set_key_map(const key_map_type& keys)
{
d_crypto_module->set_key_map(keys);
}
} /* namespace op25 */
} /* namespace gr */

View File

@ -24,11 +24,14 @@
#define INCLUDED_OP25_DECODER_BF_IMPL_H
#include <op25/decoder_bf.h>
#include <gnuradio/thread/thread.h>
#include "data_unit.h"
#include "data_unit_handler.h"
#include "imbe_decoder.h"
#include "p25cai_du_handler.h"
#include "snapshot_du_handler.h"
#include "crypto.h"
#include "crypto_module_du_handler.h"
namespace gr {
namespace op25 {
@ -102,8 +105,21 @@ namespace gr {
*/
class snapshot_du_handler *d_snapshot_du_handler;
/*
* Whether or not to output silence when no audio is synthesised.
*/
bool d_idle_silence;
bool d_verbose;
crypto_module::sptr d_crypto_module;
crypto_module_du_handler::sptr d_crypto_module_du_handler;
gr::thread::mutex d_mutex;
public:
decoder_bf_impl();
decoder_bf_impl(bool idle_silence = true, bool verbose = false);
~decoder_bf_impl();
// Where all the action really happens
@ -139,6 +155,14 @@ namespace gr {
* message queue.
*/
void set_msgq(gr::msg_queue::sptr msgq);
void set_idle_silence(bool idle_silence = true);
void set_logging(bool verbose = true);
void set_key(const key_type& key);
void set_key_map(const key_map_type& keys);
};
} // namespace op25
} // namespace gr

View File

@ -34,6 +34,7 @@
#include "offline_imbe_decoder.h"
#include "voice_du_handler.h"
#include "op25_yank.h"
#include "bch.h"
using namespace std;
@ -185,12 +186,9 @@ namespace gr {
};
size_t NID_SZ = sizeof(NID) / sizeof(NID[0]);
itpp::bvec b(63), zeroes(16);
itpp::BCH bch(63, 16, 11, "6 3 3 1 1 4 1 3 6 7 2 3 5 4 5 3", true);
bit_vector b(NID_SZ);
yank(d_frame_hdr, NID, NID_SZ, b, 0);
b = bch.decode(b);
if(b != zeroes) {
b = bch.encode(b);
if(bchDec(b) >= 0) {
yank_back(b, 0, d_frame_hdr, NID, NID_SZ);
d_data_unit = data_unit::make_data_unit(d_frame_hdr);
} else {

15
op25/gr-op25/lib/des.h Normal file
View File

@ -0,0 +1,15 @@
typedef unsigned long DES_KS[16][2]; /* Single-key DES key schedule */
typedef unsigned long DES3_KS[48][2]; /* Triple-DES key schedule */
/* In deskey.c: */
void deskey(DES_KS,unsigned char *,int);
void des3key(DES3_KS,unsigned char *,int);
/* In desport.c, desborl.cas or desgnu.s: */
void des(DES_KS,unsigned char *);
/* In des3port.c, des3borl.cas or des3gnu.s: */
void des3(DES3_KS,unsigned char *);
extern int Asmversion; /* 1 if we're linked with an asm version, 0 if C */

124
op25/gr-op25/lib/deskey.c Normal file
View File

@ -0,0 +1,124 @@
/* Portable C code to create DES key schedules from user-provided keys
* This doesn't have to be fast unless you're cracking keys or UNIX
* passwords
*/
#include <string.h>
#include "des.h"
/* Key schedule-related tables from FIPS-46 */
/* permuted choice table (key) */
static unsigned char pc1[] = {
57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4
};
/* number left rotations of pc1 */
static unsigned char totrot[] = {
1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28
};
/* permuted choice key (table) */
static unsigned char pc2[] = {
14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32
};
/* End of DES-defined tables */
/* bit 0 is left-most in byte */
static int bytebit[] = {
0200,0100,040,020,010,04,02,01
};
/* Generate key schedule for encryption or decryption
* depending on the value of "decrypt"
*/
void
deskey(DES_KS k,unsigned char *key,int decrypt)
/* Key schedule array */
/* 64 bits (will use only 56) */
/* 0 = encrypt, 1 = decrypt */
{
unsigned char pc1m[56]; /* place to modify pc1 into */
unsigned char pcr[56]; /* place to rotate pc1 into */
register int i,j,l;
int m;
unsigned char ks[8];
for (j=0; j<56; j++) { /* convert pc1 to bits of key */
l=pc1[j]-1; /* integer bit location */
m = l & 07; /* find bit */
pc1m[j]=(key[l>>3] & /* find which key byte l is in */
bytebit[m]) /* and which bit of that byte */
? 1 : 0; /* and store 1-bit result */
}
for (i=0; i<16; i++) { /* key chunk for each iteration */
memset(ks,0,sizeof(ks)); /* Clear key schedule */
for (j=0; j<56; j++) /* rotate pc1 the right amount */
pcr[j] = pc1m[(l=j+totrot[decrypt? 15-i : i])<(j<28? 28 : 56) ? l: l-28];
/* rotate left and right halves independently */
for (j=0; j<48; j++){ /* select bits individually */
/* check bit that goes to ks[j] */
if (pcr[pc2[j]-1]){
/* mask it in if it's there */
l= j % 6;
ks[j/6] |= bytebit[l] >> 2;
}
}
/* Now convert to packed odd/even interleaved form */
k[i][0] = ((long)ks[0] << 24)
| ((long)ks[2] << 16)
| ((long)ks[4] << 8)
| ((long)ks[6]);
k[i][1] = ((long)ks[1] << 24)
| ((long)ks[3] << 16)
| ((long)ks[5] << 8)
| ((long)ks[7]);
if(Asmversion){
/* The assembler versions pre-shift each subkey 2 bits
* so the Spbox indexes are already computed
*/
k[i][0] <<= 2;
k[i][1] <<= 2;
}
}
}
/* Generate key schedule for triple DES in E-D-E (or D-E-D) mode.
*
* The key argument is taken to be 24 bytes. The first 8 bytes are K1
* for the first stage, the second 8 bytes are K2 for the middle stage
* and the third 8 bytes are K3 for the last stage
*/
void
des3key(DES3_KS k,unsigned char *key,int decrypt)
/* 192 bits (will use only 168) */
/* 0 = encrypt, 1 = decrypt */
{
if(!decrypt){
deskey(&k[0],&key[0],0);
deskey(&k[16],&key[8],1);
deskey(&k[32],&key[16],0);
} else {
deskey(&k[32],&key[0],1);
deskey(&k[16],&key[8],0);
deskey(&k[0],&key[16],1);
}
}

236
op25/gr-op25/lib/desport.c Normal file
View File

@ -0,0 +1,236 @@
/* Portable C version of des() function */
#include <stdint.h>
#include "des.h"
/* Tables defined in the Data Encryption Standard documents
* Three of these tables, the initial permutation, the final
* permutation and the expansion operator, are regular enough that
* for speed, we hard-code them. They're here for reference only.
* Also, the S and P boxes are used by a separate program, gensp.c,
* to build the combined SP box, Spbox[]. They're also here just
* for reference.
*/
#ifdef notdef
/* initial permutation IP */
static unsigned char ip[] = {
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7
};
/* final permutation IP^-1 */
static unsigned char fp[] = {
40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25
};
/* expansion operation matrix */
static unsigned char ei[] = {
32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1
};
/* The (in)famous S-boxes */
static unsigned char sbox[8][64] = {
/* S1 */
14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13,
/* S2 */
15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9,
/* S3 */
10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12,
/* S4 */
7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14,
/* S5 */
2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3,
/* S6 */
12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13,
/* S7 */
4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12,
/* S8 */
13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
};
/* 32-bit permutation function P used on the output of the S-boxes */
static unsigned char p32i[] = {
16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25
};
#endif
int Asmversion = 0;
/* Combined SP lookup table, linked in
* For best results, ensure that this is aligned on a 32-bit boundary;
* Borland C++ 3.1 doesn't guarantee this!
*/
extern uint64_t Spbox[8][64]; /* Combined S and P boxes */
/* Primitive function F.
* Input is r, subkey array in keys, output is XORed into l.
* Each round consumes eight 6-bit subkeys, one for
* each of the 8 S-boxes, 2 longs for each round.
* Each long contains four 6-bit subkeys, each taking up a byte.
* The first long contains, from high to low end, the subkeys for
* S-boxes 1, 3, 5 & 7; the second contains the subkeys for S-boxes
* 2, 4, 6 & 8 (using the origin-1 S-box numbering in the standard,
* not the origin-0 numbering used elsewhere in this code)
* See comments elsewhere about the pre-rotated values of r and Spbox.
*/
#define F(l,r,key){\
work = ((r >> 4) | (r << 28)) ^ key[0];\
l ^= Spbox[6][work & 0x3f];\
l ^= Spbox[4][(work >> 8) & 0x3f];\
l ^= Spbox[2][(work >> 16) & 0x3f];\
l ^= Spbox[0][(work >> 24) & 0x3f];\
work = r ^ key[1];\
l ^= Spbox[7][work & 0x3f];\
l ^= Spbox[5][(work >> 8) & 0x3f];\
l ^= Spbox[3][(work >> 16) & 0x3f];\
l ^= Spbox[1][(work >> 24) & 0x3f];\
}
/* Encrypt or decrypt a block of data in ECB mode */
void
des(unsigned long ks[16][2],unsigned char block[8])
/* Key schedule */
/* Data block */
{
unsigned long left,right,work;
/* Read input block and place in left/right in big-endian order */
left = ((unsigned long)block[0] << 24)
| ((unsigned long)block[1] << 16)
| ((unsigned long)block[2] << 8)
| (unsigned long)block[3];
right = ((unsigned long)block[4] << 24)
| ((unsigned long)block[5] << 16)
| ((unsigned long)block[6] << 8)
| (unsigned long)block[7];
/* Hoey's clever initial permutation algorithm, from Outerbridge
* (see Schneier p 478)
*
* The convention here is the same as Outerbridge: rotate each
* register left by 1 bit, i.e., so that "left" contains permuted
* input bits 2, 3, 4, ... 1 and "right" contains 33, 34, 35, ... 32
* (using origin-1 numbering as in the FIPS). This allows us to avoid
* one of the two rotates that would otherwise be required in each of
* the 16 rounds.
*/
work = ((left >> 4) ^ right) & 0x0f0f0f0f;
right ^= work;
left ^= work << 4;
work = ((left >> 16) ^ right) & 0xffff;
right ^= work;
left ^= work << 16;
work = ((right >> 2) ^ left) & 0x33333333;
left ^= work;
right ^= (work << 2);
work = ((right >> 8) ^ left) & 0xff00ff;
left ^= work;
right ^= (work << 8);
right = (right << 1) | (right >> 31);
work = (left ^ right) & 0xaaaaaaaa;
left ^= work;
right ^= work;
left = (left << 1) | (left >> 31);
/* Now do the 16 rounds */
F(left,right,ks[0]);
F(right,left,ks[1]);
F(left,right,ks[2]);
F(right,left,ks[3]);
F(left,right,ks[4]);
F(right,left,ks[5]);
F(left,right,ks[6]);
F(right,left,ks[7]);
F(left,right,ks[8]);
F(right,left,ks[9]);
F(left,right,ks[10]);
F(right,left,ks[11]);
F(left,right,ks[12]);
F(right,left,ks[13]);
F(left,right,ks[14]);
F(right,left,ks[15]);
/* Inverse permutation, also from Hoey via Outerbridge and Schneier */
right = (right << 31) | (right >> 1);
work = (left ^ right) & 0xaaaaaaaa;
left ^= work;
right ^= work;
left = (left >> 1) | (left << 31);
work = ((left >> 8) ^ right) & 0xff00ff;
right ^= work;
left ^= work << 8;
work = ((left >> 2) ^ right) & 0x33333333;
right ^= work;
left ^= work << 2;
work = ((right >> 16) ^ left) & 0xffff;
left ^= work;
right ^= work << 16;
work = ((right >> 4) ^ left) & 0x0f0f0f0f;
left ^= work;
right ^= work << 4;
/* Put the block back into the user's buffer with final swap */
block[0] = right >> 24;
block[1] = right >> 16;
block[2] = right >> 8;
block[3] = right;
block[4] = left >> 24;
block[5] = left >> 16;
block[6] = left >> 8;
block[7] = left;
}

131
op25/gr-op25/lib/dessp.c Normal file
View File

@ -0,0 +1,131 @@
#include <stdint.h>
uint64_t Spbox[8][64] = {
0x01010400,0x00000000,0x00010000,0x01010404,
0x01010004,0x00010404,0x00000004,0x00010000,
0x00000400,0x01010400,0x01010404,0x00000400,
0x01000404,0x01010004,0x01000000,0x00000004,
0x00000404,0x01000400,0x01000400,0x00010400,
0x00010400,0x01010000,0x01010000,0x01000404,
0x00010004,0x01000004,0x01000004,0x00010004,
0x00000000,0x00000404,0x00010404,0x01000000,
0x00010000,0x01010404,0x00000004,0x01010000,
0x01010400,0x01000000,0x01000000,0x00000400,
0x01010004,0x00010000,0x00010400,0x01000004,
0x00000400,0x00000004,0x01000404,0x00010404,
0x01010404,0x00010004,0x01010000,0x01000404,
0x01000004,0x00000404,0x00010404,0x01010400,
0x00000404,0x01000400,0x01000400,0x00000000,
0x00010004,0x00010400,0x00000000,0x01010004,
0x80108020,0x80008000,0x00008000,0x00108020,
0x00100000,0x00000020,0x80100020,0x80008020,
0x80000020,0x80108020,0x80108000,0x80000000,
0x80008000,0x00100000,0x00000020,0x80100020,
0x00108000,0x00100020,0x80008020,0x00000000,
0x80000000,0x00008000,0x00108020,0x80100000,
0x00100020,0x80000020,0x00000000,0x00108000,
0x00008020,0x80108000,0x80100000,0x00008020,
0x00000000,0x00108020,0x80100020,0x00100000,
0x80008020,0x80100000,0x80108000,0x00008000,
0x80100000,0x80008000,0x00000020,0x80108020,
0x00108020,0x00000020,0x00008000,0x80000000,
0x00008020,0x80108000,0x00100000,0x80000020,
0x00100020,0x80008020,0x80000020,0x00100020,
0x00108000,0x00000000,0x80008000,0x00008020,
0x80000000,0x80100020,0x80108020,0x00108000,
0x00000208,0x08020200,0x00000000,0x08020008,
0x08000200,0x00000000,0x00020208,0x08000200,
0x00020008,0x08000008,0x08000008,0x00020000,
0x08020208,0x00020008,0x08020000,0x00000208,
0x08000000,0x00000008,0x08020200,0x00000200,
0x00020200,0x08020000,0x08020008,0x00020208,
0x08000208,0x00020200,0x00020000,0x08000208,
0x00000008,0x08020208,0x00000200,0x08000000,
0x08020200,0x08000000,0x00020008,0x00000208,
0x00020000,0x08020200,0x08000200,0x00000000,
0x00000200,0x00020008,0x08020208,0x08000200,
0x08000008,0x00000200,0x00000000,0x08020008,
0x08000208,0x00020000,0x08000000,0x08020208,
0x00000008,0x00020208,0x00020200,0x08000008,
0x08020000,0x08000208,0x00000208,0x08020000,
0x00020208,0x00000008,0x08020008,0x00020200,
0x100802001,0x100002081,0x100002081,0x00000080,
0x00802080,0x100800081,0x100800001,0x100002001,
0x00000000,0x00802000,0x00802000,0x100802081,
0x100000081,0x00000000,0x00800080,0x100800001,
0x100000001,0x00002000,0x00800000,0x100802001,
0x00000080,0x00800000,0x100002001,0x00002080,
0x100800081,0x100000001,0x00002080,0x00800080,
0x00002000,0x00802080,0x100802081,0x100000081,
0x00800080,0x100800001,0x00802000,0x100802081,
0x100000081,0x00000000,0x00000000,0x00802000,
0x00002080,0x00800080,0x100800081,0x100000001,
0x100802001,0x100002081,0x100002081,0x00000080,
0x100802081,0x100000081,0x100000001,0x00002000,
0x100800001,0x100002001,0x00802080,0x100800081,
0x100002001,0x00002080,0x00800000,0x100802001,
0x00000080,0x00800000,0x00002000,0x00802080,
0x00000100,0x02080100,0x02080000,0x42000100,
0x00080000,0x00000100,0x40000000,0x02080000,
0x40080100,0x00080000,0x02000100,0x40080100,
0x42000100,0x42080000,0x00080100,0x40000000,
0x02000000,0x40080000,0x40080000,0x00000000,
0x40000100,0x42080100,0x42080100,0x02000100,
0x42080000,0x40000100,0x00000000,0x42000000,
0x02080100,0x02000000,0x42000000,0x00080100,
0x00080000,0x42000100,0x00000100,0x02000000,
0x40000000,0x02080000,0x42000100,0x40080100,
0x02000100,0x40000000,0x42080000,0x02080100,
0x40080100,0x00000100,0x02000000,0x42080000,
0x42080100,0x00080100,0x42000000,0x42080100,
0x02080000,0x00000000,0x40080000,0x42000000,
0x00080100,0x02000100,0x40000100,0x00080000,
0x00000000,0x40080000,0x02080100,0x40000100,
0x20000010,0x20400000,0x00004000,0x20404010,
0x20400000,0x00000010,0x20404010,0x00400000,
0x20004000,0x00404010,0x00400000,0x20000010,
0x00400010,0x20004000,0x20000000,0x00004010,
0x00000000,0x00400010,0x20004010,0x00004000,
0x00404000,0x20004010,0x00000010,0x20400010,
0x20400010,0x00000000,0x00404010,0x20404000,
0x00004010,0x00404000,0x20404000,0x20000000,
0x20004000,0x00000010,0x20400010,0x00404000,
0x20404010,0x00400000,0x00004010,0x20000010,
0x00400000,0x20004000,0x20000000,0x00004010,
0x20000010,0x20404010,0x00404000,0x20400000,
0x00404010,0x20404000,0x00000000,0x20400010,
0x00000010,0x00004000,0x20400000,0x00404010,
0x00004000,0x00400010,0x20004010,0x00000000,
0x20404000,0x20000000,0x00400010,0x20004010,
0x00200000,0x04200002,0x04000802,0x00000000,
0x00000800,0x04000802,0x00200802,0x04200800,
0x04200802,0x00200000,0x00000000,0x04000002,
0x00000002,0x04000000,0x04200002,0x00000802,
0x04000800,0x00200802,0x00200002,0x04000800,
0x04000002,0x04200000,0x04200800,0x00200002,
0x04200000,0x00000800,0x00000802,0x04200802,
0x00200800,0x00000002,0x04000000,0x00200800,
0x04000000,0x00200800,0x00200000,0x04000802,
0x04000802,0x04200002,0x04200002,0x00000002,
0x00200002,0x04000000,0x04000800,0x00200000,
0x04200800,0x00000802,0x00200802,0x04200800,
0x00000802,0x04000002,0x04200802,0x04200000,
0x00200800,0x00000000,0x00000002,0x04200802,
0x00000000,0x00200802,0x04200000,0x00000800,
0x04000002,0x04000800,0x00000800,0x00200002,
0x10001040,0x00001000,0x00040000,0x10041040,
0x10000000,0x10001040,0x00000040,0x10000000,
0x00040040,0x10040000,0x10041040,0x00041000,
0x10041000,0x00041040,0x00001000,0x00000040,
0x10040000,0x10000040,0x10001000,0x00001040,
0x00041000,0x00040040,0x10040040,0x10041000,
0x00001040,0x00000000,0x00000000,0x10040040,
0x10000040,0x10001000,0x00041040,0x00040000,
0x00041040,0x00040000,0x10041000,0x00001000,
0x00000040,0x10040040,0x00001000,0x00041040,
0x10001000,0x00000040,0x10000040,0x10040000,
0x10040040,0x10000000,0x00040000,0x10001040,
0x00000000,0x10041040,0x00040040,0x10000040,
0x10040000,0x10001000,0x10001040,0x00000000,
0x10041040,0x00041000,0x00041000,0x00001040,
0x00001040,0x00040040,0x10000000,0x10041000,
};

View File

@ -28,6 +28,8 @@
#include <iomanip>
#include <sstream>
#include <boost/format.hpp>
#include <stdio.h>
using namespace std;
@ -65,6 +67,8 @@ hdu::do_correct_errors(bit_vector& frame)
{
apply_golay_correction(frame);
apply_rs_correction(frame);
if (logging_enabled()) fprintf(stderr, "\n");
}
void
@ -136,33 +140,58 @@ hdu::frame_size_max() const
return 792;
}
string
hdu::algid_str() const
uint8_t
hdu::algid() const
{
const size_t ALGID_BITS[] = {
356, 357, 360, 361, 374, 375, 376, 377
};
const size_t ALGID_BITS_SZ = sizeof(ALGID_BITS) / sizeof(ALGID_BITS[0]);
uint8_t algid = extract(frame_body(), ALGID_BITS, ALGID_BITS_SZ);
return lookup(algid, ALGIDS, ALGIDS_SZ);
return extract(frame_body(), ALGID_BITS, ALGID_BITS_SZ);
}
string
hdu::kid_str() const
hdu::algid_str() const
{
uint8_t _algid = algid();
return lookup(_algid, ALGIDS, ALGIDS_SZ);
}
uint16_t
hdu::kid() const
{
const size_t KID_BITS[] = {
378, 379, 392, 393, 394, 395, 396, 397,
410, 411, 412, 413, 414, 415, 428, 429
};
const size_t KID_BITS_SZ = sizeof(KID_BITS) / sizeof(KID_BITS[0]);
uint16_t kid = extract(frame_body(), KID_BITS, KID_BITS_SZ);
return extract(frame_body(), KID_BITS, KID_BITS_SZ);
}
string
hdu::kid_str() const
{
uint16_t _kid = kid();
ostringstream os;
os << hex << showbase << setfill('0') << setw(4) << kid;
os << hex << showbase << setfill('0') << setw(4) << _kid;
return os.str();
}
std::string
hdu::mi_str() const
{
std::vector<uint8_t> _mi(mi());
ostringstream os;
os << "0x";
for(size_t i = 0; i < _mi.size(); ++i) {
uint16_t octet = _mi[i];
os << hex << setfill('0') << setw(2) << octet;
}
return os.str();
}
std::vector<uint8_t>
hdu::mi() const
{
const size_t MI_BITS[] = {
114, 115, 116, 117, 118, 119, 132, 133,
@ -177,15 +206,9 @@ hdu::mi_str() const
};
const size_t MI_BITS_SZ = sizeof(MI_BITS) / sizeof(MI_BITS[0]);
uint8_t mi[9];
extract(frame_body(), MI_BITS, MI_BITS_SZ, mi);
ostringstream os;
os << "0x";
for(size_t i = 0; i < (sizeof(mi) / sizeof(mi[0])); ++i) {
uint16_t octet = mi[i];
os << hex << setfill('0') << setw(2) << octet;
}
return os.str();
std::vector<uint8_t> _mi(((MI_BITS_SZ + 7) / 8));
extract(frame_body(), MI_BITS, MI_BITS_SZ, &_mi[0]);
return _mi;
}
string
@ -219,7 +242,21 @@ hdu::tgid_str() const
};
const size_t TGID_BITS_SZ = sizeof(TGID_BITS) / sizeof(TGID_BITS[0]);
const uint16_t tgid = extract(frame_body(), TGID_BITS, TGID_BITS_SZ);
ostringstream os;
os << hex << showbase << setfill('0') << setw(4) << tgid;
return os.str();
// Zero fill isn't working properly in original implementation
//ostringstream os;
//os << hex << showbase << setfill('0') << setw(4) << tgid;
//return os.str();
return (boost::format("0x%04x") % tgid).str();
}
struct CryptoState
hdu::crypto_state() const
{
struct CryptoState state;
state.mi = mi();
state.kid = kid();
state.algid = algid();
return state;
}

View File

@ -25,11 +25,12 @@
#define INCLUDED_HDU_H
#include "abstract_data_unit.h"
#include "crypto.h"
/**
* P25 header data unit (HDU).
*/
class hdu : public abstract_data_unit
class hdu : public abstract_data_unit, public crypto_state_provider
{
public:
@ -96,7 +97,9 @@ protected:
*/
virtual uint16_t frame_size_max() const;
private:
public:
uint8_t algid() const;
/**
* Return a string describing the encryption algorithm ID (ALGID).
@ -105,6 +108,8 @@ private:
*/
std::string algid_str() const;
virtual uint16_t kid() const;
/**
* Returns a string describing the key id (KID).
*
@ -119,6 +124,8 @@ private:
*/
virtual std::string mfid_str() const;
virtual std::vector<uint8_t> mi() const;
/**
* Returns a string describing the message indicator (MI).
*
@ -139,6 +146,10 @@ private:
* \return A string identifying the TGID.
*/
virtual std::string tgid_str() const;
public:
struct CryptoState crypto_state() const;
};
#endif /* INCLUDED_HDU_H */

102
op25/gr-op25/lib/ldu.cc Normal file
View File

@ -0,0 +1,102 @@
#include "ldu.h"
#include <stdio.h>
#include <itpp/base/vec.h>
#include <itpp/base/mat.h>
#include <itpp/base/binary.h>
#include <itpp/base/converters.h>
const static itpp::Mat<int> ham_10_6_3_6("1 1 1 0 0 1 1 0 0 0; 1 1 0 1 0 1 0 1 0 0; 1 0 1 1 1 0 0 0 1 0; 0 1 1 1 1 0 0 0 0 1");
typedef std::vector<itpp::Vec<int> > VecArray;
ldu::ldu(const_bit_queue& frame_body) :
voice_data_unit(frame_body),
m_hamming_error_count(0)
{
}
void
ldu::do_correct_errors(bit_vector& frame_body)
{
voice_data_unit::do_correct_errors(frame_body);
}
bool
ldu::process_meta_data(bit_vector& frame_body)
{
m_hamming_error_count = 0;
//std::vector<uint8_t> lc(30);
//std::vector<uint16_t> ham(24);
int lc_bit_idx = 0;
VecArray arrayVec;
itpp::Vec<int> vecRaw(10); // First 6 bits contain data
for (int i = 400; i < 1360; i += 184)
{
for (int j = 0; j < 40; j++)
{
int x = (i + j) + (((i + j) / 70) * 2); // Adjust bit index for status
unsigned char ch = frame_body[x];
//lc[lc_bit_idx / 8] |= (ch << (7 - (lc_bit_idx % 8)));
//ham[lc_bit_idx / 10] = ((ham[lc_bit_idx / 10]) << 1) | ch;
vecRaw(lc_bit_idx % 10) = ch;
++lc_bit_idx;
if ((lc_bit_idx % 10) == 0)
arrayVec.push_back(vecRaw);
}
}
if (lc_bit_idx != 240) // Not enough bits
{
return false;
}
if (arrayVec.size() != 24) // Not enough vectors
{
return false;
}
itpp::Vec<int> vecZero(4);
vecZero.zeros();
m_raw_meta_data.clear();
for (int i = 0; i < arrayVec.size(); ++i)
{
itpp::Vec<int>& vec = arrayVec[i];
itpp::bvec vB(itpp::to_bvec(vec));
itpp::Vec<int> vS = ham_10_6_3_6 * vec;
for (int i = 0; i < vS.length(); ++i)
vS[i] = vS[i] % 2;
itpp::bvec vb(to_bvec(vS));
if (itpp::bin2dec(vb) != 0)
{
++m_hamming_error_count;
}
m_raw_meta_data = concat(m_raw_meta_data, vB.mid(0, 6)); // Includes RS for last 72 bits
}
if (logging_enabled()) fprintf(stderr, "%s: %lu hamming errors, %s\n", duid_str().c_str(), m_hamming_error_count, (meta_data_valid() ? "valid" : "invalid"));
return meta_data_valid();
}
const itpp::bvec&
ldu::raw_meta_data() const
{
return m_raw_meta_data;
}
bool
ldu::meta_data_valid() const
{
return (m_raw_meta_data.length() == 144); // Not enough bits after Hamming(10,6,3)
}

28
op25/gr-op25/lib/ldu.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef INCLUDED_LDU_H
#define INCLUDED_LDU_H
#include "voice_data_unit.h"
class ldu : public voice_data_unit
{
private:
size_t m_hamming_error_count;
itpp::bvec m_raw_meta_data;
protected:
virtual void do_correct_errors(bit_vector& frame_body);
virtual bool process_meta_data(bit_vector& frame_body);
virtual const itpp::bvec& raw_meta_data() const;
public:
ldu(const_bit_queue& frame_body);
virtual bool meta_data_valid() const;
};
#endif // INCLUDED_LDU_H

View File

@ -23,10 +23,19 @@
#include "ldu1.h"
#include <itpp/base/vec.h>
#include <itpp/base/converters.h>
#include <boost/format.hpp>
#include <iostream>
#include "pickle.h"
#include "value_string.h"
using std::string;
ldu1::ldu1(const_bit_queue& frame_body) :
voice_data_unit(frame_body)
ldu(frame_body)
{
}
@ -34,6 +43,64 @@ ldu1::~ldu1()
{
}
void ldu1::do_correct_errors(bit_vector& frame_body)
{
ldu::do_correct_errors(frame_body);
if (!process_meta_data(frame_body))
return;
const itpp::bvec& data = raw_meta_data();
std::stringstream ss;
m_meta_data.m.lcf = bin2dec(data.mid(0, 8));
m_meta_data.m.mfid = bin2dec(data.mid(8, 8));
ss << (boost::format("%s: LCF: 0x%02x, MFID: 0x%02x") % duid_str() % m_meta_data.m.lcf % m_meta_data.m.mfid);
if (m_meta_data.m.lcf == 0x00)
{
m_meta_data.m0.emergency = data[16];
m_meta_data.m0.reserved = bin2dec(data.mid(17, 15));
m_meta_data.m0.tgid = bin2dec(data.mid(32, 16));
m_meta_data.m0.source = bin2dec(data.mid(48, 24));
ss << (boost::format(", Emergency: 0x%02x, Reserved: 0x%04x, TGID: 0x%04x, Source: 0x%06x") % m_meta_data.m0.emergency % m_meta_data.m0.reserved % m_meta_data.m0.tgid % m_meta_data.m0.source);
}
else if (m_meta_data.m.lcf == 0x03)
{
m_meta_data.m3.reserved = bin2dec(data.mid(16, 8));
m_meta_data.m3.destination = bin2dec(data.mid(24, 24));
m_meta_data.m3.source = bin2dec(data.mid(48, 24));
ss << (boost::format(", Reserved: 0x%02x, Destination: 0x%06x, Source: 0x%06x") % m_meta_data.m3.reserved % m_meta_data.m3.destination % m_meta_data.m3.source);
}
else
{
ss << " (unknown LCF)";
}
if (logging_enabled()) std::cerr << ss.str() << std::endl;
}
std::string
ldu1::snapshot() const
{
pickle p;
p.add("duid", duid_str());
p.add("mfid", lookup(m_meta_data.m.mfid, MFIDS, MFIDS_SZ));
if ((m_meta_data.m.lcf == 0x00) || (m_meta_data.m.lcf == 0x03))
p.add("source", (boost::format("0x%06x") % m_meta_data.m0.source).str());
if (m_meta_data.m.lcf == 0x00)
p.add("tgid", (boost::format("0x%04x") % m_meta_data.m0.tgid).str());
if (m_meta_data.m.lcf == 0x03)
p.add("dest", (boost::format("0x%06x") % m_meta_data.m3.destination).str());
return p.to_string();
}
ldu1::combined_meta_data
ldu1::meta_data() const
{
return m_meta_data;
}
string
ldu1::duid_str() const
{

View File

@ -24,13 +24,45 @@
#ifndef INCLUDED_LDU1_H
#define INCLUDED_LDU1_H
#include "voice_data_unit.h"
#include "ldu.h"
/**
* P25 Logical Data Unit 1.
*/
class ldu1 : public voice_data_unit
class ldu1 : public ldu
{
protected:
void do_correct_errors(bit_vector& frame_body);
public:
struct base_meta_data
{
unsigned char lcf;
unsigned char mfid;
unsigned int source;
unsigned short reserved;
};
struct meta_data_0 : public base_meta_data
{
bool emergency;
unsigned short tgid;
};
struct meta_data_3 : public base_meta_data
{
unsigned int destination;
};
union combined_meta_data
{
base_meta_data m;
meta_data_0 m0;
meta_data_3 m3;
} m_meta_data;
public:
/**
@ -50,6 +82,10 @@ public:
*/
std::string duid_str() const;
virtual std::string snapshot() const;
combined_meta_data meta_data() const;
};
#endif /* INCLUDED_LDU1_H */

View File

@ -23,10 +23,18 @@
#include "ldu2.h"
#include <itpp/base/vec.h>
#include <itpp/base/converters.h>
#include <boost/format.hpp>
#include "pickle.h"
#include "value_string.h"
using std::string;
ldu2::ldu2(const_bit_queue& frame_body) :
voice_data_unit(frame_body)
ldu(frame_body)
{
}
@ -39,3 +47,43 @@ ldu2::duid_str() const
{
return string("LDU2");
}
std::string
ldu2::snapshot() const
{
pickle p;
p.add("duid", duid_str());
std::stringstream ss;
ss << "0x";
for (size_t n = 0; n < m_crypto_state.mi.size(); ++n)
ss << (boost::format("%02x") % (int)m_crypto_state.mi[n]);
p.add("mi", ss.str());
p.add("algid", lookup(m_crypto_state.algid, ALGIDS, ALGIDS_SZ));
p.add("kid", (boost::format("0x%04x") % m_crypto_state.kid).str());
return p.to_string();
}
void
ldu2::do_correct_errors(bit_vector& frame_body)
{
ldu::do_correct_errors(frame_body);
if (!process_meta_data(frame_body))
return;
const itpp::bvec& data = raw_meta_data();
for (int i = 0; i < 72; i += 8)
{
m_crypto_state.mi[i/8] = bin2dec(data.mid(i, 8));
}
m_crypto_state.algid = bin2dec(data.mid(72, 8));
m_crypto_state.kid = bin2dec(data.mid(80, 16));
}
struct CryptoState
ldu2::crypto_state() const
{
return m_crypto_state;
}

View File

@ -24,13 +24,22 @@
#ifndef INCLUDED_LDU2_H
#define INCLUDED_LDU2_H
#include "voice_data_unit.h"
#include "ldu.h"
#include "crypto.h"
/**
* P25 Logical Data Unit 2.
*/
class ldu2 : public voice_data_unit
class ldu2 : public ldu, public crypto_state_provider
{
private:
struct CryptoState m_crypto_state;
protected:
void do_correct_errors(bit_vector& frame_body);
public:
/**
@ -49,6 +58,10 @@ public:
* Returns a string describing the Data Unit ID (DUID).
*/
std::string duid_str() const;
virtual std::string snapshot() const;
struct CryptoState crypto_state() const;
};
#endif /* INCLUDED_LDU2_H */

View File

@ -1,113 +0,0 @@
/* -*- C++ -*- */
%feature("autodoc", "1");
%{
#include <stddef.h>
%}
%include "exception.i"
%import "gnuradio.i"
%{
#include "gnuradio/swig/gnuradio_swig_bug_workaround.h"
#include "op25_fsk4_demod_ff.h"
#include "op25_fsk4_slicer_fb.h"
#include "op25_decoder_bf.h"
#include "op25_pcap_source_b.h"
%}
// ----------------------------------------------------------------
/*
* This does some behind-the-scenes magic so we can
* access fsk4_square_ff from python as fsk4.square_ff
*/
GR_SWIG_BLOCK_MAGIC(op25, fsk4_demod_ff);
/*
* Publicly-accesible default constuctor function for op25_fsk4_demod_bf.
*/
op25_fsk4_demod_ff_sptr op25_make_fsk4_demod_ff(gr::msg_queue::sptr queue, float sample_rate, float symbol_rate);
class op25_fsk4_demod_ff : public gr_block
{
private:
op25_fsk4_demod_ff(gr::msg_queue::sptr queue, float sample_rate, float symbol_rate);
};
// ----------------------------------------------------------------
/*
* This does some behind-the-scenes magic so we can invoke
* op25_make_slicer_fb from python as op25.slicer_fbf.
*/
GR_SWIG_BLOCK_MAGIC(op25, fsk4_slicer_fb);
/*
* Publicly-accesible default constuctor function for op25_decoder_bf.
*/
op25_fsk4_slicer_fb_sptr op25_make_fsk4_slicer_fb(const std::vector<float> &slice_levels);
/*
* The op25_fsk4_slicer block. Takes a series of float samples and
* partitions them into dibit symbols according to the slices_levels
* provided to the constructor.
*/
class op25_fsk4_slicer_fb : public gr_sync_block
{
private:
op25_fsk4_slicer_fb (const std::vector<float> &slice_levels);
};
// ----------------------------------------------------------------
/*
* This does some behind-the-scenes magic so we can invoke
* op25_make_decoder_bsf from python as op25.decoder_bf.
*/
GR_SWIG_BLOCK_MAGIC(op25, decoder_bf);
/*
* Publicly-accesible default constuctor function for op25_decoder_bf.
*/
op25_decoder_bf_sptr op25_make_decoder_bf();
/**
* The op25_decoder_bf block. Accepts a stream of dibit symbols and
* produces an 8KS/s audio stream.
*/
class op25_decoder_bf : public gr_block
{
private:
op25_decoder_bf();
public:
const char *destination() const;
gr::msg_queue::sptr get_msgq() const;
void set_msgq(gr::msg_queue::sptr msgq);
};
// ----------------------------------------------------------------
/*
* This does some behind-the-scenes magic so we can invoke
* op25_make_pcap_source_b from python as op25.pcap_source_b.
*/
GR_SWIG_BLOCK_MAGIC(op25, pcap_source_b);
/*
* Publicly-accesible constuctor function for op25_pcap_source.
*/
op25_pcap_source_b_sptr op25_make_pcap_source_b(const char *path, float delay);
/*
* The op25_pcap_source block. Reads symbols from a tcpdump-formatted
* file and produces a stream of symbols of the appropriate size.
*/
class op25_pcap_source_b : public gr_sync_block
{
private:
op25_pcap_source_b(const char *path, float delay);
};
// ----------------------------------------------------------------

View File

@ -3,6 +3,7 @@
#include <cstddef>
#include <stdint.h>
#include <assert.h>
/*
* APCO Hamming(15,11,3) ecoder.
@ -183,4 +184,18 @@ hamming_15_decode(uint16_t& cw)
return errs;
}
static const uint32_t hmg1063EncTbl[64] = {
0, 12, 3, 15, 7, 11, 4, 8, 11, 7, 8, 4, 12, 0, 15, 3,
13, 1, 14, 2, 10, 6, 9, 5, 6, 10, 5, 9, 1, 13, 2, 14,
14, 2, 13, 1, 9, 5, 10, 6, 5, 9, 6, 10, 2, 14, 1, 13,
3, 15, 0, 12, 4, 8, 7, 11, 8, 4, 11, 7, 15, 3, 12, 0 };
static const uint32_t hmg1063DecTbl[16] = {
0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 8, 1, 16, 32, 0 };
static inline int hmg1063Dec (uint32_t Dat, uint32_t Par) {
assert ((Dat < 64) && (Par < 16));
return Dat ^ hmg1063DecTbl[hmg1063EncTbl[Dat] ^ Par];
}
#endif /* INCLUDED_OP25_HAMMING_H */

View File

@ -10,9 +10,54 @@
#include <vector>
typedef std::vector<bool> voice_codeword;
typedef std::vector<uint8_t> packed_codeword;
typedef const std::vector<bool> const_bit_vector;
typedef std::vector<bool> bit_vector;
static const uint16_t hdu_codeword_bits[658] = { // 329 symbols = 324 + 5 pad
114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 144, 145, 146, 147,
148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163,
164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195,
196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211,
212, 213, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245,
246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261,
262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277,
278, 279, 280, 281, 282, 283, 284, 285, 288, 289, 290, 291, 292, 293, 294, 295,
296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311,
312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327,
328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343,
344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 360, 361,
362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377,
378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393,
394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409,
410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425,
426, 427, 428, 429, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443,
444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459,
460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475,
476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491,
492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 504, 505, 506, 507, 508, 509,
510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525,
526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541,
542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557,
558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573,
576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591,
592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607,
608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623,
624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639,
640, 641, 642, 643, 644, 645, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657,
658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673,
674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689,
690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705,
706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 720, 721, 722, 723,
724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739,
740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755,
756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771,
772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787,
788, 789 };
static const size_t nof_voice_codewords = 9, voice_codeword_sz = 144;
static const uint16_t imbe_ldu_NID_bits[] = {
@ -251,8 +296,8 @@ pngen23(uint32_t& Pr)
* \param u0-u7 Result output vectors
*/
static inline void
imbe_header_decode(const voice_codeword& cw, uint32_t& u0, uint32_t& u1, uint32_t& u2, uint32_t& u3, uint32_t& u4, uint32_t& u5, uint32_t& u6, uint32_t& u7, uint32_t& E0, uint32_t& ET)
static inline size_t
imbe_header_decode(const voice_codeword& cw, uint32_t& u0, uint32_t& u1, uint32_t& u2, uint32_t& u3, uint32_t& u4, uint32_t& u5, uint32_t& u6, uint32_t& u7, uint32_t& E0, uint32_t& ET, bool bot_shift = true)
{
ET = 0;
@ -294,7 +339,30 @@ imbe_header_decode(const voice_codeword& cw, uint32_t& u0, uint32_t& u1, uint32_
u6 = v6;
u7 = extract(cw, 137, 144);
u7 <<= 1; /* so that bit0 is free (see note about BOT bit */
if (bot_shift)
u7 <<= 1; /* so that bit0 is free (see note about BOT bit */
return errs;
}
/*
* Pack 88 bit IMBE parameters into uint8_t vector
*/
static inline void
imbe_pack(packed_codeword& cw, uint32_t u0, uint32_t u1, uint32_t u2, uint32_t u3, uint32_t u4, uint32_t u5, uint32_t u6, uint32_t u7)
{
cw.empty();
cw.push_back(u0 >> 4);
cw.push_back(((u0 & 0xf) << 4) + (u1 >> 8));
cw.push_back(u1 & 0xff);
cw.push_back(u2 >> 4);
cw.push_back(((u2 & 0xf) << 4) + (u3 >> 8));
cw.push_back(u3 & 0xff);
cw.push_back(u4 >> 3);
cw.push_back(((u4 & 0x7) << 5) + (u5 >> 6));
cw.push_back(((u5 & 0x3f) << 2) + (u6 >> 9));
cw.push_back(u6 >> 1);
cw.push_back(((u6 & 0x1) << 7) + (u7 >> 1));
}
/* APCO IMBE header encoder.

View File

@ -2,6 +2,7 @@
/*
* Copyright 2008 Steve Glass
* Copyright 2018 Matt Ames
*
* This file is part of OP25.
*
@ -53,18 +54,38 @@ const value_string ALGIDS[] = {
{ 0x02, "FIREFLY Type 1" },
{ 0x03, "MAYFLY Type 1" },
{ 0x04, "SAVILLE" },
{ 0x05, "Motorola Assigned - PADSTONE" },
{ 0x41, "BATON (Auto Odd)" },
/* Type III */
{ 0x80, "Plain" },
{ 0x81, "DES-OFB" },
{ 0x82, "2 key Triple DES" },
{ 0x83, "3 key Triple DES" },
{ 0x84, "AES-256" },
/* Motorola proprietary */
{ 0x9F, "Motorola DES-XL" },
{ 0x80, "Unencrypted" },
{ 0x81, "DES-OFB, 56 bit key" },
{ 0x83, "3 key Triple DES, 168 bit key" },
{ 0x84, "AES-256-OFB" },
{ 0x85, "AES-128-ECB"},
{ 0x88, "AES-CBC"},
/* Motorola proprietary - some of these have been observed over the air,
some have been taken from firmware dumps on various devices, others
have come from the TIA's FTP website while it was still public,
from document "ALGID Guide 2015-04-15.pdf", and others have been
have been worked out with a little bit of guesswork */
{ 0x9F, "Motorola DES-XL 56-bit key" },
{ 0xA0, "Motorola DVI-XL" },
{ 0xA1, "Motorola DVP-XL" },
{ 0xAA, "Motorola ADP" },
{ 0xA2, "Motorola DVI-SPFL"},
{ 0xA3, "Motorola Assigned - Unknown" },
{ 0xA4, "Motorola Assigned - Unknown" },
{ 0xA5, "Motorola Assigned - Unknown" },
{ 0xA6, "Motorola Assigned - Unknown" },
{ 0xA7, "Motorola Assigned - Unknown" },
{ 0xA8, "Motorola Assigned - Unknown" },
{ 0xA9, "Motorola Assigned - Unknown" },
{ 0xAA, "Motorola ADP 40 bit RC4" },
{ 0xAB, "Motorola CFX-256" },
{ 0xAC, "Motorola Assigned - Unknown" },
{ 0xAD, "Motorola Assigned - Unknown" },
{ 0xAE, "Motorola Assigned - Unknown" },
{ 0xAF, "Motorola AES-256-GCM (possibly)" },
{ 0xB0, "Motorola DVP"},
};
const size_t ALGIDS_SZ = sizeof(ALGIDS) / sizeof(ALGIDS[0]);

View File

@ -24,32 +24,325 @@
#include "voice_data_unit.h"
#include "op25_imbe_frame.h"
#include <sstream>
#include <map>
#include <iostream>
#include <boost/format.hpp>
#include <stdio.h>
#include <itpp/base/vec.h>
#include <itpp/base/mat.h>
#include <itpp/base/binary.h>
#include <itpp/base/converters.h>
using namespace std;
voice_data_unit::~voice_data_unit()
static void vec_mod(itpp::ivec& vec, int modulus = 2)
{
for (int i = 0; i < vec.length(); ++i)
vec[i] = vec[i] % modulus;
}
class cyclic_16_8_5_syndromes
{
public:
typedef map<unsigned char,unsigned short> SyndromeTableMap;
const static itpp::imat cyclic_16_8_5;
private:
SyndromeTableMap m_syndrome_table;
public:
inline const SyndromeTableMap table() const
{
return m_syndrome_table;
}
cyclic_16_8_5_syndromes(bool generate_now = false)
{
if (generate_now)
generate();
}
int generate()
{
if (m_syndrome_table.empty() == false)
return -1;
// n=16, k=8
// E1
itpp::ivec v("1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0");
itpp::ivec r(cyclic_16_8_5 * v);
vec_mod(r);
itpp::bvec b(to_bvec(r));
unsigned char ch = (unsigned char)bin2dec(b);
itpp::bvec bV(to_bvec(v));
unsigned short us = (unsigned short)bin2dec(bV);
m_syndrome_table.insert(make_pair(ch, us));
// E2
for (int i = 0; i <= (16 - 2); ++i)
{
itpp::ivec v2(v);
v2[15-i] = 1;
r = cyclic_16_8_5 * v2;
bV = itpp::to_bvec(v2);
vec_mod(r);
b = itpp::to_bvec(r);
unsigned char ch = (unsigned char)itpp::bin2dec(b);
unsigned short us = (unsigned short)itpp::bin2dec(bV);
m_syndrome_table.insert(make_pair(ch, us));
}
// E3 - disabled: min.d = 5, t=floor(5/2)=2
/*for (int i = 0; i <= (16 - 2); ++i)
{
for (int j = 0; j < i; ++j)
{
ivec v3(v);
v3[15-i] = 1;
v3[15-j] = 1;
r = cyclic_16_8_5 * v3;
bV = to_bvec(v3);
vec_mod(r);
b = to_bvec(r);
unsigned char ch = (unsigned char)bin2dec(b);
unsigned short us = (unsigned short)bin2dec(bV);
m_syndrome_table.insert(make_pair(ch, us));
}
}*/
return m_syndrome_table.size();
}
};
const itpp::imat cyclic_16_8_5_syndromes::cyclic_16_8_5(
"0 0 1 1 1 1 0 0 1 0 0 0 0 0 0 0;"
"1 0 0 1 1 1 1 0 0 1 0 0 0 0 0 0;"
"0 1 0 0 1 1 1 1 0 0 1 0 0 0 0 0;"
"0 0 0 1 1 0 1 1 0 0 0 1 0 0 0 0;"
"1 0 1 1 0 0 0 1 0 0 0 0 1 0 0 0;"
"1 1 1 0 0 1 0 0 0 0 0 0 0 1 0 0;"
"1 1 1 1 0 0 1 0 0 0 0 0 0 0 1 0;"
"0 1 1 1 1 0 0 1 0 0 0 0 0 0 0 1"
);
static cyclic_16_8_5_syndromes g_cyclic_16_8_5_syndromes(true);
static int decode_cyclic_16_8_5(const itpp::ivec& vec, itpp::ivec& out)
{
itpp::ivec vc(cyclic_16_8_5_syndromes::cyclic_16_8_5 * vec);
vec_mod(vc);
itpp::bvec vb(to_bvec(vc));
unsigned char ch = (unsigned char)itpp::bin2dec(vb);
if (ch == 0x00)
return 0;
const cyclic_16_8_5_syndromes::SyndromeTableMap& syndrome_table = g_cyclic_16_8_5_syndromes.table();
cyclic_16_8_5_syndromes::SyndromeTableMap::const_iterator it = syndrome_table.find(ch);
int j = 0;
while (it == syndrome_table.end())
{
++j;
vc = itpp::concat(itpp::ivec("0 0 0 0 0 0 0 0"), vc); // Restore to 16 bits
vc.shift_left(vc[0]); // Rotate (s * x)
vc = cyclic_16_8_5_syndromes::cyclic_16_8_5 * vc;
vec_mod(vc);
vb = itpp::to_bvec(vc);
ch = (unsigned char)itpp::bin2dec(vb);
it = syndrome_table.find(ch);
if (j >= 15)
break;
}
if (it == syndrome_table.end())
{
return -1;
}
unsigned short us = it->second;
itpp::bvec es(itpp::dec2bin(16, us));
if (j > 0)
es.shift_right(es.mid(16-j, j)); // e
vb = itpp::to_bvec(vec);
vb -= es;
out = itpp::to_ivec(vb);
vc = cyclic_16_8_5_syndromes::cyclic_16_8_5 * out;
vec_mod(vc);
vb = itpp::to_bvec(vc);
if (itpp::bin2dec(vb) != 0x00)
{
return -1;
}
return 1;
}
static int decode_cyclic_16_8_5(itpp::ivec& vec)
{
return decode_cyclic_16_8_5(vec, vec);
}
////////////////////////////////////////////////////////////////////////////////////
voice_data_unit::voice_data_unit(const_bit_queue& frame_body) :
abstract_data_unit(frame_body)
abstract_data_unit(frame_body),
d_lsdw(0),
d_lsdw_valid(false)
{
memset(d_lsd_byte_valid, 0x00, sizeof(d_lsd_byte_valid));
}
voice_data_unit::~voice_data_unit()
{
}
void
voice_data_unit::do_correct_errors(bit_vector& frame_body)
{
if (logging_enabled()) fprintf(stderr, "\n");
d_lsd_byte_valid[0] = d_lsd_byte_valid[1] = false;
d_lsdw_valid = false;
itpp::ivec lsd1(16), lsd2(16);
for (int i = 0; i < 32; ++i)
{
int x = 1504 + i;
x = x + ((x / 70) * 2); // Adjust bit index for status
if (i < 16)
lsd1[i] = frame_body[x];
else
lsd2[i-16] = frame_body[x];
}
int iDecode1 = decode_cyclic_16_8_5(lsd1);
if (iDecode1 >= 0)
{
d_lsd_byte_valid[0] = true;
}
else if (iDecode1 == -1)
{
// Error
}
int iDecode2 = decode_cyclic_16_8_5(lsd2);
if (iDecode2 >= 0)
{
d_lsd_byte_valid[1] = true;
}
else
{
// Error
}
d_lsdw = 0;
for (int i = 0; i < 8; ++i)
d_lsdw = d_lsdw | (lsd1[i] << (7 - i)); // Little-endian byte swap
for (int i = 0; i < 8; ++i)
d_lsdw = d_lsdw | (lsd2[i] << (15 - i)); // Little-endian byte swap
if (d_lsd_byte_valid[0] && d_lsd_byte_valid[1])
d_lsdw_valid = true;
}
uint16_t
voice_data_unit::lsdw() const
{
return d_lsdw;
}
bool
voice_data_unit::lsdw_valid() const
{
return d_lsdw_valid;
}
static void extract(unsigned int u, size_t n, std::vector<bool>& out)
{
for (size_t i = 0; i < n; ++i)
out.push_back(((u & (1 << (n-1-i))) != 0));
}
void
voice_data_unit::do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe)
voice_data_unit::do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe, crypto_module::sptr crypto_mod)
{
voice_codeword cw(voice_codeword_sz);
for(size_t i = 0; i < nof_voice_codewords; ++i) {
imbe_deinterleave(frame_body, cw, i);
unsigned int u0 = 0;
unsigned int u1,u2,u3,u4,u5,u6,u7;
unsigned int E0 = 0;
unsigned int ET = 0;
// PN/Hamming/Golay - etc.
size_t errs = imbe_header_decode(cw, u0, u1, u2, u3, u4, u5, u6, u7, E0, ET, false); // E0 & ET are not used, and are always returned as 0
crypto_algorithm::sptr algorithm;
if (crypto_mod)
algorithm = crypto_mod->current_algorithm();
if (algorithm)
{
if (i == 8)
{
d_lsdw ^= algorithm->generate(16); // LSDW
}
u0 ^= (int)algorithm->generate(12);
u1 ^= (int)algorithm->generate(12);
u2 ^= (int)algorithm->generate(12);
u3 ^= (int)algorithm->generate(12);
u4 ^= (int)algorithm->generate(11);
u5 ^= (int)algorithm->generate(11);
u6 ^= (int)algorithm->generate(11);
u7 ^= (int)algorithm->generate(7);
imbe_header_encode(cw, u0, u1, u2, u3, u4, u5, u6, (u7 << 1));
}
std::vector<bool> cw_raw;
extract(u0, 12, cw_raw);
extract(u1, 12, cw_raw);
extract(u2, 12, cw_raw);
extract(u3, 12, cw_raw);
extract(u4, 11, cw_raw);
extract(u5, 11, cw_raw);
extract(u6, 11, cw_raw);
extract(u7, 7, cw_raw);
const int cw_octets = 11;
std::vector<uint8_t> cw_vector(cw_octets);
extract(cw_raw, 0, (cw_octets * 8), &cw_vector[0]);
if (logging_enabled())
{
std::stringstream ss;
for (size_t n = 0; n < cw_vector.size(); ++n)
{
ss << (boost::format("%02x") % (int)cw_vector[n]);
if (n < (cw_vector.size() - 1))
ss << " ";
}
if (errs > 0)
ss << (boost::format(" (%llu errors)") % errs);
std:cerr << (boost::format("%s:\t%s") % duid_str() % ss.str()) << std::endl;
}
imbe.decode(cw);
}
if (logging_enabled()) fprintf(stderr, "%s: LSDW: 0x%04x, %s\n", duid_str().c_str(), d_lsdw, (d_lsdw_valid ? "valid" : "invalid"));
}
uint16_t

View File

@ -38,6 +38,17 @@ public:
*/
virtual ~voice_data_unit();
const static int LSD_BYTE_COUNT=2;
private:
union {
uint8_t d_lsd_byte[LSD_BYTE_COUNT];
uint16_t d_lsdw;
};
bool d_lsdw_valid;
bool d_lsd_byte_valid[LSD_BYTE_COUNT];
protected:
/**
@ -63,7 +74,7 @@ protected:
* \param frame_body The const_bit_vector to decode.
* \param imbe The imbe_decoder to use to generate the audio.
*/
virtual void do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe);
virtual void do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe, crypto_module::sptr crypto_mod);
/**
* Returns the expected size (in bits) of this data_unit. For
@ -74,6 +85,10 @@ protected:
*/
virtual uint16_t frame_size_max() const;
virtual uint16_t lsdw() const;
virtual bool lsdw_valid() const;
};
#endif /* INCLUDED_VOICE_DATA_UNIT_H */

View File

@ -28,9 +28,10 @@
using namespace std;
voice_du_handler::voice_du_handler(data_unit_handler_sptr next, imbe_decoder_sptr decoder) :
voice_du_handler::voice_du_handler(data_unit_handler_sptr next, imbe_decoder_sptr decoder, crypto_module::sptr crypto_mod) :
data_unit_handler(next),
d_decoder(decoder)
d_decoder(decoder),
d_crypto_mod(crypto_mod)
{
}
@ -42,6 +43,6 @@ voice_du_handler::~voice_du_handler()
void
voice_du_handler::handle(data_unit_sptr du)
{
du->decode_audio(*d_decoder);
du->decode_audio(*d_decoder, d_crypto_mod);
data_unit_handler::handle(du);
}

View File

@ -26,6 +26,7 @@
#include "data_unit_handler.h"
#include "imbe_decoder.h"
#include "crypto.h"
#include <boost/noncopyable.hpp>
@ -43,7 +44,7 @@ public:
* \param next The next data_unit_handler in the chain.
* \param decoder An imbe_decoder_sptr to the IMBE decoder to use.
*/
voice_du_handler(data_unit_handler_sptr next, imbe_decoder_sptr decoder);
voice_du_handler(data_unit_handler_sptr next, imbe_decoder_sptr decoder, crypto_module::sptr crypto_mod = crypto_module::sptr()); // TODO: Add capability to decoder_ff (remove default argument)
/**
* voice_du_handler virtual destructor.
@ -64,6 +65,8 @@ private:
*/
imbe_decoder_sptr d_decoder;
crypto_module::sptr d_crypto_mod;
};
#endif /* INCLUDED_VOICE_DU_HANDLER_H */

View File

@ -1,5 +1,7 @@
/* -*- c++ -*- */
%include "pycontainer.swg"
#define OP25_API
%include "gnuradio.i" // the common stuff
@ -15,6 +17,11 @@
#include "op25/pcap_source_b.h"
%}
%template(key_type) std::vector<unsigned char>;
// This causes SWIG to segfault
//%template(key_map_type) std::map<uint16_t,key_type >;
%template(key_map_type) std::map<uint16_t,std::vector<unsigned char> >;
%include "op25/fsk4_demod_ff.h"
GR_SWIG_BLOCK_MAGIC2(op25, fsk4_demod_ff);
%include "op25/fsk4_slicer_fb.h"

View File

@ -83,7 +83,6 @@ set(GRC_BLOCKS_DIR ${GR_PKG_DATA_DIR}/grc/blocks)
########################################################################
# Find gnuradio build dependencies
########################################################################
find_package(GnuradioRuntime)
find_package(CppUnit)
# To run a more advanced search for GNU Radio and it's components and
@ -91,12 +90,9 @@ find_package(CppUnit)
# of GR_REQUIRED_COMPONENTS (in all caps) and change "version" to the
# minimum API compatible version required.
#
# set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER ...)
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
# find_package(Gnuradio "version")
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
find_package(Gnuradio)
endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
find_package(Gnuradio)
if(NOT GNURADIO_RUNTIME_FOUND)
message(FATAL_ERROR "GnuRadio Runtime required to compile op25_repeater")

View File

@ -52,6 +52,19 @@ to suspend the program and examine stderr.2 for error messages. If there
is a traceback please report the full traceback (and the command line) to
the mail list.
REMOTE TERMINAL
===============
Adding (for example) "-l 56111" to the rx.py command starts rx.py but does
not attach a curses terminal. Instead the program runs as normal in the
foreground (hit CTRL-C to end rx.py as desired). To connect to a running
instance of rx.py, (in this example)
./terminal.py 127.0.0.1 56111
NOTE: rx.py and terminal.py need not run on the same machine. The machine
where terminal.py is running need not have an SDR device directly attached;
but GNU Radio (and OP25) must be available.
WARNING: there is no security or encryption on the UDP port.
EXTERNAL UDP AUDIO SERVER
=========================
Starting rx.py with the "-w -W host" options directs udp audio data to
@ -129,6 +142,8 @@ Options:
-H HAMLIB_MODEL, --hamlib-model=HAMLIB_MODEL
specify model for hamlib
-s SEEK, --seek=SEEK ifile seek in K
-l TERMINAL_TYPE, --terminal-type=TERMINAL_TYPE
'curses' or udp port or 'http:host:port'
-L LOGFILE_WORKERS, --logfile-workers=LOGFILE_WORKERS
number of demodulators to instantiate
-S SAMPLE_RATE, --sample-rate=SAMPLE_RATE
@ -161,3 +176,67 @@ Options:
-2, --phase2-tdma enable phase2 tdma decode
-Z DECIM_AMT, --decim-amt=DECIM_AMT
spectrum decimation
HTTP CONSOLE
============
New as of Jan. 2018, the OP25 dashboard is accessible to any Web browser over
HTTP. Include the option "-l http:<host>:<port>" when starting the rx.py app,
where <host> is either "127.0.0.1" to limit access from only this host, or
"0.0.0.0" if HTTP access from anywhere is to be allowed*. After rx.py has
started it begins listening on the specified port for incoming connections.
Once connected the status page should automatically update to show trunking
system status, frequency list, adjacent sites, and other data.
Example: you have started rx.py with the option "-l http:127.0.0.1:8080".
To connect, set your web browser URL to "http://127.0.0.1:8080".
If one or more plot modes has been selected using the "-P" option you may
view them by clicking the "PLOT" button. The plots are updated approx.
every five seconds. Click "STATUS" to return to the main status page.
*WARNING*: there is no security or encryption. Be careful when using "0.0.0.0"
as the listening address since anyone with access to the network can connect.
NOTE: the python-pyramid package is required when using this option. It can
be installed by running
sudo apt-get install python-pyramid
MULTI-RECEIVER
==============
The multi_rx.py app allows an arbitrary number of SDR devices and channels
to be defined. Each channel may have one or more plot windows attached.
Configuration is achieved via a json file (see cfg.json for an example).
In this version, channels are automatically assigned to the first device
found whose frequency span includes the selected frequency.
As of this writing (winter, 2017), neither trunking nor P25 P2/TDMA are
supported in multi_rx.py. The rx.py app should be used for P25 trunking,
for either P1/FDMA or P2/TDMA.
Below is a summary of the major config file keys:
demod_type: 'cqpsk' for qpsk p25 only; 'fsk4' for ysf/dstar/dmr/fsk4 p25
filter_type: 'rc' for p25; 'rrc' for dmr and ysf; 'gmsk' for d-star
plot: 'fft', 'constellation', 'datascope', 'symbol'
[if more than one plot desired, provide a comma-separated list]
destination: 'udp://host:port' or 'file://<filename>'
name: arbitrary string used to identify channels and devices
bug: 'fft' and 'constellation' currently mutually exclusive
bug: 'gmsk' needs work
Note: DMR audio for the second time slot is sent on the specified port number
plus two. In the example 'udp://127.0.0.1:56122', audio for the first slot
would use 56122; and 56124 for the second.
The command line options for multi_rx:
Usage: multi_rx.py [options]
Options:
-h, --help show this help message and exit
-c CONFIG_FILE, --config-file=CONFIG_FILE
specify config file name
-v VERBOSITY, --verbosity=VERBOSITY
message debug level
-p, --pause block on startup

View File

@ -0,0 +1,53 @@
#!/usr/bin/env python
# Copyright 2017, 2018 Graham Norbury
#
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
#
# This file is part of OP25 and part of GNU Radio
#
# OP25 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, or (at your option)
# any later version.
#
# OP25 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 OP25; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
# 02110-1301, USA.
import signal
import sys
import time
from optparse import OptionParser
from sockaudio import socket_audio
def signal_handler(signal, frame):
audiothread.stop()
sys.exit(0)
parser = OptionParser()
parser.add_option("-O", "--audio-output", type="string", default="default", help="audio output device name")
parser.add_option("-H", "--host-ip", type="string", default="0.0.0.0", help="IP address to bind to")
parser.add_option("-u", "--wireshark-port", type="int", default=23456, help="Wireshark port")
parser.add_option("-2", "--two-channel", action="store_true", default=False, help="single or two channel audio")
parser.add_option("-x", "--audio-gain", type="float", default="1.0", help="audio gain (default = 1.0)")
(options, args) = parser.parse_args()
if len(args) != 0:
parser.print_help()
sys.exit(1)
audiothread = socket_audio(options.host_ip, options.wireshark_port, options.audio_output, options.two_channel, options.audio_gain)
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal_handler)
while True:
time.sleep(1)

View File

@ -0,0 +1,49 @@
{
"channels": [
{
"demod_type": "cqpsk",
"destination": "udp://127.0.0.1:56120",
"excess_bw": 0.2,
"filter_type": "rc",
"frequency": 460412500,
"if_rate": 24000,
"name": "p25",
"plot": "symbol",
"symbol_rate": 4800
},
{
"demod_type": "fsk4",
"destination": "file:///tmp/out1.raw",
"excess_bw": 0.2,
"filter_type": "rrc",
"frequency": 460500000,
"if_rate": 24000,
"name": "ysf",
"plot": "datascope",
"symbol_rate": 4800
},
{
"demod_type": "fsk4",
"destination": "udp://127.0.0.1:56122",
"excess_bw": 0.2,
"filter_type": "rrc",
"frequency": 460050000,
"if_rate": 24000,
"name": "dmr",
"plot": "symbol",
"symbol_rate": 4800
}
],
"devices": [
{
"args": "rtl=0",
"frequency": 460100000,
"gains": "lna:49",
"name": "rtl0",
"offset": 0,
"ppm": 38,
"rate": 1000000,
"tunable": false
}
]
}

View File

@ -20,6 +20,8 @@
# 02110-1301, USA.
import sys
import os
import time
import subprocess
from gnuradio import gr, gru, eng_notation
@ -27,6 +29,7 @@ from gnuradio import blocks, audio
from gnuradio.eng_option import eng_option
import numpy as np
from gnuradio import gr
from math import pi
_def_debug = 0
_def_sps = 10
@ -34,19 +37,42 @@ _def_sps = 10
GNUPLOT = '/usr/bin/gnuplot'
FFT_AVG = 0.25
MIX_AVG = 0.15
BAL_AVG = 0.05
FFT_BINS = 512
def degrees(r):
d = 360 * r / (2*pi)
while d <0:
d += 360
while d > 360:
d -= 360
return d
def limit(a,lim):
if a > lim:
return lim
return a
class wrap_gp(object):
def __init__(self, sps=_def_sps):
def __init__(self, sps=_def_sps, logfile=None):
self.sps = sps
self.center_freq = None
self.center_freq = 0.0
self.relative_freq = 0.0
self.offset_freq = 0.0
self.width = None
self.ffts = ()
self.freqs = ()
self.avg_pwr = np.zeros(FFT_BINS)
self.avg_sum_pwr = 0.0
self.buf = []
self.plot_count = 0
self.last_plot = 0
self.plot_interval = None
self.sequence = 0
self.output_dir = None
self.filename = None
self.logfile = logfile
self.attach_gp()
@ -56,8 +82,29 @@ class wrap_gp(object):
self.gp = subprocess.Popen(args, executable=exe, stdin=subprocess.PIPE)
def kill(self):
self.gp.kill()
self.gp.wait()
try:
self.gp.stdin.close() # closing pipe should cause subprocess to exit
except IOError:
pass
sleep_count = 0
while True: # wait politely, but only for so long
self.gp.poll()
if self.gp.returncode is not None:
break
time.sleep(0.1)
if self.gp.returncode is not None:
break
sleep_count += 1
if (sleep_count & 1) == 0:
self.gp.kill()
if sleep_count >= 3:
break
def set_interval(self, v):
self.plot_interval = v
def set_output_dir(self, v):
self.output_dir = v
def plot(self, buf, bufsz, mode='eye'):
BUFSZ = bufsz
@ -74,6 +121,7 @@ class wrap_gp(object):
plots = []
s = ''
plot_size = (320,240)
while(len(self.buf)):
if mode == 'eye':
if len(self.buf) < self.sps:
@ -83,61 +131,137 @@ class wrap_gp(object):
s += 'e\n'
self.buf=self.buf[self.sps:]
plots.append('"-" with lines')
elif mode == 'constellation':
elif mode == 'constellation':
plot_size = (240,240)
self.buf = self.buf[:100]
for b in self.buf:
s += '%f\t%f\n' % (degrees(np.angle(b)), limit(np.abs(b),1.0))
s += 'e\n'
plots.append('"-" with points')
for b in self.buf:
s += '%f\t%f\n' % (b.real, b.imag)
#s += '%f\t%f\n' % (b.real, b.imag)
s += '%f\t%f\n' % (degrees(np.angle(b)), limit(np.abs(b),1.0))
s += 'e\n'
self.buf = []
plots.append('"-" with points')
plots.append('"-" with lines')
elif mode == 'symbol':
for b in self.buf:
s += '%f\n' % (b)
s += 'e\n'
self.buf = []
plots.append('"-" with dots')
elif mode == 'fft':
plots.append('"-" with points')
elif mode == 'fft' or mode == 'mixer':
sum_pwr = 0.0
self.ffts = np.fft.fft(self.buf * np.blackman(BUFSZ)) / (0.42 * BUFSZ)
self.ffts = np.fft.fftshift(self.ffts)
self.freqs = np.fft.fftfreq(len(self.ffts))
self.freqs = np.fft.fftshift(self.freqs)
tune_freq = (self.center_freq - self.relative_freq) / 1e6
if self.center_freq and self.width:
self.freqs = ((self.freqs * self.width) + self.center_freq) / 1e6
self.freqs = ((self.freqs * self.width) + self.center_freq + self.offset_freq) / 1e6
for i in xrange(len(self.ffts)):
self.avg_pwr[i] = ((1.0 - FFT_AVG) * self.avg_pwr[i]) + (FFT_AVG * np.abs(self.ffts[i]))
if mode == 'fft':
self.avg_pwr[i] = ((1.0 - FFT_AVG) * self.avg_pwr[i]) + (FFT_AVG * np.abs(self.ffts[i]))
else:
self.avg_pwr[i] = ((1.0 - MIX_AVG) * self.avg_pwr[i]) + (MIX_AVG * np.abs(self.ffts[i]))
s += '%f\t%f\n' % (self.freqs[i], 20 * np.log10(self.avg_pwr[i]))
if (mode == 'mixer') and (self.avg_pwr[i] > 1e-5):
if (self.freqs[i] - self.center_freq) < 0:
sum_pwr -= self.avg_pwr[i]
elif (self.freqs[i] - self.center_freq) > 0:
sum_pwr += self.avg_pwr[i]
self.avg_sum_pwr = ((1.0 - BAL_AVG) * self.avg_sum_pwr) + (BAL_AVG * sum_pwr)
s += 'e\n'
self.buf = []
plots.append('"-" with lines')
elif mode == 'float':
for b in self.buf:
s += '%f\n' % (b)
s += 'e\n'
self.buf = []
plots.append('"-" with lines')
self.buf = []
h= 'set terminal x11 noraise\n'
#background = 'set object 1 circle at screen 0,0 size screen 1 fillcolor rgb"black"\n' #FIXME!
# FFT processing needs to be completed to maintain the weighted average buckets
# regardless of whether we actually produce a new plot or not.
if self.plot_interval and self.last_plot + self.plot_interval > time.time():
return consumed
self.last_plot = time.time()
filename = None
if self.output_dir:
if self.sequence >= 2:
delete_pathname = '%s/plot-%s-%d.png' % (self.output_dir, mode, self.sequence-2)
if os.access(delete_pathname, os.W_OK):
os.remove(delete_pathname)
h0= 'set terminal png size %d, %d\n' % (plot_size)
filename = 'plot-%s-%d.png' % (mode, self.sequence)
h0 += 'set output "%s/%s"\n' % (self.output_dir, filename)
self.sequence += 1
else:
h0= 'set terminal x11 noraise\n'
background = ''
h+= 'set key off\n'
h = 'set key off\n'
if mode == 'constellation':
h+= background
h+= 'set size square\n'
h+= 'set xrange [-1:1]\n'
h+= 'set yrange [-1:1]\n'
h += 'unset border\n'
h += 'set polar\n'
h += 'set angles degrees\n'
h += 'unset raxis\n'
h += 'set object circle at 0,0 size 1 fillcolor rgb 0x0f01 fillstyle solid behind\n'
h += 'set style line 10 lt 1 lc rgb 0x404040 lw 0.1\n'
h += 'set grid polar 45\n'
h += 'set grid ls 10\n'
h += 'set xtics axis\n'
h += 'set ytics axis\n'
h += 'set xtics scale 0\n'
h += 'set xtics ("" 0.2, "" 0.4, "" 0.6, "" 0.8, "" 1)\n'
h += 'set ytics 0, 0.2, 1\n'
h += 'set format ""\n'
h += 'set style line 11 lt 1 lw 2 pt 2 ps 2\n'
h+= 'set title "Constellation"\n'
elif mode == 'eye':
h+= background
h+= 'set yrange [-4:4]\n'
h+= 'set title "Datascope"\n'
elif mode == 'symbol':
h+= background
h+= 'set yrange [-4:4]\n'
elif mode == 'fft':
h+= 'set title "Symbol"\n'
elif mode == 'fft' or mode == 'mixer':
h+= 'unset arrow; unset title\n'
h+= 'set xrange [%f:%f]\n' % (self.freqs[0], self.freqs[len(self.freqs)-1])
h+= 'set yrange [-100:0]\n'
h+= 'set xlabel "Frequency"\n'
h+= 'set ylabel "Power(dB)"\n'
h+= 'set grid\n'
if self.center_freq:
arrow_pos = (self.center_freq - self.relative_freq) / 1e6
h+= 'set arrow from %f, graph 0 to %f, graph 1 nohead\n' % (arrow_pos, arrow_pos)
h+= 'set title "Tuned to %f Mhz"\n' % ((self.center_freq - self.relative_freq) / 1e6)
dat = '%splot %s\n%s' % (h, ','.join(plots), s)
self.gp.stdin.write(dat)
h+= 'set yrange [-100:0]\n'
if mode == 'mixer': # mixer
h+= 'set title "Mixer: balance %3.0f (smaller is better)"\n' % (np.abs(self.avg_sum_pwr * 1000))
else: # fft
h+= 'set title "Spectrum"\n'
if self.center_freq:
arrow_pos = (self.center_freq - self.relative_freq) / 1e6
h+= 'set arrow from %f, graph 0 to %f, graph 1 nohead\n' % (arrow_pos, arrow_pos)
h+= 'set title "Spectrum: tuned to %f Mhz"\n' % arrow_pos
elif mode == 'float':
h+= 'set yrange [-2:2]\n'
h+= 'set title "Oscilloscope"\n'
dat = '%s%splot %s\n%s' % (h0, h, ','.join(plots), s)
if self.logfile is not None:
with open(self.logfile, 'a') as fd:
fd.write(dat)
self.gp.poll()
if self.gp.returncode is None: # make sure gnuplot is still running
try:
self.gp.stdin.write(dat)
except (IOError, ValueError):
pass
if filename:
self.filename = filename
return consumed
def set_center_freq(self, f):
@ -146,9 +270,15 @@ class wrap_gp(object):
def set_relative_freq(self, f):
self.relative_freq = f
def set_offset(self, f):
self.offset_freq = f
def set_width(self, w):
self.width = w
def set_logfile(self, logfile=None):
self.logfile = logfile
class eye_sink_f(gr.sync_block):
"""
"""
@ -202,7 +332,7 @@ class fft_sink_c(gr.sync_block):
def work(self, input_items, output_items):
self.skip += 1
if self.skip == 50:
if self.skip >= 50:
self.skip = 0
in0 = input_items[0]
self.gnuplot.plot(in0, FFT_BINS, mode='fft')
@ -218,9 +348,35 @@ class fft_sink_c(gr.sync_block):
def set_relative_freq(self, f):
self.gnuplot.set_relative_freq(f)
def set_offset(self, f):
self.gnuplot.set_offset(f)
def set_width(self, w):
self.gnuplot.set_width(w)
class mixer_sink_c(gr.sync_block):
"""
"""
def __init__(self, debug = _def_debug):
gr.sync_block.__init__(self,
name="mixer_sink_c",
in_sig=[np.complex64],
out_sig=None)
self.debug = debug
self.gnuplot = wrap_gp()
self.skip = 0
def work(self, input_items, output_items):
self.skip += 1
if self.skip >= 10:
self.skip = 0
in0 = input_items[0]
self.gnuplot.plot(in0, FFT_BINS, mode='mixer')
return len(input_items[0])
def kill(self):
self.gnuplot.kill()
class symbol_sink_f(gr.sync_block):
"""
"""
@ -239,3 +395,22 @@ class symbol_sink_f(gr.sync_block):
def kill(self):
self.gnuplot.kill()
class float_sink_f(gr.sync_block):
"""
"""
def __init__(self, debug = _def_debug):
gr.sync_block.__init__(self,
name="float_sink_f",
in_sig=[np.float32],
out_sig=None)
self.debug = debug
self.gnuplot = wrap_gp()
def work(self, input_items, output_items):
in0 = input_items[0]
self.gnuplot.plot(in0, 2000, mode='float')
return len(input_items[0])
def kill(self):
self.gnuplot.kill()

View File

@ -0,0 +1,446 @@
#! /usr/bin/python
# Copyright 2017, 2018 Max H. Parke KA1RBI
#
# This file is part of OP25
#
# OP25 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, or (at your option)
# any later version.
#
# OP25 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 OP25; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
# 02110-1301, USA.
import sys
import os
import time
import re
import json
import socket
import traceback
import threading
import glob
import subprocess
import zmq
from gnuradio import gr
from waitress.server import create_server
from optparse import OptionParser
from multi_rx import byteify
from tsvfile import load_tsv, make_config
my_input_q = None
my_output_q = None
my_recv_q = None
my_port = None
my_backend = None
CFG_DIR = '../www/config/'
TSV_DIR = './'
"""
fake http and ajax server module
TODO: make less fake
"""
def static_file(environ, start_response):
content_types = { 'png': 'image/png', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'gif': 'image/gif', 'css': 'text/css', 'js': 'application/javascript', 'html': 'text/html'}
img_types = 'png jpg jpeg gif'.split()
if environ['PATH_INFO'] == '/':
filename = 'index.html'
else:
filename = re.sub(r'[^a-zA-Z0-9_.\-]', '', environ['PATH_INFO'])
suf = filename.split('.')[-1]
pathname = '../www/www-static'
if suf in img_types:
pathname = '../www/images'
pathname = '%s/%s' % (pathname, filename)
if suf not in content_types.keys() or '..' in filename or not os.access(pathname, os.R_OK):
sys.stderr.write('404 %s\n' % pathname)
status = '404 NOT FOUND'
content_type = 'text/plain'
output = status
else:
output = open(pathname).read()
content_type = content_types[suf]
status = '200 OK'
return status, content_type, output
def valid_tsv(filename):
if not os.access(filename, os.R_OK):
return False
line = open(filename).readline()
for word in 'Sysname Offset NAC Modulation TGID Whitelist Blacklist'.split():
if word not in line:
return False
return True
def tsv_config(filename):
DEFAULT_CFG = '../www/config/default.json'
filename = '%s%s' % (TSV_DIR, filename)
filename = filename.replace('[TSV]', '.tsv')
if not valid_tsv(filename):
return None
cfg = make_config(load_tsv(filename))
default_cfg = json.loads(open(DEFAULT_CFG).read())
result = default_cfg
channels = [ {'active': True,
'blacklist': cfg[nac]['blacklist'],
'whitelist': cfg[nac]['whitelist'],
'cclist': cfg[nac]['cclist'],
'demod_type': 'cqpsk',
'destination': 'udp://127.0.0.1:23456',
'filter_type': 'rc',
'frequency': 500000000,
'if_rate': 24000,
'nac': nac,
'name': cfg[nac]['sysname'],
'phase2_tdma': False,
'plot': "",
'tgids': cfg[nac]['tgid_map'],
'trunked': True
}
for nac in cfg.keys() ]
result['channels'] = channels
return {'json_type':'config_data', 'data': result}
def do_request(d):
global my_backend
if d['command'].startswith('rx-'):
msg = gr.message().make_from_string(json.dumps(d), -2, 0, 0)
if not my_backend.input_q.full_p():
my_backend.input_q.insert_tail(msg)
return None
elif d['command'] == 'config-load':
if '[TSV]' in d['data']:
return tsv_config(d['data'])
filename = '%s%s.json' % (CFG_DIR, d['data'])
if not os.access(filename, os.R_OK):
return None
js_msg = json.loads(open(filename).read())
return {'json_type':'config_data', 'data': js_msg}
elif d['command'] == 'config-list':
files = glob.glob('%s*.json' % CFG_DIR)
files = [x.replace('.json', '') for x in files]
files = [x.replace(CFG_DIR, '') for x in files]
if d['data'] == 'tsv':
tsvfiles = glob.glob('%s*.tsv' % TSV_DIR)
tsvfiles = [x for x in tsvfiles if valid_tsv(x)]
tsvfiles = [x.replace('.tsv', '[TSV]') for x in tsvfiles]
tsvfiles = [x.replace(TSV_DIR, '') for x in tsvfiles]
files += tsvfiles
return {'json_type':'config_list', 'data': files}
elif d['command'] == 'config-save':
name = d['data']['name']
if '..' in name or '.json' in name or '/' in name:
return None
filename = '%s%s.json' % (CFG_DIR, d['data']['name'])
open(filename, 'w').write(json.dumps(d['data']['value'], indent=4, separators=[',',':'], sort_keys=True))
return None
def post_req(environ, start_response, postdata):
global my_input_q, my_output_q, my_recv_q, my_port
resp_msg = []
try:
data = json.loads(postdata)
except:
sys.stderr.write('post_req: error processing input: %s:\n' % (postdata))
traceback.print_exc(limit=None, file=sys.stderr)
sys.stderr.write('*** end traceback ***\n')
for d in data:
if d['command'].startswith('config-') or d['command'].startswith('rx-'):
resp = do_request(d)
if resp:
resp_msg.append(resp)
continue
if d['command'].startswith('settings-'):
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
else:
msg = gr.message().make_from_string(str(d['command']), -2, d['data'], 0)
if my_output_q.full_p():
my_output_q.delete_head_nowait() # ignores result
if not my_output_q.full_p():
my_output_q.insert_tail(msg)
time.sleep(0.2)
while not my_recv_q.empty_p():
msg = my_recv_q.delete_head()
if msg.type() == -4:
resp_msg.append(json.loads(msg.to_string()))
status = '200 OK'
content_type = 'application/json'
output = json.dumps(resp_msg)
return status, content_type, output
def http_request(environ, start_response):
if environ['REQUEST_METHOD'] == 'GET':
status, content_type, output = static_file(environ, start_response)
elif environ['REQUEST_METHOD'] == 'POST':
postdata = environ['wsgi.input'].read()
status, content_type, output = post_req(environ, start_response, postdata)
else:
status = '200 OK'
content_type = 'text/plain'
output = status
sys.stderr.write('http_request: unexpected input %s\n' % environ['PATH_INFO'])
response_headers = [('Content-type', content_type),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]
def application(environ, start_response):
failed = False
try:
result = http_request(environ, start_response)
except:
failed = True
sys.stderr.write('application: request failed:\n%s\n' % traceback.format_exc())
sys.exit(1)
return result
def process_qmsg(msg):
if my_recv_q.full_p():
my_recv_q.delete_head_nowait() # ignores result
if my_recv_q.full_p():
return
my_recv_q.insert_tail(msg)
class http_server(object):
def __init__(self, input_q, output_q, endpoint, **kwds):
global my_input_q, my_output_q, my_recv_q, my_port
host, port = endpoint.split(':')
if my_port is not None:
raise AssertionError('this server is already active on port %s' % my_port)
my_input_q = input_q
my_output_q = output_q
my_port = int(port)
my_recv_q = gr.msg_queue(10)
self.q_watcher = queue_watcher(my_input_q, process_qmsg)
self.server = create_server(application, host=host, port=my_port)
def run(self):
self.server.run()
class queue_watcher(threading.Thread):
def __init__(self, msgq, callback, **kwds):
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)
self.msgq = msgq
self.callback = callback
self.keep_running = True
self.start()
def run(self):
while(self.keep_running):
msg = self.msgq.delete_head()
self.callback(msg)
class Backend(threading.Thread):
def __init__(self, options, input_q, output_q, init_config=None, **kwds):
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)
self.keep_running = True
self.rx_options = None
self.input_q = input_q
self.output_q = output_q
self.verbosity = options.verbosity
self.zmq_context = zmq.Context()
self.zmq_port = options.zmq_port
self.zmq_sub = self.zmq_context.socket(zmq.SUB)
self.zmq_sub.connect('tcp://localhost:%d' % self.zmq_port)
self.zmq_sub.setsockopt(zmq.SUBSCRIBE, '')
self.zmq_pub = self.zmq_context.socket(zmq.PUB)
self.zmq_pub.sndhwm = 5
self.zmq_pub.bind('tcp://*:%d' % (self.zmq_port+1))
self.start()
self.subproc = None
self.msg = None
self.q_watcher = queue_watcher(self.input_q, self.process_msg)
if init_config:
d = {'command': 'rx-start', 'data': init_config}
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
self.input_q.insert_tail(msg)
def publish(self, msg):
t = msg.type()
s = msg.to_string()
a = msg.arg1()
self.zmq_pub.send(json.dumps({'command': s, 'data': a, 'msgtype': t}))
def check_subproc(self): # return True if subprocess is active
if not self.subproc:
return False
rc = self.subproc.poll()
if rc is None:
return True
else:
self.subproc.wait()
self.subproc = None
return False
def process_msg(self, msg):
def make_command(options, config_file):
trunked_ct = [True for x in options._js_config['channels'] if x['trunked']]
total_ct = [True for x in options._js_config['channels']]
if trunked_ct and len(trunked_ct) != len(total_ct):
self.msg = 'no suitable backend found for this configuration'
return None
if not trunked_ct:
self.backend = '%s/%s' % (os.getcwd(), 'multi_rx.py')
opts = [self.backend]
filename = '%s%s.json' % (CFG_DIR, config_file)
opts.append('--config-file')
opts.append(filename)
return opts
types = {'costas-alpha': 'float', 'trunk-conf-file': 'str', 'demod-type': 'str', 'logfile-workers': 'int', 'decim-amt': 'int', 'wireshark-host': 'str', 'gain-mu': 'float', 'phase2-tdma': 'bool', 'seek': 'int', 'ifile': 'str', 'pause': 'bool', 'antenna': 'str', 'calibration': 'float', 'fine-tune': 'float', 'raw-symbols': 'str', 'audio-output': 'str', 'vocoder': 'bool', 'input': 'str', 'wireshark': 'bool', 'gains': 'str', 'args': 'str', 'sample-rate': 'int', 'terminal-type': 'str', 'gain': 'float', 'excess-bw': 'float', 'offset': 'float', 'audio-input': 'str', 'audio': 'bool', 'plot-mode': 'str', 'audio-if': 'bool', 'tone-detect': 'bool', 'frequency': 'int', 'freq-corr': 'float', 'hamlib-model': 'int', 'udp-player': 'bool', 'verbosity': 'int'}
self.backend = '%s/%s' % (os.getcwd(), 'rx.py')
opts = [self.backend]
for k in [ x for x in dir(options) if not x.startswith('_') ]:
kw = k.replace('_', '-')
val = getattr(options, k)
if kw not in types.keys():
self.msg = 'make_command: unknown option: %s %s type %s' % (k, val, type(val))
return None
elif types[kw] == 'str':
if val:
opts.append('--%s' % kw)
opts.append('%s' % (val))
elif types[kw] == 'float':
opts.append('--%s' % kw)
if val:
opts.append('%f' % (val))
else:
opts.append('%f' % (0))
elif types[kw] == 'int':
opts.append('--%s' % kw)
if val:
opts.append('%d' % (val))
else:
opts.append('%d' % (0))
elif types[kw] == 'bool':
if val:
opts.append('--%s' % kw)
else:
self.msg = 'make_command: unknown2 option: %s %s type %s' % (k, val, type(val))
return None
return opts
msg = json.loads(msg.to_string())
if msg['command'] == 'rx-start':
if self.check_subproc():
self.msg = 'start command failed: subprocess pid %d already active' % self.subproc.pid
return
options = rx_options(msg['data'])
if getattr(options, '_js_config', None) is None:
self.msg = 'start command failed: rx_options: unable to initialize config=%s' % (msg['data'])
return
options.verbosity = self.verbosity
options.terminal_type = 'zmq:tcp:%d' % (self.zmq_port)
cmd = make_command(options, msg['data'])
if cmd:
self.subproc = subprocess.Popen(cmd)
elif msg['command'] == 'rx-stop':
if not self.check_subproc():
self.msg = 'stop command failed: subprocess not active'
return
if msg['data'] == 'kill':
self.subproc.kill()
else:
self.subproc.terminate()
elif msg['command'] == 'rx-state':
d = {}
if self.check_subproc():
d['rx-state'] = 'subprocess pid %d active' % self.subproc.pid
else:
d['rx-state'] = 'subprocess not active, last msg: %s' % self.msg
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
if not self.output_q.full_p():
self.output_q.insert_tail(msg)
def run(self):
while self.keep_running:
js = self.zmq_sub.recv()
if not self.keep_running:
break
msg = gr.message().make_from_string(js, -4, 0, 0)
if not self.output_q.full_p():
self.output_q.insert_tail(msg)
class rx_options(object):
def __init__(self, name):
def map_name(k):
return k.replace('-', '_')
filename = '%s%s.json' % (CFG_DIR, name)
if not os.access(filename, os.R_OK):
sys.stderr.write('unable to access config file %s\n' % (filename))
return
config = byteify(json.loads(open(filename).read()))
dev = [x for x in config['devices'] if x['active']][0]
if not dev:
return
chan = [x for x in config['channels'] if x['active']][0]
if not chan:
return
options = object()
for k in config['backend-rx'].keys():
setattr(self, map_name(k), config['backend-rx'][k])
for k in 'args frequency gains offset'.split():
setattr(self, k, dev[k])
self.demod_type = chan['demod_type']
self.freq_corr = dev['ppm']
self.sample_rate = dev['rate']
self.plot_mode = chan['plot']
self.phase2_tdma = chan['phase2_tdma']
self.trunk_conf_file = filename
self._js_config = config
def http_main():
global my_backend
# command line argument parsing
parser = OptionParser()
parser.add_option("-c", "--config", type="string", default=None, help="config json name, without prefix/suffix")
parser.add_option("-e", "--endpoint", type="string", default="127.0.0.1:8080", help="address:port to listen on (use addr 0.0.0.0 to enable external clients)")
parser.add_option("-v", "--verbosity", type="int", default=0, help="message debug level")
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
parser.add_option("-z", "--zmq-port", type="int", default=25000, help="backend sub port")
(options, args) = parser.parse_args()
# wait for gdb
if options.pause:
print 'Ready for GDB to attach (pid = %d)' % (os.getpid(),)
raw_input("Press 'Enter' to continue...")
input_q = gr.msg_queue(20)
output_q = gr.msg_queue(20)
backend_input_q = gr.msg_queue(20)
backend_output_q = gr.msg_queue(20)
my_backend = Backend(options, backend_input_q, backend_output_q, init_config=options.config)
server = http_server(input_q, output_q, options.endpoint)
q_watcher = queue_watcher(output_q, lambda msg : my_backend.publish(msg))
backend_q_watcher = queue_watcher(backend_output_q, lambda msg : process_qmsg(msg))
server.run()
if __name__ == '__main__':
http_main()

View File

@ -0,0 +1,260 @@
#!/usr/bin/env python
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
#
# This file is part of OP25
#
# OP25 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, or (at your option)
# any later version.
#
# OP25 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 OP25; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
# 02110-1301, USA.
import os
import sys
import threading
import time
import json
import traceback
import osmosdr
from gnuradio import audio, eng_notation, gr, gru, filter, blocks, fft, analog, digital
from gnuradio.eng_option import eng_option
from math import pi
from optparse import OptionParser
import op25
import op25_repeater
import p25_demodulator
import p25_decoder
from gr_gnuplot import constellation_sink_c
from gr_gnuplot import fft_sink_c
from gr_gnuplot import mixer_sink_c
from gr_gnuplot import symbol_sink_f
from gr_gnuplot import eye_sink_f
os.environ['IMBE'] = 'soft'
_def_symbol_rate = 4800
# The P25 receiver
#
def byteify(input): # thx so
if isinstance(input, dict):
return {byteify(key): byteify(value)
for key, value in input.iteritems()}
elif isinstance(input, list):
return [byteify(element) for element in input]
elif isinstance(input, unicode):
return input.encode('utf-8')
else:
return input
class device(object):
def __init__(self, config, tb):
self.name = config['name']
self.sample_rate = config['rate']
self.args = config['args']
self.tb = tb
if config['args'].startswith('audio:'):
self.init_audio(config)
elif config['args'].startswith('file:'):
self.init_file(config)
else:
self.init_osmosdr(config)
def init_file(self, config):
filename = config['args'].replace('file:', '', 1)
src = blocks.file_source(gr.sizeof_gr_complex, filename, repeat = False)
throttle = blocks.throttle(gr.sizeof_gr_complex, config['rate'])
self.tb.connect(src, throttle)
self.src = throttle
self.frequency = config['frequency']
self.offset = config['offset']
def init_audio(self, config):
filename = config['args'].replace('audio:', '')
src = audio.source(self.sample_rate, filename)
gain = 1.0
if config['gains'].startswith('audio:'):
gain = float(config['gains'].replace('audio:', ''))
self.src = blocks.multiply_const_ff(gain)
self.tb.connect(src, self.src)
def init_osmosdr(self, config):
speeds = [250000, 1000000, 1024000, 1800000, 1920000, 2000000, 2048000, 2400000, 2560000]
sys.stderr.write('device: %s\n' % config)
if config['args'].startswith('rtl') and config['rate'] not in speeds:
sys.stderr.write('WARNING: requested sample rate %d for device %s may not\n' % (config['rate'], config['name']))
sys.stderr.write("be optimal. You may want to use one of the following rates\n")
sys.stderr.write('%s\n' % speeds)
self.src = osmosdr.source(config['args'])
for tup in config['gains'].split(','):
name, gain = tup.split(':')
self.src.set_gain(int(gain), name)
self.src.set_freq_corr(config['ppm'])
self.ppm = config['ppm']
self.src.set_sample_rate(config['rate'])
self.src.set_center_freq(config['frequency'])
self.frequency = config['frequency']
self.offset = config['offset']
class channel(object):
def __init__(self, config, dev, verbosity):
sys.stderr.write('channel (dev %s): %s\n' % (dev.name, config))
self.device = dev
self.name = config['name']
self.symbol_rate = _def_symbol_rate
if 'symbol_rate' in config.keys():
self.symbol_rate = config['symbol_rate']
self.config = config
if dev.args.startswith('audio:'):
self.demod = p25_demodulator.p25_demod_fb(
input_rate = dev.sample_rate,
filter_type = config['filter_type'],
symbol_rate = self.symbol_rate)
else:
self.demod = p25_demodulator.p25_demod_cb(
input_rate = dev.sample_rate,
demod_type = config['demod_type'],
filter_type = config['filter_type'],
excess_bw = config['excess_bw'],
relative_freq = dev.frequency + dev.offset - config['frequency'],
offset = dev.offset,
if_rate = config['if_rate'],
symbol_rate = self.symbol_rate)
q = gr.msg_queue(1)
self.decoder = op25_repeater.frame_assembler(config['destination'], verbosity, q)
self.kill_sink = []
if 'plot' not in config.keys():
return
self.sinks = []
for plot in config['plot'].split(','):
if plot == 'datascope':
assert config['demod_type'] == 'fsk4' ## datascope plot requires fsk4 demod type
sink = eye_sink_f(sps=config['if_rate'] / self.symbol_rate)
self.demod.connect_bb('symbol_filter', sink)
self.kill_sink.append(sink)
elif plot == 'symbol':
sink = symbol_sink_f()
self.demod.connect_float(sink)
self.kill_sink.append(sink)
elif plot == 'fft':
assert config['demod_type'] == 'cqpsk' ## fft plot requires cqpsk demod type
i = len(self.sinks)
self.sinks.append(fft_sink_c())
self.demod.connect_complex('src', self.sinks[i])
self.kill_sink.append(self.sinks[i])
elif plot == 'mixer':
assert config['demod_type'] == 'cqpsk' ## mixer plot requires cqpsk demod type
i = len(self.sinks)
self.sinks.append(mixer_sink_c())
self.demod.connect_complex('mixer', self.sinks[i])
self.kill_sink.append(self.sinks[i])
elif plot == 'constellation':
i = len(self.sinks)
assert config['demod_type'] == 'cqpsk' ## constellation plot requires cqpsk demod type
self.sinks.append(constellation_sink_c())
self.demod.connect_complex('diffdec', self.sinks[i])
self.kill_sink.append(self.sinks[i])
else:
sys.stderr.write('unrecognized plot type %s\n' % plot)
return
class rx_block (gr.top_block):
# Initialize the receiver
#
def __init__(self, verbosity, config):
self.verbosity = verbosity
gr.top_block.__init__(self)
self.device_id_by_name = {}
self.configure_devices(config['devices'])
self.configure_channels(config['channels'])
def configure_devices(self, config):
self.devices = []
for cfg in config:
self.device_id_by_name[cfg['name']] = len(self.devices)
self.devices.append(device(cfg, self))
def find_device(self, chan):
for dev in self.devices:
if dev.args.startswith('audio:') and chan['demod_type'] == 'fsk4':
return dev
d = abs(chan['frequency'] - dev.frequency)
nf = dev.sample_rate / 2
if d + 6250 <= nf:
return dev
return None
def configure_channels(self, config):
self.channels = []
for cfg in config:
dev = self.find_device(cfg)
if dev is None:
sys.stderr.write('* * * Frequency %d not within spectrum band of any device - ignoring!\n' % cfg['frequency'])
continue
chan = channel(cfg, dev, self.verbosity)
self.channels.append(chan)
self.connect(dev.src, chan.demod, chan.decoder)
def scan_channels(self):
for chan in self.channels:
sys.stderr.write('scan %s: error %d\n' % (chan.config['frequency'], chan.demod.get_freq_error()))
class rx_main(object):
def __init__(self):
self.keep_running = True
# command line argument parsing
parser = OptionParser(option_class=eng_option)
parser.add_option("-c", "--config-file", type="string", default=None, help="specify config file name")
parser.add_option("-v", "--verbosity", type="int", default=0, help="message debug level")
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
(options, args) = parser.parse_args()
# wait for gdb
if options.pause:
print 'Ready for GDB to attach (pid = %d)' % (os.getpid(),)
raw_input("Press 'Enter' to continue...")
if options.config_file == '-':
config = json.loads(sys.stdin.read())
else:
config = json.loads(open(options.config_file).read())
self.tb = rx_block(options.verbosity, config = byteify(config))
def run(self):
try:
self.tb.start()
while self.keep_running:
time.sleep(1)
except:
sys.stderr.write('main: exception occurred\n')
sys.stderr.write('main: exception:\n%s\n' % traceback.format_exc())
if __name__ == "__main__":
rx = rx_main()
rx.run()

View File

@ -77,9 +77,16 @@ class p25_decoder_sink_b(gr.hier_block2):
self.debug = debug
self.dest = dest
do_output = False
do_audio_output = False
do_phase2_tdma = False
if dest == 'wav':
do_output = True
do_audio_output = True
if do_imbe:
do_audio_output = True
if num_ambe > 0:
do_phase2_tdma = True
if msgq is None:
msgq = gr.msg_queue(1)
@ -93,7 +100,7 @@ class p25_decoder_sink_b(gr.hier_block2):
if num_ambe > 1:
num_decoders += num_ambe - 1
for slot in xrange(num_decoders):
self.p25_decoders.append(op25_repeater.p25_frame_assembler(wireshark_host, udp_port, debug, do_imbe, do_output, do_msgq, msgq, do_audio_output, True))
self.p25_decoders.append(op25_repeater.p25_frame_assembler(wireshark_host, udp_port, debug, do_imbe, do_output, do_msgq, msgq, do_audio_output, do_phase2_tdma))
self.p25_decoders[slot].set_slotid(slot)
self.xorhash.append('')
@ -123,6 +130,9 @@ class p25_decoder_sink_b(gr.hier_block2):
return
self.audio_sink[index].open(filename)
def set_nac(self, nac, index=0):
self.p25_decoders[index].set_nac(nac)
def set_xormask(self, xormask, xorhash, index=0):
if self.xorhash[index] == xorhash:
return

View File

@ -45,14 +45,40 @@ _def_costas_alpha = 0.04
_def_symbol_rate = 4800
_def_symbol_deviation = 600.0
_def_bb_gain = 1.0
_def_excess_bw = 0.2
_def_gmsk_mu = None
_def_mu = 0.5
_def_freq_error = 0.0
_def_omega_relative_limit = 0.005
# /////////////////////////////////////////////////////////////////////////////
# demodulator
# /////////////////////////////////////////////////////////////////////////////
def get_decim(speed):
s = int(speed)
if_freqs = [24000, 25000, 32000]
for i_f in if_freqs:
if s % i_f != 0:
continue
q = s / i_f
if q & 1:
continue
if q >= 40 and q & 3 == 0:
decim = q/4
decim2 = 4
else:
decim = q/2
decim2 = 2
return decim, decim2
return None
class p25_demod_base(gr.hier_block2):
def __init__(self,
if_rate = None,
filter_type = None,
excess_bw = _def_excess_bw,
symbol_rate = _def_symbol_rate):
"""
Hierarchical block for P25 demodulation base class
@ -66,12 +92,49 @@ class p25_demod_base(gr.hier_block2):
self.baseband_amp = blocks.multiply_const_ff(_def_bb_gain)
coeffs = op25_c4fm_mod.c4fm_taps(sample_rate=self.if_rate, span=9, generator=op25_c4fm_mod.transfer_function_rx).generate()
self.symbol_filter = filter.fir_filter_fff(1, coeffs)
autotuneq = gr.msg_queue(2)
self.fsk4_demod = op25.fsk4_demod_ff(autotuneq, self.if_rate, self.symbol_rate)
sps = self.if_rate / 4800
if filter_type == 'rrc':
ntaps = 7 * sps
if ntaps & 1 == 0:
ntaps += 1
coeffs = filter.firdes.root_raised_cosine(1.0, if_rate, symbol_rate, excess_bw, ntaps)
if filter_type == 'nxdn':
coeffs = op25_c4fm_mod.c4fm_taps(sample_rate=self.if_rate, span=9, generator=op25_c4fm_mod.transfer_function_nxdn).generate()
if filter_type == 'gmsk':
# lifted from gmsk.py
_omega = sps
_gain_mu = _def_gmsk_mu
_mu = _def_mu
if not _gain_mu:
_gain_mu = 0.175
_gain_omega = .25 * _gain_mu * _gain_mu # critically damped
self.symbol_filter = blocks.multiply_const_ff(1.0)
self.fsk4_demod = digital.clock_recovery_mm_ff(_omega, _gain_omega,
_mu, _gain_mu,
_def_omega_relative_limit)
self.slicer = digital.binary_slicer_fb()
elif filter_type == 'fsk4mm':
self.symbol_filter = filter.fir_filter_fff(1, coeffs)
_omega = sps
_gain_mu = _def_gmsk_mu
_mu = _def_mu
if not _gain_mu:
_gain_mu = 0.0175
_gain_omega = .25 * _gain_mu * _gain_mu # critically damped
self.fsk4_demod = digital.clock_recovery_mm_ff(_omega, _gain_omega,
_mu, _gain_mu,
_def_omega_relative_limit)
levels = [ -2.0, 0.0, 2.0, 4.0 ]
self.slicer = op25_repeater.fsk4_slicer_fb(levels)
else:
self.symbol_filter = filter.fir_filter_fff(1, coeffs)
autotuneq = gr.msg_queue(2)
self.fsk4_demod = op25.fsk4_demod_ff(autotuneq, self.if_rate, self.symbol_rate)
levels = [ -2.0, 0.0, 2.0, 4.0 ]
self.slicer = op25_repeater.fsk4_slicer_fb(levels)
levels = [ -2.0, 0.0, 2.0, 4.0 ]
self.slicer = op25_repeater.fsk4_slicer_fb(levels)
def set_symbol_rate(self, rate):
self.symbol_rate = rate
def set_baseband_gain(self, k):
self.baseband_amp.set_k(k)
@ -97,6 +160,8 @@ class p25_demod_fb(p25_demod_base):
def __init__(self,
input_rate = None,
filter_type = None,
excess_bw = _def_excess_bw,
symbol_rate = _def_symbol_rate):
"""
Hierarchical block for P25 demodulation.
@ -110,7 +175,7 @@ class p25_demod_fb(p25_demod_base):
gr.io_signature(1, 1, gr.sizeof_float), # Input signature
gr.io_signature(1, 1, gr.sizeof_char)) # Output signature
p25_demod_base.__init__(self, if_rate=input_rate, symbol_rate=symbol_rate)
p25_demod_base.__init__(self, if_rate=input_rate, symbol_rate=symbol_rate, filter_type=filter_type)
self.input_rate = input_rate
self.float_sink = None
@ -135,6 +200,8 @@ class p25_demod_cb(p25_demod_base):
def __init__(self,
input_rate = None,
demod_type = 'cqpsk',
filter_type = None,
excess_bw = _def_excess_bw,
relative_freq = 0,
offset = 0,
if_rate = _def_if_rate,
@ -153,7 +220,7 @@ class p25_demod_cb(p25_demod_base):
gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
gr.io_signature(1, 1, gr.sizeof_char)) # Output signature
# gr.io_signature(0, 0, 0)) # Output signature
p25_demod_base.__init__(self, if_rate=if_rate, symbol_rate=symbol_rate)
p25_demod_base.__init__(self, if_rate=if_rate, symbol_rate=symbol_rate, filter_type=filter_type)
self.input_rate = input_rate
self.if_rate = if_rate
@ -164,25 +231,52 @@ class p25_demod_cb(p25_demod_base):
self.lo_freq = 0
self.float_sink = None
self.complex_sink = None
self.if1 = 0
self.if2 = 0
self.t_cache = {}
if filter_type == 'rrc':
self.set_baseband_gain(0.61)
# local osc
self.lo = analog.sig_source_c (input_rate, analog.GR_SIN_WAVE, 0, 1.0, 0)
self.mixer = blocks.multiply_cc()
lpf_coeffs = filter.firdes.low_pass(1.0, input_rate, 7250, 725, filter.firdes.WIN_HANN)
decimation = int(input_rate / if_rate)
self.lpf = filter.fir_filter_ccf(decimation, lpf_coeffs)
decimator_values = get_decim(input_rate)
if decimator_values:
self.decim, self.decim2 = decimator_values
self.if1 = input_rate / self.decim
self.if2 = self.if1 / self.decim2
sys.stderr.write( 'Using two-stage decimator for speed=%d, decim=%d/%d if1=%d if2=%d\n' % (input_rate, self.decim, self.decim2, self.if1, self.if2))
bpf_coeffs = filter.firdes.complex_band_pass(1.0, input_rate, -self.if1/2, self.if1/2, self.if1/2, filter.firdes.WIN_HAMMING)
self.t_cache[0] = bpf_coeffs
fa = 6250
fb = self.if2 / 2
lpf_coeffs = filter.firdes.low_pass(1.0, self.if1, (fb+fa)/2, fb-fa, filter.firdes.WIN_HAMMING)
self.bpf = filter.fir_filter_ccc(self.decim, bpf_coeffs)
self.lpf = filter.fir_filter_ccf(self.decim2, lpf_coeffs)
resampled_rate = self.if2
self.bfo = analog.sig_source_c (self.if1, analog.GR_SIN_WAVE, 0, 1.0, 0)
self.connect(self, self.bpf, (self.mixer, 0))
self.connect(self.bfo, (self.mixer, 1))
else:
sys.stderr.write( 'Unable to use two-stage decimator for speed=%d\n' % (input_rate))
# local osc
self.lo = analog.sig_source_c (input_rate, analog.GR_SIN_WAVE, 0, 1.0, 0)
lpf_coeffs = filter.firdes.low_pass(1.0, input_rate, 7250, 1450, filter.firdes.WIN_HANN)
decimation = int(input_rate / if_rate)
self.lpf = filter.fir_filter_ccf(decimation, lpf_coeffs)
resampled_rate = float(input_rate) / float(decimation) # rate at output of self.lpf
self.connect(self, (self.mixer, 0))
self.connect(self.lo, (self.mixer, 1))
self.connect(self.mixer, self.lpf)
resampled_rate = float(input_rate) / float(decimation) # rate at output of self.lpf
if self.if_rate != resampled_rate:
self.if_out = filter.pfb.arb_resampler_ccf(float(self.if_rate) / resampled_rate)
self.connect(self.lpf, self.if_out)
else:
self.if_out = self.lpf
self.arb_resampler = filter.pfb.arb_resampler_ccf(
float(self.if_rate) / resampled_rate)
self.connect(self, (self.mixer, 0))
self.connect(self.lo, (self.mixer, 1))
self.connect(self.mixer, self.lpf, self.arb_resampler)
levels = [ -2.0, 0.0, 2.0, 4.0 ]
self.slicer = op25_repeater.fsk4_slicer_fb(levels)
fa = 6250
fb = fa + 625
cutoff_coeffs = filter.firdes.low_pass(1.0, self.if_rate, (fb+fa)/2, fb-fa, filter.firdes.WIN_HANN)
self.cutoff = filter.fir_filter_ccf(1, cutoff_coeffs)
omega = float(self.if_rate) / float(self.symbol_rate)
gain_omega = 0.1 * gain_mu * gain_mu
@ -214,6 +308,12 @@ class p25_demod_cb(p25_demod_base):
self.set_relative_frequency(relative_freq)
def get_error_band(self):
return int(self.clock.get_error_band())
def get_freq_error(self): # get error in Hz (approx).
return int(self.clock.get_freq_error() * self.symbol_rate)
def set_omega(self, omega):
sps = self.if_rate / float(omega)
if sps == self.sps:
@ -223,22 +323,33 @@ class p25_demod_cb(p25_demod_base):
self.clock.set_omega(self.sps)
def set_relative_frequency(self, freq):
if abs(freq) > self.input_rate/2:
if abs(freq) > ((self.input_rate / 2) - (self.if1 / 2)):
#print 'set_relative_frequency: error, relative frequency %d exceeds limit %d' % (freq, self.input_rate/2)
return False
if freq == self.lo_freq:
return True
#print 'set_relative_frequency', freq
self.lo_freq = freq
self.lo.set_frequency(self.lo_freq)
if self.if1:
if freq not in self.t_cache.keys():
self.t_cache[freq] = filter.firdes.complex_band_pass(1.0, self.input_rate, -freq - self.if1/2, -freq + self.if1/2, self.if1/2, filter.firdes.WIN_HAMMING)
self.bpf.set_taps(self.t_cache[freq])
bfo_f = self.decim * -freq / float(self.input_rate)
bfo_f -= int(bfo_f)
if bfo_f < -0.5:
bfo_f += 1.0
if bfo_f > 0.5:
bfo_f -= 1.0
self.bfo.set_frequency(-bfo_f * self.if1)
else:
self.lo.set_frequency(self.lo_freq)
return True
# assumes lock held or init
def disconnect_chain(self):
if self.connect_state == 'cqpsk':
self.disconnect(self.arb_resampler, self.agc, self.clock, self.diffdec, self.to_float, self.rescale, self.slicer)
self.disconnect(self.if_out, self.cutoff, self.agc, self.clock, self.diffdec, self.to_float, self.rescale, self.slicer)
elif self.connect_state == 'fsk4':
self.disconnect(self.arb_resampler, self.fm_demod, self.baseband_amp, self.symbol_filter, self.fsk4_demod, self.slicer)
self.disconnect(self.if_out, self.cutoff, self.fm_demod, self.baseband_amp, self.symbol_filter, self.fsk4_demod, self.slicer)
self.connect_state = None
# assumes lock held or init
@ -248,9 +359,9 @@ class p25_demod_cb(p25_demod_base):
self.disconnect_chain()
self.connect_state = demod_type
if demod_type == 'fsk4':
self.connect(self.arb_resampler, self.fm_demod, self.baseband_amp, self.symbol_filter, self.fsk4_demod, self.slicer)
self.connect(self.if_out, self.cutoff, self.fm_demod, self.baseband_amp, self.symbol_filter, self.fsk4_demod, self.slicer)
elif demod_type == 'cqpsk':
self.connect(self.arb_resampler, self.agc, self.clock, self.diffdec, self.to_float, self.rescale, self.slicer)
self.connect(self.if_out, self.cutoff, self.agc, self.clock, self.diffdec, self.to_float, self.rescale, self.slicer)
else:
print 'connect_chain failed, type: %s' % demod_type
assert 0 == 1
@ -277,25 +388,19 @@ class p25_demod_cb(p25_demod_base):
print 'connect_float: state error', self.connect_state
assert 0 == 1
def disconnect_complex(self):
# assumes lock held or init
if not self.complex_sink:
return
self.disconnect(self.complex_sink[0], self.complex_sink[1])
self.complex_sink = None
def connect_complex(self, src, sink):
# assumes lock held or init
self.disconnect_complex()
if src == 'clock':
self.connect(self.clock, sink)
self.complex_sink = [self.clock, sink]
elif src == 'diffdec':
self.connect(self.diffdec, sink)
self.complex_sink = [self.diffdec, sink]
elif src == 'mixer':
self.connect(self.mixer, sink)
self.complex_sink = [self.mixer, sink]
self.connect(self.agc, sink)
elif src == 'src':
self.connect(self, sink)
self.complex_sink = [self, sink]
elif src == 'bpf':
self.connect(self.bpf, sink)
elif src == 'if_out':
self.connect(self.if_out, sink)
elif src == 'agc':
self.connect(self.agc, sink)

View File

@ -2,7 +2,7 @@
# Copyright 2008-2011 Steve Glass
#
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Max H. Parke KA1RBI
#
# Copyright 2003,2004,2005,2006 Free Software Foundation, Inc.
# (from radiorausch)
@ -64,8 +64,9 @@ from gr_gnuplot import constellation_sink_c
from gr_gnuplot import fft_sink_c
from gr_gnuplot import symbol_sink_f
from gr_gnuplot import eye_sink_f
from gr_gnuplot import mixer_sink_c
from terminal import curses_terminal
from terminal import op25_terminal
from sockaudio import socket_audio
#speeds = [300, 600, 900, 1200, 1440, 1800, 1920, 2400, 2880, 3200, 3600, 3840, 4000, 4800, 6000, 6400, 7200, 8000, 9600, 14400, 19200]
@ -75,70 +76,39 @@ os.environ['IMBE'] = 'soft'
WIRESHARK_PORT = 23456
_def_interval = 3.0 # sec
_def_file_dir = '../www/images'
# The P25 receiver
#
class p25_rx_block (gr.top_block):
# Initialize the P25 receiver
#
def __init__(self):
def __init__(self, options):
self.trunk_rx = None
self.kill_sink = None
self.plot_sinks = []
gr.top_block.__init__(self)
# command line argument parsing
parser = OptionParser(option_class=eng_option)
parser.add_option("--args", type="string", default="", help="device args")
parser.add_option("--antenna", type="string", default="", help="select antenna")
parser.add_option("-a", "--audio", action="store_true", default=False, help="use direct audio input")
parser.add_option("-A", "--audio-if", action="store_true", default=False, help="soundcard IF mode (use --calibration to set IF freq)")
parser.add_option("-I", "--audio-input", type="string", default="", help="pcm input device name. E.g., hw:0,0 or /dev/dsp")
parser.add_option("-i", "--input", default=None, help="input file name")
parser.add_option("-b", "--excess-bw", type="eng_float", default=0.2, help="for RRC filter", metavar="Hz")
parser.add_option("-c", "--calibration", type="eng_float", default=0.0, help="USRP offset or audio IF frequency", metavar="Hz")
parser.add_option("-C", "--costas-alpha", type="eng_float", default=0.04, help="value of alpha for Costas loop", metavar="Hz")
parser.add_option("-D", "--demod-type", type="choice", default="cqpsk", choices=('cqpsk', 'fsk4'), help="cqpsk | fsk4")
parser.add_option("-P", "--plot-mode", type="choice", default=None, choices=(None, 'constellation', 'fft', 'symbol', 'datascope'), help="constellation | fft | symbol | datascope")
parser.add_option("-f", "--frequency", type="eng_float", default=0.0, help="USRP center frequency", metavar="Hz")
parser.add_option("-F", "--ifile", type="string", default=None, help="read input from complex capture file")
parser.add_option("-H", "--hamlib-model", type="int", default=None, help="specify model for hamlib")
parser.add_option("-s", "--seek", type="int", default=0, help="ifile seek in K")
parser.add_option("-L", "--logfile-workers", type="int", default=None, help="number of demodulators to instantiate")
parser.add_option("-S", "--sample-rate", type="int", default=320e3, help="source samp rate")
parser.add_option("-t", "--tone-detect", action="store_true", default=False, help="use experimental tone detect algorithm")
parser.add_option("-T", "--trunk-conf-file", type="string", default=None, help="trunking config file name")
parser.add_option("-v", "--verbosity", type="int", default=0, help="message debug level")
parser.add_option("-V", "--vocoder", action="store_true", default=False, help="voice codec")
parser.add_option("-o", "--offset", type="eng_float", default=0.0, help="tuning offset frequency [to circumvent DC offset]", metavar="Hz")
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
parser.add_option("-w", "--wireshark", action="store_true", default=False, help="output data to Wireshark")
parser.add_option("-W", "--wireshark-host", type="string", default="127.0.0.1", help="Wireshark host")
parser.add_option("-r", "--raw-symbols", type="string", default=None, help="dump decoded symbols to file")
parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=(0, 0), help="select USRP Rx side A or B (default=A)")
parser.add_option("-g", "--gain", type="eng_float", default=None, help="set USRP gain in dB (default is midpoint) or set audio gain")
parser.add_option("-G", "--gain-mu", type="eng_float", default=0.025, help="gardner gain")
parser.add_option("-N", "--gains", type="string", default=None, help="gain settings")
parser.add_option("-O", "--audio-output", type="string", default="default", help="audio output device name")
parser.add_option("-U", "--udp-player", action="store_true", default=False, help="enable built-in udp audio player")
parser.add_option("-q", "--freq-corr", type="eng_float", default=0.0, help="frequency correction")
parser.add_option("-d", "--fine-tune", type="eng_float", default=0.0, help="fine tuning")
parser.add_option("-2", "--phase2-tdma", action="store_true", default=False, help="enable phase2 tdma decode")
parser.add_option("-Z", "--decim-amt", type="int", default=1, help="spectrum decimation")
(options, args) = parser.parse_args()
if len(args) != 0:
parser.print_help()
sys.exit(1)
self.channel_rate = 0
self.baseband_input = False
self.rtl_found = False
self.channel_rate = options.sample_rate
self.fft_sink = None
self.last_error_update = 0
self.error_band = 0
self.tuning_error = 0
self.freq_correction = 0
self.last_set_freq = 0
self.last_set_freq_at = time.time()
self.last_change_freq = 0
self.last_change_freq_at = time.time()
self.last_freq_params = {'freq' : 0.0, 'tgid' : None, 'tag' : "", 'tdma' : None}
self.src = None
if not options.input:
if (not options.input) and (not options.audio) and (not options.audio_if):
# check if osmocom is accessible
try:
import osmosdr
@ -147,7 +117,7 @@ class p25_rx_block (gr.top_block):
print "osmosdr source_c creation failure"
ignore = True
if "rtl" in options.args.lower():
if any(x in options.args.lower() for x in ['rtl', 'airspy', 'hackrf', 'uhd']):
#print "'rtl' has been found in options.args (%s)" % (options.args)
self.rtl_found = True
@ -156,8 +126,8 @@ class p25_rx_block (gr.top_block):
range = self.src.get_gain_range(name)
print "gain: name: %s range: start %d stop %d step %d" % (name, range[0].start(), range[0].stop(), range[0].step())
if options.gains:
for tuple in options.gains.split(","):
name, gain = tuple.split(":")
for tup in options.gains.split(","):
name, gain = tup.split(":")
gain = int(gain)
print "setting gain %s to %d" % (name, gain)
self.src.set_gain(gain, name)
@ -184,8 +154,9 @@ class p25_rx_block (gr.top_block):
# setup (read-only) attributes
self.symbol_rate = 4800
self.symbol_deviation = 600.0
self.basic_rate = 48000
self.basic_rate = 24000
_default_speed = 4800
self.options = options
# keep track of flow graph connections
self.cnxns = []
@ -195,8 +166,6 @@ class p25_rx_block (gr.top_block):
self.constellation_scope_connected = False
self.options = options
for i in xrange(len(speeds)):
if speeds[i] == _default_speed:
self.current_speed = i
@ -216,23 +185,25 @@ class p25_rx_block (gr.top_block):
# configure specified data source
if options.input:
self.open_file(options.input)
elif options.frequency:
self.open_usrp()
elif options.audio_if:
self.open_audio_c(self.channel_rate, options.gain, options.audio_input)
elif options.audio:
self.open_audio(self.channel_rate, options.gain, options.audio_input)
elif options.ifile:
self.open_ifile(self.channel_rate, options.gain, options.ifile, options.seek)
elif (self.rtl_found or options.frequency):
self.open_usrp()
else:
pass
# attach terminal thread
self.terminal = curses_terminal(self.input_q, self.output_q)
# attach terminal thread and make sure currently tuned frequency is displayed
self.terminal = op25_terminal(self.input_q, self.output_q, self.options.terminal_type)
if self.terminal is None:
sys.stderr.write('warning: no terminal attached\n')
# attach audio thread
if self.options.udp_player:
self.audio = socket_audio("127.0.0.1", WIRESHARK_PORT, self.options.audio_output)
self.audio = socket_audio("127.0.0.1", self.options.wireshark_port, self.options.audio_output, False, self.options.audio_gain)
else:
self.audio = None
@ -241,72 +212,83 @@ class p25_rx_block (gr.top_block):
def __build_graph(self, source, capture_rate):
global speeds
global WIRESHARK_PORT
# tell the scope the source rate
sps = 5 # samples / symbol
self.rx_q = gr.msg_queue(100)
udp_port = 0
if self.options.udp_player or self.options.wireshark or (self.options.wireshark_host != "127.0.0.1"):
udp_port = WIRESHARK_PORT
vocoder = self.options.vocoder
wireshark = self.options.wireshark
wireshark_host = self.options.wireshark_host
if self.options.udp_player:
vocoder = True
wireshark = True
wireshark_host = "127.0.0.1"
if wireshark or (wireshark_host != "127.0.0.1"):
udp_port = self.options.wireshark_port
self.tdma_state = False
self.xor_cache = {}
self.fft_state = False
self.c4fm_state = False
self.fscope_state = False
self.corr_state = False
self.fac_state = False
self.fsk4_demod_connected = False
self.psk_demod_connected = False
self.fsk4_demod_mode = False
self.corr_i_chan = False
if self.baseband_input:
self.demod = p25_demodulator.p25_demod_fb(input_rate=capture_rate)
self.demod = p25_demodulator.p25_demod_fb(input_rate=capture_rate, excess_bw=self.options.excess_bw)
else: # complex input
# local osc
self.lo_freq = self.options.offset + self.options.fine_tune
self.lo_freq = self.options.offset
if self.options.audio_if or self.options.ifile or self.options.input:
self.lo_freq += self.options.calibration
self.demod = p25_demodulator.p25_demod_cb( input_rate = capture_rate,
demod_type = self.options.demod_type,
relative_freq = self.lo_freq,
offset = self.options.offset,
if_rate = 48000,
if_rate = sps * 4800,
gain_mu = self.options.gain_mu,
costas_alpha = self.options.costas_alpha,
excess_bw = self.options.excess_bw,
symbol_rate = self.symbol_rate)
num_ambe = 0
if self.options.phase2_tdma:
num_ambe = 1
self.decoder = p25_decoder.p25_decoder_sink_b(dest='audio', do_imbe=True, num_ambe=num_ambe, wireshark_host=self.options.wireshark_host, udp_port=udp_port, do_msgq = True, msgq=self.rx_q, audio_output=self.options.audio_output, debug=self.options.verbosity)
self.decoder = p25_decoder.p25_decoder_sink_b(dest='audio', do_imbe=vocoder, num_ambe=num_ambe, wireshark_host=wireshark_host, udp_port=udp_port, do_msgq = True, msgq=self.rx_q, audio_output=self.options.audio_output, debug=self.options.verbosity)
# connect it all up
self.connect(source, self.demod, self.decoder)
if self.options.plot_mode == 'constellation':
assert self.options.demod_type == 'cqpsk' ## constellation requires cqpsk demod-type
self.constellation_sink = constellation_sink_c()
self.demod.connect_complex('diffdec', self.constellation_sink)
self.kill_sink = self.constellation_sink
elif self.options.plot_mode == 'symbol':
self.symbol_sink = symbol_sink_f()
self.demod.connect_float(self.symbol_sink)
self.kill_sink = self.symbol_sink
elif self.options.plot_mode == 'fft':
self.fft_sink = fft_sink_c()
self.spectrum_decim = filter.rational_resampler_ccf(1, self.options.decim_amt)
self.connect(self.spectrum_decim, self.fft_sink)
self.demod.connect_complex('src', self.spectrum_decim)
self.kill_sink = self.fft_sink
elif self.options.plot_mode == 'datascope':
assert self.options.demod_type == 'fsk4' ## datascope requires fsk4 demod-type
self.eye_sink = eye_sink_f(sps=10)
self.demod.connect_bb('symbol_filter', self.eye_sink)
self.kill_sink = self.eye_sink
if self.baseband_input:
sps = int(capture_rate / 4800)
plot_modes = []
if self.options.plot_mode is not None:
plot_modes = self.options.plot_mode.split(',')
for plot_mode in plot_modes:
if plot_mode == 'constellation':
assert self.options.demod_type == 'cqpsk' ## constellation requires cqpsk demod-type
sink = constellation_sink_c()
self.demod.connect_complex('diffdec', sink)
elif plot_mode == 'symbol':
sink = symbol_sink_f()
self.demod.connect_float(sink)
elif plot_mode == 'fft':
sink = fft_sink_c()
self.spectrum_decim = filter.rational_resampler_ccf(1, self.options.decim_amt)
self.connect(self.spectrum_decim, sink)
self.demod.connect_complex('src', self.spectrum_decim)
elif plot_mode == 'mixer':
sink = mixer_sink_c()
self.demod.connect_complex('mixer', sink)
elif plot_mode == 'datascope':
assert self.options.demod_type == 'fsk4' ## datascope requires fsk4 demod-type
sink = eye_sink_f(sps=sps)
self.demod.connect_bb('symbol_filter', sink)
else:
raise ValueError('unsupported plot type: %s' % plot_mode)
self.plot_sinks.append(sink)
if self.is_http_term():
sink.gnuplot.set_interval(_def_interval)
sink.gnuplot.set_output_dir(_def_file_dir)
if self.options.raw_symbols:
self.sink_sf = blocks.file_sink(gr.sizeof_char, self.options.raw_symbols)
@ -320,7 +302,7 @@ class p25_rx_block (gr.top_block):
demod = p25_demodulator.p25_demod_cb(input_rate=capture_rate,
demod_type=self.options.demod_type,
offset=self.options.offset)
decoder = p25_decoder.p25_decoder_sink_b(debug = self.options.verbosity, do_imbe = self.options.vocoder, num_ambe=num_ambe)
decoder = p25_decoder.p25_decoder_sink_b(debug = self.options.verbosity, do_imbe = vocoder, num_ambe=num_ambe)
logfile_workers.append({'demod': demod, 'decoder': decoder, 'active': False})
self.connect(source, demod, decoder)
@ -360,55 +342,100 @@ class p25_rx_block (gr.top_block):
def configure_tdma(self, params):
if params['tdma'] is not None and not self.options.phase2_tdma:
print '***TDMA request for frequency %d failed- phase2_tdma option not enabled' % params['freq']
sys.stderr.write("***TDMA request for frequency %d failed- phase2_tdma option not enabled\n" % params['freq'])
return
set_tdma = False
if params['tdma'] is not None:
set_tdma = True
self.decoder.set_slotid(params['tdma'])
if set_tdma == self.tdma_state:
return # already in desired state
self.tdma_state = set_tdma
if set_tdma:
self.decoder.set_slotid(params['tdma'])
hash = '%x%x%x' % (params['nac'], params['sysid'], params['wacn'])
if hash not in self.xor_cache:
self.xor_cache[hash] = lfsr.p25p2_lfsr(params['nac'], params['sysid'], params['wacn']).xor_chars
self.decoder.set_xormask(self.xor_cache[hash], hash)
sps = self.basic_rate / 6000
self.decoder.set_nac(params['nac'])
rate = 6000
else:
sps = self.basic_rate / 4800
rate = 4800
sps = self.basic_rate / rate
self.demod.set_symbol_rate(rate) # this and the foll. call should be merged?
self.demod.clock.set_omega(float(sps))
def change_freq(self, params):
freq = params['freq']
offset = params['offset']
center_freq = params['center_frequency']
fine_tune = self.options.fine_tune
def error_tracking(self):
UPDATE_TIME = 3
if self.last_error_update + UPDATE_TIME > time.time() \
or self.last_change_freq_at + UPDATE_TIME > time.time():
return
self.last_error_update = time.time()
band = self.demod.get_error_band()
freq_error = self.demod.get_freq_error()
if band:
self.error_band += band
self.freq_correction += freq_error * 0.15
if self.freq_correction > 600:
self.freq_correction -= 1200
self.error_band += 1
elif self.freq_correction < -600:
self.freq_correction += 1200
self.error_band -= 1
self.tuning_error = self.error_band * 1200 + self.freq_correction
e = 0
if self.last_change_freq > 0:
e = (self.tuning_error*1e6) / float(self.last_change_freq)
if self.options.verbosity >= 10:
sys.stderr.write('frequency_tracking\t%d\t%d\t%d\t%d\t%f\n' % (freq_error, self.error_band, self.tuning_error, self.freq_correction, e))
if self.options.hamlib_model:
self.hamlib.set_freq(freq)
elif params['center_frequency']:
relative_freq = center_freq - freq
if abs(relative_freq + self.options.offset) > self.channel_rate / 2:
self.lo_freq = self.options.offset + self.options.fine_tune # relative tune not possible
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
self.set_freq(freq + offset) # direct tune instead
else:
self.lo_freq = self.options.offset + relative_freq + fine_tune
if self.demod.set_relative_frequency(self.lo_freq): # relative tune successful
self.set_freq(center_freq + offset)
if self.fft_sink:
self.fft_sink.set_relative_freq(self.lo_freq)
else:
self.lo_freq = self.options.offset + self.options.fine_tune # relative tune unsuccessful
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
self.set_freq(freq + offset) # direct tune instead
else:
self.set_freq(freq + offset)
def change_freq(self, params):
self.last_freq_params = params
freq = params['freq']
offset = self.options.offset
center_freq = params['center_frequency']
self.error_tracking()
self.last_change_freq = freq
self.last_change_freq_at = time.time()
self.configure_tdma(params)
if self.options.hamlib_model:
self.hamlib.set_freq(freq)
return
if not center_freq:
self.lo_freq = offset + self.tuning_error
self.demod.set_relative_frequency(self.lo_freq)
self.set_freq(freq)
return
relative_freq = center_freq - freq
if abs(relative_freq + offset + self.tuning_error) > self.channel_rate / 2:
self.lo_freq = offset + self.tuning_error # relative tune not possible
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
self.set_freq(freq) # direct tune instead
return
self.lo_freq = relative_freq + offset + self.tuning_error
if self.demod.set_relative_frequency(self.lo_freq): # relative tune successful
self.set_freq(center_freq)
if self.fft_sink:
self.fft_sink.set_relative_freq(self.lo_freq)
return
self.lo_freq = offset + self.tuning_error # relative tune unsuccessful
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
self.set_freq(freq + offset) # direct tune instead
self.configure_tdma(params)
self.freq_update()
def freq_update(self):
if self.input_q.full_p():
return
params = self.last_freq_params
params['json_type'] = 'change_freq'
params['fine_tune'] = self.options.fine_tune
js = json.dumps(params)
msg = gr.message().make_from_string(js, -4, 0, 0)
self.input_q.insert_tail(msg)
@ -440,7 +467,8 @@ class p25_rx_block (gr.top_block):
def set_audio_scaler(self, vol):
#print 'audio scaler: %f' % ((1 / 32768.0) * (vol * 0.1))
self.decoder.set_scaler_k((1 / 32768.0) * (vol * 0.1))
if hasattr(self.decoder, 'set_scaler_k'):
self.decoder.set_scaler_k((1 / 32768.0) * (vol * 0.1))
def set_rtl_ppm(self, ppm):
self.src.set_freq_corr(ppm)
@ -462,11 +490,12 @@ class p25_rx_block (gr.top_block):
"""
if not self.src:
return False
tune_freq = target_freq + self.options.calibration + self.options.offset
self.target_freq = target_freq
tune_freq = target_freq + self.options.calibration + self.options.offset + self.options.fine_tune
r = self.src.set_center_freq(tune_freq)
if self.fft_sink:
self.fft_sink.set_center_freq(tune_freq)
self.fft_sink.set_center_freq(target_freq)
self.fft_sink.set_width(self.options.sample_rate)
if r:
@ -474,6 +503,8 @@ class p25_rx_block (gr.top_block):
#if self.show_debug_info:
# self.myform['baseband'].set_value(r.baseband_freq)
# self.myform['ddc'].set_value(r.dxc_freq)
self.last_set_freq = tune_freq
self.last_set_freq_at = time.time()
return True
return False
@ -509,10 +540,9 @@ class p25_rx_block (gr.top_block):
self.src.set_antenna(self.options.antenna)
self.info["capture-rate"] = capture_rate
self.src.set_bandwidth(capture_rate)
r = self.src.set_center_freq(self.options.frequency + self.options.calibration+ self.options.offset)
print 'set_center_freq: %d' % r
r = self.src.set_center_freq(self.options.frequency + self.options.calibration+ self.options.offset + self.options.fine_tune)
if not r:
raise RuntimeError("failed to set USRP frequency")
sys.stderr.write("__set_rx_from_osmosdr(): failed to set frequency\n")
# capture file
# if preserve:
if 0:
@ -534,15 +564,6 @@ class p25_rx_block (gr.top_block):
pickle.dump(self.info, f)
f.close()
# Adjust the channel offset
#
def adjust_channel_offset(self, delta_hz):
max_delta_hz = 12000.0
delta_hz *= self.symbol_deviation
delta_hz = max(delta_hz, -max_delta_hz)
delta_hz = min(delta_hz, max_delta_hz)
self.channel_filter.set_center_freq(self.channel_offset - delta_hz+ self.options.offset)
def open_ifile(self, capture_rate, gain, input_filename, file_seek):
speed = 96000 # TODO: fixme
ifile = blocks.file_source(gr.sizeof_gr_complex, input_filename, 1)
@ -590,41 +611,63 @@ class p25_rx_block (gr.top_block):
"source-dev": "USRP",
"source-decim": 1 }
self.__set_rx_from_osmosdr()
if self.options.frequency:
self.last_freq_params['freq'] = self.options.frequency
self.set_freq(self.options.frequency)
# except Exception, x:
# wx.MessageBox("Cannot open USRP: " + x.message, "USRP Error", wx.CANCEL | wx.ICON_EXCLAMATION)
# Set the channel offset
#
def set_channel_offset(self, offset_hz, scale, units):
self.channel_offset = -offset_hz
self.channel_filter.set_center_freq(self.channel_offset+ self.options.offset)
self.frame.SetStatusText("Channel offset: " + str(offset_hz * scale) + units, 1)
def is_http_term(self):
if self.options.terminal_type.startswith('http:'):
return True
elif self.options.terminal_type.startswith('zmq:'):
return True
else:
return False
# Set the RF squelch threshold level
#
def set_squelch_threshold(self, squelch_db):
self.squelch.set_threshold(squelch_db)
self.frame.SetStatusText("Squelch: " + str(squelch_db) + "dB", 2)
def process_ajax(self):
if not self.is_http_term():
return
filenames = [sink.gnuplot.filename for sink in self.plot_sinks if sink.gnuplot.filename]
error = None
if self.options.demod_type == 'cqpsk':
error = self.demod.get_freq_error()
d = {'json_type': 'rx_update', 'error': error, 'fine_tune': self.options.fine_tune, 'files': filenames}
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
self.input_q.insert_tail(msg)
def process_qmsg(self, msg):
# return true = end top block
RX_COMMANDS = 'skip lockout hold'
RX_COMMANDS = 'skip lockout hold'.split()
s = msg.to_string()
t = msg.type()
if t == -4:
d = json.loads(s)
s = d['command']
if s == 'quit': return True
elif s == 'update':
self.freq_update()
if self.trunk_rx is None:
return False ## possible race cond - just ignore
js = self.trunk_rx.to_json()
msg = gr.message().make_from_string(js, -4, 0, 0)
self.input_q.insert_tail(msg)
self.process_ajax()
elif s == 'set_freq':
freq = msg.arg1()
self.last_freq_params['freq'] = freq
self.set_freq(freq)
elif s == 'adj_tune':
freq = msg.arg1()
elif s == 'dump_tgids':
self.trunk_rx.dump_tgids()
elif s == 'add_default_config':
nac = msg.arg1()
self.trunk_rx.add_default_config(nac)
self.trunk_rx.add_default_config(int(nac))
elif s in RX_COMMANDS:
self.rx_q.insert_tail(msg)
elif s == 'settings-enable' and self.trunk_rx is not None:
self.trunk_rx.enable_status(d['data'])
return False
############################################################################
@ -644,26 +687,88 @@ class du_queue_watcher(threading.Thread):
def run(self):
while(self.keep_running):
msg = self.msgq.delete_head()
if not self.keep_running:
break
self.callback(msg)
class rx_main(object):
def __init__(self):
self.keep_running = True
self.cli_options()
self.tb = p25_rx_block(self.options)
self.q_watcher = du_queue_watcher(self.tb.output_q, self.process_qmsg)
def process_qmsg(self, msg):
if self.tb.process_qmsg(msg):
self.keep_running = False
def run(self):
try:
self.tb.start()
while self.keep_running:
time.sleep(1)
except:
sys.stderr.write('main: exception occurred\n')
sys.stderr.write('main: exception:\n%s\n' % traceback.format_exc())
if self.tb.terminal:
self.tb.terminal.end_terminal()
if self.tb.audio:
self.tb.audio.stop()
self.tb.stop()
for sink in self.tb.plot_sinks:
sink.kill()
def cli_options(self):
# command line argument parsing
parser = OptionParser(option_class=eng_option)
parser.add_option("--args", type="string", default="", help="device args")
parser.add_option("--antenna", type="string", default="", help="select antenna")
parser.add_option("-a", "--audio", action="store_true", default=False, help="use direct audio input")
parser.add_option("-A", "--audio-if", action="store_true", default=False, help="soundcard IF mode (use --calibration to set IF freq)")
parser.add_option("-I", "--audio-input", type="string", default="", help="pcm input device name. E.g., hw:0,0 or /dev/dsp")
parser.add_option("-i", "--input", type="string", default=None, help="input file name")
parser.add_option("-b", "--excess-bw", type="eng_float", default=0.2, help="for RRC filter", metavar="Hz")
parser.add_option("-c", "--calibration", type="eng_float", default=0.0, help="USRP offset or audio IF frequency", metavar="Hz")
parser.add_option("-C", "--costas-alpha", type="eng_float", default=0.04, help="value of alpha for Costas loop", metavar="Hz")
parser.add_option("-D", "--demod-type", type="choice", default="cqpsk", choices=('cqpsk', 'fsk4'), help="cqpsk | fsk4")
parser.add_option("-P", "--plot-mode", type="string", default=None, help="one or more of constellation, fft, symbol, datascope (comma-separated)")
parser.add_option("-f", "--frequency", type="eng_float", default=0.0, help="USRP center frequency", metavar="Hz")
parser.add_option("-F", "--ifile", type="string", default=None, help="read input from complex capture file")
parser.add_option("-H", "--hamlib-model", type="int", default=None, help="specify model for hamlib")
parser.add_option("-s", "--seek", type="int", default=0, help="ifile seek in K")
parser.add_option("-l", "--terminal-type", type="string", default='curses', help="'curses' or udp port or 'http:host:port'")
parser.add_option("-L", "--logfile-workers", type="int", default=None, help="number of demodulators to instantiate")
parser.add_option("-S", "--sample-rate", type="int", default=320e3, help="source samp rate")
parser.add_option("-t", "--tone-detect", action="store_true", default=False, help="use experimental tone detect algorithm")
parser.add_option("-T", "--trunk-conf-file", type="string", default=None, help="trunking config file name")
parser.add_option("-v", "--verbosity", type="int", default=0, help="message debug level")
parser.add_option("-V", "--vocoder", action="store_true", default=False, help="voice codec")
parser.add_option("-n", "--nocrypt", action="store_true", default=False, help="silence encrypted traffic")
parser.add_option("-o", "--offset", type="eng_float", default=0.0, help="tuning offset frequency [to circumvent DC offset]", metavar="Hz")
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
parser.add_option("-w", "--wireshark", action="store_true", default=False, help="output data to Wireshark")
parser.add_option("-W", "--wireshark-host", type="string", default="127.0.0.1", help="Wireshark host")
parser.add_option("-u", "--wireshark-port", type="int", default=23456, help="Wireshark udp port")
parser.add_option("-r", "--raw-symbols", type="string", default=None, help="dump decoded symbols to file")
parser.add_option("-g", "--gain", type="eng_float", default=None, help="set USRP gain in dB (default is midpoint) or set audio gain")
parser.add_option("-G", "--gain-mu", type="eng_float", default=0.025, help="gardner gain")
parser.add_option("-N", "--gains", type="string", default=None, help="gain settings")
parser.add_option("-O", "--audio-output", type="string", default="default", help="audio output device name")
parser.add_option("-x", "--audio-gain", type="eng_float", default="1.0", help="audio gain (default = 1.0)")
parser.add_option("-U", "--udp-player", action="store_true", default=False, help="enable built-in udp audio player")
parser.add_option("-q", "--freq-corr", type="eng_float", default=0.0, help="frequency correction")
parser.add_option("-d", "--fine-tune", type="eng_float", default=0.0, help="fine tuning")
parser.add_option("-2", "--phase2-tdma", action="store_true", default=False, help="enable phase2 tdma decode")
parser.add_option("-Z", "--decim-amt", type="int", default=1, help="spectrum decimation")
(options, args) = parser.parse_args()
if len(args) != 0:
parser.print_help()
sys.exit(1)
self.options = options
# Start the receiver
#
if __name__ == "__main__":
tb = p25_rx_block()
tb.start()
try:
while True:
msg = tb.output_q.delete_head()
if tb.process_qmsg(msg):
break
except:
sys.stderr.write('main: exception occurred\n')
sys.stderr.write('main: exception:\n%s\n' % traceback.format_exc())
if tb.terminal:
tb.terminal.end_curses()
if tb.audio:
tb.audio.stop()
tb.stop()
if tb.kill_sink:
tb.kill_sink.kill()
rx = rx_main()
rx.run()

View File

@ -1,6 +1,8 @@
#!/usr/bin/env python
# Copyright 2017 Graham Norbury
# Copyright 2017, 2018 Graham Norbury
#
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
#
# This file is part of OP25
#
@ -23,12 +25,14 @@ from ctypes import *
import sys
import time
import threading
import select
import socket
import errno
import struct
# OP25 defaults
PCM_RATE = 8000 # audio sample rate (Hz)
PCM_BUFFER_SIZE = 2000 # size of ALSA buffer in frames
PCM_BUFFER_SIZE = 4000 # size of ALSA buffer in frames
MAX_SUPERFRAME_SIZE = 320 # maximum size of incoming UDP audio buffer
@ -128,7 +132,7 @@ class alsasound(object):
self.format = pcm_format
self.channels = pcm_channels
self.rate = pcm_rate
pcm_start_threshold = int(pcm_buffer_size * 0.75)
pcm_buf_sz = c_ulong(pcm_buffer_size)
c_pars = (c_void_p * int(self.libasound.snd_pcm_hw_params_sizeof() / sizeof(c_void_p)))()
err = self.libasound.snd_pcm_hw_params_any(self.c_pcm, c_pars)
@ -136,40 +140,43 @@ class alsasound(object):
sys.stderr.write("hw_params_any failed: %d\n" % err)
return err
err = self.libasound.snd_pcm_hw_params_set_access(self.c_pcm, c_pars, SND_PCM_ACCESS_RW_INTERLEAVED);
err = self.libasound.snd_pcm_hw_params_set_access(self.c_pcm, c_pars, SND_PCM_ACCESS_RW_INTERLEAVED)
if err < 0:
sys.stderr.write("set_access failed: %d\n" % err)
return err
err = self.libasound.snd_pcm_hw_params_set_format(self.c_pcm, c_pars, c_uint(self.format));
err = self.libasound.snd_pcm_hw_params_set_format(self.c_pcm, c_pars, c_uint(self.format))
if err < 0:
sys.stderr.write("set_format failed: %d\n" % err)
return err
err = self.libasound.snd_pcm_hw_params_set_channels(self.c_pcm, c_pars, c_uint(self.channels));
err = self.libasound.snd_pcm_hw_params_set_channels(self.c_pcm, c_pars, c_uint(self.channels))
if err < 0:
sys.stderr.write("set_channels failed: %d\n" % err)
return err
err = self.libasound.snd_pcm_hw_params_set_rate(self.c_pcm, c_pars, c_uint(self.rate), c_int(0));
err = self.libasound.snd_pcm_hw_params_set_rate(self.c_pcm, c_pars, c_uint(self.rate), c_int(0))
if err < 0:
sys.stderr.write("set_rate failed: %d\n" % err)
return err
err = 0 ########self.libasound.snd_pcm_hw_params_set_buffer_size(self.c_pcm, c_pars, c_ulong(pcm_buffer_size), c_int(0));
err = self.libasound.snd_pcm_hw_params_set_buffer_size_near(self.c_pcm, c_pars, byref(pcm_buf_sz))
if err < 0:
sys.stderr.write("set_buffer_size failed: %d\n" % err)
sys.stderr.write("set_buffer_size_near failed: %d\n" % err)
return err
err = self.libasound.snd_pcm_hw_params(self.c_pcm, c_pars);
if pcm_buf_sz.value != pcm_buffer_size:
sys.stderr.write("set_buffer_size_near requested %d, but returned %d\n" % (pcm_buffer_size, pcm_buf_sz.value))
err = self.libasound.snd_pcm_hw_params(self.c_pcm, c_pars)
if err < 0:
sys.stderr.write("hw_params failed: %d\n" % err)
return err
self.libasound.snd_pcm_hw_params_current(self.c_pcm, c_pars)
c_bits = self.libasound.snd_pcm_hw_params_get_sbits(c_pars)
self.framesize = self.channels * c_bits/8;
self.framesize = self.channels * c_bits/8
c_sw_pars = (c_void_p * int(self.libasound.snd_pcm_sw_params_sizeof() / sizeof(c_void_p)))()
err = self.libasound.snd_pcm_sw_params_current(self.c_pcm, c_sw_pars)
if err < 0:
sys.stderr.write("get_sw_params_current failed: %d\n" % err)
return err
pcm_start_threshold = int(pcm_buf_sz.value * 0.75)
err = self.libasound.snd_pcm_sw_params_set_start_threshold(self.c_pcm, c_sw_pars, c_uint(pcm_start_threshold))
if err < 0:
sys.stderr.write("set_sw_params_start_threshold failed: %d\n" % err)
@ -197,7 +204,7 @@ class alsasound(object):
if (ret < 0):
if (ret == -errno.EPIPE): # underrun
if (LOG_AUDIO_XRUNS):
sys.stderr.write("[%f] PCM underrun\n" % time.time())
sys.stderr.write("%f PCM underrun\n" % time.time())
ret = self.libasound.snd_pcm_recover(self.c_pcm, ret, 1)
if (ret >= 0):
ret = self.libasound.snd_pcm_writei(self.c_pcm, cast(c_data, POINTER(c_void_p)), n_frames)
@ -227,6 +234,16 @@ class alsasound(object):
time.sleep(1)
ret = self.libasound.snd_pcm_prepare(self.c_pcm)
def drop(self):
ret = self.libasound.snd_pcm_drop(self.c_pcm)
if (ret == -errno.ESTRPIPE): # suspended
while True:
ret = self.libasound.snd_pcm_resume(self.c_pcm)
if (ret != -errno.EAGAIN):
break
time.sleep(1)
ret = self.libasound.snd_pcm_prepare(self.c_pcm)
def dump(self):
if (self.c_pcm.value == None):
return
@ -242,49 +259,117 @@ class alsasound(object):
# OP25 thread to receive UDP audio samples and send to Alsa driver
class socket_audio(threading.Thread):
def __init__(self, udp_host, udp_port, pcm_device, **kwds):
def __init__(self, udp_host, udp_port, pcm_device, two_channels = False, audio_gain = 1.0, **kwds):
threading.Thread.__init__(self, **kwds)
self.setDaemon(1)
self.keep_running = True
self.sock = None
self.two_channels = two_channels
self.audio_gain = audio_gain
self.sock_a = None
self.sock_b = None
self.pcm = alsasound()
self.setup_socket(udp_host, udp_port)
self.setup_sockets(udp_host, udp_port)
self.setup_pcm(pcm_device)
self.start()
return
def run(self):
while self.keep_running:
# Data received on the udp port is 320 bytes for an audio frame or 2 bytes for a flag
#
d = self.sock.recvfrom(MAX_SUPERFRAME_SIZE) # recvfrom blocks until data becomes available
if d[0]:
d_len = len(d[0])
if (d_len == 2): # flag
flag = ord(d[0][0]) + (ord(d[0][1]) << 8) # 16 bit little endian
if (flag == 0): # 0x0000 = drain pcm buffer
self.pcm.drain()
else: # audio data
self.pcm.write(d[0])
else:
break
readable, writable, exceptional = select.select( [self.sock_a, self.sock_b], [], [self.sock_a, self.sock_b] )
in_a = None
in_b = None
data_a = ""
data_b = ""
flag_a = -1
flag_b = -1
self.close_socket()
# Data received on the udp port is 320 bytes for an audio frame or 2 bytes for a flag
if self.sock_a in readable:
in_a = self.sock_a.recvfrom(MAX_SUPERFRAME_SIZE)
if self.sock_b in readable:
in_b = self.sock_b.recvfrom(MAX_SUPERFRAME_SIZE)
if in_a is not None:
len_a = len(in_a[0])
if len_a == 2:
flag_a = ord(in_a[0][0]) + (ord(in_a[0][1]) << 8) # 16 bit little endian
elif len_a > 0:
data_a = in_a[0]
if in_b is not None:
len_b = len(in_b[0])
if len_b == 2:
flag_b = ord(in_b[0][0]) + (ord(in_b[0][1]) << 8) # 16 bit little endian
elif len_b > 0:
data_b = in_b[0]
if (((flag_a == 0) and (flag_b == 0)) or
((flag_a == 0) and ((in_b is None) or (flag_b == 1))) or
((flag_b == 0) and ((in_a is None) or (flag_a == 1)))):
self.pcm.drain()
continue
if (((flag_a == 1) and (flag_b == 1)) or
((flag_a == 1) and (in_b is None)) or
((flag_b == 1) and (in_a is None))):
self.pcm.drop()
continue
if not self.two_channels:
data_a = self.scale(data_a)
self.pcm.write(self.interleave(data_a, data_a))
else:
data_a = self.scale(data_a)
data_b = self.scale(data_b)
self.pcm.write(self.interleave(data_a, data_b))
self.close_sockets()
self.close_pcm()
return
def scale(self, data): # crude amplitude scaler (volume) for S16_LE samples
scaled_data = ""
d_len = len(data) / 2
iter_d = iter(data)
i = 0;
while i < d_len:
i += 1
pcm_r = struct.unpack('<h', next(iter_d, chr(0)) + next(iter_d, chr(0)))[0]
pcm_s = min(max((int)(pcm_r * self.audio_gain), -32768), 32767)
scaled_data += struct.pack('<h', pcm_s)
return scaled_data
def interleave(self, data_a, data_b):
combi_data = ""
d_len = max(len(data_a), len(data_b))
iter_a = iter(data_a)
iter_b = iter(data_b)
i = 0
while i < d_len:
i += 2
combi_data += next(iter_a, chr(0))
combi_data += next(iter_a, chr(0))
combi_data += next(iter_b, chr(0))
combi_data += next(iter_b, chr(0))
return combi_data
def stop(self):
self.keep_running = False
return
def setup_socket(self, udp_host, udp_port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind((udp_host, udp_port))
sys.stderr.write('setup_socket: %d\n' % udp_port)
def setup_sockets(self, udp_host, udp_port):
self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock_b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock_a.setblocking(0)
self.sock_b.setblocking(0)
self.sock_a.bind((udp_host, udp_port))
self.sock_b.bind((udp_host, udp_port + 2))
return
def close_socket(self):
self.sock.close()
def close_sockets(self):
self.sock_a.close()
self.sock_b.close()
return
def setup_pcm(self, hwdevice):
@ -296,7 +381,7 @@ class socket_audio(threading.Thread):
self.keep_running = False
return
err = self.pcm.setup(SND_PCM_FORMAT_S16_LE.value, 1, PCM_RATE, PCM_BUFFER_SIZE)
err = self.pcm.setup(SND_PCM_FORMAT_S16_LE.value, 2, PCM_RATE, PCM_BUFFER_SIZE)
if err < 0:
sys.stderr.write('failed to set up pcm stream\n')
self.keep_running = False

347
op25/gr-op25_repeater/apps/terminal.py Normal file → Executable file
View File

@ -2,7 +2,7 @@
# Copyright 2008-2011 Steve Glass
#
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Max H. Parke KA1RBI
#
# This file is part of OP25
#
@ -28,11 +28,28 @@ import time
import json
import threading
import traceback
import socket
from gnuradio import gr
KEEPALIVE_TIME = 3.0 # no data received in (seconds)
class q_watcher(threading.Thread):
def __init__(self, msgq, callback, **kwds):
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)
self.msgq = msgq
self.callback = callback
self.keep_running = True
self.start()
def run(self):
while(self.keep_running):
msg = self.msgq.delete_head()
self.callback(msg)
class curses_terminal(threading.Thread):
def __init__(self, input_q, output_q, **kwds):
def __init__(self, input_q, output_q, sock=None, **kwds):
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)
self.input_q = input_q
@ -41,31 +58,83 @@ class curses_terminal(threading.Thread):
self.last_update = 0
self.auto_update = True
self.current_nac = None
self.maxx = 0
self.maxy = 0
self.sock = sock
self.start()
def setup_curses(self):
self.stdscr = curses.initscr()
self.maxy, self.maxx = self.stdscr.getmaxyx()
if (self.maxy < 6) or (self.maxx < 60):
sys.stderr.write("Terminal window too small! Minimum size [70 x 6], actual [%d x %d]\n" % (self.maxx, self.maxy))
print "Terminal window too small! Minimum size [70 x 6], actual [%d x %d]\n" % (self.maxx, self.maxy)
self.keep_running = False
return
curses.noecho()
curses.halfdelay(1)
self.top_bar = curses.newwin(1, 80, 0, 0)
self.freq_list = curses.newwin(20, 80, 1, 0)
self.active1 = curses.newwin(1, 80, 21, 0)
self.active2 = curses.newwin(1, 80, 22, 0)
self.prompt = curses.newwin(1, 10, 23, 0)
self.text_win = curses.newwin(1, 70, 23, 10)
self.title_bar = curses.newwin(1, self.maxx, 0, 0)
self.help_bar = curses.newwin(1, self.maxx, self.maxy-1, 0)
self.top_bar = curses.newwin(1, self.maxx, 1, 0)
self.freq_list = curses.newwin(self.maxy-5, self.maxx, 2, 0)
self.active1 = curses.newwin(1, self.maxx-15, self.maxy-3, 0)
self.active2 = curses.newwin(1, self.maxx-15, self.maxy-2, 0)
self.status1 = curses.newwin(1, 15, self.maxy-3, self.maxx-15)
self.status2 = curses.newwin(1, 15, self.maxy-2, self.maxx-15)
self.prompt = curses.newwin(1, 10, self.maxy-1, 0)
self.text_win = curses.newwin(1, 11, self.maxy-1, 10)
self.textpad = curses.textpad.Textbox(self.text_win)
self.stdscr.refresh()
def end_curses(self):
self.title_help()
def resize_curses(self):
self.maxy, self.maxx = self.stdscr.getmaxyx()
if (self.maxx < 60) or (self.maxy < 6): # do not resize if window is now too small
return
self.stdscr.erase()
self.title_bar.resize(1, self.maxx)
self.help_bar.resize(1, self.maxx)
self.help_bar.mvwin(self.maxy-1, 0)
self.top_bar.resize(1, self.maxx)
self.freq_list.resize(self.maxy-5, self.maxx)
self.active1.resize(1, self.maxx-15)
self.active1.mvwin(self.maxy-3, 0)
self.active2.resize(1, self.maxx-15)
self.active2.mvwin(self.maxy-2, 0)
self.status1.resize(1, 15)
self.status1.mvwin(self.maxy-3, self.maxx-15)
self.status2.resize(1, 15)
self.status2.mvwin(self.maxy-2, self.maxx-15)
self.stdscr.refresh()
self.title_help()
def end_terminal(self):
try:
curses.endwin()
except:
pass
def title_help(self):
title_str = "OP25"
help_str = "(f)req (h)old (s)kip (l)ock (q)uit (1-5)plot (,.<>)tune"
self.title_bar.erase()
self.help_bar.erase()
self.title_bar.addstr(0, 0, title_str.center(self.maxx-1, " "), curses.A_REVERSE)
self.help_bar.addstr(0, 0, help_str.center(self.maxx-1, " "), curses.A_REVERSE)
self.title_bar.refresh()
self.help_bar.refresh()
self.stdscr.move(1,0)
self.stdscr.refresh()
def do_auto_update(self):
UPDATE_INTERVAL = 1 # sec.
UPDATE_INTERVAL = 0.5 # sec.
if not self.auto_update:
return False
if self.last_update + UPDATE_INTERVAL > time.time():
@ -75,32 +144,33 @@ class curses_terminal(threading.Thread):
def process_terminal_events(self):
# return true signifies end of main event loop
if curses.is_term_resized(self.maxy, self.maxx) is True:
self.resize_curses()
_ORD_S = ord('s')
_ORD_L = ord('l')
_ORD_H = ord('h')
COMMANDS = {_ORD_S: 'skip', _ORD_L: 'lockout', _ORD_H: 'hold'}
c = self.stdscr.getch()
if c == ord('u') or self.do_auto_update():
msg = gr.message().make_from_string('update', -2, 0, 0)
self.output_q.insert_tail(msg)
self.send_command('update', 0)
if c in COMMANDS.keys():
msg = gr.message().make_from_string(COMMANDS[c], -2, 0, 0)
self.output_q.insert_tail(msg)
self.send_command(COMMANDS[c], 0)
elif c == ord('q'):
return True
elif c == ord('t'):
if self.current_nac:
msg = gr.message().make_from_string('add_default_config', -2, int(self.current_nac), 0)
self.output_q.insert_tail(msg)
self.send_command('add_default_config', int(self.current_nac))
elif c == ord('f'):
self.prompt.addstr(0, 0, 'Frequency')
self.prompt.refresh()
self.text_win.clear()
self.text_win.erase()
response = self.textpad.edit()
self.prompt.clear()
self.prompt.erase()
self.prompt.refresh()
self.text_win.clear()
self.text_win.erase()
self.text_win.refresh()
self.title_help()
try:
freq = float(response)
if freq < 10000:
@ -108,8 +178,19 @@ class curses_terminal(threading.Thread):
except:
freq = None
if freq:
msg = gr.message().make_from_string('set_freq', -2, freq, 0)
self.output_q.insert_tail(msg)
self.send_command('set_freq', freq)
elif c == ord(','):
self.send_command('adj_tune', -100)
elif c == ord('.'):
self.send_command('adj_tune', 100)
elif c == ord('<'):
self.send_command('adj_tune', -1200)
elif c == ord('>'):
self.send_command('adj_tune', 1200)
elif (c >= ord('1') ) and (c <= ord('5')):
self.send_command('toggle_plot', (c - ord('0')))
elif c == ord('d'):
self.send_command('dump_tgids', 0)
elif c == ord('x'):
assert 1 == 0
return False
@ -118,11 +199,14 @@ class curses_terminal(threading.Thread):
# return true signifies end of main event loop
msg = json.loads(js)
if msg['json_type'] == 'trunk_update':
nacs = [x for x in msg.keys() if x != 'json_type']
nacs = [x for x in msg.keys() if x.isnumeric() ]
if not nacs:
return
times = {msg[nac]['last_tsbk']:nac for nac in nacs}
current_nac = times[ sorted(times.keys(), reverse=True)[0] ]
if msg.get('nac'):
current_nac = str(msg['nac'])
else:
times = {msg[nac]['last_tsbk']:nac for nac in nacs}
current_nac = times[ sorted(times.keys(), reverse=True)[0] ]
self.current_nac = current_nac
s = 'NAC 0x%x' % (int(current_nac))
s += ' WACN 0x%x' % (msg[current_nac]['wacn'])
@ -131,30 +215,50 @@ class curses_terminal(threading.Thread):
s += '/%f' % (msg[current_nac]['txchan']/ 1000000.0)
s += ' tsbks %d' % (msg[current_nac]['tsbks'])
freqs = sorted(msg[current_nac]['frequencies'].keys())
s = s[:79]
self.top_bar.clear()
s = s[:(self.maxx - 1)]
self.top_bar.erase()
self.top_bar.addstr(0, 0, s)
self.top_bar.refresh()
self.freq_list.clear()
self.freq_list.erase()
for i in xrange(len(freqs)):
if i > (self.maxy - 6):
break
s=msg[current_nac]['frequencies'][freqs[i]]
s = s[:79]
s = s[:(self.maxx - 1)]
self.freq_list.addstr(i, 0, s)
self.freq_list.refresh()
self.status1.erase()
if 'srcaddr' in msg:
srcaddr = msg['srcaddr']
if (srcaddr != 0) and (srcaddr != 0xffffff):
s = '%d' % (srcaddr)
s = s[:14]
self.status1.addstr(0, (14-len(s)), s)
self.status1.refresh()
self.status2.erase()
if 'encrypted' in msg:
encrypted = msg['encrypted']
if encrypted != 0:
s = 'ENCRYPTED'
self.status2.addstr(0, (14-len(s)), s, curses.A_REVERSE)
self.status2.refresh()
self.stdscr.refresh()
elif msg['json_type'] == 'change_freq':
s = 'Frequency %f' % (msg['freq'] / 1000000.0)
if msg['fine_tune'] is not None:
s +='(%d)' % msg['fine_tune']
if msg['tgid'] is not None:
s += ' Talkgroup ID %s' % (msg['tgid'])
if msg['tdma'] is not None:
s += ' TDMA Slot %s' % msg['tdma']
self.active1.clear()
self.active2.clear()
s = s[:(self.maxx - 16)]
self.active1.erase()
self.active2.erase()
self.active1.addstr(0, 0, s)
self.active1.refresh()
if msg['tag']:
s = msg['tag']
s = s[:79]
s = s[:(self.maxx - 16)]
self.active2.addstr(0, 0, s)
self.active2.refresh()
self.stdscr.refresh()
@ -163,6 +267,8 @@ class curses_terminal(threading.Thread):
def process_q_events(self):
# return true signifies end of main event loop
while True:
if curses.is_term_resized(self.maxy, self.maxx) is True:
self.resize_curses()
if self.input_q.empty_p():
break
msg = self.input_q.delete_head_nowait()
@ -170,18 +276,187 @@ class curses_terminal(threading.Thread):
return self.process_json(msg.to_string())
return False
def send_command(self, command, data):
if self.sock:
self.sock.send(json.dumps({'command': command, 'data': data}))
else:
msg = gr.message().make_from_string(command, -2, data, 0)
self.output_q.insert_tail(msg)
def run(self):
try:
self.setup_curses()
while(self.keep_running):
if self.process_terminal_events():
break
if self.process_q_events():
break
except:
sys.stderr.write('terminal: exception occurred\n')
sys.stderr.write('terminal: exception occurred (%d, %d)\n' % (self.maxx, self.maxy))
sys.stderr.write('terminal: exception:\n%s\n' % traceback.format_exc())
finally:
self.end_curses()
msg = gr.message().make_from_string('quit', -2, 0, 0)
self.output_q.insert_tail(msg)
self.end_terminal()
self.keep_running = False
self.send_command('quit', 0)
class zeromq_terminal(threading.Thread):
def __init__(self, input_q, output_q, endpoint, **kwds):
import zmq
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)
self.input_q = input_q
self.output_q = output_q
self.endpoint = endpoint
self.keep_running = True
if not endpoint.startswith('tcp:'):
sys.stderr.write('zeromq_terminal unsupported endpoint: %s\n' % endpoint)
return
port = endpoint.replace('tcp:', '')
port = int(port)
self.zmq_context = zmq.Context()
self.zmq_sub = self.zmq_context.socket(zmq.SUB)
self.zmq_sub.connect('tcp://localhost:%d' % (port+1))
self.zmq_sub.setsockopt(zmq.SUBSCRIBE, '')
self.zmq_pub = self.zmq_context.socket(zmq.PUB)
self.zmq_pub.sndhwm = 5
self.zmq_pub.bind('tcp://*:%d' % port)
self.queue_watcher = q_watcher(self.input_q, lambda msg : self.zmq_pub.send(msg.to_string()))
self.start()
def end_terminal(self):
self.keep_running = False
def run(self):
while self.keep_running:
js = self.zmq_sub.recv()
if not self.keep_running:
break
d = json.loads(js)
msg = gr.message().make_from_string(str(d['command']), d['msgtype'], d['data'], 0)
if self.output_q.full_p():
self.output_q.delete_head()
if not self.output_q.full_p():
self.output_q.insert_tail(msg)
class http_terminal(threading.Thread):
def __init__(self, input_q, output_q, endpoint, **kwds):
from http import http_server
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)
self.input_q = input_q
self.output_q = output_q
self.endpoint = endpoint
self.keep_running = True
self.server = http_server(self.input_q, self.output_q, self.endpoint)
self.start()
def end_terminal(self):
self.keep_running = False
def run(self):
self.server.run()
class udp_terminal(threading.Thread):
def __init__(self, input_q, output_q, port, **kwds):
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)
self.input_q = input_q
self.output_q = output_q
self.keep_running = True
self.port = port
self.remote_ip = '127.0.0.1'
self.remote_port = 0
self.keepalive_until = 0
self.setup_socket(port)
self.q_handler = q_watcher(self.input_q, self.process_qmsg)
self.start()
def setup_socket(self, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind(('0.0.0.0', port))
def process_qmsg(self, msg):
if time.time() >= self.keepalive_until:
return
s = msg.to_string()
if msg.type() == -4 and self.remote_port > 0:
self.sock.sendto(s, (self.remote_ip, self.remote_port))
def end_terminal(self):
self.keep_running = False
def run(self):
while self.keep_running:
data, addr = self.sock.recvfrom(2048)
data = json.loads(data)
if data['command'] == 'quit':
self.keepalive_until = 0
continue
msg = gr.message().make_from_string(str(data['command']), -2, data['data'], 0)
self.output_q.insert_tail(msg)
self.remote_ip = addr[0]
self.remote_port = addr[1]
self.keepalive_until = time.time() + KEEPALIVE_TIME
def op25_terminal(input_q, output_q, terminal_type):
if terminal_type == 'curses':
return curses_terminal(input_q, output_q)
elif terminal_type.startswith('zmq:'):
return zeromq_terminal(input_q, output_q, terminal_type.replace('zmq:', ''))
elif terminal_type[0].isdigit():
port = int(terminal_type)
return udp_terminal(input_q, output_q, port)
elif terminal_type.startswith('http:'):
return http_terminal(input_q, output_q, terminal_type.replace('http:', ''))
else:
sys.stderr.write('warning: unsupported terminal type: %s\n' % terminal_type)
return None
class terminal_client(object):
def __init__(self):
self.input_q = gr.msg_queue(10)
self.keep_running = True
self.terminal = None
ip_addr = sys.argv[1]
port = int(sys.argv[2])
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.connect((ip_addr, port))
self.sock.settimeout(0.1)
self.terminal = curses_terminal(self.input_q, None, sock=self.sock)
def run(self):
while self.keep_running:
try:
js, addr = self.sock.recvfrom(2048)
msg = gr.message().make_from_string(js, -4, 0, 0)
self.input_q.insert_tail(msg)
except socket.timeout:
pass
except:
raise
if not self.terminal.keep_running:
self.keep_running = False
if __name__ == '__main__':
terminal = None
try:
terminal = terminal_client()
terminal.run()
except:
sys.stderr.write('terminal: exception occurred\n')
sys.stderr.write('terminal: exception:\n%s\n' % traceback.format_exc())
finally:
if terminal is not None and terminal.terminal is not None:
terminal.terminal.end_terminal()

View File

@ -1,5 +1,6 @@
#! /usr/bin/env python
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Max H. Parke KA1RBI
#
# This file is part of OP25
#
@ -25,6 +26,7 @@ import collections
import json
sys.path.append('tdma')
import lfsr
from tsvfile import make_config, load_tsv
def crc16(dat,len): # slow version
poly = (1<<12) + (1<<5) + (1<<0)
@ -39,12 +41,6 @@ def crc16(dat,len): # slow version
crc = crc ^ 0xffff
return crc
def get_frequency(f): # return frequency in Hz
if f.find('.') == -1: # assume in Hz
return int(f)
else: # assume in MHz due to '.'
return int(float(f) * 1000000)
class trunked_system (object):
def __init__(self, debug=0, config=None):
self.debug = debug
@ -55,6 +51,7 @@ class trunked_system (object):
self.tsbk_cache = {}
self.secondary = {}
self.adjacent = {}
self.adjacent_data = {}
self.rfss_syid = 0
self.rfss_rfid = 0
self.rfss_stid = 0
@ -71,6 +68,7 @@ class trunked_system (object):
self.sysname = 0
self.trunk_cc = 0
self.last_trunk_cc = 0
self.cc_list = []
self.cc_list_index = 0
self.CC_HUNT_TIME = 5.0
@ -90,9 +88,16 @@ class trunked_system (object):
self.center_frequency = config['center_frequency']
self.modulation = config['modulation']
self.current_srcaddr = 0
self.current_grpaddr = 0 # from P25 LCW
self.current_alg = ""
self.current_algid = 128
self.current_keyid = 0
def to_json(self):
d = {}
d['syid'] = self.rfss_syid
d['sysname'] = self.sysname
d['rfid'] = self.rfss_rfid
d['stid'] = self.rfss_stid
d['sysid'] = self.ns_syid
@ -102,12 +107,20 @@ class trunked_system (object):
d['secondary'] = self.secondary.keys()
d['tsbks'] = self.stats['tsbks']
d['frequencies'] = {}
d['frequency_data'] = {}
d['last_tsbk'] = self.last_tsbk
d['srcaddr'] = self.current_srcaddr
d['grpaddr'] = self.current_grpaddr
d['algid'] = self.current_algid
d['alg'] = self.current_alg
d['keyid'] = self.current_keyid
t = time.time()
for f in self.voice_frequencies.keys():
tgs = '%s %s' % (self.voice_frequencies[f]['tgid'][0], self.voice_frequencies[f]['tgid'][1])
d['frequencies'][f] = 'voice frequency %f tgid(s) %s %4.1fs ago count %d' % (f / 1000000.0, tgs, t - self.voice_frequencies[f]['time'], self.voice_frequencies[f]['counter'])
d['frequency_data'][f] = {'tgids': self.voice_frequencies[f]['tgid'], 'last_activity': '%7.1f' % (t - self.voice_frequencies[f]['time']), 'counter': self.voice_frequencies[f]['counter']}
d['adjacent_data'] = self.adjacent_data
return json.dumps(d)
def to_string(self):
@ -162,23 +175,38 @@ class trunked_system (object):
return ""
if tgid not in self.tgid_map:
return "Talkgroup ID %d [0x%x]" % (tgid, tgid)
return self.tgid_map[tgid]
return self.tgid_map[tgid][0]
def get_prio(self, tgid):
if (not tgid) or (tgid not in self.tgid_map):
return 3
return self.tgid_map[tgid][1]
def update_talkgroup(self, frequency, tgid, tdma_slot, srcaddr):
if self.debug >= 5:
sys.stderr.write('%f set tgid=%s, srcaddr=%s\n' % (time.time(), tgid, srcaddr))
def update_talkgroup(self, frequency, tgid, tdma_slot):
if tgid not in self.talkgroups:
self.talkgroups[tgid] = {'counter':0}
if self.debug >= 5:
sys.stderr.write('%f new tgid: %s %s prio %d\n' % (time.time(), tgid, self.get_tag(tgid), self.get_prio(tgid)))
self.talkgroups[tgid]['time'] = time.time()
self.talkgroups[tgid]['frequency'] = frequency
self.talkgroups[tgid]['tdma_slot'] = tdma_slot
self.talkgroups[tgid]['srcaddr'] = srcaddr
self.talkgroups[tgid]['prio'] = self.get_prio(tgid)
def update_voice_frequency(self, frequency, tgid=None, tdma_slot=None):
def update_voice_frequency(self, frequency, tgid=None, tdma_slot=None, srcaddr=0):
if not frequency: # e.g., channel identifier not yet known
return
self.update_talkgroup(frequency, tgid, tdma_slot)
self.update_talkgroup(frequency, tgid, tdma_slot, srcaddr)
if frequency not in self.voice_frequencies:
self.voice_frequencies[frequency] = {'counter':0}
sorted_freqs = collections.OrderedDict(sorted(self.voice_frequencies.items()))
self.voice_frequencies = sorted_freqs
if self.debug >= 5:
sys.stderr.write('%f new freq: %f\n' % (time.time(), frequency/1000000.0))
if tdma_slot is None:
tdma_slot = 0
if 'tgid' not in self.voice_frequencies[frequency]:
@ -200,11 +228,16 @@ class trunked_system (object):
for tg in expired_tgs:
self.blacklist.pop(tg)
def find_talkgroup(self, start_time, tgid=None):
def find_talkgroup(self, start_time, tgid=None, hold=False):
tgt_tgid = None
self.blacklist_update(start_time)
if tgid is not None and tgid in self.talkgroups and self.talkgroups[tgid]['time'] >= start_time:
return self.talkgroups[tgid]['frequency'], tgid, self.talkgroups[tgid]['tdma_slot']
if tgid is not None and tgid in self.talkgroups:
tgt_tgid = tgid
for active_tgid in self.talkgroups:
if hold:
break
if self.talkgroups[active_tgid]['time'] < start_time:
continue
if active_tgid in self.blacklist:
@ -213,31 +246,42 @@ class trunked_system (object):
continue
if self.talkgroups[active_tgid]['tdma_slot'] is not None and (self.ns_syid < 0 or self.ns_wacn < 0):
continue
if tgid is None:
return self.talkgroups[active_tgid]['frequency'], active_tgid, self.talkgroups[active_tgid]['tdma_slot']
return None, None, None
if tgt_tgid is None:
tgt_tgid = active_tgid
elif self.talkgroups[active_tgid]['prio'] < self.talkgroups[tgt_tgid]['prio']:
tgt_tgid = active_tgid
if tgt_tgid is not None and self.talkgroups[tgt_tgid]['time'] >= start_time:
return self.talkgroups[tgt_tgid]['frequency'], tgt_tgid, self.talkgroups[tgt_tgid]['tdma_slot'], self.talkgroups[tgt_tgid]['srcaddr']
return None, None, None, None
def dump_tgids(self):
sys.stderr.write("Known tgids: { ")
for tgid in sorted(self.talkgroups.keys()):
sys.stderr.write("%d " % tgid);
sys.stderr.write("}\n")
def add_blacklist(self, tgid, end_time=None):
if not tgid:
return
self.blacklist[tgid] = end_time
def decode_mbt_data(self, opcode, header, mbt_data):
def decode_mbt_data(self, opcode, src, header, mbt_data):
self.cc_timeouts = 0
self.last_tsbk = time.time()
updated = 0
if self.debug > 10:
print "decode_mbt_data: %x %x" %(opcode, mbt_data)
sys.stderr.write('decode_mbt_data: %x %x\n' %(opcode, mbt_data))
if opcode == 0x0: # grp voice channel grant
ch1 = (mbt_data >> 64) & 0xffff
ch2 = (mbt_data >> 48) & 0xffff
ga = (mbt_data >> 32) & 0xffff
f = self.channel_id_to_frequency(ch1)
self.update_voice_frequency(f, tgid=ga, tdma_slot=self.get_tdma_slot(ch1))
self.update_voice_frequency(f, tgid=ga, tdma_slot=self.get_tdma_slot(ch1), srcaddr=src)
if f:
updated += 1
if self.debug > 10:
print "mbt00 voice grant ch1 %x ch2 %x addr 0x%x" %(ch1, ch2, ga)
sys.stderr.write('mbt00 voice grant ch1 %x ch2 %x addr 0x%x\n' %(ch1, ch2, ga))
elif opcode == 0x3c: # adjacent status
syid = (header >> 48) & 0xfff
rfid = (header >> 24) & 0xff
@ -248,8 +292,9 @@ class trunked_system (object):
f2 = self.channel_id_to_frequency(ch2)
if f1 and f2:
self.adjacent[f1] = 'rfid: %d stid:%d uplink:%f' % (rfid, stid, f2 / 1000000.0)
self.adjacent_data[f1] = {'rfid': rfid, 'stid':stid, 'uplink': f2, 'table': None, 'sysid': syid}
if self.debug > 10:
print "mbt3c adjacent sys %x rfid %x stid %x ch1 %x ch2 %x f1 %s f2 %s" %(syid, rfid, stid, ch1, ch2, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2))
sys.stderr.write('mbt3c adjacent sys %x rfid %x stid %x ch1 %x ch2 %x f1 %s f2 %s\n' % (syid, rfid, stid, ch1, ch2, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2)))
elif opcode == 0x3b: # network status
syid = (header >> 48) & 0xfff
wacn = (mbt_data >> 76) & 0xfffff
@ -262,7 +307,7 @@ class trunked_system (object):
self.ns_wacn = wacn
self.ns_chan = f1
if self.debug > 10:
print "mbt3b net stat sys %x wacn %x ch1 %s ch2 %s" %(syid, wacn, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2))
sys.stderr.write('mbt3b net stat sys %x wacn %x ch1 %s ch2 %s\n' %(syid, wacn, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2)))
elif opcode == 0x3a: # rfss status
syid = (header >> 48) & 0xfff
rfid = (mbt_data >> 88) & 0xff
@ -278,9 +323,9 @@ class trunked_system (object):
self.rfss_chan = f1
self.rfss_txchan = f2
if self.debug > 10:
print "mbt3a rfss stat sys %x rfid %x stid %x ch1 %s ch2 %s" %(syid, rfid, stid, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2))
#else:
# print "mbt other %x" % opcode
sys.stderr.write('mbt3a rfss stat sys %x rfid %x stid %x ch1 %s ch2 %s\n' %(syid, rfid, stid, self.channel_id_to_string(ch1), self.channel_id_to_string(ch2)))
else:
sys.stderr.write('decode_mbt_data(): received unsupported mbt opcode %x\n' % opcode)
return updated
def decode_tsbk(self, tsbk):
@ -288,13 +333,10 @@ class trunked_system (object):
self.last_tsbk = time.time()
self.stats['tsbks'] += 1
updated = 0
#if crc16(tsbk, 12) != 0:
# self.stats['crc'] += 1
# return # crc check failed
tsbk = tsbk << 16 # for missing crc
opcode = (tsbk >> 88) & 0x3f
if self.debug > 10:
print "TSBK: 0x%02x 0x%024x" % (opcode, tsbk)
sys.stderr.write('TSBK: 0x%02x 0x%024x\n' % (opcode, tsbk))
if opcode == 0x00: # group voice chan grant
mfrid = (tsbk >> 80) & 0xff
if mfrid == 0x90: # MOT_GRG_ADD_CMD
@ -303,18 +345,18 @@ class trunked_system (object):
ga2 = (tsbk >> 32) & 0xffff
ga3 = (tsbk >> 16) & 0xffff
if self.debug > 10:
print "MOT_GRG_ADD_CMD(0x00): sg:%d ga1:%d ga2:%d ga3:%d" % (sg, ga1, ga2, ga3)
sys.stderr.write('MOT_GRG_ADD_CMD(0x00): sg:%d ga1:%d ga2:%d ga3:%d\n' % (sg, ga1, ga2, ga3))
else:
opts = (tsbk >> 72) & 0xff
ch = (tsbk >> 56) & 0xffff
ga = (tsbk >> 40) & 0xffff
sa = (tsbk >> 16) & 0xffffff
f = self.channel_id_to_frequency(ch)
self.update_voice_frequency(f, tgid=ga, tdma_slot=self.get_tdma_slot(ch))
self.update_voice_frequency(f, tgid=ga, tdma_slot=self.get_tdma_slot(ch), srcaddr=sa)
if f:
updated += 1
if self.debug > 10:
print "tsbk00 grant freq %s ga %d sa %d" % (self.channel_id_to_string(ch), ga, sa)
sys.stderr.write('tsbk00 grant freq %s ga %d sa %d\n' % (self.channel_id_to_string(ch), ga, sa))
elif opcode == 0x01: # reserved
mfrid = (tsbk >> 80) & 0xff
if mfrid == 0x90: #MOT_GRG_DEL_CMD
@ -323,7 +365,7 @@ class trunked_system (object):
ga2 = (tsbk >> 32) & 0xffff
ga3 = (tsbk >> 16) & 0xffff
if self.debug > 10:
print "MOT_GRG_DEL_CMD(0x01): sg:%d ga1:%d ga2:%d ga3:%d" % (sg, ga1, ga2, ga3)
sys.stderr.write('MOT_GRG_DEL_CMD(0x01): sg:%d ga1:%d ga2:%d ga3:%d\n' % (sg, ga1, ga2, ga3))
elif opcode == 0x02: # group voice chan grant update
mfrid = (tsbk >> 80) & 0xff
if mfrid == 0x90:
@ -331,11 +373,11 @@ class trunked_system (object):
sg = (tsbk >> 40) & 0xffff
sa = (tsbk >> 16) & 0xffffff
f = self.channel_id_to_frequency(ch)
self.update_voice_frequency(f, tgid=sg, tdma_slot=self.get_tdma_slot(ch))
self.update_voice_frequency(f, tgid=sg, tdma_slot=self.get_tdma_slot(ch), srcaddr=sa)
if f:
updated += 1
if self.debug > 10:
print "MOT_GRG_CN_GRANT(0x02): freq %s sg:%d sa:%d" % (self.channel_id_to_string(ch), sg, sa)
sys.stderr.write('MOT_GRG_CN_GRANT(0x02): freq %s sg:%d sa:%d\n' % (self.channel_id_to_string(ch), sg, sa))
else:
ch1 = (tsbk >> 64) & 0xffff
ga1 = (tsbk >> 48) & 0xffff
@ -351,7 +393,7 @@ class trunked_system (object):
if f2:
updated += 1
if self.debug > 10:
print "tsbk02 grant update: chan %s %d %s %d" %(self.channel_id_to_string(ch1), ga1, self.channel_id_to_string(ch2), ga2)
sys.stderr.write('tsbk02 grant update: chan %s %d %s %d\n' %(self.channel_id_to_string(ch1), ga1, self.channel_id_to_string(ch2), ga2))
elif opcode == 0x03: # group voice chan grant update exp : TIA.102-AABC-B-2005 page 56
mfrid = (tsbk >> 80) & 0xff
if mfrid == 0x90: #MOT_GRG_CN_GRANT_UPDT
@ -369,7 +411,7 @@ class trunked_system (object):
if f2:
updated += 1
if self.debug > 10:
print "MOT_GRG_CN_GRANT_UPDT(0x03): freq %s sg1:%d freq %s sg2:%d" % (self.channel_id_to_string(ch1), sg1, self.channel_id_to_string(ch2), sg2)
sys.stderr.write('MOT_GRG_CN_GRANT_UPDT(0x03): freq %s sg1:%d freq %s sg2:%d\n' % (self.channel_id_to_string(ch1), sg1, self.channel_id_to_string(ch2), sg2))
elif mfrid == 0:
ch1 = (tsbk >> 48) & 0xffff
ch2 = (tsbk >> 32) & 0xffff
@ -379,13 +421,22 @@ class trunked_system (object):
if f:
updated += 1
if self.debug > 10:
print "tsbk03: freq-t %s freq-r %s ga:%d" % (self.channel_id_to_string(ch1), self.channel_id_to_string(ch2), ga)
sys.stderr.write('tsbk03: freq-t %s freq-r %s ga:%d\n' % (self.channel_id_to_string(ch1), self.channel_id_to_string(ch2), ga))
elif opcode == 0x16: # sndcp data ch
ch1 = (tsbk >> 48) & 0xffff
ch2 = (tsbk >> 32) & 0xffff
if self.debug > 10:
print "tsbk16 sndcp data ch: chan %x %x" %(ch1, ch2)
sys.stderr.write('tsbk16 sndcp data ch: chan %x %x\n' % (ch1, ch2))
elif opcode == 0x28: # grp_aff_rsp
mfrid = (tsbk >> 80) & 0xff
lg = (tsbk >> 79) & 0x01
gav = (tsbk >> 72) & 0x03
aga = (tsbk >> 56) & 0xffff
ga = (tsbk >> 40) & 0xffff
ta = (tsbk >> 16) & 0xffffff
if self.debug > 10:
sys.stderr.write('tsbk28 grp_aff_resp: mfrid: 0x%x, gav: %d, aga: %d, ga: %d, ta: %d\n' % (mfrid, gav, aga, ga, ta))
elif opcode == 0x34: # iden_up vhf uhf
iden = (tsbk >> 76) & 0xf
bwvu = (tsbk >> 72) & 0xf
@ -402,7 +453,7 @@ class trunked_system (object):
self.freq_table[iden]['step'] = spac * 125
self.freq_table[iden]['frequency'] = freq * 5
if self.debug > 10:
print "tsbk34 iden vhf/uhf id %d toff %f spac %f freq %f [%s]" % (iden, toff * spac * 0.125 * 1e-3, spac * 0.125, freq * 0.000005, txt[toff_sign])
sys.stderr.write('tsbk34 iden vhf/uhf id %d toff %f spac %f freq %f [%s]\n' % (iden, toff * spac * 0.125 * 1e-3, spac * 0.125, freq * 0.000005, txt[toff_sign]))
elif opcode == 0x33: # iden_up_tdma
mfrid = (tsbk >> 80) & 0xff
if mfrid == 0:
@ -422,7 +473,7 @@ class trunked_system (object):
self.freq_table[iden]['frequency'] = f1 * 5
self.freq_table[iden]['tdma'] = slots_per_carrier[channel_type]
if self.debug > 10:
print "tsbk33 iden up tdma id %d f %d offset %d spacing %d slots/carrier %d" % (iden, self.freq_table[iden]['frequency'], self.freq_table[iden]['offset'], self.freq_table[iden]['step'], self.freq_table[iden]['tdma'])
sys.stderr.write('tsbk33 iden up tdma id %d f %d offset %d spacing %d slots/carrier %d\n' % (iden, self.freq_table[iden]['frequency'], self.freq_table[iden]['offset'], self.freq_table[iden]['step'], self.freq_table[iden]['tdma']))
elif opcode == 0x3d: # iden_up
iden = (tsbk >> 76) & 0xf
@ -440,7 +491,7 @@ class trunked_system (object):
self.freq_table[iden]['step'] = spac * 125
self.freq_table[iden]['frequency'] = freq * 5
if self.debug > 10:
print "tsbk3d iden id %d toff %f spac %f freq %f" % (iden, toff * 0.25, spac * 0.125, freq * 0.000005)
sys.stderr.write('tsbk3d iden id %d toff %f spac %f freq %f\n' % (iden, toff * 0.25, spac * 0.125, freq * 0.000005))
elif opcode == 0x3a: # rfss status
syid = (tsbk >> 56) & 0xfff
rfid = (tsbk >> 48) & 0xff
@ -454,7 +505,7 @@ class trunked_system (object):
self.rfss_chan = f1
self.rfss_txchan = f1 + self.freq_table[chan >> 12]['offset']
if self.debug > 10:
print "tsbk3a rfss status: syid: %x rfid %x stid %d ch1 %x(%s)" %(syid, rfid, stid, chan, self.channel_id_to_string(chan))
sys.stderr.write('tsbk3a rfss status: syid: %x rfid %x stid %d ch1 %x(%s)\n' %(syid, rfid, stid, chan, self.channel_id_to_string(chan)))
elif opcode == 0x39: # secondary cc
rfid = (tsbk >> 72) & 0xff
stid = (tsbk >> 64) & 0xff
@ -468,7 +519,7 @@ class trunked_system (object):
sorted_freqs = collections.OrderedDict(sorted(self.secondary.items()))
self.secondary = sorted_freqs
if self.debug > 10:
print "tsbk39 secondary cc: rfid %x stid %d ch1 %x(%s) ch2 %x(%s)" %(rfid, stid, ch1, self.channel_id_to_string(ch1), ch2, self.channel_id_to_string(ch2))
sys.stderr.write('tsbk39 secondary cc: rfid %x stid %d ch1 %x(%s) ch2 %x(%s)\n' %(rfid, stid, ch1, self.channel_id_to_string(ch1), ch2, self.channel_id_to_string(ch2)))
elif opcode == 0x3b: # network status
wacn = (tsbk >> 52) & 0xfffff
syid = (tsbk >> 40) & 0xfff
@ -479,8 +530,9 @@ class trunked_system (object):
self.ns_wacn = wacn
self.ns_chan = f1
if self.debug > 10:
print "tsbk3b net stat: wacn %x syid %x ch1 %x(%s)" %(wacn, syid, ch1, self.channel_id_to_string(ch1))
sys.stderr.write('tsbk3b net stat: wacn %x syid %x ch1 %x(%s)\n' %(wacn, syid, ch1, self.channel_id_to_string(ch1)))
elif opcode == 0x3c: # adjacent status
syid = (tsbk >> 56) & 0xfff
rfid = (tsbk >> 48) & 0xff
stid = (tsbk >> 40) & 0xff
ch1 = (tsbk >> 24) & 0xffff
@ -488,28 +540,30 @@ class trunked_system (object):
f1 = self.channel_id_to_frequency(ch1)
if f1 and table in self.freq_table:
self.adjacent[f1] = 'rfid: %d stid:%d uplink:%f tbl:%d' % (rfid, stid, (f1 + self.freq_table[table]['offset']) / 1000000.0, table)
self.adjacent_data[f1] = {'rfid': rfid, 'stid':stid, 'uplink': f1 + self.freq_table[table]['offset'], 'table': table, 'sysid':syid}
if self.debug > 10:
print "tsbk3c adjacent: rfid %x stid %d ch1 %x(%s)" %(rfid, stid, ch1, self.channel_id_to_string(ch1))
sys.stderr.write('tsbk3c adjacent: rfid %x stid %d ch1 %x(%s)\n' %(rfid, stid, ch1, self.channel_id_to_string(ch1)))
if table in self.freq_table:
print "tsbk3c : %s %s" % (self.freq_table[table]['frequency'] , self.freq_table[table]['step'] )
sys.stderr.write('tsbk3c : %s %s\n' % (self.freq_table[table]['frequency'] , self.freq_table[table]['step'] ))
#else:
# print "tsbk other %x" % opcode
# sys.stderr.write('tsbk other %x\n' % opcode)
return updated
def hunt_cc(self, curr_time):
if self.cc_timeouts < 6:
return
return False
self.cc_timeouts = 0
self.cc_list_index += 1
if self.cc_list_index >= len(self.cc_list):
self.cc_list_index = 0
self.trunk_cc = self.cc_list[self.cc_list_index]
sys.stderr.write('%f set trunk_cc to %s\n' % (curr_time, self.trunk_cc))
def get_int_dict(s):
if s[0].isdigit():
return dict.fromkeys([int(d) for d in s.split(',')])
return dict.fromkeys([int(d) for d in open(s).readlines()])
if self.trunk_cc != self.last_trunk_cc:
self.last_trunk_cc = self.trunk_cc
if self.debug >=5:
sys.stderr.write('%f set control channel: %f\n' % (curr_time, self.trunk_cc / 1000000.0))
return True
return False
class rx_ctl (object):
def __init__(self, debug=0, frequency_set=None, conf_file=None, logfile_workers=None):
@ -537,23 +591,25 @@ class rx_ctl (object):
self.wait_until = time.time()
self.configs = {}
self.nacs = []
self.last_tdma_vf = 0
self.P2_GRACE_TIME = 1.0 # TODO: make more configurable
self.logfile_workers = logfile_workers
self.active_talkgroups = {}
self.working_frequencies = {}
self.xor_cache = {}
self.last_garbage_collect = 0
self.last_command = {'command': None, 'time': time.time()}
if self.logfile_workers:
self.input_rate = self.logfile_workers[0]['demod'].input_rate
self.enabled_nacs = None
if conf_file:
if conf_file.endswith('.tsv'):
self.build_config_tsv(conf_file)
elif conf_file.endswith('.json'):
self.build_config_json(conf_file)
else:
self.build_config(conf_file)
self.nacs = self.configs.keys()
self.current_nac = self.nacs[0]
self.current_nac = self.find_next_tsys()
self.current_state = self.states.CC
tsys = self.trunked_systems[self.current_nac]
@ -574,45 +630,46 @@ class rx_ctl (object):
'wacn': None,
'sysid': None})
def build_config_json(self, conf_file):
d = json.loads(open(conf_file).read())
chans = [x for x in d['channels'] if x['active'] and x['trunked']]
self.configs = { chan['nac']: {'cclist':chan['cclist'],
'offset':0,
'blacklist': {int(tgid):None for tgid in chan['blacklist']},
'whitelist': {int(tgid):None for tgid in chan['whitelist']},
'sysname': chan['name'],
'center_frequency': chan['frequency'],
'modulation': chan['demod_type'],
'tgid_map': {int(tgid): chan['tgids'][tgid] for tgid in chan['tgids'].keys()}}
for chan in chans}
for nac in self.configs.keys():
self.add_trunked_system(nac)
def set_frequency(self, params):
frequency = params['freq']
if frequency and self.frequency_set:
self.frequency_set(params)
def enable_status(self, s):
if self.debug >= 10:
sys.stderr.write('rx_ctl: enable_status: %s\n' % s)
nacs = s.split(',')
if s and len(nacs):
nacs = [int(x) for x in nacs]
else:
sys.stderr.write('cannot disable all NACs - request ignored\n')
return
self.enabled_nacs = nacs
def add_trunked_system(self, nac):
assert nac not in self.trunked_systems # duplicate nac not allowed
blacklist = {}
whitelist = None
tgid_map = {}
cfg = None
if nac in self.configs:
cfg = self.configs[nac]
self.trunked_systems[nac] = trunked_system(debug = self.debug, config=cfg)
def build_config_tsv(self, tsv_filename):
import csv
hdrmap = []
configs = {}
with open(tsv_filename, 'rb') as csvfile:
sreader = csv.reader(csvfile, delimiter='\t', quotechar='"', quoting=csv.QUOTE_ALL)
for row in sreader:
if not hdrmap:
# process first line of tsv file - header line
for hdr in row:
hdr = hdr.replace(' ', '_')
hdr = hdr.lower()
hdrmap.append(hdr)
continue
fields = {}
for i in xrange(len(row)):
if row[i]:
fields[hdrmap[i]] = row[i]
if hdrmap[i] != 'sysname':
fields[hdrmap[i]] = fields[hdrmap[i]].lower()
nac = int(fields['nac'], 0)
configs[nac] = fields
self.setup_config(configs)
self.setup_config(load_tsv(tsv_filename))
def build_config(self, config_filename):
import ConfigParser
@ -656,44 +713,40 @@ class rx_ctl (object):
self.nacs.append(nac)
def setup_config(self, configs):
for nac in configs:
self.configs[nac] = {'cclist':[], 'offset':0, 'whitelist':None, 'blacklist':{}, 'tgid_map':{}, 'sysname': configs[nac]['sysname'], 'center_frequency': None}
for f in configs[nac]['control_channel_list'].split(','):
self.configs[nac]['cclist'].append(get_frequency(f))
if 'offset' in configs[nac]:
self.configs[nac]['offset'] = int(configs[nac]['offset'])
if 'modulation' in configs[nac]:
self.configs[nac]['modulation'] = configs[nac]['modulation']
else:
self.configs[nac]['modulation'] = 'cqpsk'
for k in ['whitelist', 'blacklist']:
if k in configs[nac]:
self.configs[nac][k] = get_int_dict(configs[nac][k])
if 'tgid_tags_file' in configs[nac]:
import csv
with open(configs[nac]['tgid_tags_file'], 'rb') as csvfile:
sreader = csv.reader(csvfile, delimiter='\t', quotechar='"', quoting=csv.QUOTE_ALL)
for row in sreader:
tgid = int(row[0])
txt = row[1]
self.configs[nac]['tgid_map'][tgid] = txt
if 'center_frequency' in configs[nac]:
self.configs[nac]['center_frequency'] = get_frequency(configs[nac]['center_frequency'])
self.configs = make_config(configs)
for nac in self.configs.keys():
self.add_trunked_system(nac)
def find_next_tsys(self):
self.current_id += 1
if self.current_id >= len(self.nacs):
self.current_id = 0
return self.nacs[self.current_id]
wrap = 0
while True:
self.current_id += 1
if self.current_id >= len(self.nacs):
if wrap:
break
self.current_id = 0
wrap = 1
if self.enabled_nacs is not None and self.nacs[self.current_id] not in self.enabled_nacs:
continue
return self.nacs[self.current_id]
return self.nacs[0] ## should not occur
def to_json(self):
current_time = time.time()
d = {'json_type': 'trunk_update'}
for nac in self.trunked_systems.keys():
d[nac] = json.loads(self.trunked_systems[nac].to_json())
d['data'] = {'last_command': self.last_command['command'],
'last_command_time': int(self.last_command['time'] - current_time),
'tgid_hold': self.tgid_hold,
'tgid_hold_until': int(self.tgid_hold_until - current_time),
'hold_mode': self.hold_mode}
return json.dumps(d)
def dump_tgids(self):
for nac in self.trunked_systems.keys():
self.trunked_systems[nac].dump_tgids()
def to_string(self):
s = ''
for nac in self.trunked_systems:
@ -705,37 +758,60 @@ class rx_ctl (object):
type = msg.type()
updated = 0
curr_time = time.time()
if type == -2: # request from gui
if type == -3: # P25 call signalling data
if self.debug > 10:
sys.stderr.write("%f process_qmsg: P25 info: %s\n" % (time.time(), msg.to_string()))
js = json.loads(msg.to_string())
nac = js['nac']
if nac != self.current_nac:
return
tsys = self.trunked_systems[nac]
if 'srcaddr' in js.keys():
tsys.current_srcaddr = js['srcaddr']
if 'grpaddr' in js.keys():
tsys.current_grpaddr = js['grpaddr']
if 'algid' in js.keys():
tsys.current_algid = js['algid']
if 'alg' in js.keys():
tsys.current_alg = js['alg']
if 'keyid' in js.keys():
tsys.current_keyid = js['keyid']
return
elif type == -2: # request from gui
cmd = msg.to_string()
if self.debug > 10:
print "process_qmsg: command: %s" % cmd
sys.stderr.write('process_qmsg: command: %s\n' % cmd)
self.update_state(cmd, curr_time)
return
elif type == -1: # timeout
if self.debug:
print "process_data_unit timeout"
if self.debug > 10:
sys.stderr.write('%f process_data_unit timeout\n' % time.time())
self.update_state('timeout', curr_time)
if self.logfile_workers:
self.logging_scheduler(curr_time)
return
elif type < 0:
print 'unknown message type %d' % (type)
sys.stderr.write('unknown message type %d\n' % (type))
return
s = msg.to_string()
# nac is always 1st two bytes
nac = (ord(s[0]) << 8) + ord(s[1])
#assert nac != 0xffff # FIXME: uncomment this after any remaining usages of 0xffff removed
if nac == 0xffff:
# TDMA
self.update_state('tdma_duid%d' % type, curr_time)
return
if (type != 7) and (type != 12): # TDMA duid (end of call etc)
self.update_state('tdma_duid%d' % type, curr_time)
return
else: # voice channel derived TSBK or MBT PDU
nac = self.current_nac
s = s[2:]
if self.debug > 10:
print "nac %x type %d at %f state %d len %d" %(nac, type, time.time(), self.current_state, len(s))
sys.stderr.write('nac %x type %d at %f state %d len %d\n' %(nac, type, time.time(), self.current_state, len(s)))
if (type == 7 or type == 12) and nac not in self.trunked_systems:
if not self.configs:
# TODO: allow whitelist/blacklist rather than blind automatic-add
self.add_trunked_system(nac)
else:
sys.stderr.write("%f NAC %x not configured\n" % (time.time(), nac))
return
if type == 7: # trunk: TSBK
t = 0
@ -743,19 +819,28 @@ class rx_ctl (object):
t = (t << 8) + ord(c)
updated += self.trunked_systems[nac].decode_tsbk(t)
elif type == 12: # trunk: MBT
s1 = s[:10]
s2 = s[10:]
s1 = s[:10] # header without crc
s2 = s[12:]
header = mbt_data = 0
for c in s1:
header = (header << 8) + ord(c)
for c in s2:
mbt_data = (mbt_data << 8) + ord(c)
fmt = (header >> 72) & 0x1f
sap = (header >> 64) & 0x3f
src = (header >> 48) & 0xffffff
if fmt != 0x17: # only Extended Format MBT presently supported
return
opcode = (header >> 16) & 0x3f
if self.debug > 10:
print "type %d at %f state %d len %d/%d opcode %x [%x/%x]" %(type, time.time(), self.current_state, len(s1), len(s2), opcode, header,mbt_data)
updated += self.trunked_systems[nac].decode_mbt_data(opcode, header << 16, mbt_data << 32)
sys.stderr.write('type %d at %f state %d len %d/%d opcode %x [%x/%x]\n' %(type, time.time(), self.current_state, len(s1), len(s2), opcode, header,mbt_data))
updated += self.trunked_systems[nac].decode_mbt_data(opcode, src, header << 16, mbt_data << 32)
if nac != self.current_nac:
if self.debug > 10: # this is occasionally expected if cycling between different tsys
sys.stderr.write("%f received NAC %x does not match expected NAC %x\n" % (time.time(), nac, self.current_nac))
return
if self.logfile_workers:
@ -779,7 +864,7 @@ class rx_ctl (object):
self.working_frequencies[frequency]['worker']['demod'].set_relative_frequency(0)
self.working_frequencies[frequency]['worker']['active'] = False
self.working_frequencies.pop(frequency)
print '%f release worker frequency %d' % (curr_time, frequency)
sys.stderr.write('%f release worker frequency %d\n' % (curr_time, frequency))
def free_talkgroup(self, frequency, tgid, curr_time):
decoder = self.working_frequencies[frequency]['worker']['decoder']
@ -787,10 +872,8 @@ class rx_ctl (object):
index = tdma_slot
if tdma_slot is None:
index = 0
filename = 'idle-channel-%d-%d-%f.wav' % (frequency, index, curr_time)
decoder.set_output(filename, index=index)
self.working_frequencies[frequency]['tgids'].pop(tgid)
print '%f release tgid %d frequency %d' % (curr_time, tgid, frequency)
sys.stderr.write('%f release tgid %d frequency %d\n' % (curr_time, tgid, frequency))
def logging_scheduler(self, curr_time):
tsys = self.trunked_systems[self.current_nac]
@ -800,14 +883,14 @@ class rx_ctl (object):
# see if this tgid active on any other freq(s)
other_freqs = [f for f in self.working_frequencies if f != frequency and tgid in self.working_frequencies[f]['tgids']]
if other_freqs:
print '%f tgid %d slot %s frequency %d found on other frequencies %s' % (curr_time, tgid, tdma_slot, frequency, ','.join(['%s' % f for f in other_freqs]))
sys.stderr.write('%f tgid %d slot %s frequency %d found on other frequencies %s\n' % (curr_time, tgid, tdma_slot, frequency, ','.join(['%s' % f for f in other_freqs])))
for f in other_freqs:
self.free_talkgroup(f, tgid, curr_time)
if not self.working_frequencies[f]['tgids']:
self.free_frequency(f, curr_time)
diff = abs(tsys.center_frequency - frequency)
if diff > self.input_rate/2:
#print '%f request for frequency %d tgid %d failed, offset %d exceeds maximum %d' % (curr_time, frequency, tgid, diff, self.input_rate/2)
#sys.stderr.write('%f request for frequency %d tgid %d failed, offset %d exceeds maximum %d\n' % (curr_time, frequency, tgid, diff, self.input_rate/2))
continue
update = True
@ -817,25 +900,28 @@ class rx_ctl (object):
if tgids[tgid]['tdma_slot'] == tdma_slot:
update = False
else:
print '%f slot switch %s was %s tgid %d frequency %d' % (curr_time, tdma_slot, tgids[tgid]['tdma_slot'], tgid, frequency)
sys.stderr.write('%f slot switch %s was %s tgid %d frequency %d\n' % (curr_time, tdma_slot, tgids[tgid]['tdma_slot'], tgid, frequency))
worker = self.working_frequencies[frequency]['worker']
else:
#active_tdma_slots = [tgids[tg]['tdma_slot'] for tg in tgids]
print '%f new tgid %d slot %s arriving on already active frequency %d' % (curr_time, tgid, tdma_slot, frequency)
sys.stderr.write("%f new tgid %d slot %s arriving on already active frequency %d\n" % (curr_time, tgid, tdma_slot, frequency))
previous_tgid = [id for id in tgids if tgids[id]['tdma_slot'] == tdma_slot]
assert len(previous_tgid) == 1 ## check for logic error
self.free_talkgroup(frequency, previous_tgid[0], curr_time)
worker = self.working_frequencies[frequency]['worker']
else:
worker = self.find_available_worker()
if worker is None:
print '*** error, no free demodulators, freq %d tgid %d' % (frequency, tgid)
sys.stderr.write('*** error, no free demodulators, freq %d tgid %d\n' % (frequency, tgid))
continue
self.working_frequencies[frequency] = {'tgids' : {}, 'worker': worker}
worker['demod'].set_relative_frequency(tsys.center_frequency - frequency)
print '%f starting worker frequency %d tg %d slot %s' % (curr_time, frequency, tgid, tdma_slot)
sys.stderr.write('%f starting worker frequency %d tg %d slot %s\n' % (curr_time, frequency, tgid, tdma_slot))
self.working_frequencies[frequency]['tgids'][tgid] = {'updated': curr_time, 'tdma_slot': tdma_slot}
if not update:
continue
filename = 'tgid-%d-%f.wav' % (tgid, curr_time)
print '%f update frequency %d tg %d slot %s file %s' % (curr_time, frequency, tgid, tdma_slot, filename)
sys.stderr.write('%f update frequency %d tg %d slot %s file %s\n' % (curr_time, frequency, tgid, tdma_slot, filename))
# set demod speed, decoder slot, output file name
demod = worker['demod']
decoder = worker['decoder']
@ -850,6 +936,7 @@ class rx_ctl (object):
if xorhash not in self.xor_cache:
self.xor_cache[xorhash] = lfsr.p25p2_lfsr(self.current_nac, tsys.ns_syid, tsys.ns_wacn).xor_chars
decoder.set_xormask(self.xor_cache[xorhash], xorhash, index=index)
decoder.set_nac(self.current_nac, index=index)
demod.set_omega(symbol_rate)
decoder.set_output(filename, index=index)
@ -887,33 +974,67 @@ class rx_ctl (object):
if command == 'timeout':
if self.current_state == self.states.CC:
if self.debug > 0:
sys.stderr.write("[%f] control channel timeout\n" % time.time())
sys.stderr.write("%f control channel timeout\n" % time.time())
tsys.cc_timeouts += 1
elif self.current_state != self.states.CC and curr_time - self.last_tdma_vf > self.P2_GRACE_TIME:
if self.debug > 0:
sys.stderr.write("[%f] voice timeout\n" % time.time())
elif self.current_state != self.states.CC:
if self.debug > 1:
sys.stderr.write("%f voice timeout\n" % time.time())
if self.hold_mode is False:
self.current_tgid = None
new_state = self.states.CC
new_frequency = tsys.trunk_cc
elif command == 'update':
if self.current_state == self.states.CC:
desired_tgid = None
if self.tgid_hold_until > curr_time:
if (self.tgid_hold is not None) and (self.tgid_hold_until > curr_time):
if self.debug > 1:
sys.stderr.write("%f hold active tg(%s)\n" % (time.time(), self.tgid_hold))
desired_tgid = self.tgid_hold
new_frequency, new_tgid, tdma_slot = tsys.find_talkgroup(curr_time, tgid=desired_tgid)
elif (self.tgid_hold is not None) and (self.hold_mode == False):
self.tgid_hold = None
new_frequency, new_tgid, tdma_slot, srcaddr = tsys.find_talkgroup(curr_time, tgid=desired_tgid, hold=self.hold_mode)
if new_frequency:
if self.debug > 0:
sys.stderr.write("[%f] voice update: tg(%s), freq(%s), slot(%s)\n" % (time.time(), new_tgid, new_frequency, tdma_slot))
tslot = tdma_slot if tdma_slot is not None else '-'
sys.stderr.write("%f voice update: tg(%s), freq(%s), slot(%s), prio(%d)\n" % (time.time(), new_tgid, new_frequency, tslot, tsys.get_prio(new_tgid)))
new_state = self.states.TO_VC
self.current_tgid = new_tgid
tsys.current_srcaddr = srcaddr
self.tgid_hold = new_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
self.wait_until = curr_time + self.TSYS_HOLD_TIME
new_slot = tdma_slot
elif command == 'tdma_duid3': # tdma termination, no channel release (MAC_HANGTIME)
else: # check for priority tgid preemption
new_frequency, new_tgid, tdma_slot, srcaddr = tsys.find_talkgroup(tsys.talkgroups[self.current_tgid]['time'], tgid=self.current_tgid, hold=self.hold_mode)
if new_tgid != self.current_tgid:
if self.debug > 0:
tslot = tdma_slot if tdma_slot is not None else '-'
sys.stderr.write("%f voice preempt: tg(%s), freq(%s), slot(%s), prio(%d)\n" % (time.time(), new_tgid, new_frequency, tslot, tsys.get_prio(new_tgid)))
new_state = self.states.TO_VC
self.current_tgid = new_tgid
tsys.current_srcaddr = srcaddr
self.tgid_hold = new_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
self.wait_until = curr_time + self.TSYS_HOLD_TIME
new_slot = tdma_slot
else:
new_frequency = None
elif command == 'duid3' or command == 'tdma_duid3': # termination, no channel release
if self.current_state != self.states.CC:
self.tgid_hold = self.current_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
self.wait_until = curr_time + self.TSYS_HOLD_TIME
self.last_tdma_vf = curr_time
elif command == 'duid3' or command == 'duid15' or command == 'tdma_duid15': # fdma/tdma termination with channel release
elif command == 'duid15' or command == 'tdma_duid15': # termination with channel release
if self.current_state != self.states.CC:
if self.debug > 1:
sys.stderr.write("%f %s, tg(%d)\n" % (time.time(), command, self.current_tgid))
tsys.current_srcaddr = 0
tsys.current_grpaddr = 0
self.wait_until = curr_time + self.TSYS_HOLD_TIME
self.tgid_hold = self.current_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
if self.hold_mode is False:
self.current_tgid = None
new_state = self.states.CC
new_frequency = tsys.trunk_cc
elif command == 'duid0' or command == 'duid5' or command == 'duid10' or command == 'tdma_duid5':
@ -922,35 +1043,39 @@ class rx_ctl (object):
self.tgid_hold = self.current_tgid
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
self.wait_until = curr_time + self.TSYS_HOLD_TIME
if command == 'tdma_duid5':
self.last_tdma_vf = curr_time
elif command == 'duid7' or command == 'duid12':
elif command == 'duid7' or command == 'duid12': # tsbk/pdu should never arrive here...
pass
elif command == 'hold':
self.last_command = {'command': command, 'time': curr_time}
if self.hold_mode is False and self.current_tgid:
self.tgid_hold = self.current_tgid
self.tgid_hold_until = curr_time + 86400 * 10000
self.hold_mode = True
if self.debug > 0:
sys.stderr.write ('set hold until %f tgid %s\n' % (self.tgid_hold_until, self.current_tgid))
sys.stderr.write ('%f set hold tg(%s) until %f\n' % (time.time(), self.current_tgid, self.tgid_hold_until))
elif self.hold_mode is True:
self.current_tgid = None
self.tgid_hold = None
self.tgid_hold_until = curr_time
self.hold_mode = False
elif command == 'set_hold':
self.last_command = {'command': command, 'time': curr_time}
if self.current_tgid:
self.tgid_hold = self.current_tgid
self.tgid_hold_until = curr_time + 86400 * 10000
self.hold_mode = True
print 'set hold until %f' % self.tgid_hold_until
elif command == 'unset_hold':
self.last_command = {'command': command, 'time': curr_time}
if self.current_tgid:
if self.debug > 0:
sys.stderr.write ('%f clear hold tg(%s)\n' % (time.time(), self.tgid_hold))
self.current_tgid = None
self.tgid_hold = None
self.tgid_hold_until = curr_time
self.hold_mode = False
elif command == 'skip' or command == 'lockout':
self.last_command = {'command': command, 'time': curr_time}
if self.current_tgid:
end_time = None
if command == 'skip':
@ -964,23 +1089,41 @@ class rx_ctl (object):
new_state = self.states.CC
new_frequency = tsys.trunk_cc
else:
print 'update_state: unknown command: %s\n' % command
sys.stderr.write('update_state: unknown command: %s\n' % command)
assert 0 == 1
tsys.hunt_cc(curr_time)
hunted_cc = tsys.hunt_cc(curr_time)
if self.wait_until <= curr_time and self.tgid_hold_until <= curr_time and new_state is None:
if self.enabled_nacs is not None and self.current_nac not in self.enabled_nacs:
tsys.current_srcaddr = 0
tsys.current_grpaddr = 0
new_nac = self.find_next_tsys()
new_state = self.states.CC
elif self.current_state != self.states.CC and self.tgid_hold_until <= curr_time and self.hold_mode is False and new_state is None:
if self.debug > 1:
sys.stderr.write("%f release tg(%s)\n" % (time.time(), self.current_tgid))
self.tgid_hold = None
self.current_tgid = None
tsys.current_srcaddr = 0
tsys.current_grpaddr = 0
new_state = self.states.CC
new_frequency = tsys.trunk_cc
elif self.wait_until <= curr_time and self.tgid_hold_until <= curr_time and self.hold_mode is False and new_state is None:
self.wait_until = curr_time + self.TSYS_HOLD_TIME
tsys.current_srcaddr = 0
tsys.current_grpaddr = 0
new_nac = self.find_next_tsys()
new_state = self.states.CC
if new_nac:
if new_nac is not None:
nac = self.current_nac = new_nac
tsys = self.trunked_systems[nac]
new_frequency = tsys.trunk_cc
tsys.current_srcaddr = 0
tsys.current_grpaddr = 0
self.current_tgid = None
if new_frequency:
if new_frequency is not None:
self.set_frequency({
'freq': new_frequency,
'tgid': self.current_tgid,
@ -991,20 +1134,25 @@ class rx_ctl (object):
'center_frequency': tsys.center_frequency,
'tdma': new_slot,
'wacn': tsys.ns_wacn,
'sysid': tsys.ns_syid})
'sysid': tsys.ns_syid,
'srcaddr': tsys.current_srcaddr,
'grpaddr': tsys.current_grpaddr,
'alg': tsys.current_alg,
'algid': tsys.current_algid,
'keyid': tsys.current_keyid })
if new_state:
if new_state is not None:
self.current_state = new_state
def main():
q = 0x3a000012ae01013348704a54
rc = crc16(q,12)
print "should be zero: %x" % rc
sys.stderr.write('should be zero: %x\n' % rc)
assert rc == 0
q = 0x3a001012ae01013348704a54
rc = crc16(q,12)
print "should be nonzero: %x" % rc
sys.stderr.write('should be nonzero: %x\n' % rc)
assert rc != 0
t = trunked_system(debug=255)

View File

@ -0,0 +1,128 @@
# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Max H. Parke KA1RBI
#
# This file is part of OP25
#
# OP25 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, or (at your option)
# any later version.
#
# OP25 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 OP25; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
# 02110-1301, USA.
import sys
import csv
def get_frequency(f): # return frequency in Hz
if f.find('.') == -1: # assume in Hz
return int(f)
else: # assume in MHz due to '.'
return int(float(f) * 1000000)
def get_int_dict(s):
# parameter string s is the file name to be read,
# except when the first character of s is a digit,
# in which case s itself is the comma-separated list of ints
if s[0].isdigit():
return dict.fromkeys([int(d) for d in s.split(',')])
# create dict by reading from file
d = {} # this is the dict
with open(s,"r") as f:
for v in f:
v = v.split("\t",1) # split on tab
try:
v0 = int(v[0]) # first parameter is tgid or start of tgid range
v1 = v0
if (len(v) > 1) and (int(v[1]) > v0): # second parameter if present is end of tgid range
v1 = int(v[1])
for tg in range(v0, (v1 + 1)):
if tg not in d: # is this a new tg?
d[tg] = [] # if so, add to dict (key only, value null)
sys.stderr.write('added talkgroup %d from %s\n' % (tg,s))
except (IndexError, ValueError) as ex:
continue
f.close()
return dict.fromkeys(d)
def utf_ascii(ustr):
return (ustr.decode("utf-8")).encode("ascii", "ignore")
def load_tsv(tsv_filename):
hdrmap = []
configs = {}
with open(tsv_filename, 'rb') as csvfile:
sreader = csv.reader(csvfile, delimiter='\t', quotechar='"', quoting=csv.QUOTE_ALL)
for row in sreader:
if not hdrmap:
# process first line of tsv file - header line
for hdr in row:
hdr = hdr.replace(' ', '_')
hdr = hdr.lower()
hdrmap.append(hdr)
continue
fields = {}
for i in xrange(len(row)):
if row[i]:
fields[hdrmap[i]] = row[i]
if hdrmap[i] != 'sysname':
fields[hdrmap[i]] = fields[hdrmap[i]].lower()
nac = int(fields['nac'], 0)
configs[nac] = fields
return configs
def make_config(configs):
result_config = {}
for nac in configs:
result_config[nac] = {'cclist':[], 'offset':0, 'whitelist':None, 'blacklist':{}, 'tgid_map':{}, 'sysname': configs[nac]['sysname'], 'center_frequency': None}
for f in configs[nac]['control_channel_list'].split(','):
result_config[nac]['cclist'].append(get_frequency(f))
if 'offset' in configs[nac]:
result_config[nac]['offset'] = int(configs[nac]['offset'])
if 'modulation' in configs[nac]:
result_config[nac]['modulation'] = configs[nac]['modulation']
else:
result_config[nac]['modulation'] = 'cqpsk'
for k in ['whitelist', 'blacklist']:
if k in configs[nac]:
result_config[nac][k] = get_int_dict(configs[nac][k])
if 'tgid_tags_file' in configs[nac]:
import csv
with open(configs[nac]['tgid_tags_file'], 'rb') as csvfile:
sreader = csv.reader(csvfile, delimiter='\t', quotechar='"', quoting=csv.QUOTE_ALL)
for row in sreader:
try:
tgid = int(row[0])
txt = utf_ascii(row[1])
except (IndexError, ValueError) as ex:
continue
if len(row) >= 3:
try:
prio = int(row[2])
except ValueError as ex:
prio = 3
else:
prio = 3
result_config[nac]['tgid_map'][tgid] = (txt, prio)
if 'center_frequency' in configs[nac]:
result_config[nac]['center_frequency'] = get_frequency(configs[nac]['center_frequency'])
return result_config
def main():
import json
result = make_config(load_tsv(sys.argv[1]))
print json.dumps(result, indent=4, separators=[',',':'], sort_keys=True)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,41 @@
#################################################################################
#
# config file for DSTAR TX
#
#################################################################################
#
# This file is part of OP25
#
# This 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, or (at your option)
# any later version.
#
# This software 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 this software; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#################################################################################
#
# NOTE
#
# Syntax is unforgiving - no whitespace allowed (outside of comments)
#
#################################################################################
# flags - specify values in hex
flag1=0
flag2=0
flag3=0
# call sign fields - all have an 8-character limit
rptcall2=DIRECT
rptcall1=DIRECT
urcall=CQCQCQ
# # # # # # # # # # # # set your callsign in next line
mycall1=mycall
# appears to be used as a radio model ID - 4-char limit
mycall2=OP25

View File

@ -37,18 +37,21 @@ from math import pi
from op25_c4fm_mod import p25_mod_bf
sys.path.append('..')
from gr_gnuplot import float_sink_f
RC_FILTER = {'dmr': 'rrc', 'p25': 'rc', 'ysf': 'rrc', 'dstar': None}
output_gains = {
'dmr': 5.5,
'dstar': 0.95,
'p25': 5.0,
'p25': 4.5,
'ysf': 5.5
}
gain_adjust = {
'dmr': 3.0,
'dstar': 7.5,
'ysf': 5.0
'ysf': 4.0
}
gain_adjust_fullrate = {
'p25': 2.0,
@ -79,13 +82,15 @@ class my_top_block(gr.top_block):
parser = OptionParser(option_class=eng_option)
parser.add_option("-a", "--args", type="string", default="", help="device args")
parser.add_option("-A", "--alt-modulator-rate", type="int", default=50000, help="when mod rate is not a submutiple of IF rate")
parser.add_option("-b", "--bt", type="float", default=0.5, help="specify bt value")
parser.add_option("-c", "--config-file", type="string", default=None, help="specify the config file name")
parser.add_option("-f", "--file1", type="string", default=None, help="specify the input file slot 1")
parser.add_option("-F", "--file2", type="string", default=None, help="specify the input file slot 2 (DMR)")
parser.add_option("-g", "--gain", type="float", default=1.0, help="input gain")
parser.add_option("-i", "--if-rate", type="float", default=960000, help="output rate to sdr")
parser.add_option("-i", "--if-rate", type="int", default=480000, help="output rate to sdr")
parser.add_option("-I", "--audio-input", type="string", default="", help="pcm input device name. E.g., hw:0,0 or /dev/dsp")
parser.add_option("-k", "--symbol-sink", type="string", default=None, help="write symbols to file (optional)")
parser.add_option("-N", "--gains", type="string", default=None, help="gain settings")
parser.add_option("-O", "--audio-output", type="string", default="default", help="pcm output device name. E.g., hw:0,0 or /dev/dsp")
parser.add_option("-o", "--output-file", type="string", default=None, help="specify the output file")
@ -93,8 +98,10 @@ class my_top_block(gr.top_block):
parser.add_option("-q", "--frequency-correction", type="float", default=0.0, help="ppm")
parser.add_option("-Q", "--frequency", type="float", default=0.0, help="Hz")
parser.add_option("-r", "--repeat", action="store_true", default=False, help="input file repeat")
parser.add_option("-P", "--plot-audio", action="store_true", default=False, help="scope input")
parser.add_option("-R", "--fullrate-mode", action="store_true", default=False, help="ysf fullrate")
parser.add_option("-s", "--sample-rate", type="int", default=48000, help="output sample rate")
parser.add_option("-s", "--modulator-rate", type="int", default=48000, help="must be submultiple of IF rate - see also -A")
parser.add_option("-S", "--alsa-rate", type="int", default=48000, help="sound source/sink sample rate")
parser.add_option("-t", "--test", type="string", default=None, help="test pattern symbol file")
parser.add_option("-v", "--verbose", type="int", default=0, help="additional output")
(options, args) = parser.parse_args()
@ -105,8 +112,8 @@ class my_top_block(gr.top_block):
print 'protocol [-p] option missing'
sys.exit(0)
if options.protocol == 'ysf' or options.protocol == 'dmr':
assert options.config_file # dmr and ysf require config file ("-c FILENAME" option)
if options.protocol == 'ysf' or options.protocol == 'dmr' or options.protocol == 'dstar':
assert options.config_file # dstar, dmr and ysf require config file ("-c FILENAME" option)
output_gain = output_gains[options.protocol]
@ -124,7 +131,7 @@ class my_top_block(gr.top_block):
ENCODER = op25_repeater.dstar_tx_sb(options.verbose, options.config_file)
elif options.protocol == 'p25':
ENCODER = op25_repeater.vocoder(True, # 0=Decode,True=Encode
0, # Verbose flag
False, # Verbose flag
0, # flex amount
"", # udp ip address
0, # udp port
@ -135,7 +142,7 @@ class my_top_block(gr.top_block):
ENCODER.set_gain_adjust(gain_adjust_fullrate['ysf'])
else:
ENCODER.set_gain_adjust(gain_adjust['ysf'])
if options.protocol == 'p25':
if options.protocol == 'p25' and not options.test:
ENCODER.set_gain_adjust(gain_adjust_fullrate[options.protocol])
elif not options.test and not options.protocol == 'ysf':
ENCODER.set_gain_adjust(gain_adjust[options.protocol])
@ -145,13 +152,16 @@ class my_top_block(gr.top_block):
if options.file2 and options.protocol == 'dmr':
nfiles += 1
if nfiles < max_inputs and not options.test:
AUDIO = audio.source(options.sample_rate, options.audio_input)
lpf_taps = filter.firdes.low_pass(1.0, options.sample_rate, 3400.0, 3400 * 0.1, filter.firdes.WIN_HANN)
AUDIO = audio.source(options.alsa_rate, options.audio_input)
lpf_taps = filter.firdes.low_pass(1.0, options.alsa_rate, 3400.0, 3400 * 0.1, filter.firdes.WIN_HANN)
audio_rate = 8000
AUDIO_DECIM = filter.fir_filter_fff (int(options.sample_rate / audio_rate), lpf_taps)
AUDIO_DECIM = filter.fir_filter_fff (int(options.alsa_rate / audio_rate), lpf_taps)
AUDIO_SCALE = blocks.multiply_const_ff(32767.0 * options.gain)
AUDIO_F2S = blocks.float_to_short()
self.connect(AUDIO, AUDIO_DECIM, AUDIO_SCALE, AUDIO_F2S)
if options.plot_audio:
PLOT_F = float_sink_f()
self.connect(AUDIO, PLOT_F)
if options.file1:
IN1 = blocks.file_source(gr.sizeof_short, options.file1, options.repeat)
@ -172,24 +182,42 @@ class my_top_block(gr.top_block):
else:
self.connect(AUDIO_F2S, ENCODER2)
MOD = p25_mod_bf(output_sample_rate = options.sample_rate, dstar = (options.protocol == 'dstar'), bt = options.bt, rc = RC_FILTER[options.protocol])
MOD = p25_mod_bf(output_sample_rate = options.modulator_rate, dstar = (options.protocol == 'dstar'), bt = options.bt, rc = RC_FILTER[options.protocol])
AMP = blocks.multiply_const_ff(output_gain)
if options.output_file:
OUT = blocks.file_sink(gr.sizeof_float, options.output_file)
elif not options.args:
OUT = audio.sink(options.sample_rate, options.audio_output)
OUT = audio.sink(options.alsa_rate, options.audio_output)
if options.symbol_sink:
SYMBOL_SINK = blocks.file_sink(gr.sizeof_char, options.symbol_sink)
if options.protocol == 'dmr' and not options.test:
self.connect(DMR, MOD)
if options.symbol_sink:
self.connect(DMR, SYMBOL_SINK)
else:
self.connect(ENCODER, MOD)
if options.symbol_sink:
self.connect(ENCODER, SYMBOL_SINK)
if options.args:
self.setup_sdr_output(options, mod_adjust[options.protocol])
interp = filter.rational_resampler_fff(options.if_rate / options.sample_rate, 1)
self.attn = blocks.multiply_const_cc(0.25)
self.connect(MOD, AMP, interp, self.fm_modulator, self.attn, self.u)
f1 = float(options.if_rate) / options.modulator_rate
i1 = int(options.if_rate / options.modulator_rate)
if f1 - i1 > 1e-3:
f1 = float(options.if_rate) / options.alt_modulator_rate
i1 = int(options.if_rate / options.alt_modulator_rate)
if f1 - i1 > 1e-3:
print '*** Error, sdr rate %d not an integer multiple of alt modulator rate %d - ratio=%f' % (options.if_rate, options.alt_modulator_rate, f1)
sys.exit(0)
a_resamp = filter.pfb.arb_resampler_fff(options.alt_modulator_rate / float(options.modulator_rate))
sys.stderr.write('adding resampler for rate change %d ===> %d\n' % (options.modulator_rate, options.alt_modulator_rate))
interp = filter.rational_resampler_fff(options.if_rate / options.alt_modulator_rate, 1)
self.connect(MOD, AMP, a_resamp, interp, self.fm_modulator, self.u)
else:
interp = filter.rational_resampler_fff(options.if_rate / options.modulator_rate, 1)
self.connect(MOD, AMP, interp, self.fm_modulator, self.u)
else:
self.connect(MOD, AMP, OUT)
@ -212,7 +240,6 @@ class my_top_block(gr.top_block):
print "setting gain %s to %d" % (name, gain)
self.u.set_gain(gain, name)
print 'setting sample rate'
self.u.set_sample_rate(options.if_rate)
self.u.set_center_freq(options.frequency)
self.u.set_freq_corr(options.frequency_correction)

View File

@ -61,10 +61,11 @@ class pipeline(gr.hier_block2):
alt_input = self
self.connect(alt_input, ENCODER2, (DMR, 1))
elif protocol == 'dstar':
ENCODER = op25_repeater.dstar_tx_sb(verbose, None)
assert config_file
ENCODER = op25_repeater.dstar_tx_sb(verbose, config_file)
elif protocol == 'p25':
ENCODER = op25_repeater.vocoder(True, # 0=Decode,True=Encode
0, # Verbose flag
False, # Verbose flag
0, # flex amount
"", # udp ip address
0, # udp port
@ -107,7 +108,7 @@ class my_top_block(gr.top_block):
parser.add_option("-b", "--bt", type="float", default=0.5, help="specify bt value")
parser.add_option("-f", "--file", type="string", default=None, help="specify the input file (mono 8000 sps S16_LE)")
parser.add_option("-g", "--gain", type="float", default=1.0, help="input gain")
parser.add_option("-i", "--if-rate", type="float", default=960000, help="output rate to sdr")
parser.add_option("-i", "--if-rate", type="int", default=480000, help="output rate to sdr")
parser.add_option("-I", "--audio-input", type="string", default="", help="pcm input device name. E.g., hw:0,0 or /dev/dsp")
parser.add_option("-N", "--gains", type="string", default=None, help="gain settings")
parser.add_option("-o", "--if-offset", type="float", default=100000, help="channel spacing (Hz)")
@ -115,21 +116,36 @@ class my_top_block(gr.top_block):
parser.add_option("-Q", "--frequency", type="float", default=0.0, help="Hz")
parser.add_option("-r", "--repeat", action="store_true", default=False, help="input file repeat")
parser.add_option("-R", "--fullrate-mode", action="store_true", default=False, help="ysf fullrate")
parser.add_option("-s", "--sample-rate", type="int", default=48000, help="output sample rate")
parser.add_option("-s", "--modulator-rate", type="int", default=48000, help="must be submultiple of IF rate")
parser.add_option("-S", "--alsa-rate", type="int", default=48000, help="sound source/sink sample rate")
parser.add_option("-v", "--verbose", type="int", default=0, help="additional output")
(options, args) = parser.parse_args()
assert options.file # input file name (-f filename) required
f1 = float(options.if_rate) / options.modulator_rate
i1 = int(options.if_rate / options.modulator_rate)
if f1 - i1 > 1e-3:
print '*** Error, sdr rate %d not an integer multiple of modulator rate %d - ratio=%f' % (options.if_rate, options.modulator_rate, f1)
sys.exit(1)
protocols = 'dmr p25 dstar ysf'.split()
bw = options.if_offset * len(protocols) + 50000
if bw > options.if_rate:
print '*** Error, a %d Hz band is required for %d channels and guardband.' % (bw, len(protocols))
print '*** Either reduce channel spacing using -o (current value is %d Hz),' % (options.if_offset)
print '*** or increase SDR output sample rate using -i (current rate is %d Hz)' % (options.if_rate)
sys.exit(1)
max_inputs = 1
from dv_tx import output_gains, gain_adjust, gain_adjust_fullrate, mod_adjust
if options.do_audio:
AUDIO = audio.source(options.sample_rate, options.audio_input)
lpf_taps = filter.firdes.low_pass(1.0, options.sample_rate, 3400.0, 3400 * 0.1, filter.firdes.WIN_HANN)
AUDIO = audio.source(options.alsa_rate, options.audio_input)
lpf_taps = filter.firdes.low_pass(1.0, options.alsa_rate, 3400.0, 3400 * 0.1, filter.firdes.WIN_HANN)
audio_rate = 8000
AUDIO_DECIM = filter.fir_filter_fff (int(options.sample_rate / audio_rate), lpf_taps)
AUDIO_DECIM = filter.fir_filter_fff (int(options.alsa_rate / audio_rate), lpf_taps)
AUDIO_SCALE = blocks.multiply_const_ff(32767.0 * options.gain)
AUDIO_F2S = blocks.float_to_short()
self.connect(AUDIO, AUDIO_DECIM, AUDIO_SCALE, AUDIO_F2S)
@ -138,7 +154,6 @@ class my_top_block(gr.top_block):
alt_input = None
SUM = blocks.add_cc()
protocols = 'dmr p25 dstar ysf'.split()
input_repeat = True
for i in xrange(len(protocols)):
SOURCE = blocks.file_source(gr.sizeof_short, options.file, input_repeat)
@ -151,6 +166,8 @@ class my_top_block(gr.top_block):
cfg = 'dmr-cfg.dat'
elif protocols[i] == 'ysf':
cfg = 'ysf-cfg.dat'
elif protocols[i] == 'dstar':
cfg = 'dstar-cfg.dat'
else:
cfg = None
@ -159,9 +176,9 @@ class my_top_block(gr.top_block):
output_gain = output_gains[protocols[i]],
gain_adjust = gain_adj,
mod_adjust = mod_adjust[protocols[i]],
if_freq = i * options.if_offset,
if_freq = (i - len(protocols)/2) * options.if_offset,
if_rate = options.if_rate,
sample_rate = options.sample_rate,
sample_rate = options.modulator_rate,
bt = options.bt,
fullrate_mode = options.fullrate_mode,
alt_input = alt_input,

View File

@ -49,14 +49,14 @@ _def_span = 13 #desired number of impulse response coeffs, in units of symbols
_def_gmsk_span = 4
_def_bt = 0.25
def transfer_function_rx():
def transfer_function_rx(symbol_rate=_def_symbol_rate):
# p25 c4fm de-emphasis filter
# Specs undefined above 2,880 Hz. It would be nice to have a sharper
# rolloff, but this filter is cheap enough....
xfer = [] # frequency domain transfer function
for f in xrange(0,4800):
for f in xrange(0,symbol_rate):
# D(f)
t = pi * f / 4800
t = pi * f / symbol_rate
if t < 1e-6:
df = 1.0
else:
@ -64,7 +64,7 @@ def transfer_function_rx():
xfer.append(df)
return xfer
def transfer_function_tx():
def transfer_function_tx(symbol_rate=_def_symbol_rate):
xfer = [] # frequency domain transfer function
for f in xrange(0, 2881): # specs cover 0 - 2,880 Hz
# H(f)
@ -82,7 +82,7 @@ def transfer_function_tx():
xfer.append(pf * hf)
return xfer
def transfer_function_dmr():
def transfer_function_dmr(symbol_rate=_def_symbol_rate):
xfer = [] # frequency domain transfer function
for f in xrange(0, 2881): # specs cover 0 - 2,880 Hz
if f < 1920:
@ -94,6 +94,32 @@ def transfer_function_dmr():
xfer = np.sqrt(xfer) # root cosine
return xfer
def transfer_function_nxdn(symbol_rate=_def_symbol_rate):
assert symbol_rate == 2400 or symbol_rate == 4800
T = 1.0 / symbol_rate
a = 0.2 # rolloff
fl = int(0.5+(1-a)/(2*T))
fh = int(0.5+(1+a)/(2*T))
xfer = []
for f in xrange(0, symbol_rate):
if f < fl:
hf = 1.0
elif f >= fl and f <= fh:
hf = cos((T/(4*a)) * (2*pi*f - pi*(1-a)/T))
else:
hf = 0.0
x = pi * f * T
if f <= fh:
if x == 0 or sin(x) == 0:
df = 1.0
else:
df = x / sin(x)
else:
df = 2.0
xfer.append(hf * df)
return xfer
class c4fm_taps(object):
"""Generate filter coefficients as per P25 C4FM spec"""
def __init__(self, filter_gain = 1.0, sample_rate=_def_output_sample_rate, symbol_rate=_def_symbol_rate, span=_def_span, generator=transfer_function_tx):
@ -105,7 +131,7 @@ class c4fm_taps(object):
self.generator = generator
def generate(self):
impulse_response = np.fft.fftshift(np.fft.irfft(self.generator(), self.sample_rate))
impulse_response = np.fft.fftshift(np.fft.irfft(self.generator(symbol_rate=self.symbol_rate), self.sample_rate))
start = np.argmax(impulse_response) - (self.ntaps-1) / 2
coeffs = impulse_response[start: start+self.ntaps]
gain = self.filter_gain / sum(coeffs)
@ -180,9 +206,8 @@ class p25_mod_bf(gr.hier_block2):
gr.io_signature(1, 1, gr.sizeof_float)) # Output signature
input_sample_rate = 4800 # P25 baseband symbol rate
lcm = gru.lcm(input_sample_rate, output_sample_rate)
self._interp_factor = int(lcm // input_sample_rate)
self._decimation = int(lcm // output_sample_rate)
intermediate_rate = 48000
self._interp_factor = intermediate_rate / input_sample_rate
self.dstar = dstar
self.bt = bt
@ -201,13 +226,13 @@ class p25_mod_bf(gr.hier_block2):
assert rc is None or rc == 'rc' or rc == 'rrc'
if rc:
coeffs = filter.firdes.root_raised_cosine(1.0, output_sample_rate, input_sample_rate, 0.2, 91)
coeffs = filter.firdes.root_raised_cosine(1.0, intermediate_rate, input_sample_rate, 0.2, 91)
if rc == 'rc':
coeffs = np.convolve(coeffs, coeffs)
coeffs = c4fm_taps(sample_rate=intermediate_rate).generate()
elif self.dstar:
coeffs = gmsk_taps(sample_rate=output_sample_rate, bt=self.bt).generate()
coeffs = gmsk_taps(sample_rate=intermediate_rate, bt=self.bt).generate()
elif not rc:
coeffs = c4fm_taps(sample_rate=output_sample_rate, generator=self.generator).generate()
coeffs = c4fm_taps(sample_rate=intermediate_rate, generator=self.generator).generate()
self.filter = filter.interp_fir_filter_fff(self._interp_factor, coeffs)
if verbose:
@ -217,9 +242,9 @@ class p25_mod_bf(gr.hier_block2):
self._setup_logging()
self.connect(self, self.C2S, self.polarity, self.filter)
if (self._decimation > 1):
self.decimator = filter.rational_resampler_fff(1, self._decimation)
self.connect(self.filter, self.decimator, self)
if intermediate_rate != output_sample_rate:
self.arb_resamp = filter.pfb.arb_resampler_fff(float(output_sample_rate)/intermediate_rate)
self.connect(self.filter, self.arb_resamp, self)
else:
self.connect(self.filter, self)

View File

@ -0,0 +1,24 @@
557 5F5 FF7 7FF 293 554 7BC B19 4D0 DCE 24A 124
0D4 33C 0BE 1B9 184 4FC C16 296 276 0E4 E24 A10
90D 433 C0B E1B 918 44C FC1 629 627 60E C00 000
000 003 892 849 0D4 33C 02F 86E 461 13F C16 294
89D 839 000 000 001 C38 24A 124 350 CF0 2F8 6E4
184 4FF 058 A58 9D8 3B0 000 000 007 0E2 4A1 240
D43 3C0 BE1 B91 844 FF0 162 962 760 E6D E5D 548
ADE 389 284 90D 433 C08 F86 E46 113 FC1 629 624
D83 BA1 41C 2D2 BA3 890 A12 435 0CF 02F 86E 460
44F F05 8A5 89D 839 4C8 FB0 235 A4E 24A 124 350
33C 0BE 1B9 184 4FF 058 296 276 0EC 000 000 00C
892 849 0D4 33C 0BE 1B8 461 13F C16 296 276 0E4
557 5F5 FF7 7FF 293 AB8 A4E FB0 9A8 ACE 24A 124
0D4 33C 0BE 1B9 184 4FC C16 296 276 0EC E24 A10
90D 433 C0B E1B 918 44C FC1 629 627 60E 400 000
000 003 892 849 0D4 33C 02F 86E 461 13F C16 294
89D 83B 000 000 000 038 24A 124 350 CF0 2F8 6E4
184 4FF 058 A58 9D8 390 000 000 000 0E2 4A1 240
D43 3C0 BE1 B91 844 FF0 162 962 760 EE0 E00 000
000 389 284 90D 433 C08 F86 E46 113 FC1 629 624
D83 9AE 8B4 8B6 493 890 A12 435 0CF 02F 86E 460
44F F05 8A5 89D 83B 9A8 F4F 1FD 60E 24A 124 350
33C 0BE 1B9 184 4FF 058 296 276 0E4 000 000 00C
892 849 0D4 33C 0BE 1B8 461 13F C16 296 276 0EC

View File

@ -0,0 +1,24 @@
557 5F5 FF7 7FF 293 557 7BC B19 4D0 DCE 24A 127
0D4 33C 0BE 1B9 184 4FF C16 296 276 0E4 E24 A13
90D 433 C0B E1B 918 44F FC1 629 627 60E C00 003
000 003 892 849 0D4 33F 02F 86E 461 13F C16 297
89D 839 000 000 001 C3B 24A 124 350 CF0 2F8 6E7
184 4FF 058 A58 9D8 3B3 000 000 007 0E2 4A1 243
D43 3C0 BE1 B91 844 FF3 162 962 760 E6D E5D 54B
ADE 389 284 90D 433 C0B F86 E46 113 FC1 629 627
D83 BA1 41C 2D2 BA3 893 A12 435 0CF 02F 86E 463
44F F05 8A5 89D 839 4CB FB0 235 A4E 24A 124 353
33C 0BE 1B9 184 4FF 05B 296 276 0EC 000 000 00F
892 849 0D4 33C 0BE 1BB 461 13F C16 296 276 0E7
557 5F5 FF7 7FF 293 ABB A4E FB0 9A8 ACE 24A 127
0D4 33C 0BE 1B9 184 4FF C16 296 276 0EC E24 A13
90D 433 C0B E1B 918 44F FC1 629 627 60E 400 003
000 003 892 849 0D4 33F 02F 86E 461 13F C16 297
89D 83B 000 000 000 03B 24A 124 350 CF0 2F8 6E7
184 4FF 058 A58 9D8 393 000 000 000 0E2 4A1 243
D43 3C0 BE1 B91 844 FF3 162 962 760 EE0 E00 003
000 389 284 90D 433 C0B F86 E46 113 FC1 629 627
D83 9AE 8B4 8B6 493 893 A12 435 0CF 02F 86E 463
44F F05 8A5 89D 83B 9AB F4F 1FD 60E 24A 124 353
33C 0BE 1B9 184 4FF 05B 296 276 0E4 000 000 00F
892 849 0D4 33C 0BE 1BB 461 13F C16 296 276 0EF

View File

@ -0,0 +1,24 @@
557 5E5 FF7 6FF 292 554 7AC B19 5D0 DCF 24A 134
0D4 23C 0BF 1B9 194 4FC D16 297 276 0F4 E24 B10
90C 433 C1B E1B 818 44D FC1 639 627 70E C01 000
010 003 992 848 0D4 32C 02F 96E 460 13F C06 294
99D 838 000 010 001 D38 24B 124 340 CF0 3F8 6E5
184 4EF 058 B58 9D9 3B0 010 000 107 0E3 4A1 250
D43 2C0 BE0 B91 854 FF0 062 963 760 E7D E5D 448
ADF 389 294 90D 533 C09 F86 E56 113 EC1 628 624
D93 BA1 51C 2D3 BA3 880 A12 535 0CE 02F 87E 460
54F F04 8A5 88D 839 5C8 FB1 235 A5E 24A 024 351
33C 0AE 1B9 084 4FE 058 286 276 1EC 001 000 01C
892 949 0D5 33C 0AE 1B8 561 13E C16 286 276 1E4
556 5F5 FE7 7FF 393 AB9 A4E FA0 9A8 BCE 24B 124
0C4 33C 1BE 1B8 184 4EC C16 396 277 0EC E34 A10
80D 432 C0B E0B 918 54C FC0 629 637 60E 500 001
000 013 892 949 0D5 33C 03F 86E 561 13E C16 284
89D 93B 001 000 010 038 34A 125 350 CE0 2F8 7E4
185 4FF 048 A58 8D8 391 000 010 000 1E2 4A0 240
D53 3C0 AE1 B90 844 FE0 162 862 761 EE0 E10 000
100 388 284 91D 433 D08 F87 E46 103 FC1 729 625
D83 9BE 8B4 9B6 492 890 A02 435 1CF 02E 86E 470
44F E05 8A4 89D 82B 9A8 E4F 1FC 60E 25A 124 250
33D 0BE 1A9 184 5FF 059 296 266 0E4 100 001 00C
882 849 1D4 33D 0BE 1A8 461 03F C17 296 266 0EC

View File

@ -0,0 +1,24 @@
557 5F5 FF7 7FF 293 555 7BC B19 4D0 DCE 24A 125
0D4 33C 0BE 1B9 184 4FD C16 296 276 0E4 E24 A11
90D 433 C0B E1B 918 44D FC1 629 627 60E C00 001
000 003 892 849 0D4 33D 02F 86E 461 13F C16 295
89D 839 000 000 001 C39 24A 124 350 CF0 2F8 6E5
184 4FF 058 A58 9D8 3B1 000 000 007 0E2 4A1 241
D43 3C0 BE1 B91 844 FF1 162 962 760 E6D E5D 549
ADE 389 284 90D 433 C09 F86 E46 113 FC1 629 625
D83 BA1 41C 2D2 BA3 891 A12 435 0CF 02F 86E 461
44F F05 8A5 89D 839 4C9 FB0 235 A4E 24A 124 351
33C 0BE 1B9 184 4FF 059 296 276 0EC 000 000 00D
892 849 0D4 33C 0BE 1B9 461 13F C16 296 276 0E5
557 5F5 FF7 7FF 293 AB9 A4E FB0 9A8 ACE 24A 125
0D4 33C 0BE 1B9 184 4FD C16 296 276 0EC E24 A11
90D 433 C0B E1B 918 44D FC1 629 627 60E 400 001
000 003 892 849 0D4 33D 02F 86E 461 13F C16 295
89D 83B 000 000 000 039 24A 124 350 CF0 2F8 6E5
184 4FF 058 A58 9D8 391 000 000 000 0E2 4A1 241
D43 3C0 BE1 B91 844 FF1 162 962 760 EE0 E00 001
000 389 284 90D 433 C09 F86 E46 113 FC1 629 625
D83 9AE 8B4 8B6 493 891 A12 435 0CF 02F 86E 461
44F F05 8A5 89D 83B 9A9 F4F 1FD 60E 24A 124 351
33C 0BE 1B9 184 4FF 059 296 276 0E4 000 000 00D
892 849 0D4 33C 0BE 1B9 461 13F C16 296 276 0ED

View File

@ -0,0 +1,24 @@
557 5F5 FF7 7FF 293 557 7BC B19 4D0 DCE 24A 127
0D4 33C 0BE 1B9 184 4FF C16 296 276 0E4 E24 A13
90D 433 C0B E1B 918 44F FC1 629 627 60E C00 003
000 003 892 849 0D4 33F 02F 86E 461 13F C16 297
89D 839 000 000 001 C3B 24A 124 350 CF0 2F8 6E7
184 4FF 058 A58 9D8 3B3 000 000 007 0E2 4A1 243
D43 3C0 BE1 B91 844 FF3 162 962 760 E6D E5D 54B
ADE 389 284 90D 433 C0B F86 E46 113 FC1 629 627
D83 BA1 41C 2D2 BA3 893 A12 435 0CF 02F 86E 463
44F F05 8A5 89D 839 4CB FB0 235 A4E 24A 124 353
33C 0BE 1B9 184 4FF 05B 296 276 0EC 000 000 00F
892 849 0D4 33C 0BE 1BB 461 13F C16 296 276 0E7
557 5F5 FF7 7FF 293 ABB A4E FB0 9A8 ACE 24A 127
0D4 33C 0BE 1B9 184 4FF C16 296 276 0EC E24 A13
90D 433 C0B E1B 918 44F FC1 629 627 60E 400 003
000 003 892 849 0D4 33F 02F 86E 461 13F C16 297
89D 83B 000 000 000 03F 24A 124 350 CF0 2F8 6E7
184 4FF 058 A58 9D8 393 000 000 000 0E2 4A1 243
D43 3C0 BE1 B91 844 FF3 162 962 760 EE0 E00 003
000 389 284 90D 433 C0B F86 E46 113 FC1 629 627
D83 9AE 8B4 8B6 493 893 A12 435 0CF 02F 86E 463
44F F05 8A5 89D 83B 9AB F4F 1FD 60E 24A 124 353
33C 0BE 1B9 184 4FF 05B 296 276 0E4 000 000 00F
892 849 0D4 33C 0BE 1BB 461 13F C16 296 276 0EF

View File

@ -0,0 +1,25 @@
#! /usr/bin/env python
"""
utility program to make binary symbol files
reads source file (stdin); writes binary file to stdout
"""
import sys
s = sys.stdin.read()
s= s.replace(' ', '')
s= s.replace('\n', '')
s = s.strip()
dibits = ''
while s:
s0 = int(s[0], 16)
s = s[1:]
dibits += chr(s0>>2)
dibits += chr(s0&3)
sys.stdout.write(dibits)

View File

@ -0,0 +1,24 @@
557 5F5 FF7 7FF 8A2 540 FF8 BAA 959 E5A 18B A16
DE2 E82 693 63D 981 F98 F88 EC6 2B8 011 B10 BA0
5DE 2E8 269 363 D98 1F8 6F8 8EC 62B 801 800 000
000 006 C42 E85 DE2 E80 9A4 D8F 660 7E6 F88 EC4
8AE 004 000 000 001 C6C 10B A17 78B A09 A4D 8F4
981 F9B E23 B18 AE0 060 000 000 007 1B1 0BA 174
E2E 826 936 3D9 81F 9BC 88E C62 B80 12D E5D 548
ADE 6C4 2E8 5DE 2E8 268 4D8 F66 07E 6F8 8EC 628
E00 6A1 41C 2D2 BA6 C40 BA1 778 BA0 9A4 D8F 660
1F9 BE2 3B1 8AE 004 4C8 FB0 235 A5B 10B A17 788
E82 693 63D 981 F9B E20 EC6 2B8 018 000 000 018
C42 E85 DE2 E82 693 63C 660 7E6 F88 EC6 2B8 010
557 5F5 FF7 7FF 293 AB8 A4E FB0 9A8 ADB 10B A14
DE2 E82 693 63D 981 F98 F88 EC6 2B8 019 B10 BA0
5DE 2E8 269 363 D98 1F8 6F8 8EC 62B 801 000 000
000 006 C42 E85 DE2 E80 9A4 D8F 660 7E6 F88 EC4
8AE 006 000 000 000 06C 10B A17 78B A09 A4D 8F4
981 F9B E23 B18 AE0 040 000 000 000 1B1 0BA 174
E2E 826 936 3D9 81F 9BC 88E C62 B80 1A0 E00 000
000 6C4 2E8 5DE 2E8 268 4D8 F66 07E 6F8 8EC 628
E00 4AE 8B4 8B6 496 C40 BA1 778 BA0 9A4 D8F 660
1F9 BE2 3B1 8AE 006 9A8 F4F 1FD 61B 10B A17 788
E82 693 63D 981 F9B E20 EC6 2B8 010 000 000 018
C42 E85 DE2 E82 693 63C 660 7E6 F88 EC6 2B8 018

Binary file not shown.

View File

@ -48,7 +48,7 @@ voip=0
# call sign data fields (10 byte, except rem12 and rem34 are five byte)
#################################################################################
dest=**********
src=SRC
src=KA1RBI
down=down1
up=up1
rem12=rem12

View File

@ -25,6 +25,9 @@ install(FILES
vocoder.h
gardner_costas_cc.h
p25_frame_assembler.h
frame_assembler.h
ambe_encoder_sb.h
ysf_tx_sb.h
dstar_tx_sb.h
fsk4_slicer_fb.h DESTINATION include/op25_repeater
)

View File

@ -0,0 +1,60 @@
/* -*- c++ -*- */
/*
* Copyright 2017 Max H. Parke KA1RBI
*
* This 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, or (at your option)
* any later version.
*
* This software 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 this software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef INCLUDED_OP25_REPEATER_FRAME_ASSEMBLER_H
#define INCLUDED_OP25_REPEATER_FRAME_ASSEMBLER_H
#include <op25_repeater/api.h>
#include <gnuradio/block.h>
#include <gnuradio/msg_queue.h>
namespace gr {
namespace op25_repeater {
/*!
* \brief <+description of block+>
* \ingroup op25_repeater
*
*/
class OP25_REPEATER_API frame_assembler : virtual public gr::block
{
public:
typedef boost::shared_ptr<frame_assembler> sptr;
/*!
* \brief Return a shared_ptr to a new instance of op25_repeater::frame_assembler.
*
* To avoid accidental use of raw pointers, op25_repeater::frame_assembler's
* constructor is in a private implementation
* class. op25_repeater::frame_assembler::make is the public interface for
* creating new instances.
*/
static sptr make(const char* options, int debug, gr::msg_queue::sptr queue);
virtual void set_xormask(const char*p) {}
virtual void set_nac(int nac) {}
virtual void set_slotid(int slotid) {}
};
} // namespace op25_repeater
} // namespace gr
#endif /* INCLUDED_OP25_REPEATER_FRAME_ASSEMBLER_H */

View File

@ -48,6 +48,8 @@ namespace gr {
*/
static sptr make(float samples_per_symbol, float gain_mu, float gain_omega, float alpha, float beta, float max_freq, float min_freq);
virtual void set_omega(float omega) {}
virtual float get_freq_error(void) {}
virtual int get_error_band(void) {}
};
} // namespace op25_repeater

View File

@ -49,6 +49,7 @@ namespace gr {
*/
static sptr make(const char* udp_host, int port, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, bool do_audio_output, bool do_phase2_tdma);
virtual void set_xormask(const char*p) {}
virtual void set_nac(int nac) {}
virtual void set_slotid(int slotid) {}
};

View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2009,2011,2014 by Jonathan Naylor, G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <stdio.h>
#include <assert.h>
#include "CCITTChecksumReverse.h"
static const unsigned short ccittTab[] = {
0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf,
0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7,
0x1081,0x0108,0x3393,0x221a,0x56a5,0x472c,0x75b7,0x643e,
0x9cc9,0x8d40,0xbfdb,0xae52,0xdaed,0xcb64,0xf9ff,0xe876,
0x2102,0x308b,0x0210,0x1399,0x6726,0x76af,0x4434,0x55bd,
0xad4a,0xbcc3,0x8e58,0x9fd1,0xeb6e,0xfae7,0xc87c,0xd9f5,
0x3183,0x200a,0x1291,0x0318,0x77a7,0x662e,0x54b5,0x453c,
0xbdcb,0xac42,0x9ed9,0x8f50,0xfbef,0xea66,0xd8fd,0xc974,
0x4204,0x538d,0x6116,0x709f,0x0420,0x15a9,0x2732,0x36bb,
0xce4c,0xdfc5,0xed5e,0xfcd7,0x8868,0x99e1,0xab7a,0xbaf3,
0x5285,0x430c,0x7197,0x601e,0x14a1,0x0528,0x37b3,0x263a,
0xdecd,0xcf44,0xfddf,0xec56,0x98e9,0x8960,0xbbfb,0xaa72,
0x6306,0x728f,0x4014,0x519d,0x2522,0x34ab,0x0630,0x17b9,
0xef4e,0xfec7,0xcc5c,0xddd5,0xa96a,0xb8e3,0x8a78,0x9bf1,
0x7387,0x620e,0x5095,0x411c,0x35a3,0x242a,0x16b1,0x0738,
0xffcf,0xee46,0xdcdd,0xcd54,0xb9eb,0xa862,0x9af9,0x8b70,
0x8408,0x9581,0xa71a,0xb693,0xc22c,0xd3a5,0xe13e,0xf0b7,
0x0840,0x19c9,0x2b52,0x3adb,0x4e64,0x5fed,0x6d76,0x7cff,
0x9489,0x8500,0xb79b,0xa612,0xd2ad,0xc324,0xf1bf,0xe036,
0x18c1,0x0948,0x3bd3,0x2a5a,0x5ee5,0x4f6c,0x7df7,0x6c7e,
0xa50a,0xb483,0x8618,0x9791,0xe32e,0xf2a7,0xc03c,0xd1b5,
0x2942,0x38cb,0x0a50,0x1bd9,0x6f66,0x7eef,0x4c74,0x5dfd,
0xb58b,0xa402,0x9699,0x8710,0xf3af,0xe226,0xd0bd,0xc134,
0x39c3,0x284a,0x1ad1,0x0b58,0x7fe7,0x6e6e,0x5cf5,0x4d7c,
0xc60c,0xd785,0xe51e,0xf497,0x8028,0x91a1,0xa33a,0xb2b3,
0x4a44,0x5bcd,0x6956,0x78df,0x0c60,0x1de9,0x2f72,0x3efb,
0xd68d,0xc704,0xf59f,0xe416,0x90a9,0x8120,0xb3bb,0xa232,
0x5ac5,0x4b4c,0x79d7,0x685e,0x1ce1,0x0d68,0x3ff3,0x2e7a,
0xe70e,0xf687,0xc41c,0xd595,0xa12a,0xb0a3,0x8238,0x93b1,
0x6b46,0x7acf,0x4854,0x59dd,0x2d62,0x3ceb,0x0e70,0x1ff9,
0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,
0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78};
CCCITTChecksumReverse::CCCITTChecksumReverse() :
m_crc16(0xFFFFU)
{
}
CCCITTChecksumReverse::~CCCITTChecksumReverse()
{
}
void CCCITTChecksumReverse::update(const unsigned char* data, unsigned int length)
{
assert(data != NULL);
for (unsigned int i = 0U; i < length; i++)
m_crc16 = ((uint16_t)(m_crc8[1U])) ^ ccittTab[m_crc8[0U] ^ data[i]];
}
void CCCITTChecksumReverse::result(unsigned char* data)
{
assert(data != NULL);
m_crc16 = ~m_crc16;
data[0U] = m_crc8[0U];
data[1U] = m_crc8[1U];
}
bool CCCITTChecksumReverse::check(const unsigned char* data)
{
assert(data != NULL);
unsigned char sum[2U];
result(sum);
return sum[0U] == data[0U] && sum[1U] == data[1U];
}
void CCCITTChecksumReverse::reset()
{
m_crc16 = 0xFFFFU;
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2009,2011,2014 by Jonathan Naylor, G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#ifndef CCITTChecksumReverse_H
#define CCITTChecksumReverse_H
#include <stdint.h>
class CCCITTChecksumReverse {
public:
CCCITTChecksumReverse();
~CCCITTChecksumReverse();
void update(const unsigned char* data, unsigned int length);
void result(unsigned char* data);
bool check(const unsigned char* data);
void reset();
private:
union {
uint16_t m_crc16;
uint8_t m_crc8[2U];
};
};
#endif

View File

@ -32,6 +32,7 @@ list(APPEND op25_repeater_sources
vocoder_impl.cc
gardner_costas_cc_impl.cc
p25_frame_assembler_impl.cc
frame_assembler_impl.cc
fsk4_slicer_fb_impl.cc )
list(APPEND op25_repeater_sources
@ -52,6 +53,11 @@ list(APPEND op25_repeater_sources
ambe.c
mbelib.c
ambe_encoder.cc
rx_sync.cc
op25_audio.cc
CCITTChecksumReverse.cpp
value_string.cc
nxdn.cc
)
add_library(gnuradio-op25_repeater SHARED ${op25_repeater_sources})
@ -97,4 +103,21 @@ target_link_libraries(
GR_ADD_TEST(test_op25_repeater test-op25_repeater)
########################################################################
# d2460
########################################################################
list(APPEND d2460_sources
${CMAKE_CURRENT_SOURCE_DIR}/d2460.cc
${CMAKE_CURRENT_SOURCE_DIR}/ambe.c
${CMAKE_CURRENT_SOURCE_DIR}/mbelib.c
${CMAKE_CURRENT_SOURCE_DIR}/ambe_encoder.cc
${CMAKE_CURRENT_SOURCE_DIR}/software_imbe_decoder.cc
${CMAKE_CURRENT_SOURCE_DIR}/imbe_decoder.cc
${CMAKE_CURRENT_SOURCE_DIR}/p25p2_vf.cc
${CMAKE_CURRENT_SOURCE_DIR}/rs.cc
)
add_executable(op25-d2460 ${d2460_sources})
target_link_libraries(op25-d2460 imbe_vocoder)
add_subdirectory(imbe_vocoder)

View File

@ -140,7 +140,7 @@ mbe_dequantizeAmbeParms (mbe_parms * cur_mp, mbe_parms * prev_mp, const int *b,
#endif
if (dstar) {
deltaGamma = AmbePlusDg[b2];
cur_mp->gamma = deltaGamma + ((float) 1.0 * prev_mp->gamma);
cur_mp->gamma = deltaGamma + ((float) 0.5 * prev_mp->gamma);
} else {
deltaGamma = AmbeDg[b2];
cur_mp->gamma = deltaGamma + ((float) 0.5 * prev_mp->gamma);

View File

@ -545,6 +545,7 @@ static void encode_49bit(uint8_t outp[49], const int b[9]) {
ambe_encoder::ambe_encoder(void)
: d_49bit_mode(false),
d_dstar_mode(false),
d_alt_dstar_interleave(false),
d_gain_adjust(0)
{
mbe_parms enh_mp;
@ -579,7 +580,7 @@ void ambe_encoder::encode(int16_t samples[], uint8_t codeword[])
encode_ambe(vocoder.param(), b, &cur_mp, &prev_mp, d_dstar_mode, d_gain_adjust);
if (d_dstar_mode) {
interleaver.encode_dstar(codeword, b);
interleaver.encode_dstar(codeword, b, d_alt_dstar_interleave);
} else if (d_49bit_mode) {
encode_49bit(codeword, b);
} else {

View File

@ -28,7 +28,8 @@ public:
ambe_encoder(void);
void set_49bit_mode(void);
void set_dstar_mode(void);
void set_gain_adjust(float gain_adjust) {d_gain_adjust = gain_adjust;}
void set_gain_adjust(const float gain_adjust) {d_gain_adjust = gain_adjust;}
void set_alt_dstar_interleave(const bool v) { d_alt_dstar_interleave = v; }
private:
imbe_vocoder vocoder;
p25p2_vf interleaver;
@ -37,6 +38,7 @@ private:
bool d_49bit_mode;
bool d_dstar_mode;
float d_gain_adjust;
bool d_alt_dstar_interleave;
};
#endif /* INCLUDED_AMBE_ENCODER_H */

View File

@ -0,0 +1,59 @@
//
// This file is part of OP25
//
// OP25 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, or (at your option)
// any later version.
//
// OP25 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 OP25; see the file COPYING. If not, write to the Free
// Software Foundation, Inc., 51 Franklin Street, Boston, MA
// 02110-1301, USA.
#ifndef INCLUDED_OP25_REPEATER_BIT_UTILS_H
#define INCLUDED_OP25_REPEATER_BIT_UTILS_H
#include <stdint.h>
static inline void store_i(int reg, uint8_t val[], int len) {
for (int i=0; i<len; i++){
val[i] = (reg >> (len-1-i)) & 1;
}
}
static inline int load_i(const uint8_t val[], int len) {
int acc = 0;
for (int i=0; i<len; i++){
acc = (acc << 1) + (val[i] & 1);
}
return acc;
}
static inline uint64_t load_reg64(const uint8_t val[], int len) {
uint64_t acc = 0;
for (int i=0; i<len; i++){
acc = (acc << 1) + (val[i] & 1);
}
return acc;
}
static inline void dibits_to_bits(uint8_t * bits, const uint8_t * dibits, const int n_dibits) {
for (int i=0; i<n_dibits; i++) {
bits[i*2] = (dibits[i] >> 1) & 1;
bits[i*2+1] = dibits[i] & 1;
}
}
static inline void bits_to_dibits(uint8_t* dest, const uint8_t* src, int n_dibits) {
for (int i=0; i<n_dibits; i++) {
dest[i] = src[i*2] * 2 + src[i*2+1];
}
}
#endif /* INCLUDED_OP25_REPEATER_BIT_UTILS_H */

View File

@ -6,7 +6,7 @@
static inline bool check_frame_sync(uint64_t x, int err_threshold, int len) {
int errs=0;
static const uint64_t mask = (1LL<<len)-1;
const uint64_t mask = (1LL<<len)-1LL;
x = x & mask;
// source: https://en.wikipedia.org/wiki/Hamming_weight
static const uint64_t m1 = 0x5555555555555555; //binary: 0101...

View File

@ -0,0 +1,35 @@
/*
* This file is part of OP25
*
* This 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, or (at your option)
* any later version.
*
* This software 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 this software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef INCLUDED_CRC16_H
#define INCLUDED_CRC16_H
static inline uint16_t crc16(const uint8_t buf[], int len) {
uint32_t poly = (1<<12) + (1<<5) + (1<<0);
uint32_t crc = 0;
for(int i=0; i<len; i++) {
uint8_t bit = buf[i] & 1;
crc = ((crc << 1) | bit) & 0x1ffff;
if (crc & 0x10000)
crc = (crc & 0xffff) ^ poly;
}
crc = crc ^ 0xffff;
return crc & 0xffff;
}
#endif /* INCLUDED_CRC16_H */

View File

@ -0,0 +1,362 @@
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <termios.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <stdint.h>
#include <mbelib.h>
#include <ambe.h>
#include <p25p2_vf.h>
#include <imbe_decoder.h>
#include <software_imbe_decoder.h>
#include "imbe_vocoder/imbe_vocoder.h"
#include <ambe_encoder.h>
static const float GAIN_ADJUST=7.0; /* attenuation (dB) */
typedef uint16_t Uns;
static const Uns RC_OK=0;
static const char prodid[] = "OP25 ";
static const char verstring[] = "1.0";
static ambe_encoder encoder;
static software_imbe_decoder software_decoder;
static p25p2_vf interleaver;
static mbe_parms cur_mp;
static mbe_parms prev_mp;
static mbe_parms enh_mp;
static const Uns DV3K_START_BYTE = 0x61;
enum
{
DV3K_CONTROL_RATEP = 0x0A,
DV3K_CONTROL_CHANFMT = 0x15,
DV3K_CONTROL_PRODID = 0x30,
DV3K_CONTROL_VERSTRING = 0x31,
DV3K_CONTROL_RESET = 0x33,
DV3K_CONTROL_READY = 0x39
};
static const Uns DV3K_AMBE_FIELD_CHAND = 0x01;
static const Uns DV3K_AMBE_FIELD_CMODE = 0x02;
static const Uns DV3K_AMBE_FIELD_TONE = 0x08;
static const Uns DV3K_AUDIO_FIELD_SPEECHD = 0x00;
static const Uns DV3K_AUDIO_FIELD_CMODE = 0x02;
#pragma DATA_ALIGN(dstar_state, 2)
static Uns bitstream[72];
static Uns get_byte(Uns offset, Uns *p)
{
Uns word = p[offset >> 1];
return (offset & 1) ? (word >> 8) : (word & 0xff);
}
static void set_byte(Uns offset, Uns *p, Uns byte)
{
p[offset >> 1] =
(offset & 1) ? (byte << 8) | (p[offset >> 1] & 0xff)
: (p[offset >> 1] & 0xff00) | (byte & 0xff);
}
static Uns get_word(Uns offset, Uns *p)
{
return get_byte(offset + 1, p) | (get_byte(offset, p) << 8);
}
static void set_word(Uns offset, Uns *p, Uns word)
{
set_byte(offset, p, word >> 8);
set_byte(offset + 1, p, word & 0xff);
}
static void set_cstring(Uns offset, Uns *p, const char *str)
{
do
set_byte(offset++, p, *str);
while (*str++ != 0);
}
static Uns pkt_check_ratep(Uns offset, Uns *p)
{
static const Uns ratep[] = {
0x01, 0x30, 0x07, 0x63, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x48 };
Uns i;
for (i = 0; i < sizeof(ratep); ++i)
if (get_byte(offset + i, p) != ratep[i])
return 0;
return 1;
}
static void pack(Uns bits, Uns offset, Uns *p, Uns *bitstream)
{
Uns i;
Uns byte = 0;
for (i = 0; i < bits; ++i)
{
byte |= bitstream[i] << (7 - (i & 7));
if ((i & 7) == 7)
{
set_byte(offset++, p, byte);
byte = 0;
}
}
if (i & 7)
set_byte(offset, p, byte);
}
static void unpack(Uns bits, Uns offset, Uns *bitstream, Uns *p)
{
Uns i;
Uns byte;
for (i = 0; i < bits; ++i)
{
if ((i & 7) == 0)
byte = get_byte(offset++, p);
bitstream[i] = (byte >> (7 - (i & 7))) & 1;
}
}
static int response_len = -1;
static void bksnd(void*task, Uns bid, Uns len)
{
response_len = len;
}
static void vocoder_setup(void) {
encoder.set_dstar_mode();
encoder.set_gain_adjust(GAIN_ADJUST);
encoder.set_alt_dstar_interleave(true);
mbe_initMbeParms (&cur_mp, &prev_mp, &enh_mp);
}
static void dump(unsigned char *p, ssize_t n)
{
int i;
for (i = 0; i < n; ++i)
printf("%02x%c", p[i], i % 16 == 15 ? '\n' : ' ');
if (i % 16)
printf("\n");
}
static Uns pkt_process(Uns*pkt, Uns cnt)
{
Uns bid=0;
Uns len = cnt << 1;
Uns payload_length;
Uns i;
Uns cmode = 0;
Uns tone = 0;
uint8_t codeword[72];
int b[9];
int K;
int rc = -1;
if (len < 4 || cnt > 256)
goto fail;
if (get_byte(0, pkt) != DV3K_START_BYTE)
goto fail;
payload_length = get_word(1, pkt);
if (payload_length == 0)
goto fail;
if (4 + payload_length > len)
goto fail;
switch (get_byte(3, pkt))
{
case 0:
switch (get_byte(4, pkt))
{
case DV3K_CONTROL_RATEP:
if (payload_length != 13)
goto fail;
if (!pkt_check_ratep(5, pkt))
goto fail;
set_word(1, pkt, 1);
bksnd(NULL, bid, 3);
return RC_OK;
case DV3K_CONTROL_CHANFMT:
if (payload_length != 3)
goto fail;
if (get_word(5, pkt) != 0x0001)
goto fail;
set_word(1, pkt, 2);
set_byte(5, pkt, 0);
bksnd(NULL, bid, 3);
return RC_OK;
case DV3K_CONTROL_PRODID:
set_word(1, pkt, 8);
set_cstring(5, pkt, prodid);
bksnd(NULL, bid, 6);
return RC_OK;
case DV3K_CONTROL_VERSTRING:
set_word(1, pkt, 5);
set_cstring(5, pkt, verstring);
bksnd(NULL, bid, 5);
return RC_OK;
case DV3K_CONTROL_RESET:
if (payload_length != 1)
goto fail;
vocoder_setup();
set_byte(4, pkt, DV3K_CONTROL_READY);
bksnd(NULL, bid, 3);
return RC_OK;
default:
goto fail;
}
case 1:
switch (payload_length)
{
case 17:
if (get_byte(18, pkt) != DV3K_AMBE_FIELD_TONE)
goto fail;
tone = get_word(19, pkt);
/* FALLTHROUGH */
case 14:
if (get_byte(15, pkt) != DV3K_AMBE_FIELD_CMODE)
goto fail;
cmode = get_word(16, pkt);
/* FALLTHROUGH */
case 11:
if (get_byte(4, pkt) != DV3K_AMBE_FIELD_CHAND)
goto fail;
if (get_byte(5, pkt) != 72)
goto fail;
unpack(72, 6, bitstream, pkt);
break;
default:
goto fail;
}
for (i = 0; i < 72; i++) {
codeword[i] = bitstream[i];
}
interleaver.decode_dstar(codeword, b, true);
if (b[0] >= 120) {
memset(6+(char*)pkt, 0, 320); // silence
// FIXME: add handling for tone case (b0=126)
} else {
rc = mbe_dequantizeAmbe2400Parms(&cur_mp, &prev_mp, b);
printf("B\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]);
K = 12;
if (cur_mp.L <= 36)
K = int(float(cur_mp.L + 2.0) / 3.0);
software_decoder.decode_tap(cur_mp.L, K, cur_mp.w0, &cur_mp.Vl[1], &cur_mp.Ml[1]);
audio_samples *samples = software_decoder.audio();
int16_t snd;
for (i=0; i < 160; i++) {
if (samples->size() > 0) {
snd = (int16_t)(samples->front());
samples->pop_front();
} else {
snd = 0;
}
set_word(6 + (i << 1), pkt, snd);
}
mbe_moveMbeParms (&cur_mp, &prev_mp);
}
set_word(1, pkt, 322);
set_byte(3, pkt, 2);
set_byte(4, pkt, DV3K_AUDIO_FIELD_SPEECHD);
set_byte(5, pkt, 160);
bksnd(NULL, bid, 165);
return RC_OK;
case 2:
if (payload_length != 322 && payload_length != 325)
goto fail;
if (get_byte(4, pkt) != DV3K_AUDIO_FIELD_SPEECHD)
goto fail;
if (get_byte(5, pkt) != 160)
goto fail;
if (payload_length == 325)
{
if (get_byte(326, pkt) != DV3K_AUDIO_FIELD_CMODE)
goto fail;
cmode = get_word(323, pkt);
}
int16_t samples[160];
for (i=0; i < 160; i++) {
samples[i] = (int16_t) get_word(6 + (i << 1), pkt);
}
encoder.encode(samples, codeword);
for (i = 0; i < 72; i++) {
bitstream[i] = codeword[i];
}
set_word(1, pkt, 11);
set_byte(3, pkt, 1);
set_byte(4, pkt, DV3K_AMBE_FIELD_CHAND);
set_byte(5, pkt, 72);
pack(72, 6, pkt, bitstream);
bksnd(NULL, bid, 8);
return RC_OK;
default:
goto fail;
}
fail:
bksnd(NULL, bid, 0);
return RC_OK;
}
int main()
{
int sockfd;
const ssize_t size_max = 1024;
ssize_t size_in, size_out;
char buf_in[size_max], buf_out[size_max];
socklen_t length = sizeof(struct sockaddr_in);
struct sockaddr_in sa = { 0 };
Uns rc;
vocoder_setup();
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
exit(2);
sa.sin_family = AF_INET;
sa.sin_port = htons(2460);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
exit(3);
while (1)
{
if ((size_in = recvfrom(sockfd, buf_in, size_max,
0, (struct sockaddr *)&sa, &length)) < 0)
exit(4);
if (size_in & 1)
buf_in[size_in++] = 0;
rc = pkt_process((Uns*)buf_in, size_in >> 1);
if (response_len <= 0)
exit(9);
size_out = 4 + ntohs(*(short *)&buf_in[1]);
if (sendto(sockfd, buf_in, size_out, 0, (struct sockaddr *)&sa,
sizeof(struct sockaddr_in)) != size_out)
exit(7);
}
return 0;
}

View File

@ -186,8 +186,6 @@ static void generate_cach(uint8_t at, uint8_t tc, uint8_t lcss, const uint8_t ca
int tact = hamming_7_4[ (at << 3) | (tc << 2) | lcss ];
//printf ("tact %d %x\n", tact, tact);
//print_result("cach_payload_bits", cach_bits, 17);
static const uint8_t cach_tact_bits[] = {0, 4, 8, 12, 14, 18, 22};
static const uint8_t cach_payload_bits[] = {1,2,3,5,6,7,9,10,11,13,15,16,17,19,20,21,23};
for (int i=0; i<7; i++) {
result[cach_tact_bits[i]] = (tact >> (6-i)) & 1;
}

View File

@ -25,7 +25,18 @@ static const int hamming_7_4[] = {
0, 11, 22, 29, 39, 44, 49, 58,
69, 78, 83, 88, 98, 105, 116, 127,
};
static const int hamming_7_4_decode[] = {
0, 0, 0, 1, 0, 8, 2, 4, 0, 1, 1, 1, 5, 3, 9, 1,
0, 6, 2, 10, 2, 3, 2, 2, 11, 3, 7, 1, 3, 3, 2, 3,
0, 6, 12, 4, 5, 4, 4, 4, 5, 13, 7, 1, 5, 5, 5, 4,
6, 6, 7, 6, 14, 6, 2, 4, 7, 6, 7, 7, 5, 3, 7, 15,
0, 8, 12, 10, 8, 8, 9, 8, 11, 13, 9, 1, 9, 8, 9, 9,
11, 10, 10, 10, 14, 8, 2, 10, 11, 11, 11, 10, 11, 3, 9, 15,
12, 13, 12, 12, 14, 8, 12, 4, 13, 13, 12, 13, 5, 13, 9, 15,
14, 6, 12, 10, 14, 14, 14, 15, 11, 13, 7, 15, 14, 15, 15, 15
};
static const int hamming_17_12[] = {
0, 37, 74, 111, 148, 177, 222, 251,
269, 296, 327, 354, 409, 444, 467, 502,
@ -822,9 +833,14 @@ static const int hamming_16_11[] = {
static const uint8_t dmr_bs_voice_sync[24] = {
1,3,1,1,1,1,3,3,3,1,1,3,3,1,3,3,1,3,1,1,3,3,1,3
};
static const uint64_t DMR_VOICE_SYNC_MAGIC = 0x755fd7df75f7LL;
static const uint64_t DMR_IDLE_SYNC_MAGIC = 0xdff57d75df5dLL;
static const uint8_t dmr_bs_idle_sync[24] = {
3,1,3,3,3,3,1,1,1,3,3,1,1,3,1,1,3,1,3,3,1,1,3,1
};
static const uint8_t cach_tact_bits[] = {0, 4, 8, 12, 14, 18, 22};
static const uint8_t cach_payload_bits[] = {1,2,3,5,6,7,9,10,11,13,15,16,17,19,20,21,23};
#endif /* INCLUDED_OP25_REPEATER_DMR_CONST_H */

View File

@ -0,0 +1,130 @@
/*
* Copyright (C) 2010,2011,2015 by Jonathan Naylor, G4KLX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/* from OpenDV/DStarRepeater */
#ifndef INCLUDED_DSTAR_HEADER_H
#define INCLUDED_DSTAR_HEADER_H
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "CCITTChecksumReverse.h"
#include "bit_utils.h"
static inline uint8_t rev_byte(const uint8_t b) {
uint8_t rc = 0;
for (int i=0; i<8; i++) {
rc |= ((b >> i) & 1) << (7-i);
}
return(rc);
}
static inline void make_dstar_header(
uint8_t header_data_buf[480], // result (bits)
const uint8_t flag1, const uint8_t flag2, const uint8_t flag3,
const char RptCall2[],
const char RptCall1[],
const char YourCall[],
const char MyCall1[],
const char MyCall2[])
{
uint8_t m_headerData[60];
static const unsigned int SLOW_DATA_BLOCK_SIZE = 6U;
static const unsigned int SLOW_DATA_FULL_BLOCK_SIZE = SLOW_DATA_BLOCK_SIZE * 10U;
static const unsigned char SLOW_DATA_TYPE_HEADER = 0x50U;
::memset(m_headerData, 'f', SLOW_DATA_FULL_BLOCK_SIZE);
m_headerData[0U] = SLOW_DATA_TYPE_HEADER | 5U;
m_headerData[1U] = flag1;
m_headerData[2U] = flag2;
m_headerData[3U] = flag3;
m_headerData[4U] = RptCall2[0];
m_headerData[5U] = RptCall2[1];
m_headerData[6U] = SLOW_DATA_TYPE_HEADER | 5U;
m_headerData[7U] = RptCall2[2];
m_headerData[8U] = RptCall2[3];
m_headerData[9U] = RptCall2[4];
m_headerData[10U] = RptCall2[5];
m_headerData[11U] = RptCall2[6];
m_headerData[12U] = SLOW_DATA_TYPE_HEADER | 5U;
m_headerData[13U] = RptCall2[7];
m_headerData[14U] = RptCall1[0];
m_headerData[15U] = RptCall1[1];
m_headerData[16U] = RptCall1[2];
m_headerData[17U] = RptCall1[3];
m_headerData[18U] = SLOW_DATA_TYPE_HEADER | 5U;
m_headerData[19U] = RptCall1[4];
m_headerData[20U] = RptCall1[5];
m_headerData[21U] = RptCall1[6];
m_headerData[22U] = RptCall1[7];
m_headerData[23U] = YourCall[0];
m_headerData[24U] = SLOW_DATA_TYPE_HEADER | 5U;
m_headerData[25U] = YourCall[1];
m_headerData[26U] = YourCall[2];
m_headerData[27U] = YourCall[3];
m_headerData[28U] = YourCall[4];
m_headerData[29U] = YourCall[5];
m_headerData[30U] = SLOW_DATA_TYPE_HEADER | 5U;
m_headerData[31U] = YourCall[6];
m_headerData[32U] = YourCall[7];
m_headerData[33U] = MyCall1[0];
m_headerData[34U] = MyCall1[1];
m_headerData[35U] = MyCall1[2];
m_headerData[36U] = SLOW_DATA_TYPE_HEADER | 5U;
m_headerData[37U] = MyCall1[3];
m_headerData[38U] = MyCall1[4];
m_headerData[39U] = MyCall1[5];
m_headerData[40U] = MyCall1[6];
m_headerData[41U] = MyCall1[7];
m_headerData[42U] = SLOW_DATA_TYPE_HEADER | 5U;
m_headerData[43U] = MyCall2[0];
m_headerData[44U] = MyCall2[1];
m_headerData[45U] = MyCall2[2];
m_headerData[46U] = MyCall2[3];
CCCITTChecksumReverse cksum;
cksum.update(m_headerData + 1U, 5U);
cksum.update(m_headerData + 7U, 5U);
cksum.update(m_headerData + 13U, 5U);
cksum.update(m_headerData + 19U, 5U);
cksum.update(m_headerData + 25U, 5U);
cksum.update(m_headerData + 31U, 5U);
cksum.update(m_headerData + 37U, 5U);
cksum.update(m_headerData + 43U, 4U);
unsigned char checkSum[2U];
cksum.result(checkSum);
m_headerData[47U] = checkSum[0];
m_headerData[48U] = SLOW_DATA_TYPE_HEADER | 1U;
m_headerData[49U] = checkSum[1];
for (int i=0; i<SLOW_DATA_FULL_BLOCK_SIZE; i+=3) {
store_i(rev_byte(m_headerData[i] ^ 0x70), header_data_buf+i*8, 8);
store_i(rev_byte(m_headerData[i+1] ^ 0x4f), header_data_buf+((i+1)*8), 8);
store_i(rev_byte(m_headerData[i+2] ^ 0x93), header_data_buf+((i+2)*8), 8);
}
}
#endif /* INCLUDED_DSTAR_HEADER_H */

View File

@ -30,6 +30,7 @@
#include "dstar_tx_sb_impl.h"
// #include "dstar_const.h"
#include <op25_imbe_frame.h>
#include "dstar_header.h"
#include <vector>
#include <stdint.h>
@ -52,6 +53,15 @@ static inline void print_result(char title[], const uint8_t r[], int len) {
}
#endif
static inline void sstring(const char s[], char dest[8]) {
memset(dest, ' ', 8);
memcpy(dest, s, std::min(strlen(s), (size_t)8));
for (int i=0; i<8; i++) {
if (dest[i] < ' ')
dest [i] = ' ';
}
}
static const uint8_t FS[24] = { 1,0,1,0,1,0,1,0,1,0,1,1,0,1,0,0,0,1,1,0,1,0,0,0 };
static const uint8_t FS_DUMMY[24] = { 0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1 };
@ -102,8 +112,13 @@ dstar_tx_sb_impl::config()
FILE * fp1 = fopen(d_config_file, "r");
char line[256];
char * cp;
// TODO: add code to generate slow speed datastream
return;
int flag1, flag2, flag3;
char rptcall1[8];
char rptcall2[8];
char urcall[8];
char mycall1[8];
char mycall2[8];
if (!fp1) {
fprintf(stderr, "dstar_tx_sb_impl:config: failed to open %s\n", d_config_file);
return;
@ -112,12 +127,25 @@ dstar_tx_sb_impl::config()
cp = fgets(line, sizeof(line) - 2, fp1);
if (!cp) break;
if (line[0] == '#') continue;
#if 0
if (memcmp(line, "ft=", 3) == 0)
sscanf(&line[3], "%d", &d_ft);
#endif
if (memcmp(line, "flag1=", 6) == 0)
sscanf(&line[6], "%x", &flag1);
else if (memcmp(line, "flag2=", 6) == 0)
sscanf(&line[6], "%x", &flag2);
else if (memcmp(line, "flag3=", 6) == 0)
sscanf(&line[6], "%x", &flag3);
else if (memcmp(line, "rptcall2=", 9) == 0)
sstring(&line[9], rptcall2);
else if (memcmp(line, "rptcall1=", 9) == 0)
sstring(&line[9], rptcall1);
else if (memcmp(line, "urcall=", 7) == 0)
sstring(&line[7], urcall);
else if (memcmp(line, "mycall1=", 8) == 0)
sstring(&line[8], mycall1);
else if (memcmp(line, "mycall2=", 8) == 0)
sstring(&line[8], mycall2);
}
fclose(fp1);
make_dstar_header(d_dstar_header_data, flag1 & 0xff, flag2 & 0xff, flag3 & 0xff, rptcall2, rptcall1, urcall, mycall1, mycall2);
}
void
@ -151,7 +179,7 @@ dstar_tx_sb_impl::general_work (int noutput_items,
if (d_frame_counter == 0)
memcpy(out+72, FS, 24);
else
memcpy(out+72, FS_DUMMY, 24);
memcpy(out+72, d_dstar_header_data+((d_frame_counter-1) * 24), 24);
d_frame_counter = (d_frame_counter + 1) % 21;
in += 160;
nconsumed += 160;

View File

@ -60,6 +60,7 @@ namespace gr {
const char * d_config_file;
ambe_encoder d_encoder;
int d_frame_counter;
uint8_t d_dstar_header_data[480];
};
} // namespace op25_repeater

View File

@ -0,0 +1,128 @@
#ifndef _EZPWD_ASSERTER
#define _EZPWD_ASSERTER
#include <algorithm>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <sstream>
namespace ezpwd {
#define ISEQUAL( ... ) isequal(__FILE__, __LINE__, __VA_ARGS__ )
#define ISTRUE( ... ) istrue( __FILE__, __LINE__, __VA_ARGS__ )
#define ISFALSE( ... ) isfalse(__FILE__, __LINE__, __VA_ARGS__ )
#define ISNEAR( ... ) isnear( __FILE__, __LINE__, __VA_ARGS__ )
#define FAILURE( ... ) failure(__FILE__, __LINE__, __VA_ARGS__ )
struct asserter {
bool failed; // The last test failed
int failures; // Total number of failures
std::string out; // Last failure
asserter()
: failed( false )
, failures( 0 )
, out()
{
;
}
//
// output( <std::ostream> ) -- Output description of last failed test (or nothing if successful)
// <std::ostream> << <asserter>
//
std::ostream &output(
std::ostream &lhs )
const
{
return lhs << out;
}
//
// (bool) <asserter> -- Return status of last test
//
operator bool()
{
return failed;
}
template < typename T >
asserter &istrue( const char *file, int line, const T &a, const std::string &comment = std::string() )
{
return isequal( file, line, !!a, true, comment );
}
template < typename T >
asserter &isfalse( const char *file, int line, const T &a, const std::string &comment = std::string() )
{
return isequal( file, line, !!a, false, comment );
}
template < typename T >
asserter &isequal( const char *file, int line, const T &a, const T &b, const std::string &comment = std::string() )
{
if ( ! ( a == b )) {
std::ostringstream oss;
oss << a << " != " << b;
return failure( file, line, oss.str(), comment );
}
return success();
}
template < typename T >
asserter &isnear( const char *file, int line, const T &a, const T &b, const T &delta, const std::string &comment = std::string() )
{
T difference;
difference = ( a < b
? T( b - a )
: T( a - b ));
if ( ! ( difference < ( delta < T( 0 ) ? T( -delta ) : T( delta )))) {
std::ostringstream oss;
oss << std::setprecision( 13 ) << a << " != " << b << " +/- " << delta;
return failure( file, line, oss.str(), comment );
}
return success();
}
asserter &failure( const char *file, int line, const std::string &comparison,
const std::string &comment = std::string() )
{
++failures;
const char *needle = "/";
const char *slash = std::find_end( file, file + strlen( file ),
needle, needle + strlen( needle ));
if ( slash == file + strlen( file ))
slash = file;
else
slash += 1;
std::ostringstream oss;
oss
<< std::setw( 24 ) << slash << ", "
<< std::setw( -5 ) << line
<< "; FAILURE: " << comparison
<< ( comment.size() ? ": " : "" ) << comment
<< std::endl;
out = oss.str();
failed = true;
return *this;
}
asserter &success()
{
out.clear();
failed = false;
return *this;
}
}; // class asserter
} // namespace ezpwd
std::ostream &operator<<(
std::ostream &lhs,
ezpwd::asserter &rhs )
{
return rhs.output( lhs );
}
#endif // _EZPWD_ARRAY

View File

@ -0,0 +1,485 @@
/*
* Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library
*
* Copyright (c) 2017, Hard Consulting Corporation.
*
* Ezpwd Reed-Solomon 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. See the LICENSE file at the top of the
* source tree. Ezpwd Reed-Solomon is also available under Commercial license. The Djelic BCH code
* under djelic/ and the c++/ezpwd/bch_base wrapper is redistributed under the terms of the GPLv2+,
* regardless of the overall licensing terms.
*
* Ezpwd Reed-Solomon 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.
*/
#ifndef _EZPWD_BCH
#define _EZPWD_BCH
#include <sstream>
#include "rs_base" // Basic DEBUG, EZPWD_... preprocessor stuff, ezpwd::log_, etc.
#include "bch_base"
namespace ezpwd {
//
// ezpwd::bch_base -- Interface to underlying Djelic Linux Kernel API
// ezpwd::bch<SYMBOLS, CORRECTION> -- General BCH codec types; min. CORRECTION capacity, undefined PAYLOAD
//
// These implementations retain the original Djelic Linux Kernel API; specifically, they find
// a BCH codec of the given Galois order M (ie. has codewords of size 2**M-1), and at least the
// target bit-error correction capacity T. They may correct more than T errors, and the number
// of parity ECC bits will be selected by the algorithm. You need to compute the maximum
// non-parity payload by computing _bch->n - _bch->ecc_bits.
//
// The data and parity bits must always be on separate blocks of int8_t/uint8_t-sized data
// in the container. This is required because the underlying API must break the data and parity
// out as separate arrays for processing. So, if the computed ecc_bits is not evenly divisible
// by 8, some care must be taken to ensure that it is packed into exactly ecc_bytes of data at
// the end of the supplied container. Alternatively, it can be kept in a separate container.
// This is probably safest, as the bch_base/bch/BCH classes will never attempt to resize the
// data/parity containers when supplied separately.
//
// Like the Reed-Solomon APIs, the bch_base/bch/BCH APIs will alter the size of a variable
// container in encode(...), to add the BCH ECC "parity" data (eg. std::vector, std::string).
// Fixed containers (eg. std::array) are never resized, and it is assumed that ecc_bytes of
// parity data exist at the end of the container.
//
class bch_base {
public:
ezpwd::bch_control *_bch;
bch_base( const bch_base & ) = delete; // no copy-constructor
bch_base(
size_t m,
size_t t,
unsigned int prim_poly = 0 )
: _bch( ezpwd::init_bch( int( m ), int( t ), prim_poly ))
{
;
}
virtual ~bch_base()
{
ezpwd::free_bch( this->_bch );
}
size_t ecc_bytes()
const
{
return _bch->ecc_bytes;
}
size_t ecc_bits()
const
{
return _bch->ecc_bits;
}
size_t t()
const
{
return _bch->t;
}
//
// <ostream> << bch_base -- output codec in standard BCH( N, N-ECC, T ) form
//
virtual std::ostream &output(
std::ostream &lhs )
const
{
return lhs << *this->_bch;
}
//
// encode -- container interfaces
//
// Returns number of ECC *bits* initialized (to be consistent w/ decode, which returns
// number of bit errors corrected).
//
int encode(
std::string &data )
const
{
typedef uint8_t uT;
typedef std::pair<uT *, uT *>
uTpair;
data.resize( data.size() + ecc_bytes() );
return encode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ));
}
int encode(
const std::string &data,
std::string &parity )
const
{
typedef uint8_t uT;
typedef std::pair<const uT *, const uT *>
cuTpair;
typedef std::pair<uT *, uT *>
uTpair;
parity.resize( ecc_bytes() );
return encode( cuTpair( (const uT *)&data.front(), (const uT *)&data.front() + data.size() ),
uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ));
}
template < typename T >
int encode(
std::vector<T> &data )
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<uT *, uT *>
uTpair;
data.resize( data.size() + ecc_bytes() );
return encode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ));
}
template < typename T >
int encode(
const std::vector<T>&data,
std::vector<T> &parity )
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<const uT *, const uT *>
cuTpair;
typedef std::pair<uT *, uT *>
uTpair;
parity.resize( ecc_bytes() );
return encode( cuTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ),
uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ));
}
template < typename T, size_t N >
int encode(
std::array<T,N> &data,
int pad = 0 ) // ignore 'pad' symbols at start of array
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<uT *, uT *>
uTpair;
return encode( uTpair( (uT *)&data.front() + pad, (uT *)&data.front() + data.size() ));
}
//
// encode -- denote data+parity or data, parity using pairs of uint8_t iterators
// encode -- base implementation, in terms of uint8_t pointers
//
virtual int encode(
const std::pair<uint8_t *, uint8_t *>
&data )
const
{
return encode( data.first, data.second - data.first - ecc_bytes(), data.second - ecc_bytes() );
}
virtual int encode(
const std::pair<const uint8_t *, const uint8_t *>
&data,
const std::pair<uint8_t *, uint8_t *>
&parity )
const
{
if ( size_t( parity.second - parity.first ) != ecc_bytes() ) {
EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: parity length incompatible with number of ECC bytes", -1 );
}
return encode( data.first, data.second - data.first, parity.first );
}
virtual int encode(
const uint8_t *data,
size_t len,
uint8_t *parity )
const
{
memset( parity, 0, ecc_bytes() ); // Djelic encode_bch requires ECC to be initialized to 0
ezpwd::encode_bch( this->_bch, data, len, parity );
return int( ecc_bits() );
}
//
// decode -- container interface, w/ optional corrected bit-error positions reported
//
// Does not correct errors in parity!
//
int decode(
std::string &data,
std::vector<int> *position= 0 )
const
{
typedef uint8_t uT;
typedef std::pair<uT *, uT *>
uTpair;
return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ),
position );
}
int decode(
std::string &data,
std::string &parity,
std::vector<int> *position= 0 )
const
{
typedef uint8_t uT;
typedef std::pair<uT *, uT *>
uTpair;
return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ),
uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ),
position );
}
template < typename T >
int decode(
std::vector<T> &data,
std::vector<int> *position= 0 )
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<uT *, uT *>
uTpair;
return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ),
position );
}
template < typename T >
int decode(
std::vector<T> &data,
std::vector<T> &parity,
std::vector<int> *position= 0 )
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<uT *, uT *>
uTpair;
return decode( uTpair( (uT *)&data.front(), (uT *)&data.front() + data.size() ),
uTpair( (uT *)&parity.front(), (uT *)&parity.front() + parity.size() ),
position );
}
template < typename T, size_t N >
int decode(
std::array<T,N> &data,
int pad = 0, // ignore 'pad' symbols at start of array
std::vector<int> *position= 0 )
const
{
typedef typename std::make_unsigned<T>::type
uT;
typedef std::pair<uT *, uT *>
uTpair;
return decode( uTpair( (uT *)&data.front() + pad, (uT *)&data.front() + data.size() ),
position );
}
//
// decode -- denote data+parity or data, parity using pairs of uint8_t iterators
// decode -- decode and correct BCH codeword, returning number of corrections, or -1 if failed
//
// Corrects data in-place (unlike the basic Djelic Linux Kernel API, which only returns
// error positions. For consistency with ezpwd::rs..., we report all error positions as
// std::vector<int>, even though the underlying Djelic API reports them as arrays of
// unsigned int.
//
virtual int decode(
const std::pair<uint8_t *, uint8_t *>
&data,
std::vector<int> *position= 0 )
const
{
return decode( data.first, data.second - data.first - ecc_bytes(), data.second - ecc_bytes(),
position );
}
virtual int decode(
const std::pair<uint8_t *, uint8_t *>
&data,
const std::pair<uint8_t *, uint8_t *>
&parity,
std::vector<int> *position= 0 )
const
{
if ( size_t( parity.second - parity.first ) != ecc_bytes() ) {
EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: parity length incompatible with number ECC bytes", -1 );
}
return decode( data.first, data.second - data.first, parity.first,
position );
}
virtual int decode(
uint8_t *data,
size_t len,
uint8_t *parity,
std::vector<int> *position= 0 )
const
{
if ( position )
position->resize( t() * 2 ); // may be able to correct beyond stated capacity!
int corrects = ezpwd::correct_bch(
this->_bch, data, len, parity, 0, 0,
position ? (unsigned int *)&(*position)[0] : 0 );
if ( position && corrects >= 0 )
position->resize( corrects );
return corrects;
}
//
// {en,de}coded -- returns an encoded/corrected copy of the provided container
//
// NOTE:
//
// Must return exceptions on failure; If exceptions inhibited, returns a
// default-constructed instance of the supplied data container. This may be sufficient to
// reliably deduce failure; if not, this interface should not be used.
//
// Overloads decoded to also allow recovery of corrected error positions and count.
//
template <typename C>
C encoded(
C data )
const
{
if ( encode( data ) < 0 )
EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: Could not encode data", C() );
return data;
}
template <typename C>
C decoded(
C data )
const
{
if ( decode( data ) < 0 )
EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: Could not decode data", C() );
return data;
}
template <typename C>
C decoded(
C data,
std::vector<int> &position )
const
{
if ( decode( data, &position ) < 0 )
EZPWD_RAISE_OR_RETURN( std::runtime_error, "BCH: Could not decode data", C() );
return data;
}
}; // class bch_base
template < size_t SYMBOLS, size_t CORRECTION >
class bch
: public bch_base
{
public:
bch()
: bch_base( ezpwd::log_<SYMBOLS + 1>::value, CORRECTION )
{
;
}
virtual ~bch()
{
;
}
}; // class bch
//
// std::ostream << ezpwd::bch_base
//
// Output a BCH codec description in standard form eg. BCH( 255, 239, 2 )
//
inline
std::ostream &operator<<(
std::ostream &lhs,
const ezpwd::bch_base
&rhs )
{
return rhs.output( lhs );
}
//
// ezpwd::BCH<SYMBOLS, PAYLOAD, CAPACITY> -- Standard BCH codec types
//
// Specify and create a standard BCH codec with exactly the specified capacities. We create
// the undering BCH codec using SYMBOLS and CORRECTION capacity; the actual correction capacity
// T, the number of PARITY bits and hence PAYLOAD (CAPACITY - PARITY) is selected automatically
// by the underlying Djelic Linux Kernel BCH codec API. For this interface, we demand that the
// caller *knows* all of these values at compile time, however, mostly for future optimization
// purposes. We validate them, and fail the constructor if they don't match. See bch_test for
// an enumeration of all possible BCH codecs.
//
// In the future, this API may be re-implemented to not use the generic BCH API, but a more
// optimized locally-defined implementation that leverages the fixed SYMBOLS, PAYLOAD and
// CORRECTION capacities to produce more optimal code.
//
template < size_t SYMBOLS, size_t PAYLOAD, size_t CORRECTION >
class BCH
: public bch<SYMBOLS, CORRECTION>
{
public:
static const size_t M = ezpwd::log_<SYMBOLS + 1>::value; // Galois field order; eg. 255 --> 8
static const size_t N = SYMBOLS;
static const size_t T = CORRECTION;
static const size_t LOAD = PAYLOAD;
BCH()
: bch<SYMBOLS, CORRECTION>()
{
if ( this->_bch->t != T || this->_bch->n != N
|| this->_bch->n - this->_bch->ecc_bits != LOAD ) {
std::ostringstream err;
this->output( err )
<< " specified doesn't match underlying " << *this->_bch << " produced.";
EZPWD_RAISE_OR_ABORT( std::runtime_error, err.str().c_str() );
}
}
virtual ~BCH()
{
;
}
//
// <ostream> << BCH<...> -- output codec in standard BCH( N, N-ECC, T ) form
//
virtual std::ostream &output(
std::ostream &lhs )
const
{
return lhs
<< "BCH( " << std::setw( 3 ) << N
<< ", " << std::setw( 3 ) << LOAD
<< ", " << std::setw( 3 ) << T
<< " )";
}
}; // class BCH
//
// std::ostream << ezpwd::BCH<...>
//
// Output a BCH codec description in standard form eg. BCH( 255, 239, 2 )
//
// NOTE: clang/gcc disagree on the scoping of operator<< template/non-template functions...
//
template <size_t SYMBOLS, size_t PAYLOAD, size_t CORRECTION>
inline
std::ostream &operator<<(
std::ostream &lhs,
const ezpwd::BCH<SYMBOLS, PAYLOAD, CORRECTION>
&rhs )
{
return rhs.output( lhs );
}
} // namespace ezpwd
#endif // _EZPWD_BCH

View File

@ -0,0 +1,219 @@
/*
* Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library
*
* Copyright (c) 2017, Hard Consulting Corporation.
*
* Ezpwd Reed-Solomon 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. See the LICENSE file at the top of the
* source tree. Ezpwd Reed-Solomon is also available under Commercial license. The Djelic BCH code
* under djelic/ and the c++/ezpwd/bch_base wrapper is redistributed under the terms of the GPLv2+,
* regardless of the overall licensing terms.
*
* Ezpwd Reed-Solomon 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.
*/
#ifndef _EZPWD_BCH_BASE
#define _EZPWD_BCH_BASE
#include <iostream>
#include <iomanip>
#include <vector>
//
// Presently, we simply import the Linux Kernel's "C" BCH API directly into the ezpwd:: namespace In
// order to compile, you must (at least) run "make djelic" in the base directory of the
// https://github.com/pjkundert/bch.git repo.
//
namespace ezpwd {
/**
* struct bch_control - BCH control structure
* @m: Galois field order
* @n: maximum codeword size in bits (= 2^m-1)
* @t: error correction capability in bits
* @ecc_bits: ecc exact size in bits, i.e. generator polynomial degree (<=m*t)
* @ecc_bytes: ecc max size (m*t bits) in bytes
* @a_pow_tab: Galois field GF(2^m) exponentiation lookup table
* @a_log_tab: Galois field GF(2^m) log lookup table
* @mod8_tab: remainder generator polynomial lookup tables
* @ecc_buf: ecc parity words buffer
* @ecc_buf2: ecc parity words buffer
* @xi_tab: GF(2^m) base for solving degree 2 polynomial roots
* @syn: syndrome buffer
* @cache: log-based polynomial representation buffer
* @elp: error locator polynomial
* @poly_2t: temporary polynomials of degree 2t
*/
/**
* init_bch - initialize a BCH encoder/decoder
* @m: Galois field order, should be in the range 5-15
* @t: maximum error correction capability, in bits
* @prim_poly: user-provided primitive polynomial (or 0 to use default)
*
* Returns:
* a newly allocated BCH control structure if successful, NULL otherwise
*
* This initialization can take some time, as lookup tables are built for fast
* encoding/decoding; make sure not to call this function from a time critical
* path. Usually, init_bch() should be called on module/driver init and
* free_bch() should be called to release memory on exit.
*
* You may provide your own primitive polynomial of degree @m in argument
* @prim_poly, or let init_bch() use its default polynomial.
*
* Once init_bch() has successfully returned a pointer to a newly allocated
* BCH control structure, ecc length in bytes is given by member @ecc_bytes of
* the structure.
*/
/**
* encode_bch - calculate BCH ecc parity of data
* @bch: BCH control structure
* @data: data to encode
* @len: data length in bytes
* @ecc: ecc parity data, must be initialized by caller
*
* The @ecc parity array is used both as input and output parameter, in order to
* allow incremental computations. It should be of the size indicated by member
* @ecc_bytes of @bch, and should be initialized to 0 before the first call.
*
* The exact number of computed ecc parity bits is given by member @ecc_bits of
* @bch; it may be less than m*t for large values of t.
*/
/**
* decode_bch - decode received codeword and find bit error locations
* @bch: BCH control structure
* @data: received data, ignored if @calc_ecc is provided
* @len: data length in bytes, must always be provided
* @recv_ecc: received ecc, if NULL then assume it was XORed in @calc_ecc
* @calc_ecc: calculated ecc, if NULL then calc_ecc is computed from @data
* @syn: hw computed syndrome data (if NULL, syndrome is calculated)
* @errloc: output array of error locations
*
* Returns:
* The number of errors found, or -EBADMSG if decoding failed, or -EINVAL if
* invalid parameters were provided
*
* Depending on the available hw BCH support and the need to compute @calc_ecc
* separately (using encode_bch()), this function should be called with one of
* the following parameter configurations -
*
* by providing @data and @recv_ecc only:
* decode_bch(@bch, @data, @len, @recv_ecc, NULL, NULL, @errloc)
*
* by providing @recv_ecc and @calc_ecc:
* decode_bch(@bch, NULL, @len, @recv_ecc, @calc_ecc, NULL, @errloc)
*
* by providing ecc = recv_ecc XOR calc_ecc:
* decode_bch(@bch, NULL, @len, NULL, ecc, NULL, @errloc)
*
* by providing syndrome results @syn:
* decode_bch(@bch, NULL, @len, NULL, NULL, @syn, @errloc)
*
* Once decode_bch() has successfully returned with a positive value, error
* locations returned in array @errloc should be interpreted as follows -
*
* if (errloc[n] >= 8*len), then n-th error is located in ecc (no need for
* data correction)
*
* if (errloc[n] < 8*len), then n-th error is located in data and can be
* corrected with statement data[errloc[n]/8] ^= 1 << (errloc[n] % 8);
*
* Note that this function does not perform any data correction by itself, it
* merely indicates error locations.
*/
/**
* init_bch - initialize a BCH encoder/decoder
* @m: Galois field order, should be in the range 5-15
* @t: maximum error correction capability, in bits
* @prim_poly: user-provided primitive polynomial (or 0 to use default)
*
* Returns:
* a newly allocated BCH control structure if successful, NULL otherwise
*
* This initialization can take some time, as lookup tables are built for fast
* encoding/decoding; make sure not to call this function from a time critical
* path. Usually, init_bch() should be called on module/driver init and
* free_bch() should be called to release memory on exit.
*
* You may provide your own primitive polynomial of degree @m in argument
* @prim_poly, or let init_bch() use its default polynomial.
*
* Once init_bch() has successfully returned a pointer to a newly allocated
* BCH control structure, ecc length in bytes is given by member @ecc_bytes of
* the structure.
*/
extern "C" {
#include "../../djelic_bch.h"
}
//
// correct_bch -- corrects data (but not parity!), as suggested by decode_bch, above
//
// A convenience interface that defaults all of the not strictly required parameters, and
// automatically corrects bit-errors in data *and* the supplied parity. Does not attempt to
// correct bit errors found in the parity data. If not supplied, 'errloc' is allocated
// internally; otherwise, it is assumed to be of at least size bch->t (the minimum error
// correction capacity of the BCH codec).
//
// However, beware -- at larger values of T, the actual correction capacity of the BCH codec
// could be greater than the requested T. Therefore, it is recommended that you always supply a
// larger than required errloc array; recommend T*2?
//
inline
int correct_bch(
struct bch_control *bch,
uint8_t *data,
unsigned int len,
uint8_t *recv_ecc,
const uint8_t *calc_ecc= 0,
const unsigned int *syn = 0,
unsigned int *errloc = 0 ) // must be sized at least bch->t; often, greater!
{
unsigned int _errloc[511]; // much larger than the correction capacity of largest supported BCH codec
if ( ! errloc )
errloc = _errloc;
int err = decode_bch( bch, data, len, recv_ecc, calc_ecc, syn, errloc );
if ( err > 0 ) {
// A +'ve number of bit-error correction locations were found
for ( int n = 0; n < err; ++n ) {
/**
* if (errloc[n] < 8*len), then n-th error is located in data and can be corrected
* with statement data[errloc[n]/8] ^= 1 << (errloc[n] % 8). If in the parity, it
* is assumed to be located at the end of the data, so offset by 'len' bytes.
*/
if ( errloc[n] < 8*len ) {
data[errloc[n] / 8] ^= 1 << ( errloc[n] % 8 );
} else if ( recv_ecc && errloc[n] < 8 * len + 8 * bch->ecc_bytes ) {
recv_ecc[errloc[n] / 8 - len]
^= 1 << ( errloc[n] % 8 );
}
}
}
return err;
}
//
// <ostream> << <ezpwd::bch_control> -- output codec in standard BCH( N, N-ECC, T ) form
//
inline
std::ostream &operator<<(
std::ostream &lhs,
const ezpwd::bch_control
&bch )
{
return lhs
<< "BCH( " << std::setw( 3 ) << bch.n
<< ", " << std::setw( 3 ) << bch.n - bch.ecc_bits
<< ", " << std::setw( 3 ) << bch.t
<< " )";
}
} // namespace ezpwd
#endif // _EZPWD_BCH_BASE

View File

@ -0,0 +1,506 @@
/*
* Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library
*
* Copyright (c) 2014, Hard Consulting Corporation.
*
* Ezpwd Reed-Solomon 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. See the LICENSE file at the top of the
* source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base
* is redistributed under the terms of the LGPL, regardless of the overall licensing terms.
*
* Ezpwd Reed-Solomon 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.
*/
#ifndef _EZPWD_CORRECTOR
#define _EZPWD_CORRECTOR
#include "rs"
#include "serialize"
namespace ezpwd {
//
// best_avg -- collect <password>,<confidence> guesses, and return the unambiguous best one
//
typedef std::map<std::string, std::pair<int, int>> // (<password>, (<count>, <avgsum>))
best_avg_base_t;
class best_avg
: public best_avg_base_t
{
public:
using best_avg_base_t::begin;
using best_avg_base_t::end;
using best_avg_base_t::insert;
using best_avg_base_t::find;
using best_avg_base_t::iterator;
using best_avg_base_t::const_iterator;
using best_avg_base_t::value_type;
using best_avg_base_t::mapped_type;
//
// add -- add the given pct to the current average for <string> str
//
iterator add(
const std::string &str,
int pct )
{
iterator i = find( str );
if ( i == end() )
i = insert( i, value_type( str, mapped_type() ));
i->second.first += 1;
i->second.second += pct;
return i;
}
//
// best -- return the unambiguously best value (average is >, or == but longer), or end()
//
const_iterator best()
const
{
const_iterator top = end();
bool uni = false;
for ( const_iterator i = begin(); i != end(); ++i ) {
if ( top == end()
or i->second.second/i->second.first > top->second.second/top->second.first
or ( i->second.second/i->second.first == top->second.second/top->second.first
and i->first.size() > top->first.size())) {
top = i;
uni = true;
} else if ( i->second.second/i->second.first == top->second.second/top->second.first
and i->first.size() == top->first.size()) {
uni = false;
}
}
return uni ? top : end();
}
//
// evaluation -- process a (<password>,(<count>,<avgsum>)) into (<average>,<password>)
// sort -- return a multimap indexed by <average> --> <string>
// output -- output the <string>: <average>, sorted by average
//
static std::pair<const int,const std::string &>
evaluation( const value_type &val )
{
return std::pair<const int,const std::string &>( val.second.second/val.second.first, val.first );
}
typedef std::multimap<const int,const std::string &>
sorted_t;
sorted_t sort()
const
{
sorted_t dst;
std::transform( begin(), end(), std::inserter( dst, dst.begin() ), evaluation );
return dst;
}
std::ostream &output(
std::ostream &lhs )
const
{
for ( auto i : sort() )
lhs << std::setw( 16 ) << i.second
<< ": " << std::setw( 3 ) << i.first
<< std::endl;
return lhs;
}
};
} // namespace ezpwd
std::ostream &operator<<(
std::ostream &lhs,
const ezpwd::best_avg &rhs )
{
return rhs.output( lhs );
}
namespace ezpwd {
//
// ezpwd::corrector -- Apply statistical corrections to a string, returning the confidence
//
// All methods are static; no instance is required, as this is primarily used to create
// external language APIs.
//
template <
size_t PARITY,
size_t N = 64,
typename SERIAL = serialize::base< N, serialize::ezpwd< N >>>
class corrector {
public:
static
std::ostream &output(
std::ostream &lhs )
{
lhs << "corrector<PARITY=" << PARITY << ",N=" << N << ",SERIAL=" << SERIAL() << ">";
return lhs;
}
//
// parity(<string>) -- Returns 'PARITY' base-N symbols of R-S parity to the supplied password
//
static std::string parity(
const std::string &password )
{
std::string parity;
rscodec.encode( password, parity );
SERIAL::encode( parity );
return parity;
}
//
// encode(<string>) -- append PARITY base-N parity symbols to password
//
// The supplied password buffer size must be sufficient to contain PARITY additional
// symbols, plus the terminating NUL. Returns the resultant encoded password size
// (excluding the NUL).
//
static size_t encode(
std::string &password )
{
password += parity( password );
return password.size();
}
static size_t encode(
char *password,
size_t size ) // maximum available size
{
size_t len = ::strlen( password ); // length w/o terminating NUL
if ( len + PARITY + 1 > size )
throw std::runtime_error( "ezpwd::rspwd::encode password buffer has insufficient capacity" );
std::string par = parity( std::string( password, password + len ));
if ( par.size() != PARITY )
throw std::runtime_error( "ezpwd::rspwd::encode computed parity with incorrect size" );
std::copy( par.begin(), par.end(), password + len );
len += PARITY;
password[len] = 0;
return len;
}
//
// decode(<string>[,...]) -- Applies R-S error correction on the encoded string, removing parity
//
// Up to 'PARITY' Reed-Solomon parity symbols are examined, to determine if the supplied
// string is a valid R-S codeword and hence very likely to be correct. Optionally supply a
// vector of erasure positions.
//
// An optional 'minimum' final password length may be provided; no R-S parity is assumed
// to exist in the first 'minimum' password characters (default: PARITY). This prevents
// accidentally finding valid R-S codewords in passwords of known minimum length; validation
// codes, for example. Likewise, the optional 'maximum' allows us to limit the number of
// parity symbols that may be assumed to be missing from the end of the codeword.
//
// Returns a confidence strength rating, which is the ratio:
//
// 100 - ( errors * 2 + erasures ) * 100 / parity
//
// if an R-S codeword was solved, and 0 otherwise. If a codeword is solved, but the number
// of errors and erasures corrected indicates that all parity was consumed, the caller may
// opt to not use the corrected string, because there is a chance that our R-S polynomial
// was overwhelmed with errors and actually returned an incorrect codeword. Therefore,
// solving a codeword using all available parity results in 100 - PARITY * 100 / PARITY ==
// 0, which indicates that there is no certainty of correctness; all R-S parity resources
// were used in error/erasure recover, with none left to confirm that the result is actually
// correct. If only zero-strength results are achieved, the longest will be returned (the
// full, original string).
//
// Supports the following forms of error/erasure:
//
// 0) Full parity. All data and parity supplied, and an R-S codeword is solved.
//
// 1) Partial parity. All data and some parity supplied; remainder are deemed erasures.
//
// If PARITY > 2, then up to PARITY/2-1 trailing parity terms are marked as erasures.
// If the R-S codeword is solved and a safe number of errors are found, then we can have
// reasonable confidence that the string is correct.
//
// 1a) Erase errors. Permute the combinations of up to PARITY-1 erasures.
//
// o) Raw password. No parity terms supplied; not an R-S codeword
//
// If none of the error/erasure forms succeed, the password is returned unmodified.
//
// If a non-zero 'minimum' or 'maximum' are provided, they constrain the possible
// resultant password sizes that will be attempted.
//
static
int decode(
std::string &password,
const std::vector<int>
&erasures,
size_t minimum = PARITY,//always deemed at least 1
size_t maximum = 0 ) // if 0, no limit
{
int confidence;
best_avg best;
// Full/Partial parity. Apply some parity erasure if we have some erasure/correction
// capability while maintaining at least one excess parity symbol for verification.
// This can potentially result in longer password being returned, if the R-S decoder
// accidentally solves a codeword.
//
// For example, if PARITY=3 (or 4) then (PARITY+1)/2 == 2, and we would only attempt up
// to 1 parity erasure. This would leave 1 parity symbol to replace the 1 erasure, and
// 1 remaining to validate the integrity of the password.
//
// The password must be long enough to contain at least 1 non-parity symbol, and the
// designated number of non-erased parity symbols! However, by convention we'll demand
// that the password contain at least PARITY symbols -- any less, and we can
// accidentally correct the few remaining password symbols.
//
// Also, if any parity symbols won't decode (eg. were entered in error), we must deem
// them to be erasures, too, and if the number of erasures exceeds the capacity of the
// R-S codec, it'll fail (throw an exception, or at best solve with 0 confidence).
for ( size_t era = 0 // how many parity symbols to deem erased
; era < (PARITY+1)/2
; ++era ) {
if ( password.size() < ( minimum ? minimum : 1 ) + PARITY - era ) {
#if defined( DEBUG ) && DEBUG >= 1
output( std::cout )
<< " Rejected too short password \""
<< password << std::string( era, '_' )
<< "\"" << " (" << era << " parity skipped)"
<< std::endl;
#endif
continue; // too few password symbols to start checking parity
}
if ( maximum and password.size() > maximum + PARITY - era ) {
#if defined( DEBUG ) && DEBUG >= 1
output( std::cout )
<< " Rejected too long password \""
<< password << std::string( era, '_' )
<< "\"" << " (" << era << " parity skipped)"
<< std::endl;
#endif
continue; // too few parity symbols erased to start checking parity
}
// Copy password, adding 'era' additional NULs
std::string fixed( password.size() + era, 0 );
std::copy( password.begin(), password.end(), fixed.begin() );
// Decode the base-N parity, denoting any invalid (mistyped or trailing NUL) symbols
// as erasures (adjust erasure offsets to be from start of password, not start of
// parity). All newly added 'era' symbols will be NUL, and will be invalid. After
// decoding parity, if we've slipped below our minimum R-S capacity threshold
// (ie. because of mistyped parity symbols), don't attempt.
std::vector<int> all_era;
SERIAL::decode( fixed.begin() + fixed.size() - PARITY,
fixed.begin() + fixed.size(), &all_era, 0,
serialize::ws_invalid, serialize::pd_invalid );
if ( all_era.size() >= (PARITY+1)/2 ) {
#if defined( DEBUG ) && DEBUG >= 1
output( std::cout )
<< " Rejected low parity password \""
<< password << std::string( era, '_' )
<< "\"" << " (" << all_era.size() << " parity erasures + "
<< era << " skipped)"
<< std::endl;
#endif
continue; // Too many missing parity symbols
}
if ( all_era.size() + erasures.size() > PARITY ) {
#if defined( DEBUG ) && DEBUG >= 1
output( std::cout )
<< " Rejected hi erasure password \""
<< password << std::string( era, '_' )
<< "\"" << " (" << all_era.size() + erasures.size() << " total erasures + "
<< era << " skipped)"
<< std::endl;
#endif
continue; // Total erasures beyond capacity
}
for ( auto &o : all_era )
o += fixed.size() - PARITY;
std::copy( erasures.begin(), erasures.end(), std::back_inserter( all_era ));
// Enough parity to try to decode. A successful R-S decode with 0 (remaining)
// confidence indicates a successfully validated R-S codeword! Use it (ex. parity).
try {
std::vector<int> position;
int corrects= rscodec.decode( fixed, all_era, &position );
confidence = strength<PARITY>( corrects, all_era, position );
fixed.resize( fixed.size() - PARITY );
if ( confidence >= 0 )
best.add( fixed, confidence );
#if defined( DEBUG ) && DEBUG >= 1
output( std::cout )
<< " Reed-Solomon w/ " << era << " of " << PARITY
<< " parity erasures " << std::setw( 3 ) << confidence
<< "% confidence: \"" << password
<< "\" ==> \"" << fixed
<< "\" (corrects: " << corrects
<< ", erasures at " << all_era
<< ", fixed at " << position << "): "
<< std::endl
<< best;
#endif
} catch ( std::exception &exc ) {
#if defined( DEBUG ) && DEBUG >= 2 // should see only when ezpwd::reed_solomon<...>::decode fails
output( std::cout ) << " invalid part parity password: " << exc.what() << std::endl;
#endif
}
}
// Partial parity, but below threshold for usable error detection. For the first 1 to
// (PARITY+1)/2 parity symbols (eg. for PARITY == 3, (PARITY+1)/2 == 1 ), we cannot
// perform meaningful error or erasure detection. However, if we see that the terminal
// symbols match the R-S symbols we expect from a correct password, we'll ascribe a
// partial confidence due to the matching parity symbols.
//
// password: sock1t
// w/ 3 parity: sock1tkeB
// password ----^^^^^^
// ^^^--- parity
//
for ( size_t era = (PARITY+1)/2 // how many parity symbols are not present
; era < PARITY
; ++era ) {
if ( password.size() < ( minimum ? minimum : 1 ) + PARITY - era ) {
#if defined( DEBUG ) && DEBUG >= 1
output( std::cout )
<< " Rejected too short password \""
<< password << std::string( era, '_' )
<< "\""
<< std::endl;
#endif
continue; // too few password symbols to start checking parity
}
if ( maximum and password.size() > maximum + PARITY - era ) {
#if defined( DEBUG ) && DEBUG >= 1
output( std::cout )
<< " Rejected too long password \""
<< password << std::string( era, '_' )
<< "\"" << " (" << era << " parity skipped)"
<< std::endl;
#endif
continue; // too few parity symbols erased to start checking parity
}
std::string fixed = password;
size_t len = password.size() - ( PARITY - era );
fixed.resize( len );
encode( fixed );
auto differs = std::mismatch( fixed.begin(), fixed.end(), password.begin() );
size_t par_equ = differs.second - password.begin();
if ( par_equ < len || par_equ > len + PARITY )
throw std::runtime_error( "miscomputed R-S parity matching length" );
par_equ -= len;
// At least one parity symbol is requires to give any confidence
if ( par_equ > 0 ) {
std::string basic( fixed.begin(), fixed.begin() + len );
confidence = par_equ * 100 / PARITY; // each worth a normal parity symbol
best.add( basic, confidence );
#if defined( DEBUG ) && DEBUG >= 1
output( std::cout )
<< " Check Chars. w/ " << era << " of " << PARITY
<< " parity missing " << std::setw( 3 ) << confidence
<< "% confidence: \"" << password
<< "\" ==> \"" << basic
<< " (from computed: \"" << fixed << "\")"
<< ": "
<< std::endl
<< best;
#endif
}
}
// Select the best guess and return its confidence. Otherwise, use raw password? If no
// error/erasure attempts succeeded (if no 'best' w/ confidence >= 0), then we'll use
// the raw password w/ 0 confidence, if it meets the minimum/maximum length
// requirements.
confidence = -1;
if ( password.size() >= ( minimum ? minimum : 1 )
and ( maximum == 0 or password.size() <= maximum ))
confidence = 0;
typename best_avg::const_iterator
bi = best.best();
#if defined( DEBUG )
output( std::cout )
<< " Selected " << ( bi != best.end() ? "corrected" : "unmodified" )
<< " password \"" << ( bi != best.end() ? bi->first : password )
<< "\" of length " << ( minimum ? minimum : 1) << "-" << maximum
<< " (vs. \"" << password
<< "\") w/ confidence " << (bi != best.end() ? bi->second.second : confidence )
<< "%, from: "
<< std::endl
<< best;
#endif
if ( bi != best.end() ) {
auto better = best.evaluation( *bi ); // --> (<average>,<password>)
password = better.second;
confidence = better.first;
}
return confidence;
}
static
int decode(
std::string &password,
size_t minimum = PARITY,
size_t maximum = 0 )
{
return decode( password, std::vector<int>(), minimum, maximum );
}
//
// decode(<char*>,<size_t>,<size_t>,<size_t>) -- C interface to decode(<string>)
//
// Traditional C interface. The provided NUL-terminated password+parity is decoded
// (parity removed), and the confidence % is returned.
//
// If any failure occurs, a -'ve value will be returned, and the supplied password
// buffer will be used to contain an error description.
//
static int decode(
char *password, // NUL terminated
size_t siz, // available size
size_t minimum = PARITY,//minimum resultant password length
size_t maximum = 0 ) // maximum ''
{
std::string corrected( password );
int confidence;
try {
confidence = decode( corrected, minimum, maximum );
if ( corrected.size() + 1 > siz )
throw std::runtime_error( "password buffer has insufficient capacity" );
std::copy( corrected.begin(), corrected.end(), password );
password[corrected.size()] = 0;
} catch ( std::exception &exc ) {
confidence = -1;
ezpwd::streambuf_to_buffer sbf( password, siz );
std::ostream( &sbf ) << "corrector<" << PARITY << "> failed: " << exc.what();
}
return confidence;
}
//
// rscodec -- A ?-bit RS(N-1,N-1-PARITY) Reed-Solomon codec
//
// Encodes and decodes R-S symbols over the lower 6 bits of the supplied data. Requires
// that the last N (parity) symbols of the data are in the range [0,63]. The excess bits on
// the data symbols are masked and restored during decoding.
//
static const ezpwd::RS<N-1,N-1-PARITY>
rscodec;
};
template < size_t PARITY, size_t N, typename SERIAL >
const ezpwd::RS<N-1,N-1-PARITY>
corrector<PARITY,N,SERIAL>::rscodec;
} // namespace ezpwd
template < size_t PARITY, size_t N, typename SERIAL >
std::ostream &operator<<(
std::ostream &lhs,
const ezpwd::corrector<PARITY,N,SERIAL>
&rhs )
{
return rhs.output( lhs );
}
#endif // _EZPWD_CORRECTOR

View File

@ -0,0 +1,9 @@
//
// C++ Definitions -- include once in a single C++ compilation unit
//
#ifndef _EZPWD_DEFINITIONS
#define _EZPWD_DEFINITIONS
#include "serialize_definitions"
#endif // _EZPWD_DEFINITIONS

View File

@ -0,0 +1,725 @@
/*
* Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library
*
* Copyright (c) 2014, Hard Consulting Corporation.
*
* Ezpwd Reed-Solomon 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. See the LICENSE file at the top of the
* source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base
* is redistributed under the terms of the LGPL, regardless of the overall licensing terms.
*
* Ezpwd Reed-Solomon 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.
*/
#ifndef _EZPWD_EZCOD
#define _EZPWD_EZCOD
#include <math.h> // M_PI
#include <cmath>
#include <cctype>
#include <cstdint>
#include <ezpwd/rs>
#include <ezpwd/output>
#include <ezpwd/serialize>
//
// EZCOD 3:10 location code w/ Reed-Solomon Error Correction, and average 3m accuracy
//
// - each successive symbol provides greater precision
// - codes nearby each-other are identical in leading characters
// - average 3m precision achieved in 9 symbols
// - more than 4 base-10 digits of precision in both lat and lon after the decimal
// - from 1 to 3 symbols of Reed-Solomon parity
// - 1 parity symbol supplies validation w/ strength equivalent to a check character
// - 2 parity symbols provides correction of 1 lost symbol (no errors)
// - 3 parity symbols provides correction of any 1 error, with verification,
// or recovery of up to any 3 lost symbols (with no other errors)
//
//
// To achieve at least 4 decimal digits of precision after the decimal point, we must have
// defined lat to within 1 part in 1,800,000, and lon to within 1 part in 3,600,000. As each symbol
// supplies bits, we'll refine the computed lat/lon further, reducing the outstanding fraction of
// "parts" yet to be defined.
//
// bits
// symbols latitude longitude
// bits mul parts bits mul parts
// 1 2 4 4 3 8 8
// 2 2 4 16 3 8 64
// 3 3 8 128 2 4 256 // not quite integer lat/lon accuracy
//
// 4 2 4 512 3 8 2,048
// 5 3 8 4,096 2 4 8,192
// 6 2 4 16,384 3 8 65,536
//
// 7 3 8 131,072 2 4 262,144
// 8 2 4 524,288 3 8 2,097,152
// 9 3 8 4,194,304 2 4 8,388,608 parts resolution in 3:10 code
// over [-90, 90] over [-180,180] yields ~3m resolution
//
// vs. 1,800,000 3,600,000 parts resolution in 10:10 code
// over [-90, 90] over [-180,180] yields ~10m resolution
//
// Therefore, within 9 symbols we define lat and lon with better than double the precision of
// 10:10 code's 4 decimal digits after the decimal point. This yields an approximate lineal
// precision of 40,075,000m / 8,388,608 == ~5m in both dimensions at the equator, vs. 40,075,000m /
// 3,600,000 == ~11m for 10:10 codes.
//
// The 10:10 code provides a single check character, which provides about P(1-1/32) certainty
// that the provided code is correct. With EZCOD 3:10/11/12 codes, we provide varying levels of
// detection/correction strength.
//
// - 1 parity symbol: act as a check character (like 10:10 codes), or provide 1 symbol of erasure
// (lost symbol) recovery with no excess parity for validation.
//
// - 2 parity symbols: provide 1 symbol of erasure correction (w/ no other errors) with 1 excess parity
// symbol for validation, or 1 symbol of error detection with no excess parity for validation.
//
// - 3 parity symbols: correct 1 error anywhere w/ 1 excess parity symbol for validation, or up
// to 3 erasures with no excess parity for validation.
//
// Therefore, we'll provide Reed-Solomon RS(31,28-30) error correction (5 bit symbols,
// indicating 31 symbols in the field, and from 1 to 3 roots, therefore up to 28 data symbols in the
// field) over the 9 lat/lon data symbols.
//
//
// MINIMIZING ERROR
//
// Each input lat/lon coordinate will be effectively truncated by the encoding procedure to the
// level of precision (parts) encoded by each symbol. Subsequent symbols then add their (smaller)
// parts to increase precision.
//
// After the last symbol, we know that the actual input coordinates where somewhere
// within the rectangle:
//
// [0,0] -> [0,lon_precision] -> [lat_precision,lon_precision] -> [lat_precision,0]
//
// At first glance, the best way is to perform rounding instead of truncation on ecoding, by
// simply adding 1/2 of the precision. Then, the unmodified output lat/lon decoded represents the
// point nearest actual input coordinate. However, this is NOT ideal. Remember -- the decoding may
// not have access to all the symbols! We want to minimize the error even if only some of the
// symbols are available. Thus, we must apply a correction on decoding.
//
// One way gain rounding instead of truncation on decoding is, after adding the last symbol's
// precision, to add 50% of the value represented by the first bit of the next (missing) symbol's
// precision parts. This would be analogous to receiving the first 2 digits of a 3 digit number:
//
// original: 123
// received: 12_
// range: [120,130)
// guessed: 125 (add 1/2 of the parts represented by the missing digit)
//
// If this is done, then the resulting coordinate would be in the middle of the rectangle of
// possible input lat/lon values that could have resulted in the encoded value. This also works if
// we don't receive and decode all of the symbols; We'll end up with a lat/lon in the middle of the
// (large) rectangle of possible input coordinates.
//
namespace ezpwd {
class ezcod_base {
public:
double latitude; // [-90,+90] angle, degrees
double latitude_error; // total error bar, in meters
double longitude; // [-180,180]
double longitude_error; // total error bar, in meters
double accuracy; // linear accuracy radius, in meters
int confidence; // % parity in excess of last decode
double certainty; // and the associated probability
explicit ezcod_base(
double _lat = 0,
double _lon = 0 )
: latitude( _lat )
, latitude_error( 0 )
, longitude( _lon )
, longitude_error( 0 )
, accuracy( 0 )
, confidence( 100 )
, certainty( 1 )
{
;
}
virtual ~ezcod_base()
{
;
}
typedef std::pair<unsigned char, unsigned char>
symbols_t;
virtual symbols_t symbols()
const
= 0;
virtual std::ostream &output(
std::ostream &lhs )
const
= 0;
virtual std::string encode(
unsigned _preci = 0 ) // override precision
const
= 0;
virtual int decode(
const std::string &_ezcod )
= 0;
};
} // namespace ezpwd
inline std::ostream &operator<<(
std::ostream &lhs,
const ezpwd::ezcod_base
&rhs )
{
return rhs.output( lhs );
}
namespace ezpwd {
//
// ezcod<P,L> -- defaults to 1 PARITY and 9 location symbols (3m) of PRECISION
//
template < unsigned P=1, unsigned L=9 >
class ezcod
: public ezcod_base {
private:
typedef std::array<symbols_t, 12>
bits_t;
static const bits_t bits;
typedef std::array<std::pair<uint32_t, uint32_t>, 12>
parts_t;
static const parts_t parts;
#if defined( DEBUG )
public:
#endif
static const ezpwd::RS<31,31-P>
rscodec;
public:
static constexpr const unsigned PARITY = P; // specified symbols of R-S parity
static constexpr const unsigned PRECISION = L; // default symbols of location precision
static constexpr const unsigned CHUNK = 3; // default chunk size
static constexpr const char SEP_NONE = -1;
static constexpr const char SEP_DEFAULT = 0;
static constexpr const char SEP_DOT = '.';
static constexpr const char SEP_BANG = '!';
static constexpr const char SEP_SPACE = ' ';
static constexpr const char CHK_NONE = -1;
static constexpr const char CHK_DEFAULT = 0;
static constexpr const char CHK_DASH = '-';
static constexpr const char CHK_SPACE = ' ';
unsigned precision;
unsigned chunk; // Location symbol chunk sizes
char separator; // Separator between location and parity symbols
char space; // Fill space between location symbol chunks
//
// ezcod<P,L>() -- supply non-defaults for location precision, chunk size, etc.
//
explicit ezcod(
double _lat = 0,
double _lon = 0,
unsigned _preci = 0,
unsigned _chunk = 0,
char _seper = 0,
char _space = 0 )
: ezcod_base( _lat, _lon )
, precision( _preci ? _preci : PRECISION )
, chunk( _chunk ? _chunk : CHUNK )
, separator( _seper )
, space( _space )
{
if ( P < 1 )
throw std::runtime_error( "ezpwd::ezcod:: At least one parity symbol must be specified" );
if ( precision < 1 || precision > bits.max_size() )
throw std::runtime_error( std::string( "ezpwd::ezcod:: Only 1-" )
+ std::to_string( bits.max_size() )
+ " location symbol may be specified" );
}
explicit ezcod(
const std::string &_ezcod,
unsigned _preci = 0,
unsigned _chunk = 0,
char _seper = 0,
char _space = 0 )
: ezcod( 0, 0, _preci, _chunk, _seper, _space )
{
decode( _ezcod );
}
virtual ~ezcod()
{
;
}
//
// symbols -- return working parity and location precision
//
virtual ezcod_base::symbols_t
symbols()
const
{
return ezcod_base::symbols_t( P, precision );
}
virtual std::ostream &output(
std::ostream &lhs )
const
{
std::streamsize prec = lhs.precision();
std::ios_base::fmtflags
flg = lhs.flags();
lhs.precision( 10 );
std::string uni = "m ";
double acc = accuracy;
double dec = 2;
if ( acc > 1000 ) {
uni = "km";
acc /= 1000;
} else if ( acc < 1 ) {
uni = "mm";
acc *= 1000;
}
if ( acc >= 100 )
dec = 0;
else if ( acc >= 10 )
dec = 1;
lhs << encode( precision )
<< " (" << std::setw( 3 ) << confidence
<< "%) == " << std::showpos << std::fixed << std::setprecision( 10 ) << std::setw( 15 ) << latitude
<< ", " << std::showpos << std::fixed << std::setprecision( 10 ) << std::setw( 15 ) << longitude
<< " +/- " << std::noshowpos << std::fixed << std::setprecision( dec ) << std::setw( 6 ) << acc << uni;
lhs.precision( prec );
lhs.flags( flg );
return lhs;
}
//
// encode() -- encode the lat/lon to 'precision' symbols EZCOD representation
//
virtual std::string encode(
unsigned _preci = 0 ) // override precision
const
{
// Convert lat/lon into a fraction of number of parts assigned to each
double lat_frac= ( latitude + 90 ) / 180;
if ( lat_frac < 0 || lat_frac > 1 )
throw std::runtime_error( "ezpwd::ezcod::encode: Latitude not in range [-90,90]" );
double lon_frac= ( longitude + 180 ) / 360;
if ( lon_frac < 0 || lon_frac > 1 )
throw std::runtime_error( "ezpwd::ezcod::encode: Longitude not in range [-180,180]" );
if ( _preci == 0 )
_preci = precision;
if ( _preci < 1 || _preci > bits.max_size() )
throw std::runtime_error( std::string( "ezpwd::ezcod:: Only 1-" )
+ std::to_string( bits.max_size() )
+ " location symbol may be specified" );
// Compute the integer number of lat/lon parts represented by each coordinate, for the
// specified level of precision, and then truncate to the range [0,..._parts),
// eg. Latitude 90 --> 89.999...
uint32_t lat_parts = parts[_preci-1].first; // [ -90,90 ] / 4,194,304 parts in 9 symbols
uint32_t lon_parts = parts[_preci-1].second; // [-180,180] / 8,388,608 parts ''
uint32_t lat_rem = std::min( lat_parts-1, uint32_t( lat_parts * lat_frac ));
uint32_t lon_rem = std::min( lon_parts-1, uint32_t( lon_parts * lon_frac ));
// Initial loop condition; lat/lon multiplier is left at the base multiplier of the
// previous loop. Then, loop computing the units multiplier, and hten removing the most
// significant bits (multiples of the units multiplier). They will both reach 1
unsigned int lat_mult= lat_parts;
unsigned int lon_mult= lon_parts;
std::string res;
res.reserve( _preci // approximate result length
+ ( chunk && chunk < _preci
? _preci / chunk - 1
: 0 )
+ 1 + P );
for ( auto &b : bits ) {
unsigned char lat_bits= b.first;
unsigned char lon_bits= b.second;
lat_mult >>= lat_bits;
lon_mult >>= lon_bits;
if ( ! lat_mult || ! lon_mult )
break;
// Each set of bits represents the number of times the current multiplier (after
// division by the number of bits we're outputting) would go into the remainder.
// Eg. If _mult was 1024, and _rem is 123 and _bits is 3, we're going to put out
// the next 3 bits of the value 199. The last value ended removing all multiples of
// 1024. So, we first get the new multiplier: 1024 >> 3 == 128. So, we're
// indicating, as a 3-bit value, how many multiples of 128 there are in the value
// 199: 199 / 128 == 1, so the 3-bit value we output is 001
uint32_t lat_val = lat_rem / lat_mult;
lat_rem -= lat_val * lat_mult;
uint32_t lon_val = lon_rem / lon_mult;
lon_rem -= lon_val * lon_mult;
res += char( ( lat_val << lon_bits ) | lon_val );
}
// Add the R-S parity symbols and base-32 encode, add parity separator and chunk
rscodec.encode( res );
serialize::base32::encode( res );
switch( separator ) {
case SEP_NONE:
break;
case SEP_DOT: default:
res.insert( _preci, 1, SEP_DOT );
break;
case SEP_BANG:
case SEP_SPACE:
res.insert( _preci, 1, separator );
break;
}
if ( space != CHK_NONE && chunk && chunk < _preci ) {
for ( unsigned c = _preci / chunk - 1; c > 0; --c ) {
switch ( space ) {
case CHK_NONE:
break;
case CHK_SPACE: default:
res.insert( c * chunk, 1, CHK_SPACE );
break;
case CHK_DASH:
res.insert( c * chunk, 1, space );
break;
}
}
}
return res;
}
//
// deserialize -- Extract base-32, skip whitespace, mark invalid symbols as erasures
// validate -- Remove base-32 encoding, validate and remove parity, returning confidence
// decode -- Attempt to decode a lat/lon, returning the confidence percentage
//
// If data but no parity symbols are supplied, no error checking is performed, and the
// confidence returned will be 0%. No erasures within the supplied data are allowed (as
// there is no capacity to correct them), and an exception will be thrown.
//
// If parity is supplied, then erasures are allowed. So long as the total number of
// erasures is <= the supplied parity symbols, then the decode will proceed (using the
// parity symbols to fill in the erasures), and the returned confidence will reflect the
// amount of unused parity capacity. Each erasure consumes one parity symbol to repair.
//
// We'll allow question-mark or any of the slash characters: "_/\?" to indicate an
// erasure. Either of the "!." symbol may be used to indicates the split between location
// symbols and parity symbols, and must be in a position that corresponds to the indicated
// number of location (this->precision) and parity 'P' symbols. Whitespace symbols and dash
// are ignored: " -".
//
// Thus, an EZCOD like "R3U 1JU QUY!0" may only be decoded by an ezcod<P=1>. Without
// the "!" or ".", it could be an ezcod<P=2> w/precision == 8 -- there's no way to know for
// sure. If no explicit position-parity separator is given, then we assume the default:
// this->precision location symbols, then up to P parity symbols. If additional parity
// symbols are supplied after the separator, then However, an ezcod...<P=3>
//
// If an explicit "!" or "." separator IS provided, then we will attempt to decode the
// position with the given number of position symbols, and up to P parity symbols.
//
// NOTE
//
// Due to a perhaps unexpected feature of R-S codewords, a codeword with MORE parity
// can be successfully decoded by an R-S codec specifying LESS parity symbols. It so
// happens that the data plus (excess) parity + (remaining) parity is STILL a valid codeword
// (so long as the R-S Galois parameters are identical).
//
// Therefore, EZCODs with more parity are accepted by EZCOD parsers configured for less
// parity. Of course, they will have less error/erasure correction strength -- using the
// correctly configured EZCOD codec expecting more R-S parity will maximize the value of all
// the supplied parity.
//
// The full amount of parity (ie. everything after the location/parity separator) is
// discarded in all cases, before the EZCOD location is decoded.
//
private:
unsigned deserialize(
std::string &dec,
std::vector<int> &erasure,
std::vector<char> &invalid )
const
{
serialize::base32::decode( dec, &erasure, &invalid );
// The special symbol '!' or '.' indicates the end of the EZCOD location symbols and the
// beginning of parity: ensure the symbol counts are consistent with the encoding. By
// default the parity symbols begin at offset precision. If we see more than precision
// symbols, we assume that the Lth and subsequent symbols are parity. If a
// location/parity separator is provided, it must be at position this->precision!
// Return offset of start of parity in codeword.
unsigned parbeg = this->PRECISION; // Parity begins after Location, by default
for ( unsigned i = 0; i < invalid.size(); ++i ) {
switch ( invalid[i] ) {
case '!': case '.':
// Remember the offset of the first parity symbol (it'll be in the position of
// the last '!' or '.' symbol we're about to erase), and adjust the indices of
// any erasures following.
parbeg = erasure[i];
dec.erase( parbeg, 1 );
invalid.erase( invalid.begin() + i );
erasure.erase( erasure.begin() + i );
for ( unsigned j = i; j < erasure.size(); ++j )
erasure[j] -= 1;
break;
case '_': case '/': case '\\': case '?':
break;
default:
throw std::runtime_error( std::string( "ezpwd::ezcod::decode: invalid symbol presented: '" )
+ invalid[i] + "'" );
}
}
#if defined( DEBUG ) && DEBUG >= 1
std::cout << " --> 0x" << std::vector<uint8_t>( dec.begin(), dec.begin() + std::min( size_t( parbeg ), dec.length()) )
<< " + 0x" << std::vector<uint8_t>( dec.begin() + std::min( size_t( parbeg ), dec.length()),
dec.begin() + dec.length() )
<< " parity" << std::endl;
#endif
return parbeg;
}
int validate(
std::string &dec )
const
{
// Compute and return validity (which may later be assigned to this->confidence)
int validity = 0; // if no R-S parity provided
#if defined( DEBUG ) && DEBUG >= 1
std::cout << *this << " validate( " << dec << " ) ";
#endif
std::vector<int> erasure;
std::vector<char> invalid;
unsigned parbeg = deserialize( dec, erasure, invalid );
if ( dec.size() > parbeg || erasure.size() > 0 ) {
// Some R-S parity symbol(s) were provided (or erasures were marked). See if we can
// successfully decode/correct, or (at least) use one parity symbol as a check
// character. If we identify more erasures than R-S parity, we must fail; we can't
// recover the data. This will of course be the case if we have *any* erasures in
// the data, and no parity.
unsigned parity = 0;
if ( dec.size() > parbeg )
parity = dec.size() - parbeg;
while ( dec.size() < parbeg + P ) {
erasure.push_back( dec.size() );
dec.resize( dec.size() + 1 );
}
#if defined( DEBUG ) && DEBUG >= 2
std::cout << " --> erasures: " << erasure.size() << " vs. parity: " << parity
<< ": " << std::vector<uint8_t>( dec.begin(), dec.end() ) << std::endl;
#endif
if ( erasure.size() > parity ) {
// We cannot do R-S decoding; not enough parity symbols to even cover erasures.
// If parity symbol(s) were provided ('parity' > 0), and all erasures were due the
// missing remaining parity symbols, we can use the existing parity symbol(s) as
// "check character(s)", by simply re-encoding the supplied non-parity data, and
// see if the generated parity symbol(s) match the supplied parity. This has
// basically the same strength as the 10:10 code's check character.
if ( parity + erasure.size() == P ) {
// All erasures must be at end, in remaining parity symbols!
std::string chk( dec.begin(), dec.begin() + parbeg );
rscodec.encode( chk );
// each parity symbol provided must match the corresponding encoded chk symbol
for ( unsigned i = 0; i < parity; ++i )
if ( dec[parbeg+i] != chk[parbeg+i] )
throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; check character mismatch" );
// Check character(s) matched; erasure.size()/P of confidence gone
validity = ezpwd::strength<P>( erasure.size(), erasure, erasure );
} else
throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; too many erasures" );
} else {
// We can try R-S decoding; we have (at least) enough parity to try to recover
// any missing symbol(s).
std::vector<int>position;
int corrects= rscodec.decode( dec, erasure, &position );
if ( corrects < 0 )
throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; R-S decode failed" );
// Compute confidence, from spare parity capacity. Since R-S decode will not
// return the position of erasures that turn out (by accident) to be correct,
// but they have consumed parity capacity, we re-add them into the correction
// position vector. If the R-S correction reports more corrections than the
// parity can possibly have handled correctly, (eg. 2 reported erasures and an
// unexpected error), then the decode is almost certainly incorrect; fail.
validity = ezpwd::strength<P>( corrects, erasure, position );
if ( validity < 0 )
throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; R-S decode overwhelmed" );
}
if ( dec.size() > parbeg )
dec.resize( parbeg ); // Discard any parity symbols
}
return validity;
}
public:
virtual int decode( const std::string &str )
{
// Decode the R-S encoding, computing the confidence. Will raise an exception on any
// error. Don't change this->confidence, this->latitude, ... until there is no longer a
// chance of exception.
std::string decoded( str );
int validity= validate( decoded );
// Unpack the supplied location data; we'll take as much as we are given, up to the
// maximum possible 12 symbols supported (9 symbols yielding ~3m resolution).
uint32_t lat_tot = 0;
uint32_t lon_tot = 0;
uint32_t lat_mult= 1;
uint32_t lon_mult= 1;
auto di = decoded.begin();
for ( auto &b : bits ) {
if ( di == decoded.end() )
break;
unsigned char c = *di++;
unsigned char lat_bits= b.first;
unsigned char lon_bits= b.second;
uint32_t lat_val = c >> lon_bits;
uint32_t lon_val = c & (( 1 << lon_bits ) - 1 );
lat_mult <<= lat_bits;
lat_tot <<= lat_bits;
lat_tot += lat_val;
lon_mult <<= lon_bits;
lon_tot <<= lon_bits;
lon_tot += lon_val;
}
// Convert the sum of lat/lon parts back into degrees, and round the (truncated) value
// to the middle of the error rectangle. This allows us to minimize error even if we
// didn't have access to all of the origin symbols to decode. The absolute error bar as
// a proportional factor [0,1) for lat/lon is at most the scale of the last parts
// multiplier used. We'll use this later to compute the error in meters; for example,
// if the last value we added worked out to be worth units of 25m of the circumference,
// then we must now be within [0,25m) of the original point.
double lat_err = 1.0 / lat_mult;
double lon_err = 1.0 / lon_mult;
latitude = 180 * ( double( lat_tot ) / lat_mult + lat_err / 2 ) - 90;
longitude = 360 * ( double( lon_tot ) / lon_mult + lon_err / 2 ) - 180;
// Remember the decoded location precision for future encoding (overrides the default).
// Compute the certainty probability (0.0,1.0] given the number of parity symbols in
// excess: Given a base-32 symbol: 1 - 1 / ( 32 ^ P ) where P is the number of
// unconsumed parity.
precision = decoded.size();
confidence = validity;
certainty = 0.0;
if ( PARITY * confidence / 100 )
certainty = 1.0 - 1.0 / std::pow( double( 32.0 ),
double( PARITY * confidence / 100 ));
// Compute the resolution error (in m.) of the decoded lat/lon and compute the minimum
// accuracy -- the radius of the circle around the computed latitude/longitude, inside
// which the original latitude/longitude must have been.
//
// original latitude error bar
// \ /
// o -
// | longitude error bar
// | /
// |--x--|
// /|
// / |
// computed -
//
// The maximum distance is the length of the diagonal of the error rectangle defined by
// 1/2 the latitude/longitude error bars.
//
double lon_circ= 1 * M_PI * 6371000;
double lat_circ= 2 * M_PI * 6371000 * std::cos( latitude * M_PI / 180 );
latitude_error = lat_err * lon_circ;
longitude_error = lon_err * lat_circ;
accuracy = sqrt( latitude_error / 2 * latitude_error / 2
+ longitude_error / 2 * longitude_error / 2 );
return confidence;
}
}; // class ezcod
//
// ezcod::rscodec -- Reed-Solomon parity codec
// ezcod::bits -- distribution of lat/lon precision in each code symbol
//
// Quickly establishes an extra bit of precision for Longitude, and then evenly distributes
// future precision between lat/lon, always maintaining extra precision for Longitude.
//
template < unsigned P, unsigned L >
const ezpwd::RS<31,31-P> ezcod<P,L>::rscodec;
// Number of lat/lon bits represented for each location symbol
template < unsigned P, unsigned L >
const typename ezcod<P,L>::bits_t
ezcod<P,L>::bits = {
{
// bits per symbol lat lon
ezcod<P,L>::bits_t::value_type( 2, 3 ),
ezcod<P,L>::bits_t::value_type( 2, 3 ),
ezcod<P,L>::bits_t::value_type( 3, 2 ),
// -- --
// 7 8
ezcod<P,L>::bits_t::value_type( 2, 3 ),
ezcod<P,L>::bits_t::value_type( 3, 2 ),
ezcod<P,L>::bits_t::value_type( 2, 3 ),
// -- --
// 14 16
ezcod<P,L>::bits_t::value_type( 3, 2 ),
ezcod<P,L>::bits_t::value_type( 2, 3 ),
ezcod<P,L>::bits_t::value_type( 3, 2 ),
// -- --
// 22 23
ezcod<P,L>::bits_t::value_type( 2, 3 ),
ezcod<P,L>::bits_t::value_type( 3, 2 ),
ezcod<P,L>::bits_t::value_type( 2, 3 ),
// -- --
// 29 31
}
};
// Total number of parts that lat/lon is subdivided into, for that number of location symbols.
template < unsigned P, unsigned L >
const typename ezcod<P,L>::parts_t
ezcod<P,L>::parts = {
{
// parts per symbol lat parts lon parts lat lon bits
ezcod<P,L>::parts_t::value_type( 1UL << 2, 1UL << 3 ), // 2, 3
ezcod<P,L>::parts_t::value_type( 1UL << 4, 1UL << 6 ), // 2, 3
ezcod<P,L>::parts_t::value_type( 1UL << 7, 1UL << 8 ), // 3, 2
// -- --
// 7 8
ezcod<P,L>::parts_t::value_type( 1UL << 9, 1UL << 11 ), // 2, 3
ezcod<P,L>::parts_t::value_type( 1UL << 12, 1UL << 13 ), // 3, 2
ezcod<P,L>::parts_t::value_type( 1UL << 14, 1UL << 16 ), // 2, 3
// -- --
// 14 16
ezcod<P,L>::parts_t::value_type( 1UL << 17, 1UL << 18 ), // 3, 2
ezcod<P,L>::parts_t::value_type( 1UL << 19, 1UL << 21 ), // 2, 3
ezcod<P,L>::parts_t::value_type( 1UL << 22, 1UL << 23 ), // 3, 2
// -- --
// 22 23
ezcod<P,L>::parts_t::value_type( 1UL << 24, 1UL << 26 ), // 2, 3
ezcod<P,L>::parts_t::value_type( 1UL << 27, 1UL << 28 ), // 3, 2
ezcod<P,L>::parts_t::value_type( 1UL << 29, 1UL << 31 ), // 2, 3
// -- --
// 29 31
}
};
} // namespace ezpwd
#endif // _EZPWD_EZCOD

View File

@ -0,0 +1,344 @@
#ifndef _EZPWD_OUTPUT
#define _EZPWD_OUTPUT
#include <cstdio>
#include <streambuf>
#include <iostream>
#include <cctype>
#include <iomanip>
#include <sstream>
#include <set>
#include <map>
#include <vector>
//
// ezpwd::hexchr -- escape/hexify char c, output using func/meth f, in width w >= 2
// ezpwd::hexify -- hexify something that can be converted to an unsigned char
// ezpwd::hexout -- hexify each element in the range (beg,end], limited by stream's width
//
// std::ostream << ezpwd::hexify( c ) // output any char escaped/hex
// std::ostream << ezpwd::hexout( beg, end ) // output any char iterator to ostream
// std::ostream << std::vector<unsigend char>
// std::ostream << std::array<unsigend char, N>
// ezpwd::hexchr( c, [](unsigned char c){...;} )// output escaped/hex char via functor
// ezpwd::hexout( beg, end, FILE* ) // output any char iterator to FILE*
//
// Output unprintable unsigned char data in hex, escape printable/whitespace data.
//
namespace ezpwd {
struct hexify {
unsigned char c;
std::streamsize w;
explicit hexify(
unsigned char _c,
std::streamsize _w = 2 )
: c( _c )
, w( _w )
{ ; }
explicit hexify(
char _c,
std::streamsize _w = 2 )
: c( (unsigned char)_c )
, w( _w )
{ ; }
};
struct hexstr {
const std::string &s;
explicit hexstr(
const std::string &_s )
: s( _s )
{ ; }
};
template <typename F> // a functor taking a char
void
hexchr( unsigned char c, F f = []( unsigned char c ) { std::cout.put( c );}, size_t w = 2 )
{
for ( ; w > 2; --w )
f( ' ' );
if ( std::isprint( c ) || std::isspace( c )
|| c == '\0' || c == '\a' || c == '\b' || c == 0x1B ) { // '\e' is not standard
switch ( c ) {
case 0x00: f( '\\' ); f( '0' ); break; // NUL
case 0x07: f( '\\' ); f( 'a' ); break; // BEL
case 0x08: f( '\\' ); f( 'b' ); break; // BS
case 0x09: f( '\\' ); f( 't' ); break; // HT
case 0x0A: f( '\\' ); f( 'n' ); break; // LF
case 0x0B: f( '\\' ); f( 'v' ); break; // VT
case 0x0C: f( '\\' ); f( 'f' ); break; // FF
case 0x0D: f( '\\' ); f( 'r' ); break; // CR
case 0x1B: f( '\\' ); f( 'e' ); break; // ESC
case '\"': f( '\\' ); f( '"' ); break; // "
case '\'': f( '\\' ); f( '\''); break; // '
case '\\': f( '\\' ); f( '\\'); break; // '\'
default: f( ' ' ); f( c ); // space, any other printable character
}
} else {
f( "0123456789ABCDEF"[( c >> 4 ) & 0x0f ] );
f( "0123456789ABCDEF"[( c >> 0 ) & 0x0f ] );
}
}
inline
std::ostream &operator<<(
std::ostream &lhs,
const ezpwd::hexify&rhs )
{
ezpwd::hexchr( rhs.c, [ &lhs ]( unsigned char c ) { lhs.put( c ); }, rhs.w );
return lhs;
}
template < typename iter_t >
inline
std::ostream &hexout(
std::ostream &lhs,
const iter_t &beg,
const iter_t &end )
{
std::streamsize wid = lhs.width( 0 );
int col = 0;
for ( auto i = beg; i != end; ++i ) {
if ( wid && col == wid ) {
lhs << std::endl;
col = 0;
}
lhs << hexify( *i );
++col;
}
return lhs;
}
template < typename iter_t >
inline
std::FILE *hexout(
const iter_t &beg,
const iter_t &end,
std::FILE *lhs )
{
for ( auto i = beg; i != end; ++i ) {
ezpwd::hexchr( *i, [ lhs ]( unsigned char c ) { std::fputc( c, lhs ); } );
}
return lhs;
}
inline
std::ostream &operator<<(
std::ostream &lhs,
const ezpwd::hexstr&rhs )
{
return ezpwd::hexout( lhs, rhs.s.begin(), rhs.s.end() );
}
} // namespace ezpwd
namespace std {
template < size_t S >
inline
std::ostream &operator<<(
std::ostream &lhs,
const std::array<unsigned char,S>
&rhs )
{
return ezpwd::hexout( lhs, rhs.begin(), rhs.end() );
}
inline
std::ostream &operator<<(
std::ostream &lhs,
const std::vector<unsigned char>
&rhs )
{
return ezpwd::hexout( lhs, rhs.begin(), rhs.end() );
}
//
// <ostream&> << pair<T,U>
// <ostream&> << set<T> -- sorted by T
// <ostream&> << map<T,U> -- sorted by T (key)
// <ostream&> << vector<T>
//
// Handle output of various container types.
//
// Output pairs and sets of pairs, respecting specified widths (as appropriate). For example
// a set of pairs of integeters 's', if output as "... << std::setw( 13 ) << s;", would yield:
//
// ( 1, 2) ( 3, 4) ...
//
template <class T, class U>
std::ostream &operator<<(
std::ostream &lhs,
const std::pair<T,U> &rhs )
{
std::streamsize w = std::max( std::streamsize( 0 ),
std::streamsize( lhs.width() - 3 ));
lhs << std::setw( 0 )
<< '(' << std::setw( w / 2 ) << rhs.first
<< ',' << std::setw( w - w / 2 ) << rhs.second
<< ')';
return lhs;
}
template <class T>
std::ostream &operator<<(
std::ostream &lhs,
const std::set<T> &rhs )
{
std::streamsize w = lhs.width(); // If width is set, use if for each item
for ( typename std::set<T>::const_iterator
si = rhs.begin()
; si != rhs.end()
; ++si ) {
if ( si != rhs.begin())
lhs << ' ';
lhs << std::setw( w ) << *si;
}
lhs << std::setw( 0 ); // If container empty, must clear
return lhs;
}
template <class T, class U>
std::ostream &operator<<(
std::ostream &lhs,
const std::map<T,U>&rhs )
{
std::streamsize w = lhs.width(); // If width is set, use if for each item
std::vector<T> key;
for ( typename std::map<T,U>::const_iterator
mi = rhs.begin()
; mi != rhs.end()
; ++mi )
key.push_back( mi->first );
std::sort( key.begin(), key.end() );
for ( typename std::vector<T>::const_iterator
ki = key.begin()
; ki != key.end()
; ++ki ) {
if ( ki != key.begin())
lhs << ' ';
lhs << std::setw( w ) << *rhs.find( *ki );
}
lhs << std::setw( 0 ); // If container empty, must clear
return lhs;
}
template <class T>
std::ostream &operator<<(
std::ostream &lhs,
const std::vector<T> &rhs )
{
for( size_t i = 0; i < rhs.size(); ++i ) {
if ( i )
lhs << ", ";
lhs << rhs[i];
}
return lhs;
}
} // namespace std
//
// ezpwd::buf_t -- describe a C string buffer, to allow C++ output operations
// ezpwd::streambuf_to_buf_t -- output charcters, always NUL terminated
//
// <buf_t> << ... -- Copy the <string> into the C <char*,size_t> buffer, always NUL terminating
//
// Copies <string> contents into buffer, and always NUL-terminates. Returns advanced buf_t (NOT
// including the terminating NUL, suitable for repeating ... << <string> operations.
//
// std::ostream( &<streambuf_to_buf_t> ) << ...
//
// Use standard ostream operations to send output to a C buffer, always NUL
// terminating, and never exceeding capacity.
//
namespace ezpwd {
typedef std::pair<char *,size_t>
buf_t;
class streambuf_to_buffer
: public std::streambuf {
private:
char *_buf;
size_t _siz;
public:
//
// streambuf_to_buf_t -- remember buf_t details
// ~streambuf_to_buf_t -- no virtual destructor required; nothing to clean up
//
streambuf_to_buffer(
char *buf,
size_t siz )
: _buf( buf )
, _siz( siz )
{
if ( _siz > 0 )
*_buf = 0;
}
explicit streambuf_to_buffer(
const buf_t &buf )
: streambuf_to_buffer( buf.first, buf.second )
{
;
}
//
// overflow -- Append c, always NUL terminating
//
virtual int overflow(
int c )
{
if ( _siz <= 1 )
return EOF; // No room for c and NUL; EOF
if ( EOF == c )
return 0; // EOF provided; do nothing
--_siz;
*_buf++ = char( c );
*_buf = 0;
return c;
}
}; // class streambuf_to_buffer
} // namespace ezpwd
namespace std {
inline
ezpwd::buf_t operator<<(
const ezpwd::buf_t &buf,
const std::string &str )
{
if ( buf.first && str.size() + 1 <= buf.second ) {
std::copy( str.begin(), str.end(), buf.first );
buf.first[str.size()] = 0;
return ezpwd::buf_t( buf.first + str.size(), buf.second - str.size() );
} else if ( buf.first && buf.second ) {
std::copy( str.begin(), str.begin() + buf.second - 1, buf.first );
buf.first[buf.second-1] = 0;
return ezpwd::buf_t( buf.first + buf.second - 1, 1 );
}
return buf; // NULL pointer or 0 size.
}
//
// <std::string> << ...
//
// Useful (but inefficient) standard output formatting directly to a std::string. Use only for
// testing code, for efficiency reasons...
//
template < typename T >
inline
std::string operator<<(
const std::string &lhs,
const T &rhs )
{
std::ostringstream oss;
oss << rhs;
return std::string( lhs ).append( oss.str() );
}
} // namespace std
#endif // _EZPWD_OUTPUT

View File

@ -0,0 +1,168 @@
/*
* Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library
*
* Copyright (c) 2014, Hard Consulting Corporation.
*
* Ezpwd Reed-Solomon 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. See the LICENSE file at the top of the
* source tree. Ezpwd Reed-Solomon is also available under Commercial license. c++/ezpwd/rs_base
* is redistributed under the terms of the LGPL, regardless of the overall licensing terms.
*
* Ezpwd Reed-Solomon 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.
*/
#ifndef _EZPWD_RS
#define _EZPWD_RS
#include "rs_base"
//
// ezpwd::RS<SYMBOLS,PAYLOAD> -- Implements an RS(SYMBOLS,PAYLOAD) codec
// ezpwd::RS_CCSDS<...> -- CCSDS standard 8-bit R-S codec
//
// Support for Reed-Solomon codecs for symbols of 2 to 16 bits is supported. The R-S "codeword"
// for an N-bit symbol is defined to be 2^N-1 symbols in size. For example, for 5-bit symbols,
// 2^5-1 == 31, so the notation for defining an Reed-Solomon codec for 5-bit symbols is always:
// RS(31,PAYLOAD), where PAYLOAD is always some value less than 31. The difference is the number of
// "parity" symbols.
//
// For example, to define an RS codeword of 31 symbols w/ 4 symbols of parity and up to 27
// symbols of data, you would say: RS(31,27). Of course, you can supply smaller amounts of data;
// the balance is assumed to be NUL (zero) symbols.
//
namespace ezpwd {
//
// __RS( ... ) -- Define a reed-solomon codec
//
// @SYMBOLS: Total number of symbols; must be a power of 2 minus 1, eg 2^8-1 == 255
// @PAYLOAD: The maximum number of non-parity symbols, eg 253 ==> 2 parity symbols
// @POLY: A primitive polynomial appropriate to the SYMBOLS size
// @FCR: The first consecutive root of the Reed-Solomon generator polynomial
// @PRIM: The primitive root of the generator polynomial
//
# define __RS_TYP( TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM ) \
ezpwd::reed_solomon< \
TYPE, \
ezpwd::log_< (SYMBOLS) + 1 >::value, \
(SYMBOLS) - (PAYLOAD), FCR, PRIM, \
ezpwd::gfpoly< \
ezpwd::log_< (SYMBOLS) + 1 >::value, \
POLY >>
# define __RS( NAME, TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM ) \
__RS_TYP( TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM ) { \
NAME() \
: __RS_TYP( TYPE, SYMBOLS, PAYLOAD, POLY, FCR, PRIM )() \
{;} \
}
//
// RS<SYMBOLS, PAYLOAD> -- Standard partial specializations for Reed-Solomon codec type access
//
// Normally, Reed-Solomon codecs are described with terms like RS(255,252). Obtain various
// standard Reed-Solomon codecs using macros of a similar form, eg. RS<255, 252>. Standard PLY,
// FCR and PRM values are provided for various SYMBOL sizes, along with appropriate basic types
// capable of holding all internal Reed-Solomon tabular data.
//
// In order to provide "default initialization" of const RS<...> types, a user-provided
// default constructor must be provided.
//
template < size_t SYMBOLS, size_t PAYLOAD > struct RS;
template < size_t PAYLOAD > struct RS< 3, PAYLOAD> : public __RS( RS, uint8_t, 3, PAYLOAD, 0x7, 1, 1 );
template < size_t PAYLOAD > struct RS< 7, PAYLOAD> : public __RS( RS, uint8_t, 7, PAYLOAD, 0xb, 1, 1 );
template < size_t PAYLOAD > struct RS< 15, PAYLOAD> : public __RS( RS, uint8_t, 15, PAYLOAD, 0x13, 1, 1 );
template < size_t PAYLOAD > struct RS< 31, PAYLOAD> : public __RS( RS, uint8_t, 31, PAYLOAD, 0x25, 1, 1 );
template < size_t PAYLOAD > struct RS< 63, PAYLOAD> : public __RS( RS, uint8_t, 63, PAYLOAD, 0x43, 1, 1 );
template < size_t PAYLOAD > struct RS< 127, PAYLOAD> : public __RS( RS, uint8_t, 127, PAYLOAD, 0x89, 1, 1 );
template < size_t PAYLOAD > struct RS< 255, PAYLOAD> : public __RS( RS, uint8_t, 255, PAYLOAD, 0x11d, 1, 1 );
template < size_t PAYLOAD > struct RS< 511, PAYLOAD> : public __RS( RS, uint16_t, 511, PAYLOAD, 0x211, 1, 1 );
template < size_t PAYLOAD > struct RS< 1023, PAYLOAD> : public __RS( RS, uint16_t, 1023, PAYLOAD, 0x409, 1, 1 );
template < size_t PAYLOAD > struct RS< 2047, PAYLOAD> : public __RS( RS, uint16_t, 2047, PAYLOAD, 0x805, 1, 1 );
template < size_t PAYLOAD > struct RS< 4095, PAYLOAD> : public __RS( RS, uint16_t, 4095, PAYLOAD, 0x1053, 1, 1 );
template < size_t PAYLOAD > struct RS< 8191, PAYLOAD> : public __RS( RS, uint16_t, 8191, PAYLOAD, 0x201b, 1, 1 );
template < size_t PAYLOAD > struct RS<16383, PAYLOAD> : public __RS( RS, uint16_t, 16383, PAYLOAD, 0x4443, 1, 1 );
template < size_t PAYLOAD > struct RS<32767, PAYLOAD> : public __RS( RS, uint16_t, 32767, PAYLOAD, 0x8003, 1, 1 );
template < size_t PAYLOAD > struct RS<65535, PAYLOAD> : public __RS( RS, uint16_t, 65535, PAYLOAD, 0x1100b, 1, 1 );
template < size_t SYMBOLS, size_t PAYLOAD > struct RS_CCSDS;
template < size_t PAYLOAD > struct RS_CCSDS<255, PAYLOAD> : public __RS( RS_CCSDS, uint8_t, 255, PAYLOAD, 0x187, 112, 11 );
//
// strength<PARITY> -- compute strength (given N parity symbols) of R-S correction
//
// Returns a confidence strength rating, which is the ratio:
//
// 100 - ( errors * 2 + erasures ) * 100 / parity
//
// which is proportional to the number of parity symbols unused by the reported number of
// corrected symbols. If 0, then all parity resources were consumed to recover the R-S
// codeword, and we can have no confidence in the result. If -'ve, indicates more parity
// resources were consumed than available, indicating that the result is likely incorrect.
//
// Accounts for the fact that a signalled erasure may not be reported in the corrected
// position vector, if the symbol happens to match the computed value. Note that even if the
// error or erasure occurs within the "parity" portion of the codeword, this doesn't reduce the
// effective strength -- all symbols in the R-S complete codeword are equally effective in
// recovering any other symbol in error/erasure.
//
template < size_t PARITY >
int strength(
int corrected,
const std::vector<int>&erasures, // original erasures positions
const std::vector<int>&positions ) // all reported correction positions
{
// -'ve indicates R-S failure; all parity consumed, but insufficient to correct the R-S
// codeword. Missing an unknown number of additional required parity symbols, so just
// return -1 as the strength.
if ( corrected < 0 ) {
#if defined( DEBUG ) && DEBUG >= 2
std::cout
<< corrected << " corrections (R-S decode failure) == -1 confidence"
<< std::endl;
#endif
return -1;
}
if ( corrected != int( positions.size() ))
EZPWD_RAISE_OR_RETURN( std::runtime_error, "inconsistent R-S decode results", -1 );
// Any erasures that don't turn out to contain errors are not returned as fixed positions.
// However, they have consumed parity resources. Search for each erasure location in
// positions, and if not reflected, add to the corrected/missed counters.
int missed = 0;
for ( auto e : erasures ) {
if ( std::find( positions.begin(), positions.end(), e ) == positions.end() ) {
++corrected;
++missed;
#if defined( DEBUG ) && DEBUG >= 2
std::cout
<< corrected << " corrections (R-S erasure missed): " << e
<< std::endl;
#endif
}
}
int errors = corrected - erasures.size();
int consumed= errors * 2 + erasures.size();
int confidence= 100 - consumed * 100 / PARITY;
#if defined( DEBUG ) && DEBUG >= 2
std::cout
<< corrected << " corrections (R-S decode success)"
<< " at: " << positions
<< ", " << erasures.size() + missed
<< " erasures (" << missed
<< " unreported) at: " << erasures
<< ") ==> " << errors
<< " errors, and " << consumed << " / " << PARITY
<< " parity used == " << confidence
<< "% confidence"
<< std::endl;
#endif
return confidence;
}
} // namespace ezpwd
#endif // _EZPWD_RS

Some files were not shown because too many files have changed in this diff Show More