FOSS-VG/src/lib/nbt.cpp

1857 lines
77 KiB
C++
Raw Normal View History

2022-06-27 11:46:13 +02:00
// Copyright 2022, FOSS-VG Developers and Contributers
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// version 3 along with this program.
// If not, see https://www.gnu.org/licenses/agpl-3.0.en.html
#include <bit>
2022-06-27 04:50:32 +02:00
#include <cstdint>
#include <vector>
#include <tinyutf8/tinyutf8.h>
#include "nbt.hpp"
#include "error.hpp"
#include "javacompat.hpp"
2022-06-27 04:50:32 +02:00
2022-07-01 21:15:18 +02:00
#include "../../.endianness"
#ifdef FOSSVG_ENDIAN_BIG_WORD
#error "Honeywell-316-style endianness is not supported. If you feel like it should, feel free to participate in the project to maintain it."
#endif
#ifdef FOSSVG_ENDIAN_LITTLE_WORD
#error "PDP-11-style endianness is not supported. If you feel like it should, feel free to participate in the project to maintain it."
#endif
#ifdef FOSSVG_ENDIAN_UNKNOWN
#error "The endianness of your system could not be determined. Please set it manually. FOSS-VG is currently implemented using some endian-specific functions."
#endif
2022-06-27 04:50:32 +02:00
namespace NBT {
namespace Helper {
ErrorOr<int8_t> readInt8(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
if (currentPosition>=dataSize) return ErrorOr<int8_t>(true, ErrorCodes::OUT_OF_RANGE);
return ErrorOr<int8_t>((int8_t) data[currentPosition]);
2022-06-27 04:50:32 +02:00
}
ErrorOr<int16_t> readInt16(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
if (currentPosition>=dataSize) return ErrorOr<int16_t>(true, ErrorCodes::OUT_OF_RANGE);
if (dataSize<currentPosition+2) return ErrorOr<int16_t>(true, ErrorCodes::OVERRUN);
return ErrorOr<int16_t>((int16_t) ((static_cast<int16_t>(data[currentPosition]) << 8) | static_cast<int16_t>(data[currentPosition+1])));
2022-06-27 04:50:32 +02:00
}
ErrorOr<int32_t> readInt32(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
if (currentPosition>=dataSize) return ErrorOr<int32_t>(true, ErrorCodes::OUT_OF_RANGE);
if (dataSize<currentPosition+4) return ErrorOr<int32_t>(true, ErrorCodes::OVERRUN);
return ErrorOr<int32_t>((int32_t) (
(static_cast<int32_t>(data[currentPosition ]) << 24) |
(static_cast<int32_t>(data[currentPosition+1]) << 16) |
(static_cast<int32_t>(data[currentPosition+2]) << 8) |
static_cast<int32_t>(data[currentPosition+3])
));
2022-06-27 04:50:32 +02:00
}
ErrorOr<int64_t> readInt64(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
if (currentPosition>=dataSize) return ErrorOr<int64_t>(true, ErrorCodes::OUT_OF_RANGE);
if (dataSize<currentPosition+8) return ErrorOr<int64_t>(true, ErrorCodes::OVERRUN);
return ErrorOr<int64_t>((int64_t) (
(static_cast<int64_t>(data[currentPosition ]) << 56) |
(static_cast<int64_t>(data[currentPosition+1]) << 48) |
(static_cast<int64_t>(data[currentPosition+2]) << 40) |
(static_cast<int64_t>(data[currentPosition+3]) << 32) |
(static_cast<int64_t>(data[currentPosition+4]) << 24) |
(static_cast<int64_t>(data[currentPosition+5]) << 16) |
(static_cast<int64_t>(data[currentPosition+6]) << 8) |
static_cast<int64_t>(data[currentPosition+7])
));
2022-06-27 04:50:32 +02:00
}
//FIXME: endian-dependent implementations
ErrorOr<float> readFloat(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
2022-07-01 21:15:18 +02:00
float* value = new float;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
if (dataSize<=currentPosition) return ErrorOr<float>(true, ErrorCodes::OUT_OF_RANGE);
if (dataSize<currentPosition+4) return ErrorOr<float>(true, ErrorCodes::OVERRUN);
2022-07-01 21:15:18 +02:00
#ifdef FOSSVG_BIG_ENDIAN
*valueAsBytes = data[currentPosition];
*(valueAsBytes+1) = data[currentPosition+1];
*(valueAsBytes+2) = data[currentPosition+2];
*(valueAsBytes+3) = data[currentPosition+3];
#else
#ifdef FOSSVG_LITTLE_ENDIAN
*valueAsBytes = data[currentPosition+3];
*(valueAsBytes+1) = data[currentPosition+2];
*(valueAsBytes+2) = data[currentPosition+1];
*(valueAsBytes+3) = data[currentPosition];
#else
#error "NBT::Helper::readFloat: An implementation for your endianness is unavailable."
2022-07-01 21:15:18 +02:00
#endif
#endif
float dereferencedValue = *value;
delete value;
return ErrorOr<float>(dereferencedValue);
2022-06-27 04:50:32 +02:00
}
//FIXME: endian-dependent implementations
ErrorOr<double> readDouble(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
2022-07-01 21:15:18 +02:00
double* value = new double;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
if (dataSize<=currentPosition) return ErrorOr<double>(true, ErrorCodes::OUT_OF_RANGE);
if (dataSize<currentPosition+8) return ErrorOr<double>(true, ErrorCodes::OVERRUN);
2022-07-01 21:15:18 +02:00
#ifdef FOSSVG_BIG_ENDIAN
*valueAsBytes = data[currentPosition];
*(valueAsBytes+1) = data[currentPosition+1];
*(valueAsBytes+2) = data[currentPosition+2];
*(valueAsBytes+3) = data[currentPosition+3];
*(valueAsBytes+4) = data[currentPosition+4];
*(valueAsBytes+5) = data[currentPosition+5];
*(valueAsBytes+6) = data[currentPosition+6];
*(valueAsBytes+7) = data[currentPosition+7];
#else
#ifdef FOSSVG_LITTLE_ENDIAN
*valueAsBytes = data[currentPosition+7];
*(valueAsBytes+1) = data[currentPosition+6];
*(valueAsBytes+2) = data[currentPosition+5];
*(valueAsBytes+3) = data[currentPosition+4];
*(valueAsBytes+4) = data[currentPosition+3];
*(valueAsBytes+5) = data[currentPosition+2];
*(valueAsBytes+6) = data[currentPosition+1];
*(valueAsBytes+7) = data[currentPosition];
#else
#error "NBT::Helper::readDouble: An implementation for your endianness is unavailable."
2022-07-01 21:15:18 +02:00
#endif
#endif
double dereferencedValue = *value;
delete value;
return ErrorOr<double>(dereferencedValue);
2022-06-27 04:50:32 +02:00
}
ErrorOr<std::vector<int8_t>> readInt8Array(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
ErrorOr<int32_t> size = readInt32(data, dataSize, currentPosition);
if (size.isError) {
// It is okay to pass up the error code here without further
// processing because both OVERRUN and OUT_OF_RANGE errors will
// still apply.
return ErrorOr<std::vector<int8_t>>(true, size.errorCode);
}
// get content
if (currentPosition+4+size.value > dataSize) return ErrorOr<std::vector<int8_t>>(true, ErrorCodes::OVERRUN);
std::vector<int8_t> result = std::vector<int8_t>();
for (int i=0; i<size.value; i++) {
result.push_back(data[currentPosition+4+i]);
}
return ErrorOr<std::vector<int8_t>>(result);
2022-06-27 04:50:32 +02:00
}
ErrorOr<tiny_utf8::string> readString(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
ErrorOr<int16_t> stringSize = readInt16(data, dataSize, currentPosition);
if (stringSize.isError) {
// It is okay to pass up the error code here without further
// processing because both OVERRUN and OUT_OF_RANGE errors will
// still apply.
return ErrorOr<tiny_utf8::string>(true, stringSize.errorCode);
}
if (currentPosition + (uint64_t) stringSize.value + 2 > dataSize) {
return ErrorOr<tiny_utf8::string>(true, ErrorCodes::OVERRUN);
}
ErrorOr<tiny_utf8::string> output = JavaCompat::importJavaString(data+currentPosition, stringSize.value);
if(output.isError){
return ErrorOr<tiny_utf8::string>(true, output.errorCode);
}
return output;
}
2022-06-27 04:50:32 +02:00
ErrorOr<std::vector<int32_t>> readInt32Array(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
ErrorOr<int32_t> size = readInt32(data, dataSize, currentPosition);
if (size.isError) {
// It is okay to pass up the error code here without further
// processing because both OVERRUN and OUT_OF_RANGE errors will
// still apply.
return ErrorOr<std::vector<int32_t>>(true, size.errorCode);
}
// get content
if (currentPosition+4+(size.value*4) > dataSize) return ErrorOr<std::vector<int32_t>>(true, ErrorCodes::OVERRUN);
std::vector<int32_t> result = std::vector<int32_t>();
for (int i=0; i<size.value; i++) {
ErrorOr<int32_t> nextInt32 = readInt32(data, dataSize, currentPosition+4+(i*4));
if (nextInt32.isError) return ErrorOr<std::vector<int32_t>>(true, nextInt32.errorCode);
result.push_back(nextInt32.value);
}
return ErrorOr<std::vector<int32_t>>(result);
2022-06-27 04:50:32 +02:00
}
ErrorOr<std::vector<int64_t>> readInt64Array(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
ErrorOr<int32_t> size = readInt32(data, dataSize, currentPosition);
if (size.isError) {
// It is okay to pass up the error code here without further
// processing because both OVERRUN and OUT_OF_RANGE errors will
// still apply.
return ErrorOr<std::vector<int64_t>>(true, size.errorCode);
}
// get content
if (currentPosition+4+(size.value*8) > dataSize) return ErrorOr<std::vector<int64_t>>(true, ErrorCodes::OVERRUN);
std::vector<int64_t> result = std::vector<int64_t>();
for (int i=0; i<size.value; i++) {
ErrorOr<int64_t> nextInt64 = readInt64(data, dataSize, currentPosition+4+(i*8));
if (nextInt64.isError) return ErrorOr<std::vector<int64_t>>(true, nextInt64.errorCode);
result.push_back(nextInt64.value);
}
return ErrorOr<std::vector<int64_t>>(result);
2022-06-27 04:50:32 +02:00
}
2022-07-02 02:08:32 +02:00
void writeInt8(std::vector<uint8_t>* destination, int8_t data) {
destination->push_back((uint8_t) data);
2022-07-02 02:08:32 +02:00
}
//FIXME: endian dependent implementation
2022-07-02 02:08:32 +02:00
void writeInt16(std::vector<uint8_t>* destination, int16_t data) {
int16_t* value = new int16_t;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
*value = data;
#ifdef FOSSVG_BIG_ENDIAN
destination->push_back(*valueAsBytes);
destination->push_back(*(valueAsBytes+1));
#else
#ifdef FOSSVG_LITTLE_ENDIAN
destination->push_back(*(valueAsBytes+1));
destination->push_back(*valueAsBytes);
#else
#error "NBT::Helper::writeInt16: An implementation for your endianness is unavailable."
#endif
#endif
delete value;
2022-07-02 02:08:32 +02:00
}
//FIXME: endian dependent implementation
2022-07-02 02:08:32 +02:00
void writeInt32(std::vector<uint8_t>* destination, int32_t data) {
int32_t* value = new int32_t;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
*value = data;
#ifdef FOSSVG_BIG_ENDIAN
destination->push_back(*valueAsBytes);
destination->push_back(*(valueAsBytes+1));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+3));
#else
#ifdef FOSSVG_LITTLE_ENDIAN
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+1));
destination->push_back(*valueAsBytes);
#else
#error "NBT::Helper::writeInt16: An implementation for your endianness is unavailable."
#endif
#endif
delete value;
2022-07-02 02:08:32 +02:00
}
//FIXME: endian dependent implementation
2022-07-02 02:08:32 +02:00
void writeInt64(std::vector<uint8_t>* destination, int64_t data) {
int64_t* value = new int64_t;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
*value = data;
#ifdef FOSSVG_BIG_ENDIAN
destination->push_back(*valueAsBytes);
destination->push_back(*(valueAsBytes+1));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+4));
destination->push_back(*(valueAsBytes+5));
destination->push_back(*(valueAsBytes+6));
destination->push_back(*(valueAsBytes+7));
#else
#ifdef FOSSVG_LITTLE_ENDIAN
destination->push_back(*(valueAsBytes+7));
destination->push_back(*(valueAsBytes+6));
destination->push_back(*(valueAsBytes+5));
destination->push_back(*(valueAsBytes+4));
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+1));
destination->push_back(*valueAsBytes);
#else
#error "NBT::Helper::writeInt16: An implementation for your endianness is unavailable."
#endif
#endif
delete value;
2022-07-02 02:08:32 +02:00
}
//FIXME: endian-specific implementations
void writeFloat(std::vector<uint8_t>* destination, float data) {
2022-07-04 19:43:31 +02:00
float* value = new float;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
*value = data;
#ifdef FOSSVG_BIG_ENDIAN
destination->push_back(*valueAsBytes);
destination->push_back(*(valueAsBytes+1));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+3));
#else
#ifdef FOSSVG_LITTLE_ENDIAN
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+1));
destination->push_back(*valueAsBytes);
#else
#error "NBT::Helper::writeInt16: An implementation for your endianness is unavailable."
2022-07-04 19:43:31 +02:00
#endif
#endif
delete value;
2022-07-02 02:08:32 +02:00
}
//FIXME: endian-specific implementations
void writeDouble(std::vector<uint8_t>* destination, double data) {
2022-07-04 19:43:31 +02:00
double* value = new double;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
*value = data;
#ifdef FOSSVG_BIG_ENDIAN
destination->push_back(*valueAsBytes);
destination->push_back(*(valueAsBytes+1));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+4));
destination->push_back(*(valueAsBytes+5));
destination->push_back(*(valueAsBytes+6));
destination->push_back(*(valueAsBytes+7));
#else
#ifdef FOSSVG_LITTLE_ENDIAN
destination->push_back(*(valueAsBytes+7));
destination->push_back(*(valueAsBytes+6));
destination->push_back(*(valueAsBytes+5));
destination->push_back(*(valueAsBytes+4));
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+1));
destination->push_back(*valueAsBytes);
#else
#error "NBT::Helper::writeInt16: An implementation for your endianness is unavailable."
2022-07-04 19:43:31 +02:00
#endif
#endif
delete value;
2022-07-02 02:08:32 +02:00
}
void writeInt8Array(std::vector<uint8_t>* destination, std::vector<int8_t> data) {
writeInt32(destination, data.size());
for(int8_t datum: data){
destination->push_back(datum);
}
2022-07-02 02:08:32 +02:00
}
2022-07-06 12:57:32 +02:00
void writeInt8Array(std::vector<uint8_t>* destination, int8_t data[], uint32_t dataSize) {
writeInt32(destination, dataSize);
for(uint32_t i=0; i < dataSize; i++){
destination->push_back(data[i]);
}
2022-07-02 02:08:32 +02:00
}
ErrorOrVoid writeString(std::vector<uint8_t>* destination, tiny_utf8::string data) {
ErrorOr<std::vector<uint8_t>> exportedString = JavaCompat::exportJavaString(data);
if(exportedString.isError){
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
destination->insert(destination->end(), exportedString.value.begin(), exportedString.value.end());
return ErrorOrVoid();
}
2022-07-02 02:08:32 +02:00
void writeInt32Array(std::vector<uint8_t>* destination, std::vector<int32_t> data) {
writeInt32(destination, data.size());
for(int32_t element: data){
writeInt32(destination, element);
}
2022-07-02 02:08:32 +02:00
}
void writeInt32Array(std::vector<uint8_t>* destination, int32_t data[], uint32_t dataSize) {
writeInt32(destination, dataSize);
for(uint32_t i = 0; i<dataSize; i++){
writeInt32(destination, data[i]);
}
2022-07-02 02:08:32 +02:00
}
void writeInt64Array(std::vector<uint8_t>* destination, std::vector<int64_t> data) {
writeInt32(destination, data.size());
for(int64_t element: data){
writeInt64(destination, element);
}
2022-07-02 02:08:32 +02:00
}
void writeInt64Array(std::vector<uint8_t>* destination, int64_t data[], uint32_t dataSize) {
writeInt32(destination, dataSize);
for(uint32_t i = 0; i<dataSize; i++){
writeInt64(destination, data[i]);
}
2022-07-02 02:08:32 +02:00
}
//FIXME: instead of blindly passing the error code upwards, choose
// one that is applicable to the situation (for example replace
// OUT_OF_RANGE with OVERRUN where appropriate)
//
// The total size in bytes
//
// Does not work for compound tags and lists. This is an intended
// feature as compound tags and lists need to be dealt with
// separately to avoid unnecessarily long and complex code.
//
// Regarding lists specifically: The size of some lists can can
// be determined easily by looking at the contained data type and
// size information but cases like string lists or compound lists
// are significantly more difficult to deal with. Parsing their
// contents requires special attention anyway due to the tag headers
// of contained tags being absent so they may as well get treated
// separately for this as well.
ErrorOr<uint64_t> totalTagSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
uint8_t nextTag;
if (dataSize <= currentPosition) {
return ErrorOr<uint64_t>(true, ErrorCodes::OVERRUN);
} else {
nextTag = data[currentPosition];
}
// deal with compound tags and lists separately
if (nextTag == TagType::COMPOUND || nextTag == TagType::LIST) return ErrorOr<uint64_t>(true, ErrorCodes::NOT_YET_KNOWN);
// deal with end tag before trying to access the name
if (nextTag == TagType::END) return ErrorOr<uint64_t>(1);
ErrorOr<int16_t> nameSize = Helper::readInt16(data, dataSize, currentPosition+1);
if (nameSize.isError) {
return ErrorOr<uint64_t>(true, nameSize.errorCode);
}
// add type byte and name size bytes
uint64_t prefixSize = (uint64_t) nameSize.value + 3;
switch (nextTag) {
case TagType::INT8:
return ErrorOr<uint64_t>(prefixSize+1);
case TagType::INT16:
return ErrorOr<uint64_t>(prefixSize+2);
case TagType::INT32:
return ErrorOr<uint64_t>(prefixSize+4);
case TagType::INT64:
return ErrorOr<uint64_t>(prefixSize+8);
case TagType::FLOAT:
return ErrorOr<uint64_t>(prefixSize+4);
case TagType::DOUBLE:
return ErrorOr<uint64_t>(prefixSize+8);
case TagType::INT8_ARRAY: {
ErrorOr<int32_t> arrayLength = Helper::readInt32(data, dataSize, currentPosition+prefixSize);
if (arrayLength.isError) {
return ErrorOr<uint64_t>(true, arrayLength.errorCode);
}
return ErrorOr<uint64_t>((uint64_t) arrayLength.value + prefixSize + 4);
}
case TagType::STRING: {
ErrorOr<int16_t> stringSize = Helper::readInt16(data, dataSize, currentPosition+prefixSize);
if (stringSize.isError) {
return ErrorOr<uint64_t>(true, stringSize.errorCode);
}
return ErrorOr<uint64_t>((uint64_t) stringSize.value + prefixSize + 2);
}
case TagType::INT32_ARRAY: {
ErrorOr<int32_t> arrayLength = Helper::readInt32(data, dataSize, currentPosition+prefixSize);
if (arrayLength.isError) {
return ErrorOr<uint64_t>(true, arrayLength.errorCode);
}
return ErrorOr<uint64_t>((uint64_t) arrayLength.value*4 + prefixSize + 4);
}
case TagType::INT64_ARRAY: {
ErrorOr<int32_t> arrayLength = Helper::readInt32(data, dataSize, currentPosition+prefixSize);
if (arrayLength.isError) {
return ErrorOr<uint64_t>(true, arrayLength.errorCode);
}
return ErrorOr<uint64_t>((uint64_t) arrayLength.value*8 + prefixSize + 4);
}
// unknown tag or parsing error
default:
return ErrorOr<uint64_t>(true, ErrorCodes::UNKNOWN);
}
}
//FIXME: instead of blindly passing the error code upwards, choose
// one that is applicable to the situation (for example replace
// OUT_OF_RANGE with OVERRUN where appropriate)
//
// Length is the number of stored elements, not to be confused with size
// which is the size in bytes.
ErrorOr<int32_t> containedDataLength(uint8_t data[], uint64_t dataSize, uint64_t currentPosition){
uint8_t nextTag;
if (dataSize <= currentPosition) {
return ErrorOr<int32_t>(true, ErrorCodes::OVERRUN);
} else {
nextTag = data[currentPosition];
}
// deal with compound tags separately
if (nextTag == TagType::COMPOUND) {
return ErrorOr<int32_t>(true, ErrorCodes::NOT_YET_KNOWN);
}
// deal with end tag before trying to access the name
if (nextTag == TagType::END) {
return ErrorOr<int32_t>(0);
}
// tags that only ever hold one value
if (nextTag == TagType::INT8 || nextTag == TagType::INT16 || nextTag == TagType::INT32 || nextTag == TagType::INT64 || nextTag == TagType::FLOAT || nextTag == TagType::DOUBLE) {
return ErrorOr<int32_t>(1);
}
ErrorOr<int16_t> nameSize = Helper::readInt16(data, dataSize, currentPosition+1);
if (nameSize.isError) {
return ErrorOr<int32_t>(true, nameSize.errorCode);
}
// add type byte and name size bytes
uint64_t prefixSize = (uint64_t) nameSize.value + 3;
switch (nextTag) {
case TagType::INT8_ARRAY: {
return Helper::readInt32(data, dataSize, currentPosition+prefixSize);
}
case TagType::STRING: {
ErrorOr<int16_t> stringSize = Helper::readInt16(data, dataSize, currentPosition+prefixSize);
if (stringSize.isError) {
return ErrorOr<int32_t>(true, stringSize.errorCode);
}
return ErrorOr<int32_t>((int32_t) stringSize.value);
}
case TagType::LIST: {
// add an additional byte for the contained data type
return Helper::readInt32(data, dataSize, currentPosition+prefixSize+1);
}
case TagType::INT32_ARRAY: {
return Helper::readInt32(data, dataSize, currentPosition+prefixSize);
}
case TagType::INT64_ARRAY: {
return Helper::readInt32(data, dataSize, currentPosition+prefixSize);
}
default:
// unknown tag or parsing error
return ErrorOr<int32_t>(true, ErrorCodes::UNKNOWN);
}
}
2022-06-27 04:50:32 +02:00
}
namespace Tag {
2022-08-03 20:31:12 +02:00
Generic::Generic() {
this->type = TagType::INVALID;
}
Generic::~Generic() {}
ErrorOrVoid Generic::serializeWithoutHeader([[maybe_unused]] std::vector<uint8_t>* rawData) {
return ErrorOrVoid(true, ErrorCodes::INVALID_TYPE);
}
ErrorOrVoid Generic::serialize(std::vector<uint8_t>* rawData) {
rawData->push_back(this->type);
if (Helper::writeString(rawData, this->name).isError) {
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
return this->serializeWithoutHeader(rawData);
}
uint8_t Generic::getTagType(){
return this->type;
}
End::End() {
this->type = TagType::END;
}
ErrorOrVoid End::serializeWithoutHeader([[maybe_unused]] std::vector<uint8_t>* rawData) {
return ErrorOrVoid();
}
ErrorOrVoid End::serialize(std::vector<uint8_t>* rawData) {
rawData->push_back(this->type);
return ErrorOrVoid();
}
Int8::Int8() {
this->type = TagType::INT8;
}
Int8::Int8(tiny_utf8::string name, int8_t value) {
this->type = TagType::INT8;
this->name = name;
this->value = value;
}
ErrorOrVoid Int8::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt8(rawData, this->value);
return ErrorOrVoid();
}
int8_t Int8::getValue() {
this->mutex.lock();
int8_t value = this->value;
this->mutex.unlock();
return value;
}
void Int8::setValue(int8_t value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Int16::Int16() {
this->type = TagType::INT16;
}
Int16::Int16(tiny_utf8::string name, int16_t value) {
this->type = TagType::INT16;
this->name = name;
this->value = value;
}
ErrorOrVoid Int16::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt16(rawData, this->value);
return ErrorOrVoid();
}
int16_t Int16::getValue() {
this->mutex.lock();
int16_t value = this->value;
this->mutex.unlock();
return value;
}
void Int16::setValue(int16_t value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Int32::Int32() {
this->type = TagType::INT32;
}
Int32::Int32(tiny_utf8::string name, int32_t value) {
this->type = TagType::INT32;
this->name = name;
this->value = value;
}
ErrorOrVoid Int32::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt32(rawData, this->value);
return ErrorOrVoid();
}
int32_t Int32::getValue() {
this->mutex.lock();
int32_t value = this->value;
this->mutex.unlock();
return value;
}
void Int32::setValue(int32_t value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Int64::Int64() {
this->type = TagType::INT64;
}
Int64::Int64(tiny_utf8::string name, int64_t value) {
this->type = TagType::INT64;
this->name = name;
this->value = value;
}
ErrorOrVoid Int64::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt64(rawData, this->value);
return ErrorOrVoid();
}
int64_t Int64::getValue() {
this->mutex.lock();
int64_t value = this->value;
this->mutex.unlock();
return value;
}
void Int64::setValue(int64_t value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Float::Float() {
this->type = TagType::FLOAT;
}
Float::Float(tiny_utf8::string name, float value) {
this->type = TagType::FLOAT;
this->name = name;
this->value = value;
}
ErrorOrVoid Float::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeFloat(rawData, this->value);
return ErrorOrVoid();
}
float Float::getValue() {
this->mutex.lock();
float value = this->value;
this->mutex.unlock();
return value;
}
void Float::setValue(float value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Double::Double() {
this->type = TagType::DOUBLE;
}
Double::Double(tiny_utf8::string name, double value) {
this->type = TagType::DOUBLE;
this->name = name;
this->value = value;
}
ErrorOrVoid Double::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeDouble(rawData, this->value);
return ErrorOrVoid();
}
double Double::getValue() {
this->mutex.lock();
double value = this->value;
this->mutex.unlock();
return value;
}
void Double::setValue(double value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Int8Array::Int8Array() {
this->type = TagType::INT8_ARRAY;
}
Int8Array::Int8Array(tiny_utf8::string name, std::vector<int8_t> data) {
this->type = TagType::INT8_ARRAY;
this->name = name;
this->data = data;
}
Int8Array::Int8Array(tiny_utf8::string name, uint64_t length, int8_t data[]){
this->type = TagType::INT8_ARRAY;
this->name = name;
this->data = std::vector<int8_t>(data, data+length);
}
ErrorOrVoid Int8Array::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt8Array(rawData, this->data);
return ErrorOrVoid();
}
std::vector<int8_t> Int8Array::getData() {
return this->data;
}
ErrorOr<int8_t> Int8Array::getValue(uint64_t position) {
if (this->data.size() <= position) {
return ErrorOr<int8_t>(true, ErrorCodes::OUT_OF_RANGE);
}
return ErrorOr<int8_t>(this->data.at(position));
}
void Int8Array::setData(std::vector<int8_t> newData) {
this->data = newData;
}
ErrorOrVoid Int8Array::setValue(uint64_t position, int8_t value) {
if (this->data.size() <= position) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data[position] = value;
return ErrorOrVoid();
}
uint64_t Int8Array::length() {
return this->data.size();
}
void Int8Array::addElement(int8_t element) {
this->data.push_back(element);
}
ErrorOrVoid Int8Array::removeElement(uint64_t position) {
if (position >= this->data.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data.erase(this->data.begin()+position);
return ErrorOrVoid();
}
String::String() {
this->type = TagType::STRING;
}
String::String(tiny_utf8::string name, tiny_utf8::string value) {
this->type = TagType::STRING;
this->name = name;
this->value = value;
}
ErrorOrVoid String::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
return Helper::writeString(rawData, this->value);
}
tiny_utf8::string String::getValue() {
return this->value;
}
void String::setValue(tiny_utf8::string value) {
this->value = value;
}
List::List() {
this->type = TagType::LIST;
this->containedType = TagType::INVALID;
}
List::List(tiny_utf8::string name, uint8_t type) {
this->type = TagType::LIST;
this->name = name;
this->containedType = type;
}
// WARNING: The pointers inside the vector are automatically cleaned
// up upon deletion of the List object. Do not retain a copy of them
// elsewhere and especially do not delete them externally.
List::List(tiny_utf8::string name, std::vector<Generic*> data) {
this->type = TagType::LIST;
this->name = name;
this->tags = data;
if (data.size() == 0) {
this->containedType = TagType::END;
} else {
this->containedType = data.at(0)->getTagType();
}
}
ErrorOr<List*> List::constructWithData(tiny_utf8::string name, std::vector<Generic*> data) {
if (data.size() > 0xFFFFFFFF) {
return ErrorOr<List*>(true, ErrorCodes::OVERRUN, nullptr);
}
if (data.size() > 0) {
uint8_t dataType = data[0]->getTagType();
for (uint32_t i=1; i<data.size(); i++) {
if (data[i]->getTagType() != dataType) {
return ErrorOr<List*>(true, ErrorCodes::MIXED_TYPES, nullptr);
}
}
}
return ErrorOr<List*>(new List(name, data));
}
List::~List() {
for (uint64_t i=0; i<this->tags.size(); i++) {
delete this->tags.at(i);
}
}
uint8_t List::getContainedType() {
return this->containedType;
}
ErrorOrVoid List::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
if (this->containedType == TagType::INVALID) {
return ErrorOrVoid(true, ErrorCodes::INVALID_TYPE);
}
rawData->push_back(this->containedType);
// 32 bit signed integer max
if (this->tags.size() > 0x7FFFFFFF) {
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
Helper::writeInt32(rawData, this->tags.size());
// unsigned integer bc of compiler warning (shouldn't matter)
for (uint32_t i=0; i<this->tags.size(); i++) {
ErrorOrVoid result = this->tags.at(i)->serializeWithoutHeader(rawData);
if (result.isError) {
return result;
}
}
return ErrorOrVoid();
}
ErrorOr<Generic*> List::getElementPointer(uint64_t position) {
if (this->tags.size() <= position) {
return ErrorOr<Generic*>(true, ErrorCodes::OUT_OF_RANGE);
}
return ErrorOr<Generic*>(this->tags.at(position));
}
ErrorOrVoid List::setElementPointerAt(uint64_t position, Generic* pointer) {
if (this->tags.size() <= position) {
2022-10-09 10:18:20 +02:00
delete pointer;
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
if (pointer->getTagType() != this->containedType) {
2022-10-09 10:18:20 +02:00
delete pointer;
return ErrorOrVoid(true, ErrorCodes::INVALID_TYPE);
}
delete this->tags[position];
this->tags[position] = pointer;
return ErrorOrVoid();
}
ErrorOrVoid List::appendPointer(Generic* pointer) {
if (pointer->getTagType() != this->containedType) {
return ErrorOrVoid(true, ErrorCodes::INVALID_TYPE);
}
this->tags.push_back(pointer);
return ErrorOrVoid();
}
ErrorOrVoid List::deleteElement(uint64_t position) {
if (position >= this->tags.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
delete this->tags[position];
this->tags.erase(this->tags.begin()+position);
return ErrorOrVoid();
}
uint64_t List::length() {
return this->tags.size();
}
Compound::Compound() {
this->type = TagType::COMPOUND;
this->endPointer = new End();
}
Compound::Compound(tiny_utf8::string name) {
this->type = TagType::COMPOUND;
this->name = name;
this->endPointer = new End();
}
Compound::Compound(tiny_utf8::string name, std::vector<Generic*> data) {
this->type = TagType::COMPOUND;
this->name = name;
this->tags = data;
this->endPointer = new End();
}
ErrorOr<Compound*> Compound::constructWithData(tiny_utf8::string name, std::vector<Generic*> data) {
if (data.size() > 0) {
for (uint64_t i=0; i<data.size(); i++) {
if (data[i]->getTagType() == TagType::END && i != data.size()-1) {
return ErrorOr<Compound*>(true, ErrorCodes::NOT_ALLOWED, nullptr);
}
}
if (data[data.size()-1]->getTagType() == TagType::END) {
return ErrorOr<Compound*>(new Compound(name, std::vector(data.begin(), data.end()-1)));
}
}
return ErrorOr<Compound*>(new Compound(name, data));
}
Compound::~Compound() {
for (uint64_t i=0; i<this->tags.size(); i++) {
delete this->tags.at(i);
}
delete this->endPointer;
}
ErrorOrVoid Compound::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
for (uint64_t i=0; i<this->tags.size(); i++) {
ErrorOrVoid result = this->tags.at(i)->serialize(rawData);
if (result.isError) {
return result;
}
}
this->endPointer->serialize(rawData);
return ErrorOrVoid();
}
ErrorOr<Generic*> Compound::getElementPointer(uint64_t position) {
if (position > this->tags.size()) {
return ErrorOr<Generic*>(true, ErrorCodes::OUT_OF_RANGE);
}
if (position == this->tags.size()) {
return this->endPointer;
}
return ErrorOr<Generic*>(this->tags.at(position));
}
ErrorOrVoid Compound::setElementPointerAt(uint64_t position, Generic* pointer) {
if (position == this->tags.size() || pointer->getTagType() == TagType::END) {
if (position == this->tags.size() && pointer->getTagType() == TagType::END) {
delete pointer;
// do nothing, already have one of those
return ErrorOrVoid();
} else {
delete pointer;
// End tags may only go at the end and
// the end may only hold an end tag.
return ErrorOrVoid(true, ErrorCodes::NOT_ALLOWED);
}
}
if (position > this->tags.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
delete this->tags[position];
this->tags[position] = pointer;
return ErrorOrVoid();
}
ErrorOrVoid Compound::appendPointer(Generic* pointer) {
if (pointer->getTagType() == TagType::END) {
2022-10-14 19:22:05 +02:00
delete pointer;
return ErrorOrVoid(true, ErrorCodes::NOT_ALLOWED);
}
this->tags.push_back(pointer);
return ErrorOrVoid();
}
ErrorOrVoid Compound::deleteElement(uint64_t position) {
// built-in end tag
if (position == this->tags.size()) {
return ErrorOrVoid(true, ErrorCodes::NOT_ALLOWED);
}
if (position > this->tags.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
delete this->tags[position];
this->tags.erase(this->tags.begin()+position);
return ErrorOrVoid();
}
uint64_t Compound::length() {
// account for built-in end tag
return this->tags.size()+1;
}
Int32Array::Int32Array() {
this->type = TagType::INT32_ARRAY;
}
Int32Array::Int32Array(tiny_utf8::string name, std::vector<int32_t> data) {
this->type = TagType::INT32_ARRAY;
this->name = name;
this->data = data;
}
Int32Array::Int32Array(tiny_utf8::string name, uint64_t length, int32_t data[]) {
this->type = TagType::INT32_ARRAY;
this->name = name;
this->data = std::vector(data, data+length);
}
ErrorOrVoid Int32Array::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt32Array(rawData, this->data);
return ErrorOrVoid();
}
std::vector<int32_t> Int32Array::getData() {
return this->data;
}
ErrorOr<int32_t> Int32Array::getValue(uint64_t position) {
if (position >= this->data.size()) {
return ErrorOr<int32_t>(true, ErrorCodes::OUT_OF_RANGE);
}
return ErrorOr<int32_t>(this->data.at(position));
}
void Int32Array::setData(std::vector<int32_t> newData) {
this->data = newData;
}
ErrorOrVoid Int32Array::setValue(uint64_t position, int32_t value) {
if (position >= this->data.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data[position] = value;
return ErrorOrVoid();
}
uint64_t Int32Array::length() {
return this->data.size();
}
void Int32Array::addElement(int32_t element) {
this->data.push_back(element);
}
ErrorOrVoid Int32Array::removeElement(uint64_t position) {
if (position >= this->data.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data.erase(this->data.begin()+position);
return ErrorOrVoid();
}
Int64Array::Int64Array() {
this->type = TagType::INT64_ARRAY;
}
Int64Array::Int64Array(tiny_utf8::string name, std::vector<int64_t> data) {
this->type = TagType::INT64_ARRAY;
this->name = name;
this->data = data;
}
Int64Array::Int64Array(tiny_utf8::string name, uint64_t length, int64_t data[]) {
this->type = TagType::INT64_ARRAY;
this->name = name;
this->data = std::vector(data, data+length);
}
ErrorOrVoid Int64Array::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt64Array(rawData, this->data);
return ErrorOrVoid();
}
std::vector<int64_t> Int64Array::getData() {
return this->data;
}
ErrorOr<int64_t> Int64Array::getValue(uint64_t position) {
if (position >= this->data.size()) {
return ErrorOr<int64_t>(true, ErrorCodes::OUT_OF_RANGE);
}
return ErrorOr<int64_t>(this->data[position]);
}
void Int64Array::setData(std::vector<int64_t> newData) {
this->data = newData;
}
ErrorOrVoid Int64Array::setValue(uint64_t position, int64_t value) {
if (position >= this->data.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data[position] = value;
return ErrorOrVoid();
}
uint64_t Int64Array::length() {
return this->data.size();
}
void Int64Array::addElement(int64_t element) {
this->data.push_back(element);
}
ErrorOrVoid Int64Array::removeElement(uint64_t position) {
if (position >= this->data.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data.erase(this->data.begin()+position);
return ErrorOrVoid();
}
2022-08-03 20:31:12 +02:00
}
2022-10-15 23:05:26 +02:00
// the same comment about blindly passing up error codes applies to this function
// FIXME: memory leak when returning errors
ErrorOr<std::vector<Tag::Generic*>> deserializeRawListContents(uint8_t data[], uint64_t dataSize, uint64_t initialPosition, uint64_t* processedDataSize) {
std::vector<Tag::Generic*> contents;
ErrorOr<std::vector<Tag::Generic*>> returnValue;
2022-10-15 23:05:26 +02:00
// get contained data length by reading it manually because
// the function that does it normally can't deal with
// headerless tags
//
// add one byte to position to skip the type byte
ErrorOr<int32_t> elementCount = Helper::readInt32(data, dataSize, initialPosition+1);
if (elementCount.isError) {
// this is before the creation of any pointers so we can just return
// without using the returnError label at the end of this function
2022-10-15 23:05:26 +02:00
return ErrorOr<std::vector<Tag::Generic*>>(true, elementCount.errorCode);
}
uint8_t contentType = data[initialPosition];
// contained type byte + 4 length bytes = 5
*processedDataSize = 5;
switch (contentType) {
case TagType::END: {
// everything except content has been touched at this point
// and a list of end tags has no content that could be read
for (int32_t i=0; i<elementCount.value; i++) {
contents.push_back(new Tag::End());
}
break;
}
case TagType::INT8: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<int8_t> nextInt = Helper::readInt8(data, dataSize, initialPosition+*processedDataSize);
if (nextInt.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextInt.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(new Tag::Int8("", nextInt.value));
*processedDataSize += 1;
}
break;
}
case TagType::INT16: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<int16_t> nextInt = Helper::readInt16(data, dataSize, initialPosition+*processedDataSize);
if (nextInt.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextInt.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(new Tag::Int16("", nextInt.value));
*processedDataSize += 2;
}
break;
}
case TagType::INT32: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<int32_t> nextInt = Helper::readInt32(data, dataSize, initialPosition+*processedDataSize);
if (nextInt.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextInt.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(new Tag::Int32("", nextInt.value));
*processedDataSize += 4;
}
break;
}
case TagType::FLOAT: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<float> nextFloat = Helper::readFloat(data, dataSize, initialPosition+*processedDataSize);
if (nextFloat.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextFloat.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(new Tag::Float("", nextFloat.value));
*processedDataSize += 4;
}
break;
}
case TagType::INT64: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<int64_t> nextInt = Helper::readInt64(data, dataSize, initialPosition+*processedDataSize);
if (nextInt.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextInt.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(new Tag::Int64("", nextInt.value));
*processedDataSize += 8;
}
break;
}
case TagType::DOUBLE: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<double> nextDouble = Helper::readDouble(data, dataSize, initialPosition+*processedDataSize);
if (nextDouble.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextDouble.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(new Tag::Double("", nextDouble.value));
*processedDataSize += 8;
}
break;
}
case TagType::INT8_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int8_t>> nextArray = Helper::readInt8Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextArray.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(new Tag::Int8Array("", nextArray.value));
*processedDataSize += (uint64_t) nextArray.value.size();
}
break;
}
case TagType::STRING: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<tiny_utf8::string> nextString = Helper::readString(data, dataSize, initialPosition+*processedDataSize);
if (nextString.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextString.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(new Tag::String("", nextString.value));
// this cannot be an error because it just got read
int16_t nextStringSize = Helper::readInt16(data, dataSize, initialPosition+*processedDataSize).value;
*processedDataSize += (uint64_t) nextStringSize + 2;
}
break;
}
case TagType::LIST: {
uint64_t* containedDataSize = new uint64_t;
for (int32_t i=0; i<elementCount.value; i++) {
*containedDataSize = 0;
ErrorOr<std::vector<Tag::Generic*>> nextListContents = deserializeRawListContents(data, dataSize, initialPosition+*processedDataSize, containedDataSize);
if (nextListContents.isError) {
delete containedDataSize;
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextListContents.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(Tag::List::constructWithData("", nextListContents.value).value);
2022-10-15 23:05:26 +02:00
*processedDataSize += *containedDataSize;
}
delete containedDataSize;
break;
}
case TagType::COMPOUND: {
uint64_t* containedDataSize = new uint64_t;
for (int32_t i=0; i<elementCount.value; i++) {
*containedDataSize = 0;
ErrorOr<std::vector<Tag::Generic*>> nextCompoundData = deserialize(data, dataSize, initialPosition+*processedDataSize, containedDataSize);
if (nextCompoundData.isError) {
delete containedDataSize;
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextCompoundData.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(reinterpret_cast<Tag::Generic*>(Tag::Compound::constructWithData("", nextCompoundData.value).value));
2022-10-15 23:05:26 +02:00
*processedDataSize += *containedDataSize;
}
delete containedDataSize;
break;
}
case TagType::INT32_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int32_t>> nextArray = Helper::readInt32Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextArray.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(new Tag::Int32Array("", nextArray.value));
*processedDataSize += (uint64_t) nextArray.value.size() * 4;
}
break;
}
case TagType::INT64_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int64_t>> nextArray = Helper::readInt64Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextArray.errorCode);
goto returnError;
2022-10-15 23:05:26 +02:00
}
contents.push_back(new Tag::Int64Array("", nextArray.value));
*processedDataSize += (uint64_t) nextArray.value.size() * 8;
}
break;
}
default:
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, ErrorCodes::INVALID_TYPE);
goto returnError;
2022-10-15 23:05:26 +02:00
}
return ErrorOr<std::vector<Tag::Generic*>>(contents);
returnError:
for (uint64_t i=0; i<contents.size(); i++) {
delete contents.at(i);
}
return returnValue;
2022-10-15 23:05:26 +02:00
}
// comment about blindly passing up error codes applies here
//
// The return value of this function is a vector of tags
// instead of a compound tag due to a spec extension that allows
// for any bare tag to be valid NBT data without a containing
// compound tag. This also just makes the implementation easier.
ErrorOr<std::vector<Tag::Generic*>> deserialize(uint8_t data[], uint64_t dataSize, uint64_t initialPosition, uint64_t* processedDataSize){
if (initialPosition >= dataSize) {
if (processedDataSize!=nullptr) *processedDataSize=0;
return ErrorOr<std::vector<Tag::Generic*>>(true, ErrorCodes::OUT_OF_RANGE);
// An interesting question at this point is whether we should
// consider empty input valid or invalid NBT data.
//
// The original spec says that the top-most tag is always a
// compound (or in more recent times, the Microsoft-commercialized
// in-game-purchase-enabling version also allows list tags)
// which automatically means that no data is invalid data...
// I don't see a reason why having a different tag as the top-most
// tag shouldn't be valid NBT in which case we have to face the
// question whether no data is invalid or just empty NBT data.
//
// This seems like a reasonable extension to the spec to me and
// it should be backwards compatible AFAIK.
//
// - BodgeMaster
}
std::vector<Tag::Generic*> tags = std::vector<Tag::Generic*>();
ErrorOr<std::vector<Tag::Generic*>> returnValue;
uint64_t currentPosition = initialPosition;
while (currentPosition<dataSize) {
ErrorOr<uint64_t> nextTagSize = Helper::totalTagSize(data, dataSize, currentPosition);
if (nextTagSize.isError) {
if (nextTagSize.errorCode == ErrorCodes::NOT_YET_KNOWN) {
ErrorOr<tiny_utf8::string> tagName = Helper::readString(data, dataSize, currentPosition+1);
if (tagName.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, tagName.errorCode);
goto returnNow;
}
// used seek to the start of the list's/compounds contents
//
// there is no way this is an error bc it gets
// checked while trying to parse the string above
int16_t nameSize = Helper::readInt16(data, dataSize, currentPosition+1).value;
uint64_t* processedTagSize = new uint64_t;
*processedTagSize = 0;
if (data[currentPosition]==TagType::LIST) {
// type byte + two name size bytes = 3
ErrorOr<std::vector<Tag::Generic*>> listData = deserializeRawListContents(data, dataSize, currentPosition + (uint64_t) nameSize + 3, processedTagSize);
if (listData.isError) {
delete processedTagSize;
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, listData.errorCode);
goto returnNow;
}
tags.push_back(Tag::List::constructWithData(tagName.value, listData.value).value);
2022-10-15 23:05:26 +02:00
*processedTagSize += (uint64_t) nameSize + 3;
}
if (data[currentPosition]==TagType::COMPOUND) {
// type byte + two name size bytes = 3
ErrorOr<std::vector<Tag::Generic*>> compoundData = deserialize(data, dataSize, currentPosition + (uint64_t) nameSize + 3, processedTagSize);
if (compoundData.isError) {
delete processedTagSize;
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, compoundData.errorCode);
goto returnNow;
}
tags.push_back(reinterpret_cast<Tag::Generic*>(Tag::Compound::constructWithData(tagName.value, compoundData.value).value));
2022-10-15 23:05:26 +02:00
*processedTagSize += (uint64_t) nameSize + 3;
}
currentPosition += *processedTagSize;
delete processedTagSize;
continue;
}
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextTagSize.errorCode);
goto returnNow;
}
if (currentPosition + nextTagSize.value > dataSize) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, ErrorCodes::OVERRUN);
goto returnNow;
}
// recursion abort condition
if (data[currentPosition]==TagType::END) {
// not appending an end tag as it is built into
// the compound anyway
currentPosition++;
returnValue = ErrorOr<std::vector<Tag::Generic*>>(tags);
goto returnNow;
}
// nameSize cannot be an error here bc it got checked in
// nextTagSize() already
int16_t nameSize = Helper::readInt16(data, dataSize, currentPosition+1).value;
ErrorOr<tiny_utf8::string> name = Helper::readString(data, dataSize, currentPosition+1);
if (name.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, name.errorCode);
goto returnNow;
}
// Overrun / out of range errors have already been ruled out by
// checking the tag size against the total amount of data.
switch (data[currentPosition]) {
case TagType::INT8: {
int8_t content = Helper::readInt8(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int8(name.value, content));
break;
}
case TagType::INT16: {
int16_t content = Helper::readInt16(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int16(name.value, content));
break;
}
case TagType::INT32: {
int32_t content = Helper::readInt32(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int32(name.value, content));
break;
}
case TagType::INT64: {
int64_t content = Helper::readInt64(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int64(name.value, content));
break;
}
case TagType::FLOAT: {
float content = Helper::readFloat(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Float(name.value, content));
break;
}
case TagType::DOUBLE: {
double content = Helper::readDouble(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Double(name.value, content));
break;
}
case TagType::INT8_ARRAY: {
std::vector<int8_t> content = Helper::readInt8Array(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int8Array(name.value, content));
break;
}
case TagType::STRING: {
ErrorOr<tiny_utf8::string> content = Helper::readString(data, dataSize, currentPosition+nameSize+3);
if (content.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, content.errorCode);
goto returnNow;
}
tags.push_back(new Tag::String(name.value, content.value));
break;
}
case TagType::INT32_ARRAY: {
std::vector<int32_t> content = Helper::readInt32Array(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int32Array(name.value, content));
break;
}
case TagType::INT64_ARRAY: {
std::vector<int64_t> content = Helper::readInt64Array(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int64Array(name.value, content));
break;
}
default: {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, ErrorCodes::UNKNOWN);
goto returnNow;
}
}
currentPosition += nextTagSize.value;
}
returnValue = ErrorOr<std::vector<Tag::Generic*>>(tags);
goto returnNow;
returnNow:
if (processedDataSize!=nullptr) {
*processedDataSize = currentPosition-initialPosition;
}
if (returnValue.isError) {
for (uint64_t i=0; i<tags.size(); i++) {
delete tags[i];
}
}
return returnValue;
}
bool validateRawListContents(uint8_t data[], uint64_t dataSize, uint64_t initialPosition, uint64_t* processedDataSize) {
// get contained data length by reading it manually because
// the function that does it normally can't deal with
// headerless tags
//
// add one byte to position to skip the type byte
ErrorOr<int32_t> elementCount = Helper::readInt32(data, dataSize, initialPosition+1);
if (elementCount.isError) {
return false;
}
uint8_t contentType = data[initialPosition];
// contained type byte + 4 length bytes = 5
*processedDataSize = 5;
switch (contentType) {
case TagType::END:
// everything except content has been touched at this point
// and a list of end tags has no content
return true;
case TagType::INT8: {
*processedDataSize += (uint64_t) elementCount.value;
return initialPosition + *processedDataSize < dataSize;
}
case TagType::INT16: {
*processedDataSize += (uint64_t) elementCount.value * 2;
return initialPosition + *processedDataSize < dataSize;
}
case TagType::INT32:
case TagType::FLOAT: {
*processedDataSize += (uint64_t) elementCount.value * 4;
return initialPosition + *processedDataSize < dataSize;
}
case TagType::INT64:
case TagType::DOUBLE: {
*processedDataSize += (uint64_t) elementCount.value * 8;
return initialPosition + *processedDataSize < dataSize;
}
case TagType::INT8_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int8_t>> nextArray = Helper::readInt8Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
return false;
}
*processedDataSize += (uint64_t) nextArray.value.size();
}
return true;
}
case TagType::STRING: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<tiny_utf8::string> nextString = Helper::readString(data, dataSize, initialPosition+*processedDataSize);
if (nextString.isError) {
return false;
}
// this cannot be an error because it just got checked
int16_t nextStringSize = Helper::readInt16(data, dataSize, initialPosition+*processedDataSize).value;
*processedDataSize += (uint64_t) nextStringSize + 2;
}
return true;
}
case TagType::LIST: {
uint64_t* containedDataSize = new uint64_t;
for (int32_t i=0; i<elementCount.value; i++) {
*containedDataSize = 0;
if (validateRawListContents(data, dataSize, initialPosition+*processedDataSize, containedDataSize)) {
*processedDataSize += *containedDataSize;
} else {
delete containedDataSize;
return false;
}
}
delete containedDataSize;
return true;
}
case TagType::COMPOUND: {
uint64_t* containedDataSize = new uint64_t;
for (int32_t i=0; i<elementCount.value; i++) {
*containedDataSize = 0;
if (validateRawNBTData(data, dataSize, initialPosition+*processedDataSize, containedDataSize)) {
*processedDataSize += *containedDataSize;
} else {
delete containedDataSize;
return false;
}
}
delete containedDataSize;
return true;
}
case TagType::INT32_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int32_t>> nextArray = Helper::readInt32Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
return false;
}
*processedDataSize += (uint64_t) nextArray.value.size() * 4;
}
return true;
}
case TagType::INT64_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int64_t>> nextArray = Helper::readInt64Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
return false;
}
*processedDataSize += (uint64_t) nextArray.value.size() * 8;
}
return true;
}
default:
return false;
}
}
bool validateRawNBTData(uint8_t data[], uint64_t dataSize, uint64_t initialPosition, uint64_t* processedDataSize){
if (initialPosition >= dataSize) {
// Yes, this *could* return an instance of ErrorOr with
// ErrorCodes::OVERRUN but we only care to know if what is
// at that position is valid NBT which it clearly isn't according
// to the original spec.
if (processedDataSize!=nullptr) *processedDataSize=0;
return false;
// An interesting question at this point is whether we should
// consider empty input valid or invalid NBT data.
//
// The original spec says that the top-most tag is always a
// compound (or in more recent times, the Microsoft-commercialized
// in-game-purchase-enabling version also allows list tags)
// which automatically means that no data is invalid data...
// I don't see a reason why having a different tag as the top-most
// tag shouldn't be valid NBT in which case we have to face the
// question whether no data is invalid or just empty NBT data.
//
// This seems like a reasonable extension to the spec to me and
// it should be backwards compatible AFAIK.
//
// - BodgeMaster
}
bool returnValue;
uint64_t currentPosition = initialPosition;
while (currentPosition<dataSize) {
ErrorOr<uint64_t> nextTagSize = Helper::totalTagSize(data, dataSize, currentPosition);
if (nextTagSize.isError) {
if (nextTagSize.errorCode == ErrorCodes::NOT_YET_KNOWN) {
// attempt parsing the name
ErrorOr<tiny_utf8::string> tagName = Helper::readString(data, dataSize, currentPosition+1);
if (tagName.isError) {
returnValue = false;
goto returnNow;
}
// used seek to the start of the list's/compounds contents
//
// there is no way this is an error bc it gets
// checked while trying to parse the string above
int16_t nameSize = Helper::readInt16(data, dataSize, currentPosition+1).value;
uint64_t* processedTagSize = new uint64_t;
*processedTagSize = 0;
if (data[currentPosition]==TagType::LIST) {
// type byte + two name size bytes = 3
if (!validateRawListContents(data, dataSize, currentPosition + (uint64_t) nameSize + 3, processedTagSize)) {
delete processedTagSize;
returnValue = false;
goto returnNow;
}
*processedTagSize += (uint64_t) nameSize + 3;
}
if (data[currentPosition]==TagType::COMPOUND) {
// type byte + two name size bytes = 3
if (!validateRawNBTData(data, dataSize, currentPosition + (uint64_t) nameSize + 3, processedTagSize)) {
delete processedTagSize;
returnValue = false;
goto returnNow;
}
*processedTagSize += (uint64_t) nameSize + 3;
}
currentPosition += *processedTagSize;
delete processedTagSize;
continue;
}
returnValue = false;
goto returnNow;
}
if (currentPosition + nextTagSize.value > dataSize) {
returnValue = false;
goto returnNow;
}
// recursion abort condition
if (data[currentPosition]==TagType::END) {
currentPosition++;
returnValue = true;
goto returnNow;
}
// nameSize cannot be an error here bc it got checked in
// nextTagSize() already
int16_t nameSize = Helper::readInt16(data, dataSize, currentPosition+1).value;
// attempt parsing the name
//
// This shouldn't matter too much here as the only error condition
// the parser function deals with rn is an overrun which is already
// being guarded against with
// if (currentPosition + nextTagSize.value > dataSize) {
// returnValue = false;
// goto returnNow;
// }
// It might, however, turn out to be a useful check in the future.
ErrorOr<tiny_utf8::string> name = Helper::readString(data, dataSize, currentPosition+1);
if (name.isError) {
returnValue = false;
goto returnNow;
}
switch (data[currentPosition]) {
case TagType::INT8:
case TagType::INT16:
case TagType::INT32:
case TagType::INT64:
case TagType::FLOAT:
case TagType::DOUBLE:
case TagType::INT8_ARRAY:
break;
case TagType::STRING: {
// attempt parsing the content
//
// This shouldn't matter too much here as the only
// error condition the parser function deals with rn is
// an overrun which is already being guarded against with
// if (currentPosition + nextTagSize.value > dataSize) {
// returnValue = false;
// goto returnNow;
// }
// It might, however, turn out to be a useful check
// in the future.
//
// type byte + two name size bytes = 3
ErrorOr<tiny_utf8::string> content = Helper::readString(data, dataSize, currentPosition+nameSize+3);
if (content.isError) {
returnValue = false;
goto returnNow;
}
break;
}
case TagType::INT32_ARRAY:
case TagType::INT64_ARRAY:
break;
default:
returnValue = false;
goto returnNow;
}
currentPosition += nextTagSize.value;
}
returnValue = true;
goto returnNow;
returnNow:
if (processedDataSize!=nullptr) {
*processedDataSize = currentPosition-initialPosition;
}
return returnValue;
2022-06-27 04:50:32 +02:00
}
}