Goby Underwater Autonomy Project
Series: 1.1, revision: 163, released on 2013-02-06 14:23:27 -0500
|
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 }