diff --git a/op25/gr-op25/CMakeLists.txt b/op25/gr-op25/CMakeLists.txt
index f5edd4f..938bd67 100644
--- a/op25/gr-op25/CMakeLists.txt
+++ b/op25/gr-op25/CMakeLists.txt
@@ -83,7 +83,6 @@ set(GRC_BLOCKS_DIR ${GR_PKG_DATA_DIR}/grc/blocks)
########################################################################
# Find gnuradio build dependencies
########################################################################
-find_package(GnuradioRuntime)
find_package(CppUnit)
# To run a more advanced search for GNU Radio and it's components and
@@ -93,10 +92,8 @@ find_package(CppUnit)
#
# set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER ...)
# find_package(Gnuradio "version")
-if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
- set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
- find_package(Gnuradio)
-endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+set(GR_REQUIRED_COMPONENTS RUNTIME BLOCKS FILTER PMT)
+find_package(Gnuradio)
if(NOT GNURADIO_RUNTIME_FOUND)
message(FATAL_ERROR "GnuRadio Runtime required to compile op25")
diff --git a/op25/gr-op25/grc/op25_decoder_ff.xml b/op25/gr-op25/grc/op25_decoder_ff.xml
index c503081..2b3ef88 100644
--- a/op25/gr-op25/grc/op25_decoder_ff.xml
+++ b/op25/gr-op25/grc/op25_decoder_ff.xml
@@ -4,17 +4,17 @@
op25_decoder_ff
op25
import op25
- op25.decoder_ff($)
+ op25.decoder_ff()
-
+
in
-
+ float
diff --git a/op25/gr-op25/grc/op25_fsk4_demod_ff.xml b/op25/gr-op25/grc/op25_fsk4_demod_ff.xml
index 5c970dc..7354502 100644
--- a/op25/gr-op25/grc/op25_fsk4_demod_ff.xml
+++ b/op25/gr-op25/grc/op25_fsk4_demod_ff.xml
@@ -4,7 +4,7 @@
op25_fsk4_demod_ff
op25
import op25
- op25.fsk4_demod_ff(self.auto_tune_msgq, $sample_rate, $symbol_rate)
+ op25.fsk4_demod_ff($(id)_msgq_out, $sample_rate, $symbol_rate)
Sample Rate
@@ -20,7 +20,8 @@
real
-
+
+
in
@@ -45,4 +46,10 @@
dibits
float
+
+
diff --git a/op25/gr-op25/include/op25/decoder_bf.h b/op25/gr-op25/include/op25/decoder_bf.h
index ca74ad3..a964244 100644
--- a/op25/gr-op25/include/op25/decoder_bf.h
+++ b/op25/gr-op25/include/op25/decoder_bf.h
@@ -51,7 +51,7 @@ namespace gr {
* class. op25::decoder_bf::make is the public interface for
* creating new instances.
*/
- static sptr make();
+ static sptr make(bool idle_silence = true, bool verbose = false);
/**
* Return a pointer to a string identifying the destination of
@@ -78,6 +78,17 @@ namespace gr {
* message queue.
*/
virtual void set_msgq(gr::msg_queue::sptr msgq) = 0;
+
+ virtual void set_idle_silence(bool idle_silence = true) = 0;
+
+ virtual void set_logging(bool verbose = true) = 0;
+
+ typedef std::vector key_type;
+ typedef std::map 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
diff --git a/op25/gr-op25/lib/CMakeLists.txt b/op25/gr-op25/lib/CMakeLists.txt
index 255ef93..1befdd9 100644
--- a/op25/gr-op25/lib/CMakeLists.txt
+++ b/op25/gr-op25/lib/CMakeLists.txt
@@ -53,6 +53,13 @@ list(APPEND op25_sources
value_string.cc
pickle.cc
pcap_source_b_impl.cc
+ bch.cc
+ ldu.cc
+ crypto.cc
+ crypto_module_du_handler.cc
+ deskey.c
+ desport.c
+ dessp.c
)
add_library(gnuradio-op25 SHARED ${op25_sources})
diff --git a/op25/gr-op25/lib/abstract_data_unit.cc b/op25/gr-op25/lib/abstract_data_unit.cc
index 74bbfad..3b03efb 100644
--- a/op25/gr-op25/lib/abstract_data_unit.cc
+++ b/op25/gr-op25/lib/abstract_data_unit.cc
@@ -55,10 +55,10 @@ abstract_data_unit::correct_errors()
}
void
-abstract_data_unit::decode_audio(imbe_decoder& imbe)
+abstract_data_unit::decode_audio(imbe_decoder& imbe, crypto_module::sptr crypto_mod)
{
if(is_complete()) {
- do_decode_audio(d_frame_body, imbe);
+ do_decode_audio(d_frame_body, imbe, crypto_mod);
} else {
ostringstream msg;
msg << "cannot decode audio - frame is not complete" << endl;
@@ -153,7 +153,8 @@ abstract_data_unit::dump(ostream& os) const
}
abstract_data_unit::abstract_data_unit(const_bit_queue& frame_body) :
- d_frame_body(frame_body.size())
+ d_frame_body(frame_body.size()),
+ d_logging_enabled(false)
{
copy(frame_body.begin(), frame_body.end(), d_frame_body.begin());
}
@@ -164,7 +165,7 @@ abstract_data_unit::do_correct_errors(bit_vector& frame_body)
}
void
-abstract_data_unit::do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe)
+abstract_data_unit::do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe, crypto_module::sptr crypto_mod)
{
}
@@ -179,3 +180,15 @@ abstract_data_unit::frame_size() const
{
return d_frame_body.size();
}
+
+void
+abstract_data_unit::set_logging(bool on)
+{
+ d_logging_enabled = on;
+}
+
+bool
+abstract_data_unit::logging_enabled() const
+{
+ return d_logging_enabled;
+}
diff --git a/op25/gr-op25/lib/abstract_data_unit.h b/op25/gr-op25/lib/abstract_data_unit.h
index d4fb7f2..1f4ae27 100644
--- a/op25/gr-op25/lib/abstract_data_unit.h
+++ b/op25/gr-op25/lib/abstract_data_unit.h
@@ -26,6 +26,7 @@
#include "data_unit.h"
#include "op25_yank.h"
+#include "crypto.h"
#include
#include
@@ -62,7 +63,7 @@ public:
* \precondition is_complete() == true.
* \param imbe The imbe_decoder to use to generate the audio.
*/
- virtual void decode_audio(imbe_decoder& imbe);
+ virtual void decode_audio(imbe_decoder& imbe, crypto_module::sptr crypto_mod);
/**
* Decode the frame into an octet vector.
@@ -117,6 +118,15 @@ public:
*/
virtual std::string snapshot() const;
+ /**
+ * Returns a string describing the Data Unit ID (DUID).
+ *
+ * \return A string identifying the DUID.
+ */
+ virtual std::string duid_str() const = 0;
+
+ virtual void set_logging(bool on);
+
protected:
/**
@@ -140,7 +150,7 @@ protected:
* \param frame_body The const_bit_vector to decode.
* \param imbe The imbe_decoder to use.
*/
- virtual void do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe);
+ virtual void do_decode_audio(const_bit_vector& frame_body, imbe_decoder& imbe, crypto_module::sptr crypto_mod);
/**
* Decode frame_body and write the decoded frame contents to msg.
@@ -152,13 +162,6 @@ protected:
*/
virtual size_t decode_frame(const_bit_vector& frame_body, size_t msg_sz, uint8_t *msg);
- /**
- * Returns a string describing the Data Unit ID (DUID).
- *
- * \return A string identifying the DUID.
- */
- virtual std::string duid_str() const = 0;
-
/**
* Return a reference to the frame body.
*/
@@ -180,6 +183,8 @@ protected:
*/
virtual uint16_t frame_size() const;
+ virtual bool logging_enabled() const;
+
private:
/**
@@ -187,6 +192,7 @@ private:
*/
bit_vector d_frame_body;
+ bool d_logging_enabled;
};
#endif /* INCLUDED_ABSTRACT_DATA_UNIT_H */
diff --git a/op25/gr-op25/lib/bch.cc b/op25/gr-op25/lib/bch.cc
new file mode 100644
index 0000000..e2e58a7
--- /dev/null
+++ b/op25/gr-op25/lib/bch.cc
@@ -0,0 +1,162 @@
+
+#include
+#include
+#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;
+}
+
diff --git a/op25/gr-op25/lib/bch.h b/op25/gr-op25/lib/bch.h
new file mode 100644
index 0000000..d151405
--- /dev/null
+++ b/op25/gr-op25/lib/bch.h
@@ -0,0 +1,4 @@
+#include
+typedef std::vector bit_vector;
+int bchDec(bit_vector& Codeword);
+
diff --git a/op25/gr-op25/lib/crypto.cc b/op25/gr-op25/lib/crypto.cc
new file mode 100644
index 0000000..c67770c
--- /dev/null
+++ b/op25/gr-op25/lib/crypto.cc
@@ -0,0 +1,286 @@
+#include "crypto.h"
+
+#include
+#include
+#include
+#include
+#include
+
+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);
+}
diff --git a/op25/gr-op25/lib/crypto.h b/op25/gr-op25/lib/crypto.h
new file mode 100644
index 0000000..75785b6
--- /dev/null
+++ b/op25/gr-op25/lib/crypto.h
@@ -0,0 +1,73 @@
+#ifndef INCLUDED_CRYPTO_H
+#define INCLUDED_CRYPTO_H
+
+#include
+#include
+#include