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