merge branch max into master
This commit is contained in:
commit
b6b0eeb7b8
|
@ -1,6 +1,9 @@
|
||||||
cmake_minimum_required(VERSION 2.6)
|
cmake_minimum_required(VERSION 2.6)
|
||||||
project(gr-op25 CXX C)
|
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)
|
||||||
add_subdirectory(op25/gr-op25_repeater)
|
add_subdirectory(op25/gr-op25_repeater)
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ fi
|
||||||
|
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get build-dep gnuradio
|
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
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
|
|
@ -83,7 +83,6 @@ set(GRC_BLOCKS_DIR ${GR_PKG_DATA_DIR}/grc/blocks)
|
||||||
########################################################################
|
########################################################################
|
||||||
# Find gnuradio build dependencies
|
# Find gnuradio build dependencies
|
||||||
########################################################################
|
########################################################################
|
||||||
find_package(GnuradioRuntime)
|
|
||||||
find_package(CppUnit)
|
find_package(CppUnit)
|
||||||
|
|
||||||
# To run a more advanced search for GNU Radio and it's components and
|
# 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 ...)
|
# set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER ...)
|
||||||
# find_package(Gnuradio "version")
|
# find_package(Gnuradio "version")
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
|
||||||
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
|
find_package(Gnuradio)
|
||||||
find_package(Gnuradio)
|
|
||||||
endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
|
||||||
|
|
||||||
if(NOT GNURADIO_RUNTIME_FOUND)
|
if(NOT GNURADIO_RUNTIME_FOUND)
|
||||||
message(FATAL_ERROR "GnuRadio Runtime required to compile op25")
|
message(FATAL_ERROR "GnuRadio Runtime required to compile op25")
|
||||||
|
|
|
@ -4,17 +4,17 @@
|
||||||
<key>op25_decoder_ff</key>
|
<key>op25_decoder_ff</key>
|
||||||
<category>op25</category>
|
<category>op25</category>
|
||||||
<import>import op25</import>
|
<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.
|
<!-- Make one 'param' node for every Parameter you want settable from the GUI.
|
||||||
Sub-nodes:
|
Sub-nodes:
|
||||||
* name
|
* name
|
||||||
* key (makes the value accessible as $keyname, e.g. in the make node)
|
* key (makes the value accessible as $keyname, e.g. in the make node)
|
||||||
* type -->
|
* type -->
|
||||||
<param>
|
<!--<param>
|
||||||
<name>...</name>
|
<name>...</name>
|
||||||
<key>...</key>
|
<key>...</key>
|
||||||
<type>...</type>
|
<type>...</type>
|
||||||
</param>
|
</param>-->
|
||||||
|
|
||||||
<!-- Make one 'sink' node per input. Sub-nodes:
|
<!-- Make one 'sink' node per input. Sub-nodes:
|
||||||
* name (an identifier for the GUI)
|
* name (an identifier for the GUI)
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
* optional (set to 1 for optional inputs) -->
|
* optional (set to 1 for optional inputs) -->
|
||||||
<sink>
|
<sink>
|
||||||
<name>in</name>
|
<name>in</name>
|
||||||
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
|
<type>float</type>
|
||||||
</sink>
|
</sink>
|
||||||
|
|
||||||
<!-- Make one 'source' node per output. Sub-nodes:
|
<!-- Make one 'source' node per output. Sub-nodes:
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
* vlen
|
* vlen
|
||||||
* optional (set to 1 for optional inputs) -->
|
* optional (set to 1 for optional inputs) -->
|
||||||
<source>
|
<source>
|
||||||
<name>out</name>
|
<name>audio</name>
|
||||||
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
|
<type>float</type>
|
||||||
</source>
|
</source>
|
||||||
</block>
|
</block>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<key>op25_fsk4_demod_ff</key>
|
<key>op25_fsk4_demod_ff</key>
|
||||||
<category>op25</category>
|
<category>op25</category>
|
||||||
<import>import op25</import>
|
<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>
|
<param>
|
||||||
<name>Sample Rate</name>
|
<name>Sample Rate</name>
|
||||||
|
@ -20,7 +20,8 @@
|
||||||
<type>real</type>
|
<type>real</type>
|
||||||
</param>
|
</param>
|
||||||
|
|
||||||
<param>
|
<!-- Must connect tune message queue as the block expects a queue as first argument! -->
|
||||||
|
<!--<param>
|
||||||
<name>Output Auto Tune</name>
|
<name>Output Auto Tune</name>
|
||||||
<key>tune_out</key>
|
<key>tune_out</key>
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
<name>No</name>
|
<name>No</name>
|
||||||
<key>False</key>
|
<key>False</key>
|
||||||
</option>
|
</option>
|
||||||
</param>
|
</param>-->
|
||||||
|
|
||||||
<sink>
|
<sink>
|
||||||
<name>in</name>
|
<name>in</name>
|
||||||
|
@ -45,4 +46,10 @@
|
||||||
<name>dibits</name>
|
<name>dibits</name>
|
||||||
<type>float</type>
|
<type>float</type>
|
||||||
</source>
|
</source>
|
||||||
|
|
||||||
|
<source>
|
||||||
|
<name>tune</name>
|
||||||
|
<type>msg</type>
|
||||||
|
<!--<optional>1</optional>-->
|
||||||
|
</source>
|
||||||
</block>
|
</block>
|
||||||
|
|
|
@ -51,7 +51,7 @@ namespace gr {
|
||||||
* class. op25::decoder_bf::make is the public interface for
|
* class. op25::decoder_bf::make is the public interface for
|
||||||
* creating new instances.
|
* 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
|
* Return a pointer to a string identifying the destination of
|
||||||
|
@ -78,6 +78,17 @@ namespace gr {
|
||||||
* message queue.
|
* message queue.
|
||||||
*/
|
*/
|
||||||
virtual void set_msgq(gr::msg_queue::sptr msgq) = 0;
|
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
|
} // namespace op25
|
||||||
|
|
|
@ -53,6 +53,13 @@ list(APPEND op25_sources
|
||||||
value_string.cc
|
value_string.cc
|
||||||
pickle.cc
|
pickle.cc
|
||||||
pcap_source_b_impl.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})
|
add_library(gnuradio-op25 SHARED ${op25_sources})
|
||||||
|
|
|
@ -55,10 +55,10 @@ abstract_data_unit::correct_errors()
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
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()) {
|
if(is_complete()) {
|
||||||
do_decode_audio(d_frame_body, imbe);
|
do_decode_audio(d_frame_body, imbe, crypto_mod);
|
||||||
} else {
|
} else {
|
||||||
ostringstream msg;
|
ostringstream msg;
|
||||||
msg << "cannot decode audio - frame is not complete" << endl;
|
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) :
|
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());
|
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
|
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();
|
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 "data_unit.h"
|
||||||
#include "op25_yank.h"
|
#include "op25_yank.h"
|
||||||
|
#include "crypto.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -62,7 +63,7 @@ public:
|
||||||
* \precondition is_complete() == true.
|
* \precondition is_complete() == true.
|
||||||
* \param imbe The imbe_decoder to use to generate the audio.
|
* \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.
|
* Decode the frame into an octet vector.
|
||||||
|
@ -117,6 +118,15 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual std::string snapshot() const;
|
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:
|
protected:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,7 +150,7 @@ protected:
|
||||||
* \param frame_body The const_bit_vector to decode.
|
* \param frame_body The const_bit_vector to decode.
|
||||||
* \param imbe The imbe_decoder to use.
|
* \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.
|
* 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);
|
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.
|
* Return a reference to the frame body.
|
||||||
*/
|
*/
|
||||||
|
@ -180,6 +183,8 @@ protected:
|
||||||
*/
|
*/
|
||||||
virtual uint16_t frame_size() const;
|
virtual uint16_t frame_size() const;
|
||||||
|
|
||||||
|
virtual bool logging_enabled() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -187,6 +192,7 @@ private:
|
||||||
*/
|
*/
|
||||||
bit_vector d_frame_body;
|
bit_vector d_frame_body;
|
||||||
|
|
||||||
|
bool d_logging_enabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* INCLUDED_ABSTRACT_DATA_UNIT_H */
|
#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 <iosfwd>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "crypto.h"
|
||||||
|
|
||||||
typedef std::deque<bool> bit_queue;
|
typedef std::deque<bool> bit_queue;
|
||||||
typedef const std::deque<bool> const_bit_queue;
|
typedef const std::deque<bool> const_bit_queue;
|
||||||
|
|
||||||
|
@ -76,7 +78,7 @@ public:
|
||||||
* \precondition is_complete() == true.
|
* \precondition is_complete() == true.
|
||||||
* \param imbe The imbe_decoder to use to generate the audio.
|
* \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.
|
* Decode the frame into an octet vector.
|
||||||
|
@ -132,6 +134,8 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual std::string snapshot() const = 0;
|
virtual std::string snapshot() const = 0;
|
||||||
|
|
||||||
|
virtual void set_logging(bool on) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "offline_imbe_decoder.h"
|
#include "offline_imbe_decoder.h"
|
||||||
#include "voice_du_handler.h"
|
#include "voice_du_handler.h"
|
||||||
#include "op25_yank.h"
|
#include "op25_yank.h"
|
||||||
|
#include "bch.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -41,13 +42,13 @@ namespace gr {
|
||||||
namespace op25 {
|
namespace op25 {
|
||||||
|
|
||||||
decoder_bf::sptr
|
decoder_bf::sptr
|
||||||
decoder_bf::make()
|
decoder_bf::make(bool idle_silence /*= true*/, bool verbose /*= false*/)
|
||||||
{
|
{
|
||||||
return gnuradio::get_initial_sptr
|
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::block("decoder_bf",
|
||||||
gr::io_signature::make(1, 1, sizeof(uint8_t)),
|
gr::io_signature::make(1, 1, sizeof(uint8_t)),
|
||||||
gr::io_signature::make(0, 1, sizeof(float))),
|
gr::io_signature::make(0, 1, sizeof(float))),
|
||||||
|
@ -56,14 +57,25 @@ namespace gr {
|
||||||
d_frame_hdr(),
|
d_frame_hdr(),
|
||||||
d_imbe(imbe_decoder::make()),
|
d_imbe(imbe_decoder::make()),
|
||||||
d_state(SYNCHRONIZING),
|
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,
|
d_p25cai_du_handler = new p25cai_du_handler(d_data_unit_handler,
|
||||||
"224.0.0.1", 23456);
|
"224.0.0.1", 23456);
|
||||||
d_data_unit_handler = data_unit_handler_sptr(d_p25cai_du_handler);
|
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_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(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()
|
decoder_bf_impl::~decoder_bf_impl()
|
||||||
|
@ -104,34 +116,35 @@ namespace gr {
|
||||||
gr_vector_void_star &output_items)
|
gr_vector_void_star &output_items)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
gr::thread::scoped_lock lock(d_mutex);
|
||||||
|
|
||||||
// process input
|
// process input
|
||||||
const uint8_t *in = reinterpret_cast<const uint8_t*>(input_items[0]);
|
const uint8_t *in = reinterpret_cast<const uint8_t*>(input_items[0]);
|
||||||
for(int i = 0; i < ninput_items[0]; ++i) {
|
for(int i = 0; i < ninput_items[0]; ++i) {
|
||||||
dibit d = in[i] & 0x3;
|
dibit d = in[i] & 0x3;
|
||||||
receive_symbol(d);
|
receive_symbol(d);
|
||||||
}
|
}
|
||||||
consume_each(ninput_items[0]);
|
consume_each(ninput_items[0]);
|
||||||
|
|
||||||
// produce audio
|
// produce audio
|
||||||
audio_samples *samples = d_imbe->audio();
|
audio_samples *samples = d_imbe->audio();
|
||||||
float *out = reinterpret_cast<float*>(output_items[0]);
|
float *out = reinterpret_cast<float*>(output_items[0]);
|
||||||
const int n = min(static_cast<int>(samples->size()), noutput_items);
|
const int n = min(static_cast<int>(samples->size()), noutput_items);
|
||||||
if(0 < n) {
|
if(0 < n) {
|
||||||
copy(samples->begin(), samples->begin() + n, out);
|
copy(samples->begin(), samples->begin() + n, out);
|
||||||
samples->erase(samples->begin(), samples->begin() + n);
|
samples->erase(samples->begin(), samples->begin() + n);
|
||||||
}
|
}
|
||||||
if(n < noutput_items) {
|
if((d_idle_silence) && (n < noutput_items)) {
|
||||||
fill(out + n, out + noutput_items, 0.0);
|
fill(out + n, out + noutput_items, 0.0);
|
||||||
}
|
}
|
||||||
return noutput_items;
|
return (d_idle_silence ? noutput_items : n);
|
||||||
|
|
||||||
} catch(const std::exception& x) {
|
} catch(const std::exception& x) {
|
||||||
cerr << x.what() << endl;
|
cerr << x.what() << endl;
|
||||||
exit(1);
|
exit(1);
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
cerr << "unhandled exception" << endl;
|
cerr << "unhandled exception" << endl;
|
||||||
exit(2); }
|
exit(2); }
|
||||||
}
|
}
|
||||||
|
|
||||||
const char*
|
const char*
|
||||||
|
@ -177,14 +190,12 @@ namespace gr {
|
||||||
};
|
};
|
||||||
size_t NID_SZ = sizeof(NID) / sizeof(NID[0]);
|
size_t NID_SZ = sizeof(NID) / sizeof(NID[0]);
|
||||||
|
|
||||||
itpp::bvec b(63), zeroes(16);
|
bit_vector b(NID_SZ);
|
||||||
itpp::BCH bch(63, 16, 11, "6 3 3 1 1 4 1 3 6 7 2 3 5 4 5 3", true);
|
|
||||||
yank(d_frame_hdr, NID, NID_SZ, b, 0);
|
yank(d_frame_hdr, NID, NID_SZ, b, 0);
|
||||||
b = bch.decode(b);
|
if(bchDec(b) >= 0) {
|
||||||
if(b != zeroes) {
|
|
||||||
b = bch.encode(b);
|
|
||||||
yank_back(b, 0, d_frame_hdr, NID, NID_SZ);
|
yank_back(b, 0, d_frame_hdr, NID, NID_SZ);
|
||||||
d_data_unit = data_unit::make_data_unit(d_frame_hdr);
|
d_data_unit = data_unit::make_data_unit(d_frame_hdr);
|
||||||
|
d_data_unit->set_logging(d_verbose);
|
||||||
} else {
|
} else {
|
||||||
data_unit_sptr null;
|
data_unit_sptr null;
|
||||||
d_data_unit = null;
|
d_data_unit = null;
|
||||||
|
@ -229,5 +240,36 @@ namespace gr {
|
||||||
break;
|
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 op25 */
|
||||||
} /* namespace gr */
|
} /* namespace gr */
|
||||||
|
|
|
@ -24,11 +24,14 @@
|
||||||
#define INCLUDED_OP25_DECODER_BF_IMPL_H
|
#define INCLUDED_OP25_DECODER_BF_IMPL_H
|
||||||
|
|
||||||
#include <op25/decoder_bf.h>
|
#include <op25/decoder_bf.h>
|
||||||
|
#include <gnuradio/thread/thread.h>
|
||||||
#include "data_unit.h"
|
#include "data_unit.h"
|
||||||
#include "data_unit_handler.h"
|
#include "data_unit_handler.h"
|
||||||
#include "imbe_decoder.h"
|
#include "imbe_decoder.h"
|
||||||
#include "p25cai_du_handler.h"
|
#include "p25cai_du_handler.h"
|
||||||
#include "snapshot_du_handler.h"
|
#include "snapshot_du_handler.h"
|
||||||
|
#include "crypto.h"
|
||||||
|
#include "crypto_module_du_handler.h"
|
||||||
|
|
||||||
namespace gr {
|
namespace gr {
|
||||||
namespace op25 {
|
namespace op25 {
|
||||||
|
@ -102,8 +105,21 @@ namespace gr {
|
||||||
*/
|
*/
|
||||||
class snapshot_du_handler *d_snapshot_du_handler;
|
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:
|
public:
|
||||||
decoder_bf_impl();
|
decoder_bf_impl(bool idle_silence = true, bool verbose = false);
|
||||||
~decoder_bf_impl();
|
~decoder_bf_impl();
|
||||||
|
|
||||||
// Where all the action really happens
|
// Where all the action really happens
|
||||||
|
@ -139,6 +155,14 @@ namespace gr {
|
||||||
* message queue.
|
* message queue.
|
||||||
*/
|
*/
|
||||||
void set_msgq(gr::msg_queue::sptr msgq);
|
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 op25
|
||||||
} // namespace gr
|
} // namespace gr
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "offline_imbe_decoder.h"
|
#include "offline_imbe_decoder.h"
|
||||||
#include "voice_du_handler.h"
|
#include "voice_du_handler.h"
|
||||||
#include "op25_yank.h"
|
#include "op25_yank.h"
|
||||||
|
#include "bch.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -185,12 +186,9 @@ namespace gr {
|
||||||
};
|
};
|
||||||
size_t NID_SZ = sizeof(NID) / sizeof(NID[0]);
|
size_t NID_SZ = sizeof(NID) / sizeof(NID[0]);
|
||||||
|
|
||||||
itpp::bvec b(63), zeroes(16);
|
bit_vector b(NID_SZ);
|
||||||
itpp::BCH bch(63, 16, 11, "6 3 3 1 1 4 1 3 6 7 2 3 5 4 5 3", true);
|
|
||||||
yank(d_frame_hdr, NID, NID_SZ, b, 0);
|
yank(d_frame_hdr, NID, NID_SZ, b, 0);
|
||||||
b = bch.decode(b);
|
if(bchDec(b) >= 0) {
|
||||||
if(b != zeroes) {
|
|
||||||
b = bch.encode(b);
|
|
||||||
yank_back(b, 0, d_frame_hdr, NID, NID_SZ);
|
yank_back(b, 0, d_frame_hdr, NID, NID_SZ);
|
||||||
d_data_unit = data_unit::make_data_unit(d_frame_hdr);
|
d_data_unit = data_unit::make_data_unit(d_frame_hdr);
|
||||||
} else {
|
} 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 <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -65,6 +67,8 @@ hdu::do_correct_errors(bit_vector& frame)
|
||||||
{
|
{
|
||||||
apply_golay_correction(frame);
|
apply_golay_correction(frame);
|
||||||
apply_rs_correction(frame);
|
apply_rs_correction(frame);
|
||||||
|
|
||||||
|
if (logging_enabled()) fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -136,33 +140,58 @@ hdu::frame_size_max() const
|
||||||
return 792;
|
return 792;
|
||||||
}
|
}
|
||||||
|
|
||||||
string
|
uint8_t
|
||||||
hdu::algid_str() const
|
hdu::algid() const
|
||||||
{
|
{
|
||||||
const size_t ALGID_BITS[] = {
|
const size_t ALGID_BITS[] = {
|
||||||
356, 357, 360, 361, 374, 375, 376, 377
|
356, 357, 360, 361, 374, 375, 376, 377
|
||||||
};
|
};
|
||||||
const size_t ALGID_BITS_SZ = sizeof(ALGID_BITS) / sizeof(ALGID_BITS[0]);
|
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 extract(frame_body(), ALGID_BITS, ALGID_BITS_SZ);
|
||||||
return lookup(algid, ALGIDS, ALGIDS_SZ);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string
|
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[] = {
|
const size_t KID_BITS[] = {
|
||||||
378, 379, 392, 393, 394, 395, 396, 397,
|
378, 379, 392, 393, 394, 395, 396, 397,
|
||||||
410, 411, 412, 413, 414, 415, 428, 429
|
410, 411, 412, 413, 414, 415, 428, 429
|
||||||
};
|
};
|
||||||
const size_t KID_BITS_SZ = sizeof(KID_BITS) / sizeof(KID_BITS[0]);
|
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;
|
ostringstream os;
|
||||||
os << hex << showbase << setfill('0') << setw(4) << kid;
|
os << hex << showbase << setfill('0') << setw(4) << _kid;
|
||||||
return os.str();
|
return os.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
hdu::mi_str() const
|
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[] = {
|
const size_t MI_BITS[] = {
|
||||||
114, 115, 116, 117, 118, 119, 132, 133,
|
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]);
|
const size_t MI_BITS_SZ = sizeof(MI_BITS) / sizeof(MI_BITS[0]);
|
||||||
|
|
||||||
uint8_t mi[9];
|
std::vector<uint8_t> _mi(((MI_BITS_SZ + 7) / 8));
|
||||||
extract(frame_body(), MI_BITS, MI_BITS_SZ, mi);
|
extract(frame_body(), MI_BITS, MI_BITS_SZ, &_mi[0]);
|
||||||
ostringstream os;
|
return _mi;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string
|
string
|
||||||
|
@ -219,7 +242,21 @@ hdu::tgid_str() const
|
||||||
};
|
};
|
||||||
const size_t TGID_BITS_SZ = sizeof(TGID_BITS) / sizeof(TGID_BITS[0]);
|
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);
|
const uint16_t tgid = extract(frame_body(), TGID_BITS, TGID_BITS_SZ);
|
||||||
ostringstream os;
|
// Zero fill isn't working properly in original implementation
|
||||||
os << hex << showbase << setfill('0') << setw(4) << tgid;
|
//ostringstream os;
|
||||||
return os.str();
|
//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
|
#define INCLUDED_HDU_H
|
||||||
|
|
||||||
#include "abstract_data_unit.h"
|
#include "abstract_data_unit.h"
|
||||||
|
#include "crypto.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P25 header data unit (HDU).
|
* P25 header data unit (HDU).
|
||||||
*/
|
*/
|
||||||
class hdu : public abstract_data_unit
|
class hdu : public abstract_data_unit, public crypto_state_provider
|
||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -96,7 +97,9 @@ protected:
|
||||||
*/
|
*/
|
||||||
virtual uint16_t frame_size_max() const;
|
virtual uint16_t frame_size_max() const;
|
||||||
|
|
||||||
private:
|
public:
|
||||||
|
|
||||||
|
uint8_t algid() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a string describing the encryption algorithm ID (ALGID).
|
* Return a string describing the encryption algorithm ID (ALGID).
|
||||||
|
@ -105,6 +108,8 @@ private:
|
||||||
*/
|
*/
|
||||||
std::string algid_str() const;
|
std::string algid_str() const;
|
||||||
|
|
||||||
|
virtual uint16_t kid() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a string describing the key id (KID).
|
* Returns a string describing the key id (KID).
|
||||||
*
|
*
|
||||||
|
@ -119,6 +124,8 @@ private:
|
||||||
*/
|
*/
|
||||||
virtual std::string mfid_str() const;
|
virtual std::string mfid_str() const;
|
||||||
|
|
||||||
|
virtual std::vector<uint8_t> mi() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a string describing the message indicator (MI).
|
* Returns a string describing the message indicator (MI).
|
||||||
*
|
*
|
||||||
|
@ -139,6 +146,10 @@ private:
|
||||||
* \return A string identifying the TGID.
|
* \return A string identifying the TGID.
|
||||||
*/
|
*/
|
||||||
virtual std::string tgid_str() const;
|
virtual std::string tgid_str() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct CryptoState crypto_state() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* INCLUDED_HDU_H */
|
#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 "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;
|
using std::string;
|
||||||
|
|
||||||
ldu1::ldu1(const_bit_queue& frame_body) :
|
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
|
string
|
||||||
ldu1::duid_str() const
|
ldu1::duid_str() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,13 +24,45 @@
|
||||||
#ifndef INCLUDED_LDU1_H
|
#ifndef INCLUDED_LDU1_H
|
||||||
#define INCLUDED_LDU1_H
|
#define INCLUDED_LDU1_H
|
||||||
|
|
||||||
#include "voice_data_unit.h"
|
#include "ldu.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P25 Logical Data Unit 1.
|
* 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:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,6 +82,10 @@ public:
|
||||||
*/
|
*/
|
||||||
std::string duid_str() const;
|
std::string duid_str() const;
|
||||||
|
|
||||||
|
virtual std::string snapshot() const;
|
||||||
|
|
||||||
|
combined_meta_data meta_data() const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* INCLUDED_LDU1_H */
|
#endif /* INCLUDED_LDU1_H */
|
||||||
|
|
|
@ -23,10 +23,18 @@
|
||||||
|
|
||||||
#include "ldu2.h"
|
#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;
|
using std::string;
|
||||||
|
|
||||||
ldu2::ldu2(const_bit_queue& frame_body) :
|
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");
|
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
|
#ifndef INCLUDED_LDU2_H
|
||||||
#define INCLUDED_LDU2_H
|
#define INCLUDED_LDU2_H
|
||||||
|
|
||||||
#include "voice_data_unit.h"
|
#include "ldu.h"
|
||||||
|
#include "crypto.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* P25 Logical Data Unit 2.
|
* 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:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,6 +58,10 @@ public:
|
||||||
* Returns a string describing the Data Unit ID (DUID).
|
* Returns a string describing the Data Unit ID (DUID).
|
||||||
*/
|
*/
|
||||||
std::string duid_str() const;
|
std::string duid_str() const;
|
||||||
|
|
||||||
|
virtual std::string snapshot() const;
|
||||||
|
|
||||||
|
struct CryptoState crypto_state() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* INCLUDED_LDU2_H */
|
#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 <cstddef>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* APCO Hamming(15,11,3) ecoder.
|
* APCO Hamming(15,11,3) ecoder.
|
||||||
|
@ -183,4 +184,18 @@ hamming_15_decode(uint16_t& cw)
|
||||||
return errs;
|
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 */
|
#endif /* INCLUDED_OP25_HAMMING_H */
|
||||||
|
|
|
@ -10,9 +10,54 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
typedef std::vector<bool> voice_codeword;
|
typedef std::vector<bool> voice_codeword;
|
||||||
|
typedef std::vector<uint8_t> packed_codeword;
|
||||||
typedef const std::vector<bool> const_bit_vector;
|
typedef const std::vector<bool> const_bit_vector;
|
||||||
typedef std::vector<bool> 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 size_t nof_voice_codewords = 9, voice_codeword_sz = 144;
|
||||||
|
|
||||||
static const uint16_t imbe_ldu_NID_bits[] = {
|
static const uint16_t imbe_ldu_NID_bits[] = {
|
||||||
|
@ -251,8 +296,8 @@ pngen23(uint32_t& Pr)
|
||||||
* \param u0-u7 Result output vectors
|
* \param u0-u7 Result output vectors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static inline void
|
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)
|
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;
|
ET = 0;
|
||||||
|
|
||||||
|
@ -294,7 +339,30 @@ imbe_header_decode(const voice_codeword& cw, uint32_t& u0, uint32_t& u1, uint32_
|
||||||
u6 = v6;
|
u6 = v6;
|
||||||
|
|
||||||
u7 = extract(cw, 137, 144);
|
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.
|
/* APCO IMBE header encoder.
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2008 Steve Glass
|
* Copyright 2008 Steve Glass
|
||||||
|
* Copyright 2018 Matt Ames
|
||||||
*
|
*
|
||||||
* This file is part of OP25.
|
* This file is part of OP25.
|
||||||
*
|
*
|
||||||
|
@ -53,18 +54,38 @@ const value_string ALGIDS[] = {
|
||||||
{ 0x02, "FIREFLY Type 1" },
|
{ 0x02, "FIREFLY Type 1" },
|
||||||
{ 0x03, "MAYFLY Type 1" },
|
{ 0x03, "MAYFLY Type 1" },
|
||||||
{ 0x04, "SAVILLE" },
|
{ 0x04, "SAVILLE" },
|
||||||
|
{ 0x05, "Motorola Assigned - PADSTONE" },
|
||||||
{ 0x41, "BATON (Auto Odd)" },
|
{ 0x41, "BATON (Auto Odd)" },
|
||||||
/* Type III */
|
/* Type III */
|
||||||
{ 0x80, "Plain" },
|
{ 0x80, "Unencrypted" },
|
||||||
{ 0x81, "DES-OFB" },
|
{ 0x81, "DES-OFB, 56 bit key" },
|
||||||
{ 0x82, "2 key Triple DES" },
|
{ 0x83, "3 key Triple DES, 168 bit key" },
|
||||||
{ 0x83, "3 key Triple DES" },
|
{ 0x84, "AES-256-OFB" },
|
||||||
{ 0x84, "AES-256" },
|
{ 0x85, "AES-128-ECB"},
|
||||||
/* Motorola proprietary */
|
{ 0x88, "AES-CBC"},
|
||||||
{ 0x9F, "Motorola DES-XL" },
|
/* 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" },
|
{ 0xA0, "Motorola DVI-XL" },
|
||||||
{ 0xA1, "Motorola DVP-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]);
|
const size_t ALGIDS_SZ = sizeof(ALGIDS) / sizeof(ALGIDS[0]);
|
||||||
|
|
||||||
|
|
|
@ -24,32 +24,325 @@
|
||||||
#include "voice_data_unit.h"
|
#include "voice_data_unit.h"
|
||||||
#include "op25_imbe_frame.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;
|
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) :
|
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
|
void
|
||||||
voice_data_unit::do_correct_errors(bit_vector& frame_body)
|
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
|
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);
|
voice_codeword cw(voice_codeword_sz);
|
||||||
for(size_t i = 0; i < nof_voice_codewords; ++i) {
|
for(size_t i = 0; i < nof_voice_codewords; ++i) {
|
||||||
imbe_deinterleave(frame_body, cw, 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);
|
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
|
uint16_t
|
||||||
|
|
|
@ -38,6 +38,17 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual ~voice_data_unit();
|
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:
|
protected:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,7 +74,7 @@ protected:
|
||||||
* \param frame_body The const_bit_vector to decode.
|
* \param frame_body The const_bit_vector to decode.
|
||||||
* \param imbe The imbe_decoder to use to generate the audio.
|
* \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
|
* 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 frame_size_max() const;
|
||||||
|
|
||||||
|
virtual uint16_t lsdw() const;
|
||||||
|
|
||||||
|
virtual bool lsdw_valid() const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* INCLUDED_VOICE_DATA_UNIT_H */
|
#endif /* INCLUDED_VOICE_DATA_UNIT_H */
|
||||||
|
|
|
@ -28,9 +28,10 @@
|
||||||
|
|
||||||
using namespace std;
|
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),
|
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
|
void
|
||||||
voice_du_handler::handle(data_unit_sptr du)
|
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);
|
data_unit_handler::handle(du);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include "data_unit_handler.h"
|
#include "data_unit_handler.h"
|
||||||
#include "imbe_decoder.h"
|
#include "imbe_decoder.h"
|
||||||
|
#include "crypto.h"
|
||||||
|
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ public:
|
||||||
* \param next The next data_unit_handler in the chain.
|
* \param next The next data_unit_handler in the chain.
|
||||||
* \param decoder An imbe_decoder_sptr to the IMBE decoder to use.
|
* \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.
|
* voice_du_handler virtual destructor.
|
||||||
|
@ -64,6 +65,8 @@ private:
|
||||||
*/
|
*/
|
||||||
imbe_decoder_sptr d_decoder;
|
imbe_decoder_sptr d_decoder;
|
||||||
|
|
||||||
|
crypto_module::sptr d_crypto_mod;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* INCLUDED_VOICE_DU_HANDLER_H */
|
#endif /* INCLUDED_VOICE_DU_HANDLER_H */
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
/* -*- c++ -*- */
|
/* -*- c++ -*- */
|
||||||
|
|
||||||
|
%include "pycontainer.swg"
|
||||||
|
|
||||||
#define OP25_API
|
#define OP25_API
|
||||||
|
|
||||||
%include "gnuradio.i" // the common stuff
|
%include "gnuradio.i" // the common stuff
|
||||||
|
@ -15,6 +17,11 @@
|
||||||
#include "op25/pcap_source_b.h"
|
#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"
|
%include "op25/fsk4_demod_ff.h"
|
||||||
GR_SWIG_BLOCK_MAGIC2(op25, fsk4_demod_ff);
|
GR_SWIG_BLOCK_MAGIC2(op25, fsk4_demod_ff);
|
||||||
%include "op25/fsk4_slicer_fb.h"
|
%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 gnuradio build dependencies
|
||||||
########################################################################
|
########################################################################
|
||||||
find_package(GnuradioRuntime)
|
|
||||||
find_package(CppUnit)
|
find_package(CppUnit)
|
||||||
|
|
||||||
# To run a more advanced search for GNU Radio and it's components and
|
# 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
|
# of GR_REQUIRED_COMPONENTS (in all caps) and change "version" to the
|
||||||
# minimum API compatible version required.
|
# minimum API compatible version required.
|
||||||
#
|
#
|
||||||
# set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER ...)
|
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
|
||||||
# find_package(Gnuradio "version")
|
# find_package(Gnuradio "version")
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
find_package(Gnuradio)
|
||||||
set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
|
|
||||||
find_package(Gnuradio)
|
|
||||||
endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
|
||||||
|
|
||||||
if(NOT GNURADIO_RUNTIME_FOUND)
|
if(NOT GNURADIO_RUNTIME_FOUND)
|
||||||
message(FATAL_ERROR "GnuRadio Runtime required to compile op25_repeater")
|
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
|
is a traceback please report the full traceback (and the command line) to
|
||||||
the mail list.
|
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
|
EXTERNAL UDP AUDIO SERVER
|
||||||
=========================
|
=========================
|
||||||
Starting rx.py with the "-w -W host" options directs udp audio data to
|
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
|
-H HAMLIB_MODEL, --hamlib-model=HAMLIB_MODEL
|
||||||
specify model for hamlib
|
specify model for hamlib
|
||||||
-s SEEK, --seek=SEEK ifile seek in K
|
-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
|
-L LOGFILE_WORKERS, --logfile-workers=LOGFILE_WORKERS
|
||||||
number of demodulators to instantiate
|
number of demodulators to instantiate
|
||||||
-S SAMPLE_RATE, --sample-rate=SAMPLE_RATE
|
-S SAMPLE_RATE, --sample-rate=SAMPLE_RATE
|
||||||
|
@ -161,3 +176,67 @@ Options:
|
||||||
-2, --phase2-tdma enable phase2 tdma decode
|
-2, --phase2-tdma enable phase2 tdma decode
|
||||||
-Z DECIM_AMT, --decim-amt=DECIM_AMT
|
-Z DECIM_AMT, --decim-amt=DECIM_AMT
|
||||||
spectrum decimation
|
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.
|
# 02110-1301, USA.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
import time
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from gnuradio import gr, gru, eng_notation
|
from gnuradio import gr, gru, eng_notation
|
||||||
|
@ -27,6 +29,7 @@ from gnuradio import blocks, audio
|
||||||
from gnuradio.eng_option import eng_option
|
from gnuradio.eng_option import eng_option
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from gnuradio import gr
|
from gnuradio import gr
|
||||||
|
from math import pi
|
||||||
|
|
||||||
_def_debug = 0
|
_def_debug = 0
|
||||||
_def_sps = 10
|
_def_sps = 10
|
||||||
|
@ -34,19 +37,42 @@ _def_sps = 10
|
||||||
GNUPLOT = '/usr/bin/gnuplot'
|
GNUPLOT = '/usr/bin/gnuplot'
|
||||||
|
|
||||||
FFT_AVG = 0.25
|
FFT_AVG = 0.25
|
||||||
|
MIX_AVG = 0.15
|
||||||
|
BAL_AVG = 0.05
|
||||||
FFT_BINS = 512
|
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):
|
class wrap_gp(object):
|
||||||
def __init__(self, sps=_def_sps):
|
def __init__(self, sps=_def_sps, logfile=None):
|
||||||
self.sps = sps
|
self.sps = sps
|
||||||
self.center_freq = None
|
self.center_freq = 0.0
|
||||||
self.relative_freq = 0.0
|
self.relative_freq = 0.0
|
||||||
|
self.offset_freq = 0.0
|
||||||
self.width = None
|
self.width = None
|
||||||
self.ffts = ()
|
self.ffts = ()
|
||||||
self.freqs = ()
|
self.freqs = ()
|
||||||
self.avg_pwr = np.zeros(FFT_BINS)
|
self.avg_pwr = np.zeros(FFT_BINS)
|
||||||
|
self.avg_sum_pwr = 0.0
|
||||||
self.buf = []
|
self.buf = []
|
||||||
self.plot_count = 0
|
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()
|
self.attach_gp()
|
||||||
|
|
||||||
|
@ -56,8 +82,29 @@ class wrap_gp(object):
|
||||||
self.gp = subprocess.Popen(args, executable=exe, stdin=subprocess.PIPE)
|
self.gp = subprocess.Popen(args, executable=exe, stdin=subprocess.PIPE)
|
||||||
|
|
||||||
def kill(self):
|
def kill(self):
|
||||||
self.gp.kill()
|
try:
|
||||||
self.gp.wait()
|
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'):
|
def plot(self, buf, bufsz, mode='eye'):
|
||||||
BUFSZ = bufsz
|
BUFSZ = bufsz
|
||||||
|
@ -74,6 +121,7 @@ class wrap_gp(object):
|
||||||
|
|
||||||
plots = []
|
plots = []
|
||||||
s = ''
|
s = ''
|
||||||
|
plot_size = (320,240)
|
||||||
while(len(self.buf)):
|
while(len(self.buf)):
|
||||||
if mode == 'eye':
|
if mode == 'eye':
|
||||||
if len(self.buf) < self.sps:
|
if len(self.buf) < self.sps:
|
||||||
|
@ -83,61 +131,137 @@ class wrap_gp(object):
|
||||||
s += 'e\n'
|
s += 'e\n'
|
||||||
self.buf=self.buf[self.sps:]
|
self.buf=self.buf[self.sps:]
|
||||||
plots.append('"-" with lines')
|
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:
|
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'
|
s += 'e\n'
|
||||||
self.buf = []
|
self.buf = []
|
||||||
plots.append('"-" with points')
|
plots.append('"-" with lines')
|
||||||
elif mode == 'symbol':
|
elif mode == 'symbol':
|
||||||
for b in self.buf:
|
for b in self.buf:
|
||||||
s += '%f\n' % (b)
|
s += '%f\n' % (b)
|
||||||
s += 'e\n'
|
s += 'e\n'
|
||||||
self.buf = []
|
self.buf = []
|
||||||
plots.append('"-" with dots')
|
plots.append('"-" with points')
|
||||||
elif mode == 'fft':
|
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.fft(self.buf * np.blackman(BUFSZ)) / (0.42 * BUFSZ)
|
||||||
self.ffts = np.fft.fftshift(self.ffts)
|
self.ffts = np.fft.fftshift(self.ffts)
|
||||||
self.freqs = np.fft.fftfreq(len(self.ffts))
|
self.freqs = np.fft.fftfreq(len(self.ffts))
|
||||||
self.freqs = np.fft.fftshift(self.freqs)
|
self.freqs = np.fft.fftshift(self.freqs)
|
||||||
|
tune_freq = (self.center_freq - self.relative_freq) / 1e6
|
||||||
if self.center_freq and self.width:
|
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)):
|
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]))
|
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'
|
s += 'e\n'
|
||||||
self.buf = []
|
self.buf = []
|
||||||
plots.append('"-" with lines')
|
plots.append('"-" with lines')
|
||||||
self.buf = []
|
self.buf = []
|
||||||
|
|
||||||
h= 'set terminal x11 noraise\n'
|
# FFT processing needs to be completed to maintain the weighted average buckets
|
||||||
#background = 'set object 1 circle at screen 0,0 size screen 1 fillcolor rgb"black"\n' #FIXME!
|
# 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 = ''
|
background = ''
|
||||||
h+= 'set key off\n'
|
h = 'set key off\n'
|
||||||
if mode == 'constellation':
|
if mode == 'constellation':
|
||||||
h+= background
|
h+= background
|
||||||
h+= 'set size square\n'
|
h+= 'set size square\n'
|
||||||
h+= 'set xrange [-1:1]\n'
|
h+= 'set xrange [-1:1]\n'
|
||||||
h+= 'set yrange [-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':
|
elif mode == 'eye':
|
||||||
h+= background
|
h+= background
|
||||||
h+= 'set yrange [-4:4]\n'
|
h+= 'set yrange [-4:4]\n'
|
||||||
|
h+= 'set title "Datascope"\n'
|
||||||
elif mode == 'symbol':
|
elif mode == 'symbol':
|
||||||
h+= background
|
h+= background
|
||||||
h+= 'set yrange [-4:4]\n'
|
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+= 'unset arrow; unset title\n'
|
||||||
h+= 'set xrange [%f:%f]\n' % (self.freqs[0], self.freqs[len(self.freqs)-1])
|
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 xlabel "Frequency"\n'
|
||||||
h+= 'set ylabel "Power(dB)"\n'
|
h+= 'set ylabel "Power(dB)"\n'
|
||||||
h+= 'set grid\n'
|
h+= 'set grid\n'
|
||||||
if self.center_freq:
|
h+= 'set yrange [-100:0]\n'
|
||||||
arrow_pos = (self.center_freq - self.relative_freq) / 1e6
|
if mode == 'mixer': # mixer
|
||||||
h+= 'set arrow from %f, graph 0 to %f, graph 1 nohead\n' % (arrow_pos, arrow_pos)
|
h+= 'set title "Mixer: balance %3.0f (smaller is better)"\n' % (np.abs(self.avg_sum_pwr * 1000))
|
||||||
h+= 'set title "Tuned to %f Mhz"\n' % ((self.center_freq - self.relative_freq) / 1e6)
|
else: # fft
|
||||||
dat = '%splot %s\n%s' % (h, ','.join(plots), s)
|
h+= 'set title "Spectrum"\n'
|
||||||
self.gp.stdin.write(dat)
|
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
|
return consumed
|
||||||
|
|
||||||
def set_center_freq(self, f):
|
def set_center_freq(self, f):
|
||||||
|
@ -146,9 +270,15 @@ class wrap_gp(object):
|
||||||
def set_relative_freq(self, f):
|
def set_relative_freq(self, f):
|
||||||
self.relative_freq = f
|
self.relative_freq = f
|
||||||
|
|
||||||
|
def set_offset(self, f):
|
||||||
|
self.offset_freq = f
|
||||||
|
|
||||||
def set_width(self, w):
|
def set_width(self, w):
|
||||||
self.width = w
|
self.width = w
|
||||||
|
|
||||||
|
def set_logfile(self, logfile=None):
|
||||||
|
self.logfile = logfile
|
||||||
|
|
||||||
class eye_sink_f(gr.sync_block):
|
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):
|
def work(self, input_items, output_items):
|
||||||
self.skip += 1
|
self.skip += 1
|
||||||
if self.skip == 50:
|
if self.skip >= 50:
|
||||||
self.skip = 0
|
self.skip = 0
|
||||||
in0 = input_items[0]
|
in0 = input_items[0]
|
||||||
self.gnuplot.plot(in0, FFT_BINS, mode='fft')
|
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):
|
def set_relative_freq(self, f):
|
||||||
self.gnuplot.set_relative_freq(f)
|
self.gnuplot.set_relative_freq(f)
|
||||||
|
|
||||||
|
def set_offset(self, f):
|
||||||
|
self.gnuplot.set_offset(f)
|
||||||
|
|
||||||
def set_width(self, w):
|
def set_width(self, w):
|
||||||
self.gnuplot.set_width(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):
|
class symbol_sink_f(gr.sync_block):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
|
@ -239,3 +395,22 @@ class symbol_sink_f(gr.sync_block):
|
||||||
|
|
||||||
def kill(self):
|
def kill(self):
|
||||||
self.gnuplot.kill()
|
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.debug = debug
|
||||||
self.dest = dest
|
self.dest = dest
|
||||||
do_output = False
|
do_output = False
|
||||||
|
do_audio_output = False
|
||||||
|
do_phase2_tdma = False
|
||||||
if dest == 'wav':
|
if dest == 'wav':
|
||||||
do_output = True
|
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:
|
if msgq is None:
|
||||||
msgq = gr.msg_queue(1)
|
msgq = gr.msg_queue(1)
|
||||||
|
@ -93,7 +100,7 @@ class p25_decoder_sink_b(gr.hier_block2):
|
||||||
if num_ambe > 1:
|
if num_ambe > 1:
|
||||||
num_decoders += num_ambe - 1
|
num_decoders += num_ambe - 1
|
||||||
for slot in xrange(num_decoders):
|
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.p25_decoders[slot].set_slotid(slot)
|
||||||
|
|
||||||
self.xorhash.append('')
|
self.xorhash.append('')
|
||||||
|
@ -123,6 +130,9 @@ class p25_decoder_sink_b(gr.hier_block2):
|
||||||
return
|
return
|
||||||
self.audio_sink[index].open(filename)
|
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):
|
def set_xormask(self, xormask, xorhash, index=0):
|
||||||
if self.xorhash[index] == xorhash:
|
if self.xorhash[index] == xorhash:
|
||||||
return
|
return
|
||||||
|
|
|
@ -45,14 +45,40 @@ _def_costas_alpha = 0.04
|
||||||
_def_symbol_rate = 4800
|
_def_symbol_rate = 4800
|
||||||
_def_symbol_deviation = 600.0
|
_def_symbol_deviation = 600.0
|
||||||
_def_bb_gain = 1.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
|
# 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):
|
class p25_demod_base(gr.hier_block2):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
if_rate = None,
|
if_rate = None,
|
||||||
|
filter_type = None,
|
||||||
|
excess_bw = _def_excess_bw,
|
||||||
symbol_rate = _def_symbol_rate):
|
symbol_rate = _def_symbol_rate):
|
||||||
"""
|
"""
|
||||||
Hierarchical block for P25 demodulation base class
|
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)
|
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()
|
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)
|
sps = self.if_rate / 4800
|
||||||
autotuneq = gr.msg_queue(2)
|
if filter_type == 'rrc':
|
||||||
self.fsk4_demod = op25.fsk4_demod_ff(autotuneq, self.if_rate, self.symbol_rate)
|
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 ]
|
def set_symbol_rate(self, rate):
|
||||||
self.slicer = op25_repeater.fsk4_slicer_fb(levels)
|
self.symbol_rate = rate
|
||||||
|
|
||||||
def set_baseband_gain(self, k):
|
def set_baseband_gain(self, k):
|
||||||
self.baseband_amp.set_k(k)
|
self.baseband_amp.set_k(k)
|
||||||
|
@ -97,6 +160,8 @@ class p25_demod_fb(p25_demod_base):
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
input_rate = None,
|
input_rate = None,
|
||||||
|
filter_type = None,
|
||||||
|
excess_bw = _def_excess_bw,
|
||||||
symbol_rate = _def_symbol_rate):
|
symbol_rate = _def_symbol_rate):
|
||||||
"""
|
"""
|
||||||
Hierarchical block for P25 demodulation.
|
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_float), # Input signature
|
||||||
gr.io_signature(1, 1, gr.sizeof_char)) # Output 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.input_rate = input_rate
|
||||||
self.float_sink = None
|
self.float_sink = None
|
||||||
|
@ -135,6 +200,8 @@ class p25_demod_cb(p25_demod_base):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
input_rate = None,
|
input_rate = None,
|
||||||
demod_type = 'cqpsk',
|
demod_type = 'cqpsk',
|
||||||
|
filter_type = None,
|
||||||
|
excess_bw = _def_excess_bw,
|
||||||
relative_freq = 0,
|
relative_freq = 0,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
if_rate = _def_if_rate,
|
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_gr_complex), # Input signature
|
||||||
gr.io_signature(1, 1, gr.sizeof_char)) # Output signature
|
gr.io_signature(1, 1, gr.sizeof_char)) # Output signature
|
||||||
# gr.io_signature(0, 0, 0)) # 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.input_rate = input_rate
|
||||||
self.if_rate = if_rate
|
self.if_rate = if_rate
|
||||||
|
@ -164,25 +231,52 @@ class p25_demod_cb(p25_demod_base):
|
||||||
self.lo_freq = 0
|
self.lo_freq = 0
|
||||||
self.float_sink = None
|
self.float_sink = None
|
||||||
self.complex_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()
|
self.mixer = blocks.multiply_cc()
|
||||||
lpf_coeffs = filter.firdes.low_pass(1.0, input_rate, 7250, 725, filter.firdes.WIN_HANN)
|
decimator_values = get_decim(input_rate)
|
||||||
decimation = int(input_rate / if_rate)
|
if decimator_values:
|
||||||
self.lpf = filter.fir_filter_ccf(decimation, lpf_coeffs)
|
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(
|
fa = 6250
|
||||||
float(self.if_rate) / resampled_rate)
|
fb = fa + 625
|
||||||
|
cutoff_coeffs = filter.firdes.low_pass(1.0, self.if_rate, (fb+fa)/2, fb-fa, filter.firdes.WIN_HANN)
|
||||||
self.connect(self, (self.mixer, 0))
|
self.cutoff = filter.fir_filter_ccf(1, cutoff_coeffs)
|
||||||
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)
|
|
||||||
|
|
||||||
omega = float(self.if_rate) / float(self.symbol_rate)
|
omega = float(self.if_rate) / float(self.symbol_rate)
|
||||||
gain_omega = 0.1 * gain_mu * gain_mu
|
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)
|
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):
|
def set_omega(self, omega):
|
||||||
sps = self.if_rate / float(omega)
|
sps = self.if_rate / float(omega)
|
||||||
if sps == self.sps:
|
if sps == self.sps:
|
||||||
|
@ -223,22 +323,33 @@ class p25_demod_cb(p25_demod_base):
|
||||||
self.clock.set_omega(self.sps)
|
self.clock.set_omega(self.sps)
|
||||||
|
|
||||||
def set_relative_frequency(self, freq):
|
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)
|
#print 'set_relative_frequency: error, relative frequency %d exceeds limit %d' % (freq, self.input_rate/2)
|
||||||
return False
|
return False
|
||||||
if freq == self.lo_freq:
|
if freq == self.lo_freq:
|
||||||
return True
|
return True
|
||||||
#print 'set_relative_frequency', freq
|
|
||||||
self.lo_freq = 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
|
return True
|
||||||
|
|
||||||
# assumes lock held or init
|
# assumes lock held or init
|
||||||
def disconnect_chain(self):
|
def disconnect_chain(self):
|
||||||
if self.connect_state == 'cqpsk':
|
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':
|
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
|
self.connect_state = None
|
||||||
|
|
||||||
# assumes lock held or init
|
# assumes lock held or init
|
||||||
|
@ -248,9 +359,9 @@ class p25_demod_cb(p25_demod_base):
|
||||||
self.disconnect_chain()
|
self.disconnect_chain()
|
||||||
self.connect_state = demod_type
|
self.connect_state = demod_type
|
||||||
if demod_type == 'fsk4':
|
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':
|
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:
|
else:
|
||||||
print 'connect_chain failed, type: %s' % demod_type
|
print 'connect_chain failed, type: %s' % demod_type
|
||||||
assert 0 == 1
|
assert 0 == 1
|
||||||
|
@ -277,25 +388,19 @@ class p25_demod_cb(p25_demod_base):
|
||||||
print 'connect_float: state error', self.connect_state
|
print 'connect_float: state error', self.connect_state
|
||||||
assert 0 == 1
|
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):
|
def connect_complex(self, src, sink):
|
||||||
# assumes lock held or init
|
# assumes lock held or init
|
||||||
self.disconnect_complex()
|
|
||||||
if src == 'clock':
|
if src == 'clock':
|
||||||
self.connect(self.clock, sink)
|
self.connect(self.clock, sink)
|
||||||
self.complex_sink = [self.clock, sink]
|
|
||||||
elif src == 'diffdec':
|
elif src == 'diffdec':
|
||||||
self.connect(self.diffdec, sink)
|
self.connect(self.diffdec, sink)
|
||||||
self.complex_sink = [self.diffdec, sink]
|
|
||||||
elif src == 'mixer':
|
elif src == 'mixer':
|
||||||
self.connect(self.mixer, sink)
|
self.connect(self.agc, sink)
|
||||||
self.complex_sink = [self.mixer, sink]
|
|
||||||
elif src == 'src':
|
elif src == 'src':
|
||||||
self.connect(self, sink)
|
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 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.
|
# Copyright 2003,2004,2005,2006 Free Software Foundation, Inc.
|
||||||
# (from radiorausch)
|
# (from radiorausch)
|
||||||
|
@ -64,8 +64,9 @@ from gr_gnuplot import constellation_sink_c
|
||||||
from gr_gnuplot import fft_sink_c
|
from gr_gnuplot import fft_sink_c
|
||||||
from gr_gnuplot import symbol_sink_f
|
from gr_gnuplot import symbol_sink_f
|
||||||
from gr_gnuplot import eye_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
|
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]
|
#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
|
WIRESHARK_PORT = 23456
|
||||||
|
|
||||||
|
_def_interval = 3.0 # sec
|
||||||
|
_def_file_dir = '../www/images'
|
||||||
|
|
||||||
# The P25 receiver
|
# The P25 receiver
|
||||||
#
|
#
|
||||||
class p25_rx_block (gr.top_block):
|
class p25_rx_block (gr.top_block):
|
||||||
|
|
||||||
# Initialize the P25 receiver
|
# Initialize the P25 receiver
|
||||||
#
|
#
|
||||||
def __init__(self):
|
def __init__(self, options):
|
||||||
|
|
||||||
self.trunk_rx = None
|
self.trunk_rx = None
|
||||||
self.kill_sink = None
|
self.plot_sinks = []
|
||||||
|
|
||||||
gr.top_block.__init__(self)
|
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.channel_rate = 0
|
||||||
self.baseband_input = False
|
self.baseband_input = False
|
||||||
self.rtl_found = False
|
self.rtl_found = False
|
||||||
self.channel_rate = options.sample_rate
|
self.channel_rate = options.sample_rate
|
||||||
self.fft_sink = None
|
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
|
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
|
# check if osmocom is accessible
|
||||||
try:
|
try:
|
||||||
import osmosdr
|
import osmosdr
|
||||||
|
@ -147,7 +117,7 @@ class p25_rx_block (gr.top_block):
|
||||||
print "osmosdr source_c creation failure"
|
print "osmosdr source_c creation failure"
|
||||||
ignore = True
|
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)
|
#print "'rtl' has been found in options.args (%s)" % (options.args)
|
||||||
self.rtl_found = True
|
self.rtl_found = True
|
||||||
|
|
||||||
|
@ -156,8 +126,8 @@ class p25_rx_block (gr.top_block):
|
||||||
range = self.src.get_gain_range(name)
|
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())
|
print "gain: name: %s range: start %d stop %d step %d" % (name, range[0].start(), range[0].stop(), range[0].step())
|
||||||
if options.gains:
|
if options.gains:
|
||||||
for tuple in options.gains.split(","):
|
for tup in options.gains.split(","):
|
||||||
name, gain = tuple.split(":")
|
name, gain = tup.split(":")
|
||||||
gain = int(gain)
|
gain = int(gain)
|
||||||
print "setting gain %s to %d" % (name, gain)
|
print "setting gain %s to %d" % (name, gain)
|
||||||
self.src.set_gain(gain, name)
|
self.src.set_gain(gain, name)
|
||||||
|
@ -184,8 +154,9 @@ class p25_rx_block (gr.top_block):
|
||||||
# setup (read-only) attributes
|
# setup (read-only) attributes
|
||||||
self.symbol_rate = 4800
|
self.symbol_rate = 4800
|
||||||
self.symbol_deviation = 600.0
|
self.symbol_deviation = 600.0
|
||||||
self.basic_rate = 48000
|
self.basic_rate = 24000
|
||||||
_default_speed = 4800
|
_default_speed = 4800
|
||||||
|
self.options = options
|
||||||
|
|
||||||
# keep track of flow graph connections
|
# keep track of flow graph connections
|
||||||
self.cnxns = []
|
self.cnxns = []
|
||||||
|
@ -195,8 +166,6 @@ class p25_rx_block (gr.top_block):
|
||||||
|
|
||||||
self.constellation_scope_connected = False
|
self.constellation_scope_connected = False
|
||||||
|
|
||||||
self.options = options
|
|
||||||
|
|
||||||
for i in xrange(len(speeds)):
|
for i in xrange(len(speeds)):
|
||||||
if speeds[i] == _default_speed:
|
if speeds[i] == _default_speed:
|
||||||
self.current_speed = i
|
self.current_speed = i
|
||||||
|
@ -216,23 +185,25 @@ class p25_rx_block (gr.top_block):
|
||||||
# configure specified data source
|
# configure specified data source
|
||||||
if options.input:
|
if options.input:
|
||||||
self.open_file(options.input)
|
self.open_file(options.input)
|
||||||
elif options.frequency:
|
|
||||||
self.open_usrp()
|
|
||||||
elif options.audio_if:
|
elif options.audio_if:
|
||||||
self.open_audio_c(self.channel_rate, options.gain, options.audio_input)
|
self.open_audio_c(self.channel_rate, options.gain, options.audio_input)
|
||||||
elif options.audio:
|
elif options.audio:
|
||||||
self.open_audio(self.channel_rate, options.gain, options.audio_input)
|
self.open_audio(self.channel_rate, options.gain, options.audio_input)
|
||||||
elif options.ifile:
|
elif options.ifile:
|
||||||
self.open_ifile(self.channel_rate, options.gain, options.ifile, options.seek)
|
self.open_ifile(self.channel_rate, options.gain, options.ifile, options.seek)
|
||||||
|
elif (self.rtl_found or options.frequency):
|
||||||
|
self.open_usrp()
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# attach terminal thread
|
# attach terminal thread and make sure currently tuned frequency is displayed
|
||||||
self.terminal = curses_terminal(self.input_q, self.output_q)
|
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
|
# attach audio thread
|
||||||
if self.options.udp_player:
|
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:
|
else:
|
||||||
self.audio = None
|
self.audio = None
|
||||||
|
|
||||||
|
@ -241,72 +212,83 @@ class p25_rx_block (gr.top_block):
|
||||||
def __build_graph(self, source, capture_rate):
|
def __build_graph(self, source, capture_rate):
|
||||||
global speeds
|
global speeds
|
||||||
global WIRESHARK_PORT
|
global WIRESHARK_PORT
|
||||||
# tell the scope the source rate
|
|
||||||
|
sps = 5 # samples / symbol
|
||||||
|
|
||||||
self.rx_q = gr.msg_queue(100)
|
self.rx_q = gr.msg_queue(100)
|
||||||
udp_port = 0
|
udp_port = 0
|
||||||
|
|
||||||
if self.options.udp_player or self.options.wireshark or (self.options.wireshark_host != "127.0.0.1"):
|
vocoder = self.options.vocoder
|
||||||
udp_port = WIRESHARK_PORT
|
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.tdma_state = False
|
||||||
self.xor_cache = {}
|
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:
|
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
|
else: # complex input
|
||||||
# local osc
|
# 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:
|
if self.options.audio_if or self.options.ifile or self.options.input:
|
||||||
self.lo_freq += self.options.calibration
|
self.lo_freq += self.options.calibration
|
||||||
self.demod = p25_demodulator.p25_demod_cb( input_rate = capture_rate,
|
self.demod = p25_demodulator.p25_demod_cb( input_rate = capture_rate,
|
||||||
demod_type = self.options.demod_type,
|
demod_type = self.options.demod_type,
|
||||||
relative_freq = self.lo_freq,
|
relative_freq = self.lo_freq,
|
||||||
offset = self.options.offset,
|
offset = self.options.offset,
|
||||||
if_rate = 48000,
|
if_rate = sps * 4800,
|
||||||
gain_mu = self.options.gain_mu,
|
gain_mu = self.options.gain_mu,
|
||||||
costas_alpha = self.options.costas_alpha,
|
costas_alpha = self.options.costas_alpha,
|
||||||
|
excess_bw = self.options.excess_bw,
|
||||||
symbol_rate = self.symbol_rate)
|
symbol_rate = self.symbol_rate)
|
||||||
|
|
||||||
num_ambe = 0
|
num_ambe = 0
|
||||||
if self.options.phase2_tdma:
|
if self.options.phase2_tdma:
|
||||||
num_ambe = 1
|
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
|
# connect it all up
|
||||||
self.connect(source, self.demod, self.decoder)
|
self.connect(source, self.demod, self.decoder)
|
||||||
|
|
||||||
if self.options.plot_mode == 'constellation':
|
if self.baseband_input:
|
||||||
assert self.options.demod_type == 'cqpsk' ## constellation requires cqpsk demod-type
|
sps = int(capture_rate / 4800)
|
||||||
self.constellation_sink = constellation_sink_c()
|
plot_modes = []
|
||||||
self.demod.connect_complex('diffdec', self.constellation_sink)
|
if self.options.plot_mode is not None:
|
||||||
self.kill_sink = self.constellation_sink
|
plot_modes = self.options.plot_mode.split(',')
|
||||||
elif self.options.plot_mode == 'symbol':
|
for plot_mode in plot_modes:
|
||||||
self.symbol_sink = symbol_sink_f()
|
if plot_mode == 'constellation':
|
||||||
self.demod.connect_float(self.symbol_sink)
|
assert self.options.demod_type == 'cqpsk' ## constellation requires cqpsk demod-type
|
||||||
self.kill_sink = self.symbol_sink
|
sink = constellation_sink_c()
|
||||||
elif self.options.plot_mode == 'fft':
|
self.demod.connect_complex('diffdec', sink)
|
||||||
self.fft_sink = fft_sink_c()
|
elif plot_mode == 'symbol':
|
||||||
self.spectrum_decim = filter.rational_resampler_ccf(1, self.options.decim_amt)
|
sink = symbol_sink_f()
|
||||||
self.connect(self.spectrum_decim, self.fft_sink)
|
self.demod.connect_float(sink)
|
||||||
self.demod.connect_complex('src', self.spectrum_decim)
|
elif plot_mode == 'fft':
|
||||||
self.kill_sink = self.fft_sink
|
sink = fft_sink_c()
|
||||||
elif self.options.plot_mode == 'datascope':
|
self.spectrum_decim = filter.rational_resampler_ccf(1, self.options.decim_amt)
|
||||||
assert self.options.demod_type == 'fsk4' ## datascope requires fsk4 demod-type
|
self.connect(self.spectrum_decim, sink)
|
||||||
self.eye_sink = eye_sink_f(sps=10)
|
self.demod.connect_complex('src', self.spectrum_decim)
|
||||||
self.demod.connect_bb('symbol_filter', self.eye_sink)
|
elif plot_mode == 'mixer':
|
||||||
self.kill_sink = self.eye_sink
|
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:
|
if self.options.raw_symbols:
|
||||||
self.sink_sf = blocks.file_sink(gr.sizeof_char, 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 = p25_demodulator.p25_demod_cb(input_rate=capture_rate,
|
||||||
demod_type=self.options.demod_type,
|
demod_type=self.options.demod_type,
|
||||||
offset=self.options.offset)
|
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})
|
logfile_workers.append({'demod': demod, 'decoder': decoder, 'active': False})
|
||||||
self.connect(source, demod, decoder)
|
self.connect(source, demod, decoder)
|
||||||
|
|
||||||
|
@ -360,55 +342,100 @@ class p25_rx_block (gr.top_block):
|
||||||
|
|
||||||
def configure_tdma(self, params):
|
def configure_tdma(self, params):
|
||||||
if params['tdma'] is not None and not self.options.phase2_tdma:
|
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
|
return
|
||||||
set_tdma = False
|
set_tdma = False
|
||||||
if params['tdma'] is not None:
|
if params['tdma'] is not None:
|
||||||
set_tdma = True
|
set_tdma = True
|
||||||
|
self.decoder.set_slotid(params['tdma'])
|
||||||
if set_tdma == self.tdma_state:
|
if set_tdma == self.tdma_state:
|
||||||
return # already in desired state
|
return # already in desired state
|
||||||
self.tdma_state = set_tdma
|
self.tdma_state = set_tdma
|
||||||
if set_tdma:
|
if set_tdma:
|
||||||
self.decoder.set_slotid(params['tdma'])
|
|
||||||
hash = '%x%x%x' % (params['nac'], params['sysid'], params['wacn'])
|
hash = '%x%x%x' % (params['nac'], params['sysid'], params['wacn'])
|
||||||
if hash not in self.xor_cache:
|
if hash not in self.xor_cache:
|
||||||
self.xor_cache[hash] = lfsr.p25p2_lfsr(params['nac'], params['sysid'], params['wacn']).xor_chars
|
self.xor_cache[hash] = lfsr.p25p2_lfsr(params['nac'], params['sysid'], params['wacn']).xor_chars
|
||||||
self.decoder.set_xormask(self.xor_cache[hash], hash)
|
self.decoder.set_xormask(self.xor_cache[hash], hash)
|
||||||
sps = self.basic_rate / 6000
|
self.decoder.set_nac(params['nac'])
|
||||||
|
rate = 6000
|
||||||
else:
|
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))
|
self.demod.clock.set_omega(float(sps))
|
||||||
|
|
||||||
def change_freq(self, params):
|
def error_tracking(self):
|
||||||
freq = params['freq']
|
UPDATE_TIME = 3
|
||||||
offset = params['offset']
|
if self.last_error_update + UPDATE_TIME > time.time() \
|
||||||
center_freq = params['center_frequency']
|
or self.last_change_freq_at + UPDATE_TIME > time.time():
|
||||||
fine_tune = self.options.fine_tune
|
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:
|
def change_freq(self, params):
|
||||||
self.hamlib.set_freq(freq)
|
self.last_freq_params = params
|
||||||
elif params['center_frequency']:
|
freq = params['freq']
|
||||||
relative_freq = center_freq - freq
|
offset = self.options.offset
|
||||||
if abs(relative_freq + self.options.offset) > self.channel_rate / 2:
|
center_freq = params['center_frequency']
|
||||||
self.lo_freq = self.options.offset + self.options.fine_tune # relative tune not possible
|
self.error_tracking()
|
||||||
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
|
self.last_change_freq = freq
|
||||||
self.set_freq(freq + offset) # direct tune instead
|
self.last_change_freq_at = time.time()
|
||||||
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)
|
|
||||||
|
|
||||||
self.configure_tdma(params)
|
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['json_type'] = 'change_freq'
|
||||||
|
params['fine_tune'] = self.options.fine_tune
|
||||||
js = json.dumps(params)
|
js = json.dumps(params)
|
||||||
msg = gr.message().make_from_string(js, -4, 0, 0)
|
msg = gr.message().make_from_string(js, -4, 0, 0)
|
||||||
self.input_q.insert_tail(msg)
|
self.input_q.insert_tail(msg)
|
||||||
|
@ -440,7 +467,8 @@ class p25_rx_block (gr.top_block):
|
||||||
|
|
||||||
def set_audio_scaler(self, vol):
|
def set_audio_scaler(self, vol):
|
||||||
#print 'audio scaler: %f' % ((1 / 32768.0) * (vol * 0.1))
|
#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):
|
def set_rtl_ppm(self, ppm):
|
||||||
self.src.set_freq_corr(ppm)
|
self.src.set_freq_corr(ppm)
|
||||||
|
@ -462,11 +490,12 @@ class p25_rx_block (gr.top_block):
|
||||||
"""
|
"""
|
||||||
if not self.src:
|
if not self.src:
|
||||||
return False
|
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)
|
r = self.src.set_center_freq(tune_freq)
|
||||||
|
|
||||||
if self.fft_sink:
|
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)
|
self.fft_sink.set_width(self.options.sample_rate)
|
||||||
|
|
||||||
if r:
|
if r:
|
||||||
|
@ -474,6 +503,8 @@ class p25_rx_block (gr.top_block):
|
||||||
#if self.show_debug_info:
|
#if self.show_debug_info:
|
||||||
# self.myform['baseband'].set_value(r.baseband_freq)
|
# self.myform['baseband'].set_value(r.baseband_freq)
|
||||||
# self.myform['ddc'].set_value(r.dxc_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 True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -509,10 +540,9 @@ class p25_rx_block (gr.top_block):
|
||||||
self.src.set_antenna(self.options.antenna)
|
self.src.set_antenna(self.options.antenna)
|
||||||
self.info["capture-rate"] = capture_rate
|
self.info["capture-rate"] = capture_rate
|
||||||
self.src.set_bandwidth(capture_rate)
|
self.src.set_bandwidth(capture_rate)
|
||||||
r = self.src.set_center_freq(self.options.frequency + self.options.calibration+ self.options.offset)
|
r = self.src.set_center_freq(self.options.frequency + self.options.calibration+ self.options.offset + self.options.fine_tune)
|
||||||
print 'set_center_freq: %d' % r
|
|
||||||
if not r:
|
if not r:
|
||||||
raise RuntimeError("failed to set USRP frequency")
|
sys.stderr.write("__set_rx_from_osmosdr(): failed to set frequency\n")
|
||||||
# capture file
|
# capture file
|
||||||
# if preserve:
|
# if preserve:
|
||||||
if 0:
|
if 0:
|
||||||
|
@ -534,15 +564,6 @@ class p25_rx_block (gr.top_block):
|
||||||
pickle.dump(self.info, f)
|
pickle.dump(self.info, f)
|
||||||
f.close()
|
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):
|
def open_ifile(self, capture_rate, gain, input_filename, file_seek):
|
||||||
speed = 96000 # TODO: fixme
|
speed = 96000 # TODO: fixme
|
||||||
ifile = blocks.file_source(gr.sizeof_gr_complex, input_filename, 1)
|
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-dev": "USRP",
|
||||||
"source-decim": 1 }
|
"source-decim": 1 }
|
||||||
self.__set_rx_from_osmosdr()
|
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:
|
# except Exception, x:
|
||||||
# wx.MessageBox("Cannot open USRP: " + x.message, "USRP Error", wx.CANCEL | wx.ICON_EXCLAMATION)
|
# wx.MessageBox("Cannot open USRP: " + x.message, "USRP Error", wx.CANCEL | wx.ICON_EXCLAMATION)
|
||||||
|
|
||||||
# Set the channel offset
|
def is_http_term(self):
|
||||||
#
|
if self.options.terminal_type.startswith('http:'):
|
||||||
def set_channel_offset(self, offset_hz, scale, units):
|
return True
|
||||||
self.channel_offset = -offset_hz
|
elif self.options.terminal_type.startswith('zmq:'):
|
||||||
self.channel_filter.set_center_freq(self.channel_offset+ self.options.offset)
|
return True
|
||||||
self.frame.SetStatusText("Channel offset: " + str(offset_hz * scale) + units, 1)
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
# Set the RF squelch threshold level
|
def process_ajax(self):
|
||||||
#
|
if not self.is_http_term():
|
||||||
def set_squelch_threshold(self, squelch_db):
|
return
|
||||||
self.squelch.set_threshold(squelch_db)
|
filenames = [sink.gnuplot.filename for sink in self.plot_sinks if sink.gnuplot.filename]
|
||||||
self.frame.SetStatusText("Squelch: " + str(squelch_db) + "dB", 2)
|
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):
|
def process_qmsg(self, msg):
|
||||||
# return true = end top block
|
# return true = end top block
|
||||||
RX_COMMANDS = 'skip lockout hold'
|
RX_COMMANDS = 'skip lockout hold'.split()
|
||||||
s = msg.to_string()
|
s = msg.to_string()
|
||||||
|
t = msg.type()
|
||||||
|
if t == -4:
|
||||||
|
d = json.loads(s)
|
||||||
|
s = d['command']
|
||||||
if s == 'quit': return True
|
if s == 'quit': return True
|
||||||
elif s == 'update':
|
elif s == 'update':
|
||||||
|
self.freq_update()
|
||||||
if self.trunk_rx is None:
|
if self.trunk_rx is None:
|
||||||
return False ## possible race cond - just ignore
|
return False ## possible race cond - just ignore
|
||||||
js = self.trunk_rx.to_json()
|
js = self.trunk_rx.to_json()
|
||||||
msg = gr.message().make_from_string(js, -4, 0, 0)
|
msg = gr.message().make_from_string(js, -4, 0, 0)
|
||||||
self.input_q.insert_tail(msg)
|
self.input_q.insert_tail(msg)
|
||||||
|
self.process_ajax()
|
||||||
elif s == 'set_freq':
|
elif s == 'set_freq':
|
||||||
freq = msg.arg1()
|
freq = msg.arg1()
|
||||||
|
self.last_freq_params['freq'] = freq
|
||||||
self.set_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':
|
elif s == 'add_default_config':
|
||||||
nac = msg.arg1()
|
nac = msg.arg1()
|
||||||
self.trunk_rx.add_default_config(nac)
|
self.trunk_rx.add_default_config(int(nac))
|
||||||
elif s in RX_COMMANDS:
|
elif s in RX_COMMANDS:
|
||||||
self.rx_q.insert_tail(msg)
|
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
|
return False
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
|
@ -644,26 +687,88 @@ class du_queue_watcher(threading.Thread):
|
||||||
def run(self):
|
def run(self):
|
||||||
while(self.keep_running):
|
while(self.keep_running):
|
||||||
msg = self.msgq.delete_head()
|
msg = self.msgq.delete_head()
|
||||||
|
if not self.keep_running:
|
||||||
|
break
|
||||||
self.callback(msg)
|
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
|
# Start the receiver
|
||||||
#
|
#
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
tb = p25_rx_block()
|
rx = rx_main()
|
||||||
tb.start()
|
rx.run()
|
||||||
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()
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#!/usr/bin/env python
|
#!/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
|
# This file is part of OP25
|
||||||
#
|
#
|
||||||
|
@ -23,12 +25,14 @@ from ctypes import *
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
import select
|
||||||
import socket
|
import socket
|
||||||
import errno
|
import errno
|
||||||
|
import struct
|
||||||
|
|
||||||
# OP25 defaults
|
# OP25 defaults
|
||||||
PCM_RATE = 8000 # audio sample rate (Hz)
|
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
|
MAX_SUPERFRAME_SIZE = 320 # maximum size of incoming UDP audio buffer
|
||||||
|
|
||||||
|
@ -128,7 +132,7 @@ class alsasound(object):
|
||||||
self.format = pcm_format
|
self.format = pcm_format
|
||||||
self.channels = pcm_channels
|
self.channels = pcm_channels
|
||||||
self.rate = pcm_rate
|
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)))()
|
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)
|
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)
|
sys.stderr.write("hw_params_any failed: %d\n" % err)
|
||||||
return 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:
|
if err < 0:
|
||||||
sys.stderr.write("set_access failed: %d\n" % err)
|
sys.stderr.write("set_access failed: %d\n" % err)
|
||||||
return 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:
|
if err < 0:
|
||||||
sys.stderr.write("set_format failed: %d\n" % err)
|
sys.stderr.write("set_format failed: %d\n" % err)
|
||||||
return 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:
|
if err < 0:
|
||||||
sys.stderr.write("set_channels failed: %d\n" % err)
|
sys.stderr.write("set_channels failed: %d\n" % err)
|
||||||
return 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:
|
if err < 0:
|
||||||
sys.stderr.write("set_rate failed: %d\n" % err)
|
sys.stderr.write("set_rate failed: %d\n" % err)
|
||||||
return 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:
|
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
|
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:
|
if err < 0:
|
||||||
sys.stderr.write("hw_params failed: %d\n" % err)
|
sys.stderr.write("hw_params failed: %d\n" % err)
|
||||||
return err
|
return err
|
||||||
|
|
||||||
self.libasound.snd_pcm_hw_params_current(self.c_pcm, c_pars)
|
self.libasound.snd_pcm_hw_params_current(self.c_pcm, c_pars)
|
||||||
c_bits = self.libasound.snd_pcm_hw_params_get_sbits(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)))()
|
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)
|
err = self.libasound.snd_pcm_sw_params_current(self.c_pcm, c_sw_pars)
|
||||||
if err < 0:
|
if err < 0:
|
||||||
sys.stderr.write("get_sw_params_current failed: %d\n" % err)
|
sys.stderr.write("get_sw_params_current failed: %d\n" % err)
|
||||||
return 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))
|
err = self.libasound.snd_pcm_sw_params_set_start_threshold(self.c_pcm, c_sw_pars, c_uint(pcm_start_threshold))
|
||||||
if err < 0:
|
if err < 0:
|
||||||
sys.stderr.write("set_sw_params_start_threshold failed: %d\n" % err)
|
sys.stderr.write("set_sw_params_start_threshold failed: %d\n" % err)
|
||||||
|
@ -197,7 +204,7 @@ class alsasound(object):
|
||||||
if (ret < 0):
|
if (ret < 0):
|
||||||
if (ret == -errno.EPIPE): # underrun
|
if (ret == -errno.EPIPE): # underrun
|
||||||
if (LOG_AUDIO_XRUNS):
|
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)
|
ret = self.libasound.snd_pcm_recover(self.c_pcm, ret, 1)
|
||||||
if (ret >= 0):
|
if (ret >= 0):
|
||||||
ret = self.libasound.snd_pcm_writei(self.c_pcm, cast(c_data, POINTER(c_void_p)), n_frames)
|
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)
|
time.sleep(1)
|
||||||
ret = self.libasound.snd_pcm_prepare(self.c_pcm)
|
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):
|
def dump(self):
|
||||||
if (self.c_pcm.value == None):
|
if (self.c_pcm.value == None):
|
||||||
return
|
return
|
||||||
|
@ -242,49 +259,117 @@ class alsasound(object):
|
||||||
|
|
||||||
# OP25 thread to receive UDP audio samples and send to Alsa driver
|
# OP25 thread to receive UDP audio samples and send to Alsa driver
|
||||||
class socket_audio(threading.Thread):
|
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)
|
threading.Thread.__init__(self, **kwds)
|
||||||
self.setDaemon(1)
|
self.setDaemon(1)
|
||||||
self.keep_running = True
|
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.pcm = alsasound()
|
||||||
self.setup_socket(udp_host, udp_port)
|
self.setup_sockets(udp_host, udp_port)
|
||||||
self.setup_pcm(pcm_device)
|
self.setup_pcm(pcm_device)
|
||||||
self.start()
|
self.start()
|
||||||
return
|
return
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while self.keep_running:
|
while self.keep_running:
|
||||||
# Data received on the udp port is 320 bytes for an audio frame or 2 bytes for a flag
|
readable, writable, exceptional = select.select( [self.sock_a, self.sock_b], [], [self.sock_a, self.sock_b] )
|
||||||
#
|
in_a = None
|
||||||
d = self.sock.recvfrom(MAX_SUPERFRAME_SIZE) # recvfrom blocks until data becomes available
|
in_b = None
|
||||||
if d[0]:
|
data_a = ""
|
||||||
d_len = len(d[0])
|
data_b = ""
|
||||||
if (d_len == 2): # flag
|
flag_a = -1
|
||||||
flag = ord(d[0][0]) + (ord(d[0][1]) << 8) # 16 bit little endian
|
flag_b = -1
|
||||||
if (flag == 0): # 0x0000 = drain pcm buffer
|
|
||||||
self.pcm.drain()
|
|
||||||
else: # audio data
|
|
||||||
self.pcm.write(d[0])
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
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()
|
self.close_pcm()
|
||||||
return
|
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):
|
def stop(self):
|
||||||
self.keep_running = False
|
self.keep_running = False
|
||||||
return
|
return
|
||||||
|
|
||||||
def setup_socket(self, udp_host, udp_port):
|
def setup_sockets(self, udp_host, udp_port):
|
||||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
self.sock_a = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
self.sock.bind((udp_host, udp_port))
|
self.sock_b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
sys.stderr.write('setup_socket: %d\n' % udp_port)
|
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
|
return
|
||||||
|
|
||||||
def close_socket(self):
|
def close_sockets(self):
|
||||||
self.sock.close()
|
self.sock_a.close()
|
||||||
|
self.sock_b.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
def setup_pcm(self, hwdevice):
|
def setup_pcm(self, hwdevice):
|
||||||
|
@ -296,7 +381,7 @@ class socket_audio(threading.Thread):
|
||||||
self.keep_running = False
|
self.keep_running = False
|
||||||
return
|
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:
|
if err < 0:
|
||||||
sys.stderr.write('failed to set up pcm stream\n')
|
sys.stderr.write('failed to set up pcm stream\n')
|
||||||
self.keep_running = False
|
self.keep_running = False
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Copyright 2008-2011 Steve Glass
|
# 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
|
# This file is part of OP25
|
||||||
#
|
#
|
||||||
|
@ -28,11 +28,28 @@ import time
|
||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
import socket
|
||||||
|
|
||||||
from gnuradio import gr
|
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):
|
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)
|
threading.Thread.__init__ (self, **kwds)
|
||||||
self.setDaemon(1)
|
self.setDaemon(1)
|
||||||
self.input_q = input_q
|
self.input_q = input_q
|
||||||
|
@ -41,31 +58,83 @@ class curses_terminal(threading.Thread):
|
||||||
self.last_update = 0
|
self.last_update = 0
|
||||||
self.auto_update = True
|
self.auto_update = True
|
||||||
self.current_nac = None
|
self.current_nac = None
|
||||||
|
self.maxx = 0
|
||||||
|
self.maxy = 0
|
||||||
|
self.sock = sock
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def setup_curses(self):
|
def setup_curses(self):
|
||||||
self.stdscr = curses.initscr()
|
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.noecho()
|
||||||
curses.halfdelay(1)
|
curses.halfdelay(1)
|
||||||
|
|
||||||
self.top_bar = curses.newwin(1, 80, 0, 0)
|
self.title_bar = curses.newwin(1, self.maxx, 0, 0)
|
||||||
self.freq_list = curses.newwin(20, 80, 1, 0)
|
self.help_bar = curses.newwin(1, self.maxx, self.maxy-1, 0)
|
||||||
self.active1 = curses.newwin(1, 80, 21, 0)
|
self.top_bar = curses.newwin(1, self.maxx, 1, 0)
|
||||||
self.active2 = curses.newwin(1, 80, 22, 0)
|
self.freq_list = curses.newwin(self.maxy-5, self.maxx, 2, 0)
|
||||||
self.prompt = curses.newwin(1, 10, 23, 0)
|
self.active1 = curses.newwin(1, self.maxx-15, self.maxy-3, 0)
|
||||||
self.text_win = curses.newwin(1, 70, 23, 10)
|
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.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:
|
try:
|
||||||
curses.endwin()
|
curses.endwin()
|
||||||
except:
|
except:
|
||||||
pass
|
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):
|
def do_auto_update(self):
|
||||||
UPDATE_INTERVAL = 1 # sec.
|
UPDATE_INTERVAL = 0.5 # sec.
|
||||||
if not self.auto_update:
|
if not self.auto_update:
|
||||||
return False
|
return False
|
||||||
if self.last_update + UPDATE_INTERVAL > time.time():
|
if self.last_update + UPDATE_INTERVAL > time.time():
|
||||||
|
@ -75,32 +144,33 @@ class curses_terminal(threading.Thread):
|
||||||
|
|
||||||
def process_terminal_events(self):
|
def process_terminal_events(self):
|
||||||
# return true signifies end of main event loop
|
# 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_S = ord('s')
|
||||||
_ORD_L = ord('l')
|
_ORD_L = ord('l')
|
||||||
_ORD_H = ord('h')
|
_ORD_H = ord('h')
|
||||||
COMMANDS = {_ORD_S: 'skip', _ORD_L: 'lockout', _ORD_H: 'hold'}
|
COMMANDS = {_ORD_S: 'skip', _ORD_L: 'lockout', _ORD_H: 'hold'}
|
||||||
c = self.stdscr.getch()
|
c = self.stdscr.getch()
|
||||||
if c == ord('u') or self.do_auto_update():
|
if c == ord('u') or self.do_auto_update():
|
||||||
msg = gr.message().make_from_string('update', -2, 0, 0)
|
self.send_command('update', 0)
|
||||||
self.output_q.insert_tail(msg)
|
|
||||||
if c in COMMANDS.keys():
|
if c in COMMANDS.keys():
|
||||||
msg = gr.message().make_from_string(COMMANDS[c], -2, 0, 0)
|
self.send_command(COMMANDS[c], 0)
|
||||||
self.output_q.insert_tail(msg)
|
|
||||||
elif c == ord('q'):
|
elif c == ord('q'):
|
||||||
return True
|
return True
|
||||||
elif c == ord('t'):
|
elif c == ord('t'):
|
||||||
if self.current_nac:
|
if self.current_nac:
|
||||||
msg = gr.message().make_from_string('add_default_config', -2, int(self.current_nac), 0)
|
self.send_command('add_default_config', int(self.current_nac))
|
||||||
self.output_q.insert_tail(msg)
|
|
||||||
elif c == ord('f'):
|
elif c == ord('f'):
|
||||||
self.prompt.addstr(0, 0, 'Frequency')
|
self.prompt.addstr(0, 0, 'Frequency')
|
||||||
self.prompt.refresh()
|
self.prompt.refresh()
|
||||||
self.text_win.clear()
|
self.text_win.erase()
|
||||||
response = self.textpad.edit()
|
response = self.textpad.edit()
|
||||||
self.prompt.clear()
|
self.prompt.erase()
|
||||||
self.prompt.refresh()
|
self.prompt.refresh()
|
||||||
self.text_win.clear()
|
self.text_win.erase()
|
||||||
self.text_win.refresh()
|
self.text_win.refresh()
|
||||||
|
self.title_help()
|
||||||
try:
|
try:
|
||||||
freq = float(response)
|
freq = float(response)
|
||||||
if freq < 10000:
|
if freq < 10000:
|
||||||
|
@ -108,8 +178,19 @@ class curses_terminal(threading.Thread):
|
||||||
except:
|
except:
|
||||||
freq = None
|
freq = None
|
||||||
if freq:
|
if freq:
|
||||||
msg = gr.message().make_from_string('set_freq', -2, freq, 0)
|
self.send_command('set_freq', freq)
|
||||||
self.output_q.insert_tail(msg)
|
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'):
|
elif c == ord('x'):
|
||||||
assert 1 == 0
|
assert 1 == 0
|
||||||
return False
|
return False
|
||||||
|
@ -118,11 +199,14 @@ class curses_terminal(threading.Thread):
|
||||||
# return true signifies end of main event loop
|
# return true signifies end of main event loop
|
||||||
msg = json.loads(js)
|
msg = json.loads(js)
|
||||||
if msg['json_type'] == 'trunk_update':
|
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:
|
if not nacs:
|
||||||
return
|
return
|
||||||
times = {msg[nac]['last_tsbk']:nac for nac in nacs}
|
if msg.get('nac'):
|
||||||
current_nac = times[ sorted(times.keys(), reverse=True)[0] ]
|
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
|
self.current_nac = current_nac
|
||||||
s = 'NAC 0x%x' % (int(current_nac))
|
s = 'NAC 0x%x' % (int(current_nac))
|
||||||
s += ' WACN 0x%x' % (msg[current_nac]['wacn'])
|
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 += '/%f' % (msg[current_nac]['txchan']/ 1000000.0)
|
||||||
s += ' tsbks %d' % (msg[current_nac]['tsbks'])
|
s += ' tsbks %d' % (msg[current_nac]['tsbks'])
|
||||||
freqs = sorted(msg[current_nac]['frequencies'].keys())
|
freqs = sorted(msg[current_nac]['frequencies'].keys())
|
||||||
s = s[:79]
|
s = s[:(self.maxx - 1)]
|
||||||
self.top_bar.clear()
|
self.top_bar.erase()
|
||||||
self.top_bar.addstr(0, 0, s)
|
self.top_bar.addstr(0, 0, s)
|
||||||
self.top_bar.refresh()
|
self.top_bar.refresh()
|
||||||
self.freq_list.clear()
|
self.freq_list.erase()
|
||||||
for i in xrange(len(freqs)):
|
for i in xrange(len(freqs)):
|
||||||
|
if i > (self.maxy - 6):
|
||||||
|
break
|
||||||
s=msg[current_nac]['frequencies'][freqs[i]]
|
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.addstr(i, 0, s)
|
||||||
self.freq_list.refresh()
|
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()
|
self.stdscr.refresh()
|
||||||
elif msg['json_type'] == 'change_freq':
|
elif msg['json_type'] == 'change_freq':
|
||||||
s = 'Frequency %f' % (msg['freq'] / 1000000.0)
|
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:
|
if msg['tgid'] is not None:
|
||||||
s += ' Talkgroup ID %s' % (msg['tgid'])
|
s += ' Talkgroup ID %s' % (msg['tgid'])
|
||||||
if msg['tdma'] is not None:
|
if msg['tdma'] is not None:
|
||||||
s += ' TDMA Slot %s' % msg['tdma']
|
s += ' TDMA Slot %s' % msg['tdma']
|
||||||
self.active1.clear()
|
s = s[:(self.maxx - 16)]
|
||||||
self.active2.clear()
|
self.active1.erase()
|
||||||
|
self.active2.erase()
|
||||||
self.active1.addstr(0, 0, s)
|
self.active1.addstr(0, 0, s)
|
||||||
self.active1.refresh()
|
self.active1.refresh()
|
||||||
if msg['tag']:
|
if msg['tag']:
|
||||||
s = msg['tag']
|
s = msg['tag']
|
||||||
s = s[:79]
|
s = s[:(self.maxx - 16)]
|
||||||
self.active2.addstr(0, 0, s)
|
self.active2.addstr(0, 0, s)
|
||||||
self.active2.refresh()
|
self.active2.refresh()
|
||||||
self.stdscr.refresh()
|
self.stdscr.refresh()
|
||||||
|
@ -163,6 +267,8 @@ class curses_terminal(threading.Thread):
|
||||||
def process_q_events(self):
|
def process_q_events(self):
|
||||||
# return true signifies end of main event loop
|
# return true signifies end of main event loop
|
||||||
while True:
|
while True:
|
||||||
|
if curses.is_term_resized(self.maxy, self.maxx) is True:
|
||||||
|
self.resize_curses()
|
||||||
if self.input_q.empty_p():
|
if self.input_q.empty_p():
|
||||||
break
|
break
|
||||||
msg = self.input_q.delete_head_nowait()
|
msg = self.input_q.delete_head_nowait()
|
||||||
|
@ -170,18 +276,187 @@ class curses_terminal(threading.Thread):
|
||||||
return self.process_json(msg.to_string())
|
return self.process_json(msg.to_string())
|
||||||
return False
|
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):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
self.setup_curses()
|
self.setup_curses()
|
||||||
|
|
||||||
while(self.keep_running):
|
while(self.keep_running):
|
||||||
if self.process_terminal_events():
|
if self.process_terminal_events():
|
||||||
break
|
break
|
||||||
if self.process_q_events():
|
if self.process_q_events():
|
||||||
break
|
break
|
||||||
except:
|
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())
|
sys.stderr.write('terminal: exception:\n%s\n' % traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
self.end_curses()
|
self.end_terminal()
|
||||||
msg = gr.message().make_from_string('quit', -2, 0, 0)
|
self.keep_running = False
|
||||||
self.output_q.insert_tail(msg)
|
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
|
# This file is part of OP25
|
||||||
#
|
#
|
||||||
|
@ -25,6 +26,7 @@ import collections
|
||||||
import json
|
import json
|
||||||
sys.path.append('tdma')
|
sys.path.append('tdma')
|
||||||
import lfsr
|
import lfsr
|
||||||
|
from tsvfile import make_config, load_tsv
|
||||||
|
|
||||||
def crc16(dat,len): # slow version
|
def crc16(dat,len): # slow version
|
||||||
poly = (1<<12) + (1<<5) + (1<<0)
|
poly = (1<<12) + (1<<5) + (1<<0)
|
||||||
|
@ -39,12 +41,6 @@ def crc16(dat,len): # slow version
|
||||||
crc = crc ^ 0xffff
|
crc = crc ^ 0xffff
|
||||||
return crc
|
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):
|
class trunked_system (object):
|
||||||
def __init__(self, debug=0, config=None):
|
def __init__(self, debug=0, config=None):
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
|
@ -55,6 +51,7 @@ class trunked_system (object):
|
||||||
self.tsbk_cache = {}
|
self.tsbk_cache = {}
|
||||||
self.secondary = {}
|
self.secondary = {}
|
||||||
self.adjacent = {}
|
self.adjacent = {}
|
||||||
|
self.adjacent_data = {}
|
||||||
self.rfss_syid = 0
|
self.rfss_syid = 0
|
||||||
self.rfss_rfid = 0
|
self.rfss_rfid = 0
|
||||||
self.rfss_stid = 0
|
self.rfss_stid = 0
|
||||||
|
@ -71,6 +68,7 @@ class trunked_system (object):
|
||||||
self.sysname = 0
|
self.sysname = 0
|
||||||
|
|
||||||
self.trunk_cc = 0
|
self.trunk_cc = 0
|
||||||
|
self.last_trunk_cc = 0
|
||||||
self.cc_list = []
|
self.cc_list = []
|
||||||
self.cc_list_index = 0
|
self.cc_list_index = 0
|
||||||
self.CC_HUNT_TIME = 5.0
|
self.CC_HUNT_TIME = 5.0
|
||||||
|
@ -90,9 +88,16 @@ class trunked_system (object):
|
||||||
self.center_frequency = config['center_frequency']
|
self.center_frequency = config['center_frequency']
|
||||||
self.modulation = config['modulation']
|
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):
|
def to_json(self):
|
||||||
d = {}
|
d = {}
|
||||||
d['syid'] = self.rfss_syid
|
d['syid'] = self.rfss_syid
|
||||||
|
d['sysname'] = self.sysname
|
||||||
d['rfid'] = self.rfss_rfid
|
d['rfid'] = self.rfss_rfid
|
||||||
d['stid'] = self.rfss_stid
|
d['stid'] = self.rfss_stid
|
||||||
d['sysid'] = self.ns_syid
|
d['sysid'] = self.ns_syid
|
||||||
|
@ -102,12 +107,20 @@ class trunked_system (object):
|
||||||
d['secondary'] = self.secondary.keys()
|
d['secondary'] = self.secondary.keys()
|
||||||
d['tsbks'] = self.stats['tsbks']
|
d['tsbks'] = self.stats['tsbks']
|
||||||
d['frequencies'] = {}
|
d['frequencies'] = {}
|
||||||
|
d['frequency_data'] = {}
|
||||||
d['last_tsbk'] = self.last_tsbk
|
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()
|
t = time.time()
|
||||||
for f in self.voice_frequencies.keys():
|
for f in self.voice_frequencies.keys():
|
||||||
tgs = '%s %s' % (self.voice_frequencies[f]['tgid'][0], self.voice_frequencies[f]['tgid'][1])
|
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['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)
|
return json.dumps(d)
|
||||||
|
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
|
@ -162,23 +175,38 @@ class trunked_system (object):
|
||||||
return ""
|
return ""
|
||||||
if tgid not in self.tgid_map:
|
if tgid not in self.tgid_map:
|
||||||
return "Talkgroup ID %d [0x%x]" % (tgid, tgid)
|
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:
|
if tgid not in self.talkgroups:
|
||||||
self.talkgroups[tgid] = {'counter':0}
|
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]['time'] = time.time()
|
||||||
self.talkgroups[tgid]['frequency'] = frequency
|
self.talkgroups[tgid]['frequency'] = frequency
|
||||||
self.talkgroups[tgid]['tdma_slot'] = tdma_slot
|
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
|
if not frequency: # e.g., channel identifier not yet known
|
||||||
return
|
return
|
||||||
self.update_talkgroup(frequency, tgid, tdma_slot)
|
self.update_talkgroup(frequency, tgid, tdma_slot, srcaddr)
|
||||||
if frequency not in self.voice_frequencies:
|
if frequency not in self.voice_frequencies:
|
||||||
self.voice_frequencies[frequency] = {'counter':0}
|
self.voice_frequencies[frequency] = {'counter':0}
|
||||||
sorted_freqs = collections.OrderedDict(sorted(self.voice_frequencies.items()))
|
sorted_freqs = collections.OrderedDict(sorted(self.voice_frequencies.items()))
|
||||||
self.voice_frequencies = sorted_freqs
|
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:
|
if tdma_slot is None:
|
||||||
tdma_slot = 0
|
tdma_slot = 0
|
||||||
if 'tgid' not in self.voice_frequencies[frequency]:
|
if 'tgid' not in self.voice_frequencies[frequency]:
|
||||||
|
@ -200,11 +228,16 @@ class trunked_system (object):
|
||||||
for tg in expired_tgs:
|
for tg in expired_tgs:
|
||||||
self.blacklist.pop(tg)
|
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)
|
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:
|
for active_tgid in self.talkgroups:
|
||||||
|
if hold:
|
||||||
|
break
|
||||||
if self.talkgroups[active_tgid]['time'] < start_time:
|
if self.talkgroups[active_tgid]['time'] < start_time:
|
||||||
continue
|
continue
|
||||||
if active_tgid in self.blacklist:
|
if active_tgid in self.blacklist:
|
||||||
|
@ -213,31 +246,42 @@ class trunked_system (object):
|
||||||
continue
|
continue
|
||||||
if self.talkgroups[active_tgid]['tdma_slot'] is not None and (self.ns_syid < 0 or self.ns_wacn < 0):
|
if self.talkgroups[active_tgid]['tdma_slot'] is not None and (self.ns_syid < 0 or self.ns_wacn < 0):
|
||||||
continue
|
continue
|
||||||
if tgid is None:
|
if tgt_tgid is None:
|
||||||
return self.talkgroups[active_tgid]['frequency'], active_tgid, self.talkgroups[active_tgid]['tdma_slot']
|
tgt_tgid = active_tgid
|
||||||
return None, None, None
|
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):
|
def add_blacklist(self, tgid, end_time=None):
|
||||||
if not tgid:
|
if not tgid:
|
||||||
return
|
return
|
||||||
self.blacklist[tgid] = end_time
|
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.cc_timeouts = 0
|
||||||
self.last_tsbk = time.time()
|
self.last_tsbk = time.time()
|
||||||
updated = 0
|
updated = 0
|
||||||
if self.debug > 10:
|
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
|
if opcode == 0x0: # grp voice channel grant
|
||||||
ch1 = (mbt_data >> 64) & 0xffff
|
ch1 = (mbt_data >> 64) & 0xffff
|
||||||
ch2 = (mbt_data >> 48) & 0xffff
|
ch2 = (mbt_data >> 48) & 0xffff
|
||||||
ga = (mbt_data >> 32) & 0xffff
|
ga = (mbt_data >> 32) & 0xffff
|
||||||
f = self.channel_id_to_frequency(ch1)
|
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:
|
if f:
|
||||||
updated += 1
|
updated += 1
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x3c: # adjacent status
|
||||||
syid = (header >> 48) & 0xfff
|
syid = (header >> 48) & 0xfff
|
||||||
rfid = (header >> 24) & 0xff
|
rfid = (header >> 24) & 0xff
|
||||||
|
@ -248,8 +292,9 @@ class trunked_system (object):
|
||||||
f2 = self.channel_id_to_frequency(ch2)
|
f2 = self.channel_id_to_frequency(ch2)
|
||||||
if f1 and f2:
|
if f1 and f2:
|
||||||
self.adjacent[f1] = 'rfid: %d stid:%d uplink:%f' % (rfid, stid, f2 / 1000000.0)
|
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:
|
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
|
elif opcode == 0x3b: # network status
|
||||||
syid = (header >> 48) & 0xfff
|
syid = (header >> 48) & 0xfff
|
||||||
wacn = (mbt_data >> 76) & 0xfffff
|
wacn = (mbt_data >> 76) & 0xfffff
|
||||||
|
@ -262,7 +307,7 @@ class trunked_system (object):
|
||||||
self.ns_wacn = wacn
|
self.ns_wacn = wacn
|
||||||
self.ns_chan = f1
|
self.ns_chan = f1
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x3a: # rfss status
|
||||||
syid = (header >> 48) & 0xfff
|
syid = (header >> 48) & 0xfff
|
||||||
rfid = (mbt_data >> 88) & 0xff
|
rfid = (mbt_data >> 88) & 0xff
|
||||||
|
@ -278,9 +323,9 @@ class trunked_system (object):
|
||||||
self.rfss_chan = f1
|
self.rfss_chan = f1
|
||||||
self.rfss_txchan = f2
|
self.rfss_txchan = f2
|
||||||
if self.debug > 10:
|
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))
|
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:
|
else:
|
||||||
# print "mbt other %x" % opcode
|
sys.stderr.write('decode_mbt_data(): received unsupported mbt opcode %x\n' % opcode)
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
def decode_tsbk(self, tsbk):
|
def decode_tsbk(self, tsbk):
|
||||||
|
@ -288,13 +333,10 @@ class trunked_system (object):
|
||||||
self.last_tsbk = time.time()
|
self.last_tsbk = time.time()
|
||||||
self.stats['tsbks'] += 1
|
self.stats['tsbks'] += 1
|
||||||
updated = 0
|
updated = 0
|
||||||
#if crc16(tsbk, 12) != 0:
|
|
||||||
# self.stats['crc'] += 1
|
|
||||||
# return # crc check failed
|
|
||||||
tsbk = tsbk << 16 # for missing crc
|
tsbk = tsbk << 16 # for missing crc
|
||||||
opcode = (tsbk >> 88) & 0x3f
|
opcode = (tsbk >> 88) & 0x3f
|
||||||
if self.debug > 10:
|
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
|
if opcode == 0x00: # group voice chan grant
|
||||||
mfrid = (tsbk >> 80) & 0xff
|
mfrid = (tsbk >> 80) & 0xff
|
||||||
if mfrid == 0x90: # MOT_GRG_ADD_CMD
|
if mfrid == 0x90: # MOT_GRG_ADD_CMD
|
||||||
|
@ -303,18 +345,18 @@ class trunked_system (object):
|
||||||
ga2 = (tsbk >> 32) & 0xffff
|
ga2 = (tsbk >> 32) & 0xffff
|
||||||
ga3 = (tsbk >> 16) & 0xffff
|
ga3 = (tsbk >> 16) & 0xffff
|
||||||
if self.debug > 10:
|
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:
|
else:
|
||||||
opts = (tsbk >> 72) & 0xff
|
opts = (tsbk >> 72) & 0xff
|
||||||
ch = (tsbk >> 56) & 0xffff
|
ch = (tsbk >> 56) & 0xffff
|
||||||
ga = (tsbk >> 40) & 0xffff
|
ga = (tsbk >> 40) & 0xffff
|
||||||
sa = (tsbk >> 16) & 0xffffff
|
sa = (tsbk >> 16) & 0xffffff
|
||||||
f = self.channel_id_to_frequency(ch)
|
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:
|
if f:
|
||||||
updated += 1
|
updated += 1
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x01: # reserved
|
||||||
mfrid = (tsbk >> 80) & 0xff
|
mfrid = (tsbk >> 80) & 0xff
|
||||||
if mfrid == 0x90: #MOT_GRG_DEL_CMD
|
if mfrid == 0x90: #MOT_GRG_DEL_CMD
|
||||||
|
@ -323,7 +365,7 @@ class trunked_system (object):
|
||||||
ga2 = (tsbk >> 32) & 0xffff
|
ga2 = (tsbk >> 32) & 0xffff
|
||||||
ga3 = (tsbk >> 16) & 0xffff
|
ga3 = (tsbk >> 16) & 0xffff
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x02: # group voice chan grant update
|
||||||
mfrid = (tsbk >> 80) & 0xff
|
mfrid = (tsbk >> 80) & 0xff
|
||||||
if mfrid == 0x90:
|
if mfrid == 0x90:
|
||||||
|
@ -331,11 +373,11 @@ class trunked_system (object):
|
||||||
sg = (tsbk >> 40) & 0xffff
|
sg = (tsbk >> 40) & 0xffff
|
||||||
sa = (tsbk >> 16) & 0xffffff
|
sa = (tsbk >> 16) & 0xffffff
|
||||||
f = self.channel_id_to_frequency(ch)
|
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:
|
if f:
|
||||||
updated += 1
|
updated += 1
|
||||||
if self.debug > 10:
|
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:
|
else:
|
||||||
ch1 = (tsbk >> 64) & 0xffff
|
ch1 = (tsbk >> 64) & 0xffff
|
||||||
ga1 = (tsbk >> 48) & 0xffff
|
ga1 = (tsbk >> 48) & 0xffff
|
||||||
|
@ -351,7 +393,7 @@ class trunked_system (object):
|
||||||
if f2:
|
if f2:
|
||||||
updated += 1
|
updated += 1
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x03: # group voice chan grant update exp : TIA.102-AABC-B-2005 page 56
|
||||||
mfrid = (tsbk >> 80) & 0xff
|
mfrid = (tsbk >> 80) & 0xff
|
||||||
if mfrid == 0x90: #MOT_GRG_CN_GRANT_UPDT
|
if mfrid == 0x90: #MOT_GRG_CN_GRANT_UPDT
|
||||||
|
@ -369,7 +411,7 @@ class trunked_system (object):
|
||||||
if f2:
|
if f2:
|
||||||
updated += 1
|
updated += 1
|
||||||
if self.debug > 10:
|
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:
|
elif mfrid == 0:
|
||||||
ch1 = (tsbk >> 48) & 0xffff
|
ch1 = (tsbk >> 48) & 0xffff
|
||||||
ch2 = (tsbk >> 32) & 0xffff
|
ch2 = (tsbk >> 32) & 0xffff
|
||||||
|
@ -379,13 +421,22 @@ class trunked_system (object):
|
||||||
if f:
|
if f:
|
||||||
updated += 1
|
updated += 1
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x16: # sndcp data ch
|
||||||
ch1 = (tsbk >> 48) & 0xffff
|
ch1 = (tsbk >> 48) & 0xffff
|
||||||
ch2 = (tsbk >> 32) & 0xffff
|
ch2 = (tsbk >> 32) & 0xffff
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x34: # iden_up vhf uhf
|
||||||
iden = (tsbk >> 76) & 0xf
|
iden = (tsbk >> 76) & 0xf
|
||||||
bwvu = (tsbk >> 72) & 0xf
|
bwvu = (tsbk >> 72) & 0xf
|
||||||
|
@ -402,7 +453,7 @@ class trunked_system (object):
|
||||||
self.freq_table[iden]['step'] = spac * 125
|
self.freq_table[iden]['step'] = spac * 125
|
||||||
self.freq_table[iden]['frequency'] = freq * 5
|
self.freq_table[iden]['frequency'] = freq * 5
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x33: # iden_up_tdma
|
||||||
mfrid = (tsbk >> 80) & 0xff
|
mfrid = (tsbk >> 80) & 0xff
|
||||||
if mfrid == 0:
|
if mfrid == 0:
|
||||||
|
@ -422,7 +473,7 @@ class trunked_system (object):
|
||||||
self.freq_table[iden]['frequency'] = f1 * 5
|
self.freq_table[iden]['frequency'] = f1 * 5
|
||||||
self.freq_table[iden]['tdma'] = slots_per_carrier[channel_type]
|
self.freq_table[iden]['tdma'] = slots_per_carrier[channel_type]
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x3d: # iden_up
|
||||||
iden = (tsbk >> 76) & 0xf
|
iden = (tsbk >> 76) & 0xf
|
||||||
|
@ -440,7 +491,7 @@ class trunked_system (object):
|
||||||
self.freq_table[iden]['step'] = spac * 125
|
self.freq_table[iden]['step'] = spac * 125
|
||||||
self.freq_table[iden]['frequency'] = freq * 5
|
self.freq_table[iden]['frequency'] = freq * 5
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x3a: # rfss status
|
||||||
syid = (tsbk >> 56) & 0xfff
|
syid = (tsbk >> 56) & 0xfff
|
||||||
rfid = (tsbk >> 48) & 0xff
|
rfid = (tsbk >> 48) & 0xff
|
||||||
|
@ -454,7 +505,7 @@ class trunked_system (object):
|
||||||
self.rfss_chan = f1
|
self.rfss_chan = f1
|
||||||
self.rfss_txchan = f1 + self.freq_table[chan >> 12]['offset']
|
self.rfss_txchan = f1 + self.freq_table[chan >> 12]['offset']
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x39: # secondary cc
|
||||||
rfid = (tsbk >> 72) & 0xff
|
rfid = (tsbk >> 72) & 0xff
|
||||||
stid = (tsbk >> 64) & 0xff
|
stid = (tsbk >> 64) & 0xff
|
||||||
|
@ -468,7 +519,7 @@ class trunked_system (object):
|
||||||
sorted_freqs = collections.OrderedDict(sorted(self.secondary.items()))
|
sorted_freqs = collections.OrderedDict(sorted(self.secondary.items()))
|
||||||
self.secondary = sorted_freqs
|
self.secondary = sorted_freqs
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x3b: # network status
|
||||||
wacn = (tsbk >> 52) & 0xfffff
|
wacn = (tsbk >> 52) & 0xfffff
|
||||||
syid = (tsbk >> 40) & 0xfff
|
syid = (tsbk >> 40) & 0xfff
|
||||||
|
@ -479,8 +530,9 @@ class trunked_system (object):
|
||||||
self.ns_wacn = wacn
|
self.ns_wacn = wacn
|
||||||
self.ns_chan = f1
|
self.ns_chan = f1
|
||||||
if self.debug > 10:
|
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
|
elif opcode == 0x3c: # adjacent status
|
||||||
|
syid = (tsbk >> 56) & 0xfff
|
||||||
rfid = (tsbk >> 48) & 0xff
|
rfid = (tsbk >> 48) & 0xff
|
||||||
stid = (tsbk >> 40) & 0xff
|
stid = (tsbk >> 40) & 0xff
|
||||||
ch1 = (tsbk >> 24) & 0xffff
|
ch1 = (tsbk >> 24) & 0xffff
|
||||||
|
@ -488,28 +540,30 @@ class trunked_system (object):
|
||||||
f1 = self.channel_id_to_frequency(ch1)
|
f1 = self.channel_id_to_frequency(ch1)
|
||||||
if f1 and table in self.freq_table:
|
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[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:
|
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:
|
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:
|
#else:
|
||||||
# print "tsbk other %x" % opcode
|
# sys.stderr.write('tsbk other %x\n' % opcode)
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
def hunt_cc(self, curr_time):
|
def hunt_cc(self, curr_time):
|
||||||
if self.cc_timeouts < 6:
|
if self.cc_timeouts < 6:
|
||||||
return
|
return False
|
||||||
self.cc_timeouts = 0
|
self.cc_timeouts = 0
|
||||||
self.cc_list_index += 1
|
self.cc_list_index += 1
|
||||||
if self.cc_list_index >= len(self.cc_list):
|
if self.cc_list_index >= len(self.cc_list):
|
||||||
self.cc_list_index = 0
|
self.cc_list_index = 0
|
||||||
self.trunk_cc = self.cc_list[self.cc_list_index]
|
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))
|
sys.stderr.write('%f set trunk_cc to %s\n' % (curr_time, self.trunk_cc))
|
||||||
|
if self.trunk_cc != self.last_trunk_cc:
|
||||||
def get_int_dict(s):
|
self.last_trunk_cc = self.trunk_cc
|
||||||
if s[0].isdigit():
|
if self.debug >=5:
|
||||||
return dict.fromkeys([int(d) for d in s.split(',')])
|
sys.stderr.write('%f set control channel: %f\n' % (curr_time, self.trunk_cc / 1000000.0))
|
||||||
return dict.fromkeys([int(d) for d in open(s).readlines()])
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
class rx_ctl (object):
|
class rx_ctl (object):
|
||||||
def __init__(self, debug=0, frequency_set=None, conf_file=None, logfile_workers=None):
|
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.wait_until = time.time()
|
||||||
self.configs = {}
|
self.configs = {}
|
||||||
self.nacs = []
|
self.nacs = []
|
||||||
self.last_tdma_vf = 0
|
|
||||||
self.P2_GRACE_TIME = 1.0 # TODO: make more configurable
|
|
||||||
self.logfile_workers = logfile_workers
|
self.logfile_workers = logfile_workers
|
||||||
self.active_talkgroups = {}
|
self.active_talkgroups = {}
|
||||||
self.working_frequencies = {}
|
self.working_frequencies = {}
|
||||||
self.xor_cache = {}
|
self.xor_cache = {}
|
||||||
self.last_garbage_collect = 0
|
self.last_garbage_collect = 0
|
||||||
|
self.last_command = {'command': None, 'time': time.time()}
|
||||||
if self.logfile_workers:
|
if self.logfile_workers:
|
||||||
self.input_rate = self.logfile_workers[0]['demod'].input_rate
|
self.input_rate = self.logfile_workers[0]['demod'].input_rate
|
||||||
|
self.enabled_nacs = None
|
||||||
|
|
||||||
if conf_file:
|
if conf_file:
|
||||||
if conf_file.endswith('.tsv'):
|
if conf_file.endswith('.tsv'):
|
||||||
self.build_config_tsv(conf_file)
|
self.build_config_tsv(conf_file)
|
||||||
|
elif conf_file.endswith('.json'):
|
||||||
|
self.build_config_json(conf_file)
|
||||||
else:
|
else:
|
||||||
self.build_config(conf_file)
|
self.build_config(conf_file)
|
||||||
self.nacs = self.configs.keys()
|
self.nacs = self.configs.keys()
|
||||||
self.current_nac = self.nacs[0]
|
self.current_nac = self.find_next_tsys()
|
||||||
self.current_state = self.states.CC
|
self.current_state = self.states.CC
|
||||||
|
|
||||||
tsys = self.trunked_systems[self.current_nac]
|
tsys = self.trunked_systems[self.current_nac]
|
||||||
|
@ -574,45 +630,46 @@ class rx_ctl (object):
|
||||||
'wacn': None,
|
'wacn': None,
|
||||||
'sysid': 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):
|
def set_frequency(self, params):
|
||||||
frequency = params['freq']
|
frequency = params['freq']
|
||||||
if frequency and self.frequency_set:
|
if frequency and self.frequency_set:
|
||||||
self.frequency_set(params)
|
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):
|
def add_trunked_system(self, nac):
|
||||||
assert nac not in self.trunked_systems # duplicate nac not allowed
|
assert nac not in self.trunked_systems # duplicate nac not allowed
|
||||||
blacklist = {}
|
|
||||||
whitelist = None
|
|
||||||
tgid_map = {}
|
|
||||||
cfg = None
|
cfg = None
|
||||||
if nac in self.configs:
|
if nac in self.configs:
|
||||||
cfg = self.configs[nac]
|
cfg = self.configs[nac]
|
||||||
self.trunked_systems[nac] = trunked_system(debug = self.debug, config=cfg)
|
self.trunked_systems[nac] = trunked_system(debug = self.debug, config=cfg)
|
||||||
|
|
||||||
def build_config_tsv(self, tsv_filename):
|
def build_config_tsv(self, tsv_filename):
|
||||||
import csv
|
self.setup_config(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
|
|
||||||
|
|
||||||
self.setup_config(configs)
|
|
||||||
|
|
||||||
def build_config(self, config_filename):
|
def build_config(self, config_filename):
|
||||||
import ConfigParser
|
import ConfigParser
|
||||||
|
@ -656,44 +713,40 @@ class rx_ctl (object):
|
||||||
self.nacs.append(nac)
|
self.nacs.append(nac)
|
||||||
|
|
||||||
def setup_config(self, configs):
|
def setup_config(self, configs):
|
||||||
for nac in configs:
|
self.configs = make_config(configs)
|
||||||
self.configs[nac] = {'cclist':[], 'offset':0, 'whitelist':None, 'blacklist':{}, 'tgid_map':{}, 'sysname': configs[nac]['sysname'], 'center_frequency': None}
|
for nac in self.configs.keys():
|
||||||
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.add_trunked_system(nac)
|
self.add_trunked_system(nac)
|
||||||
|
|
||||||
def find_next_tsys(self):
|
def find_next_tsys(self):
|
||||||
self.current_id += 1
|
wrap = 0
|
||||||
if self.current_id >= len(self.nacs):
|
while True:
|
||||||
self.current_id = 0
|
self.current_id += 1
|
||||||
return self.nacs[self.current_id]
|
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):
|
def to_json(self):
|
||||||
|
current_time = time.time()
|
||||||
d = {'json_type': 'trunk_update'}
|
d = {'json_type': 'trunk_update'}
|
||||||
for nac in self.trunked_systems.keys():
|
for nac in self.trunked_systems.keys():
|
||||||
d[nac] = json.loads(self.trunked_systems[nac].to_json())
|
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)
|
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):
|
def to_string(self):
|
||||||
s = ''
|
s = ''
|
||||||
for nac in self.trunked_systems:
|
for nac in self.trunked_systems:
|
||||||
|
@ -705,37 +758,60 @@ class rx_ctl (object):
|
||||||
type = msg.type()
|
type = msg.type()
|
||||||
updated = 0
|
updated = 0
|
||||||
curr_time = time.time()
|
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()
|
cmd = msg.to_string()
|
||||||
if self.debug > 10:
|
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)
|
self.update_state(cmd, curr_time)
|
||||||
return
|
return
|
||||||
elif type == -1: # timeout
|
elif type == -1: # timeout
|
||||||
if self.debug:
|
if self.debug > 10:
|
||||||
print "process_data_unit timeout"
|
sys.stderr.write('%f process_data_unit timeout\n' % time.time())
|
||||||
self.update_state('timeout', curr_time)
|
self.update_state('timeout', curr_time)
|
||||||
if self.logfile_workers:
|
if self.logfile_workers:
|
||||||
self.logging_scheduler(curr_time)
|
self.logging_scheduler(curr_time)
|
||||||
return
|
return
|
||||||
elif type < 0:
|
elif type < 0:
|
||||||
print 'unknown message type %d' % (type)
|
sys.stderr.write('unknown message type %d\n' % (type))
|
||||||
return
|
return
|
||||||
s = msg.to_string()
|
s = msg.to_string()
|
||||||
# nac is always 1st two bytes
|
# nac is always 1st two bytes
|
||||||
nac = (ord(s[0]) << 8) + ord(s[1])
|
nac = (ord(s[0]) << 8) + ord(s[1])
|
||||||
|
#assert nac != 0xffff # FIXME: uncomment this after any remaining usages of 0xffff removed
|
||||||
if nac == 0xffff:
|
if nac == 0xffff:
|
||||||
# TDMA
|
if (type != 7) and (type != 12): # TDMA duid (end of call etc)
|
||||||
self.update_state('tdma_duid%d' % type, curr_time)
|
self.update_state('tdma_duid%d' % type, curr_time)
|
||||||
return
|
return
|
||||||
|
else: # voice channel derived TSBK or MBT PDU
|
||||||
|
nac = self.current_nac
|
||||||
s = s[2:]
|
s = s[2:]
|
||||||
if self.debug > 10:
|
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 (type == 7 or type == 12) and nac not in self.trunked_systems:
|
||||||
if not self.configs:
|
if not self.configs:
|
||||||
# TODO: allow whitelist/blacklist rather than blind automatic-add
|
# TODO: allow whitelist/blacklist rather than blind automatic-add
|
||||||
self.add_trunked_system(nac)
|
self.add_trunked_system(nac)
|
||||||
else:
|
else:
|
||||||
|
sys.stderr.write("%f NAC %x not configured\n" % (time.time(), nac))
|
||||||
return
|
return
|
||||||
if type == 7: # trunk: TSBK
|
if type == 7: # trunk: TSBK
|
||||||
t = 0
|
t = 0
|
||||||
|
@ -743,19 +819,28 @@ class rx_ctl (object):
|
||||||
t = (t << 8) + ord(c)
|
t = (t << 8) + ord(c)
|
||||||
updated += self.trunked_systems[nac].decode_tsbk(t)
|
updated += self.trunked_systems[nac].decode_tsbk(t)
|
||||||
elif type == 12: # trunk: MBT
|
elif type == 12: # trunk: MBT
|
||||||
s1 = s[:10]
|
s1 = s[:10] # header without crc
|
||||||
s2 = s[10:]
|
s2 = s[12:]
|
||||||
header = mbt_data = 0
|
header = mbt_data = 0
|
||||||
for c in s1:
|
for c in s1:
|
||||||
header = (header << 8) + ord(c)
|
header = (header << 8) + ord(c)
|
||||||
for c in s2:
|
for c in s2:
|
||||||
mbt_data = (mbt_data << 8) + ord(c)
|
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
|
opcode = (header >> 16) & 0x3f
|
||||||
if self.debug > 10:
|
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)
|
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, header << 16, mbt_data << 32)
|
updated += self.trunked_systems[nac].decode_mbt_data(opcode, src, header << 16, mbt_data << 32)
|
||||||
|
|
||||||
if nac != self.current_nac:
|
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
|
return
|
||||||
|
|
||||||
if self.logfile_workers:
|
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']['demod'].set_relative_frequency(0)
|
||||||
self.working_frequencies[frequency]['worker']['active'] = False
|
self.working_frequencies[frequency]['worker']['active'] = False
|
||||||
self.working_frequencies.pop(frequency)
|
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):
|
def free_talkgroup(self, frequency, tgid, curr_time):
|
||||||
decoder = self.working_frequencies[frequency]['worker']['decoder']
|
decoder = self.working_frequencies[frequency]['worker']['decoder']
|
||||||
|
@ -787,10 +872,8 @@ class rx_ctl (object):
|
||||||
index = tdma_slot
|
index = tdma_slot
|
||||||
if tdma_slot is None:
|
if tdma_slot is None:
|
||||||
index = 0
|
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)
|
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):
|
def logging_scheduler(self, curr_time):
|
||||||
tsys = self.trunked_systems[self.current_nac]
|
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)
|
# 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']]
|
other_freqs = [f for f in self.working_frequencies if f != frequency and tgid in self.working_frequencies[f]['tgids']]
|
||||||
if other_freqs:
|
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:
|
for f in other_freqs:
|
||||||
self.free_talkgroup(f, tgid, curr_time)
|
self.free_talkgroup(f, tgid, curr_time)
|
||||||
if not self.working_frequencies[f]['tgids']:
|
if not self.working_frequencies[f]['tgids']:
|
||||||
self.free_frequency(f, curr_time)
|
self.free_frequency(f, curr_time)
|
||||||
diff = abs(tsys.center_frequency - frequency)
|
diff = abs(tsys.center_frequency - frequency)
|
||||||
if diff > self.input_rate/2:
|
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
|
continue
|
||||||
|
|
||||||
update = True
|
update = True
|
||||||
|
@ -817,25 +900,28 @@ class rx_ctl (object):
|
||||||
if tgids[tgid]['tdma_slot'] == tdma_slot:
|
if tgids[tgid]['tdma_slot'] == tdma_slot:
|
||||||
update = False
|
update = False
|
||||||
else:
|
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']
|
worker = self.working_frequencies[frequency]['worker']
|
||||||
else:
|
else:
|
||||||
#active_tdma_slots = [tgids[tg]['tdma_slot'] for tg in tgids]
|
#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']
|
worker = self.working_frequencies[frequency]['worker']
|
||||||
else:
|
else:
|
||||||
worker = self.find_available_worker()
|
worker = self.find_available_worker()
|
||||||
if worker is None:
|
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
|
continue
|
||||||
self.working_frequencies[frequency] = {'tgids' : {}, 'worker': worker}
|
self.working_frequencies[frequency] = {'tgids' : {}, 'worker': worker}
|
||||||
worker['demod'].set_relative_frequency(tsys.center_frequency - frequency)
|
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}
|
self.working_frequencies[frequency]['tgids'][tgid] = {'updated': curr_time, 'tdma_slot': tdma_slot}
|
||||||
if not update:
|
if not update:
|
||||||
continue
|
continue
|
||||||
filename = 'tgid-%d-%f.wav' % (tgid, curr_time)
|
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
|
# set demod speed, decoder slot, output file name
|
||||||
demod = worker['demod']
|
demod = worker['demod']
|
||||||
decoder = worker['decoder']
|
decoder = worker['decoder']
|
||||||
|
@ -850,6 +936,7 @@ class rx_ctl (object):
|
||||||
if xorhash not in self.xor_cache:
|
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
|
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_xormask(self.xor_cache[xorhash], xorhash, index=index)
|
||||||
|
decoder.set_nac(self.current_nac, index=index)
|
||||||
demod.set_omega(symbol_rate)
|
demod.set_omega(symbol_rate)
|
||||||
decoder.set_output(filename, index=index)
|
decoder.set_output(filename, index=index)
|
||||||
|
|
||||||
|
@ -887,33 +974,67 @@ class rx_ctl (object):
|
||||||
if command == 'timeout':
|
if command == 'timeout':
|
||||||
if self.current_state == self.states.CC:
|
if self.current_state == self.states.CC:
|
||||||
if self.debug > 0:
|
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
|
tsys.cc_timeouts += 1
|
||||||
elif self.current_state != self.states.CC and curr_time - self.last_tdma_vf > self.P2_GRACE_TIME:
|
elif self.current_state != self.states.CC:
|
||||||
if self.debug > 0:
|
if self.debug > 1:
|
||||||
sys.stderr.write("[%f] voice timeout\n" % time.time())
|
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_state = self.states.CC
|
||||||
new_frequency = tsys.trunk_cc
|
new_frequency = tsys.trunk_cc
|
||||||
elif command == 'update':
|
elif command == 'update':
|
||||||
if self.current_state == self.states.CC:
|
if self.current_state == self.states.CC:
|
||||||
desired_tgid = None
|
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
|
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 new_frequency:
|
||||||
if self.debug > 0:
|
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
|
new_state = self.states.TO_VC
|
||||||
self.current_tgid = new_tgid
|
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
|
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:
|
if self.current_state != self.states.CC:
|
||||||
self.tgid_hold = self.current_tgid
|
self.tgid_hold = self.current_tgid
|
||||||
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
|
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.wait_until = curr_time + self.TSYS_HOLD_TIME
|
||||||
self.last_tdma_vf = curr_time
|
elif command == 'duid15' or command == 'tdma_duid15': # termination with channel release
|
||||||
elif command == 'duid3' or command == 'duid15' or command == 'tdma_duid15': # fdma/tdma termination with channel release
|
|
||||||
if self.current_state != self.states.CC:
|
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_state = self.states.CC
|
||||||
new_frequency = tsys.trunk_cc
|
new_frequency = tsys.trunk_cc
|
||||||
elif command == 'duid0' or command == 'duid5' or command == 'duid10' or command == 'tdma_duid5':
|
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 = self.current_tgid
|
||||||
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
|
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.wait_until = curr_time + self.TSYS_HOLD_TIME
|
||||||
if command == 'tdma_duid5':
|
elif command == 'duid7' or command == 'duid12': # tsbk/pdu should never arrive here...
|
||||||
self.last_tdma_vf = curr_time
|
|
||||||
elif command == 'duid7' or command == 'duid12':
|
|
||||||
pass
|
pass
|
||||||
elif command == 'hold':
|
elif command == 'hold':
|
||||||
|
self.last_command = {'command': command, 'time': curr_time}
|
||||||
if self.hold_mode is False and self.current_tgid:
|
if self.hold_mode is False and self.current_tgid:
|
||||||
self.tgid_hold = self.current_tgid
|
self.tgid_hold = self.current_tgid
|
||||||
self.tgid_hold_until = curr_time + 86400 * 10000
|
self.tgid_hold_until = curr_time + 86400 * 10000
|
||||||
self.hold_mode = True
|
self.hold_mode = True
|
||||||
if self.debug > 0:
|
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:
|
elif self.hold_mode is True:
|
||||||
self.current_tgid = None
|
self.current_tgid = None
|
||||||
self.tgid_hold = None
|
self.tgid_hold = None
|
||||||
self.tgid_hold_until = curr_time
|
self.tgid_hold_until = curr_time
|
||||||
self.hold_mode = False
|
self.hold_mode = False
|
||||||
elif command == 'set_hold':
|
elif command == 'set_hold':
|
||||||
|
self.last_command = {'command': command, 'time': curr_time}
|
||||||
if self.current_tgid:
|
if self.current_tgid:
|
||||||
self.tgid_hold = self.current_tgid
|
self.tgid_hold = self.current_tgid
|
||||||
self.tgid_hold_until = curr_time + 86400 * 10000
|
self.tgid_hold_until = curr_time + 86400 * 10000
|
||||||
self.hold_mode = True
|
self.hold_mode = True
|
||||||
print 'set hold until %f' % self.tgid_hold_until
|
print 'set hold until %f' % self.tgid_hold_until
|
||||||
elif command == 'unset_hold':
|
elif command == 'unset_hold':
|
||||||
|
self.last_command = {'command': command, 'time': curr_time}
|
||||||
if self.current_tgid:
|
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.current_tgid = None
|
||||||
self.tgid_hold = None
|
self.tgid_hold = None
|
||||||
self.tgid_hold_until = curr_time
|
self.tgid_hold_until = curr_time
|
||||||
self.hold_mode = False
|
self.hold_mode = False
|
||||||
elif command == 'skip' or command == 'lockout':
|
elif command == 'skip' or command == 'lockout':
|
||||||
|
self.last_command = {'command': command, 'time': curr_time}
|
||||||
if self.current_tgid:
|
if self.current_tgid:
|
||||||
end_time = None
|
end_time = None
|
||||||
if command == 'skip':
|
if command == 'skip':
|
||||||
|
@ -964,23 +1089,41 @@ class rx_ctl (object):
|
||||||
new_state = self.states.CC
|
new_state = self.states.CC
|
||||||
new_frequency = tsys.trunk_cc
|
new_frequency = tsys.trunk_cc
|
||||||
else:
|
else:
|
||||||
print 'update_state: unknown command: %s\n' % command
|
sys.stderr.write('update_state: unknown command: %s\n' % command)
|
||||||
assert 0 == 1
|
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
|
self.wait_until = curr_time + self.TSYS_HOLD_TIME
|
||||||
|
tsys.current_srcaddr = 0
|
||||||
|
tsys.current_grpaddr = 0
|
||||||
new_nac = self.find_next_tsys()
|
new_nac = self.find_next_tsys()
|
||||||
new_state = self.states.CC
|
new_state = self.states.CC
|
||||||
|
|
||||||
if new_nac:
|
if new_nac is not None:
|
||||||
nac = self.current_nac = new_nac
|
nac = self.current_nac = new_nac
|
||||||
tsys = self.trunked_systems[nac]
|
tsys = self.trunked_systems[nac]
|
||||||
new_frequency = tsys.trunk_cc
|
new_frequency = tsys.trunk_cc
|
||||||
|
tsys.current_srcaddr = 0
|
||||||
|
tsys.current_grpaddr = 0
|
||||||
self.current_tgid = None
|
self.current_tgid = None
|
||||||
|
|
||||||
if new_frequency:
|
if new_frequency is not None:
|
||||||
self.set_frequency({
|
self.set_frequency({
|
||||||
'freq': new_frequency,
|
'freq': new_frequency,
|
||||||
'tgid': self.current_tgid,
|
'tgid': self.current_tgid,
|
||||||
|
@ -991,20 +1134,25 @@ class rx_ctl (object):
|
||||||
'center_frequency': tsys.center_frequency,
|
'center_frequency': tsys.center_frequency,
|
||||||
'tdma': new_slot,
|
'tdma': new_slot,
|
||||||
'wacn': tsys.ns_wacn,
|
'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
|
self.current_state = new_state
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
q = 0x3a000012ae01013348704a54
|
q = 0x3a000012ae01013348704a54
|
||||||
rc = crc16(q,12)
|
rc = crc16(q,12)
|
||||||
print "should be zero: %x" % rc
|
sys.stderr.write('should be zero: %x\n' % rc)
|
||||||
assert rc == 0
|
assert rc == 0
|
||||||
|
|
||||||
q = 0x3a001012ae01013348704a54
|
q = 0x3a001012ae01013348704a54
|
||||||
rc = crc16(q,12)
|
rc = crc16(q,12)
|
||||||
print "should be nonzero: %x" % rc
|
sys.stderr.write('should be nonzero: %x\n' % rc)
|
||||||
assert rc != 0
|
assert rc != 0
|
||||||
|
|
||||||
t = trunked_system(debug=255)
|
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
|
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}
|
RC_FILTER = {'dmr': 'rrc', 'p25': 'rc', 'ysf': 'rrc', 'dstar': None}
|
||||||
|
|
||||||
output_gains = {
|
output_gains = {
|
||||||
'dmr': 5.5,
|
'dmr': 5.5,
|
||||||
'dstar': 0.95,
|
'dstar': 0.95,
|
||||||
'p25': 5.0,
|
'p25': 4.5,
|
||||||
'ysf': 5.5
|
'ysf': 5.5
|
||||||
}
|
}
|
||||||
gain_adjust = {
|
gain_adjust = {
|
||||||
'dmr': 3.0,
|
'dmr': 3.0,
|
||||||
'dstar': 7.5,
|
'dstar': 7.5,
|
||||||
'ysf': 5.0
|
'ysf': 4.0
|
||||||
}
|
}
|
||||||
gain_adjust_fullrate = {
|
gain_adjust_fullrate = {
|
||||||
'p25': 2.0,
|
'p25': 2.0,
|
||||||
|
@ -79,13 +82,15 @@ class my_top_block(gr.top_block):
|
||||||
parser = OptionParser(option_class=eng_option)
|
parser = OptionParser(option_class=eng_option)
|
||||||
|
|
||||||
parser.add_option("-a", "--args", type="string", default="", help="device args")
|
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("-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("-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", "--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("-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("-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("-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("-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", "--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")
|
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-correction", type="float", default=0.0, help="ppm")
|
||||||
parser.add_option("-Q", "--frequency", type="float", default=0.0, help="Hz")
|
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", "--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("-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("-t", "--test", type="string", default=None, help="test pattern symbol file")
|
||||||
parser.add_option("-v", "--verbose", type="int", default=0, help="additional output")
|
parser.add_option("-v", "--verbose", type="int", default=0, help="additional output")
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
@ -105,8 +112,8 @@ class my_top_block(gr.top_block):
|
||||||
print 'protocol [-p] option missing'
|
print 'protocol [-p] option missing'
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if options.protocol == 'ysf' or options.protocol == 'dmr':
|
if options.protocol == 'ysf' or options.protocol == 'dmr' or options.protocol == 'dstar':
|
||||||
assert options.config_file # dmr and ysf require config file ("-c FILENAME" option)
|
assert options.config_file # dstar, dmr and ysf require config file ("-c FILENAME" option)
|
||||||
|
|
||||||
output_gain = output_gains[options.protocol]
|
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)
|
ENCODER = op25_repeater.dstar_tx_sb(options.verbose, options.config_file)
|
||||||
elif options.protocol == 'p25':
|
elif options.protocol == 'p25':
|
||||||
ENCODER = op25_repeater.vocoder(True, # 0=Decode,True=Encode
|
ENCODER = op25_repeater.vocoder(True, # 0=Decode,True=Encode
|
||||||
0, # Verbose flag
|
False, # Verbose flag
|
||||||
0, # flex amount
|
0, # flex amount
|
||||||
"", # udp ip address
|
"", # udp ip address
|
||||||
0, # udp port
|
0, # udp port
|
||||||
|
@ -135,7 +142,7 @@ class my_top_block(gr.top_block):
|
||||||
ENCODER.set_gain_adjust(gain_adjust_fullrate['ysf'])
|
ENCODER.set_gain_adjust(gain_adjust_fullrate['ysf'])
|
||||||
else:
|
else:
|
||||||
ENCODER.set_gain_adjust(gain_adjust['ysf'])
|
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])
|
ENCODER.set_gain_adjust(gain_adjust_fullrate[options.protocol])
|
||||||
elif not options.test and not options.protocol == 'ysf':
|
elif not options.test and not options.protocol == 'ysf':
|
||||||
ENCODER.set_gain_adjust(gain_adjust[options.protocol])
|
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':
|
if options.file2 and options.protocol == 'dmr':
|
||||||
nfiles += 1
|
nfiles += 1
|
||||||
if nfiles < max_inputs and not options.test:
|
if nfiles < max_inputs and not options.test:
|
||||||
AUDIO = audio.source(options.sample_rate, options.audio_input)
|
AUDIO = audio.source(options.alsa_rate, options.audio_input)
|
||||||
lpf_taps = filter.firdes.low_pass(1.0, options.sample_rate, 3400.0, 3400 * 0.1, filter.firdes.WIN_HANN)
|
lpf_taps = filter.firdes.low_pass(1.0, options.alsa_rate, 3400.0, 3400 * 0.1, filter.firdes.WIN_HANN)
|
||||||
audio_rate = 8000
|
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_SCALE = blocks.multiply_const_ff(32767.0 * options.gain)
|
||||||
AUDIO_F2S = blocks.float_to_short()
|
AUDIO_F2S = blocks.float_to_short()
|
||||||
self.connect(AUDIO, AUDIO_DECIM, AUDIO_SCALE, AUDIO_F2S)
|
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:
|
if options.file1:
|
||||||
IN1 = blocks.file_source(gr.sizeof_short, options.file1, options.repeat)
|
IN1 = blocks.file_source(gr.sizeof_short, options.file1, options.repeat)
|
||||||
|
@ -172,24 +182,42 @@ class my_top_block(gr.top_block):
|
||||||
else:
|
else:
|
||||||
self.connect(AUDIO_F2S, ENCODER2)
|
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)
|
AMP = blocks.multiply_const_ff(output_gain)
|
||||||
|
|
||||||
if options.output_file:
|
if options.output_file:
|
||||||
OUT = blocks.file_sink(gr.sizeof_float, options.output_file)
|
OUT = blocks.file_sink(gr.sizeof_float, options.output_file)
|
||||||
elif not options.args:
|
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:
|
if options.protocol == 'dmr' and not options.test:
|
||||||
self.connect(DMR, MOD)
|
self.connect(DMR, MOD)
|
||||||
|
if options.symbol_sink:
|
||||||
|
self.connect(DMR, SYMBOL_SINK)
|
||||||
else:
|
else:
|
||||||
self.connect(ENCODER, MOD)
|
self.connect(ENCODER, MOD)
|
||||||
|
if options.symbol_sink:
|
||||||
|
self.connect(ENCODER, SYMBOL_SINK)
|
||||||
|
|
||||||
if options.args:
|
if options.args:
|
||||||
self.setup_sdr_output(options, mod_adjust[options.protocol])
|
self.setup_sdr_output(options, mod_adjust[options.protocol])
|
||||||
interp = filter.rational_resampler_fff(options.if_rate / options.sample_rate, 1)
|
f1 = float(options.if_rate) / options.modulator_rate
|
||||||
self.attn = blocks.multiply_const_cc(0.25)
|
i1 = int(options.if_rate / options.modulator_rate)
|
||||||
self.connect(MOD, AMP, interp, self.fm_modulator, self.attn, self.u)
|
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:
|
else:
|
||||||
self.connect(MOD, AMP, OUT)
|
self.connect(MOD, AMP, OUT)
|
||||||
|
|
||||||
|
@ -212,7 +240,6 @@ class my_top_block(gr.top_block):
|
||||||
print "setting gain %s to %d" % (name, gain)
|
print "setting gain %s to %d" % (name, gain)
|
||||||
self.u.set_gain(gain, name)
|
self.u.set_gain(gain, name)
|
||||||
|
|
||||||
print 'setting sample rate'
|
|
||||||
self.u.set_sample_rate(options.if_rate)
|
self.u.set_sample_rate(options.if_rate)
|
||||||
self.u.set_center_freq(options.frequency)
|
self.u.set_center_freq(options.frequency)
|
||||||
self.u.set_freq_corr(options.frequency_correction)
|
self.u.set_freq_corr(options.frequency_correction)
|
||||||
|
|
|
@ -61,10 +61,11 @@ class pipeline(gr.hier_block2):
|
||||||
alt_input = self
|
alt_input = self
|
||||||
self.connect(alt_input, ENCODER2, (DMR, 1))
|
self.connect(alt_input, ENCODER2, (DMR, 1))
|
||||||
elif protocol == 'dstar':
|
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':
|
elif protocol == 'p25':
|
||||||
ENCODER = op25_repeater.vocoder(True, # 0=Decode,True=Encode
|
ENCODER = op25_repeater.vocoder(True, # 0=Decode,True=Encode
|
||||||
0, # Verbose flag
|
False, # Verbose flag
|
||||||
0, # flex amount
|
0, # flex amount
|
||||||
"", # udp ip address
|
"", # udp ip address
|
||||||
0, # udp port
|
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("-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("-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("-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("-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("-N", "--gains", type="string", default=None, help="gain settings")
|
||||||
parser.add_option("-o", "--if-offset", type="float", default=100000, help="channel spacing (Hz)")
|
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("-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", "--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("-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")
|
parser.add_option("-v", "--verbose", type="int", default=0, help="additional output")
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
assert options.file # input file name (-f filename) required
|
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
|
max_inputs = 1
|
||||||
|
|
||||||
from dv_tx import output_gains, gain_adjust, gain_adjust_fullrate, mod_adjust
|
from dv_tx import output_gains, gain_adjust, gain_adjust_fullrate, mod_adjust
|
||||||
|
|
||||||
if options.do_audio:
|
if options.do_audio:
|
||||||
AUDIO = audio.source(options.sample_rate, options.audio_input)
|
AUDIO = audio.source(options.alsa_rate, options.audio_input)
|
||||||
lpf_taps = filter.firdes.low_pass(1.0, options.sample_rate, 3400.0, 3400 * 0.1, filter.firdes.WIN_HANN)
|
lpf_taps = filter.firdes.low_pass(1.0, options.alsa_rate, 3400.0, 3400 * 0.1, filter.firdes.WIN_HANN)
|
||||||
audio_rate = 8000
|
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_SCALE = blocks.multiply_const_ff(32767.0 * options.gain)
|
||||||
AUDIO_F2S = blocks.float_to_short()
|
AUDIO_F2S = blocks.float_to_short()
|
||||||
self.connect(AUDIO, AUDIO_DECIM, AUDIO_SCALE, AUDIO_F2S)
|
self.connect(AUDIO, AUDIO_DECIM, AUDIO_SCALE, AUDIO_F2S)
|
||||||
|
@ -138,7 +154,6 @@ class my_top_block(gr.top_block):
|
||||||
alt_input = None
|
alt_input = None
|
||||||
|
|
||||||
SUM = blocks.add_cc()
|
SUM = blocks.add_cc()
|
||||||
protocols = 'dmr p25 dstar ysf'.split()
|
|
||||||
input_repeat = True
|
input_repeat = True
|
||||||
for i in xrange(len(protocols)):
|
for i in xrange(len(protocols)):
|
||||||
SOURCE = blocks.file_source(gr.sizeof_short, options.file, input_repeat)
|
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'
|
cfg = 'dmr-cfg.dat'
|
||||||
elif protocols[i] == 'ysf':
|
elif protocols[i] == 'ysf':
|
||||||
cfg = 'ysf-cfg.dat'
|
cfg = 'ysf-cfg.dat'
|
||||||
|
elif protocols[i] == 'dstar':
|
||||||
|
cfg = 'dstar-cfg.dat'
|
||||||
else:
|
else:
|
||||||
cfg = None
|
cfg = None
|
||||||
|
|
||||||
|
@ -159,9 +176,9 @@ class my_top_block(gr.top_block):
|
||||||
output_gain = output_gains[protocols[i]],
|
output_gain = output_gains[protocols[i]],
|
||||||
gain_adjust = gain_adj,
|
gain_adjust = gain_adj,
|
||||||
mod_adjust = mod_adjust[protocols[i]],
|
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,
|
if_rate = options.if_rate,
|
||||||
sample_rate = options.sample_rate,
|
sample_rate = options.modulator_rate,
|
||||||
bt = options.bt,
|
bt = options.bt,
|
||||||
fullrate_mode = options.fullrate_mode,
|
fullrate_mode = options.fullrate_mode,
|
||||||
alt_input = alt_input,
|
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_gmsk_span = 4
|
||||||
_def_bt = 0.25
|
_def_bt = 0.25
|
||||||
|
|
||||||
def transfer_function_rx():
|
def transfer_function_rx(symbol_rate=_def_symbol_rate):
|
||||||
# p25 c4fm de-emphasis filter
|
# p25 c4fm de-emphasis filter
|
||||||
# Specs undefined above 2,880 Hz. It would be nice to have a sharper
|
# Specs undefined above 2,880 Hz. It would be nice to have a sharper
|
||||||
# rolloff, but this filter is cheap enough....
|
# rolloff, but this filter is cheap enough....
|
||||||
xfer = [] # frequency domain transfer function
|
xfer = [] # frequency domain transfer function
|
||||||
for f in xrange(0,4800):
|
for f in xrange(0,symbol_rate):
|
||||||
# D(f)
|
# D(f)
|
||||||
t = pi * f / 4800
|
t = pi * f / symbol_rate
|
||||||
if t < 1e-6:
|
if t < 1e-6:
|
||||||
df = 1.0
|
df = 1.0
|
||||||
else:
|
else:
|
||||||
|
@ -64,7 +64,7 @@ def transfer_function_rx():
|
||||||
xfer.append(df)
|
xfer.append(df)
|
||||||
return xfer
|
return xfer
|
||||||
|
|
||||||
def transfer_function_tx():
|
def transfer_function_tx(symbol_rate=_def_symbol_rate):
|
||||||
xfer = [] # frequency domain transfer function
|
xfer = [] # frequency domain transfer function
|
||||||
for f in xrange(0, 2881): # specs cover 0 - 2,880 Hz
|
for f in xrange(0, 2881): # specs cover 0 - 2,880 Hz
|
||||||
# H(f)
|
# H(f)
|
||||||
|
@ -82,7 +82,7 @@ def transfer_function_tx():
|
||||||
xfer.append(pf * hf)
|
xfer.append(pf * hf)
|
||||||
return xfer
|
return xfer
|
||||||
|
|
||||||
def transfer_function_dmr():
|
def transfer_function_dmr(symbol_rate=_def_symbol_rate):
|
||||||
xfer = [] # frequency domain transfer function
|
xfer = [] # frequency domain transfer function
|
||||||
for f in xrange(0, 2881): # specs cover 0 - 2,880 Hz
|
for f in xrange(0, 2881): # specs cover 0 - 2,880 Hz
|
||||||
if f < 1920:
|
if f < 1920:
|
||||||
|
@ -94,6 +94,32 @@ def transfer_function_dmr():
|
||||||
xfer = np.sqrt(xfer) # root cosine
|
xfer = np.sqrt(xfer) # root cosine
|
||||||
return xfer
|
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):
|
class c4fm_taps(object):
|
||||||
"""Generate filter coefficients as per P25 C4FM spec"""
|
"""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):
|
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
|
self.generator = generator
|
||||||
|
|
||||||
def generate(self):
|
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
|
start = np.argmax(impulse_response) - (self.ntaps-1) / 2
|
||||||
coeffs = impulse_response[start: start+self.ntaps]
|
coeffs = impulse_response[start: start+self.ntaps]
|
||||||
gain = self.filter_gain / sum(coeffs)
|
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
|
gr.io_signature(1, 1, gr.sizeof_float)) # Output signature
|
||||||
|
|
||||||
input_sample_rate = 4800 # P25 baseband symbol rate
|
input_sample_rate = 4800 # P25 baseband symbol rate
|
||||||
lcm = gru.lcm(input_sample_rate, output_sample_rate)
|
intermediate_rate = 48000
|
||||||
self._interp_factor = int(lcm // input_sample_rate)
|
self._interp_factor = intermediate_rate / input_sample_rate
|
||||||
self._decimation = int(lcm // output_sample_rate)
|
|
||||||
|
|
||||||
self.dstar = dstar
|
self.dstar = dstar
|
||||||
self.bt = bt
|
self.bt = bt
|
||||||
|
@ -201,13 +226,13 @@ class p25_mod_bf(gr.hier_block2):
|
||||||
|
|
||||||
assert rc is None or rc == 'rc' or rc == 'rrc'
|
assert rc is None or rc == 'rc' or rc == 'rrc'
|
||||||
if rc:
|
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':
|
if rc == 'rc':
|
||||||
coeffs = np.convolve(coeffs, coeffs)
|
coeffs = c4fm_taps(sample_rate=intermediate_rate).generate()
|
||||||
elif self.dstar:
|
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:
|
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)
|
self.filter = filter.interp_fir_filter_fff(self._interp_factor, coeffs)
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
|
@ -217,9 +242,9 @@ class p25_mod_bf(gr.hier_block2):
|
||||||
self._setup_logging()
|
self._setup_logging()
|
||||||
|
|
||||||
self.connect(self, self.C2S, self.polarity, self.filter)
|
self.connect(self, self.C2S, self.polarity, self.filter)
|
||||||
if (self._decimation > 1):
|
if intermediate_rate != output_sample_rate:
|
||||||
self.decimator = filter.rational_resampler_fff(1, self._decimation)
|
self.arb_resamp = filter.pfb.arb_resampler_fff(float(output_sample_rate)/intermediate_rate)
|
||||||
self.connect(self.filter, self.decimator, self)
|
self.connect(self.filter, self.arb_resamp, self)
|
||||||
else:
|
else:
|
||||||
self.connect(self.filter, self)
|
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)
|
# call sign data fields (10 byte, except rem12 and rem34 are five byte)
|
||||||
#################################################################################
|
#################################################################################
|
||||||
dest=**********
|
dest=**********
|
||||||
src=SRC
|
src=KA1RBI
|
||||||
down=down1
|
down=down1
|
||||||
up=up1
|
up=up1
|
||||||
rem12=rem12
|
rem12=rem12
|
||||||
|
|
|
@ -25,6 +25,9 @@ install(FILES
|
||||||
vocoder.h
|
vocoder.h
|
||||||
gardner_costas_cc.h
|
gardner_costas_cc.h
|
||||||
p25_frame_assembler.h
|
p25_frame_assembler.h
|
||||||
|
frame_assembler.h
|
||||||
ambe_encoder_sb.h
|
ambe_encoder_sb.h
|
||||||
|
ysf_tx_sb.h
|
||||||
|
dstar_tx_sb.h
|
||||||
fsk4_slicer_fb.h DESTINATION include/op25_repeater
|
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);
|
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 void set_omega(float omega) {}
|
||||||
|
virtual float get_freq_error(void) {}
|
||||||
|
virtual int get_error_band(void) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace op25_repeater
|
} // 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);
|
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_xormask(const char*p) {}
|
||||||
|
virtual void set_nac(int nac) {}
|
||||||
virtual void set_slotid(int slotid) {}
|
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
|
vocoder_impl.cc
|
||||||
gardner_costas_cc_impl.cc
|
gardner_costas_cc_impl.cc
|
||||||
p25_frame_assembler_impl.cc
|
p25_frame_assembler_impl.cc
|
||||||
|
frame_assembler_impl.cc
|
||||||
fsk4_slicer_fb_impl.cc )
|
fsk4_slicer_fb_impl.cc )
|
||||||
|
|
||||||
list(APPEND op25_repeater_sources
|
list(APPEND op25_repeater_sources
|
||||||
|
@ -52,6 +53,11 @@ list(APPEND op25_repeater_sources
|
||||||
ambe.c
|
ambe.c
|
||||||
mbelib.c
|
mbelib.c
|
||||||
ambe_encoder.cc
|
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})
|
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)
|
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)
|
add_subdirectory(imbe_vocoder)
|
||||||
|
|
|
@ -140,7 +140,7 @@ mbe_dequantizeAmbeParms (mbe_parms * cur_mp, mbe_parms * prev_mp, const int *b,
|
||||||
#endif
|
#endif
|
||||||
if (dstar) {
|
if (dstar) {
|
||||||
deltaGamma = AmbePlusDg[b2];
|
deltaGamma = AmbePlusDg[b2];
|
||||||
cur_mp->gamma = deltaGamma + ((float) 1.0 * prev_mp->gamma);
|
cur_mp->gamma = deltaGamma + ((float) 0.5 * prev_mp->gamma);
|
||||||
} else {
|
} else {
|
||||||
deltaGamma = AmbeDg[b2];
|
deltaGamma = AmbeDg[b2];
|
||||||
cur_mp->gamma = deltaGamma + ((float) 0.5 * prev_mp->gamma);
|
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)
|
ambe_encoder::ambe_encoder(void)
|
||||||
: d_49bit_mode(false),
|
: d_49bit_mode(false),
|
||||||
d_dstar_mode(false),
|
d_dstar_mode(false),
|
||||||
|
d_alt_dstar_interleave(false),
|
||||||
d_gain_adjust(0)
|
d_gain_adjust(0)
|
||||||
{
|
{
|
||||||
mbe_parms enh_mp;
|
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);
|
encode_ambe(vocoder.param(), b, &cur_mp, &prev_mp, d_dstar_mode, d_gain_adjust);
|
||||||
|
|
||||||
if (d_dstar_mode) {
|
if (d_dstar_mode) {
|
||||||
interleaver.encode_dstar(codeword, b);
|
interleaver.encode_dstar(codeword, b, d_alt_dstar_interleave);
|
||||||
} else if (d_49bit_mode) {
|
} else if (d_49bit_mode) {
|
||||||
encode_49bit(codeword, b);
|
encode_49bit(codeword, b);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -28,7 +28,8 @@ public:
|
||||||
ambe_encoder(void);
|
ambe_encoder(void);
|
||||||
void set_49bit_mode(void);
|
void set_49bit_mode(void);
|
||||||
void set_dstar_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:
|
private:
|
||||||
imbe_vocoder vocoder;
|
imbe_vocoder vocoder;
|
||||||
p25p2_vf interleaver;
|
p25p2_vf interleaver;
|
||||||
|
@ -37,6 +38,7 @@ private:
|
||||||
bool d_49bit_mode;
|
bool d_49bit_mode;
|
||||||
bool d_dstar_mode;
|
bool d_dstar_mode;
|
||||||
float d_gain_adjust;
|
float d_gain_adjust;
|
||||||
|
bool d_alt_dstar_interleave;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* INCLUDED_AMBE_ENCODER_H */
|
#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) {
|
static inline bool check_frame_sync(uint64_t x, int err_threshold, int len) {
|
||||||
int errs=0;
|
int errs=0;
|
||||||
static const uint64_t mask = (1LL<<len)-1;
|
const uint64_t mask = (1LL<<len)-1LL;
|
||||||
x = x & mask;
|
x = x & mask;
|
||||||
// source: https://en.wikipedia.org/wiki/Hamming_weight
|
// source: https://en.wikipedia.org/wiki/Hamming_weight
|
||||||
static const uint64_t m1 = 0x5555555555555555; //binary: 0101...
|
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 ];
|
int tact = hamming_7_4[ (at << 3) | (tc << 2) | lcss ];
|
||||||
//printf ("tact %d %x\n", tact, tact);
|
//printf ("tact %d %x\n", tact, tact);
|
||||||
//print_result("cach_payload_bits", cach_bits, 17);
|
//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++) {
|
for (int i=0; i<7; i++) {
|
||||||
result[cach_tact_bits[i]] = (tact >> (6-i)) & 1;
|
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,
|
0, 11, 22, 29, 39, 44, 49, 58,
|
||||||
69, 78, 83, 88, 98, 105, 116, 127,
|
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[] = {
|
static const int hamming_17_12[] = {
|
||||||
0, 37, 74, 111, 148, 177, 222, 251,
|
0, 37, 74, 111, 148, 177, 222, 251,
|
||||||
269, 296, 327, 354, 409, 444, 467, 502,
|
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] = {
|
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
|
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] = {
|
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
|
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 */
|
#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_tx_sb_impl.h"
|
||||||
// #include "dstar_const.h"
|
// #include "dstar_const.h"
|
||||||
#include <op25_imbe_frame.h>
|
#include <op25_imbe_frame.h>
|
||||||
|
#include "dstar_header.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
@ -52,6 +53,15 @@ static inline void print_result(char title[], const uint8_t r[], int len) {
|
||||||
}
|
}
|
||||||
#endif
|
#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[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 };
|
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");
|
FILE * fp1 = fopen(d_config_file, "r");
|
||||||
char line[256];
|
char line[256];
|
||||||
char * cp;
|
char * cp;
|
||||||
// TODO: add code to generate slow speed datastream
|
int flag1, flag2, flag3;
|
||||||
return;
|
char rptcall1[8];
|
||||||
|
char rptcall2[8];
|
||||||
|
char urcall[8];
|
||||||
|
char mycall1[8];
|
||||||
|
char mycall2[8];
|
||||||
|
|
||||||
if (!fp1) {
|
if (!fp1) {
|
||||||
fprintf(stderr, "dstar_tx_sb_impl:config: failed to open %s\n", d_config_file);
|
fprintf(stderr, "dstar_tx_sb_impl:config: failed to open %s\n", d_config_file);
|
||||||
return;
|
return;
|
||||||
|
@ -112,12 +127,25 @@ dstar_tx_sb_impl::config()
|
||||||
cp = fgets(line, sizeof(line) - 2, fp1);
|
cp = fgets(line, sizeof(line) - 2, fp1);
|
||||||
if (!cp) break;
|
if (!cp) break;
|
||||||
if (line[0] == '#') continue;
|
if (line[0] == '#') continue;
|
||||||
#if 0
|
if (memcmp(line, "flag1=", 6) == 0)
|
||||||
if (memcmp(line, "ft=", 3) == 0)
|
sscanf(&line[6], "%x", &flag1);
|
||||||
sscanf(&line[3], "%d", &d_ft);
|
else if (memcmp(line, "flag2=", 6) == 0)
|
||||||
#endif
|
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);
|
fclose(fp1);
|
||||||
|
make_dstar_header(d_dstar_header_data, flag1 & 0xff, flag2 & 0xff, flag3 & 0xff, rptcall2, rptcall1, urcall, mycall1, mycall2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -151,7 +179,7 @@ dstar_tx_sb_impl::general_work (int noutput_items,
|
||||||
if (d_frame_counter == 0)
|
if (d_frame_counter == 0)
|
||||||
memcpy(out+72, FS, 24);
|
memcpy(out+72, FS, 24);
|
||||||
else
|
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;
|
d_frame_counter = (d_frame_counter + 1) % 21;
|
||||||
in += 160;
|
in += 160;
|
||||||
nconsumed += 160;
|
nconsumed += 160;
|
||||||
|
|
|
@ -60,6 +60,7 @@ namespace gr {
|
||||||
const char * d_config_file;
|
const char * d_config_file;
|
||||||
ambe_encoder d_encoder;
|
ambe_encoder d_encoder;
|
||||||
int d_frame_counter;
|
int d_frame_counter;
|
||||||
|
uint8_t d_dstar_header_data[480];
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace op25_repeater
|
} // 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