/ src / serialization.h
serialization.h
  1  #pragma once
  2  
  3  #include <stdint.h>
  4  
  5  #ifndef __MINGW32__
  6  #pragma warning(push)
  7  
  8  #pragma warning(disable : 4061) // enum is not explicitly handled by a case label
  9  #pragma warning(disable : 4365) // signed/unsigned mismatch
 10  #pragma warning(disable : 4464) // relative include path contains
 11  #pragma warning(disable : 4668) // is not defined as a preprocessor macro
 12  #pragma warning(disable : 6313) // Incorrect operator
 13  #endif                          // __MINGW32__
 14  
 15  #include "rapidjson/document.h"
 16  #include "rapidjson/stringbuffer.h"
 17  #include "rapidjson/writer.h"
 18  
 19  #ifndef __MINGW32__
 20  #pragma warning(pop)
 21  #endif // __MINGW32__
 22  
 23  // if only there was a standard library function for this
 24  template <size_t Len>
 25  inline size_t StringCopy(char (&dest)[Len], const char* src)
 26  {
 27      if (!src || !Len) {
 28          return 0;
 29      }
 30      size_t copied;
 31      char* out = dest;
 32      for (copied = 1; *src && copied < Len; ++copied) {
 33          *out++ = *src++;
 34      }
 35      *out = 0;
 36      return copied - 1;
 37  }
 38  
 39  size_t JsonWriteHandshakeObj(char* dest, size_t maxLen, int version, const char* applicationId);
 40  
 41  // Commands
 42  struct DiscordRichPresence;
 43  size_t JsonWriteRichPresenceObj(char* dest,
 44                                  size_t maxLen,
 45                                  int nonce,
 46                                  int pid,
 47                                  const DiscordRichPresence* presence);
 48  size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName);
 49  
 50  size_t JsonWriteUnsubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName);
 51  
 52  size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce);
 53  
 54  // I want to use as few allocations as I can get away with, and to do that with RapidJson, you need
 55  // to supply some of your own allocators for stuff rather than use the defaults
 56  
 57  class LinearAllocator {
 58  public:
 59      char* buffer_;
 60      char* end_;
 61      LinearAllocator()
 62      {
 63          assert(0); // needed for some default case in rapidjson, should not use
 64      }
 65      LinearAllocator(char* buffer, size_t size)
 66        : buffer_(buffer)
 67        , end_(buffer + size)
 68      {
 69      }
 70      static const bool kNeedFree = false;
 71      void* Malloc(size_t size)
 72      {
 73          char* res = buffer_;
 74          buffer_ += size;
 75          if (buffer_ > end_) {
 76              buffer_ = res;
 77              return nullptr;
 78          }
 79          return res;
 80      }
 81      void* Realloc(void* originalPtr, size_t originalSize, size_t newSize)
 82      {
 83          if (newSize == 0) {
 84              return nullptr;
 85          }
 86          // allocate how much you need in the first place
 87          assert(!originalPtr && !originalSize);
 88          // unused parameter warning
 89          (void)(originalPtr);
 90          (void)(originalSize);
 91          return Malloc(newSize);
 92      }
 93      static void Free(void* ptr)
 94      {
 95          /* shrug */
 96          (void)ptr;
 97      }
 98  };
 99  
100  template <size_t Size>
101  class FixedLinearAllocator : public LinearAllocator {
102  public:
103      char fixedBuffer_[Size];
104      FixedLinearAllocator()
105        : LinearAllocator(fixedBuffer_, Size)
106      {
107      }
108      static const bool kNeedFree = false;
109  };
110  
111  // wonder why this isn't a thing already, maybe I missed it
112  class DirectStringBuffer {
113  public:
114      using Ch = char;
115      char* buffer_;
116      char* end_;
117      char* current_;
118  
119      DirectStringBuffer(char* buffer, size_t maxLen)
120        : buffer_(buffer)
121        , end_(buffer + maxLen)
122        , current_(buffer)
123      {
124      }
125  
126      void Put(char c)
127      {
128          if (current_ < end_) {
129              *current_++ = c;
130          }
131      }
132      void Flush() {}
133      size_t GetSize() const { return (size_t)(current_ - buffer_); }
134  };
135  
136  using MallocAllocator = rapidjson::CrtAllocator;
137  using PoolAllocator = rapidjson::MemoryPoolAllocator<MallocAllocator>;
138  using UTF8 = rapidjson::UTF8<char>;
139  // Writer appears to need about 16 bytes per nested object level (with 64bit size_t)
140  using StackAllocator = FixedLinearAllocator<2048>;
141  constexpr size_t WriterNestingLevels = 2048 / (2 * sizeof(size_t));
142  using JsonWriterBase =
143    rapidjson::Writer<DirectStringBuffer, UTF8, UTF8, StackAllocator, rapidjson::kWriteNoFlags>;
144  class JsonWriter : public JsonWriterBase {
145  public:
146      DirectStringBuffer stringBuffer_;
147      StackAllocator stackAlloc_;
148  
149      JsonWriter(char* dest, size_t maxLen)
150        : JsonWriterBase(stringBuffer_, &stackAlloc_, WriterNestingLevels)
151        , stringBuffer_(dest, maxLen)
152        , stackAlloc_()
153      {
154      }
155  
156      size_t Size() const { return stringBuffer_.GetSize(); }
157  };
158  
159  using JsonDocumentBase = rapidjson::GenericDocument<UTF8, PoolAllocator, StackAllocator>;
160  class JsonDocument : public JsonDocumentBase {
161  public:
162      static const int kDefaultChunkCapacity = 32 * 1024;
163      // json parser will use this buffer first, then allocate more if needed; I seriously doubt we
164      // send any messages that would use all of this, though.
165      char parseBuffer_[32 * 1024];
166      MallocAllocator mallocAllocator_;
167      PoolAllocator poolAllocator_;
168      StackAllocator stackAllocator_;
169      JsonDocument()
170        : JsonDocumentBase(rapidjson::kObjectType,
171                           &poolAllocator_,
172                           sizeof(stackAllocator_.fixedBuffer_),
173                           &stackAllocator_)
174        , poolAllocator_(parseBuffer_, sizeof(parseBuffer_), kDefaultChunkCapacity, &mallocAllocator_)
175        , stackAllocator_()
176      {
177      }
178  };
179  
180  using JsonValue = rapidjson::GenericValue<UTF8, PoolAllocator>;
181  
182  inline JsonValue* GetObjMember(JsonValue* obj, const char* name)
183  {
184      if (obj) {
185          auto member = obj->FindMember(name);
186          if (member != obj->MemberEnd() && member->value.IsObject()) {
187              return &member->value;
188          }
189      }
190      return nullptr;
191  }
192  
193  inline int GetIntMember(JsonValue* obj, const char* name, int notFoundDefault = 0)
194  {
195      if (obj) {
196          auto member = obj->FindMember(name);
197          if (member != obj->MemberEnd() && member->value.IsInt()) {
198              return member->value.GetInt();
199          }
200      }
201      return notFoundDefault;
202  }
203  
204  inline const char* GetStrMember(JsonValue* obj,
205                                  const char* name,
206                                  const char* notFoundDefault = nullptr)
207  {
208      if (obj) {
209          auto member = obj->FindMember(name);
210          if (member != obj->MemberEnd() && member->value.IsString()) {
211              return member->value.GetString();
212          }
213      }
214      return notFoundDefault;
215  }