#include #include #include "MiscSupport.h" #include "Marshal.h" void unmarshalFinishedOrThrow(std::string const &source, size_t offset) { if (!unmarshalFinished(source, offset)) throw MarshallingException("Extra bytes after unmarshalling."); } void const *unmarshalBytes(std::string const &source, size_t &offset, size_t count) { if (offset + count > source.size()) throw MarshallingException("Reading past end of data. data size = " + ntoa(source.length()) + ", initial offset = " + ntoa(offset) + ", requested count = " + ntoa(count)); void const *result = &source[offset]; offset += count; return result; } void marshal(std::string &destination, std::string const &string) { marshalSize(destination, string.size()); destination.append(string); } void unmarshal(std::string const &source, size_t &offset, std::string &value) { const size_t length = unmarshalSize(source, offset); void const *const start = unmarshalBytes(source, offset, length); value.assign(static_cast< char const * >(start), length); } static const uint8_t BIG_SIZE_MARKER = 0xff; // This may be overkill. If size < 255, send it as 1 byte. If (size >= 255) // && (size <= 0xffffffff) first send 0xff to say that it's a big number, // then send the number as a normal 4 byte integer. If size > 0xffffffff, // throw an exception. // // If we have a string of 3 bytes, we don't want to send 8 bytes for the // size followed by 3 bytes of data. Instead we'll send 1 byte of size and 3 // bytes of data. // // If we need a bigger size, we use 5 bytes to send the size. It only would // have cost of 4 bytes if we didn't have the optimization. But in that case // the actual data will be at least 255 bytes and maybe a lot more, so this // extra byte will be a very small percentage of the total. void marshalSize(std::string &destination, size_t value) { if (value < BIG_SIZE_MARKER) marshal(destination, (uint8_t)value); else if (value > std::numeric_limits< uint32_t >::max()) throw MarshallingException("Size is too big: " + ntoa(value) + " bytes."); else { marshal(destination, BIG_SIZE_MARKER); marshal(destination, (uint32_t)value); } } size_t unmarshalSize(std::string const &source, size_t &offset) { uint8_t shortSize; unmarshal(source, offset, shortSize); if (shortSize != BIG_SIZE_MARKER) return shortSize; uint32_t longSize; unmarshal(source, offset, longSize); return longSize; } void mPush(std::string &destination, std::string const &source) { uint32_t size = source.size(); if (size != source.size()) throw MarshallingException("Size is too big: " + ntoa(source.size()) + " bytes."); destination.append(source); if (size < 255) destination += (char)size; else { destination.append((char const *)&size, sizeof(size)); destination += '\xff'; } } std::string mPop(std::string &source) { if (source.empty()) throw MarshallingException("Can't mPop() an empty string."); char const *end = source.c_str() + source.length() - 1; uint32_t size = (unsigned char)*end; if (size == 255) { if (source.length() < 5) throw MarshallingException("Can't mPop()."); end -= 4; size = *(uint32_t *)end; } size_t remaining = end - source.c_str(); if (remaining < size) throw MarshallingException("Can't mPop()."); std::string result(end - size, end); source.resize(remaining - size); return result; } #include void testMarshal() { std::map< char *, int * > x; x[NULL] = NULL; std::string m; // If you uncomment the following line you will get an error at compile time. // That's on purpose. I was testing the C++ rules because they are confusing // at best. This line will immediately match the function for marshaling a // std::map. That function will try to marshal a std::pair with a char * // and an int *. That call will try to use the version of marshal made for // std::pair. That second function call will try to marshal char * and int * // and the latter will fail. The error works exactly like it should, naming // the specific rule "You cannot marshal a pointer" and the line where that // can be found, but also pointing directly to the line below, labeling it // "required from here". // // I was worried that SFINAE would allow for some backtracking. I was // worried that the compiler might try looking at the std::pair as a POD // type, if not at first, as part of the backtracking. It does not. //marshal(m, x); // Any time you try to marshal a char *, we automatically convert that to a // std::string and marshal the string instead. The result is exactly the // same as if you'd tried to marshal the string in the first place. This // example is more complicated than anything I'd expect to see in real code. // I created that rule mostly for cases when you want to use a string literal // directly in the call to marshal. We use std::string almost exclusively. // Most of the time if you mix in a string literal C++ will automatically // convert that literal from char * to std::string. But sometimes it gets // confused and converts it to bool or something else and gives you // unexpected results with no warning. This test case assures us that string // literals work as expected. std::string charStarResult; std::map< char const *, int > y; y["hello"]=65; y["goodbye"]=66; marshal(charStarResult, y); std::string stringResult; std::map< std::string, int > z; z["hello"]=65; z["goodbye"]=66; marshal(stringResult, z); // I had to disable the next line. The basic idea was close but there's one // problem. When you marshal() a map the items are sorted by the keys. // For std::string "goodbye" always comes before "hello". For the pointer // versions it's pretty random. I looked at the two results in the debugger // and I can say that they were the same aside from the order. I should use // unmarshal() on the strings and then compare the resulting trees. //assert(charStarResult == stringResult); std::map< std::string, int > yRecreated, zRecreated; yRecreated["this should be erased"]=42; unmarshal(charStarResult, yRecreated); zRecreated = unmarshal< std::map< std::string, int > >(stringResult); assert(yRecreated == zRecreated); assert(z == zRecreated); marshal(stringResult, "hello"); // Now we have two top level items. try { unmarshal< std::map< std::string, int > >(stringResult); std::cout<<"unmarshal() succeeded when we exected it to fail. :(" <