Merge pull request #1 from aziascreations/dev-binary-data

Added Bin/Str/Ext, Optimized code duplication
This commit is contained in:
2026-10-28 21:56:44 +01:00
committed by GitHub
26 changed files with 2646 additions and 404 deletions
+3
View File
@@ -18,3 +18,6 @@ Trash/
*.7z
*.zip
/spec*
# Security test suite
SecurityTests/
+92
View File
@@ -80,8 +80,12 @@ PureBasic include for creating/reading MessagePack data.
* `#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_CannotGrowBufferDueToConfig`
* `.MsgPack_DataFlag` \
@@ -127,6 +131,94 @@ PureBasic include for creating/reading MessagePack data.
Returns `#MsgPack_Error_Success` on success, something else otherwise.
### Integers & Floats
* `MsgPackWriteInteger.MsgPack_ErrorCode(*MsgPackData.MsgPackData, Value.q)` \
Writes `Value` using the smallest int format code that fits, fixints included. \
Positive values always use the unsigned format codes. \
UInt64 values above `(2^63)-1` must be written with `MsgPackWriteUInt64`.
* `MsgPackWriteFixInt.MsgPack_ErrorCode(*MsgPackData.MsgPackData, Value.q)` \
Writes `Value` as a fixint, from `-32` to `127`. \
Returns `#MsgPack_Error_ValueOverflow` if it doesn't fit.
* `MsgPackWrite/Read[U]Int8/16/32/64`, `MsgPackWrite/ReadFloat32/64` \
Force the given format code. Aliases: `MsgPackWrite/ReadU8`, `I8`, ..., `F64`. \
`UInt32` and `UInt64` use `.l` and `.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. \
Returns `0` on errors and sets the internal error code. \
UInt64 values above `(2^63)-1` fail with `#MsgPack_Error_ValueOverflow` without moving the offset, so they can be read with `MsgPackReadUInt64`. \
See: `MsgPackLastError`
* `MsgPackReadFloat.d(*MsgPackData.MsgPackData)` \
Reads any float field at the current internal offset. \
Returns `0` on errors and sets the internal error code. \
See: `MsgPackLastError`
### Binary
* `MsgPackWriteBinary.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)` \
Writes `BufferSize` bytes from `*Buffer` using the smallest bin format code that fits. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackWriteBin8/16/32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)` \
Same as `MsgPackWriteBinary`, 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_InvalidSize` on 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 with `FreeMemory()`. \
Returns `#Null` on errors, or for empty bin fields with `#MsgPack_Error_Success` as 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)` \
Writes `String` using the smallest str format code that fits its encoded byte size. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackWriteFixStr/Str8/Str16/Str32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, String.s, Encoding.i = #PB_UTF8)` \
Same as `MsgPackWriteString`, 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)` \
Writes `BufferSize` bytes from `*Buffer` using fixext for 1/2/4/8/16 bytes, or the smallest ext format code otherwise. \
Returns `#MsgPack_Error_Success` on success, something else otherwise.
* `MsgPackWriteExt8/16/32.MsgPack_ErrorCode(*MsgPackData.MsgPackData, ExtType.b, *Buffer, BufferSize.q)` \
Same as `MsgPackWriteExtension`, 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 `*Buffer` as the given fixext.
* `MsgPackGetExtensionType.b(*MsgPackData.MsgPackData)` \
Returns the type of the ext field at the current internal offset, without moving it. \
Returns `0` on errors and sets the internal error code. \
See: `MsgPackLastError`
* `MsgPackReadExtension.q(*MsgPackData.MsgPackData, *OutputBuffer, OutputBufferSize.q)` \
Same as `MsgPackReadBinary`, for the data of ext fields.
* `MsgPackCopyExtension.i(*MsgPackData.MsgPackData)` \
Same as `MsgPackCopyBinary`, for the data of ext fields.
## Remarks
* Never touch the `MsgPackData` structure unless you **absolutely** have to.
+1
View File
@@ -30,4 +30,5 @@ XIncludeFile "./_Literals.pbi"
XIncludeFile "./_Binary.pbi"
XIncludeFile "./_Primitives.pbi"
XIncludeFile "./_Strings.pbi"
XIncludeFile "./_Extensions.pbi"
XIncludeFile "./_Iterators.pbi"
+6 -5
View File
@@ -47,8 +47,8 @@ Procedure.i MsgPackCreate(InitialSize.i, GrowthIncrement.i = 1)
*ReturnedValue\BufferOffset = 0
*ReturnedValue\BufferSize = InitialSize
*ReturnedValue\BufferGrowthIncrement = GrowthIncrement
*ReturnedValue\DataFlags = 0
*ReturnedValue\DataFlags = #_MsgPack_DefaultDataFlags
*ReturnedValue\Buffer = AllocateMemory(InitialSize)
If Not *ReturnedValue\Buffer
Debug("Failed to allocate memory for buffer !")
@@ -85,11 +85,11 @@ Procedure.i MsgPackWrapMsgBuffer(*Buffer, BufferSize.i, GiveOwnership.b = #False
*ReturnedValue\BufferOffset = 0
*ReturnedValue\BufferSize = BufferSize
*ReturnedValue\BufferGrowthIncrement = GrowthIncrement
*ReturnedValue\DataFlags = 0
*ReturnedValue\DataFlags = #_MsgPack_DefaultDataFlags
*ReturnedValue\Buffer = *Buffer
If Not GiveOwnership
*ReturnedValue\DataFlags = #MsgPack_DataFlags_CannotGrow | #MsgPack_DataFlags_NoBufferFree
*ReturnedValue\DataFlags = *ReturnedValue\DataFlags | #MsgPack_DataFlags_CannotGrow | #MsgPack_DataFlags_NoBufferFree
EndIf
ProcedureReturn *ReturnedValue
@@ -146,7 +146,8 @@ Procedure.MsgPack_ErrorCode MsgPackGrow(*MsgPackData.MsgPackData, DesiredSizeInc
EndIf
; `DesiredSizeIncrease` is positive, so a new size that isn't bigger can only come from an overflow.
If NewSize <= *MsgPackData\BufferSize
; Sizes that don't fit in a `.i` would also be truncated by `ReAllocateMemory()` on x86.
If NewSize <= *MsgPackData\BufferSize Or NewSize > #_MsgPack_MaxIntegerValue
DebuggerError("We had an overflow in one of the growth values, the calculation is messed up !")
*MsgPackData\LastError = #MsgPack_Error_ValueOverflow
ProcedureReturn #MsgPack_Error_ValueOverflow
+296 -148
View File
@@ -9,7 +9,7 @@
; * //PureBasic 5.73 LTS (x86/x64)
; * //PureBasic 6.21 (x86/x64)
; * //PureBasic 6.21 C Backend (arm64)
;
;
; ==- Links & License -===========================
; License: CC0 1.0 Universal (Public Domain)
; GitHub: https://github.com/aziascreations/PB-MessagePack
@@ -20,6 +20,16 @@
; Sizes are all unsigned 8/16/32, using Q fixes any potential issues !
; The "sized field" helpers below handle every `[FormatCode][Size][Data]` layout
; shared by the str, bin and ext families.
; The `SizeFieldByteLength` parameter is the number of bytes used to store the size:
; * `0` - Size is stored in the format code's lower 5 bits. (FixStr)
; Or implied by the format code. (FixExt1/2/4/8/16)
; * `1` - Str8, Bin8, Ext8
; * `2` - Str16, Bin16, Ext16
; * `4` - Str32, Bin32, Ext32
; The ext family also has a 1 byte type between its size and its data.
; ------------------------------------------------------------------------------
@@ -30,194 +40,332 @@ CompilerIf #PB_Compiler_IsMainFile
CompilerEndIf
XIncludeFile "./_Commons.pbi"
XIncludeFile "./_Utils.pbi"
XIncludeFile "./_Allocators.pbi"
; ------------------------------------------------------------------------------
;- Constants
#MsgPack_Binary_InvalidSize = -1
XIncludeFile "./_Primitives.pbi"
; ------------------------------------------------------------------------------
;- Procedures
;-> Utils
;-> Internal helpers
; Validates `DataSize`, reserves space for the whole field, writes its format code
; and size, and moves the offset right after them.
; `ExtraHeaderSize` more bytes are reserved right after the size, for the ext type.
; The caller is expected to write the extra header bytes and `DataSize` bytes of data,
; and move the offset afterward.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackWriteSizedFieldHeader(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, SizeFieldByteLength.a, DataSize.q, ExtraHeaderSize.a = 0)
Protected MaxDataSize.q
Protected ErrorCode.MsgPack_ErrorCode
; TODO: Change this into a simplified global one for strings/maps/arrays/bin !
Procedure.q _MsgPackGetSizeAfterFormatCode(*MsgPackData.MsgPackData)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Binary_InvalidSize
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
; FIXME: Not implemented !
Select SizeFieldByteLength
Case 0 :
MaxDataSize = 31
Case 1 :
MaxDataSize = 255
Case 2 :
MaxDataSize = 65535
Case 4 :
MaxDataSize = 4294967295
Default
DebuggerError("Invalid size field length: " + Str(SizeFieldByteLength))
*MsgPackData\LastError = #MsgPack_Error_InvalidValue
ProcedureReturn #MsgPack_Error_InvalidValue
EndSelect
If DataSize < 0
DebuggerWarning("Received negative size !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsNegative
ProcedureReturn #MsgPack_Error_BinaryDataSizeIsNegative
ElseIf DataSize > MaxDataSize
DebuggerWarning("Data is bigger than `" + Str(MaxDataSize) + "` bytes !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsTooBig
ProcedureReturn #MsgPack_Error_BinaryDataSizeIsTooBig
EndIf
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 1 + SizeFieldByteLength + ExtraHeaderSize + DataSize)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
Select SizeFieldByteLength
Case 0
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode | DataSize)
Case 1
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode)
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, DataSize)
Case 2
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode)
PokeU(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapU16(DataSize))
Case 4
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode)
PokeL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI32(DataSize))
EndSelect
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1 + SizeFieldByteLength
ProcedureReturn #MsgPack_Error_Success
EndProcedure
; Writes a full `[FormatCode][Size][Data]` field by copying `DataSize` bytes from `*Data`.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackWriteSizedField(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, SizeFieldByteLength.a, *Data, DataSize.q)
Protected ErrorCode.MsgPack_ErrorCode
CompilerIf Not #MsgPack_DisableNullChecks
; Checked before the header is written, so nothing is left half-written.
If *MsgPackData And DataSize > 0 And Not *Data
DebuggerError("A #Null data buffer was passed !")
*MsgPackData\LastError = #MsgPack_Error_NullPointerGiven
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
If DataSize > 0
CopyMemory(*Data, *MsgPackData\Buffer + *MsgPackData\BufferOffset, DataSize)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + DataSize
EndIf
ProcedureReturn #MsgPack_Error_Success
EndProcedure
; Reads the header of a str, bin or ext field at the current offset, without moving it.
; `ExpectedDataType` must be `#MsgPack_DataTypes_String`, `#MsgPack_DataTypes_Binary`
; or `#MsgPack_DataTypes_Extension`.
; On success, `*HeaderSize` receives the format code + size field (+ ext type) length,
; and `*DataSize` the data's length, both guaranteed to be within the buffer.
; For ext fields, the type is the header's last byte.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackReadSizedFieldHeader(*MsgPackData.MsgPackData, ExpectedDataType.MsgPack_DataType, *HeaderSize.Integer, *DataSize.Quad)
Protected FormatCode.MsgPack_FormatCode
Protected SizeFieldByteLength.a
Protected ExtTypeByteLength.a = 0
Protected DataSize.q
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
If _MsgPackIsAtTheEnd(*MsgPackData)
Debug("End of buffer reached, cannot check data format code !")
*MsgPackData\LastError = #MsgPack_Error_AtOrPastTheEndOfBuffer
ProcedureReturn #MsgPack_Error_AtOrPastTheEndOfBuffer
EndIf
FormatCode = MsgPackGetFormatCode(*MsgPackData)
If MsgPackFormatCodeToDataType(FormatCode) <> ExpectedDataType
Debug("Unexpected format code ! (" + RSet(Hex(PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset)), 2, "0") + ")")
*MsgPackData\LastError = #MsgPack_Error_InvalidFormatCode
ProcedureReturn #MsgPack_Error_InvalidFormatCode
EndIf
Select FormatCode
Case #MsgPack_FormatCode_FixStr:
SizeFieldByteLength = 0
Case #MsgPack_FormatCode_Str8,
#MsgPack_FormatCode_Bin8,
#MsgPack_FormatCode_Ext8:
SizeFieldByteLength = 1
Case #MsgPack_FormatCode_Str16,
#MsgPack_FormatCode_Bin16,
#MsgPack_FormatCode_Ext16:
SizeFieldByteLength = 2
Case #MsgPack_FormatCode_Str32,
#MsgPack_FormatCode_Bin32,
#MsgPack_FormatCode_Ext32:
SizeFieldByteLength = 4
Case #MsgPack_FormatCode_FixExt1:
SizeFieldByteLength = 0 : DataSize = 1
Case #MsgPack_FormatCode_FixExt2:
SizeFieldByteLength = 0 : DataSize = 2
Case #MsgPack_FormatCode_FixExt4:
SizeFieldByteLength = 0 : DataSize = 4
Case #MsgPack_FormatCode_FixExt8:
SizeFieldByteLength = 0 : DataSize = 8
Case #MsgPack_FormatCode_FixExt16:
SizeFieldByteLength = 0 : DataSize = 16
EndSelect
If ExpectedDataType = #MsgPack_DataTypes_Extension
ExtTypeByteLength = 1
EndIf
If Not _MsgPackHasSpaceLeft(*MsgPackData, 1 + SizeFieldByteLength + ExtTypeByteLength)
Debug("End of buffer reached, cannot read the field's size !")
*MsgPackData\LastError = #MsgPack_Error_BufferTooSmall
ProcedureReturn #MsgPack_Error_BufferTooSmall
EndIf
Select SizeFieldByteLength
Case 0:
; FixExt sizes were already set above.
If FormatCode = #MsgPack_FormatCode_FixStr
DataSize = PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset) & %00011111
EndIf
Case 1:
DataSize = PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1)
Case 2:
DataSize = EndianSwapU16(PeekU(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
Case 4:
DataSize = EndianSwapI32(PeekL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1)) & $FFFFFFFF
EndSelect
If Not _MsgPackHasSpaceLeft(*MsgPackData, 1 + SizeFieldByteLength + ExtTypeByteLength + DataSize)
Debug("End of buffer reached, cannot read the field's data !")
*MsgPackData\LastError = #MsgPack_Error_BufferTooSmall
ProcedureReturn #MsgPack_Error_BufferTooSmall
EndIf
*HeaderSize\i = 1 + SizeFieldByteLength + ExtTypeByteLength
*DataSize\q = DataSize
*MsgPackData\LastError = #MsgPack_Error_Success
ProcedureReturn #MsgPack_Error_Success
EndProcedure
; Copies the data of the bin or ext field at the current offset into `*OutputBuffer`.
; Returns: The number of bytes copied.
; OnError: Returns `#MsgPack_InvalidSize` and sets the `MsgPackData\LastError` field.
; The offset is left untouched, so a bigger buffer can be given afterward.
Procedure.q _MsgPackReadSizedFieldData(*MsgPackData.MsgPackData, ExpectedDataType.MsgPack_DataType, *OutputBuffer, OutputBufferSize.q)
Protected HeaderSize.i
Protected DataSize.q
If _MsgPackReadSizedFieldHeader(*MsgPackData, ExpectedDataType, @HeaderSize, @DataSize) <> #MsgPack_Error_Success
ProcedureReturn #MsgPack_InvalidSize
EndIf
If DataSize > OutputBufferSize
Debug("Output buffer is too small ! (" + Str(OutputBufferSize) + " < " + Str(DataSize) + ")")
*MsgPackData\LastError = #MsgPack_Error_OutputBufferTooSmall
ProcedureReturn #MsgPack_InvalidSize
EndIf
If DataSize > 0
CompilerIf Not #MsgPack_DisableNullChecks
If Not *OutputBuffer
DebuggerError("A #Null output buffer was passed !")
*MsgPackData\LastError = #MsgPack_Error_NullPointerGiven
ProcedureReturn #MsgPack_InvalidSize
EndIf
CompilerEndIf
CopyMemory(*MsgPackData\Buffer + *MsgPackData\BufferOffset + HeaderSize, *OutputBuffer, DataSize)
EndIf
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + HeaderSize + DataSize
ProcedureReturn DataSize
EndProcedure
; Copies the data of the bin or ext field at the current offset into a new buffer.
; Returns: A buffer from `AllocateMemory()` that the caller must free with `FreeMemory()`.
; Its size can be obtained with `MemorySize()`.
; Empty fields return `#Null` with `MsgPackData\LastError` set to `#MsgPack_Error_Success`.
; OnError: Returns `#Null` and sets the `MsgPackData\LastError` field.
Procedure.i _MsgPackCopySizedFieldData(*MsgPackData.MsgPackData, ExpectedDataType.MsgPack_DataType)
Protected HeaderSize.i
Protected DataSize.q
Protected *ReturnedValue = #Null
If _MsgPackReadSizedFieldHeader(*MsgPackData, ExpectedDataType, @HeaderSize, @DataSize) <> #MsgPack_Error_Success
ProcedureReturn #Null
EndIf
If DataSize > 0
*ReturnedValue = AllocateMemory(DataSize, #PB_Memory_NoClear)
If Not *ReturnedValue
Debug("Failed to allocate memory for the copied data !")
*MsgPackData\LastError = #MsgPack_Error_FailedToAllocateBuffer
ProcedureReturn #Null
EndIf
CopyMemory(*MsgPackData\Buffer + *MsgPackData\BufferOffset + HeaderSize, *ReturnedValue, DataSize)
EndIf
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + HeaderSize + DataSize
ProcedureReturn *ReturnedValue
EndProcedure
;-> Getters
Procedure.i MsgPackCopyBinary(*MsgPackData.MsgPackData)
; Copies the bin field at the current offset into `*OutputBuffer`.
; Returns: The number of bytes copied.
; OnError: Returns `#MsgPack_InvalidSize` and sets the `MsgPackData\LastError` field.
; The offset is left untouched, so a bigger buffer can be given afterward.
Procedure.q MsgPackReadBinary(*MsgPackData.MsgPackData, *OutputBuffer, OutputBufferSize.q)
ProcedureReturn _MsgPackReadSizedFieldData(*MsgPackData, #MsgPack_DataTypes_Binary, *OutputBuffer, OutputBufferSize)
EndProcedure
Procedure.i MsgPackReadBinary(*MsgPackData.MsgPackData, *OutputBuffer, OutputBufferSize.q)
; Copies the bin field at the current offset into a new buffer.
; Returns: A buffer from `AllocateMemory()` that the caller must free with `FreeMemory()`.
; Its size can be obtained with `MemorySize()`.
; Empty bin fields return `#Null` with `MsgPackData\LastError` set to `#MsgPack_Error_Success`.
; OnError: Returns `#Null` and sets the `MsgPackData\LastError` field.
Procedure.i MsgPackCopyBinary(*MsgPackData.MsgPackData)
ProcedureReturn _MsgPackCopySizedFieldData(*MsgPackData, #MsgPack_DataTypes_Binary)
EndProcedure
;-> Setters
; Writes `BufferSize` bytes from `*Buffer` as a bin8.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteBin8(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
*MsgPackData\LastError = #MsgPack_Error_Success
If BufferSize < 0
DebuggerWarning("Received negative size !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsNegative
ProcedureReturn *MsgPackData\LastError
ElseIf BufferSize > 255
DebuggerWarning("Binary buffer is bigger than `(2^8)-1` bytes !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsTooBig
ProcedureReturn *MsgPackData\LastError
EndIf
If Not _MsgPackHasSpaceLeft(*MsgPackData, BufferSize + 2)
Protected GrowthErrorCode.MsgPack_ErrorCode
Debug("Growing buffer by `" + Str(BufferSize + 2) + "` bytes for bin8...")
GrowthErrorCode = MsgPackGrow(*MsgPackData, BufferSize + 2)
If GrowthErrorCode <> #MsgPack_Error_Success
; The structure's error code field is set by the called function !
ProcedureReturn GrowthErrorCode
EndIf
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_Bin8)
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, BufferSize)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 2
CopyMemory(*Buffer, *MsgPackData\Buffer + *MsgPackData\BufferOffset, BufferSize)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + BufferSize
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteSizedField(*MsgPackData, #MsgPack_FormatCode_Bin8, 1, *Buffer, BufferSize)
EndProcedure
; Writes `BufferSize` bytes from `*Buffer` as a bin16, even if a smaller format code fits.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteBin16(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
*MsgPackData\LastError = #MsgPack_Error_Success
If BufferSize < 0
DebuggerWarning("Received negative size !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsNegative
ProcedureReturn *MsgPackData\LastError
ElseIf BufferSize > 65535
DebuggerWarning("Binary buffer is bigger than `(2^16)-1` bytes !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsTooBig
ProcedureReturn *MsgPackData\LastError
EndIf
If Not _MsgPackHasSpaceLeft(*MsgPackData, BufferSize + 3)
Protected GrowthErrorCode.MsgPack_ErrorCode
Debug("Growing buffer by `" + Str(BufferSize + 3) + "` bytes for bin16...")
GrowthErrorCode = MsgPackGrow(*MsgPackData, BufferSize + 3)
If GrowthErrorCode <> #MsgPack_Error_Success
; The structure's error code field is set by the called function !
ProcedureReturn GrowthErrorCode
EndIf
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_Bin16)
PokeU(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapU(BufferSize))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 3
CopyMemory(*Buffer, *MsgPackData\Buffer + *MsgPackData\BufferOffset, BufferSize)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + BufferSize
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteSizedField(*MsgPackData, #MsgPack_FormatCode_Bin16, 2, *Buffer, BufferSize)
EndProcedure
; Writes `BufferSize` bytes from `*Buffer` as a bin32, even if a smaller format code fits.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteBin32(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
*MsgPackData\LastError = #MsgPack_Error_Success
If BufferSize < 0
DebuggerWarning("Received negative size !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsNegative
ProcedureReturn *MsgPackData\LastError
ElseIf BufferSize > 4294967295
DebuggerWarning("Binary buffer is bigger than `(2^32)-1` bytes !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsTooBig
ProcedureReturn *MsgPackData\LastError
EndIf
If Not _MsgPackHasSpaceLeft(*MsgPackData, BufferSize + 5)
Protected GrowthErrorCode.MsgPack_ErrorCode
Debug("Growing buffer by `" + Str(BufferSize + 5) + "` bytes for bin16...")
GrowthErrorCode = MsgPackGrow(*MsgPackData, BufferSize + 5)
If GrowthErrorCode <> #MsgPack_Error_Success
; The structure's error code field is set by the called function !
ProcedureReturn GrowthErrorCode
EndIf
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_Bin32)
PokeI(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI(BufferSize))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 5
CopyMemory(*Buffer, *MsgPackData\Buffer + *MsgPackData\BufferOffset, BufferSize)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + BufferSize
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteSizedField(*MsgPackData, #MsgPack_FormatCode_Bin32, 4, *Buffer, BufferSize)
EndProcedure
; Writes `BufferSize` bytes from `*Buffer` using the smallest bin format code that fits.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteBinary(*MsgPackData.MsgPackData, *Buffer, BufferSize.q)
If BufferSize < 0
DebuggerWarning("Received negative size !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsNegative
ProcedureReturn *MsgPackData\LastError
ElseIf BufferSize <= 255
If BufferSize <= 255
ProcedureReturn MsgPackWriteBin8(*MsgPackData, *Buffer, BufferSize)
ElseIf BufferSize <= 65535
ElseIf BufferSize <= 65535
ProcedureReturn MsgPackWriteBin16(*MsgPackData, *Buffer, BufferSize)
ElseIf BufferSize <= 4294967295
Else
; Sizes above `(2^32)-1` are rejected by the helper.
ProcedureReturn MsgPackWriteBin32(*MsgPackData, *Buffer, BufferSize)
Else
DebuggerWarning("Binary buffer is bigger than `(2^32)-1` bytes !")
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsTooBig
ProcedureReturn *MsgPackData\LastError
EndIf
EndIf
EndProcedure
+15
View File
@@ -51,6 +51,12 @@ CompilerEndIf
; Used to represent any size that couldn't be calculated/derived/...
#MsgPack_InvalidSize = -1
; Biggest value a `.i` can hold, used to reject sizes that would be truncated on x86.
CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
#_MsgPack_MaxIntegerValue = $7FFFFFFF
CompilerElse
#_MsgPack_MaxIntegerValue = $7FFFFFFFFFFFFFFF
CompilerEndIf
; ------------------------------------------------------------------------------
@@ -146,10 +152,16 @@ Enumeration _MsgPack_ErrorCodes
#MsgPack_Error_AtOrPastTheEndOfBuffer = 11
#MsgPack_Error_ReadPastEndOfBuffer = 12
#MsgPack_Error_InvalidFormatCode = 13
#MsgPack_Error_FailedToAllocateBuffer = 14
; 5x - Binary
#MsgPack_Error_BinaryDataSizeIsNegative = 50
#MsgPack_Error_BinaryDataSizeIsTooBig = 51
#MsgPack_Error_OutputBufferTooSmall = 52
; 6x - Strings
#MsgPack_Error_UnsupportedStringEncoding = 60
#MsgPack_Error_InvalidStringSize = 61
#MsgPack_Error_CannotGrowBufferDueToConfig = 100
@@ -182,6 +194,9 @@ EnumerationBinary _MsgPack_DataFlags
#MsgPack_DataFlags_CanProvideVals = %0000000010000000
EndEnumeration
; Flags set on every new `MsgPackData`, so all reads and writes move the offset.
#_MsgPack_DefaultDataFlags = #MsgPack_DataFlags_MoveOffsetAfterRead | #MsgPack_DataFlags_MoveOffsetAfterWrite
EnumerationBinary _MsgPack_ExaminationOptions
; If set, we don't seek the end of the examined map/array.
#MsgPack_ExaminationOptions_NoEndSeeking = %0000000000000001
+229
View File
@@ -0,0 +1,229 @@
;{- Code Header
; ==- Basic Info -================================
; Name: _Extensions.pbi
; Version: 0.0.0
; Author: Herwin Bozet (NibblePoker)
;
; ==- Compatibility -=============================
; Tested compiler version:
; * //PureBasic 5.73 LTS (x86/x64)
; * //PureBasic 6.21 (x86/x64)
; * //PureBasic 6.21 C Backend (arm64)
;
; ==- Links & License -===========================
; License: CC0 1.0 Universal (Public Domain)
; GitHub: https://github.com/aziascreations/PB-MessagePack
;}
; ------------------------------------------------------------------------------
;- Notes
; Specification:
; * https://github.com/msgpack/msgpack/blob/master/spec.md
; The ext type is a signed byte, `-128` to `-1` being reserved by the spec. (`-1` = timestamp)
; Reserved types are not rejected, so predefined types can be written with these procedures.
; FixExt fields have no size field, their data size is implied by the format code.
; See "_Binary.pbi" for the shared "sized field" helpers.
; ------------------------------------------------------------------------------
;- Compiler directive
CompilerIf #PB_Compiler_IsMainFile
EnableExplicit
CompilerEndIf
XIncludeFile "./_Commons.pbi"
XIncludeFile "./_Primitives.pbi"
XIncludeFile "./_Binary.pbi"
; ------------------------------------------------------------------------------
;- Procedures
;-> Internal helpers
; Writes a full ext field by copying `DataSize` bytes from `*Data`.
; `SizeFieldByteLength` set to `0` indicates a FixExt whose implied size must match `DataSize`.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackWriteExtField(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, SizeFieldByteLength.a, ExtType.b, *Data, DataSize.q)
Protected ErrorCode.MsgPack_ErrorCode
Protected FixExtSize.q
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
; Checked before the header is written, so nothing is left half-written.
If DataSize > 0 And Not *Data
DebuggerError("A #Null data buffer was passed !")
*MsgPackData\LastError = #MsgPack_Error_NullPointerGiven
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
If SizeFieldByteLength = 0
Select FormatCode
Case #MsgPack_FormatCode_FixExt1 : FixExtSize = 1
Case #MsgPack_FormatCode_FixExt2 : FixExtSize = 2
Case #MsgPack_FormatCode_FixExt4 : FixExtSize = 4
Case #MsgPack_FormatCode_FixExt8 : FixExtSize = 8
Case #MsgPack_FormatCode_FixExt16 : FixExtSize = 16
Default
FixExtSize = #MsgPack_InvalidSize
EndSelect
If DataSize <> FixExtSize
DebuggerError("Invalid FixExt size: " + Str(DataSize))
*MsgPackData\LastError = #MsgPack_Error_InvalidValue
ProcedureReturn #MsgPack_Error_InvalidValue
EndIf
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 2 + DataSize)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1
Else
ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, DataSize, 1)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
EndIf
PokeB(*MsgPackData\Buffer + *MsgPackData\BufferOffset, ExtType)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1
If DataSize > 0
CopyMemory(*Data, *MsgPackData\Buffer + *MsgPackData\BufferOffset, DataSize)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + DataSize
EndIf
ProcedureReturn #MsgPack_Error_Success
EndProcedure
;-> Getters
; Reads the type of the ext field at the current offset, without moving it.
; Returns: The ext type.
; OnError: Returns `0` and sets the `MsgPackData\LastError` field.
Procedure.b MsgPackGetExtensionType(*MsgPackData.MsgPackData)
Protected HeaderSize.i
Protected DataSize.q
If _MsgPackReadSizedFieldHeader(*MsgPackData, #MsgPack_DataTypes_Extension, @HeaderSize, @DataSize) <> #MsgPack_Error_Success
ProcedureReturn 0
EndIf
ProcedureReturn PeekB(*MsgPackData\Buffer + *MsgPackData\BufferOffset + HeaderSize - 1)
EndProcedure
; Copies the data of the ext field at the current offset into `*OutputBuffer`.
; Its type can be read beforehand with `MsgPackGetExtensionType()`.
; Returns: The number of bytes copied.
; OnError: Returns `#MsgPack_InvalidSize` and sets the `MsgPackData\LastError` field.
; The offset is left untouched, so a bigger buffer can be given afterward.
Procedure.q MsgPackReadExtension(*MsgPackData.MsgPackData, *OutputBuffer, OutputBufferSize.q)
ProcedureReturn _MsgPackReadSizedFieldData(*MsgPackData, #MsgPack_DataTypes_Extension, *OutputBuffer, OutputBufferSize)
EndProcedure
; Copies the data of the ext field at the current offset into a new buffer.
; Its type can be read beforehand with `MsgPackGetExtensionType()`.
; Returns: A buffer from `AllocateMemory()` that the caller must free with `FreeMemory()`.
; Its size can be obtained with `MemorySize()`.
; Empty ext fields return `#Null` with `MsgPackData\LastError` set to `#MsgPack_Error_Success`.
; OnError: Returns `#Null` and sets the `MsgPackData\LastError` field.
Procedure.i MsgPackCopyExtension(*MsgPackData.MsgPackData)
ProcedureReturn _MsgPackCopySizedFieldData(*MsgPackData, #MsgPack_DataTypes_Extension)
EndProcedure
;-> Setters
; Writes `BufferSize` bytes from `*Buffer` as an ext8 of type `ExtType`, even if a smaller format code fits.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteExt8(*MsgPackData.MsgPackData, ExtType.b, *Buffer, BufferSize.q)
ProcedureReturn _MsgPackWriteExtField(*MsgPackData, #MsgPack_FormatCode_Ext8, 1, ExtType, *Buffer, BufferSize)
EndProcedure
; Writes `BufferSize` bytes from `*Buffer` as an ext16 of type `ExtType`, even if a smaller format code fits.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteExt16(*MsgPackData.MsgPackData, ExtType.b, *Buffer, BufferSize.q)
ProcedureReturn _MsgPackWriteExtField(*MsgPackData, #MsgPack_FormatCode_Ext16, 2, ExtType, *Buffer, BufferSize)
EndProcedure
; Writes `BufferSize` bytes from `*Buffer` as an ext32 of type `ExtType`, even if a smaller format code fits.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteExt32(*MsgPackData.MsgPackData, ExtType.b, *Buffer, BufferSize.q)
ProcedureReturn _MsgPackWriteExtField(*MsgPackData, #MsgPack_FormatCode_Ext32, 4, ExtType, *Buffer, BufferSize)
EndProcedure
; Writes `BufferSize` bytes from `*Buffer` as an ext of type `ExtType` using the smallest
; format code that fits.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteExtension(*MsgPackData.MsgPackData, ExtType.b, *Buffer, BufferSize.q)
Select BufferSize
Case 1
ProcedureReturn _MsgPackWriteExtField(*MsgPackData, #MsgPack_FormatCode_FixExt1, 0, ExtType, *Buffer, BufferSize)
Case 2
ProcedureReturn _MsgPackWriteExtField(*MsgPackData, #MsgPack_FormatCode_FixExt2, 0, ExtType, *Buffer, BufferSize)
Case 4
ProcedureReturn _MsgPackWriteExtField(*MsgPackData, #MsgPack_FormatCode_FixExt4, 0, ExtType, *Buffer, BufferSize)
Case 8
ProcedureReturn _MsgPackWriteExtField(*MsgPackData, #MsgPack_FormatCode_FixExt8, 0, ExtType, *Buffer, BufferSize)
Case 16
ProcedureReturn _MsgPackWriteExtField(*MsgPackData, #MsgPack_FormatCode_FixExt16, 0, ExtType, *Buffer, BufferSize)
EndSelect
If BufferSize <= 255
ProcedureReturn MsgPackWriteExt8(*MsgPackData, ExtType, *Buffer, BufferSize)
ElseIf BufferSize <= 65535
ProcedureReturn MsgPackWriteExt16(*MsgPackData, ExtType, *Buffer, BufferSize)
Else
; Sizes above `(2^32)-1` are rejected by the helper.
ProcedureReturn MsgPackWriteExt32(*MsgPackData, ExtType, *Buffer, BufferSize)
EndIf
EndProcedure
; ------------------------------------------------------------------------------
;- Macros
; Writes the 1/2/4/8/16 bytes at `Buffer` as a FixExt of type `ExtType`.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Macro MsgPackWriteFixExt1(MsgPackData, ExtType, Buffer)
_MsgPackWriteExtField(MsgPackData, #MsgPack_FormatCode_FixExt1, 0, ExtType, Buffer, 1)
EndMacro
Macro MsgPackWriteFixExt2(MsgPackData, ExtType, Buffer)
_MsgPackWriteExtField(MsgPackData, #MsgPack_FormatCode_FixExt2, 0, ExtType, Buffer, 2)
EndMacro
Macro MsgPackWriteFixExt4(MsgPackData, ExtType, Buffer)
_MsgPackWriteExtField(MsgPackData, #MsgPack_FormatCode_FixExt4, 0, ExtType, Buffer, 4)
EndMacro
Macro MsgPackWriteFixExt8(MsgPackData, ExtType, Buffer)
_MsgPackWriteExtField(MsgPackData, #MsgPack_FormatCode_FixExt8, 0, ExtType, Buffer, 8)
EndMacro
Macro MsgPackWriteFixExt16(MsgPackData, ExtType, Buffer)
_MsgPackWriteExtField(MsgPackData, #MsgPack_FormatCode_FixExt16, 0, ExtType, Buffer, 16)
EndMacro
+10 -4
View File
@@ -51,7 +51,7 @@ Macro _MsgPackGetFieldDynamicSize_Within(MsgPackData, Mask) : (PeekA(MsgPackData
; Applies to: Str8, Bin8, Ext8
; Remark: Doesn't apply to Int/UInt variants since those have their own function for extracting the value.
Procedure.q _MsgPackGetFieldDynamicSize_1After(*MsgPackData.MsgPackData)
If Not _MsgPackHasSpaceLeft(*MsgPackData, *MsgPackData\BufferSize + 1)
If Not _MsgPackHasSpaceLeft(*MsgPackData, 1 + 1)
DebuggerWarning("We have less than 1 byte to read the field's dynamic size !")
ProcedureReturn #MsgPack_InvalidSize
EndIf
@@ -63,7 +63,7 @@ EndProcedure
; Applies to: Str16, Bin16, Ext16, Array16, Map16
; Remark: Doesn't apply to Int/UInt variants since those have their own function for extracting the value.
Procedure.q _MsgPackGetFieldDynamicSize_2After(*MsgPackData.MsgPackData)
If Not _MsgPackHasSpaceLeft(*MsgPackData, *MsgPackData\BufferSize + 2)
If Not _MsgPackHasSpaceLeft(*MsgPackData, 1 + 2)
DebuggerWarning("We have less than 2 bytes to read the field's dynamic size !")
ProcedureReturn #MsgPack_InvalidSize
EndIf
@@ -75,12 +75,12 @@ EndProcedure
; Applies to: Str32, Bin32, Ext32, Array32, Map32
; Remark: Doesn't apply to Int/UInt variants since those have their own function for extracting the value.
Procedure.q _MsgPackGetFieldDynamicSize_4After(*MsgPackData.MsgPackData)
If Not _MsgPackHasSpaceLeft(*MsgPackData, *MsgPackData\BufferSize + 4)
If Not _MsgPackHasSpaceLeft(*MsgPackData, 1 + 4)
DebuggerWarning("We have less than 4 bytes to read the field's dynamic size !")
ProcedureReturn #MsgPack_InvalidSize
EndIf
ProcedureReturn EndianSwapL(PeekL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
ProcedureReturn EndianSwapL(PeekL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1)) & $FFFFFFFF
EndProcedure
; Returns a given format code's spacing info based on itself and the data in the given MsgPack buffer.
@@ -98,6 +98,12 @@ Procedure _MsgPackGetFieldSpacingInfo(*MsgPackData.MsgPackData, ExaminedDataType
ProcedureReturn #False
EndIf
; The "within" sizes are read from the format code, which must be in the buffer.
If _MsgPackIsAtTheEnd(*MsgPackData)
DebuggerWarning("End of buffer reached, cannot read the field's dynamic size !")
ExaminedDataType = #MsgPack_FormatCode_Invalid
EndIf
Select ExaminedDataType
Case #MsgPack_FormatCode_FixIntPositive:
If *VariableSizeQ <> #Null
+250 -212
View File
@@ -26,6 +26,7 @@ CompilerEndIf
; Includes the required "Endianness.pbi"
XIncludeFile "./_Commons.pbi"
XIncludeFile "./_Allocators.pbi"
XIncludeFile "./_Utils.pbi"
@@ -37,16 +38,25 @@ XIncludeFile "./_Allocators.pbi"
; Ensures `Size` bytes can be written at the current offset, growing the buffer by
; exactly the missing amount if needed.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackReserveWrite(*MsgPackData.MsgPackData, Size.i)
Protected MissingSize.i
; FIXME: Move elsewhere since it's used in multiple places !
Procedure.MsgPack_ErrorCode _MsgPackReserveWrite(*MsgPackData.MsgPackData, Size.q)
Protected MissingSize.q
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
; `Size` is a quad so sized fields up to 4 GiB can't be truncated, but the end
; offset must still fit in a `.i`, which isn't the case on x86 past 2 GiB.
If Size < 0 Or Size > #_MsgPack_MaxIntegerValue - *MsgPackData\BufferOffset
DebuggerWarning("Requested size can't be addressed: " + Str(Size))
*MsgPackData\LastError = #MsgPack_Error_BinaryDataSizeIsTooBig
ProcedureReturn #MsgPack_Error_BinaryDataSizeIsTooBig
EndIf
MissingSize = (*MsgPackData\BufferOffset + Size) - *MsgPackData\BufferSize
If MissingSize > 0
; The structure's error code field is set by the called function !
@@ -95,255 +105,283 @@ EndProcedure
;-> Getters & Setters
; The "fixed field" helpers below handle every `[FormatCode][Value]` layout
; shared by the int and float families.
; The `ValueByteLength` parameter is the number of bytes used to store the value:
; * `0` - Value is stored in the format code's lower bits. (FixIntPositive, FixIntNegative)
; * `1` - UInt8, Int8
; * `2` - UInt16, Int16
; * `4` - UInt32, Int32, Float32
; * `8` - UInt64, Int64, Float64
; Values are passed around as raw bits in a quad, floats included.
;-> > UInt8
Procedure.MsgPack_ErrorCode MsgPackWriteUInt8(*MsgPackData.MsgPackData, Value.a)
; Reserves space for the whole field, writes its format code and big-endian value,
; and moves the offset right after them.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackWriteFixedField(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, ValueByteLength.a, Value.q)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 2)
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 1 + ValueByteLength)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_UInt8)
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, Value)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 2
Select ValueByteLength
Case 0
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode | (Value & $FF))
Case 1
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode)
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, Value)
Case 2
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode)
PokeU(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapU16(Value))
Case 4
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode)
PokeL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI32(Value))
Case 8
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, FormatCode)
PokeQ(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI64(Value))
EndSelect
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1 + ValueByteLength
ProcedureReturn #MsgPack_Error_Success
EndProcedure
Procedure.a MsgPackReadUInt8(*MsgPackData.MsgPackData)
Protected ReturnedValue.a
If _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_UInt8, 2) <> #MsgPack_Error_Success
; Reads the value of the field at the current offset if its format code is `FormatCode`.
; Doesn't support fixints, since their format code varies with the value.
; Returns: The value's raw bits, zero-extended. (e.g.: `$FF` for an Int8 `-1`)
; OnError: Returns `0` and sets the `MsgPackData\LastError` field.
Procedure.q _MsgPackReadFixedField(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, ValueByteLength.a)
Protected ReturnedValue.q
If _MsgPackExpectRead(*MsgPackData, FormatCode, 1 + ValueByteLength) <> #MsgPack_Error_Success
ProcedureReturn 0
EndIf
ReturnedValue = PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 2
Select ValueByteLength
Case 1
ReturnedValue = PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1)
Case 2
ReturnedValue = EndianSwapU16(PeekU(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
Case 4
ReturnedValue = EndianSwapI32(PeekL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1)) & $FFFFFFFF
Case 8
ReturnedValue = EndianSwapI64(PeekQ(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
EndSelect
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1 + ValueByteLength
ProcedureReturn ReturnedValue
EndProcedure
;-> Getters & Setters
;-> > UInt8
Procedure.MsgPack_ErrorCode MsgPackWriteUInt8(*MsgPackData.MsgPackData, Value.a)
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_UInt8, 1, Value)
EndProcedure
Procedure.a MsgPackReadUInt8(*MsgPackData.MsgPackData)
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_UInt8, 1)
EndProcedure
;-> > Int8
Procedure.MsgPack_ErrorCode MsgPackWriteInt8(*MsgPackData.MsgPackData, Value.b)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 2)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_Int8)
PokeB(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, Value)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 2
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_Int8, 1, Value)
EndProcedure
Procedure.b MsgPackReadInt8(*MsgPackData.MsgPackData)
Protected ReturnedValue.b
If _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_Int8, 2) <> #MsgPack_Error_Success
ProcedureReturn 0
EndIf
ReturnedValue = PeekB(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 2
ProcedureReturn ReturnedValue
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_Int8, 1)
EndProcedure
;-> > UInt16
Procedure.MsgPack_ErrorCode MsgPackWriteUInt16(*MsgPackData.MsgPackData, Value.u)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 3)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_UInt16)
PokeU(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapU16(Value))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 3
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_UInt16, 2, Value)
EndProcedure
Procedure.u MsgPackReadUInt16(*MsgPackData.MsgPackData)
Protected ReturnedValue.u
If _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_UInt16, 3) <> #MsgPack_Error_Success
ProcedureReturn 0
EndIf
ReturnedValue = EndianSwapU16(PeekU(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 3
ProcedureReturn ReturnedValue
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_UInt16, 2)
EndProcedure
;-> > > Int16
Procedure.MsgPack_ErrorCode MsgPackWriteInt16(*MsgPackData.MsgPackData, Value.w)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 3)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_Int16)
PokeW(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI16(Value))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 3
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_Int16, 2, Value)
EndProcedure
Procedure.w MsgPackReadInt16(*MsgPackData.MsgPackData)
Protected ReturnedValue.w
If _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_Int16, 3) <> #MsgPack_Error_Success
ProcedureReturn 0
EndIf
ReturnedValue = EndianSwapI16(PeekW(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 3
ProcedureReturn ReturnedValue
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_Int16, 2)
EndProcedure
;-> > > UInt32
; No unsigned Long in PureBasic: value is peeked/poked as-is via .l and returned unchanged.
Procedure.MsgPack_ErrorCode MsgPackWriteUInt32(*MsgPackData.MsgPackData, Value.l)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 5)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_UInt32)
PokeL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI32(Value))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 5
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_UInt32, 4, Value)
EndProcedure
Procedure.l MsgPackReadUInt32(*MsgPackData.MsgPackData)
Protected ReturnedValue.l
If _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_UInt32, 5) <> #MsgPack_Error_Success
ProcedureReturn 0
EndIf
ReturnedValue = EndianSwapI32(PeekL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 5
ProcedureReturn ReturnedValue
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_UInt32, 4)
EndProcedure
;-> > > Int32
Procedure.MsgPack_ErrorCode MsgPackWriteInt32(*MsgPackData.MsgPackData, Value.l)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 5)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_Int32)
PokeL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI32(Value))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 5
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_Int32, 4, Value)
EndProcedure
Procedure.l MsgPackReadInt32(*MsgPackData.MsgPackData)
Protected ReturnedValue.l
If _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_Int32, 5) <> #MsgPack_Error_Success
ProcedureReturn 0
EndIf
ReturnedValue = EndianSwapI32(PeekL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 5
ProcedureReturn ReturnedValue
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_Int32, 4)
EndProcedure
;-> > > UInt64
; No unsigned Quad in PureBasic: value is peeked/poked as-is via .q and returned unchanged.
Procedure.MsgPack_ErrorCode MsgPackWriteUInt64(*MsgPackData.MsgPackData, Value.q)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 9)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_UInt64)
PokeQ(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI64(Value))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 9
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_UInt64, 8, Value)
EndProcedure
Procedure.q MsgPackReadUInt64(*MsgPackData.MsgPackData)
Protected ReturnedValue.q
If _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_UInt64, 9) <> #MsgPack_Error_Success
ProcedureReturn 0
EndIf
ReturnedValue = EndianSwapI64(PeekQ(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 9
ProcedureReturn ReturnedValue
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_UInt64, 8)
EndProcedure
;-> > > Int64
Procedure.MsgPack_ErrorCode MsgPackWriteInt64(*MsgPackData.MsgPackData, Value.q)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 9)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_Int64)
PokeQ(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI64(Value))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 9
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_Int64, 8, Value)
EndProcedure
Procedure.q MsgPackReadInt64(*MsgPackData.MsgPackData)
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_Int64, 8)
EndProcedure
;-> > > FixInt
; Writes `Value` as a positive (`0` to `127`) or negative (`-32` to `-1`) fixint.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteFixInt(*MsgPackData.MsgPackData, Value.q)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_Error_NullPointerGiven
EndIf
CompilerEndIf
If Value >= 0 And Value <= 127
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_FixIntPositive, 0, Value)
ElseIf Value >= -32 And Value < 0
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_FixIntNegative, 0, Value)
EndIf
DebuggerWarning("Value doesn't fit in a fixint: " + Str(Value))
*MsgPackData\LastError = #MsgPack_Error_ValueOverflow
ProcedureReturn #MsgPack_Error_ValueOverflow
EndProcedure
;-> > > Any integer
; Writes `Value` using the smallest int format code that fits.
; Positive values always use the unsigned format codes, as recommended by the spec.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
; Remark: UInt64 values above `(2^63)-1` must be written with `MsgPackWriteUInt64()`.
Procedure.MsgPack_ErrorCode MsgPackWriteInteger(*MsgPackData.MsgPackData, Value.q)
If Value >= 0
If Value <= 127
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_FixIntPositive, 0, Value)
ElseIf Value <= 255
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_UInt8, 1, Value)
ElseIf Value <= 65535
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_UInt16, 2, Value)
ElseIf Value <= 4294967295
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_UInt32, 4, Value)
Else
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_UInt64, 8, Value)
EndIf
Else
If Value >= -32
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_FixIntNegative, 0, Value)
ElseIf Value >= -128
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_Int8, 1, Value)
ElseIf Value >= -32768
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_Int16, 2, Value)
ElseIf Value >= -2147483648
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_Int32, 4, Value)
Else
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_Int64, 8, Value)
EndIf
EndIf
EndProcedure
; Reads any int field at the current offset, fixints included.
; Returns: The value as a signed quad.
; OnError: Returns `0` and sets the `MsgPackData\LastError` field.
; UInt64 values above `(2^63)-1` fail with `#MsgPack_Error_ValueOverflow` and
; leave the offset untouched, so they can be read with `MsgPackReadUInt64()`.
Procedure.q MsgPackReadInteger(*MsgPackData.MsgPackData)
Protected ReturnedValue.q
If _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_Int64, 9) <> #MsgPack_Error_Success
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn 0
EndIf
CompilerEndIf
If _MsgPackIsAtTheEnd(*MsgPackData)
Debug("End of buffer reached, cannot check data format code !")
*MsgPackData\LastError = #MsgPack_Error_AtOrPastTheEndOfBuffer
ProcedureReturn 0
EndIf
ReturnedValue = EndianSwapI64(PeekQ(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 9
ProcedureReturn ReturnedValue
Select MsgPackGetFormatCode(*MsgPackData)
Case #MsgPack_FormatCode_FixIntPositive,
#MsgPack_FormatCode_FixIntNegative:
; The whole byte is the value, both ranges are valid signed bytes.
ReturnedValue = PeekB(*MsgPackData\Buffer + *MsgPackData\BufferOffset)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 1
*MsgPackData\LastError = #MsgPack_Error_Success
ProcedureReturn ReturnedValue
Case #MsgPack_FormatCode_UInt8:
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_UInt8, 1)
Case #MsgPack_FormatCode_UInt16:
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_UInt16, 2)
Case #MsgPack_FormatCode_UInt32:
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_UInt32, 4)
Case #MsgPack_FormatCode_UInt64:
If _MsgPackHasSpaceLeft(*MsgPackData, 9) And (PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1) & $80)
Debug("UInt64 value doesn't fit in a signed quad !")
*MsgPackData\LastError = #MsgPack_Error_ValueOverflow
ProcedureReturn 0
EndIf
ProcedureReturn _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_UInt64, 8)
Case #MsgPack_FormatCode_Int8:
ProcedureReturn MsgPackReadInt8(*MsgPackData)
Case #MsgPack_FormatCode_Int16:
ProcedureReturn MsgPackReadInt16(*MsgPackData)
Case #MsgPack_FormatCode_Int32:
ProcedureReturn MsgPackReadInt32(*MsgPackData)
Case #MsgPack_FormatCode_Int64:
ProcedureReturn MsgPackReadInt64(*MsgPackData)
EndSelect
Debug("Unexpected format code ! (" + RSet(Hex(PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset)), 2, "0") + ")")
*MsgPackData\LastError = #MsgPack_Error_InvalidFormatCode
ProcedureReturn 0
EndProcedure
@@ -352,31 +390,15 @@ EndProcedure
; The value's bits are moved and swapped as an integer, since loading a byte-swapped
; float into the FPU can alter its bits. (e.g.: NaN quieting on x87)
Procedure.MsgPack_ErrorCode MsgPackWriteFloat32(*MsgPackData.MsgPackData, Value.f)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 5)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_Float32)
PokeL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI32(PeekL(@Value)))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 5
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_Float32, 4, PeekL(@Value))
EndProcedure
Procedure.f MsgPackReadFloat32(*MsgPackData.MsgPackData)
Protected ReturnedValue.f
If _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_Float32, 5) <> #MsgPack_Error_Success
ProcedureReturn 0
EndIf
PokeL(@ReturnedValue, EndianSwapI32(PeekL(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1)))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 5
PokeL(@ReturnedValue, _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_Float32, 4))
ProcedureReturn ReturnedValue
EndProcedure
@@ -386,32 +408,48 @@ EndProcedure
; The value's bits are moved and swapped as an integer, since loading a byte-swapped
; float into the FPU can alter its bits. (e.g.: NaN quieting on x87)
Procedure.MsgPack_ErrorCode MsgPackWriteFloat64(*MsgPackData.MsgPackData, Value.d)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackReserveWrite(*MsgPackData, 9)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
PokeA(*MsgPackData\Buffer + *MsgPackData\BufferOffset, #MsgPack_FormatCode_Float64)
PokeQ(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1, EndianSwapI64(PeekQ(@Value)))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 9
ProcedureReturn #MsgPack_Error_Success
ProcedureReturn _MsgPackWriteFixedField(*MsgPackData, #MsgPack_FormatCode_Float64, 8, PeekQ(@Value))
EndProcedure
Procedure.d MsgPackReadFloat64(*MsgPackData.MsgPackData)
Protected ReturnedValue.d
If _MsgPackExpectRead(*MsgPackData, #MsgPack_FormatCode_Float64, 9) <> #MsgPack_Error_Success
PokeQ(@ReturnedValue, _MsgPackReadFixedField(*MsgPackData, #MsgPack_FormatCode_Float64, 8))
ProcedureReturn ReturnedValue
EndProcedure
;-> > > Any float
; Reads any float field at the current offset.
; Returns: The value as a double, float32 values being widened losslessly.
; OnError: Returns `0` and sets the `MsgPackData\LastError` field.
Procedure.d MsgPackReadFloat(*MsgPackData.MsgPackData)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn 0
EndIf
CompilerEndIf
If _MsgPackIsAtTheEnd(*MsgPackData)
Debug("End of buffer reached, cannot check data format code !")
*MsgPackData\LastError = #MsgPack_Error_AtOrPastTheEndOfBuffer
ProcedureReturn 0
EndIf
PokeQ(@ReturnedValue, EndianSwapI64(PeekQ(*MsgPackData\Buffer + *MsgPackData\BufferOffset + 1)))
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + 9
ProcedureReturn ReturnedValue
Select MsgPackGetFormatCode(*MsgPackData)
Case #MsgPack_FormatCode_Float32:
ProcedureReturn MsgPackReadFloat32(*MsgPackData)
Case #MsgPack_FormatCode_Float64:
ProcedureReturn MsgPackReadFloat64(*MsgPackData)
EndSelect
Debug("Unexpected format code ! (" + RSet(Hex(PeekA(*MsgPackData\Buffer + *MsgPackData\BufferOffset)), 2, "0") + ")")
*MsgPackData\LastError = #MsgPack_Error_InvalidFormatCode
ProcedureReturn 0
EndProcedure
+169 -2
View File
@@ -9,24 +9,191 @@
; * //PureBasic 5.73 LTS (x86/x64)
; * //PureBasic 6.21 (x86/x64)
; * //PureBasic 6.21 C Backend (arm64)
;
;
; ==- Links & License -===========================
; License: CC0 1.0 Universal (Public Domain)
; GitHub: https://github.com/aziascreations/PB-MessagePack
;}
; ------------------------------------------------------------------------------
;- Notes
; The spec defines str as UTF-8, which is the default everywhere.
; `#PB_Ascii` and `#PB_Unicode` (UTF-16 LE) are supported for peers that don't follow it.
; Sizes are always in bytes of the encoded string, without any null terminator.
; Strings containing a null character will be truncated on read, as with `PeekS()`.
; ------------------------------------------------------------------------------
;- Compiler directive
CompilerIf #PB_Compiler_IsMainFile
EnableExplicit
CompilerEndIf
XIncludeFile "./_Commons.pbi"
XIncludeFile "./_Allocators.pbi"
XIncludeFile "./_Binary.pbi"
; ------------------------------------------------------------------------------
;- Private macros
Macro _MsgPackIsSupportedStringEncoding(Encoding)
(Encoding = #PB_UTF8 Or Encoding = #PB_Ascii Or Encoding = #PB_Unicode)
EndMacro
; ------------------------------------------------------------------------------
;- Procedures
;-> Getters & Setters
;-> Internal helpers
; Returns the byte length of `String` once encoded in `Encoding`, without a null terminator.
; Sets the structure's error code and returns `#MsgPack_InvalidSize` on unsupported encodings.
Procedure.q _MsgPackGetEncodedStringSize(*MsgPackData.MsgPackData, String.s, Encoding.i)
CompilerIf Not #MsgPack_DisableNullChecks
If Not *MsgPackData
DebuggerError("A #Null MsgPackData pointer was passed !")
ProcedureReturn #MsgPack_InvalidSize
EndIf
CompilerEndIf
If Not _MsgPackIsSupportedStringEncoding(Encoding)
DebuggerError("Unsupported string encoding: " + Str(Encoding))
*MsgPackData\LastError = #MsgPack_Error_UnsupportedStringEncoding
ProcedureReturn #MsgPack_InvalidSize
EndIf
ProcedureReturn StringByteLength(String, Encoding)
EndProcedure
; Writes a full str field whose encoded size is already known.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackWriteStringField(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, SizeFieldByteLength.a, String.s, StringSize.q, Encoding.i)
Protected ErrorCode.MsgPack_ErrorCode
ErrorCode = _MsgPackWriteSizedFieldHeader(*MsgPackData, FormatCode, SizeFieldByteLength, StringSize)
If ErrorCode <> #MsgPack_Error_Success
ProcedureReturn ErrorCode
EndIf
If StringSize > 0
PokeS(*MsgPackData\Buffer + *MsgPackData\BufferOffset, String, -1, Encoding | #PB_String_NoZero)
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + StringSize
EndIf
ProcedureReturn #MsgPack_Error_Success
EndProcedure
; Writes a str field with an explicit format code.
; Sets the structure's error code and returns it.
Procedure.MsgPack_ErrorCode _MsgPackWriteStringAs(*MsgPackData.MsgPackData, FormatCode.MsgPack_FormatCode, SizeFieldByteLength.a, String.s, Encoding.i)
Protected StringSize.q
StringSize = _MsgPackGetEncodedStringSize(*MsgPackData, String, Encoding)
If StringSize = #MsgPack_InvalidSize
ProcedureReturn MsgPackLastError(*MsgPackData)
EndIf
ProcedureReturn _MsgPackWriteStringField(*MsgPackData, FormatCode, SizeFieldByteLength, String, StringSize, Encoding)
EndProcedure
;-> Getters
; Reads the str field at the current offset and decodes it from `Encoding`.
; Returns: The decoded string.
; OnError: Returns `""` and sets the `MsgPackData\LastError` field.
Procedure.s MsgPackReadString(*MsgPackData.MsgPackData, Encoding.i = #PB_UTF8)
Protected HeaderSize.i
Protected StringSize.q
Protected *StringStart
Protected ReturnedValue.s = ""
If _MsgPackReadSizedFieldHeader(*MsgPackData, #MsgPack_DataTypes_String, @HeaderSize, @StringSize) <> #MsgPack_Error_Success
ProcedureReturn ""
EndIf
If Not _MsgPackIsSupportedStringEncoding(Encoding)
DebuggerError("Unsupported string encoding: " + Str(Encoding))
*MsgPackData\LastError = #MsgPack_Error_UnsupportedStringEncoding
ProcedureReturn ""
EndIf
If Encoding = #PB_Unicode And StringSize % 2 <> 0
DebuggerWarning("UTF-16 string has an odd byte size !")
*MsgPackData\LastError = #MsgPack_Error_InvalidStringSize
ProcedureReturn ""
EndIf
*StringStart = *MsgPackData\Buffer + *MsgPackData\BufferOffset + HeaderSize
If StringSize > 0
Select Encoding
Case #PB_UTF8
ReturnedValue = PeekS(*StringStart, StringSize, #PB_UTF8 | #PB_ByteLength)
Case #PB_Ascii
ReturnedValue = PeekS(*StringStart, StringSize, #PB_Ascii)
Case #PB_Unicode
ReturnedValue = PeekS(*StringStart, StringSize / 2, #PB_Unicode)
EndSelect
EndIf
*MsgPackData\BufferOffset = *MsgPackData\BufferOffset + HeaderSize + StringSize
ProcedureReturn ReturnedValue
EndProcedure
;-> Setters
; Writes `String` encoded as `Encoding` using the smallest str format code that fits.
; Returns `#MsgPack_Error_Success` on success, something else otherwise.
Procedure.MsgPack_ErrorCode MsgPackWriteString(*MsgPackData.MsgPackData, String.s, Encoding.i = #PB_UTF8)
Protected StringSize.q
StringSize = _MsgPackGetEncodedStringSize(*MsgPackData, String, Encoding)
If StringSize = #MsgPack_InvalidSize
ProcedureReturn MsgPackLastError(*MsgPackData)
EndIf
If StringSize <= 31
ProcedureReturn _MsgPackWriteStringField(*MsgPackData, #MsgPack_FormatCode_FixStr, 0, String, StringSize, Encoding)
ElseIf StringSize <= 255
ProcedureReturn _MsgPackWriteStringField(*MsgPackData, #MsgPack_FormatCode_Str8, 1, String, StringSize, Encoding)
ElseIf StringSize <= 65535
ProcedureReturn _MsgPackWriteStringField(*MsgPackData, #MsgPack_FormatCode_Str16, 2, String, StringSize, Encoding)
Else
; Sizes above `(2^32)-1` are rejected by the helper.
ProcedureReturn _MsgPackWriteStringField(*MsgPackData, #MsgPack_FormatCode_Str32, 4, String, StringSize, Encoding)
EndIf
EndProcedure
; ------------------------------------------------------------------------------
;- Macros
Macro MsgPackWriteFixStr(MsgPackData, String, Encoding = #PB_UTF8)
_MsgPackWriteStringAs(MsgPackData, #MsgPack_FormatCode_FixStr, 0, String, Encoding)
EndMacro
Macro MsgPackWriteStr8(MsgPackData, String, Encoding = #PB_UTF8)
_MsgPackWriteStringAs(MsgPackData, #MsgPack_FormatCode_Str8, 1, String, Encoding)
EndMacro
Macro MsgPackWriteStr16(MsgPackData, String, Encoding = #PB_UTF8)
_MsgPackWriteStringAs(MsgPackData, #MsgPack_FormatCode_Str16, 2, String, Encoding)
EndMacro
Macro MsgPackWriteStr32(MsgPackData, String, Encoding = #PB_UTF8)
_MsgPackWriteStringAs(MsgPackData, #MsgPack_FormatCode_Str32, 4, String, Encoding)
EndMacro
-25
View File
@@ -1,25 +0,0 @@
EnableExplicit
XIncludeFile "./Includes/MessagePack.pbi"
Define *Buffer = MsgPackCreateMsgBuffer(16)
MsgPackWriteI8(*Buffer, 42)
MsgPackWriteI8(*Buffer, 7)
MsgPackFreeMsgBuffer(*Buffer, #True)
; Wrapping an existing buffer for reading
Define *RawData = AllocateMemory(4)
PokeA(*RawData + 0, #MsgPack_FormatCode_UInt8)
PokeA(*RawData + 1, 42)
PokeA(*RawData + 2, #MsgPack_FormatCode_Int8)
PokeB(*RawData + 3, -3)
Define *ReadBuffer = MsgPackWrapMsgBuffer(*RawData, 4)
Debug MsgPackReadU8(*ReadBuffer)
Debug MsgPackReadI8(*ReadBuffer)
MsgPackFreeMsgBuffer(*ReadBuffer, #False) ; #False: *RawData is owned by us
FreeMemory(*RawData)
@@ -0,0 +1,34 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
; /!\ Every case here calls `DebuggerError()`, which halts the program under the debugger.
; Run it without the debugger and check the exit code.
; Null MsgPack pointers
AssertEquals(MsgPackWriteInteger(#Null, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteInteger is given #Null for a fixint")
AssertEquals(MsgPackWriteInteger(#Null, 100000), #MsgPack_Error_NullPointerGiven, "MsgPackWriteInteger is given #Null for a UInt32")
AssertEquals(MsgPackWriteInteger(#Null, -100), #MsgPack_Error_NullPointerGiven, "MsgPackWriteInteger is given #Null for an Int8")
AssertEquals(MsgPackWriteFixInt(#Null, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteFixInt is given #Null")
AssertEquals(MsgPackWriteFixInt(#Null, 1000), #MsgPack_Error_NullPointerGiven, "MsgPackWriteFixInt is given #Null before the range check")
AssertEquals(MsgPackWriteUInt8(#Null, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteUInt8 is given #Null")
AssertEquals(MsgPackWriteInt64(#Null, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteInt64 is given #Null")
AssertEqualsQ(MsgPackReadInteger(#Null), 0, "MsgPackReadInteger is given #Null")
AssertEqualsA(MsgPackReadUInt8(#Null), 0, "MsgPackReadUInt8 is given #Null")
AssertEqualsQ(MsgPackReadInt64(#Null), 0, "MsgPackReadInt64 is given #Null")
; The generic float reader shares the integer one's checks.
Assert(Bool(MsgPackReadFloat(#Null) = 0), "MsgPackReadFloat is given #Null")
; Ending
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
@@ -0,0 +1,194 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Writes `Value` with `MsgPackWriteInteger()` into a 1 byte buffer that has to grow,
; checks its first byte and size, and reads it back with `MsgPackReadInteger()`.
; For fixints, `ExpectedFirstByte` is the value itself.
Procedure CheckInteger(Value.q, ExpectedFirstByte.a, ExpectedSize.i)
Protected *MsgPack.MsgPackData = MsgPackCreate(1)
Protected Name.s = Str(Value)
AssertEquals(MsgPackWriteInteger(*MsgPack, Value), #MsgPack_Error_Success, "Wrote " + Name)
AssertEquals(*MsgPack\BufferOffset, ExpectedSize, Name + " takes " + Str(ExpectedSize) + " bytes")
AssertEquals(*MsgPack\BufferSize, ExpectedSize, Name + " grew the buffer exactly")
AssertEqualsA(PeekA(*MsgPack\Buffer), ExpectedFirstByte, Name + " first byte")
*MsgPack\BufferOffset = 0
AssertEqualsQ(MsgPackReadInteger(*MsgPack), Value, "Read back " + Name)
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Read back " + Name + " without errors")
AssertEquals(*MsgPack\BufferOffset, ExpectedSize, "Read back " + Name + " moved the offset")
MsgPackFree(*MsgPack, #True)
EndProcedure
; Setup
Define *MsgPack.MsgPackData
Define QuadMin.q = -9223372036854775807 - 1
; Automatic format code selection, on each boundary
CheckInteger(0, $00, 1)
CheckInteger(1, $01, 1)
CheckInteger(127, $7F, 1)
CheckInteger(128, #MsgPack_FormatCode_UInt8, 2)
CheckInteger(255, #MsgPack_FormatCode_UInt8, 2)
CheckInteger(256, #MsgPack_FormatCode_UInt16, 3)
CheckInteger(65535, #MsgPack_FormatCode_UInt16, 3)
CheckInteger(65536, #MsgPack_FormatCode_UInt32, 5)
CheckInteger(4294967295, #MsgPack_FormatCode_UInt32, 5)
CheckInteger(4294967296, #MsgPack_FormatCode_UInt64, 9)
CheckInteger(9223372036854775807, #MsgPack_FormatCode_UInt64, 9)
CheckInteger(-1, $FF, 1)
CheckInteger(-32, $E0, 1)
CheckInteger(-33, #MsgPack_FormatCode_Int8, 2)
CheckInteger(-128, #MsgPack_FormatCode_Int8, 2)
CheckInteger(-129, #MsgPack_FormatCode_Int16, 3)
CheckInteger(-32768, #MsgPack_FormatCode_Int16, 3)
CheckInteger(-32769, #MsgPack_FormatCode_Int32, 5)
CheckInteger(-2147483648, #MsgPack_FormatCode_Int32, 5)
CheckInteger(-2147483649, #MsgPack_FormatCode_Int64, 9)
CheckInteger(QuadMin, #MsgPack_FormatCode_Int64, 9)
; Big-endian layouts
*MsgPack = MsgPackCreate(16)
MsgPackWriteInteger(*MsgPack, 4294967295)
MsgPackWriteInteger(*MsgPack, -129)
AssertEqualsA(PeekA(*MsgPack\Buffer + 1), $FF, "UInt32 byte 1")
AssertEqualsA(PeekA(*MsgPack\Buffer + 4), $FF, "UInt32 byte 4")
AssertEqualsA(PeekA(*MsgPack\Buffer + 5), #MsgPack_FormatCode_Int16, "Int16 format code")
AssertEqualsA(PeekA(*MsgPack\Buffer + 6), $FF, "Int16 high byte first")
AssertEqualsA(PeekA(*MsgPack\Buffer + 7), $7F, "Int16 low byte last")
MsgPackFree(*MsgPack, #True)
; Reading values written with forced format codes
*MsgPack = MsgPackCreate(1)
AssertEquals(MsgPackWriteUInt32(*MsgPack, $89ABCDEF), #MsgPack_Error_Success, "Wrote UInt32 with its high bit set")
AssertEquals(MsgPackWriteInt8(*MsgPack, -1), #MsgPack_Error_Success, "Wrote Int8")
AssertEquals(MsgPackWriteUInt8(*MsgPack, 200), #MsgPack_Error_Success, "Wrote UInt8")
AssertEquals(MsgPackWriteInt16(*MsgPack, 5), #MsgPack_Error_Success, "Wrote small Int16")
AssertEquals(MsgPackWriteUInt16(*MsgPack, $ABCD), #MsgPack_Error_Success, "Wrote UInt16")
AssertEquals(MsgPackWriteInt32(*MsgPack, -123456), #MsgPack_Error_Success, "Wrote Int32")
AssertEquals(MsgPackWriteInt64(*MsgPack, -2), #MsgPack_Error_Success, "Wrote small Int64")
AssertEquals(MsgPackWriteFixInt(*MsgPack, -5), #MsgPack_Error_Success, "Wrote negative fixint")
AssertEquals(MsgPackWriteFixInt(*MsgPack, 100), #MsgPack_Error_Success, "Wrote positive fixint")
AssertEquals(MsgPackWriteUInt8(*MsgPack, 42), #MsgPack_Error_Success, "Wrote trailing UInt8")
AssertEqualsA(PeekA(*MsgPack\Buffer + 5 + 2 + 2 + 3 + 3 + 5 + 9), $FB, "Negative fixint byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + 5 + 2 + 2 + 3 + 3 + 5 + 9 + 1), $64, "Positive fixint byte")
*MsgPack\BufferOffset = 0
AssertEqualsQ(MsgPackReadInteger(*MsgPack), $89ABCDEF, "UInt32 is read as unsigned")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), -1, "Int8 is read as signed")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 200, "UInt8 is read as unsigned")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 5, "Small Int16 is read")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), $ABCD, "UInt16 is read as unsigned")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), -123456, "Int32 is read as signed")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), -2, "Small Int64 is read")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), -5, "Negative fixint is read")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 100, "Positive fixint is read")
AssertEqualsA(MsgPackReadUInt8(*MsgPack), 42, "Read trailing UInt8")
AssertEquals(*MsgPack\BufferOffset, *MsgPack\BufferSize, "Offset is at the end")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "No error after reading")
MsgPackFree(*MsgPack, #True)
; Known fixint vectors
*MsgPack = WrapHex("007F80E0FF")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 0, "0x00 is 0")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 127, "0x7F is 127")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 0, "0x80 is a fixmap, not an int")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Fixmap error is set")
AssertEquals(*MsgPack\BufferOffset, 2, "Offset is untouched by the fixmap")
*MsgPack\BufferOffset = 3
AssertEqualsQ(MsgPackReadInteger(*MsgPack), -32, "0xE0 is -32")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), -1, "0xFF is -1")
FreeWrappedHex(*MsgPack)
; Fixints out of range
*MsgPack = MsgPackCreate(8)
AssertEquals(MsgPackWriteFixInt(*MsgPack, 128), #MsgPack_Error_ValueOverflow, "128 isn't a fixint")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_ValueOverflow, "Fixint overflow error is set")
AssertEquals(MsgPackWriteFixInt(*MsgPack, -33), #MsgPack_Error_ValueOverflow, "-33 isn't a fixint")
AssertEquals(MsgPackWriteFixInt(*MsgPack, 256 + 5), #MsgPack_Error_ValueOverflow, "261 isn't truncated into a fixint")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by rejected writes")
AssertEquals(MsgPackWriteFixInt(*MsgPack, 0), #MsgPack_Error_Success, "0 is a fixint")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Successful write cleared the error")
MsgPackFree(*MsgPack, #True)
; Buffers that can't grow
*MsgPack = WrapHex("0000")
AssertEquals(MsgPackWriteInteger(*MsgPack, 256), #MsgPack_Error_CannotGrowBufferDueToConfig, "UInt16's 3 bytes don't fit in 2")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the failed write")
AssertEquals(MsgPackWriteInteger(*MsgPack, -100), #MsgPack_Error_Success, "Int8's 2 bytes fit exactly")
FreeWrappedHex(*MsgPack)
; Reading errors
*MsgPack = WrapHex("01")
*MsgPack\BufferOffset = 1
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 0, "Read at the end fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Read at the end error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("CA3F800000")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 0, "Reading a float as an int fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Float as int error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the wrong format code")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("A161")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 0, "Reading a str as an int fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Str as int error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("CD00")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 0, "UInt16 with half its value fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncated UInt16 error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the truncated value")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("D1")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 0, "Int16 without its value fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncated Int16 error is set")
FreeWrappedHex(*MsgPack)
; UInt64 values that don't fit in a signed quad
*MsgPack = WrapHex("CF7FFFFFFFFFFFFFFF")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 9223372036854775807, "UInt64 (2^63)-1 fits")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("CF8000000000000000")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 0, "UInt64 2^63 doesn't fit")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_ValueOverflow, "UInt64 overflow error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the overflow")
AssertEqualsQ(MsgPackReadUInt64(*MsgPack), QuadMin, "It can still be read as raw bits with MsgPackReadUInt64")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Raw read is a success")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("CF80")
AssertEqualsQ(MsgPackReadInteger(*MsgPack), 0, "Truncated UInt64 with its high bit set fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncation is reported over the overflow")
FreeWrappedHex(*MsgPack)
; Ending
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
+58
View File
@@ -0,0 +1,58 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Setup
Define *MsgPack.MsgPackData = MsgPackCreate(1)
; Reading both float sizes
AssertEquals(MsgPackWriteFloat32(*MsgPack, 1.5), #MsgPack_Error_Success, "Wrote Float32")
AssertEquals(MsgPackWriteFloat64(*MsgPack, -2.25), #MsgPack_Error_Success, "Wrote Float64")
AssertEquals(MsgPackWriteFloat32(*MsgPack, 0.1), #MsgPack_Error_Success, "Wrote inexact Float32")
AssertEquals(MsgPackWriteUInt8(*MsgPack, 42), #MsgPack_Error_Success, "Wrote trailing UInt8")
*MsgPack\BufferOffset = 0
Assert(Bool(MsgPackReadFloat(*MsgPack) = 1.5), "Float32 is read")
AssertEquals(*MsgPack\BufferOffset, 5, "Float32 moved the offset by 5")
Assert(Bool(MsgPackReadFloat(*MsgPack) = -2.25), "Float64 is read")
AssertEquals(*MsgPack\BufferOffset, 5 + 9, "Float64 moved the offset by 9")
; 0.1 isn't exact as a float32, so it must match the float32 value, not the double one.
Assert(Bool(MsgPackReadFloat(*MsgPack) = ValF("0.1")), "Inexact Float32 is widened as-is")
AssertEqualsA(MsgPackReadUInt8(*MsgPack), 42, "Read trailing UInt8")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "No error after reading")
MsgPackFree(*MsgPack, #True)
; Reading errors
*MsgPack = WrapHex("CA")
*MsgPack\BufferOffset = 1
Assert(Bool(MsgPackReadFloat(*MsgPack) = 0), "Read at the end fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Read at the end error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("01")
Assert(Bool(MsgPackReadFloat(*MsgPack) = 0), "Reading an int as a float fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Int as float error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the wrong format code")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("CB3FF0000000")
Assert(Bool(MsgPackReadFloat(*MsgPack) = 0), "Truncated Float64 fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Truncated Float64 error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the truncated value")
FreeWrappedHex(*MsgPack)
; Ending
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
@@ -39,6 +39,8 @@ PokeA(*MyBuffer + 1, $C1) ; Invalid
*MsgPack = MsgPackWrapMsgBuffer(*MyBuffer, MemorySize(*MyBuffer))
AssertEqualsA(*MsgPack\BufferOffset, 0, "[False] Offset is 0")
AssertIsTrue(Bool(_MsgPackHasFlag(*MsgPack, #MsgPack_DataFlags_MoveOffsetAfterRead)), "[False] Move after read flag is set by default")
*MsgPack\DataFlags = *MsgPack\DataFlags & (~#MsgPack_DataFlags_MoveOffsetAfterRead)
AssertEqualsA(MsgPackGetFormatCode(*MsgPack), #MsgPack_FormatCode_False, "[False] First format code is false")
AssertEqualsA(MsgPackReadBoolean(*MsgPack), #False, "[False] Boolean literal getter returns #False")
AssertEqualsA(*MsgPack\BufferOffset, 0, "[False] Offset is still 0, we'll change the flag")
@@ -63,6 +65,8 @@ PokeA(*MyBuffer + 1, $C1) ; Invalid
*MsgPack = MsgPackWrapMsgBuffer(*MyBuffer, MemorySize(*MyBuffer))
AssertEqualsA(*MsgPack\BufferOffset, 0, "[True] Offset is 0")
AssertIsTrue(Bool(_MsgPackHasFlag(*MsgPack, #MsgPack_DataFlags_MoveOffsetAfterRead)), "[True] Move after read flag is set by default")
*MsgPack\DataFlags = *MsgPack\DataFlags & (~#MsgPack_DataFlags_MoveOffsetAfterRead)
AssertEqualsA(MsgPackGetFormatCode(*MsgPack), #MsgPack_FormatCode_True, "[True] First format code is false")
AssertEqualsA(MsgPackReadBoolean(*MsgPack), #True, "[True] Boolean literal getter returns #True")
AssertEqualsA(*MsgPack\BufferOffset, 0, "[True] Offset is still 0, we'll change the flag")
+144
View File
@@ -0,0 +1,144 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Note: Only covers errors that don't call `DebuggerError()`.
; See "TestBinaryFatalErrors.pb" for the others.
; Setup
Define *MsgPack.MsgPackData
Define *Source = AllocatePattern(16)
Define *Output = AllocateMemory(16)
; Sizes past each format code's limit, checked before anything is allocated
*MsgPack = MsgPackCreate(8)
AssertEquals(MsgPackWriteBin8(*MsgPack, *Source, 256), #MsgPack_Error_BinaryDataSizeIsTooBig, "Bin8 rejects 256 bytes")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BinaryDataSizeIsTooBig, "Bin8 too big error is set")
AssertEquals(MsgPackWriteBin16(*MsgPack, *Source, 65536), #MsgPack_Error_BinaryDataSizeIsTooBig, "Bin16 rejects 65536 bytes")
AssertEquals(MsgPackWriteBin32(*MsgPack, *Source, 4294967296), #MsgPack_Error_BinaryDataSizeIsTooBig, "Bin32 rejects 2^32 bytes")
AssertEquals(MsgPackWriteBinary(*MsgPack, *Source, 4294967296), #MsgPack_Error_BinaryDataSizeIsTooBig, "Binary rejects 2^32 bytes")
AssertEquals(MsgPackWriteBin8(*MsgPack, *Source, -1), #MsgPack_Error_BinaryDataSizeIsNegative, "Bin8 rejects negative sizes")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BinaryDataSizeIsNegative, "Negative size error is set")
AssertEquals(MsgPackWriteBin16(*MsgPack, *Source, -1), #MsgPack_Error_BinaryDataSizeIsNegative, "Bin16 rejects negative sizes")
AssertEquals(MsgPackWriteBin32(*MsgPack, *Source, -1), #MsgPack_Error_BinaryDataSizeIsNegative, "Bin32 rejects negative sizes")
AssertEquals(MsgPackWriteBinary(*MsgPack, *Source, -1), #MsgPack_Error_BinaryDataSizeIsNegative, "Binary rejects negative sizes")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by rejected writes")
AssertEquals(*MsgPack\BufferSize, 8, "Buffer wasn't grown by rejected writes")
AssertEquals(MsgPackWriteBin8(*MsgPack, *Source, 4), #MsgPack_Error_Success, "Wrote bin8 after rejected writes")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Successful write cleared the error")
MsgPackFree(*MsgPack, #True)
; Buffers that can't grow
*MsgPack = WrapHex("0000000000")
AssertEquals(MsgPackWriteBin8(*MsgPack, *Source, 4), #MsgPack_Error_CannotGrowBufferDueToConfig, "6 bytes don't fit in 5")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_CannotGrowBufferDueToConfig, "Growth error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the failed write")
AssertEqualsA(PeekA(*MsgPack\Buffer), $00, "Nothing was written by the failed write")
AssertEquals(MsgPackWriteBin8(*MsgPack, *Source, 3), #MsgPack_Error_Success, "5 bytes fit exactly")
AssertEquals(*MsgPack\BufferOffset, 5, "Offset is at the end")
FreeWrappedHex(*MsgPack)
; Reading at the end
*MsgPack = WrapHex("C40100")
*MsgPack\BufferOffset = 3
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Read at the end fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Read at the end error is set")
AssertEquals(MsgPackCopyBinary(*MsgPack), #Null, "Copy at the end fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Copy at the end error is set")
FreeWrappedHex(*MsgPack)
; Wrong format codes
*MsgPack = WrapHex("A3616263")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Reading a str as a bin fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Str as bin read error is set")
AssertEquals(MsgPackCopyBinary(*MsgPack), #Null, "Copying a str as a bin fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Str as bin copy error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the wrong format code")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("C1")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Reading the 0xC1 never-used code fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "0xC1 error is set")
FreeWrappedHex(*MsgPack)
; Truncated size fields
*MsgPack = WrapHex("C4")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Bin8 without its size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Bin8 without its size error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("C500")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Bin16 with half its size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Bin16 with half its size error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("C6000000")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Bin32 with 3/4 of its size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Bin32 with 3/4 of its size error is set")
FreeWrappedHex(*MsgPack)
; Truncated data
*MsgPack = WrapHex("C4056162")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Bin8 missing data fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Bin8 missing data error is set")
AssertEquals(MsgPackCopyBinary(*MsgPack), #Null, "Copying a bin8 missing data fails")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the truncated data")
FreeWrappedHex(*MsgPack)
; The bin32 size is unsigned, `0xFFFFFFFF` must not be read as `-1`.
*MsgPack = WrapHex("C6FFFFFFFF00")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Bin32 with a 0xFFFFFFFF size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Bin32 size is treated as unsigned")
FreeWrappedHex(*MsgPack)
; Output buffer too small, then retried
*MsgPack = WrapHex("C403010203")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 2), #MsgPack_InvalidSize, "3 bytes don't fit in 2")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_OutputBufferTooSmall, "Output too small error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched so the read can be retried")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 3), 3, "Retried with an exactly sized buffer")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Retry cleared the error")
AssertEqualsA(PeekA(*Output), $01, "Retried data byte 1")
AssertEqualsA(PeekA(*Output + 1), $02, "Retried data byte 2")
AssertEqualsA(PeekA(*Output + 2), $03, "Retried data byte 3")
AssertEquals(*MsgPack\BufferOffset, 5, "Offset is at the end")
FreeWrappedHex(*MsgPack)
; Ending
FreeMemory(*Source)
FreeMemory(*Output)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
@@ -0,0 +1,47 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; /!\ Every case here calls `DebuggerError()`, which halts the program under the debugger.
; Run it without the debugger and check the exit code.
; Setup
Define *MsgPack.MsgPackData
Define *Source = AllocatePattern(16)
Define *Output = AllocateMemory(16)
; Null MsgPack pointers
AssertEquals(MsgPackWriteBin8(#Null, *Source, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteBin8 is given #Null")
AssertEquals(MsgPackWriteBin16(#Null, *Source, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteBin16 is given #Null")
AssertEquals(MsgPackWriteBin32(#Null, *Source, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteBin32 is given #Null")
AssertEquals(MsgPackWriteBinary(#Null, *Source, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteBinary is given #Null")
AssertEqualsQ(MsgPackReadBinary(#Null, *Output, 16), #MsgPack_InvalidSize, "MsgPackReadBinary is given #Null")
AssertEquals(MsgPackCopyBinary(#Null), #Null, "MsgPackCopyBinary is given #Null")
; Null output buffer with data to copy
*MsgPack = WrapHex("C4020102")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, #Null, 2), #MsgPack_InvalidSize, "Reading into a #Null buffer fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_NullPointerGiven, "Null output buffer error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the failed read")
FreeWrappedHex(*MsgPack)
; Ending
FreeMemory(*Source)
FreeMemory(*Output)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
+149
View File
@@ -0,0 +1,149 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Setup
; Starting with 1 byte so every write has to grow the buffer.
Define *MsgPack.MsgPackData = MsgPackCreate(1)
Define *Source = AllocatePattern(65536)
Define *Output = AllocateMemory(65536)
Define *Copy
Define OffsetEmpty.i, Offset255.i, Offset256.i, Offset65535.i, Offset65536.i
Define OffsetBin16Empty.i, OffsetBin32Small.i, OffsetBin16Small.i
Define OffsetEnd.i
; Automatic format code selection, on each boundary
OffsetEmpty = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteBinary(*MsgPack, #Null, 0), #MsgPack_Error_Success, "Wrote 0 bytes from a #Null buffer")
Offset255 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteBinary(*MsgPack, *Source, 255), #MsgPack_Error_Success, "Wrote 255 bytes")
Offset256 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteBinary(*MsgPack, *Source, 256), #MsgPack_Error_Success, "Wrote 256 bytes")
Offset65535 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteBinary(*MsgPack, *Source, 65535), #MsgPack_Error_Success, "Wrote 65535 bytes")
Offset65536 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteBinary(*MsgPack, *Source, 65536), #MsgPack_Error_Success, "Wrote 65536 bytes")
; Forced format codes, even when a smaller one fits
OffsetBin16Empty = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteBin16(*MsgPack, #Null, 0), #MsgPack_Error_Success, "Wrote 0 bytes as bin16")
OffsetBin32Small = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteBin32(*MsgPack, *Source, 5), #MsgPack_Error_Success, "Wrote 5 bytes as bin32")
OffsetBin16Small = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteBin16(*MsgPack, *Source, 1), #MsgPack_Error_Success, "Wrote 1 byte as bin16")
; Something right after, to catch offset drifts
AssertEquals(MsgPackWriteUInt8(*MsgPack, 42), #MsgPack_Error_Success, "Wrote trailing UInt8")
OffsetEnd = *MsgPack\BufferOffset
; Offsets
AssertEquals(Offset255, OffsetEmpty + 2, "Empty bin8 takes 2 bytes")
AssertEquals(Offset256, Offset255 + 2 + 255, "255 bytes bin8 takes 257 bytes")
AssertEquals(Offset65535, Offset256 + 3 + 256, "256 bytes bin16 takes 259 bytes")
AssertEquals(Offset65536, Offset65535 + 3 + 65535, "65535 bytes bin16 takes 65538 bytes")
AssertEquals(OffsetBin16Empty, Offset65536 + 5 + 65536, "65536 bytes bin32 takes 65541 bytes")
AssertEquals(OffsetBin32Small, OffsetBin16Empty + 3, "Empty bin16 takes 3 bytes")
AssertEquals(OffsetBin16Small, OffsetBin32Small + 5 + 5, "5 bytes bin32 takes 10 bytes")
AssertEquals(OffsetEnd, OffsetBin16Small + 3 + 1 + 2, "1 byte bin16 and UInt8 take 6 bytes")
AssertEquals(*MsgPack\BufferSize, OffsetEnd, "Buffer grew to exactly what was written")
; Big-endian layouts
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetEmpty), #MsgPack_FormatCode_Bin8, "Empty uses bin8")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetEmpty + 1), $00, "Empty bin8 size")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset255), #MsgPack_FormatCode_Bin8, "255 bytes uses bin8")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset255 + 1), $FF, "255 bytes bin8 size")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset256), #MsgPack_FormatCode_Bin16, "256 bytes uses bin16")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset256 + 1), $01, "256 bytes bin16 size high byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset256 + 2), $00, "256 bytes bin16 size low byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65535), #MsgPack_FormatCode_Bin16, "65535 bytes uses bin16")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65535 + 1), $FF, "65535 bytes bin16 size high byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65535 + 2), $FF, "65535 bytes bin16 size low byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536), #MsgPack_FormatCode_Bin32, "65536 bytes uses bin32")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 1), $00, "65536 bytes bin32 size byte 1")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 2), $01, "65536 bytes bin32 size byte 2")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 3), $00, "65536 bytes bin32 size byte 3")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 4), $00, "65536 bytes bin32 size byte 4")
Assert(CompareMemory(*MsgPack\Buffer + Offset65536 + 5, *Source, 65536), "65536 bytes bin32 data")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetBin16Empty), #MsgPack_FormatCode_Bin16, "Forced empty bin16")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetBin16Empty + 1), $00, "Forced empty bin16 size high byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetBin16Empty + 2), $00, "Forced empty bin16 size low byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetBin32Small), #MsgPack_FormatCode_Bin32, "Forced 5 bytes bin32")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetBin32Small + 1), $00, "Forced 5 bytes bin32 size byte 1")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetBin32Small + 2), $00, "Forced 5 bytes bin32 size byte 2")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetBin32Small + 3), $00, "Forced 5 bytes bin32 size byte 3")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetBin32Small + 4), $05, "Forced 5 bytes bin32 size byte 4")
Assert(CompareMemory(*MsgPack\Buffer + OffsetBin32Small + 5, *Source, 5), "Forced 5 bytes bin32 data")
; Reading them back
*MsgPack\BufferOffset = 0
AssertEqualsQ(MsgPackReadBinary(*MsgPack, #Null, 0), 0, "Read empty bin8 into a #Null buffer")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Empty read is a success")
FillMemory(*Output, 65536, $AA)
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 255), 255, "Read 255 bytes into an exactly sized buffer")
Assert(CompareMemory(*Output, *Source, 255), "255 bytes data matches")
AssertEqualsA(PeekA(*Output + 255), $AA, "Nothing written past the data")
*Copy = MsgPackCopyBinary(*MsgPack)
Assert(Bool(*Copy <> #Null), "Copied 256 bytes")
If *Copy
AssertEquals(MemorySize(*Copy), 256, "Copy is exactly 256 bytes")
Assert(CompareMemory(*Copy, *Source, 256), "256 bytes data matches")
FreeMemory(*Copy)
EndIf
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 65536), 65535, "Read 65535 bytes into a bigger buffer")
Assert(CompareMemory(*Output, *Source, 65535), "65535 bytes data matches")
*Copy = MsgPackCopyBinary(*MsgPack)
Assert(Bool(*Copy <> #Null), "Copied 65536 bytes")
If *Copy
AssertEquals(MemorySize(*Copy), 65536, "Copy is exactly 65536 bytes")
Assert(CompareMemory(*Copy, *Source, 65536), "65536 bytes data matches")
FreeMemory(*Copy)
EndIf
AssertEquals(MsgPackCopyBinary(*MsgPack), #Null, "Copying an empty bin16 returns #Null")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Empty copy is a success")
AssertEquals(*MsgPack\BufferOffset, OffsetBin32Small, "Empty copy still moved the offset")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 65536), 5, "Read forced 5 bytes bin32")
Assert(CompareMemory(*Output, *Source, 5), "Forced 5 bytes bin32 data matches")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 65536), 1, "Read forced 1 byte bin16")
AssertEqualsA(PeekA(*Output), PeekA(*Source), "Forced 1 byte bin16 data matches")
AssertEqualsA(MsgPackReadUInt8(*MsgPack), 42, "Read trailing UInt8")
AssertEquals(*MsgPack\BufferOffset, OffsetEnd, "Offset is at the end")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "No error after reading")
; Ending
MsgPackFree(*MsgPack, #True)
FreeMemory(*Source)
FreeMemory(*Output)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
+129
View File
@@ -0,0 +1,129 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Note: Only covers errors that don't call `DebuggerError()`.
; See "TestStringsFatalErrors.pb" for the others.
; Setup
Define *MsgPack.MsgPackData
; Strings too big for a forced format code
*MsgPack = MsgPackCreate(8)
AssertEquals(MsgPackWriteFixStr(*MsgPack, LSet("", 32, "a")), #MsgPack_Error_BinaryDataSizeIsTooBig, "Fixstr rejects 32 bytes")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BinaryDataSizeIsTooBig, "Fixstr too big error is set")
AssertEquals(MsgPackWriteFixStr(*MsgPack, LSet("", 16, Chr($E9))), #MsgPack_Error_BinaryDataSizeIsTooBig, "Fixstr rejects 16 accents (32 bytes)")
AssertEquals(MsgPackWriteFixStr(*MsgPack, LSet("", 16, "a"), #PB_Unicode), #MsgPack_Error_BinaryDataSizeIsTooBig, "Fixstr rejects 16 UTF-16 characters (32 bytes)")
AssertEquals(MsgPackWriteStr8(*MsgPack, LSet("", 256, "a")), #MsgPack_Error_BinaryDataSizeIsTooBig, "Str8 rejects 256 bytes")
AssertEquals(MsgPackWriteStr16(*MsgPack, LSet("", 65536, "a")), #MsgPack_Error_BinaryDataSizeIsTooBig, "Str16 rejects 65536 bytes")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by rejected writes")
AssertEquals(*MsgPack\BufferSize, 8, "Buffer wasn't grown by rejected writes")
AssertEquals(MsgPackWriteFixStr(*MsgPack, LSet("", 31, "a")), #MsgPack_Error_Success, "Fixstr accepts 31 bytes")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Successful write cleared the error")
MsgPackFree(*MsgPack, #True)
; Buffers that can't grow
*MsgPack = WrapHex("0000")
AssertEquals(MsgPackWriteString(*MsgPack, "ab"), #MsgPack_Error_CannotGrowBufferDueToConfig, "3 bytes don't fit in 2")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_CannotGrowBufferDueToConfig, "Growth error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the failed write")
AssertEqualsA(PeekA(*MsgPack\Buffer), $00, "Nothing was written by the failed write")
AssertEquals(MsgPackWriteString(*MsgPack, "a"), #MsgPack_Error_Success, "2 bytes fit exactly")
AssertEquals(*MsgPack\BufferOffset, 2, "Offset is at the end")
FreeWrappedHex(*MsgPack)
; Reading at the end
*MsgPack = WrapHex("A161")
*MsgPack\BufferOffset = 2
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Read at the end returns an empty string")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Read at the end error is set")
FreeWrappedHex(*MsgPack)
; Wrong format codes
*MsgPack = WrapHex("C40161")
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Reading a bin as a str fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Bin as str error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the wrong format code")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("C1")
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Reading the 0xC1 never-used code fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "0xC1 error is set")
FreeWrappedHex(*MsgPack)
; Truncated size fields
*MsgPack = WrapHex("D9")
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Str8 without its size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Str8 without its size error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("DA00")
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Str16 with half its size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Str16 with half its size error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("DB000000")
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Str32 with 3/4 of its size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Str32 with 3/4 of its size error is set")
FreeWrappedHex(*MsgPack)
; Truncated data
*MsgPack = WrapHex("A36162")
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Fixstr missing data fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Fixstr missing data error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the truncated data")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("D90561")
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Str8 missing data fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Str8 missing data error is set")
FreeWrappedHex(*MsgPack)
; The str32 size is unsigned, `0xFFFFFFFF` must not be read as `-1`.
*MsgPack = WrapHex("DBFFFFFFFF61")
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Str32 with a 0xFFFFFFFF size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Str32 size is treated as unsigned")
FreeWrappedHex(*MsgPack)
; UTF-16 with an odd byte count, then read as UTF-8 instead
*MsgPack = WrapHex("A3414243")
Assert(Bool(MsgPackReadString(*MsgPack, #PB_Unicode) = ""), "Odd sized UTF-16 fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidStringSize, "Odd sized UTF-16 error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched so the read can be retried")
Assert(Bool(MsgPackReadString(*MsgPack) = "ABC"), "Retried as UTF-8")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Retry cleared the error")
AssertEquals(*MsgPack\BufferOffset, 4, "Offset is at the end")
FreeWrappedHex(*MsgPack)
; Ending
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
@@ -0,0 +1,61 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; /!\ Every case here calls `DebuggerError()`, which halts the program under the debugger.
; Run it without the debugger and check the exit code.
; Setup
#UnsupportedEncoding = 999
Define *MsgPack.MsgPackData
; Null MsgPack pointers
AssertEquals(MsgPackWriteString(#Null, "a"), #MsgPack_Error_NullPointerGiven, "MsgPackWriteString is given #Null")
AssertEquals(MsgPackWriteFixStr(#Null, "a"), #MsgPack_Error_NullPointerGiven, "MsgPackWriteFixStr is given #Null")
AssertEquals(MsgPackWriteStr8(#Null, "a"), #MsgPack_Error_NullPointerGiven, "MsgPackWriteStr8 is given #Null")
AssertEquals(MsgPackWriteStr16(#Null, "a"), #MsgPack_Error_NullPointerGiven, "MsgPackWriteStr16 is given #Null")
AssertEquals(MsgPackWriteStr32(#Null, "a"), #MsgPack_Error_NullPointerGiven, "MsgPackWriteStr32 is given #Null")
Assert(Bool(MsgPackReadString(#Null) = ""), "MsgPackReadString is given #Null")
; Unsupported encodings when writing
*MsgPack = MsgPackCreate(8)
AssertEquals(MsgPackWriteString(*MsgPack, "a", #UnsupportedEncoding), #MsgPack_Error_UnsupportedStringEncoding, "MsgPackWriteString rejects the encoding")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_UnsupportedStringEncoding, "Unsupported encoding error is set")
AssertEquals(MsgPackWriteStr8(*MsgPack, "a", #UnsupportedEncoding), #MsgPack_Error_UnsupportedStringEncoding, "MsgPackWriteStr8 rejects the encoding")
AssertEquals(MsgPackWriteString(*MsgPack, "", #UnsupportedEncoding), #MsgPack_Error_UnsupportedStringEncoding, "Empty strings are rejected too")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by rejected writes")
MsgPackFree(*MsgPack, #True)
; Unsupported encodings when reading
*MsgPack = WrapHex("A161")
Assert(Bool(MsgPackReadString(*MsgPack, #UnsupportedEncoding) = ""), "MsgPackReadString rejects the encoding")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_UnsupportedStringEncoding, "Unsupported encoding error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the rejected read")
FreeWrappedHex(*MsgPack)
; Nothing to decode, but the encoding is still checked.
*MsgPack = WrapHex("A0")
Assert(Bool(MsgPackReadString(*MsgPack, #UnsupportedEncoding) = ""), "Empty string read rejects the encoding")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_UnsupportedStringEncoding, "Empty string encoding error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the rejected empty read")
FreeWrappedHex(*MsgPack)
; Ending
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
@@ -0,0 +1,211 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Note: Non-ASCII characters are built with `Chr()` to not depend on the source file's encoding.
; Setup
; Starting with 1 byte so every write has to grow the buffer.
Define *MsgPack.MsgPackData = MsgPackCreate(1)
Define String31.s = LSet("", 31, "a")
Define String32.s = LSet("", 32, "b")
Define String255.s = LSet("", 255, "c")
Define String256.s = LSet("", 256, "d")
Define String65535.s = LSet("", 65535, "e")
Define String65536.s = LSet("", 65536, "f")
; U+00E9 is 2 bytes in UTF-8, so the format code depends on bytes, not characters.
Define Accents15.s = LSet("", 15, Chr($E9)) ; 30 bytes
Define Accents16.s = LSet("", 16, Chr($E9)) ; 32 bytes
; U+20AC is 3 bytes in UTF-8.
Define Euro.s = "Price: 5" + Chr($20AC) ; 11 bytes
Define OffsetEmpty.i, Offset31.i, Offset32.i, Offset255.i, Offset256.i, Offset65535.i, Offset65536.i
Define OffsetAccents15.i, OffsetAccents16.i, OffsetEuro.i
Define OffsetStr32.i, OffsetStr8Empty.i, OffsetFixStr.i, OffsetStr16.i
Define OffsetAscii.i, OffsetUnicode.i, OffsetEnd.i
; Automatic format code selection, on each boundary
OffsetEmpty = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, ""), #MsgPack_Error_Success, "Wrote empty string")
Offset31 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, String31), #MsgPack_Error_Success, "Wrote 31 bytes")
Offset32 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, String32), #MsgPack_Error_Success, "Wrote 32 bytes")
Offset255 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, String255), #MsgPack_Error_Success, "Wrote 255 bytes")
Offset256 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, String256), #MsgPack_Error_Success, "Wrote 256 bytes")
Offset65535 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, String65535), #MsgPack_Error_Success, "Wrote 65535 bytes")
Offset65536 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, String65536), #MsgPack_Error_Success, "Wrote 65536 bytes")
; Multi-byte UTF-8 characters
OffsetAccents15 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, Accents15), #MsgPack_Error_Success, "Wrote 15 accents")
OffsetAccents16 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, Accents16), #MsgPack_Error_Success, "Wrote 16 accents")
OffsetEuro = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, Euro), #MsgPack_Error_Success, "Wrote euro string")
; Forced format codes, even when a smaller one fits
OffsetStr32 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteStr32(*MsgPack, "abc"), #MsgPack_Error_Success, "Wrote 3 bytes as str32")
OffsetStr8Empty = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteStr8(*MsgPack, ""), #MsgPack_Error_Success, "Wrote empty string as str8")
OffsetFixStr = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteFixStr(*MsgPack, "xyz"), #MsgPack_Error_Success, "Wrote 3 bytes as fixstr")
OffsetStr16 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteStr16(*MsgPack, "q"), #MsgPack_Error_Success, "Wrote 1 byte as str16")
; Other encodings
OffsetAscii = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, "Hello", #PB_Ascii), #MsgPack_Error_Success, "Wrote ASCII string")
OffsetUnicode = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteString(*MsgPack, "Hi" + Chr($20AC), #PB_Unicode), #MsgPack_Error_Success, "Wrote UTF-16 string")
; Something right after, to catch offset drifts
AssertEquals(MsgPackWriteUInt8(*MsgPack, 42), #MsgPack_Error_Success, "Wrote trailing UInt8")
OffsetEnd = *MsgPack\BufferOffset
; Offsets
AssertEquals(Offset31, OffsetEmpty + 1, "Empty fixstr takes 1 byte")
AssertEquals(Offset32, Offset31 + 1 + 31, "31 bytes fixstr takes 32 bytes")
AssertEquals(Offset255, Offset32 + 2 + 32, "32 bytes str8 takes 34 bytes")
AssertEquals(Offset256, Offset255 + 2 + 255, "255 bytes str8 takes 257 bytes")
AssertEquals(Offset65535, Offset256 + 3 + 256, "256 bytes str16 takes 259 bytes")
AssertEquals(Offset65536, Offset65535 + 3 + 65535, "65535 bytes str16 takes 65538 bytes")
AssertEquals(OffsetAccents15, Offset65536 + 5 + 65536, "65536 bytes str32 takes 65541 bytes")
AssertEquals(OffsetAccents16, OffsetAccents15 + 1 + 30, "15 accents fixstr takes 31 bytes")
AssertEquals(OffsetEuro, OffsetAccents16 + 2 + 32, "16 accents str8 takes 34 bytes")
AssertEquals(OffsetStr32, OffsetEuro + 1 + 11, "Euro fixstr takes 12 bytes")
AssertEquals(OffsetStr8Empty, OffsetStr32 + 5 + 3, "3 bytes str32 takes 8 bytes")
AssertEquals(OffsetFixStr, OffsetStr8Empty + 2, "Empty str8 takes 2 bytes")
AssertEquals(OffsetStr16, OffsetFixStr + 1 + 3, "3 bytes fixstr takes 4 bytes")
AssertEquals(OffsetAscii, OffsetStr16 + 3 + 1, "1 byte str16 takes 4 bytes")
AssertEquals(OffsetUnicode, OffsetAscii + 1 + 5, "5 bytes ASCII fixstr takes 6 bytes")
AssertEquals(OffsetEnd, OffsetUnicode + 1 + 6 + 2, "6 bytes UTF-16 fixstr and UInt8 take 9 bytes")
AssertEquals(*MsgPack\BufferSize, OffsetEnd, "Buffer grew to exactly what was written")
; Layouts
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetEmpty), $A0, "Empty uses fixstr")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset31), $BF, "31 bytes uses fixstr")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset32), #MsgPack_FormatCode_Str8, "32 bytes uses str8")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset32 + 1), 32, "32 bytes str8 size")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset255), #MsgPack_FormatCode_Str8, "255 bytes uses str8")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset255 + 1), $FF, "255 bytes str8 size")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset256), #MsgPack_FormatCode_Str16, "256 bytes uses str16")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset256 + 1), $01, "256 bytes str16 size high byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset256 + 2), $00, "256 bytes str16 size low byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65535), #MsgPack_FormatCode_Str16, "65535 bytes uses str16")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65535 + 1), $FF, "65535 bytes str16 size high byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65535 + 2), $FF, "65535 bytes str16 size low byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536), #MsgPack_FormatCode_Str32, "65536 bytes uses str32")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 1), $00, "65536 bytes str32 size byte 1")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 2), $01, "65536 bytes str32 size byte 2")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 3), $00, "65536 bytes str32 size byte 3")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 4), $00, "65536 bytes str32 size byte 4")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetAccents15), $BE, "15 accents use a 30 bytes fixstr")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetAccents15 + 1), $C3, "Accent UTF-8 byte 1")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetAccents15 + 2), $A9, "Accent UTF-8 byte 2")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetAccents16), #MsgPack_FormatCode_Str8, "16 accents use str8, not fixstr")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetAccents16 + 1), 32, "16 accents str8 size is in bytes")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetEuro), $AB, "Euro uses a 11 bytes fixstr")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetEuro + 9), $E2, "Euro UTF-8 byte 1")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetEuro + 10), $82, "Euro UTF-8 byte 2")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetEuro + 11), $AC, "Euro UTF-8 byte 3")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetStr32), #MsgPack_FormatCode_Str32, "Forced str32")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetStr32 + 4), $03, "Forced str32 size low byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetStr32 + 5), 'a', "Forced str32 data")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetStr8Empty), #MsgPack_FormatCode_Str8, "Forced empty str8")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetStr8Empty + 1), $00, "Forced empty str8 size")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFixStr), $A3, "Forced fixstr")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetStr16), #MsgPack_FormatCode_Str16, "Forced str16")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetAscii), $A5, "ASCII uses a 5 bytes fixstr")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetAscii + 1), 'H', "ASCII data")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetUnicode), $A6, "UTF-16 uses a 6 bytes fixstr")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetUnicode + 1), 'H', "UTF-16 is little-endian (1)")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetUnicode + 2), $00, "UTF-16 is little-endian (2)")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetUnicode + 5), $AC, "UTF-16 euro low byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetUnicode + 6), $20, "UTF-16 euro high byte")
; Reading them back
*MsgPack\BufferOffset = 0
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Read empty string")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Empty read is a success")
Assert(Bool(MsgPackReadString(*MsgPack) = String31), "Read 31 bytes")
Assert(Bool(MsgPackReadString(*MsgPack) = String32), "Read 32 bytes")
Assert(Bool(MsgPackReadString(*MsgPack) = String255), "Read 255 bytes")
Assert(Bool(MsgPackReadString(*MsgPack) = String256), "Read 256 bytes")
Assert(Bool(MsgPackReadString(*MsgPack) = String65535), "Read 65535 bytes")
Assert(Bool(MsgPackReadString(*MsgPack) = String65536), "Read 65536 bytes")
Assert(Bool(MsgPackReadString(*MsgPack) = Accents15), "Read 15 accents")
Assert(Bool(MsgPackReadString(*MsgPack) = Accents16), "Read 16 accents")
Assert(Bool(MsgPackReadString(*MsgPack) = Euro), "Read euro string")
Assert(Bool(MsgPackReadString(*MsgPack) = "abc"), "Read forced str32")
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Read forced empty str8")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Forced empty str8 read is a success")
Assert(Bool(MsgPackReadString(*MsgPack) = "xyz"), "Read forced fixstr")
Assert(Bool(MsgPackReadString(*MsgPack) = "q"), "Read forced str16")
Assert(Bool(MsgPackReadString(*MsgPack, #PB_Ascii) = "Hello"), "Read ASCII string")
Assert(Bool(MsgPackReadString(*MsgPack, #PB_Unicode) = "Hi" + Chr($20AC)), "Read UTF-16 string")
AssertEqualsA(MsgPackReadUInt8(*MsgPack), 42, "Read trailing UInt8")
AssertEquals(*MsgPack\BufferOffset, OffsetEnd, "Offset is at the end")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "No error after reading")
MsgPackFree(*MsgPack, #True)
; Raw strings from other writers
; A null character cuts the string, but the whole field is still skipped.
*MsgPack = WrapHex("A46100626301")
Assert(Bool(MsgPackReadString(*MsgPack) = "a"), "Null character cuts the string")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Null character isn't an error")
AssertEquals(*MsgPack\BufferOffset, 5, "Whole field was skipped")
FreeWrappedHex(*MsgPack)
; A string ending exactly at the end of the buffer.
*MsgPack = WrapHex("D903616263")
Assert(Bool(MsgPackReadString(*MsgPack) = "abc"), "Read a str8 ending with the buffer")
AssertEquals(*MsgPack\BufferOffset, 5, "Offset is at the end")
FreeWrappedHex(*MsgPack)
; Ending
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
@@ -0,0 +1,188 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Note: Only covers errors that don't call `DebuggerError()`.
; See "TestExtensionsFatalErrors.pb" for the others.
; Setup
Define *MsgPack.MsgPackData
Define *Source = AllocatePattern(16)
Define *Output = AllocateMemory(16)
; Sizes past each format code's limit, checked before anything is allocated
*MsgPack = MsgPackCreate(8)
AssertEquals(MsgPackWriteExt8(*MsgPack, 1, *Source, 256), #MsgPack_Error_BinaryDataSizeIsTooBig, "Ext8 rejects 256 bytes")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BinaryDataSizeIsTooBig, "Ext8 too big error is set")
AssertEquals(MsgPackWriteExt16(*MsgPack, 1, *Source, 65536), #MsgPack_Error_BinaryDataSizeIsTooBig, "Ext16 rejects 65536 bytes")
AssertEquals(MsgPackWriteExt32(*MsgPack, 1, *Source, 4294967296), #MsgPack_Error_BinaryDataSizeIsTooBig, "Ext32 rejects 2^32 bytes")
AssertEquals(MsgPackWriteExtension(*MsgPack, 1, *Source, 4294967296), #MsgPack_Error_BinaryDataSizeIsTooBig, "Extension rejects 2^32 bytes")
AssertEquals(MsgPackWriteExt8(*MsgPack, 1, *Source, -1), #MsgPack_Error_BinaryDataSizeIsNegative, "Ext8 rejects negative sizes")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BinaryDataSizeIsNegative, "Negative size error is set")
AssertEquals(MsgPackWriteExt16(*MsgPack, 1, *Source, -1), #MsgPack_Error_BinaryDataSizeIsNegative, "Ext16 rejects negative sizes")
AssertEquals(MsgPackWriteExt32(*MsgPack, 1, *Source, -1), #MsgPack_Error_BinaryDataSizeIsNegative, "Ext32 rejects negative sizes")
AssertEquals(MsgPackWriteExtension(*MsgPack, 1, *Source, -1), #MsgPack_Error_BinaryDataSizeIsNegative, "Extension rejects negative sizes")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by rejected writes")
AssertEquals(*MsgPack\BufferSize, 8, "Buffer wasn't grown by rejected writes")
AssertEquals(MsgPackWriteExt8(*MsgPack, 1, *Source, 3), #MsgPack_Error_Success, "Wrote ext8 after rejected writes")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Successful write cleared the error")
MsgPackFree(*MsgPack, #True)
; Buffers that can't grow
*MsgPack = WrapHex("0000000000")
AssertEquals(MsgPackWriteFixExt4(*MsgPack, 1, *Source), #MsgPack_Error_CannotGrowBufferDueToConfig, "fixext4's 6 bytes don't fit in 5")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_CannotGrowBufferDueToConfig, "Growth error is set")
AssertEquals(MsgPackWriteExt8(*MsgPack, 1, *Source, 3), #MsgPack_Error_CannotGrowBufferDueToConfig, "ext8's 6 bytes don't fit in 5")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the failed writes")
AssertEqualsA(PeekA(*MsgPack\Buffer), $00, "Nothing was written by the failed writes")
AssertEquals(MsgPackWriteExt8(*MsgPack, 1, *Source, 2), #MsgPack_Error_Success, "ext8's 5 bytes fit exactly")
AssertEquals(*MsgPack\BufferOffset, 5, "Offset is at the end")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("00000000")
AssertEquals(MsgPackWriteFixExt2(*MsgPack, 1, *Source), #MsgPack_Error_Success, "fixext2's 4 bytes fit exactly")
AssertEquals(*MsgPack\BufferOffset, 4, "Offset is at the end")
FreeWrappedHex(*MsgPack)
; Reading at the end
*MsgPack = WrapHex("D40100")
*MsgPack\BufferOffset = 3
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Read at the end fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Read at the end error is set")
AssertEquals(MsgPackCopyExtension(*MsgPack), #Null, "Copy at the end fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Copy at the end error is set")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 0, "Type at the end fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_AtOrPastTheEndOfBuffer, "Type at the end error is set")
FreeWrappedHex(*MsgPack)
; Wrong format codes, in both directions
*MsgPack = WrapHex("C4020102")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Reading a bin as an ext fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Bin as ext read error is set")
AssertEquals(MsgPackCopyExtension(*MsgPack), #Null, "Copying a bin as an ext fails")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 0, "Getting the type of a bin fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Bin type error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the wrong format code")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("C702050102")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Reading an ext8 as a bin fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Ext8 as bin error is set")
Assert(Bool(MsgPackReadString(*MsgPack) = ""), "Reading an ext8 as a str fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Ext8 as str error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("D40501")
AssertEqualsQ(MsgPackReadBinary(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Reading a fixext1 as a bin fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Fixext1 as bin error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("A3616263")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Reading a str as an ext fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_InvalidFormatCode, "Str as ext error is set")
FreeWrappedHex(*MsgPack)
; Truncated size and type fields
*MsgPack = WrapHex("C7")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Ext8 without its size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Ext8 without its size error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("C700")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Empty ext8 without its type fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Empty ext8 without its type error is set")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 0, "Getting a missing type fails")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("C80000")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Empty ext16 without its type fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Empty ext16 without its type error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("C9000000")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Ext32 with 3/4 of its size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Ext32 with 3/4 of its size error is set")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("D4")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Fixext1 without its type fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Fixext1 without its type error is set")
FreeWrappedHex(*MsgPack)
; Truncated data
*MsgPack = WrapHex("D6FF0000")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Fixext4 missing data fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Fixext4 missing data error is set")
AssertEquals(MsgPackCopyExtension(*MsgPack), #Null, "Copying a fixext4 missing data fails")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the truncated data")
FreeWrappedHex(*MsgPack)
*MsgPack = WrapHex("C705016162")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Ext8 missing data fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Ext8 missing data error is set")
FreeWrappedHex(*MsgPack)
; The type byte must not be counted as data.
*MsgPack = WrapHex("C70201AA")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Ext8 with its type as the last data byte fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Type isn't counted as data")
FreeWrappedHex(*MsgPack)
; The ext32 size is unsigned, `0xFFFFFFFF` must not be read as `-1`.
*MsgPack = WrapHex("C9FFFFFFFF0100")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), #MsgPack_InvalidSize, "Ext32 with a 0xFFFFFFFF size fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_BufferTooSmall, "Ext32 size is treated as unsigned")
FreeWrappedHex(*MsgPack)
; Output buffer too small, then retried
*MsgPack = WrapHex("C70307010203")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 2), #MsgPack_InvalidSize, "3 bytes don't fit in 2")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_OutputBufferTooSmall, "Output too small error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched so the read can be retried")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 7, "Type is still readable")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 3), 3, "Retried with an exactly sized buffer")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Retry cleared the error")
AssertEqualsA(PeekA(*Output), $01, "Retried data byte 1")
AssertEqualsA(PeekA(*Output + 1), $02, "Retried data byte 2")
AssertEqualsA(PeekA(*Output + 2), $03, "Retried data byte 3")
AssertEquals(*MsgPack\BufferOffset, 6, "Offset is at the end")
FreeWrappedHex(*MsgPack)
; Ending
FreeMemory(*Source)
FreeMemory(*Output)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
@@ -0,0 +1,51 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; /!\ Every case here calls `DebuggerError()`, which halts the program under the debugger.
; Run it without the debugger and check the exit code.
; Setup
Define *MsgPack.MsgPackData
Define *Source = AllocatePattern(16)
Define *Output = AllocateMemory(16)
; Null MsgPack pointers
AssertEquals(MsgPackWriteExt8(#Null, 1, *Source, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteExt8 is given #Null")
AssertEquals(MsgPackWriteExt16(#Null, 1, *Source, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteExt16 is given #Null")
AssertEquals(MsgPackWriteExt32(#Null, 1, *Source, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteExt32 is given #Null")
AssertEquals(MsgPackWriteExtension(#Null, 1, *Source, 1), #MsgPack_Error_NullPointerGiven, "MsgPackWriteExtension is given #Null for a fixext")
AssertEquals(MsgPackWriteExtension(#Null, 1, *Source, 3), #MsgPack_Error_NullPointerGiven, "MsgPackWriteExtension is given #Null for an ext8")
AssertEquals(MsgPackWriteFixExt1(#Null, 1, *Source), #MsgPack_Error_NullPointerGiven, "MsgPackWriteFixExt1 is given #Null")
AssertEquals(MsgPackWriteFixExt16(#Null, 1, *Source), #MsgPack_Error_NullPointerGiven, "MsgPackWriteFixExt16 is given #Null")
AssertEqualsQ(MsgPackReadExtension(#Null, *Output, 16), #MsgPack_InvalidSize, "MsgPackReadExtension is given #Null")
AssertEquals(MsgPackCopyExtension(#Null), #Null, "MsgPackCopyExtension is given #Null")
AssertEqualsB(MsgPackGetExtensionType(#Null), 0, "MsgPackGetExtensionType is given #Null")
; Null output buffer with data to copy
*MsgPack = WrapHex("D5010102")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, #Null, 2), #MsgPack_InvalidSize, "Reading into a #Null buffer fails")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_NullPointerGiven, "Null output buffer error is set")
AssertEquals(*MsgPack\BufferOffset, 0, "Offset is untouched by the failed read")
FreeWrappedHex(*MsgPack)
; Ending
FreeMemory(*Source)
FreeMemory(*Output)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
@@ -0,0 +1,255 @@
EnableExplicit
XIncludeFile "../_UnitTesting.pbi"
XIncludeFile "../../Includes/MessagePack.pbi"
XIncludeFile "../_TestBuffers.pbi"
; Setup
; Starting with 1 byte so every write has to grow the buffer.
Define *MsgPack.MsgPackData = MsgPackCreate(1)
Define *Source = AllocatePattern(65536)
Define *Output = AllocateMemory(65536)
Define *Copy
Define OffsetEmpty.i, OffsetFix1.i, OffsetFix2.i, OffsetFix4.i, OffsetFix8.i, OffsetFix16.i
Define Offset3.i, Offset255.i, Offset256.i, Offset65535.i, Offset65536.i
Define OffsetExt8Fix.i, OffsetExt16Small.i, OffsetExt32Empty.i, OffsetFixExt4.i, OffsetFixExt16.i
Define OffsetEnd.i
; Automatic format code selection, on each boundary
OffsetEmpty = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, 5, #Null, 0), #MsgPack_Error_Success, "Wrote 0 bytes from a #Null buffer")
OffsetFix1 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, 1, *Source, 1), #MsgPack_Error_Success, "Wrote 1 byte")
OffsetFix2 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, -1, *Source, 2), #MsgPack_Error_Success, "Wrote 2 bytes")
OffsetFix4 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, 127, *Source, 4), #MsgPack_Error_Success, "Wrote 4 bytes")
OffsetFix8 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, -128, *Source, 8), #MsgPack_Error_Success, "Wrote 8 bytes")
OffsetFix16 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, 42, *Source, 16), #MsgPack_Error_Success, "Wrote 16 bytes")
Offset3 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, 3, *Source, 3), #MsgPack_Error_Success, "Wrote 3 bytes")
Offset255 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, 6, *Source, 255), #MsgPack_Error_Success, "Wrote 255 bytes")
Offset256 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, 7, *Source, 256), #MsgPack_Error_Success, "Wrote 256 bytes")
Offset65535 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, 8, *Source, 65535), #MsgPack_Error_Success, "Wrote 65535 bytes")
Offset65536 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExtension(*MsgPack, 9, *Source, 65536), #MsgPack_Error_Success, "Wrote 65536 bytes")
; Forced format codes, even when a smaller one fits
OffsetExt8Fix = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExt8(*MsgPack, 10, *Source, 4), #MsgPack_Error_Success, "Wrote 4 bytes as ext8")
OffsetExt16Small = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExt16(*MsgPack, 11, *Source, 1), #MsgPack_Error_Success, "Wrote 1 byte as ext16")
OffsetExt32Empty = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteExt32(*MsgPack, 12, #Null, 0), #MsgPack_Error_Success, "Wrote 0 bytes as ext32")
OffsetFixExt4 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteFixExt4(*MsgPack, 13, *Source), #MsgPack_Error_Success, "Wrote fixext4")
OffsetFixExt16 = *MsgPack\BufferOffset
AssertEquals(MsgPackWriteFixExt16(*MsgPack, 14, *Source), #MsgPack_Error_Success, "Wrote fixext16")
; Something right after, to catch offset drifts
AssertEquals(MsgPackWriteUInt8(*MsgPack, 42), #MsgPack_Error_Success, "Wrote trailing UInt8")
OffsetEnd = *MsgPack\BufferOffset
; Offsets
AssertEquals(OffsetFix1, OffsetEmpty + 3, "Empty ext8 takes 3 bytes")
AssertEquals(OffsetFix2, OffsetFix1 + 2 + 1, "fixext1 takes 3 bytes")
AssertEquals(OffsetFix4, OffsetFix2 + 2 + 2, "fixext2 takes 4 bytes")
AssertEquals(OffsetFix8, OffsetFix4 + 2 + 4, "fixext4 takes 6 bytes")
AssertEquals(OffsetFix16, OffsetFix8 + 2 + 8, "fixext8 takes 10 bytes")
AssertEquals(Offset3, OffsetFix16 + 2 + 16, "fixext16 takes 18 bytes")
AssertEquals(Offset255, Offset3 + 3 + 3, "3 bytes ext8 takes 6 bytes")
AssertEquals(Offset256, Offset255 + 3 + 255, "255 bytes ext8 takes 258 bytes")
AssertEquals(Offset65535, Offset256 + 4 + 256, "256 bytes ext16 takes 260 bytes")
AssertEquals(Offset65536, Offset65535 + 4 + 65535, "65535 bytes ext16 takes 65539 bytes")
AssertEquals(OffsetExt8Fix, Offset65536 + 6 + 65536, "65536 bytes ext32 takes 65542 bytes")
AssertEquals(OffsetExt16Small, OffsetExt8Fix + 3 + 4, "4 bytes ext8 takes 7 bytes")
AssertEquals(OffsetExt32Empty, OffsetExt16Small + 4 + 1, "1 byte ext16 takes 5 bytes")
AssertEquals(OffsetFixExt4, OffsetExt32Empty + 6, "Empty ext32 takes 6 bytes")
AssertEquals(OffsetFixExt16, OffsetFixExt4 + 2 + 4, "Forced fixext4 takes 6 bytes")
AssertEquals(OffsetEnd, OffsetFixExt16 + 2 + 16 + 2, "Forced fixext16 and UInt8 take 20 bytes")
AssertEquals(*MsgPack\BufferSize, OffsetEnd, "Buffer grew to exactly what was written")
; Big-endian layouts
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetEmpty), #MsgPack_FormatCode_Ext8, "Empty uses ext8")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetEmpty + 1), $00, "Empty ext8 size")
AssertEqualsB(PeekB(*MsgPack\Buffer + OffsetEmpty + 2), 5, "Empty ext8 type")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFix1), #MsgPack_FormatCode_FixExt1, "1 byte uses fixext1")
AssertEqualsB(PeekB(*MsgPack\Buffer + OffsetFix1 + 1), 1, "fixext1 type")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFix1 + 2), PeekA(*Source), "fixext1 data")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFix2), #MsgPack_FormatCode_FixExt2, "2 bytes uses fixext2")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFix2 + 1), $FF, "fixext2 negative type")
Assert(CompareMemory(*MsgPack\Buffer + OffsetFix2 + 2, *Source, 2), "fixext2 data")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFix4), #MsgPack_FormatCode_FixExt4, "4 bytes uses fixext4")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFix4 + 1), $7F, "fixext4 type")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFix8), #MsgPack_FormatCode_FixExt8, "8 bytes uses fixext8")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFix8 + 1), $80, "fixext8 type")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFix16), #MsgPack_FormatCode_FixExt16, "16 bytes uses fixext16")
Assert(CompareMemory(*MsgPack\Buffer + OffsetFix16 + 2, *Source, 16), "fixext16 data")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset3), #MsgPack_FormatCode_Ext8, "3 bytes uses ext8")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset3 + 1), $03, "3 bytes ext8 size")
AssertEqualsB(PeekB(*MsgPack\Buffer + Offset3 + 2), 3, "3 bytes ext8 type")
Assert(CompareMemory(*MsgPack\Buffer + Offset3 + 3, *Source, 3), "3 bytes ext8 data")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset255), #MsgPack_FormatCode_Ext8, "255 bytes uses ext8")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset255 + 1), $FF, "255 bytes ext8 size")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset256), #MsgPack_FormatCode_Ext16, "256 bytes uses ext16")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset256 + 1), $01, "256 bytes ext16 size high byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset256 + 2), $00, "256 bytes ext16 size low byte")
AssertEqualsB(PeekB(*MsgPack\Buffer + Offset256 + 3), 7, "256 bytes ext16 type")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65535), #MsgPack_FormatCode_Ext16, "65535 bytes uses ext16")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65535 + 1), $FF, "65535 bytes ext16 size high byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65535 + 2), $FF, "65535 bytes ext16 size low byte")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536), #MsgPack_FormatCode_Ext32, "65536 bytes uses ext32")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 1), $00, "65536 bytes ext32 size byte 1")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 2), $01, "65536 bytes ext32 size byte 2")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 3), $00, "65536 bytes ext32 size byte 3")
AssertEqualsA(PeekA(*MsgPack\Buffer + Offset65536 + 4), $00, "65536 bytes ext32 size byte 4")
AssertEqualsB(PeekB(*MsgPack\Buffer + Offset65536 + 5), 9, "65536 bytes ext32 type")
Assert(CompareMemory(*MsgPack\Buffer + Offset65536 + 6, *Source, 65536), "65536 bytes ext32 data")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetExt8Fix), #MsgPack_FormatCode_Ext8, "Forced 4 bytes ext8")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetExt8Fix + 1), $04, "Forced 4 bytes ext8 size")
AssertEqualsB(PeekB(*MsgPack\Buffer + OffsetExt8Fix + 2), 10, "Forced 4 bytes ext8 type")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetExt32Empty), #MsgPack_FormatCode_Ext32, "Forced empty ext32")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetExt32Empty + 4), $00, "Forced empty ext32 size byte 4")
AssertEqualsB(PeekB(*MsgPack\Buffer + OffsetExt32Empty + 5), 12, "Forced empty ext32 type")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFixExt4), #MsgPack_FormatCode_FixExt4, "Forced fixext4")
AssertEqualsA(PeekA(*MsgPack\Buffer + OffsetFixExt16), #MsgPack_FormatCode_FixExt16, "Forced fixext16")
AssertEqualsB(PeekB(*MsgPack\Buffer + OffsetFixExt16 + 1), 14, "Forced fixext16 type")
; Reading them back
*MsgPack\BufferOffset = 0
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 5, "Empty ext8 type is read")
AssertEquals(*MsgPack\BufferOffset, 0, "Reading the type didn't move the offset")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, #Null, 0), 0, "Read empty ext8 into a #Null buffer")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Empty read is a success")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 1, "fixext1 type is read")
FillMemory(*Output, 65536, $AA)
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 1), 1, "Read fixext1 into an exactly sized buffer")
AssertEqualsA(PeekA(*Output), PeekA(*Source), "fixext1 data matches")
AssertEqualsA(PeekA(*Output + 1), $AA, "Nothing written past the data")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), -1, "fixext2 negative type is read")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 65536), 2, "Read fixext2")
Assert(CompareMemory(*Output, *Source, 2), "fixext2 data matches")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 127, "fixext4 type is read")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 65536), 4, "Read fixext4")
Assert(CompareMemory(*Output, *Source, 4), "fixext4 data matches")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), -128, "fixext8 type is read")
*Copy = MsgPackCopyExtension(*MsgPack)
Assert(Bool(*Copy <> #Null), "Copied fixext8")
If *Copy
AssertEquals(MemorySize(*Copy), 8, "Copy is exactly 8 bytes")
Assert(CompareMemory(*Copy, *Source, 8), "fixext8 data matches")
FreeMemory(*Copy)
EndIf
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 42, "fixext16 type is read")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 16), 16, "Read fixext16")
Assert(CompareMemory(*Output, *Source, 16), "fixext16 data matches")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 3, "3 bytes ext8 type is read")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 65536), 3, "Read 3 bytes ext8")
Assert(CompareMemory(*Output, *Source, 3), "3 bytes ext8 data matches")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 255), 255, "Read 255 bytes ext8")
Assert(CompareMemory(*Output, *Source, 255), "255 bytes data matches")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 7, "256 bytes ext16 type is read")
*Copy = MsgPackCopyExtension(*MsgPack)
Assert(Bool(*Copy <> #Null), "Copied 256 bytes")
If *Copy
AssertEquals(MemorySize(*Copy), 256, "Copy is exactly 256 bytes")
Assert(CompareMemory(*Copy, *Source, 256), "256 bytes data matches")
FreeMemory(*Copy)
EndIf
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 65536), 65535, "Read 65535 bytes ext16")
Assert(CompareMemory(*Output, *Source, 65535), "65535 bytes data matches")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 9, "65536 bytes ext32 type is read")
*Copy = MsgPackCopyExtension(*MsgPack)
Assert(Bool(*Copy <> #Null), "Copied 65536 bytes")
If *Copy
AssertEquals(MemorySize(*Copy), 65536, "Copy is exactly 65536 bytes")
Assert(CompareMemory(*Copy, *Source, 65536), "65536 bytes data matches")
FreeMemory(*Copy)
EndIf
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 10, "Forced ext8 type is read")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 65536), 4, "Read forced 4 bytes ext8")
Assert(CompareMemory(*Output, *Source, 4), "Forced 4 bytes ext8 data matches")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 11, "Forced ext16 type is read")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 65536), 1, "Read forced 1 byte ext16")
AssertEqualsA(PeekA(*Output), PeekA(*Source), "Forced 1 byte ext16 data matches")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 12, "Forced ext32 type is read")
AssertEquals(MsgPackCopyExtension(*MsgPack), #Null, "Copying an empty ext32 returns #Null")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "Empty copy is a success")
AssertEquals(*MsgPack\BufferOffset, OffsetFixExt4, "Empty copy still moved the offset")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 13, "Forced fixext4 type is read")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 65536), 4, "Read forced fixext4")
Assert(CompareMemory(*Output, *Source, 4), "Forced fixext4 data matches")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), 14, "Forced fixext16 type is read")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 65536), 16, "Read forced fixext16")
Assert(CompareMemory(*Output, *Source, 16), "Forced fixext16 data matches")
AssertEqualsA(MsgPackReadUInt8(*MsgPack), 42, "Read trailing UInt8")
AssertEquals(*MsgPack\BufferOffset, OffsetEnd, "Offset is at the end")
AssertEquals(MsgPackLastError(*MsgPack), #MsgPack_Error_Success, "No error after reading")
MsgPackFree(*MsgPack, #True)
; Known vector: timestamp 32 for 1970-01-01T00:00:01Z
*MsgPack = WrapHex("D6FF00000001")
AssertEquals(MsgPackFormatCodeToDataType(MsgPackGetFormatCode(*MsgPack)), #MsgPack_DataTypes_Extension, "Timestamp is detected as an extension")
AssertEqualsB(MsgPackGetExtensionType(*MsgPack), -1, "Timestamp type is -1")
AssertEqualsQ(MsgPackReadExtension(*MsgPack, *Output, 4), 4, "Read the timestamp's 4 bytes")
AssertEquals(EndianSwapI32(PeekL(*Output)), 1, "Timestamp seconds are big-endian")
AssertEquals(*MsgPack\BufferOffset, 6, "Offset is at the end")
FreeWrappedHex(*MsgPack)
; Ending
FreeMemory(*Source)
FreeMemory(*Output)
If FailedUnitTests
End 1
EndIf
Debug "Done !"
End 0
+38
View File
@@ -0,0 +1,38 @@
; Test-only helpers for building raw MessagePack buffers.
; Must be included after "MessagePack.pbi".
; Wraps a new buffer filled from a hex string like "C50001" into a non-growable `MsgPackData`.
; Free it with `FreeWrappedHex()`.
Procedure.i WrapHex(Hex.s)
Protected Size.i = Len(Hex) / 2
Protected *Buffer = AllocateMemory(Size)
Protected i.i
For i = 0 To Size - 1
PokeA(*Buffer + i, Val("$" + Mid(Hex, (i * 2) + 1, 2)))
Next
ProcedureReturn MsgPackWrapMsgBuffer(*Buffer, Size)
EndProcedure
Procedure FreeWrappedHex(*MsgPack.MsgPackData)
FreeMemory(*MsgPack\Buffer)
MsgPackFree(*MsgPack, #False)
EndProcedure
; Returns a new buffer of `Size` bytes holding `(Index * 7 + 3 + Index / 256) & $FF`.
; The pattern shifts every 256 bytes so a misplaced offset can't hide behind a repeat,
; and it contains both `$00` and `$FF` bytes.
Procedure.i AllocatePattern(Size.i)
Protected *Buffer = AllocateMemory(Size)
Protected i.i
For i = 0 To Size - 1
PokeA(*Buffer + i, ((i * 7) + 3 + (i / 256)) & $FF)
Next
ProcedureReturn *Buffer
EndProcedure
+12 -8
View File
@@ -5,19 +5,23 @@ Based on the [msgpack/msgpack](https://github.com/msgpack/msgpack) specification
## Features
* Full support of the [MessagePack](https://github.com/msgpack/msgpack) specification
* Explicit or implicit [typing]
* [the autotyping over which other libs sometimes give no control]
* ???
* ~~Full support of the [MessagePack](https://github.com/msgpack/msgpack) specification~~
* **Still missing Map and Array**
* Explicit or implicit data formats when reading/writing
* *ReadInteger vs ReadI8/ReadU8/...*
* ~~Fully documented~~
## Limitations
* Usage of larger `.q` type for field sizes
* *Helps prevent unsigned numbers being passed around carelessly*
* No custom `malloc`/`realloc`/`free` callbacks
* More involved Map and Array parsing
* *No dynamic typing*
* No custom `malloc`/`realloc`/`free` callbacks
* ???
* Usage of larger `.q` type for field sizes
* *Helps prevent unsigned numbers causing issues on x86*
* Particular handling of `NaN` for *Float32* and *Float64*
* `0x7F800001` gets returned as `0x7FC00001` (x87 FPU limitation)
* **ARM64 untested yet**
* No implementation of the *Timestamp* extension
## Usage