binaryXml.cxx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. // Filename: binaryXml.cxx
  2. // Created by: drose (13Jul09)
  3. //
  4. ////////////////////////////////////////////////////////////////////
  5. //
  6. // PANDA 3D SOFTWARE
  7. // Copyright (c) Carnegie Mellon University. All rights reserved.
  8. //
  9. // All use of this software is subject to the terms of the revised BSD
  10. // license. You should have received a copy of this license along
  11. // with this source code in a file named "LICENSE."
  12. //
  13. ////////////////////////////////////////////////////////////////////
  14. #include "binaryXml.h"
  15. #include "p3d_lock.h"
  16. #include <sstream>
  17. static const bool debug_xml_output = false;
  18. static LOCK xml_lock;
  19. static bool xml_lock_initialized = false;
  20. #define DO_BINARY_XML 1
  21. enum NodeType {
  22. NT_unknown,
  23. NT_document,
  24. NT_element,
  25. NT_text,
  26. };
  27. // This typedef defines a 32-bit unsigned integer. It's used for
  28. // passing values through the binary XML stream.
  29. typedef unsigned int xml_uint32;
  30. // These are both prime numbers, though I don't know if that really
  31. // matters. Mainly, they're big random numbers.
  32. static const xml_uint32 length_nonce1 = 812311453;
  33. static const xml_uint32 length_nonce2 = 612811373;
  34. ////////////////////////////////////////////////////////////////////
  35. // Function: init_xml
  36. // Description: Should be called before spawning any threads to
  37. // ensure the lock is initialized.
  38. ////////////////////////////////////////////////////////////////////
  39. void
  40. init_xml() {
  41. if (!xml_lock_initialized) {
  42. INIT_LOCK(xml_lock);
  43. xml_lock_initialized = true;
  44. }
  45. }
  46. ////////////////////////////////////////////////////////////////////
  47. // Function: write_xml_node
  48. // Description: Recursively writes a node and all of its children to
  49. // the given stream.
  50. ////////////////////////////////////////////////////////////////////
  51. static void
  52. write_xml_node(ostream &out, TiXmlNode *xnode) {
  53. const string &value = xnode->ValueStr();
  54. xml_uint32 value_length = value.length();
  55. xml_uint32 value_proof = (value_length + length_nonce1) * length_nonce2;
  56. // We write out not only value_length, but the same value again
  57. // hashed by length_nonce1 and 2 (and truncated back to xml_uint32),
  58. // just to prove to the reader that we're still on the same page.
  59. // We do this only on the top node; we don't bother for the nested
  60. // nodes.
  61. out.write((char *)&value_length, sizeof(value_length));
  62. out.write((char *)&value_proof, sizeof(value_proof));
  63. out.write(value.data(), value_length);
  64. // Now write out the node type.
  65. NodeType type = NT_element;
  66. if (xnode->ToDocument() != NULL) {
  67. type = NT_document;
  68. } else if (xnode->ToElement() != NULL) {
  69. type = NT_element;
  70. } else if (xnode->ToText() != NULL) {
  71. type = NT_text;
  72. } else {
  73. type = NT_unknown;
  74. }
  75. out.put((char)type);
  76. // We don't bother to write any further data for the unknown types.
  77. if (type == NT_unknown) {
  78. return;
  79. }
  80. if (type == NT_element) {
  81. // Write the element attributes.
  82. TiXmlElement *xelement = xnode->ToElement();
  83. assert(xelement != NULL);
  84. const TiXmlAttribute *xattrib = xelement->FirstAttribute();
  85. while (xattrib != NULL) {
  86. // We have an attribute.
  87. out.put((char)true);
  88. string name = xattrib->Name();
  89. xml_uint32 name_length = name.length();
  90. out.write((char *)&name_length, sizeof(name_length));
  91. out.write(name.data(), name_length);
  92. const string &value = xattrib->ValueStr();
  93. xml_uint32 value_length = value.length();
  94. out.write((char *)&value_length, sizeof(value_length));
  95. out.write(value.data(), value_length);
  96. xattrib = xattrib->Next();
  97. }
  98. // The end of the attributes list.
  99. out.put((char)false);
  100. }
  101. // Now write all of the children.
  102. TiXmlNode *xchild = xnode->FirstChild();
  103. while (xchild != NULL) {
  104. // We have a child.
  105. out.put((char)true);
  106. write_xml_node(out, xchild);
  107. xchild = xchild->NextSibling();
  108. }
  109. // The end of the children list.
  110. out.put((char)false);
  111. }
  112. ////////////////////////////////////////////////////////////////////
  113. // Function: read_xml_node
  114. // Description: Recursively reads a node and all of its children to
  115. // the given stream. Returns the newly-allocated node.
  116. // The caller is responsible for eventually deleting the
  117. // return value. Returns NULL on error.
  118. ////////////////////////////////////////////////////////////////////
  119. static TiXmlNode *
  120. read_xml_node(istream &in, char *&buffer, xml_uint32 &buffer_length,
  121. ostream &logfile) {
  122. xml_uint32 value_length;
  123. in.read((char *)&value_length, sizeof(value_length));
  124. if (in.gcount() != sizeof(value_length)) {
  125. return NULL;
  126. }
  127. xml_uint32 value_proof_expect = (value_length + length_nonce1) * length_nonce2;
  128. xml_uint32 value_proof;
  129. in.read((char *)&value_proof, sizeof(value_proof));
  130. if (in.gcount() != sizeof(value_proof)) {
  131. return NULL;
  132. }
  133. if (value_proof != value_proof_expect) {
  134. // Hey, we ran into garbage: the proof value didn't match our
  135. // expected proof value.
  136. logfile << "Garbage on XML stream!\n";
  137. // Print out the garbage; maybe it will help the developer figure
  138. // out where it came from.
  139. logfile << "Begin garbage:\n";
  140. ostringstream strm;
  141. strm.write((char *)&value_length, sizeof(value_length));
  142. strm.write((char *)&value_proof, sizeof(value_proof));
  143. logfile << strm.str();
  144. for (size_t i = 0; i < 100; ++i) {
  145. int ch = in.get();
  146. if (ch != EOF) {
  147. logfile.put(ch);
  148. }
  149. }
  150. logfile << "\n";
  151. logfile << "End garbage.\n";
  152. return NULL;
  153. }
  154. if (value_length > buffer_length) {
  155. delete[] buffer;
  156. buffer_length = value_length;
  157. buffer = new char[buffer_length];
  158. }
  159. in.read(buffer, value_length);
  160. string value(buffer, value_length);
  161. // Read the node type.
  162. NodeType type = (NodeType)in.get();
  163. if (type == NT_unknown) {
  164. return NULL;
  165. }
  166. TiXmlNode *xnode = NULL;
  167. if (type == NT_element) {
  168. xnode = new TiXmlElement(value);
  169. } else if (type == NT_document) {
  170. xnode = new TiXmlDocument;
  171. } else if (type == NT_text) {
  172. xnode = new TiXmlText(value);
  173. } else {
  174. assert(false);
  175. }
  176. if (type == NT_element) {
  177. // Read the element attributes.
  178. TiXmlElement *xelement = xnode->ToElement();
  179. assert(xelement != NULL);
  180. bool got_attrib = (bool)(in.get() != 0);
  181. while (got_attrib && in && !in.eof()) {
  182. // We have an attribute.
  183. xml_uint32 name_length;
  184. in.read((char *)&name_length, sizeof(name_length));
  185. if (in.gcount() != sizeof(name_length)) {
  186. delete xnode;
  187. return NULL;
  188. }
  189. if (name_length > buffer_length) {
  190. delete[] buffer;
  191. buffer_length = name_length;
  192. buffer = new char[buffer_length];
  193. }
  194. in.read(buffer, name_length);
  195. string name(buffer, name_length);
  196. xml_uint32 value_length;
  197. in.read((char *)&value_length, sizeof(value_length));
  198. if (in.gcount() != sizeof(value_length)) {
  199. delete xnode;
  200. return NULL;
  201. }
  202. if (value_length > buffer_length) {
  203. delete[] buffer;
  204. buffer_length = value_length;
  205. buffer = new char[buffer_length];
  206. }
  207. in.read(buffer, value_length);
  208. string value(buffer, value_length);
  209. xelement->SetAttribute(name, value);
  210. got_attrib = (bool)(in.get() != 0);
  211. }
  212. }
  213. // Now read all of the children.
  214. bool got_child = (bool)(in.get() != 0);
  215. while (got_child && in && !in.eof()) {
  216. // We have a child.
  217. TiXmlNode *xchild = read_xml_node(in, buffer, buffer_length, logfile);
  218. if (xchild != NULL) {
  219. xnode->LinkEndChild(xchild);
  220. }
  221. got_child = (bool)(in.get() != 0);
  222. }
  223. return xnode;
  224. }
  225. ////////////////////////////////////////////////////////////////////
  226. // Function: write_xml
  227. // Description: Writes the indicated TinyXml document to the given
  228. // stream.
  229. ////////////////////////////////////////////////////////////////////
  230. void
  231. write_xml(ostream &out, TiXmlDocument *doc, ostream &logfile) {
  232. assert(xml_lock_initialized);
  233. ACQUIRE_LOCK(xml_lock);
  234. #ifdef DO_BINARY_XML
  235. // Binary write.
  236. write_xml_node(out, doc);
  237. #else
  238. // Formatted ASCII write.
  239. // We need a declaration to write it safely.
  240. TiXmlDeclaration decl("1.0", "utf-8", "");
  241. doc->InsertBeforeChild(doc->FirstChild(), decl);
  242. out << *doc;
  243. #endif
  244. out << flush;
  245. if (debug_xml_output) {
  246. // Write via ostringstream, so it all goes in one operation, to
  247. // help out the interleaving from multiple threads.
  248. ostringstream logout;
  249. logout << "sent: " << *doc << "\n";
  250. logfile << logout.str() << flush;
  251. }
  252. RELEASE_LOCK(xml_lock);
  253. }
  254. ////////////////////////////////////////////////////////////////////
  255. // Function: read_xml
  256. // Description: Reads a TinyXml document from the given stream, and
  257. // returns it. If the document is not yet available,
  258. // blocks until it is, or until there is an error
  259. // condition on the input.
  260. //
  261. // The return value is NULL if there is an error, or the
  262. // newly-allocated document if it is successfully read.
  263. // If not NULL, the document has been allocated with
  264. // new, and should be eventually freed by the caller
  265. // with delete.
  266. ////////////////////////////////////////////////////////////////////
  267. TiXmlDocument *
  268. read_xml(istream &in, ostream &logfile) {
  269. // We don't acquire xml_lock while reading. We can't, because our
  270. // XML readers are all designed to block until data is available,
  271. // and they can't block while holding the lock.
  272. // Fortunately, there should be only one reader at a time, so a lock
  273. // isn't really needed here.
  274. #if DO_BINARY_XML
  275. // binary read.
  276. xml_uint32 buffer_length = 128;
  277. char *buffer = new char[buffer_length];
  278. TiXmlNode *xnode = read_xml_node(in, buffer, buffer_length, logfile);
  279. delete[] buffer;
  280. if (xnode == NULL) {
  281. return NULL;
  282. }
  283. TiXmlDocument *doc = xnode->ToDocument();
  284. assert(doc != NULL);
  285. #else
  286. // standard ASCII read.
  287. TiXmlDocument *doc = new TiXmlDocument;
  288. in >> *doc;
  289. if (in.fail() || in.eof()) {
  290. delete doc;
  291. return NULL;
  292. }
  293. #endif
  294. if (debug_xml_output) {
  295. // Write via ostringstream, so it all goes in one operation, to
  296. // help out the interleaving from multiple threads.
  297. ostringstream logout;
  298. logout << "received: " << *doc << "\n";
  299. logfile << logout.str() << flush;
  300. }
  301. return doc;
  302. }