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 <protobuf_comm/message_register.h>
38 
39 #include <google/protobuf/compiler/importer.h>
40 #include <google/protobuf/dynamic_message.h>
41 #include <netinet/in.h>
42 #include <sys/types.h>
43 #include <dirent.h>
44 #include <fnmatch.h>
45 
46 namespace protobuf_comm {
47 #if 0 /* just to make Emacs auto-indent happy */
48 }
49 #endif
50 
51 
52 /** @class MessageRegister <protobuf_comm/message_register.h>
53  * Register to map msg type numbers to Protobuf messages.
54  * The register is used to automatically parse incoming messages to the
55  * appropriate type. In your application, you need to register any
56  * message you want to read. All unknown messages are silently dropped.
57  * @author Tim Niemueller
58  */
59 
60 /** Constructor. */
62 {
63  pb_srctree_ = NULL;
64  pb_importer_ = NULL;
65  pb_factory_ = NULL;
66 }
67 
68 
69 /** Constructor.
70  * @param proto_path file paths to search for proto files. All message types
71  * within these files will automatically be registered and available for dynamic
72  * message creation.
73  */
74 MessageRegister::MessageRegister(std::vector<std::string> &proto_path)
75 {
76  pb_srctree_ = new google::protobuf::compiler::DiskSourceTree();
77  for (size_t i = 0; i < proto_path.size(); ++i) {
78  pb_srctree_->MapPath("", proto_path[i]);
79  }
80  pb_importer_ = new google::protobuf::compiler::Importer(pb_srctree_, NULL);
81  pb_factory_ = new google::protobuf::DynamicMessageFactory(pb_importer_->pool());
82 
83  for (size_t i = 0; i < proto_path.size(); ++i) {
84  DIR *dir;
85  struct dirent *ent;
86  if ((dir = opendir(proto_path[i].c_str())) != NULL) {
87  while ((ent = readdir(dir)) != NULL) {
88  if (fnmatch("*.proto", ent->d_name, FNM_PATHNAME) != FNM_NOMATCH) {
89  //printf ("%s\n", ent->d_name);
90  const google::protobuf::FileDescriptor *fd =
91  pb_importer_->Import(ent->d_name);
92  for (int i = 0; i < fd->message_type_count(); ++i) {
93  const google::protobuf::Descriptor *desc = fd->message_type(i);
94  //printf(" Type: %s\n", desc->full_name().c_str());
95  if (! desc->FindEnumTypeByName("CompType")) continue;
96 
97  try {
98  add_message_type(desc->full_name());
99  } catch (std::logic_error &e) {
100  // cannot open for some reason
101  failed_to_load_types_.insert(std::make_pair(desc->full_name(), e.what()));
102  }
103  }
104  }
105  }
106  closedir (dir);
107  }
108  }
109 }
110 
111 /** Destructor. */
113 {
114  TypeMap::iterator m;
115  for (m = message_by_comp_type_.begin(); m != message_by_comp_type_.end(); ++m) {
116  delete m->second;
117  }
118  delete pb_factory_;
119  delete pb_importer_;
120  delete pb_srctree_;
121 }
122 
123 
124 google::protobuf::Message *
125 MessageRegister::create_msg(std::string &msg_type)
126 {
127  const google::protobuf::DescriptorPool *pool =
128  google::protobuf::DescriptorPool::generated_pool();
129  google::protobuf::MessageFactory *factory =
130  google::protobuf::MessageFactory::generated_factory();
131 
132  const google::protobuf::Descriptor *desc = pool->FindMessageTypeByName(msg_type);
133  if (desc) {
134  return factory->GetPrototype(desc)->New();
135  } else if (pb_importer_) {
136  pool = pb_importer_->pool();
137  factory = pb_factory_;
138 
139  const google::protobuf::Descriptor *cdesc = pool->FindMessageTypeByName(msg_type);
140  if (cdesc) {
141  return factory->GetPrototype(cdesc)->New();
142  }
143  }
144  return NULL;
145 }
146 
147 /** Add a message type from generated pool.
148  * This will check all message libraries for a type of the given name
149  * and if found registers it.
150  * @param msg_type the full name of the message type to add, i.e. including
151  * a package name if the message type has one. The message must have been
152  * registered with either the generated messages pool or with the pool
153  * associated with the proto paths passed to the constructor.
154  */
155 void
157 {
158  google::protobuf::Message *m = create_msg(msg_type);
159  if (m) {
160  KeyType key = key_from_desc(m->GetDescriptor());
161  std::lock_guard<std::mutex> lock(maps_mutex_);
162  if (message_by_comp_type_.find(key) != message_by_comp_type_.end()) {
163 #if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))
164  std::string msg = "Message type " + std::to_string((long long)key.first) + ":" +
165  std::to_string((long long)key.second) + " already registered";
166 #else
167  std::string msg = "Message type " + std::to_string(key.first) + ":" +
168  std::to_string(key.second) + " already registered";
169 #endif
170  throw std::runtime_error(msg);
171  }
172  //printf("Registering %s (%u:%u)\n", msg_type.c_str(), key.first, key.second);
173  message_by_comp_type_[key] = m;
174  message_by_typename_[m->GetTypeName()] = m;
175  } else {
176  throw std::runtime_error("Unknown message type");
177  }
178 }
179 
180 
181 /** Remove the given message type.
182  * @param component_id ID of component this message type belongs to
183  * @param msg_type message type
184  */
185 void
186 MessageRegister::remove_message_type(uint16_t component_id, uint16_t msg_type)
187 {
188  KeyType key(component_id, msg_type);
189  std::lock_guard<std::mutex> lock(maps_mutex_);
190  if (message_by_comp_type_.find(key) != message_by_comp_type_.end()) {
191  message_by_typename_.erase(message_by_comp_type_[key]->GetDescriptor()->full_name());
192  message_by_comp_type_.erase(key);
193  }
194 }
195 
196 
197 MessageRegister::KeyType
198 MessageRegister::key_from_desc(const google::protobuf::Descriptor *desc)
199 {
200  const google::protobuf::EnumDescriptor *enumdesc = desc->FindEnumTypeByName("CompType");
201  if (! enumdesc) {
202  throw std::logic_error("Message does not have CompType enum");
203  }
204  const google::protobuf::EnumValueDescriptor *compdesc =
205  enumdesc->FindValueByName("COMP_ID");
206  const google::protobuf::EnumValueDescriptor *msgtdesc =
207  enumdesc->FindValueByName("MSG_TYPE");
208  if (! compdesc || ! msgtdesc) {
209  throw std::logic_error("Message CompType enum hs no COMP_ID or MSG_TYPE value");
210  }
211  int comp_id = compdesc->number();
212  int msg_type = msgtdesc->number();
213  if (comp_id < 0 || comp_id > std::numeric_limits<uint16_t>::max()) {
214  throw std::logic_error("Message has invalid COMP_ID");
215  }
216  if (msg_type < 0 || msg_type > std::numeric_limits<uint16_t>::max()) {
217  throw std::logic_error("Message has invalid MSG_TYPE");
218  }
219  return KeyType(comp_id, msg_type);
220 }
221 
222 /** Create a new message instance.
223  * @param component_id ID of component this message type belongs to
224  * @param msg_type message type
225  * @return new instance of a protobuf message that has been registered
226  * for the given message type.
227  */
228 std::shared_ptr<google::protobuf::Message>
229 MessageRegister::new_message_for(uint16_t component_id, uint16_t msg_type)
230 {
231  KeyType key(component_id, msg_type);
232 
233  std::lock_guard<std::mutex> lock(maps_mutex_);
234  if (message_by_comp_type_.find(key) == message_by_comp_type_.end()) {
235 #if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))
236  std::string msg = "Message type " + std::to_string((long long)component_id) + ":" +
237  std::to_string((long long)msg_type) + " not registered";
238 #else
239  std::string msg = "Message type " + std::to_string(component_id) + ":" +
240  std::to_string(msg_type) + " not registered";
241 #endif
242  throw std::runtime_error(msg);
243  }
244 
245  google::protobuf::Message *m = message_by_comp_type_[key]->New();
246  return std::shared_ptr<google::protobuf::Message>(m);
247 }
248 
249 
250 /** Create a new message instance.
251  * @param full_name full message type name, i.e. the message type name
252  * possibly with a package name prefix.
253  * @return new instance of a protobuf message that has been registered
254  * for the given message type.
255  */
256 std::shared_ptr<google::protobuf::Message>
257 MessageRegister::new_message_for(std::string &full_name)
258 {
259  std::lock_guard<std::mutex> lock(maps_mutex_);
260  if (message_by_typename_.find(full_name) == message_by_typename_.end()) {
261  google::protobuf::Message *m = create_msg(full_name);
262  if (m) {
263  return std::shared_ptr<google::protobuf::Message>(m);
264  } else {
265  throw std::runtime_error("Message type not registered");
266  }
267  } else {
268  google::protobuf::Message *m = message_by_typename_[full_name]->New();
269  return std::shared_ptr<google::protobuf::Message>(m);
270  }
271 }
272 
273 
274 /** Serialize a message.
275  * @param component_id ID of component this message type belongs to
276  * @param msg_type message type
277  * @param msg message to seialize
278  * @param frame_header upon return, the frame header is filled out according to
279  * the given information and message.
280  * @param message_header upon return, the frame header is filled out according to
281  * the given information and message.
282  * @param data upon return, contains the serialized message
283  */
284 void
285 MessageRegister::serialize(uint16_t component_id, uint16_t msg_type,
286  google::protobuf::Message &msg,
287  frame_header_t &frame_header,
288  message_header_t &message_header,
289  std::string &data)
290 {
291  bool serialized = false;
292 #if GOOGLE_PROTOBUF_VERSION >= 2004000
293  try {
294  serialized = msg.SerializeToString(&data);
295  } catch (google::protobuf::FatalException &e) {
296  std::string msg = std::string("Failed to serialize message: ") + e.what();
297  throw std::runtime_error(msg);
298  }
299 #else
300  // No exceptions in earlier versions
301  serialized = msg.SerializeToString(&data);
302 #endif
303 
304 
305  if (serialized) {
306  message_header.component_id = htons(component_id);
307  message_header.msg_type = htons(msg_type);
308  frame_header.payload_size = htonl(sizeof(message_header) + data.size());
309  } else {
310  throw std::runtime_error("Cannot serialize message");
311  }
312 }
313 
314 
315 /** Deserialize message.
316  * @param frame_header incoming message's frame header
317  * @param message_header incoming message's message header
318  * @param data incoming message's data buffer
319  * @return new instance of a protobuf message type that has been registered
320  * for the given type.
321  * @exception std::runtime_error thrown if anything goes wrong when
322  * deserializing the message, e.g. if no protobuf message has been registered
323  * for the given component ID and message type.
324  */
325 std::shared_ptr<google::protobuf::Message>
326 MessageRegister::deserialize(frame_header_t &frame_header, message_header_t &message_header, void *data)
327 {
328  uint16_t comp_id = ntohs(message_header.component_id);
329  uint16_t msg_type = ntohs(message_header.msg_type);
330  size_t data_size = ntohl(frame_header.payload_size) - sizeof(message_header);
331 
332  std::shared_ptr<google::protobuf::Message> m =
333  new_message_for(comp_id, msg_type);
334  if (! m->ParseFromArray(data, data_size)) {
335  throw std::runtime_error("Failed to parse message");
336  }
337 
338  return m;
339 }
340 
341 } // end namespace protobuf_comm
uint16_t msg_type
message type
Definition: frame_header.h:103
void remove_message_type(uint16_t component_id, uint16_t msg_type)
Remove the given message type.
std::shared_ptr< google::protobuf::Message > deserialize(frame_header_t &frame_header, message_header_t &message_header, void *data)
Deserialize message.
Network framing header.
Definition: frame_header.h:74
void serialize(uint16_t component_id, uint16_t msg_type, google::protobuf::Message &msg, frame_header_t &frame_header, message_header_t &message_header, std::string &data)
Serialize a message.
uint32_t payload_size
payload size in bytes includes message and header, not IV
Definition: frame_header.h:86
std::shared_ptr< google::protobuf::Message > new_message_for(uint16_t component_id, uint16_t msg_type)
Create a new message instance.
Network message header.
Definition: frame_header.h:99
std::enable_if< std::is_base_of< google::protobuf::Message, MT >::value, void >::type add_message_type()
Add a new message type.
uint16_t component_id
component id
Definition: frame_header.h:101