// Filename: dcPacker.cxx // Created by: drose (15Jun04) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved // // All use of this software is subject to the terms of the Panda 3d // Software license. You should have received a copy of this license // along with this source code; you will also find a current copy of // the license at http://etc.cmu.edu/panda3d/docs/license/ . // // To contact the maintainers of this program write to // panda3d-general@lists.sourceforge.net . // //////////////////////////////////////////////////////////////////// #include "dcPacker.h" #include "dcParserDefs.h" #include "dcLexerDefs.h" //////////////////////////////////////////////////////////////////// // Function: DCPacker::Constructor // Access: Published // Description: //////////////////////////////////////////////////////////////////// DCPacker:: DCPacker() { _mode = M_idle; _unpack_data = NULL; _unpack_length = 0; _owns_unpack_data = false; _unpack_p = 0; _root = NULL; _catalog = NULL; _live_catalog = NULL; _current_field = NULL; _current_parent = NULL; _current_field_index = 0; _num_nested_fields = 0; _pack_error = false; _range_error = false; } //////////////////////////////////////////////////////////////////// // Function: DCPacker::Destructor // Access: Published // Description: //////////////////////////////////////////////////////////////////// DCPacker:: ~DCPacker() { clear(); } //////////////////////////////////////////////////////////////////// // Function: DCPacker::begin_pack // Access: Published // Description: Begins a packing session. The parameter is the DC // object that describes the packing format; it may be a // DCParameter or DCField. //////////////////////////////////////////////////////////////////// void DCPacker:: begin_pack(const DCPackerInterface *root) { nassertv(_mode == M_idle); _mode = M_pack; _pack_error = false; _range_error = false; _pack_data.clear(); _root = root; _catalog = NULL; _live_catalog = NULL; _current_field = root; _current_parent = NULL; _current_field_index = 0; _num_nested_fields = 0; } //////////////////////////////////////////////////////////////////// // Function: DCPacker::end_pack // Access: Published, Virtual // Description: Finishes a packing session. // // The return value is true on success, or false if // there has been some error during packing. //////////////////////////////////////////////////////////////////// bool DCPacker:: end_pack() { nassertr(_mode == M_pack, false); _mode = M_idle; if (!_stack.empty() || _current_field != NULL || _current_parent != NULL) { _pack_error = true; } clear(); return !_pack_error && !_range_error; } //////////////////////////////////////////////////////////////////// // Function: DCPacker::begin_unpack // Access: Published // Description: Begins an unpacking session. Unlike the other // version of begin_unpack(), this version makes a copy // of the data string. //////////////////////////////////////////////////////////////////// void DCPacker:: begin_unpack(const string &data, const DCPackerInterface *root) { _unpack_str = data; begin_unpack(_unpack_str.data(), _unpack_str.length(), root); } //////////////////////////////////////////////////////////////////// // Function: DCPacker::begin_unpack // Access: Public // Description: Begins an unpacking session. The data pointer is // used directly; the data buffer is not copied. // Therefore, you must not delete or modify the data // pointer until you call end_unpack(). //////////////////////////////////////////////////////////////////// void DCPacker:: begin_unpack(const char *data, size_t length, const DCPackerInterface *root) { nassertv(_mode == M_idle); _mode = M_unpack; _pack_error = false; _range_error = false; set_unpack_data(data, length, false); _unpack_p = 0; _root = root; _catalog = NULL; _live_catalog = NULL; _current_field = root; _current_parent = NULL; _current_field_index = 0; _num_nested_fields = 0; } //////////////////////////////////////////////////////////////////// // Function: DCPacker::end_unpack // Access: Published // Description: Finishes the unpacking session. // // The return value is true on success, or false if // there has been some error during unpacking (or if all // fields have not been unpacked). //////////////////////////////////////////////////////////////////// bool DCPacker:: end_unpack() { nassertr(_mode == M_unpack, false); _mode = M_idle; if (!_stack.empty() || _current_field != NULL || _current_parent != NULL) { // This happens if we have not unpacked all of the fields. // However, this is not an error if we have called seek() during // the unpack session (in which case the _catalog will be // non-NULL). On the other hand, if the catalog is still NULL, // then we have never called seek() and it is an error not to // unpack all values. if (_catalog == (DCPackerCatalog *)NULL) { _pack_error = true; } } clear(); return !_pack_error && !_range_error; } //////////////////////////////////////////////////////////////////// // Function: DCPacker::begin_repack // Access: Published // Description: Begins an repacking session. Unlike the other // version of begin_repack(), this version makes a copy // of the data string. //////////////////////////////////////////////////////////////////// void DCPacker:: begin_repack(const string &data, const DCPackerInterface *root) { _unpack_str = data; begin_repack(_unpack_str.data(), _unpack_str.length(), root); } //////////////////////////////////////////////////////////////////// // Function: DCPacker::begin_repack // Access: Public // Description: Begins an repacking session. The data pointer is // used directly; the data buffer is not copied. // Therefore, you must not delete or modify the data // pointer until you call end_repack(). // // When repacking, unlike in packing or unpacking modes, // you may not walk through the fields from beginning to // end, or even pack two consecutive fields at once. // Instead, you must call seek() for each field you wish // to modify and pack only that one field; then call // seek() again to modify another field. //////////////////////////////////////////////////////////////////// void DCPacker:: begin_repack(const char *data, size_t length, const DCPackerInterface *root) { nassertv(_mode == M_idle); _mode = M_repack; _pack_error = false; _range_error = false; set_unpack_data(data, length, false); _unpack_p = 0; // In repack mode, we immediately get the catalog, since we know // we'll need it. _root = root; _catalog = _root->get_catalog(); _live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length); // We don't begin at the first field in repack mode. Instead, you // must explicitly call seek(). _current_field = NULL; _current_parent = NULL; _current_field_index = 0; _num_nested_fields = 0; } //////////////////////////////////////////////////////////////////// // Function: DCPacker::end_repack // Access: Published // Description: Finishes the repacking session. // // The return value is true on success, or false if // there has been some error during repacking (or if all // fields have not been repacked). //////////////////////////////////////////////////////////////////// bool DCPacker:: end_repack() { nassertr(_mode == M_repack, false); // Put the rest of the data onto the pack stream. _pack_data.append_data(_unpack_data + _unpack_p, _unpack_length - _unpack_p); _mode = M_idle; clear(); return !_pack_error && !_range_error; } //////////////////////////////////////////////////////////////////// // Function: DCPacker::seek // Access: Published // Description: Sets the current unpack (or repack) position to the // named field. In unpack mode, the next call to // unpack_*() or push() will begin to read the named // field. In repack mode, the next call to pack_*() or // push() will modify the named field. // // Returns true if successful, false if the field is not // known (or if the packer is in an invalid mode). //////////////////////////////////////////////////////////////////// bool DCPacker:: seek(const string &field_name) { if (_mode == M_unpack) { if (_catalog == (DCPackerCatalog *)NULL) { _catalog = _root->get_catalog(); _live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length); } nassertr(_catalog != (DCPackerCatalog *)NULL, false); int entry_index = _catalog->find_entry_by_name(field_name); if (entry_index < 0) { // The field was not known. _pack_error = true; return false; } const DCPackerCatalog::Entry &entry = _catalog->get_entry(entry_index); // If we are seeking, we don't need to remember our current stack // position. _stack.clear(); _current_field = entry._field; _current_parent = entry._parent; _current_field_index = entry._field_index; _num_nested_fields = _current_parent->get_num_nested_fields(); _unpack_p = _live_catalog->get_begin(entry_index); return true; } else if (_mode == M_repack) { nassertr(_catalog != (DCPackerCatalog *)NULL, false); if (!_stack.empty() || _current_field != (DCPackerInterface *)NULL) { // It is an error to reseek while the stack is nonempty--that // means we haven't finished packing the current field. _pack_error = true; return false; } int entry_index = _catalog->find_entry_by_name(field_name); if (entry_index < 0) { // The field was not known. _pack_error = true; return false; } const DCPackerCatalog::Entry &entry = _catalog->get_entry(entry_index); size_t begin = _live_catalog->get_begin(entry_index); if (begin < _unpack_p) { // Whoops, we are seeking fields out-of-order. That means we // need to write the entire record and start again. _pack_data.append_data(_unpack_data + _unpack_p, _unpack_length - _unpack_p); size_t length = _pack_data.get_length(); char *buffer = _pack_data.take_data(); set_unpack_data(buffer, length, true); _unpack_p = 0; _catalog->release_live_catalog(_live_catalog); _live_catalog = _catalog->get_live_catalog(_unpack_data, _unpack_length); begin = _live_catalog->get_begin(entry_index); } // Now copy the bytes from _unpack_p to begin from the // _unpack_data to the _pack_data. These are the bytes we just // skipped over with the call to seek(). _pack_data.append_data(_unpack_data + _unpack_p, begin - _unpack_p); // And set the packer up to pack the indicated field (but no // subsequent fields). _current_field = entry._field; _current_parent = entry._parent; _current_field_index = entry._field_index; _num_nested_fields = 0; // this makes advance() stop after this field. _unpack_p = _live_catalog->get_end(entry_index); return true; } // Invalid mode. _pack_error = true; return false; } //////////////////////////////////////////////////////////////////// // Function: DCPacker::push // Access: Published // Description: Marks the beginning of a nested series of fields. // // This must be called before filling the elements of an // array or the individual fields in a structure field. // It must also be balanced by a matching pop(). // // It is necessary to use push() / pop() only if // has_nested_fields() returns true. //////////////////////////////////////////////////////////////////// void DCPacker:: push() { if (!has_nested_fields()) { _pack_error = true; } else { StackElement element; element._current_parent = _current_parent; element._current_field_index = _current_field_index; element._push_marker = _push_marker; _stack.push_back(element); _current_parent = _current_field; // Now deal with the length prefix that might or might not be // before a sequence of nested fields. int num_nested_fields = _current_parent->get_num_nested_fields(); size_t length_bytes = _current_parent->get_num_length_bytes(); if (_mode == M_pack || _mode == M_repack) { // Reserve length_bytes for when we figure out what the length // is. _push_marker = _pack_data.get_length(); _pack_data.append_junk(length_bytes); } else if (_mode == M_unpack) { // Read length_bytes to determine the end of this nested // sequence. _push_marker = 0; if (length_bytes != 0) { if (_unpack_p + length_bytes > _unpack_length) { _pack_error = true; } else { size_t length; if (length_bytes == 4) { length = DCPackerInterface::do_unpack_uint32 (_unpack_data + _unpack_p); _unpack_p += 4; _push_marker = _unpack_p + length; } else { length = DCPackerInterface::do_unpack_uint16 (_unpack_data + _unpack_p); _unpack_p += 2; } _push_marker = _unpack_p + length; // The explicit length trumps the number of nested fields // reported by get_num_nested_fields(). if (length == 0) { num_nested_fields = 0; } else { num_nested_fields = _current_parent->calc_num_nested_fields(length); } } } } else { _pack_error = true; } // Now point to the first field in the nested range. _num_nested_fields = num_nested_fields; _current_field_index = 0; if (_num_nested_fields >= 0 && _current_field_index >= _num_nested_fields) { _current_field = NULL; } else { _current_field = _current_parent->get_nested_field(_current_field_index); } } } //////////////////////////////////////////////////////////////////// // Function: DCPacker::pop // Access: Published // Description: Marks the end of a nested series of fields. // // This must be called to match a previous push() only // after all the expected number of nested fields have // been packed. It is an error to call it too early, or // too late. //////////////////////////////////////////////////////////////////// void DCPacker:: pop() { if (_current_field != NULL && _num_nested_fields >= 0) { // Oops, didn't pack or unpack enough values. _pack_error = true; } else if (_mode == M_unpack && _push_marker != 0 && _unpack_p != _push_marker) { // Didn't unpack the right number of values. _pack_error = true; } if (_stack.empty()) { // Unbalanced pop(). _pack_error = true; } else { if (!_current_parent->validate_num_nested_fields(_current_field_index)) { // Incorrect number of nested elements. _pack_error = true; } if (_mode == M_pack || _mode == M_repack) { size_t length_bytes = _current_parent->get_num_length_bytes(); if (length_bytes != 0) { // Now go back and fill in the length of the array. size_t length = _pack_data.get_length() - _push_marker - length_bytes; if (length_bytes == 4) { DCPackerInterface::do_pack_uint32 (_pack_data.get_rewrite_pointer(_push_marker, 4), length); } else { DCPackerInterface::validate_uint_limits(length, 16, _range_error); DCPackerInterface::do_pack_uint16 (_pack_data.get_rewrite_pointer(_push_marker, 2), length); } } } _current_field = _current_parent; _current_parent = _stack.back()._current_parent; _current_field_index = _stack.back()._current_field_index; _push_marker = _stack.back()._push_marker; _num_nested_fields = (_current_parent == NULL) ? 0 : _current_parent->get_num_nested_fields(); _stack.pop_back(); } advance(); } //////////////////////////////////////////////////////////////////// // Function: DCPacker::unpack_validate // Access: Published // Description: Internally unpacks the current numeric or string // value and validates it against the type range limits, // but does not return the value. If the current field // contains nested fields, validates all of them. //////////////////////////////////////////////////////////////////// void DCPacker:: unpack_validate() { nassertv(_mode == M_unpack); if (_current_field == NULL) { _pack_error = true; } else { if (_current_field->unpack_validate(_unpack_data, _unpack_length, _unpack_p, _pack_error, _range_error)) { advance(); } else { // If the single field couldn't be validated, try validating // nested fields. push(); while (more_nested_fields()) { unpack_validate(); } pop(); } } } //////////////////////////////////////////////////////////////////// // Function: DCPacker::unpack_skip // Access: Published // Description: Skips the current field without unpacking it and // advances to the next field. If the current field // contains nested fields, skips all of them. //////////////////////////////////////////////////////////////////// void DCPacker:: unpack_skip() { nassertv(_mode == M_unpack); if (_current_field == NULL) { _pack_error = true; } else if (_current_field->has_fixed_byte_size()) { _unpack_p += _current_field->get_fixed_byte_size(); advance(); } else { if (_current_field->unpack_skip(_unpack_data, _unpack_length, _unpack_p)) { advance(); } else { // If the single field couldn't be skipped, try skipping nested fields. push(); while (more_nested_fields()) { unpack_skip(); } pop(); } } } #ifdef HAVE_PYTHON //////////////////////////////////////////////////////////////////// // Function: DCPacker::pack_object // Access: Published // Description: Packs the Python object of whatever type into the // packer. Each numeric object and string object maps // to the corresponding pack_value() call; a tuple or // sequence maps to a push() followed by all of the // tuple's contents followed by a pop(). //////////////////////////////////////////////////////////////////// void DCPacker:: pack_object(PyObject *object) { nassertv(_mode == M_pack || _mode == M_repack); PyObject *str = PyObject_Str(object); Py_DECREF(str); if (PyInt_Check(object)) { pack_int(PyInt_AS_LONG(object)); } else if (PyFloat_Check(object)) { pack_double(PyFloat_AS_DOUBLE(object)); } else if (PyLong_Check(object)) { pack_int64(PyLong_AsLongLong(object)); } else if (PyString_Check(object) || PyUnicode_Check(object)) { char *buffer; int length; PyString_AsStringAndSize(object, &buffer, &length); if (buffer) { pack_string(string(buffer, length)); } } else if (PySequence_Check(object)) { push(); int size = PySequence_Size(object); for (int i = 0; i < size; i++) { PyObject *element = PySequence_GetItem(object, i); pack_object(element); Py_DECREF(element); } pop(); } } #endif // HAVE_PYTHON #ifdef HAVE_PYTHON //////////////////////////////////////////////////////////////////// // Function: DCPacker::unpack_object // Access: Published // Description: Unpacks a Python object of the appropriate type from // the stream for the current field. This may be an // integer or a string for a simple field object; if the // current field represents a list of fields it will be // a tuple. //////////////////////////////////////////////////////////////////// PyObject *DCPacker:: unpack_object() { PyObject *object = NULL; DCPackType pack_type = get_pack_type(); switch (pack_type) { case PT_invalid: object = Py_None; unpack_skip(); break; case PT_double: { double value = unpack_double(); object = PyFloat_FromDouble(value); } break; case PT_int: { int value = unpack_int(); object = PyInt_FromLong(value); } break; case PT_uint: { unsigned int value = unpack_uint(); if (value & 0x80000000) { object = PyLong_FromUnsignedLong(value); } else { object = PyInt_FromLong(value); } } break; case PT_int64: { PN_int64 value = unpack_int64(); object = PyLong_FromLongLong(value); } break; case PT_uint64: { PN_uint64 value = unpack_uint64(); object = PyLong_FromUnsignedLongLong(value); } break; case PT_string: { string str = unpack_string(); object = PyString_FromStringAndSize(str.data(), str.size()); } break; default: { // First, build up a list from the nested objects. object = PyList_New(0); push(); while (more_nested_fields()) { PyObject *element = unpack_object(); PyList_Append(object, element); Py_DECREF(element); } pop(); if (pack_type != PT_array) { // For these other kinds of objects, we'll convert the list // into a tuple. PyObject *tuple = PyList_AsTuple(object); Py_DECREF(object); object = tuple; } } break; } return object; } #endif // HAVE_PYTHON //////////////////////////////////////////////////////////////////// // Function: DCPacker::parse_and_pack // Access: Published // Description: Parses an object's value according to the DC file // syntax (e.g. as a default value string) and packs it. // Returns true on success, false on a parse error. //////////////////////////////////////////////////////////////////// bool DCPacker:: parse_and_pack(const string &formatted_object) { istringstream strm(formatted_object); return parse_and_pack(strm); } //////////////////////////////////////////////////////////////////// // Function: DCPacker::parse_and_pack // Access: Published // Description: Parses an object's value according to the DC file // syntax (e.g. as a default value string) and packs it. // Returns true on success, false on a parse error. //////////////////////////////////////////////////////////////////// bool DCPacker:: parse_and_pack(istream &in) { dc_init_parser_parameter_value(in, "parse_and_pack", *this); dcyyparse(); dc_cleanup_parser(); return (dc_error_count() == 0); } //////////////////////////////////////////////////////////////////// // Function: DCPacker::unpack_and_format // Access: Published // Description: Unpacks an object and formats its value into a syntax // suitable for parsing in the dc file (e.g. as a // default value), or as an input to parse_object. //////////////////////////////////////////////////////////////////// string DCPacker:: unpack_and_format() { ostringstream strm; unpack_and_format(strm); return strm.str(); } //////////////////////////////////////////////////////////////////// // Function: DCPacker::unpack_and_format // Access: Published // Description: Unpacks an object and formats its value into a syntax // suitable for parsing in the dc file (e.g. as a // default value), or as an input to parse_object. //////////////////////////////////////////////////////////////////// void DCPacker:: unpack_and_format(ostream &out) { DCPackType pack_type = get_pack_type(); switch (pack_type) { case PT_invalid: out << ""; break; case PT_double: out << unpack_double(); break; case PT_int: out << unpack_int(); break; case PT_uint: out << unpack_uint(); break; case PT_int64: out << unpack_int64(); break; case PT_uint64: out << unpack_uint64(); break; case PT_string: out << '"' << unpack_string() << '"'; break; default: { switch (pack_type) { case PT_array: out << '['; break; case PT_field: out << '('; break; case PT_class: default: out << '{'; break; } push(); while (more_nested_fields()) { unpack_and_format(out); if (more_nested_fields()) { out << ", "; } } pop(); switch (pack_type) { case PT_array: out << ']'; break; case PT_field: out << ')'; break; case PT_class: default: out << '}'; break; } } break; } } //////////////////////////////////////////////////////////////////// // Function: DCPacker::clear // Access: Private // Description: Resets the data structures after a pack or unpack // sequence. //////////////////////////////////////////////////////////////////// void DCPacker:: clear() { _stack.clear(); _current_field = NULL; _current_parent = NULL; _current_field_index = 0; _num_nested_fields = 0; if (_live_catalog != (DCPackerCatalog::LiveCatalog *)NULL) { _catalog->release_live_catalog(_live_catalog); _live_catalog = NULL; } _catalog = NULL; _root = NULL; set_unpack_data(NULL, 0, false); } //////////////////////////////////////////////////////////////////// // Function: DCPacker::set_unpack_data // Access: Private // Description: Sets up the unpack_data pointer. //////////////////////////////////////////////////////////////////// void DCPacker:: set_unpack_data(const char *unpack_data, size_t unpack_length, bool owns_unpack_data) { if (_owns_unpack_data) { delete[] _unpack_data; } _unpack_data = unpack_data; _unpack_length = unpack_length; _owns_unpack_data = owns_unpack_data; }