merge branch max into master
This commit is contained in:
commit
b6b0eeb7b8
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#include <vector>
|
||||
typedef std::vector<bool> bit_vector;
|
||||
int bchDec(bit_vector& Codeword);
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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:
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
|
@ -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 */
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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.
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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 */
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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) {}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
|
@ -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...
|
||||
|
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue