src/third-party/discord-rpc/src/serialization.h (view raw)
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
24template <size_t Len>
25inline 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
39size_t JsonWriteHandshakeObj(char* dest, size_t maxLen, int version, const char* applicationId);
40
41// Commands
42struct DiscordRichPresence;
43size_t JsonWriteRichPresenceObj(char* dest,
44 size_t maxLen,
45 int nonce,
46 int pid,
47 const DiscordRichPresence* presence);
48size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName);
49
50size_t JsonWriteUnsubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName);
51
52size_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
57class LinearAllocator {
58public:
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
100template <size_t Size>
101class FixedLinearAllocator : public LinearAllocator {
102public:
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
112class DirectStringBuffer {
113public:
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
136using MallocAllocator = rapidjson::CrtAllocator;
137using PoolAllocator = rapidjson::MemoryPoolAllocator<MallocAllocator>;
138using UTF8 = rapidjson::UTF8<char>;
139// Writer appears to need about 16 bytes per nested object level (with 64bit size_t)
140using StackAllocator = FixedLinearAllocator<2048>;
141constexpr size_t WriterNestingLevels = 2048 / (2 * sizeof(size_t));
142using JsonWriterBase =
143 rapidjson::Writer<DirectStringBuffer, UTF8, UTF8, StackAllocator, rapidjson::kWriteNoFlags>;
144class JsonWriter : public JsonWriterBase {
145public:
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
159using JsonDocumentBase = rapidjson::GenericDocument<UTF8, PoolAllocator, StackAllocator>;
160class JsonDocument : public JsonDocumentBase {
161public:
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
180using JsonValue = rapidjson::GenericValue<UTF8, PoolAllocator>;
181
182inline 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
193inline 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
204inline 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}