/* * Copyright 2017 Discord, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ #ifndef DISCORD_SERIALIZATION_H #define DISCORD_SERIALIZATION_H #include #include #include struct DiscordRichPresence; namespace discord_rpc { // if only there was a standard library function for this template inline size_t StringCopy(char (&dest)[Len], const char *src) { if (!src || !Len) { return 0; } size_t copied; char *out = dest; for (copied = 1; *src && copied < Len; ++copied) { *out++ = *src++; } *out = 0; return copied - 1; } size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char *applicationId); // Commands size_t JsonWriteRichPresenceObj(char *dest, const size_t maxLen, const int nonce, const int pid, const DiscordRichPresence *presence); size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName); size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName); size_t JsonWriteJoinReply(char *dest, size_t maxLen, const char *userId, int reply, int nonce); // I want to use as few allocations as I can get away with, and to do that with RapidJson, you need // to supply some of your own allocators for stuff rather than use the defaults class LinearAllocator { public: char *buffer_; char *end_; LinearAllocator() { assert(0); // needed for some default case in rapidjson, should not use } LinearAllocator(char *buffer, size_t size) : buffer_(buffer), end_(buffer + size) { } static const bool kNeedFree = false; void *Malloc(size_t size) { char *res = buffer_; buffer_ += size; if (buffer_ > end_) { buffer_ = res; return nullptr; } return res; } void *Realloc(void *originalPtr, size_t originalSize, size_t newSize) { if (newSize == 0) { return nullptr; } // allocate how much you need in the first place assert(!originalPtr && !originalSize); // unused parameter warning (void)(originalPtr); (void)(originalSize); return Malloc(newSize); } static void Free(void *ptr) { /* shrug */ (void)ptr; } }; template class FixedLinearAllocator : public LinearAllocator { public: char fixedBuffer_[Size]; FixedLinearAllocator() : LinearAllocator(fixedBuffer_, Size) { } static const bool kNeedFree = false; }; // wonder why this isn't a thing already, maybe I missed it class DirectStringBuffer { public: using Ch = char; char *buffer_; char *end_; char *current_; DirectStringBuffer(char *buffer, size_t maxLen) : buffer_(buffer), end_(buffer + maxLen), current_(buffer) { } void Put(char c) { if (current_ < end_) { *current_++ = c; } } void Flush() {} size_t GetSize() const { return static_cast(current_ - buffer_); } }; using MallocAllocator = rapidjson::CrtAllocator; using PoolAllocator = rapidjson::MemoryPoolAllocator; using UTF8 = rapidjson::UTF8; // Writer appears to need about 16 bytes per nested object level (with 64bit size_t) using StackAllocator = FixedLinearAllocator<2048>; constexpr size_t WriterNestingLevels = 2048 / (2 * sizeof(size_t)); using JsonWriterBase = rapidjson::Writer; class JsonWriter : public JsonWriterBase { public: DirectStringBuffer stringBuffer_; StackAllocator stackAlloc_; JsonWriter(char *dest, size_t maxLen) : JsonWriterBase(stringBuffer_, &stackAlloc_, WriterNestingLevels), stringBuffer_(dest, maxLen), stackAlloc_() { } size_t Size() const { return stringBuffer_.GetSize(); } }; using JsonDocumentBase = rapidjson::GenericDocument; class JsonDocument : public JsonDocumentBase { public: static const int kDefaultChunkCapacity = 32 * 1024; // json parser will use this buffer first, then allocate more if needed; I seriously doubt we // send any messages that would use all of this, though. char parseBuffer_[32 * 1024]; MallocAllocator mallocAllocator_; PoolAllocator poolAllocator_; StackAllocator stackAllocator_; JsonDocument() : JsonDocumentBase(rapidjson::kObjectType, &poolAllocator_, sizeof(stackAllocator_.fixedBuffer_), &stackAllocator_), poolAllocator_(parseBuffer_, sizeof(parseBuffer_), kDefaultChunkCapacity, &mallocAllocator_), stackAllocator_() { } }; using JsonValue = rapidjson::GenericValue; inline JsonValue *GetObjMember(JsonValue *obj, const char *name) { if (obj) { auto member = obj->FindMember(name); if (member != obj->MemberEnd() && member->value.IsObject()) { return &member->value; } } return nullptr; } inline int GetIntMember(JsonValue *obj, const char *name, int notFoundDefault = 0) { if (obj) { auto member = obj->FindMember(name); if (member != obj->MemberEnd() && member->value.IsInt()) { return member->value.GetInt(); } } return notFoundDefault; } inline const char *GetStrMember(JsonValue *obj, const char *name, const char *notFoundDefault = nullptr) { if (obj) { auto member = obj->FindMember(name); if (member != obj->MemberEnd() && member->value.IsString()) { return member->value.GetString(); } } return notFoundDefault; } } // namespace discord_rpc #endif // DISCORD_SERIALIZATION_H