1 #include "core/global.h"
2 #include "core/msgtypes.h"
3 #include "config/constraints.h"
4 #include "dclass/dc/Class.h"
5 #include "dclass/dc/Field.h"
6 #include <unordered_set>
7
8 #include "DBStateServer.h"
9 #include "LoadingObject.h"
10
11 using dclass::Class;
12 using dclass::Field;
13
14 static RoleFactoryItem<DBStateServer> dbss_fact("dbss");
15
16 static RoleConfigGroup dbss_config("dbss");
17 static ConfigVariable<channel_t> database_channel("database", INVALID_CHANNEL, dbss_config);
18 static InvalidChannelConstraint db_channel_not_invalid(database_channel);
19 static ReservedChannelConstraint db_channel_not_reserved(database_channel);
20
21 static ConfigList ranges_config("ranges", dbss_config);
22 static ConfigVariable<doid_t> range_min("min", INVALID_DO_ID, ranges_config);
23 static ConfigVariable<doid_t> range_max("max", DOID_MAX, ranges_config);
24 static InvalidDoidConstraint min_not_invalid(range_min);
25 static InvalidDoidConstraint max_not_invalid(range_max);
26 static ReservedDoidConstraint min_not_reserved(range_min);
27 static ReservedDoidConstraint max_not_reserved(range_max);
28
DBStateServer(RoleConfig roleconfig)29 DBStateServer::DBStateServer(RoleConfig roleconfig) : StateServer(roleconfig),
30 m_db_channel(database_channel.get_rval(m_roleconfig)), m_next_context(0)
31 {
32 ConfigNode ranges = dbss_config.get_child_node(ranges_config, roleconfig);
33 for(const auto& it : ranges) {
34 channel_t min = range_min.get_rval(it);
35 channel_t max = range_max.get_rval(it);
36 subscribe_range(min, max);
37 }
38
39 std::stringstream name;
40 name << "DBSS(Database: " << m_db_channel << ")";
41 m_log = std::unique_ptr<LogCategory>(new LogCategory("dbss", name.str()));
42 set_con_name(name.str());
43 }
44
handle_datagram(DatagramHandle,DatagramIterator & dgi)45 void DBStateServer::handle_datagram(DatagramHandle, DatagramIterator &dgi)
46 {
47 channel_t sender = dgi.read_channel();
48 uint16_t msgtype = dgi.read_uint16();
49 switch(msgtype) {
50 case DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS:
51 handle_activate(dgi, false);
52 break;
53 case DBSS_OBJECT_ACTIVATE_WITH_DEFAULTS_OTHER:
54 handle_activate(dgi, true);
55 break;
56 case DBSS_OBJECT_DELETE_DISK:
57 handle_delete_disk(sender, dgi);
58 break;
59 case STATESERVER_OBJECT_SET_FIELD:
60 handle_set_field(dgi);
61 break;
62 case STATESERVER_OBJECT_SET_FIELDS:
63 handle_set_fields(dgi);
64 break;
65 case STATESERVER_OBJECT_GET_FIELD:
66 handle_get_field(sender, dgi);
67 break;
68 case DBSERVER_OBJECT_GET_FIELD_RESP:
69 handle_get_field_resp(dgi);
70 break;
71 case STATESERVER_OBJECT_GET_FIELDS:
72 handle_get_fields(sender, dgi);
73 break;
74 case DBSERVER_OBJECT_GET_FIELDS_RESP:
75 handle_get_fields_resp(dgi);
76 break;
77 case STATESERVER_OBJECT_GET_ALL:
78 handle_get_all(sender, dgi);
79 break;
80 case DBSERVER_OBJECT_GET_ALL_RESP:
81 handle_get_all_resp(dgi);
82 break;
83 case DBSS_OBJECT_GET_ACTIVATED:
84 handle_get_activated(sender, dgi);
85 break;
86 default:
87 m_log->trace() << "Ignoring message of type '" << msgtype << "'.\n";
88 }
89 }
90
handle_activate(DatagramIterator & dgi,bool has_other)91 void DBStateServer::handle_activate(DatagramIterator &dgi, bool has_other)
92 {
93 doid_t do_id = dgi.read_doid();
94 doid_t parent_id = dgi.read_doid();
95 zone_t zone_id = dgi.read_zone();
96
97 // Check object is not already active
98 if(m_objs.find(do_id) != m_objs.end() || m_loading.find(do_id) != m_loading.end()) {
99 m_log->warning() << "Received activate for already-active object with id " << do_id << "\n";
100 return;
101 }
102
103 if(!has_other) {
104 auto load_it = m_inactive_loads.find(do_id);
105 if(load_it == m_inactive_loads.end()) {
106 m_loading[do_id] = new LoadingObject(this, do_id, parent_id, zone_id);
107 m_loading[do_id]->begin();
108 } else {
109 m_loading[do_id] = new LoadingObject(this, do_id, parent_id, zone_id, load_it->second);
110 }
111 } else {
112 uint16_t dc_id = dgi.read_uint16();
113
114 // Check id is a valid type id
115 if(dc_id >= g_dcf->get_num_types()) {
116 m_log->error() << "Received activate_other with unknown dclass"
117 " with id " << dc_id << "\n";
118 return;
119 }
120
121 const Class *dcc = g_dcf->get_class_by_id(dc_id);
122 if(!dcc) {
123 m_log->error() << "Tried to activate_other with non-class distributed_type #" << dc_id << "\n";
124 }
125 auto load_it = m_inactive_loads.find(do_id);
126 if(load_it == m_inactive_loads.end()) {
127 m_loading[do_id] = new LoadingObject(this, do_id, parent_id, zone_id, dcc, dgi);
128 m_loading[do_id]->begin();
129 } else {
130 m_loading[do_id] = new LoadingObject(this, do_id, parent_id, zone_id, dcc, dgi, load_it->second);
131 }
132 }
133 }
134
handle_get_activated(channel_t sender,DatagramIterator & dgi)135 void DBStateServer::handle_get_activated(channel_t sender, DatagramIterator& dgi)
136 {
137 uint32_t r_context = dgi.read_uint32();
138 doid_t r_do_id = dgi.read_doid();
139 if(m_loading.find(r_do_id) != m_loading.end()) {
140 return;
141 }
142
143 m_log->trace() << "Received GetActivated for id " << r_do_id << "\n";
144
145 if(m_objs.find(r_do_id) != m_objs.end()) {
146 // If object is active return true
147 DatagramPtr dg = Datagram::create(sender, r_do_id, DBSS_OBJECT_GET_ACTIVATED_RESP);
148 dg->add_uint32(r_context);
149 dg->add_doid(r_do_id);
150 dg->add_bool(true);
151 route_datagram(dg);
152 } else {
153 // If object isn't active or loading, we can return false
154 DatagramPtr dg = Datagram::create(sender, r_do_id, DBSS_OBJECT_GET_ACTIVATED_RESP);
155 dg->add_uint32(r_context);
156 dg->add_doid(r_do_id);
157 dg->add_bool(false);
158 route_datagram(dg);
159 }
160 }
161
handle_delete_disk(channel_t sender,DatagramIterator & dgi)162 void DBStateServer::handle_delete_disk(channel_t sender, DatagramIterator& dgi)
163 {
164 doid_t do_id = dgi.read_doid();
165 if(m_loading.find(do_id) != m_loading.end()) {
166 // Ignore this message for now, it'll be bounced back to us
167 // from the loading object if it succeeds or fails at loading.
168 return;
169 }
170
171 // If object exists broadcast the delete message
172 auto obj_keyval = m_objs.find(do_id);
173 if(obj_keyval != m_objs.end()) {
174 DistributedObject* obj = obj_keyval->second;
175 std::unordered_set<channel_t> targets;
176
177 // Add location to broadcast
178 if(obj->get_location()) {
179 targets.insert(obj->get_location());
180 }
181
182 // Add AI to broadcast
183 if(obj->get_ai()) {
184 targets.insert(obj->get_ai());
185 }
186
187 // Add owner to broadcast
188 if(obj->get_owner()) {
189 targets.insert(obj->get_owner());
190 }
191
192 // Build and send datagram
193 DatagramPtr dg = Datagram::create(targets, sender, DBSS_OBJECT_DELETE_DISK);
194 dg->add_doid(do_id);
195 route_datagram(dg);
196 }
197
198 // Send delete to database
199 DatagramPtr dg = Datagram::create(m_db_channel, do_id, DBSERVER_OBJECT_DELETE);
200 dg->add_doid(do_id);
201 route_datagram(dg);
202
203 }
204
handle_set_field(DatagramIterator & dgi)205 void DBStateServer::handle_set_field(DatagramIterator &dgi)
206 {
207 doid_t do_id = dgi.read_doid();
208 if(m_loading.find(do_id) != m_loading.end()) {
209 // Ignore this message for now, it'll be bounced back to us
210 // from the loading object if it succeeds or fails at loading.
211 return;
212 }
213
214 uint16_t field_id = dgi.read_uint16();
215
216 const Field* field = g_dcf->get_field_by_id(field_id);
217 if(field && field->has_keyword("db")) {
218 m_log->trace() << "Forwarding SetField for field \"" << field->get_name()
219 << "\" on object with id " << do_id << " to database.\n";
220
221 DatagramPtr dg = Datagram::create(m_db_channel, do_id, DBSERVER_OBJECT_SET_FIELD);
222 dg->add_doid(do_id);
223 dg->add_uint16(field_id);
224 dg->add_data(dgi.read_remainder());
225 route_datagram(dg);
226 }
227 }
228
handle_set_fields(DatagramIterator & dgi)229 void DBStateServer::handle_set_fields(DatagramIterator &dgi)
230 {
231 doid_t do_id = dgi.read_doid();
232 if(m_loading.find(do_id) != m_loading.end()) {
233 // Ignore this message for now, it'll be bounced back to us
234 // from the loading object if it succeeds or fails at loading.
235 return;
236 }
237
238 uint16_t field_count = dgi.read_uint16();
239
240 FieldValues db_fields;
241 for(uint16_t i = 0; i < field_count; ++i) {
242 uint16_t field_id = dgi.read_uint16();
243 const Field* field = g_dcf->get_field_by_id(field_id);
244 if(!field) {
245 m_log->warning() << "Received invalid field with id " << field_id << " in SetFields.\n";
246 return;
247 }
248 if(field->has_keyword("db")) {
249 dgi.unpack_field(field, db_fields[field]);
250 } else {
251 dgi.skip_field(field);
252 }
253 }
254
255 if(db_fields.size() > 0) {
256 m_log->trace() << "Forwarding SetFields on object with id " << do_id << " to database.\n";
257
258 DatagramPtr dg = Datagram::create(m_db_channel, do_id, DBSERVER_OBJECT_SET_FIELDS);
259 dg->add_doid(do_id);
260 dg->add_uint16(db_fields.size());
261 for(const auto& it : db_fields) {
262 dg->add_uint16(it.first->get_id());
263 dg->add_data(it.second);
264 }
265 route_datagram(dg);
266 }
267 }
268
handle_get_field(channel_t sender,DatagramIterator & dgi)269 void DBStateServer::handle_get_field(channel_t sender, DatagramIterator &dgi)
270 {
271 uint32_t r_context = dgi.read_uint32();
272 doid_t r_do_id = dgi.read_doid();
273 uint16_t field_id = dgi.read_uint16();
274 if(is_activated_object(r_do_id)) {
275 return;
276 }
277
278 m_log->trace() << "Received GetField for field with id " << field_id
279 << " on inactive object with id " << r_do_id << "\n";
280
281 // Check field is "ram db" or "required"
282 const Field* field = g_dcf->get_field_by_id(field_id);
283 if(!field || !(field->has_keyword("required") || field->has_keyword("ram"))) {
284 DatagramPtr dg = Datagram::create(sender, r_do_id, STATESERVER_OBJECT_GET_FIELD_RESP);
285 dg->add_uint32(r_context);
286 dg->add_bool(false);
287 route_datagram(dg);
288 return;
289 }
290
291 if(field->has_keyword("db")) {
292 // Get context for db query
293 uint32_t db_context = m_next_context++;
294
295 // Prepare reponse datagram
296 DatagramPtr dg_resp = Datagram::create(sender, r_do_id, STATESERVER_OBJECT_GET_FIELD_RESP);
297 dg_resp->add_uint32(r_context);
298 m_context_datagrams[db_context] = dg_resp;
299
300 // Send query to database
301 DatagramPtr dg = Datagram::create(m_db_channel, r_do_id, DBSERVER_OBJECT_GET_FIELD);
302 dg->add_uint32(db_context);
303 dg->add_doid(r_do_id);
304 dg->add_uint16(field_id);
305 route_datagram(dg);
306 } else if(field->has_default_value()) { // Field is required and not-db
307 DatagramPtr dg = Datagram::create(sender, r_do_id, STATESERVER_OBJECT_GET_FIELD_RESP);
308 dg->add_uint32(r_context);
309 dg->add_bool(true);
310 dg->add_uint16(field_id);
311 dg->add_data(field->get_default_value());
312 route_datagram(dg);
313 } else {
314 DatagramPtr dg = Datagram::create(sender, r_do_id, STATESERVER_OBJECT_GET_FIELD_RESP);
315 dg->add_uint32(r_context);
316 dg->add_bool(false);
317 route_datagram(dg);
318 }
319 }
320
handle_get_field_resp(DatagramIterator & dgi)321 void DBStateServer::handle_get_field_resp(DatagramIterator& dgi)
322 {
323 uint32_t db_context = dgi.read_uint32();
324 if(!is_expected_context(db_context)) {
325 return;
326 }
327
328 // Get the datagram from the db_context
329 DatagramPtr dg = m_context_datagrams[db_context];
330 m_context_datagrams.erase(db_context);
331
332 // Check to make sure the datagram is appropriate
333 DatagramIterator check_dgi = DatagramIterator(dg);
334 uint16_t resp_type = check_dgi.get_msg_type();
335 if(resp_type != STATESERVER_OBJECT_GET_FIELD_RESP) {
336 if(resp_type == STATESERVER_OBJECT_GET_FIELDS_RESP) {
337 m_log->warning() << "Received GetFieldsResp, but expecting GetFieldResp." << std::endl;
338 } else if(resp_type == STATESERVER_OBJECT_GET_ALL_RESP) {
339 m_log->warning() << "Received GetAllResp, but expecting GetFieldResp." << std::endl;
340 }
341 return;
342 }
343
344 m_log->trace() << "Received GetFieldResp from database." << std::endl;
345
346 // Add database field payload to response (don't know dclass, so must copy payload) and send
347 dg->add_data(dgi.read_remainder());
348 route_datagram(dg);
349 }
350
handle_get_fields(channel_t sender,DatagramIterator & dgi)351 void DBStateServer::handle_get_fields(channel_t sender, DatagramIterator &dgi)
352 {
353 uint32_t r_context = dgi.read_uint32();
354 doid_t r_do_id = dgi.read_doid();
355 uint16_t field_count = dgi.read_uint16();
356 if(is_activated_object(r_do_id)) {
357 return;
358 }
359
360 m_log->trace() << "Received GetFields for inactive object with id " << r_do_id << std::endl;
361
362 // Read requested fields from datagram
363 std::vector<const Field*> db_fields; // Ram|required db fields in request
364 std::vector<const Field*> ram_fields; // Ram|required but not-db fields in request
365 for(uint16_t i = 0; i < field_count; ++i) {
366 uint16_t field_id = dgi.read_uint16();
367 const Field* field = g_dcf->get_field_by_id(field_id);
368 if(!field) {
369 DatagramPtr dg = Datagram::create(sender, r_do_id, STATESERVER_OBJECT_GET_FIELDS_RESP);
370 dg->add_uint32(r_context);
371 dg->add_uint8(false);
372 route_datagram(dg);
373 } else if(field->has_keyword("ram") || field->has_keyword("required")) {
374 if(field->has_keyword("db")) {
375 db_fields.push_back(field);
376 } else {
377 ram_fields.push_back(field);
378 }
379 }
380 }
381
382 if(db_fields.size()) {
383 // Get context for db query
384 uint32_t db_context = m_next_context++;
385
386 // Prepare reponse datagram
387 if(m_context_datagrams.find(db_context) == m_context_datagrams.end()) {
388 m_context_datagrams[db_context] = Datagram::create(sender, r_do_id,
389 STATESERVER_OBJECT_GET_FIELDS_RESP);
390 }
391 m_context_datagrams[db_context]->add_uint32(r_context);
392 m_context_datagrams[db_context]->add_bool(true);
393 m_context_datagrams[db_context]->add_uint16(ram_fields.size() + db_fields.size());
394 for(const auto& it : ram_fields) {
395 m_context_datagrams[db_context]->add_uint16(it->get_id());
396 m_context_datagrams[db_context]->add_data(it->get_default_value());
397 }
398
399 // Send query to database
400 DatagramPtr dg = Datagram::create(m_db_channel, r_do_id, DBSERVER_OBJECT_GET_FIELDS);
401 dg->add_uint32(db_context);
402 dg->add_doid(r_do_id);
403 dg->add_uint16(db_fields.size());
404 for(const auto& it : db_fields) {
405 dg->add_uint16(it->get_id());
406 }
407 route_datagram(dg);
408 } else { // If no database fields exist
409 DatagramPtr dg = Datagram::create(sender, r_do_id, STATESERVER_OBJECT_GET_FIELDS_RESP);
410 dg->add_uint32(r_context);
411 dg->add_bool(true);
412 dg->add_uint16(ram_fields.size());
413 for(const auto& it : ram_fields) {
414 dg->add_uint16(it->get_id());
415 dg->add_data(it->get_default_value());
416 }
417 route_datagram(dg);
418 }
419 }
420
handle_get_fields_resp(DatagramIterator & dgi)421 void DBStateServer::handle_get_fields_resp(DatagramIterator& dgi)
422 {
423 uint32_t db_context = dgi.read_uint32();
424 if(!is_expected_context(db_context)) {
425 return;
426 }
427
428 // Get the datagram from the db_context
429 DatagramPtr dg = m_context_datagrams[db_context];
430 m_context_datagrams.erase(db_context);
431
432 // Check to make sure the datagram is appropriate
433 DatagramIterator check_dgi = DatagramIterator(dg);
434 uint16_t resp_type = check_dgi.get_msg_type();
435 if(resp_type != STATESERVER_OBJECT_GET_FIELDS_RESP) {
436 if(resp_type == STATESERVER_OBJECT_GET_FIELD_RESP) {
437 m_log->warning() << "Received GetFieldResp, but expecting GetFieldsResp." << std::endl;
438 } else if(resp_type == STATESERVER_OBJECT_GET_ALL_RESP) {
439 m_log->warning() << "Received GetAllResp, but expecting GetFieldsResp." << std::endl;
440 }
441 return;
442 }
443
444 m_log->trace() << "Received GetFieldResp from database." << std::endl;
445
446 // Add database field payload to response (don't know dclass, so must copy payload).
447 if(dgi.read_bool() == true) {
448 dgi.read_uint16(); // Discard field count
449 dg->add_data(dgi.read_remainder());
450 }
451 route_datagram(dg);
452 }
453
454
handle_get_all(channel_t sender,DatagramIterator & dgi)455 void DBStateServer::handle_get_all(channel_t sender, DatagramIterator &dgi)
456 {
457 uint32_t r_context = dgi.read_uint32();
458 doid_t r_do_id = dgi.read_doid();
459 if(is_activated_object(r_do_id)) {
460 return;
461 }
462
463 m_log->trace() << "Received GetAll for inactive object with id " << r_do_id << std::endl;
464
465 // Get context for db query, and remember caller with it
466 uint32_t db_context = m_next_context++;
467
468 DatagramPtr resp_dg = Datagram::create(sender, r_do_id, STATESERVER_OBJECT_GET_ALL_RESP);
469 resp_dg->add_uint32(r_context);
470 resp_dg->add_doid(r_do_id);
471 resp_dg->add_channel(INVALID_CHANNEL); // Location
472 m_context_datagrams[db_context] = resp_dg;
473
474 // Cache the do_id --> context in case we get a dbss_activate
475 m_inactive_loads[r_do_id].insert(db_context);
476
477 // Send query to database
478 DatagramPtr dg = Datagram::create(m_db_channel, r_do_id, DBSERVER_OBJECT_GET_ALL);
479 dg->add_uint32(db_context);
480 dg->add_doid(r_do_id);
481 route_datagram(dg);
482 }
483
handle_get_all_resp(DatagramIterator & dgi)484 void DBStateServer::handle_get_all_resp(DatagramIterator& dgi)
485 {
486 uint32_t db_context = dgi.read_uint32();
487 if(!is_expected_context(db_context)) {
488 return;
489 }
490
491 // Get the datagram from the db_context
492 DatagramPtr dg = m_context_datagrams[db_context];
493 m_context_datagrams.erase(db_context);
494
495 // Check to make sure the datagram is appropriate
496 DatagramIterator check_dgi = DatagramIterator(dg);
497 uint16_t resp_type = check_dgi.get_msg_type();
498 if(resp_type != STATESERVER_OBJECT_GET_ALL_RESP) {
499 if(resp_type == STATESERVER_OBJECT_GET_FIELD_RESP) {
500 m_log->warning() << "Received GetFieldResp, but expecting GetAllResp." << std::endl;
501 } else if(resp_type == STATESERVER_OBJECT_GET_FIELDS_RESP) {
502 m_log->warning() << "Received GetFieldsResp, but expecting GetAllResp." << std::endl;
503 }
504 return;
505 }
506
507 // Get do_id from datagram
508 check_dgi.seek_payload();
509 check_dgi.skip(sizeof(channel_t) + sizeof(doid_t)); // skip over sender and context to do_id;
510 doid_t do_id = check_dgi.read_doid();
511
512 // Remove cached loading operation
513 if(m_inactive_loads[do_id].size() > 1) {
514 m_inactive_loads[do_id].erase(db_context);
515 } else {
516 m_inactive_loads.erase(do_id);
517 }
518
519 m_log->trace() << "Received GetAllResp from database." << std::endl;
520
521 // If object not found, just cleanup the context map
522 if(dgi.read_bool() != true) {
523 return; // Object not found
524 }
525
526 // Read object class
527 uint16_t dc_id = dgi.read_uint16();
528 if(!dc_id) {
529 m_log->error() << "Received object from database with unknown dclass"
530 << " - id:" << dc_id << std::endl;
531 return;
532 }
533 const Class* r_class = g_dcf->get_class_by_id(dc_id);
534
535 // Get fields from database
536 UnorderedFieldValues required_fields;
537 FieldValues ram_fields;
538 if(!unpack_db_fields(dgi, r_class, required_fields, ram_fields)) {
539 m_log->error() << "Error while unpacking fields from database." << std::endl;
540 return;
541 }
542
543 // Add class to response
544 dg->add_uint16(r_class->get_id());
545
546 // Add required fields to datagram
547 int dcc_field_count = r_class->get_num_fields();
548 for(int i = 0; i < dcc_field_count; ++i) {
549 const Field *field = r_class->get_field(i);
550 if(!field->as_molecular() && field->has_keyword("required")) {
551 auto req_it = required_fields.find(field);
552 if(req_it != required_fields.end()) {
553 dg->add_data(req_it->second);
554 } else {
555 dg->add_data(field->get_default_value());
556 }
557 }
558 }
559
560 // Add ram fields to datagram
561 dg->add_uint16(ram_fields.size());
562 for(const auto& it : ram_fields) {
563 dg->add_uint16(it.first->get_id());
564 dg->add_data(it.second);
565 }
566
567 // Send response back to caller
568 route_datagram(dg);
569 }
570
receive_object(DistributedObject * obj)571 void DBStateServer::receive_object(DistributedObject* obj)
572 {
573 m_objs[obj->get_id()] = obj;
574 }
575
discard_loader(doid_t do_id)576 void DBStateServer::discard_loader(doid_t do_id)
577 {
578 m_loading.erase(do_id);
579 }
580
is_expected_context(uint32_t context)581 bool DBStateServer::is_expected_context(uint32_t context)
582 {
583 return m_context_datagrams.find(context) != m_context_datagrams.end();
584 }
585
is_activated_object(doid_t do_id)586 bool DBStateServer::is_activated_object(doid_t do_id)
587 {
588 return m_objs.find(do_id) != m_objs.end() || m_loading.find(do_id) != m_loading.end();
589 }
590
591
unpack_db_fields(DatagramIterator & dgi,const Class * dc_class,UnorderedFieldValues & required,FieldValues & ram)592 bool unpack_db_fields(DatagramIterator &dgi, const Class* dc_class,
593 UnorderedFieldValues &required, FieldValues &ram)
594 {
595 // Unload ram and required fields from database resp
596 uint16_t db_field_count = dgi.read_uint16();
597 for(uint16_t i = 0; i < db_field_count; ++i) {
598 uint16_t field_id = dgi.read_uint16();
599 const Field *field = dc_class->get_field_by_id(field_id);
600 if(!field) {
601 return false;
602 }
603 if(field->has_keyword("required")) {
604 dgi.unpack_field(field, required[field]);
605 } else if(field->has_keyword("ram")) {
606 dgi.unpack_field(field, ram[field]);
607 } else {
608 dgi.skip_field(field);
609 }
610 }
611
612 return true;
613 }
614