Fawkes API  Fawkes Development Version
message_register.cpp
1 
2 /***************************************************************************
3  * message_register.cpp - Protobuf stream protocol - message register
4  *
5  * Created: Fri Feb 01 15:48:36 2013
6  * Copyright 2013 Tim Niemueller [www.niemueller.de]
7  ****************************************************************************/
8 
9 /* Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * - Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  * - Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in
17  * the documentation and/or other materials provided with the
18  * distribution.
19  * - Neither the name of the authors nor the names of its contributors
20  * may be used to endorse or promote products derived from this
21  * software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
28  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
34  * OF THE POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 #include <google/protobuf/compiler/importer.h>
38 #include <google/protobuf/dynamic_message.h>
39 #include <netinet/in.h>
40 #include <protobuf_comm/message_register.h>
41 #include <sys/types.h>
42 
43 #include <dirent.h>
44 #include <fnmatch.h>
45 
46 namespace protobuf_comm {
47 
48 /** @class MessageRegister <protobuf_comm/message_register.h>
49  * Register to map msg type numbers to Protobuf messages.
50  * The register is used to automatically parse incoming messages to the
51  * appropriate type. In your application, you need to register any
52  * message you want to read. All unknown messages are silently dropped.
53  * @author Tim Niemueller
54  */
55 
56 /** Constructor. */
58 {
59  pb_srctree_ = NULL;
60  pb_importer_ = NULL;
61  pb_factory_ = NULL;
62 }
63 
64 /** Constructor.
65  * @param proto_path file paths to search for proto files. All message types
66  * within these files will automatically be registered and available for dynamic
67  * message creation.
68  */
69 MessageRegister::MessageRegister(const std::vector<std::string> &proto_path)
70 {
71  pb_srctree_ = new google::protobuf::compiler::DiskSourceTree();
72  for (size_t i = 0; i < proto_path.size(); ++i) {
73  pb_srctree_->MapPath("", proto_path[i]);
74  }
75  pb_importer_ = new google::protobuf::compiler::Importer(pb_srctree_, NULL);
76  pb_factory_ = new google::protobuf::DynamicMessageFactory(pb_importer_->pool());
77 
78  for (size_t i = 0; i < proto_path.size(); ++i) {
79  DIR * dir;
80  struct dirent *ent;
81  if ((dir = opendir(proto_path[i].c_str())) != NULL) {
82  while ((ent = readdir(dir)) != NULL) {
83  if (fnmatch("*.proto", ent->d_name, FNM_PATHNAME) != FNM_NOMATCH) {
84  //printf ("%s\n", ent->d_name);
85  const google::protobuf::FileDescriptor *fd = pb_importer_->Import(ent->d_name);
86  for (int i = 0; i < fd->message_type_count(); ++i) {
87  const google::protobuf::Descriptor *desc = fd->message_type(i);
88  //printf(" Type: %s\n", desc->full_name().c_str());
89  if (!desc->FindEnumTypeByName("CompType"))
90  continue;
91 
92  try {
93  add_message_type(desc->full_name());
94  } catch (std::logic_error &e) {
95  // cannot open for some reason
96  failed_to_load_types_.insert(std::make_pair(desc->full_name(), e.what()));
97  }
98  }
99  }
100  }
101  closedir(dir);
102  }
103  }
104 }
105 
106 /** Destructor. */
108 {
109  TypeMap::iterator m;
110  for (m = message_by_comp_type_.begin(); m != message_by_comp_type_.end(); ++m) {
111  delete m->second;
112  }
113  delete pb_factory_;
114  delete pb_importer_;
115  delete pb_srctree_;
116 }
117 
118 google::protobuf::Message *
119 MessageRegister::create_msg(const std::string &msg_type)
120 {
121  const google::protobuf::DescriptorPool *pool = google::protobuf::DescriptorPool::generated_pool();
122  google::protobuf::MessageFactory *factory = google::protobuf::MessageFactory::generated_factory();
123 
124  const google::protobuf::Descriptor *desc = pool->FindMessageTypeByName(msg_type);
125  if (desc) {
126  return factory->GetPrototype(desc)->New();
127  } else if (pb_importer_) {
128  pool = pb_importer_->pool();
129  factory = pb_factory_;
130 
131  const google::protobuf::Descriptor *cdesc = pool->FindMessageTypeByName(msg_type);
132  if (cdesc) {
133  return factory->GetPrototype(cdesc)->New();
134  }
135  }
136  return NULL;
137 }
138 
139 /** Add a message type from generated pool.
140  * This will check all message libraries for a type of the given name
141  * and if found registers it.
142  * @param msg_type the full name of the message type to add, i.e. including
143  * a package name if the message type has one. The message must have been
144  * registered with either the generated messages pool or with the pool
145  * associated with the proto paths passed to the constructor.
146  */
147 void
149 {
150  google::protobuf::Message *m = create_msg(msg_type);
151  if (m) {
152  KeyType key = key_from_desc(m->GetDescriptor());
153  std::lock_guard<std::mutex> lock(maps_mutex_);
154  if (message_by_comp_type_.find(key) != message_by_comp_type_.end()) {
155 #if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))
156  std::string msg = "Message type " + std::to_string((long long)key.first) + ":"
157  + std::to_string((long long)key.second) + " already registered";
158 #else
159  std::string msg = "Message type " + std::to_string(key.first) + ":"
160  + std::to_string(key.second) + " already registered";
161 #endif
162  throw std::runtime_error(msg);
163  }
164  //printf("Registering %s (%u:%u)\n", msg_type.c_str(), key.first, key.second);
165  message_by_comp_type_[key] = m;
166  message_by_typename_[m->GetTypeName()] = m;
167  } else {
168  throw std::runtime_error("Unknown message type");
169  }
170 }
171 
172 /** Remove the given message type.
173  * @param component_id ID of component this message type belongs to
174  * @param msg_type message type
175  */
176 void
177 MessageRegister::remove_message_type(uint16_t component_id, uint16_t msg_type)
178 {
179  KeyType key(component_id, msg_type);
180  std::lock_guard<std::mutex> lock(maps_mutex_);
181  if (message_by_comp_type_.find(key) != message_by_comp_type_.end()) {
182  message_by_typename_.erase(message_by_comp_type_[key]->GetDescriptor()->full_name());
183  message_by_comp_type_.erase(key);
184  }
185 }
186 
187 MessageRegister::KeyType
188 MessageRegister::key_from_desc(const google::protobuf::Descriptor *desc)
189 {
190  const google::protobuf::EnumDescriptor *enumdesc = desc->FindEnumTypeByName("CompType");
191  if (!enumdesc) {
192  throw std::logic_error("Message does not have CompType enum");
193  }
194  const google::protobuf::EnumValueDescriptor *compdesc = enumdesc->FindValueByName("COMP_ID");
195  const google::protobuf::EnumValueDescriptor *msgtdesc = enumdesc->FindValueByName("MSG_TYPE");
196  if (!compdesc || !msgtdesc) {
197  throw std::logic_error("Message CompType enum hs no COMP_ID or MSG_TYPE value");
198  }
199  int comp_id = compdesc->number();
200  int msg_type = msgtdesc->number();
201  if (comp_id < 0 || comp_id > std::numeric_limits<uint16_t>::max()) {
202  throw std::logic_error("Message has invalid COMP_ID");
203  }
204  if (msg_type < 0 || msg_type > std::numeric_limits<uint16_t>::max()) {
205  throw std::logic_error("Message has invalid MSG_TYPE");
206  }
207  return KeyType(comp_id, msg_type);
208 }
209 
210 /** Create a new message instance.
211  * @param component_id ID of component this message type belongs to
212  * @param msg_type message type
213  * @return new instance of a protobuf message that has been registered
214  * for the given message type.
215  */
216 std::shared_ptr<google::protobuf::Message>
217 MessageRegister::new_message_for(uint16_t component_id, uint16_t msg_type)
218 {
219  KeyType key(component_id, msg_type);
220 
221  std::lock_guard<std::mutex> lock(maps_mutex_);
222  if (message_by_comp_type_.find(key) == message_by_comp_type_.end()) {
223 #if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))
224  std::string msg = "Message type " + std::to_string((long long)component_id) + ":"
225  + std::to_string((long long)msg_type) + " not registered";
226 #else
227  std::string msg = "Message type " + std::to_string(component_id) + ":"
228  + std::to_string(msg_type) + " not registered";
229 #endif
230  throw std::runtime_error(msg);
231  }
232 
233  google::protobuf::Message *m = message_by_comp_type_[key]->New();
234  return std::shared_ptr<google::protobuf::Message>(m);
235 }
236 
237 /** Create a new message instance.
238  * @param full_name full message type name, i.e. the message type name
239  * possibly with a package name prefix.
240  * @return new instance of a protobuf message that has been registered
241  * for the given message type.
242  */
243 std::shared_ptr<google::protobuf::Message>
244 MessageRegister::new_message_for(const std::string &full_name)
245 {
246  std::lock_guard<std::mutex> lock(maps_mutex_);
247  if (message_by_typename_.find(full_name) == message_by_typename_.end()) {
248  google::protobuf::Message *m = create_msg(full_name);
249  if (m) {
250  return std::shared_ptr<google::protobuf::Message>(m);
251  } else {
252  throw std::runtime_error("Message type not registered");
253  }
254  } else {
255  google::protobuf::Message *m = message_by_typename_[full_name]->New();
256  return std::shared_ptr<google::protobuf::Message>(m);
257  }
258 }
259 
260 /** Serialize a message.
261  * @param component_id ID of component this message type belongs to
262  * @param msg_type message type
263  * @param msg message to seialize
264  * @param frame_header upon return, the frame header is filled out according to
265  * the given information and message.
266  * @param message_header upon return, the frame header is filled out according to
267  * the given information and message.
268  * @param data upon return, contains the serialized message
269  */
270 void
271 MessageRegister::serialize(uint16_t component_id,
272  uint16_t msg_type,
273  const google::protobuf::Message &msg,
274  frame_header_t & frame_header,
275  message_header_t & message_header,
276  std::string & data)
277 {
278  bool serialized = false;
279 #if GOOGLE_PROTOBUF_VERSION >= 2004000
280  try {
281  serialized = msg.SerializeToString(&data);
282  } catch (google::protobuf::FatalException &e) {
283  std::string error_msg = std::string("Failed to serialize message: ") + e.what();
284  throw std::runtime_error(error_msg);
285  }
286 #else
287  // No exceptions in earlier versions
288  serialized = msg.SerializeToString(&data);
289 #endif
290 
291  if (serialized) {
292  message_header.component_id = htons(component_id);
293  message_header.msg_type = htons(msg_type);
294  frame_header.payload_size = htonl(sizeof(message_header) + data.size());
295  } else {
296  throw std::runtime_error("Cannot serialize message");
297  }
298 }
299 
300 /** Deserialize message.
301  * @param frame_header incoming message's frame header
302  * @param message_header incoming message's message header
303  * @param data incoming message's data buffer
304  * @return new instance of a protobuf message type that has been registered
305  * for the given type.
306  * @exception std::runtime_error thrown if anything goes wrong when
307  * deserializing the message, e.g. if no protobuf message has been registered
308  * for the given component ID and message type.
309  */
310 std::shared_ptr<google::protobuf::Message>
312  message_header_t &message_header,
313  void * data)
314 {
315  uint16_t comp_id = ntohs(message_header.component_id);
316  uint16_t msg_type = ntohs(message_header.msg_type);
317  size_t data_size = ntohl(frame_header.payload_size) - sizeof(message_header);
318 
319  std::shared_ptr<google::protobuf::Message> m = new_message_for(comp_id, msg_type);
320  if (!m->ParseFromArray(data, data_size)) {
321  throw std::runtime_error("Failed to parse message");
322  }
323 
324  return m;
325 }
326 
327 } // end namespace protobuf_comm
protobuf_comm::MessageRegister::serialize
void serialize(uint16_t component_id, uint16_t msg_type, const google::protobuf::Message &msg, frame_header_t &frame_header, message_header_t &message_header, std::string &data)
Serialize a message.
Definition: message_register.cpp:271
protobuf_comm::MessageRegister::MessageRegister
MessageRegister()
Constructor.
Definition: message_register.cpp:57
protobuf_comm::message_header_t
Network message header.
Definition: frame_header.h:98
protobuf_comm::frame_header_t
Network framing header.
Definition: frame_header.h:72
protobuf_comm::MessageRegister::remove_message_type
void remove_message_type(uint16_t component_id, uint16_t msg_type)
Remove the given message type.
Definition: message_register.cpp:177
protobuf_comm::MessageRegister::deserialize
std::shared_ptr< google::protobuf::Message > deserialize(frame_header_t &frame_header, message_header_t &message_header, void *data)
Deserialize message.
Definition: message_register.cpp:311
protobuf_comm::MessageRegister::add_message_type
void add_message_type()
Add a new message type.
Definition: message_register.h:111
protobuf_comm::message_header_t::component_id
uint16_t component_id
component id
Definition: frame_header.h:100
protobuf_comm::MessageRegister::~MessageRegister
~MessageRegister()
Destructor.
Definition: message_register.cpp:107
protobuf_comm::message_header_t::msg_type
uint16_t msg_type
message type
Definition: frame_header.h:102
protobuf_comm::frame_header_t::payload_size
uint32_t payload_size
payload size in bytes includes message and header, not IV
Definition: frame_header.h:84
protobuf_comm::MessageRegister::new_message_for
std::shared_ptr< google::protobuf::Message > new_message_for(uint16_t component_id, uint16_t msg_type)
Create a new message instance.
Definition: message_register.cpp:217