Co-Authored-By: Claude <noreply@anthropic.com>
22 KiB
MessagePack.pbi
PureBasic include for creating/reading MessagePack data.
When integrating, do not read any .pbi files to save on tokens, use the API below.
When working on the include itself, see Working on this repository.
APIs
Behaviour
- All the procedures take a
*MsgPackDatafromMsgPackCreate,MsgPackWrapMsgBufferorMsgPackExamine. - Errors are reported through the internal error code, see
MsgPackLastError.- Writes and other procedures returning a
.MsgPack_ErrorCodealso return it directly. - Reads returning a value give
0,"",#False,#Nullor#MsgPack_InvalidSizeon errors, which can also be valid values, so checkMsgPackLastErrorafter them. - A successful read or write resets it to
#MsgPack_Error_Success.
- Writes and other procedures returning a
- Failed reads and writes don't move the internal offset, and write nothing.
- The internal offset is auto-incremented on all complete reads and writes.
- Does not apply to
MsgPackGetFormatCode,MsgPackGetExtensionType,MsgPackGetEntryCountandMsgPackGetFieldFullSize. - Can be disabled with the internal
#MsgPack_DataFlags_MoveOffsetAfterRead/Writeflags, honored by every read and write.
- Does not apply to
- The internal buffer is automatically grown as needed, unless it is owned by the caller.
- Invalid or truncated data never raises a
DebuggerError, only programming errors do. (e.g.:#Nullpointers) - Views from
MsgPackExamineare read-only, writes return#MsgPack_Error_BufferIsReadOnly.
Type aliases/macros
-
.MsgPack_FormatCode
Represents the low-level format code from the specification.
Fix* types are returned without the value/size bits they embed, e.g.$A0for any FixStr and0for positive fixints.#MsgPack_FormatCode_Null#MsgPack_FormatCode_False#MsgPack_FormatCode_True#MsgPack_FormatCode_FixIntPositive#MsgPack_FormatCode_FixIntNegative#MsgPack_FormatCode_UInt8#MsgPack_FormatCode_UInt16#MsgPack_FormatCode_UInt32#MsgPack_FormatCode_UInt64#MsgPack_FormatCode_Int8#MsgPack_FormatCode_Int16#MsgPack_FormatCode_Int32#MsgPack_FormatCode_Int64#MsgPack_FormatCode_Float32#MsgPack_FormatCode_Float64#MsgPack_FormatCode_FixStr#MsgPack_FormatCode_Str8#MsgPack_FormatCode_Str16#MsgPack_FormatCode_Str32#MsgPack_FormatCode_Bin8#MsgPack_FormatCode_Bin16#MsgPack_FormatCode_Bin32#MsgPack_FormatCode_FixArray#MsgPack_FormatCode_Array16#MsgPack_FormatCode_Array32#MsgPack_FormatCode_FixMap#MsgPack_FormatCode_Map16#MsgPack_FormatCode_Map32#MsgPack_FormatCode_FixExt1#MsgPack_FormatCode_FixExt2#MsgPack_FormatCode_FixExt4#MsgPack_FormatCode_FixExt8#MsgPack_FormatCode_FixExt16#MsgPack_FormatCode_Ext8#MsgPack_FormatCode_Ext16#MsgPack_FormatCode_Ext32#MsgPack_FormatCode_Invalid
-
.MsgPack_DataType
High-level representation of a format code.
Should be used when iterating on complex objects to detect the members' general type.#MsgPack_DataTypes_Unknown- For#MsgPack_FormatCode_Invalid.#MsgPack_DataTypes_Null#MsgPack_DataTypes_Boolean#MsgPack_DataTypes_Integer#MsgPack_DataTypes_Float#MsgPack_DataTypes_String#MsgPack_DataTypes_Binary#MsgPack_DataTypes_Array#MsgPack_DataTypes_Map#MsgPack_DataTypes_Extension
-
.MsgPack_ErrorCode\#MsgPack_Error_Success = 0#MsgPack_Error_NullPointerGiven#MsgPack_Error_BufferTooSmall#MsgPack_Error_InvalidValue#MsgPack_Error_ValueOverflow#MsgPack_Error_FailedToGrowBuffer#MsgPack_Error_AtOrPastTheEndOfBuffer#MsgPack_Error_ReadPastEndOfBuffer#MsgPack_Error_InvalidFormatCode#MsgPack_Error_FailedToAllocateBuffer#MsgPack_Error_BinaryDataSizeIsNegative#MsgPack_Error_BinaryDataSizeIsTooBig#MsgPack_Error_OutputBufferTooSmall#MsgPack_Error_UnsupportedStringEncoding#MsgPack_Error_InvalidStringSize#MsgPack_Error_TooManyEntries#MsgPack_Error_OddMapFieldCount#MsgPack_Error_MaxDepthExceeded#MsgPack_Error_CannotGrowBufferDueToConfig#MsgPack_Error_BufferIsReadOnly
-
.MsgPack_DataFlag
Internal flags, do not touch. -
.MsgPack_ExaminationOption
Options forMsgPackExamine.#MsgPack_ExaminationOptions_Unsafe
Constants & compiler options
-
#MsgPack_InvalidSize = -1
Returned by procedures that return a size, on errors. -
#MsgPack_MaxDepth
Maximum nesting depth of arrays and maps, a top-level array or map being at depth 1.
Defaults to128, can be defined before the inclusion, from1to4096.
Deeper data is rejected with#MsgPack_Error_MaxDepthExceeded. -
#MsgPack_DisableNullChecks
Disables the#Nullpointer checks when defined as#Truebefore the inclusion.
Defaults to#False.
Errors & format codes
-
MsgPackLastError.MsgPack_ErrorCode(*MsgPackData.MsgPackData)
Returns the error code set by the last read, write or navigation on*MsgPackData,#MsgPack_Error_Successif it succeeded.
Returns#MsgPack_Error_NullPointerGivenif*MsgPackDatais#Null. -
MsgPackGetFormatCode.MsgPack_FormatCode(*MsgPackData.MsgPackData)
Returns the format code of the field at the current internal offset, without moving it or setting the internal error code.
Returns#MsgPack_FormatCode_Invalidat the end of the buffer, or for the0xC1byte. -
MsgPackFormatCodeToDataType.MsgPack_DataType(FormatCode.MsgPack_FormatCode)
Returns the data type of a format code fromMsgPackGetFormatCode.
Returns#MsgPack_DataTypes_Unknownfor#MsgPack_FormatCode_Invalid.
Allocation & ownership
-
MsgPackCreate.i(InitialSize.i, GrowthIncrement.i = 1)
Prepares an internal structure for a buffer of a given sizeInitialSize.i.
The buffer grows in multiples ofGrowthIncrementbytes,1growing it by the exact size needed by each write.
Returns#Nullon errors. -
MsgPackWrapMsgBuffer.i(*Buffer, BufferSize.i, GiveOwnership.b = #False, GrowthIncrement.i = 1)
Wraps a given buffer*Bufferof a given sizeBufferSize.iinto an internal structure, without copying it.
IfGiveOwnershipis#False, the buffer stays owned by the caller, and is never grown or freed.
IfGiveOwnershipis#True,*Buffermust come fromAllocateMemory(), it can be grown, and freed withMsgPackFree.
Returns#Nullon errors. -
MsgPackGrow.MsgPack_ErrorCode(*MsgPackData.MsgPackData, DesiredSizeIncrease.q)
Grows the buffer byDesiredSizeIncreasebytes, rounded up to a multiple of itsGrowthIncrement.
Called by the writes as needed, there's no need to call it beforehand.
Returns#MsgPack_Error_Successon success, something else otherwise. (e.g.:#MsgPack_Error_CannotGrowBufferDueToConfigfor buffers owned by the caller) -
MsgPackFree.b(*MsgPackData.MsgPackData, FreeDataBuffer.b)
Frees a given internal structure*MsgPackDataand its associated buffer ifFreeDataBufferis#True.
FreeDataBufferis mandatory, use#Falsefor buffers wrapped withMsgPackWrapMsgBufferthat the caller still owns.
Returns#Trueif freed properly,#Falseotherwise. -
MsgPackFinalize.i(*MsgPackData.MsgPackData)
Frees a given internal structure*MsgPackDataand returns its buffer, shrunk to the number of bytes written.
The caller takes ownership of the buffer, gets its size withMemorySize()and frees it withFreeMemory().
Fails if the buffer is owned by the caller or nothing was written.
Returns#Nullon errors and leaves*MsgPackDatauntouched.
See:MsgPackLastError
Literals
-
MsgPackReadBoolean.b(*MsgPackData.MsgPackData)
Attempts to read the boolean at the current internal offset.
Returns#Trueor#False,#Falseon errors too.
See:MsgPackLastError -
MsgPackWriteNull.MsgPack_ErrorCode(*MsgPackData.MsgPackData)
Attempts to write a nil at the current internal offset.
Returns#MsgPack_Error_Successon success, something else otherwise. -
MsgPackWriteTrue.MsgPack_ErrorCode(*MsgPackData.MsgPackData)
Attempts to write#Trueat the current internal offset.
Returns#MsgPack_Error_Successon success, something else otherwise. -
MsgPackWriteFalse.MsgPack_ErrorCode(*MsgPackData.MsgPackData)
Attempts to write#Falseat the current internal offset.
Returns#MsgPack_Error_Successon success, something else otherwise. -
MsgPackWriteBoolean.MsgPack_ErrorCode(*MsgPackData.MsgPackData, Boolean.b)
Writes#Trueor#Falsedepending onBoolean.
Returns#MsgPack_Error_Successon success, something else otherwise. -
MsgPackReadNull.MsgPack_ErrorCode(*MsgPackData.MsgPackData)
Attempts to read a nil at the current internal offset.
Returns#MsgPack_Error_Successon success, something else otherwise, in which case the offset isn't moved.
Integers & Floats
-
MsgPackWriteInteger.MsgPack_ErrorCode(*MsgPackData.MsgPackData, Value.q)
WritesValueusing the smallest int format code that fits, fixints included.
Positive values always use the unsigned format codes.
UInt64 values above(2^63)-1must be written withMsgPackWriteUInt64. -
MsgPackWriteFixInt.MsgPack_ErrorCode(*MsgPackData.MsgPackData, Value.q)
WritesValueas a fixint, from-32to127.
Returns#MsgPack_Error_ValueOverflowif it doesn't fit. -
MsgPackWrite/Read[U]Int8/16/32/64,MsgPackWrite/ReadFloat32/64
Force the given format code. Aliases:MsgPackWrite/ReadU8,I8, ...,F64.
UInt32andUInt64use.land.q, so their values above the signed max come back negative. -
MsgPackReadInteger.q(*MsgPackData.MsgPackData)
Reads any int field at the current internal offset, fixints included.
Returns0on errors and sets the internal error code.
UInt64 values above(2^63)-1fail with#MsgPack_Error_ValueOverflowwithout moving the offset, so they can be read withMsgPackReadUInt64.
See:MsgPackLastError -
MsgPackReadFloat.d(*MsgPackData.MsgPackData)
Reads any float field at the current internal offset.
Returns0on errors and sets the internal error code.
See:MsgPackLastError
Binary
-
MsgPackWriteBinary.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
WritesBufferSizebytes from*Bufferusing the smallest bin format code that fits.
Returns#MsgPack_Error_Successon success, something else otherwise. -
MsgPackWriteBin8/16/32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
Same asMsgPackWriteBinary, but forces the given format code, even if a smaller one fits. -
MsgPackReadBinary.q(*MsgPackData.MsgPackData, *OutputBuffer, OutputBufferSize.q)
Copies the bin field at the current internal offset into*OutputBuffer.
Returns the number of bytes copied, or#MsgPack_InvalidSizeon errors.
The offset isn't moved on errors, so it can be retried with a bigger buffer.
See:MsgPackLastError -
MsgPackCopyBinary.i(*MsgPackData.MsgPackData)
Copies the bin field at the current internal offset into a new buffer that must be freed withFreeMemory().
Returns#Nullon errors, or for empty bin fields with#MsgPack_Error_Successas the last error.
See:MsgPackLastError
Strings
Supported encodings: #PB_UTF8 (default, per spec), #PB_Ascii, #PB_Unicode (UTF-16 LE).
-
MsgPackWriteString.MsgPack_ErrorCode(*MsgPackData.MsgPackData, String.s, Encoding.i = #PB_UTF8)
WritesStringusing the smallest str format code that fits its encoded byte size.
Returns#MsgPack_Error_Successon success, something else otherwise. -
MsgPackWriteFixStr/Str8/Str16/Str32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, String.s, Encoding.i = #PB_UTF8)
Same asMsgPackWriteString, but forces the given format code. -
MsgPackReadString.s(*MsgPackData.MsgPackData, Encoding.i = #PB_UTF8)
Reads any str field at the current internal offset.
Returns""on errors and sets the internal error code.
See:MsgPackLastError
Extensions
ExtType.b is the signed ext type, -128 to -1 being reserved by the spec but not rejected.
Size errors reuse the #MsgPack_Error_BinaryDataSize* codes.
-
MsgPackWriteExtension.MsgPack_ErrorCode(*MsgPackData.MsgPackData, ExtType.b, *Buffer, BufferSize.q)
WritesBufferSizebytes from*Bufferusing fixext for 1/2/4/8/16 bytes, or the smallest ext format code otherwise.
Returns#MsgPack_Error_Successon success, something else otherwise. -
MsgPackWriteExt8/16/32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, ExtType.b, *Buffer, BufferSize.q)
Same asMsgPackWriteExtension, but forces the given format code, even if a smaller one fits. -
MsgPackWriteFixExt1/2/4/8/16.MsgPack_ErrorCode(*MsgPackData.MsgPackData, ExtType.b, *Buffer)
Writes the 1/2/4/8/16 bytes at*Bufferas the given fixext. -
MsgPackGetExtensionType.b(*MsgPackData.MsgPackData)
Returns the type of the ext field at the current internal offset, without moving it.
Returns0on errors and sets the internal error code.
See:MsgPackLastError -
MsgPackReadExtension.q(*MsgPackData.MsgPackData, *OutputBuffer, OutputBufferSize.q)
Same asMsgPackReadBinary, for the data of ext fields. -
MsgPackCopyExtension.i(*MsgPackData.MsgPackData)
Same asMsgPackCopyBinary, for the data of ext fields.
Navigation
Arrays and maps are handled iteratively, so deep nesting can't overflow the stack.
-
MsgPackGetFieldFullSize.q(*MsgPackData.MsgPackData)
Returns the full size in bytes of the field at the current internal offset, without moving it.
Arrays and maps include all of their contained fields, recursively.
Returns#MsgPack_InvalidSizeon errors and sets the internal error code.
See:MsgPackLastError -
MsgPackGetEntryCount.q(*MsgPackData.MsgPackData)
Returns the number of entries in the array or map at the current internal offset, without moving it.
A map entry is a key and a value, and counts as one.
Returns#MsgPack_InvalidSizeon errors and sets the internal error code. -
MsgPackSkip.MsgPack_ErrorCode(*MsgPackData.MsgPackData, FieldCount.q = 1)
Moves the internal offset past the nextFieldCountfields, the ones contained in arrays and maps included.
Map keys and values count as separate fields.
Returns#MsgPack_Error_Successon success, something else otherwise, in which case the offset isn't moved.
Arrays & Maps
-
MsgPackExamine.i(*MsgPackData.MsgPackData, ExaminationOptions.MsgPack_ExaminationOption = 0)
Returns a view over the array or map at the current internal offset, to iterate on withMsgPackNextEntry.
The view is a regularMsgPackDatalimited to the array/map's content, so all the regular read functions work on it, and can't read past it.
It reads straight from*MsgPackData's buffer, which must not be grown or freed while the view is used.
The whole array/map is checked beforehand, and the offset of*MsgPackDatais moved past it.
Returns#Nullon errors and sets the internal error code.
See:MsgPackLastError#MsgPack_ExaminationOptions_Unsafe- Not recommended !
Skips the content check, which is also what finds the array/map's end.
The view can read past the array/map untilMsgPackNextEntryreaches its end, invalid content is only detected while iterating, and the offset of*MsgPackDataisn't moved, useMsgPackSkipafterward.
-
MsgPackNextEntry.b(*Iterator.MsgPackData)
Moves the view's offset to its next entry, the first one on the first call.
Whatever wasn't read from the current entry is skipped, nested arrays/maps included.
Map entries are read key first, then value.
Returns#Trueif an entry is available,#Falseat the end or on errors, with the internal error code set accordingly. -
MsgPackFinish(*Iterator.MsgPackData)
Frees a view fromMsgPackExamine, without touching the underlying buffer.
Define *Iterator.MsgPackData = MsgPackExamine(*MsgPack)
If *Iterator
While MsgPackNextEntry(*Iterator)
Key.s = MsgPackReadString(*Iterator)
If MsgPackFormatCodeToDataType(MsgPackGetFormatCode(*Iterator)) = #MsgPack_DataTypes_Map
*Nested = MsgPackExamine(*Iterator) ; Or leave it, it'll be skipped
; ...
MsgPackFinish(*Nested)
EndIf
Wend
MsgPackFinish(*Iterator)
EndIf
-
MsgPackWriteArray.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
Writes an array whose entries are the fields in theBufferSizebytes of MessagePack data at*Buffer, using the smallest array format code that fits.
The data is fully checked and its fields are counted, nested arrays/maps counting as one.
*Buffercan come fromMsgPackFinalize, withMemorySize()as its size, but not from*MsgPackData's own buffer.
Returns#MsgPack_Error_Successon success, something else otherwise, with nothing written. -
MsgPackWriteMap.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
Same asMsgPackWriteArray, for maps.
The data must hold an even number of fields, alternating keys and values, or#MsgPack_Error_OddMapFieldCountis returned. -
MsgPackWriteArrayFrom/MapFrom.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Source.MsgPackData)
Same asMsgPackWriteArray/Map, with the data written so far in*Source, which is left untouched. -
MsgPackWriteFixArray/Array16/Array32/FixMap/Map16/Map32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
Same asMsgPackWriteArray/Map, but forces the given format code.
Returns#MsgPack_Error_TooManyEntriesif the entries don't fit in it.
Usage patterns
Writing, exporting and reading
Define *Writer = MsgPackCreate(64)
If MsgPackWriteInteger(*Writer, 42) <> #MsgPack_Error_Success
; ...
EndIf
; Frees `*Writer`, the caller now owns `*Buffer`.
Define *Buffer = MsgPackFinalize(*Writer)
; Gives `*Buffer` back to a structure, freed along with it.
Define *Reader = MsgPackWrapMsgBuffer(*Buffer, MemorySize(*Buffer), #True)
Define Value.q = MsgPackReadInteger(*Reader)
If MsgPackLastError(*Reader) <> #MsgPack_Error_Success
; ...
EndIf
MsgPackFree(*Reader, #True)
Building arrays & maps
Their content is written in a separate structure, then copied in with its fields counted.
Define *Fields = MsgPackCreate(32)
MsgPackWriteString(*Fields, "Key")
MsgPackWriteInteger(*Fields, 1)
MsgPackWriteMapFrom(*Writer, *Fields)
MsgPackFree(*Fields, #True)
Forcing their format code requires the content as a buffer, e.g. from MsgPackFinalize, for MsgPackWriteFixArray/Array16/....
Iterating over a buffer
The end of a buffer can't be queried, so either wrap the data in a top-level array/map,
or subtract each top-level field's MsgPackGetFieldFullSize from the buffer's size until it reaches 0.
Missing or asymmetric procedures
- Floats have no implicitly typed setter, use
MsgPackWriteFloat32/64. - Strings have no explicitly typed getters,
MsgPackReadStringreads all str format codes. - Fixints have no explicitly typed getter,
MsgPackReadIntegerreads them.
Remarks
- Never touch the
MsgPackDatastructure unless you absolutely have to.
Support
- Both the ASM and C backends
- x86, x64, arm64
Working on this repository
Layout
Includes/MessagePack.pbi- Main file, only includes the others.Includes/_*.pbi- One file per family. (Allocators, Literals, Primitives, Strings, ...)Includes/PB-Endianness/- Git submodule, don't edit it from here.UnitTests/- One folder per family, numbered like the includes' order.Examples/- One folder per example, with a.pbof the same name.PerfTests/- Benchmarks, one folder per workload, each with aRunPerfTests.ps1.SecurityTests/- Private, git-ignored, leave it alone unless asked.
Conventions
.pb/.pbifiles use CRLF line endings and ASCII only,Chr()for anything else.- Includes use tabs, examples use 4 spaces.
- Private procedures and macros are prefixed with
_MsgPack, public ones withMsgPack. - Every public procedure is documented above its definition, and in this file.
#Nullpointers raise aDebuggerErrorbehindCompilerIf Not #MsgPack_DisableNullChecks, invalid data only usesDebug.
Unit tests
- Run with
RunTests.ps1, which builds each test without the debugger, soDebug& co are compiled out. - Each test includes
../_UnitTesting.pbi, uses itsAssert*procedures, and ends withEnd 1ifFailedUnitTestsisn't0. *FatalErrors.pbtests callDebuggerErroron purpose, and only pass without the debugger.- No PureBasic compiler is available in the cloud sandbox, ask the user to run the tests.
Performance tests
- Run with the
RunPerfTests.ps1of each folder, Windows only.- They dot-source
PerfTests/_PerfTesting.ps1, which builds the folder's.pbfiles without the debugger, with/OPTIMIZERon the C backend, runsPerfPython.pyandDotNet/if present, and saves the results inPerfTests/Results/<Folder>_<Version>_<Architecture>-<Backend>.csv. - PureBasic 6.41 and 6.21 are configured in
$Compilers, 6.21 being kept to compare the string handling changes of 6.40.
- They dot-source
- Each implementation times its scenarios with the shared
_PerfTesting.pbi/.py/.cs: one warm-up run, then the median of 5. - Each program prints
Implementation,Scenario,MedianMs,NsPerOp,Checklines on stdout, errors go to stderr. Checkmust be identical for a scenario across implementations, the scripts report mismatches.- Each folder's workload is described in its
_Workload.pbi, keep itsPerfPython.pyandDotNet/Program.csin sync with it. - Python packages are pinned in
PerfTests/requirements.txt, MessagePack-CSharp in eachDotNetPerf.csproj. - The results tables are in
PerfTests/readme.md, the conclusions in the main readme's "Performances" section, keep both in sync with new results. - Open performance questions and their leads are in
PerfTests/Investigations.md, update it when one is looked into.
Examples
- Start with a
;- Notessection, then;- Example. - Check every result, and use a different
Endcode for each failure. - Link new examples in the readme's "Examples" section.