Note: Goby version 1 (shown here) is now considered obsolete. Please use version 2 for new projects, and consider upgrading old projects.

Goby Underwater Autonomy Project  Series: 1.1, revision: 163, released on 2013-02-06 14:23:27 -0500
acomms/libdccl/dccl.cpp
00001 // copyright 2009 t. schneider tes@mit.edu
00002 // 
00003 // this file is part of the Dynamic Compact Control Language (DCCL),
00004 // the goby-acomms codec. goby-acomms is a collection of libraries 
00005 // for acoustic underwater networking
00006 //
00007 // This program is free software: you can redistribute it and/or modify
00008 // it under the terms of the GNU General Public License as published by
00009 // the Free Software Foundation, either version 3 of the License, or
00010 // (at your option) any later version.
00011 //
00012 // This software is distributed in the hope that it will be useful,
00013 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 // GNU General Public License for more details.
00016 //
00017 // You should have received a copy of the GNU General Public License
00018 // along with this software.  If not, see <http://www.gnu.org/licenses/>.
00019 
00020 #include <boost/foreach.hpp>
00021 
00022 #ifdef CRYPTOPP_PATH_USES_PLUS_SIGN
00023 #include <crypto++/filters.h>
00024 #include <crypto++/sha.h>
00025 #include <crypto++/modes.h>
00026 #include <crypto++/aes.h>
00027 #else
00028 #include <cryptopp/filters.h>
00029 #include <cryptopp/sha.h>
00030 #include <cryptopp/modes.h>
00031 #include <cryptopp/aes.h>
00032 #endif
00033 
00034 #include "dccl.h"
00035 #include "goby/acomms/xml/xml_parser.h"
00036 #include "message_xml_callbacks.h"
00037 #include "goby/util/logger.h"
00038 #include "goby/util/string.h"
00039 #include "goby/protobuf/acomms_proto_helpers.h"
00040 
00041 using goby::util::goby_time;
00042 using goby::util::as;
00043 
00045 // public methods (general use)
00047 goby::acomms::DCCLCodec::DCCLCodec(std::ostream* log /* =0 */)
00048     : log_(log),
00049       start_time_(goby_time())
00050 { }
00051 
00052 std::set<unsigned> goby::acomms::DCCLCodec::add_xml_message_file(const std::string& xml_file)
00053 {
00054     size_t begin_size = messages_.size();
00055             
00056         
00057     // Register handlers for XML parsing
00058     DCCLMessageContentHandler content(messages_);
00059     DCCLMessageErrorHandler error;
00060     // instantiate a parser for the xml message files
00061     XMLParser parser(content, error);
00062     // parse(file)
00063 
00064     parser.parse(xml_file);
00065 
00066     size_t end_size = messages_.size();
00067     
00068     check_duplicates();
00069 
00070     std::set<unsigned> added_ids;
00071     
00072     for(size_t i = 0, n = end_size - begin_size; i < n; ++i)
00073     {
00074         // map name/id to position in messages_ vector for later use
00075         size_t new_index = messages_.size()-i-1;
00076         name2messages_.insert(std::pair<std::string, size_t>(messages_[new_index].name(), new_index));
00077         id2messages_.insert(std::pair<unsigned, size_t>(messages_[new_index].id(), new_index));
00078         added_ids.insert(messages_[new_index].id());
00079     }
00080     
00081     return added_ids;
00082 }
00083 
00084 std::set<unsigned> goby::acomms::DCCLCodec::all_message_ids()
00085 {
00086     std::set<unsigned> s;
00087     BOOST_FOREACH(const DCCLMessage &msg, messages_)
00088         s.insert(msg.id());
00089     return s;
00090 }    
00091 std::set<std::string> goby::acomms::DCCLCodec::all_message_names()
00092 {
00093     std::set<std::string> s;
00094     BOOST_FOREACH(const DCCLMessage &msg, messages_)
00095         s.insert(msg.name());
00096     return s;
00097 }
00098 
00099     
00100 std::string goby::acomms::DCCLCodec::summary() const 
00101 { 
00102     std::string out;
00103     for(std::vector<DCCLMessage>::const_iterator it = messages_.begin(), n = messages_.end(); it != n; ++it)
00104         out += it->get_display();
00105     return out;
00106 }
00107 
00108 std::string goby::acomms::DCCLCodec::brief_summary() const 
00109 { 
00110     std::string out;
00111     for(std::vector<DCCLMessage>::const_iterator it = messages_.begin(), n = messages_.end(); it != n; ++it)
00112         out += it->get_short_display();
00113     return out;
00114 }
00115 
00116 void goby::acomms::DCCLCodec::add_algorithm(const std::string& name, AlgFunction1 func)
00117 {
00118     DCCLAlgorithmPerformer* ap = DCCLAlgorithmPerformer::getInstance();
00119     ap -> add_algorithm(name, func);
00120 }
00121 
00122 void goby::acomms::DCCLCodec::add_adv_algorithm(const std::string& name, AlgFunction2 func)
00123 {
00124     DCCLAlgorithmPerformer* ap = DCCLAlgorithmPerformer::getInstance();
00125     ap -> add_algorithm(name, func);
00126 }
00127 
00128 void goby::acomms::DCCLCodec::add_flex_groups(util::FlexOstream* tout)
00129 {
00130     tout->add_group("dccl_enc", util::Colors::lt_magenta, "encoder messages (goby_dccl)");
00131     tout->add_group("dccl_dec", util::Colors::lt_blue, "decoder messages (goby_dccl)");
00132 }
00133 
00134 
00135 std::ostream& goby::acomms::operator<< (std::ostream& out, const DCCLCodec& d)
00136 {
00137     out << d.summary();
00138     return out;
00139 }
00140 
00141 
00142 std::ostream& goby::acomms::operator<< (std::ostream& out, const std::set<std::string>& s)
00143 {
00144     out << "std::set<std::string>:" << std::endl;
00145     for (std::set<std::string>::const_iterator it = s.begin(), n = s.end(); it != n; ++it)
00146         out << (*it) << std::endl;
00147     return out;
00148 }
00149 
00150 std::ostream& goby::acomms::operator<< (std::ostream& out, const std::set<unsigned>& s)
00151 {
00152     out << "std::set<unsigned>:" << std::endl;
00153     for (std::set<unsigned>::const_iterator it = s.begin(), n = s.end(); it != n; ++it)
00154         out << (*it) << std::endl;
00155     return out;
00156 }
00157 
00158 
00160 // public methods (more MOOS specific, but still could be general use)
00162 
00163 // <trigger_var mandatory_content="string"></trigger_var>
00164 // mandatory content is a string that must be contained in the
00165 // *contents* of the trigger publish in order for the trigger
00166 // to be processed. this allows the SAME moos trigger variable
00167 // to be used to trigger creation of different messages based on the
00168 // contents of the trigger message itself.
00169 bool goby::acomms::DCCLCodec::is_publish_trigger(std::set<unsigned>& id, const std::string& key, const std::string& value)
00170 {
00171     for (std::vector<DCCLMessage>::const_iterator it = messages_.begin(), n = messages_.end(); it != n; ++it)
00172     {
00173         if(key == it->trigger_var() && (it->trigger_mandatory() == "" || value.find(it->trigger_mandatory()) != std::string::npos))
00174             id.insert(it->id());
00175     }
00176     return (id.empty()) ? false : true;
00177 }
00178 
00179 bool goby::acomms::DCCLCodec::is_time_trigger(std::set<unsigned>& id)
00180 {
00181     using boost::posix_time::seconds;
00182     
00183     for (std::vector<DCCLMessage>::iterator it = messages_.begin(), n = messages_.end(); it != n; ++it)
00184     {
00185         if(it->trigger_type() == "time" &&
00186            goby_time() > (start_time_ + seconds(it->trigger_number() * it->trigger_time())))
00187         {
00188             id.insert(it->id());
00189             // increment message counter
00190             ++(*it);
00191         }        
00192     }
00193 
00194     return (id.empty()) ? false : true;
00195 }
00196 
00197     
00198 bool goby::acomms::DCCLCodec::is_incoming(unsigned& id, const std::string& key)
00199 {
00200     for (std::vector<DCCLMessage>::const_iterator it = messages_.begin(), n = messages_.end(); it != n; ++it)
00201     {
00202         if (key == it->in_var())
00203         {
00204             id = it->id();
00205             return true;
00206         }
00207     }
00208     return false;
00209 }
00210 
00212 // private methods
00214 
00215 
00216 void goby::acomms::DCCLCodec::check_duplicates()
00217 {
00218     std::map<unsigned, std::vector<DCCLMessage>::iterator> all_ids;
00219     for(std::vector<DCCLMessage>::iterator it = messages_.begin(), n = messages_.end(); it != n; ++it)
00220     {
00221         unsigned id = it->id();
00222             
00223         std::map<unsigned, std::vector<DCCLMessage>::iterator>::const_iterator id_it = all_ids.find(id);
00224         if(id_it != all_ids.end())
00225             throw DCCLException(std::string("DCCL: duplicate variable id " + as<std::string>(id) + " specified for " + it->name() + " and " + id_it->second->name()));
00226             
00227         all_ids.insert(std::pair<unsigned, std::vector<DCCLMessage>::iterator>(id, it));
00228     }
00229 }
00230 
00231 std::vector<goby::acomms::DCCLMessage>::const_iterator goby::acomms::DCCLCodec::to_iterator(const std::string& message_name) const
00232 {
00233     if(name2messages_.count(message_name))
00234         return messages_.begin() + name2messages_.find(message_name)->second;
00235     else
00236         throw DCCLException(std::string("DCCL: attempted an operation on message [" + message_name + "] which is not loaded"));
00237 }
00238 std::vector<goby::acomms::DCCLMessage>::iterator goby::acomms::DCCLCodec::to_iterator(const std::string& message_name)
00239 {
00240     if(name2messages_.count(message_name))
00241         return messages_.begin() + name2messages_.find(message_name)->second;
00242     else
00243         throw DCCLException(std::string("DCCL: attempted an operation on message [" + message_name + "] which is not loaded"));
00244 }
00245 std::vector<goby::acomms::DCCLMessage>::const_iterator goby::acomms::DCCLCodec::to_iterator(const unsigned& id) const
00246 {
00247     if(id2messages_.count(id))
00248         return messages_.begin() + id2messages_.find(id)->second;
00249     else
00250         throw DCCLException(std::string("DCCL: attempted an operation on message [" + as<std::string>(id) + "] which is not loaded"));
00251 }
00252 
00253 std::vector<goby::acomms::DCCLMessage>::iterator goby::acomms::DCCLCodec::to_iterator(const unsigned& id)
00254 {
00255     if(id2messages_.count(id))
00256         return messages_.begin() + id2messages_.find(id)->second;
00257     else
00258         throw DCCLException(std::string("DCCL: attempted an operation on message [" + as<std::string>(id) + "] which is not loaded"));
00259 }
00260 
00261 
00262 void goby::acomms::DCCLCodec::encode_private(std::vector<DCCLMessage>::iterator it,
00263                                              std::string& out,
00264                                              std::map<std::string, std::vector<DCCLMessageVal> > in /* copy */)
00265 {
00266     if(manip_manager_.has(it->id(), protobuf::MessageFile::NO_ENCODE))
00267     {
00268         if(log_) *log_ << group("dccl_enc") << "not encoding DCCL ID: " << it->id() << "; NO_ENCODE manipulator is set" << std::endl;
00269         throw(DCCLException("NO_ENCODE manipulator set"));
00270     }
00271     
00272     if(log_)
00273     {
00274         *log_ << group("dccl_enc") << "starting encode for " << it->name() << std::endl;        
00275 
00276         typedef std::pair<std::string, std::vector<DCCLMessageVal> > P;
00277         BOOST_FOREACH(const P& p, in)                    
00278         {
00279             if(!p.first.empty())
00280             {
00281                 BOOST_FOREACH(const DCCLMessageVal& mv, p.second)
00282                     *log_ << group("dccl_enc") << "\t" << p.first << ": "<< mv << std::endl;
00283             }
00284         }
00285     }
00286     
00287     // 1. encode parts
00288     std::string body, head;
00289     
00290     it->set_head_defaults(in, cfg_.modem_id());
00291 
00292     it->head_encode(head, in);
00293     it->body_encode(body, in);
00294     
00295     // 2. encrypt
00296     if(!crypto_key_.empty()) encrypt(body, head);
00297  
00298     // 3. join head and body
00299     out = head + body;
00300 
00301     
00302     if(log_) *log_ << group("dccl_enc") << "finished encode of " << it->name() <<  std::endl;    
00303 }
00304 
00305 std::vector<goby::acomms::DCCLMessage>::iterator goby::acomms::DCCLCodec::decode_private(std::string in,
00306                                                                                          std::map<std::string, std::vector<DCCLMessageVal> >& out)
00307 {
00308     std::vector<DCCLMessage>::iterator it = to_iterator(unsigned(DCCLHeaderDecoder(in)[HEAD_DCCL_ID]));
00309 
00310     if(manip_manager_.has(it->id(), protobuf::MessageFile::NO_DECODE))
00311     {
00312         if(log_) *log_ << group("dccl_enc") << "not decoding DCCL ID: " << it->id() << "; NO_DECODE manipulator is set" << std::endl;
00313         throw(DCCLException("NO_DECODE manipulator set"));
00314     }
00315 
00316 
00317     if(log_) *log_ << group("dccl_dec") << "starting decode for " << it->name() << std::endl;        
00318     
00319     // clean up any ending junk added by modem
00320     in.resize(in.find_last_not_of(char(0))+1);
00321     
00322     // 3. split body and header (avoid substr::out_of_range)
00323     std::string body = (DCCL_NUM_HEADER_BYTES < in.size()) ?
00324         in.substr(DCCL_NUM_HEADER_BYTES) : "";
00325     std::string head = in.substr(0, DCCL_NUM_HEADER_BYTES);
00326     
00327     // 2. decrypt
00328     if(!crypto_key_.empty()) decrypt(body, head);
00329     
00330     // 1. decode parts
00331     it->head_decode(head, out);
00332     it->body_decode(body, out);
00333     
00334     if(log_)
00335     {
00336         typedef std::pair<std::string, std::vector<DCCLMessageVal> > P;
00337         BOOST_FOREACH(const P& p, out)                    
00338         {
00339             if(!p.first.empty())
00340             {
00341                 BOOST_FOREACH(const DCCLMessageVal& mv, p.second)
00342                     *log_ << group("dccl_dec") << "\t" << p.first << ": "<< mv << std::endl;
00343             }
00344         }
00345         *log_ << group("dccl_dec") << "finished decode of "<< it->name() << std::endl;
00346     }
00347 
00348     return it;
00349 }
00350         
00351 void goby::acomms::DCCLCodec::encode_private(std::vector<DCCLMessage>::iterator it,
00352                                              protobuf::ModemDataTransmission* out_msg,
00353                                              const std::map<std::string, std::vector<DCCLMessageVal> >& in)
00354 {
00355     std::string out;
00356     encode_private(it, out, in);
00357 
00358     DCCLHeaderDecoder head_dec(out);
00359 
00360     out_msg->set_data(out);
00361     
00362     DCCLMessageVal& t = head_dec[HEAD_TIME];
00363     DCCLMessageVal& src = head_dec[HEAD_SRC_ID];
00364     DCCLMessageVal& dest = head_dec[HEAD_DEST_ID];
00365 
00366     out_msg->mutable_base()->set_time(goby::util::as<std::string>(goby::util::unix_double2ptime(double(t))));
00367     out_msg->mutable_base()->set_src(long(src));
00368     out_msg->mutable_base()->set_dest(long(dest));
00369 
00370     if(log_) *log_ << group("dccl_enc") << "created encoded message: " << *out_msg << std::endl;
00371 }
00372 
00373 std::vector<goby::acomms::DCCLMessage>::iterator goby::acomms::DCCLCodec::decode_private(const protobuf::ModemDataTransmission& in_msg, std::map<std::string,std::vector<DCCLMessageVal> >& out)
00374 {
00375     if(log_) *log_ << group("dccl_dec") << "using message for decode: " << in_msg << std::endl;
00376 
00377     return decode_private(in_msg.data(), out);
00378 }
00379 
00380 
00381 
00382 
00383 void goby::acomms::DCCLCodec::encrypt(std::string& s, const std::string& nonce)
00384 {
00385     using namespace CryptoPP;
00386 
00387     std::string iv;
00388     SHA256 hash;
00389     StringSource unused(nonce, true, new HashFilter(hash, new StringSink(iv)));
00390     
00391     CTR_Mode<AES>::Encryption encryptor;
00392     encryptor.SetKeyWithIV((byte*)crypto_key_.c_str(), crypto_key_.size(), (byte*)iv.c_str());
00393 
00394     std::string cipher;
00395     StreamTransformationFilter in(encryptor, new StringSink(cipher));
00396     in.Put((byte*)s.c_str(), s.size());
00397     in.MessageEnd();
00398     s = cipher;
00399 }
00400 
00401 void goby::acomms::DCCLCodec::decrypt(std::string& s, const std::string& nonce)
00402 {
00403     using namespace CryptoPP;
00404 
00405     std::string iv;
00406     SHA256 hash;
00407     StringSource unused(nonce, true, new HashFilter(hash, new StringSink(iv)));
00408     
00409     CTR_Mode<AES>::Decryption decryptor;    
00410     decryptor.SetKeyWithIV((byte*)crypto_key_.c_str(), crypto_key_.size(), (byte*)iv.c_str());
00411     
00412     std::string recovered;
00413     StreamTransformationFilter out(decryptor, new StringSink(recovered));
00414     out.Put((byte*)s.c_str(), s.size());
00415     out.MessageEnd();
00416     s = recovered;
00417 }
00418 
00419 void goby::acomms::DCCLCodec::merge_cfg(const protobuf::DCCLConfig& cfg)
00420 {
00421     cfg_.MergeFrom(cfg);
00422     process_cfg();
00423 }
00424 
00425 void goby::acomms::DCCLCodec::set_cfg(const protobuf::DCCLConfig& cfg)
00426 {
00427     cfg_.CopyFrom(cfg);
00428     process_cfg();
00429 }
00430 
00431 void goby::acomms::DCCLCodec::process_cfg()
00432 {
00433     messages_.clear();
00434     name2messages_.clear();
00435     id2messages_.clear();
00436     manip_manager_.clear();
00437 
00438     for(int i = 0, n = cfg_.message_file_size(); i < n; ++i)
00439     {
00440         std::set<unsigned> new_ids = add_xml_message_file(cfg_.message_file(i).path());
00441         BOOST_FOREACH(unsigned new_id, new_ids)
00442         {
00443             for(int j = 0, o = cfg_.message_file(i).manipulator_size(); j < o; ++j)
00444                 manip_manager_.add(new_id, cfg_.message_file(i).manipulator(j));
00445         }
00446     }
00447     
00448     using namespace CryptoPP;
00449     
00450     SHA256 hash;
00451     StringSource unused(cfg_.crypto_passphrase(), true, new HashFilter(hash, new StringSink(crypto_key_)));
00452 
00453     if(log_) *log_ << group("dccl_enc") << "cryptography enabled with given passphrase" << std::endl;
00454 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends