UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
StringNetSerializerUtils.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
7
8namespace UE::Net
9{
10
12
13}
14
15namespace UE::Net::Private
16{
17
19{
20public:
21 // Note: Keep the allocations a multiple of 4 and at least 4 byte aligned to allow use of NetBitstreamWriter::WriteBitStream
22
23 // More compact format than UTF-8. A codepoint will use at most 24 bits, versus 32 bits for UTF-8.
24 template<typename InWideCharType>
26 {
27 public:
31
32 static bool Encode(EncodeType* Dest, uint32 DestLen, const WideCharType* Source, uint32 SourceLen, uint32& OutDestLen)
33 {
34 return EncodeImpl(Dest, DestLen, Source, SourceLen, OutDestLen);
35 }
36
37 // Decodes into a WideCharType string. It is mandatory to call IsValidEncoding() before Decode().
38 static bool Decode(WideCharType* Dest, uint32 DestLen, const EncodeType* Source, uint32 SourceLen, uint32& OutDestLen)
39 {
40 return DecodeImpl(Dest, DestLen, Source, SourceLen, OutDestLen);
41 }
42
47
48 // Returns the length in characters of type WideCharType that is needed to safely decode EncodedLen
50 {
51 if constexpr (sizeof(WideCharType) == 2)
52 {
53 // If the machine sending had 32-bit characters there may be codepoints that are encoded as 3 bytes but require 4 bytes in the destination buffer.
54 // Worst case scenario every codepoint requires 4 bytes in the destination buffer.
55 return (EncodedLen * 4U) / 3U;
56 }
57 else
58 {
59 return EncodedLen;
60 }
61 }
62
63 // This checks for very serious errors such as a codepoint encoded into more than three bytes, or codepoint passing end of buffer.
64 static bool IsValidEncoding(const EncodeType* Encoding, uint32 EncodeLen)
65 {
66 uint32 Continue = 0;
68 {
70 do
71 {
72 if ((++EncodedCount > 3U) | (EncodeIt == EncodeEndIt))
73 {
74 return false;
75 }
76
77 EncodeType Code = Encoding[EncodeIt];
78 Continue = (Code & 0x80) ? 1U : 0U;
80 } while (Continue);
81 }
82
83 return true;
84 }
85
86 private:
87
88 static bool IsInvalidCodepoint(uint32 Codepoint)
89 {
90 // Valid codepoints are in range [0, 0x10FFFF], except for 0xFFFE and 0xFFFF
91 return (Codepoint > 0x10FFFFU) | ((Codepoint - 0xFFFEU) <= 1U);
92 }
93
94 static bool IsSurrogate(uint32 Codepoint)
95 {
96 return (Codepoint >= 0xD800U) & (Codepoint <= 0xDFFFU);
97 }
98
99 static bool IsHighSurrogate(WideCharType Char)
100 {
101 return (Char >= WideCharType(0xD800U)) & (Char <= WideCharType(0xDBFFU));
102 }
103
104 static bool IsLowSurrogate(WideCharType Char)
105 {
106 return (Char >= WideCharType(0xDC00U)) & (Char <= WideCharType(0xDFFFU));
107 }
108
109 static uint32 GetCodepointFromSurrogates(WideCharType HighSurrogate, WideCharType LowSurrogate)
110 {
111 return (uint32(uint16(HighSurrogate) - 0xD800U) << 10U) + (uint32(uint16(LowSurrogate) - 0xDC00U) + 0x10000U);
112 }
113
114 static bool IsInNeedOfSurrogates(uint32 Codepoint)
115 {
116 return (Codepoint >= 0x10000U) & (Codepoint <= 0x10FFFFU);
117 }
118
119 static WideCharType GetHighSurrogate(uint32 Codepoint)
120 {
121 return static_cast<WideCharType>(((Codepoint & 0xFFFFU) >> 10U) + 0xD800U);
122 }
123
124 static WideCharType GetLowSurrogate(uint32 Codepoint)
125 {
126 return static_cast<WideCharType>((Codepoint & 0x3FFU) + 0xDC00U);
127 }
128
133 template<typename T = WideCharType, typename TEnableIf<sizeof(T) == 4, char>::Type CharSize = 4>
134 static bool EncodeImpl(EncodeType* Dest, uint32 DestLen, const WideCharType* Source, uint32 SourceLen, uint32& OutDestLen)
135 {
136 constexpr WideCharType LastCodePoint = 0x10FFFF;
137 constexpr WideCharType ReplacementCharacter = 0xFFFD;
138
139 // Unless the destination length is safe let's not encode. Encoding would require OOB checks.
140 if (DestLen < 3U*SourceLen)
141 {
142 return false;
143 }
144
145 uint32 DestIt = 0;
146 uint32 SourceIt = 0;
147 for (const uint32 SourceEndIt = SourceLen; SourceIt < SourceEndIt; ++SourceIt)
148 {
151
152 if (Char < WideCharType(0x80))
153 {
154 Dest[DestIt++] = static_cast<EncodeType>(Char);
155 }
156 else if (Char < WideCharType(0x400))
157 {
158 Dest[DestIt + 0] = EncodeType(0x80) | static_cast<EncodeType>(Char >> 7U);
159 Dest[DestIt + 1] = (Char & 0x7F);
160 DestIt += 2;
161 }
162 else
163 {
164 Dest[DestIt + 0] = EncodeType(0x80) | static_cast<EncodeType>(Char >> 14U);
165 Dest[DestIt + 1] = EncodeType(0x80) | static_cast<EncodeType>((Char >> 7U) & 0x7F);
166 Dest[DestIt + 2] = (Char & 0x7F);
167 DestIt += 3;
168 }
169 }
170
172 return SourceIt == SourceLen;
173 }
174
179 template<typename T = WideCharType, typename TEnableIf<sizeof(T) == 2, char>::Type CharSize = 2>
180 static bool EncodeImpl(EncodeType* Dest, uint32 DestLen, const WideCharType* Source, uint32 SourceLen, uint32& OutDestLen)
181 {
182 // Unless the destination length is safe let's not encode. Encoding would require OOB checks.
183 if (DestLen < 3U*SourceLen)
184 {
185 return false;
186 }
187
188 uint32 DestIt = 0;
189 uint32 SourceIt = 0;
190 for (const uint32 SourceEndIt = SourceLen; SourceIt < SourceEndIt; ++SourceIt)
191 {
193 const WideCharType NextChar = Source[FMath::Min(SourceIt + 1U, SourceEndIt - 1U)];
198 if (IsHighSurrogate(Char) && IsLowSurrogate(NextChar))
199 {
200 const uint32 Codepoint = GetCodepointFromSurrogates(Char, NextChar);
201 Dest[DestIt + 0] = EncodeType(0x80) | static_cast<EncodeType>(Codepoint >> 14U);
202 Dest[DestIt + 1] = EncodeType(0x80) | static_cast<EncodeType>((Codepoint >> 7U) & 0x7F);
203 Dest[DestIt + 2] = (Codepoint & 0x7F);
204 DestIt += 3;
205 ++SourceIt;
206 }
207 else if (Char < WideCharType(0x80))
208 {
209 Dest[DestIt++] = static_cast<EncodeType>(Char);
210 }
211 else if (Char < WideCharType(0x400))
212 {
213 Dest[DestIt + 0] = EncodeType(0x80) | static_cast<EncodeType>(Char >> 7U);
214 Dest[DestIt + 1] = (Char & 0x7F);
215 DestIt += 2;
216 }
217 else
218 {
219 Dest[DestIt + 0] = EncodeType(0x80) | static_cast<EncodeType>(Char >> 14U);
220 Dest[DestIt + 1] = EncodeType(0x80) | static_cast<EncodeType>((Char >> 7U) & 0x7F);
221 Dest[DestIt + 2] = (Char & 0x7F);
222 DestIt += 3;
223 }
224 }
225
227 return SourceIt == SourceLen;
228 }
229
230 // 4-byte destination character type version
231 template<typename T = WideCharType, typename TEnableIf<sizeof(T) == 4, char>::Type CharSize = 4>
232 static bool DecodeImpl(WideCharType* Dest, uint32 DestLen, const EncodeType* Source, uint32 SourceLen, uint32& OutDestLen)
233 {
234 if (SourceLen == 0)
235 {
236 OutDestLen = 0;
237 return true;
238 }
239
240 if (DestLen < GetSafeDecodedBufferLength(SourceLen))
241 {
242 return false;
243 }
244
245 constexpr WideCharType ReplacementCharacter = 0xFFFD;
246
248 uint32 DestIt = 0;
249 bool bIsErrorDetected = false;
250 for (uint32 SourceIt = 0, SourceEndIt = SourceLen, DestEndIt = DestLen; (SourceIt < SourceEndIt) & (DestIt < DestEndIt) & (!bIsErrorDetected); ++SourceIt)
251 {
254 uint8 Mask;
255
256 // Decode first byte
258 Codepoint = Byte & 0x7FU;
259
260 // Optionally decode second byte. Current state will remain unchanged unless the most significant bit in Byte is set.
261 Mask = int8(Byte) >> 7U;
262 SourceIt += 1U & Mask;
263 if (SourceIt >= SourceEndIt)
264 {
265 bIsErrorDetected = true;
266 break;
267 }
268
270 Codepoint = (Codepoint << (7U & Mask)) | (Byte & 0x7FU);
271
272 // Optionally decode third byte. Current state will remain unchanged unless the most significant bit in Byte is set.
273 Mask = int8(Byte) >> 7U;
274 SourceIt += 1U & Mask;
275 if (SourceIt >= SourceEndIt)
276 {
277 bIsErrorDetected = true;
278 break;
279 }
281 Codepoint = (Codepoint << (7U & Mask)) | (Byte & 0x7FU);
282
283 const bool bIsInvalidCodepoint = IsInvalidCodepoint(Codepoint);
285 SurrogateCount += IsSurrogate(Codepoint);
286
288 }
289
290 // Make sure decoding is null terminated
291 if (Dest[DestIt - 1] != 0)
292 {
293 bIsErrorDetected = true;
294 // This may overwrite the last character
295 uint32 LastIndex = FMath::Min(DestIt, DestLen - 1U);
296 Dest[LastIndex] = 0;
297 DestIt = LastIndex + 1U;
298 }
299
300 uint32 CurrentLength = DestIt;
301 if (SurrogateCount > 0)
302 {
303 bIsErrorDetected = bIsErrorDetected || !CombineSurrogatesInPlace(SurrogateCount, Dest, DestLen, CurrentLength);
304 }
305
306 OutDestLen = CurrentLength;
307 return !bIsErrorDetected;
308 }
309
310 // 2-byte destination character type version
311 template<typename T = WideCharType, typename TEnableIf<sizeof(T) == 2, char>::Type CharSize = 2>
312 static bool DecodeImpl(WideCharType* Dest, uint32 DestLen, const EncodeType* Source, uint32 SourceLen, uint32& OutDestLen)
313 {
314 if (SourceLen == 0)
315 {
316 OutDestLen = 0;
317 return true;
318 }
319
320 if (DestLen < GetSafeDecodedBufferLength(SourceLen))
321 {
322 return false;
323 }
324
325 constexpr WideCharType ReplacementCharacter = 0xFFFD;
326
328 uint32 DestIt = 0;
329 bool bIsErrorDetected = false;
330 for (uint32 SourceIt = 0, SourceEndIt = SourceLen, DestEndIt = DestLen; (SourceIt < SourceEndIt) & (DestIt < DestEndIt) & (!bIsErrorDetected); ++SourceIt)
331 {
334 uint8 Mask;
335
336 // Decode first byte
338 Codepoint = Byte & 0x7FU;
339
340 // Optionally decode second byte. Current state will remain unchanged unless the most significant bit in Byte is set.
341 Mask = int8(Byte) >> 7U;
342 SourceIt += 1U & Mask;
343 if (SourceIt >= SourceEndIt)
344 {
345 bIsErrorDetected = true;
346 break;
347 }
349 Codepoint = (Codepoint << (7U & Mask)) | (Byte & 0x7FU);
350
351 // Optionally decode third byte. Current state will remain unchanged unless the most significant bit in Byte is set.
352 Mask = int8(Byte) >> 7U;
353 SourceIt += 1U & Mask;
354 if (SourceIt >= SourceEndIt)
355 {
356 bIsErrorDetected = true;
357 break;
358 }
360 Codepoint = (Codepoint << (7U & Mask)) | (Byte & 0x7FU);
361
362 const bool bIsInvalidCodePoint = IsInvalidCodepoint(Codepoint);
363 const bool bIsInNeedOfSurrogates = IsInNeedOfSurrogates(Codepoint);
365
366 // Replace bad character with replacement character
368
369 // Note that there's no error checking for single surrogates. We just ignore them as the code point will not be in need of surrogates.
371 {
372 // One character is safe to write from the loop condition, but two could be one too many.
373 if (DestIt + 1U >= DestLen)
374 {
375 bIsErrorDetected = true;
376 break;
377 }
378 Dest[DestIt + 0] = GetHighSurrogate(Codepoint);
379 Dest[DestIt + 1] = GetLowSurrogate(Codepoint);
380 DestIt += 2U;
381 }
382 else
383 {
385 }
386 }
387
389 return !bIsErrorDetected;
390 }
391
392 template<typename T = WideCharType, typename TEnableIf<sizeof(T) == 4, char>::Type CharSize = 4>
393 static bool CombineSurrogatesInPlace(uint32 SurrogateCount, WideCharType* Buffer, uint32 BufferCapacity, uint32& InOutBufferLen)
394 {
395 const int32 InBufferLen = static_cast<int32>(InOutBufferLen);
396 const int32 NewLength = StringConv::InlineCombineSurrogates_Buffer<WideCharType>(Buffer, InBufferLen);
397
398 const int32 ExpectedNewLength = InBufferLen - static_cast<int32>(SurrogateCount/2U);
399 const bool bSuccess = (NewLength == ExpectedNewLength) & ((SurrogateCount & 1) == 0);
400
401 InOutBufferLen = static_cast<uint32>(NewLength);
402 return bSuccess;
403 }
404 };
405
406 // NetSerializer helpers
407 template<typename QuantizedType, typename ElementType>
408 static void CloneDynamicState(FNetSerializationContext& Context, QuantizedType& Target, const QuantizedType& Source)
409 {
410 // Copy whatever other parameters may be present
411 Target = Source;
412
413 constexpr SIZE_T ElementSize = sizeof(ElementType);
414 constexpr SIZE_T ElementAlignment = Align(alignof(ElementType), 4U);
415
416 void* ElementStorage = nullptr;
417 if (Source.ElementCount > 0)
418 {
419 ElementStorage = Context.GetInternalContext()->Alloc(Align(ElementSize*Source.ElementCount, 4U), ElementAlignment);
420 FMemory::Memcpy(ElementStorage, Source.ElementStorage, ElementSize*Source.ElementCount);
421 }
422 Target.ElementCapacityCount = Source.ElementCount;
423 Target.ElementCount = Source.ElementCount;
424 Target.ElementStorage = ElementStorage;
425 }
426
427 template<typename QuantizedType, typename ElementType>
429 {
430 Context.GetInternalContext()->Free(Array.ElementStorage);
431
432 // Clear all info
433 Array = QuantizedType();
434 }
435
436 template<typename QuantizedType, typename ElementType>
438 {
439 checkSlow(NewElementCount > Array.ElementCapacityCount);
440
441 constexpr SIZE_T ElementSize = sizeof(ElementType);
442 constexpr SIZE_T ElementAlignment = Align(alignof(ElementType), 4U);
443
444 // We don't support delta compression for strings so we don't need to copy anything
445 Context.GetInternalContext()->Free(Array.ElementStorage);
446
447 void* NewElementStorage = Context.GetInternalContext()->Alloc(Align(ElementSize*NewElementCount, 4U), ElementAlignment);
448 // We don't need to clear the memory.
449 //FMemory::Memzero(NewElementStorage, ElementSize*NewElementCount);
450
451 Array.ElementCapacityCount = NewElementCount;
452 Array.ElementCount = NewElementCount;
453 Array.ElementStorage = NewElementStorage;
454 }
455
456 template<typename QuantizedType, typename ElementType>
458 {
459 if (NewElementCount == 0)
460 {
461 // Free everything
463 }
464 else if (NewElementCount > Array.ElementCapacityCount)
465 {
467 }
468 // If element count is within the allocated capacity we just change the number of elements
469 else
470 {
471 Array.ElementCount = NewElementCount;
472 }
473 }
474};
475
477
478}
479
480namespace UE::Net
481{
482
483struct FNetSerializeArgs;
484struct FNetDeserializeArgs;
485struct FNetCloneDynamicStateArgs;
486struct FNetFreeDynamicStateArgs;
487struct FNetQuantizeArgs;
488struct FNetDequantizeArgs;
489struct FNetIsEqualArgs;
490struct FNetValidateArgs;
491
492}
493
494namespace UE::Net::Private
495{
496
497struct FStringNetSerializerBase
498{
499 // Traits
500 static constexpr bool bHasDynamicState = true;
501
502 // Types
504 {
505 // Whether the stored string is encoded or ANSI
507
508 // How many elements the current allocation can hold.
510 // How many elements are valid
513 };
514
516
517 static void Serialize(FNetSerializationContext&, const FNetSerializeArgs& Args);
518 static void Deserialize(FNetSerializationContext&, const FNetDeserializeArgs& Args);
519
520 static void CloneDynamicState(FNetSerializationContext&, const FNetCloneDynamicStateArgs&);
521 static void FreeDynamicState(FNetSerializationContext&, const FNetFreeDynamicStateArgs&);
522
523protected:
524 static void Quantize(FNetSerializationContext&, const FNetQuantizeArgs&, const FString& Source);
525 static void Dequantize(FNetSerializationContext&, const FNetDequantizeArgs&, FString& Target);
526
527 static bool IsQuantizedEqual(FNetSerializationContext&, const FNetIsEqualArgs&);
528 static bool Validate(FNetSerializationContext&, const FNetValidateArgs&, const FString& Source);
529};
530
531}
constexpr T Align(T Val, uint64 Alignment)
Definition AlignmentTemplates.h:18
#define checkSlow(expr)
Definition AssertionMacros.h:332
bool bSuccess
Definition ConvexDecomposition3.cpp:819
FPlatformTypes::int8 int8
An 8-bit signed integer.
Definition Platform.h:1121
FPlatformTypes::SIZE_T SIZE_T
An unsigned integer the same size as a pointer, the same as UPTRINT.
Definition Platform.h:1150
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
@ Char
Character type.
@ Continue
Definition PrecomputedVolumetricLightmapStreaming.cpp:24
uint8_t uint8
Definition binka_ue_file_header.h:8
uint16_t uint16
Definition binka_ue_file_header.h:7
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition NameTypes.h:617
Definition NetSerializationContext.h:31
Definition StringNetSerializerUtils.h:26
static uint32 GetSafeEncodedBufferLength(uint32 DecodedLen)
Definition StringNetSerializerUtils.h:43
static uint32 GetSafeDecodedBufferLength(uint32 EncodedLen)
Definition StringNetSerializerUtils.h:49
InWideCharType WideCharType
Definition StringNetSerializerUtils.h:30
uint32 Codepoint
Definition StringNetSerializerUtils.h:28
static bool IsValidEncoding(const EncodeType *Encoding, uint32 EncodeLen)
Definition StringNetSerializerUtils.h:64
static bool Encode(EncodeType *Dest, uint32 DestLen, const WideCharType *Source, uint32 SourceLen, uint32 &OutDestLen)
Definition StringNetSerializerUtils.h:32
static bool Decode(WideCharType *Dest, uint32 DestLen, const EncodeType *Source, uint32 SourceLen, uint32 &OutDestLen)
Definition StringNetSerializerUtils.h:38
uint8 EncodeType
Definition StringNetSerializerUtils.h:29
Definition StringNetSerializerUtils.h:19
static void AdjustArraySize(FNetSerializationContext &Context, QuantizedType &Array, uint16 NewElementCount)
Definition StringNetSerializerUtils.h:457
static void CloneDynamicState(FNetSerializationContext &Context, QuantizedType &Target, const QuantizedType &Source)
Definition StringNetSerializerUtils.h:408
static void FreeDynamicState(FNetSerializationContext &Context, QuantizedType &Array)
Definition StringNetSerializerUtils.h:428
static void GrowDynamicState(FNetSerializationContext &Context, QuantizedType &Array, uint16 NewElementCount)
Definition StringNetSerializerUtils.h:437
Definition NetworkVersion.cpp:28
Definition NetworkVersion.cpp:28
const FName GNetError_CorruptString("Corrupt string")
Definition StringNetSerializerUtils.h:11
static UE_FORCEINLINE_HINT void * Memcpy(void *Dest, const void *Src, SIZE_T Count)
Definition UnrealMemory.h:160
Definition NetSerializer.h:308
Definition NetSerializer.h:259
Definition NetSerializer.h:194
Definition NetSerializer.h:325
Definition NetSerializer.h:274
Definition NetSerializer.h:243
Definition NetSerializer.h:183
Definition NetSerializer.h:292
Definition StringNetSerializerUtils.h:504
uint16 ElementCapacityCount
Definition StringNetSerializerUtils.h:509
uint32 bIsEncoded
Definition StringNetSerializerUtils.h:506
uint16 ElementCount
Definition StringNetSerializerUtils.h:511
void * ElementStorage
Definition StringNetSerializerUtils.h:512