lib/nbt: Start implementing NBT data types, make NBT::Helper::writeString() return an error instead of aborting the program

The former is finally some progress on getting NBT going,
though at this point it is all unvalidated work.
For all I know it might cause your computer to burst into flames,
grow an arm and a leg and an eye, and attack you with a sword.
But probably, it won’t do that and instead start working after I
have debugged it so much that I wish I could attack my PC with a sword.

The latter is the implementation of a change I prepared in a previous commit
when i added ErrorOrVoid.
Soda
BodgeMaster 2022-09-26 03:11:44 +02:00
parent 398321e415
commit 10b1d9fa0c
6 changed files with 345 additions and 62 deletions

View File

@ -36,7 +36,6 @@ COMPILE_COMMANDS=(
"$CXX_WITH_FLAGS src/test/nbt_read_write_helpers.cpp -I./include -Lbin/lib -l:nbt.so -l:javacompat.so -o bin/test/nbt_read_write_helpers"
"$CXX_WITH_FLAGS src/test/cli_argument_parser.cpp -Lbin/lib -l:cli.so -o bin/test/cli_argument_parser"
"$CXX_WITH_FLAGS src/test/javacompat.cpp -I./include -Lbin/lib -l:javacompat.so -o bin/test/javacompat"
"$CXX_WITH_FLAGS src/test/nbt_write_string_failure_mode.cpp -I./include -Lbin/lib -l:nbt.so -l:javacompat.so -o bin/test/nbt_write_string_failure_mode"
"$CXX_WITH_FLAGS src/test/nbt_tags.cpp -I./include -Lbin/lib -l:nbt.so -l:javacompat.so -o bin/test/nbt_tags"
"$CXX_WITH_FLAGS src/test/nbt_size_helpers.cpp -I./include -Lbin/lib -l:nbt.so -l:javacompat.so -o bin/test/nbt_size_helpers"
)

View File

@ -17,7 +17,6 @@
#include <cstdint>
#include <vector>
#include <tinyutf8/tinyutf8.h>
#include <iostream>
#include "nbt.hpp"
#include "error.hpp"
@ -360,13 +359,13 @@ namespace NBT {
}
}
void writeString(std::vector<uint8_t>* destination, tiny_utf8::string data) {
ErrorOrVoid writeString(std::vector<uint8_t>* destination, tiny_utf8::string data) {
ErrorOr<std::vector<uint8_t>> exportedString = JavaCompat::exportJavaString(data);
if(exportedString.isError){
std::cerr << "NBT::Helpers::writeString encountered an error: " << (int) exportedString.errorCode << std::endl;
std::abort();
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
*destination = exportedString.value;
return ErrorOrVoid();
}
void writeInt32Array(std::vector<uint8_t>* destination, std::vector<int32_t> data) {
@ -543,15 +542,241 @@ namespace NBT {
}
}
// generic class that all tag types are derived from
template <typename T>
Tag<T>::Tag() {
namespace Tag {
Generic::Generic() {
this->type = TagType::INVALID;
}
template <typename T>
ErrorOr<std::vector<uint8_t>> Tag<T>::toRawData() {
return ErrorOr<std::vector<uint8_t>>(true, ErrorCodes::INVALID_TYPE);
Generic::~Generic() {}
ErrorOrVoid Generic::toRawData([[maybe_unused]] std::vector<uint8_t>* rawData) {
return ErrorOrVoid(true, ErrorCodes::INVALID_TYPE);
}
uint8_t Generic::getTagType(){
return this->type;
}
End::End() {
this->type = TagType::END;
}
ErrorOrVoid End::toRawData(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::toRawData(std::vector<uint8_t>* rawData) {
rawData->push_back(this->type);
if (Helper::writeString(rawData, this->name).isError) {
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
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::toRawData(std::vector<uint8_t>* rawData) {
rawData->push_back(this->type);
if (Helper::writeString(rawData, this->name).isError) {
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
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::toRawData(std::vector<uint8_t>* rawData) {
rawData->push_back(this->type);
if (Helper::writeString(rawData, this->name).isError) {
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
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::toRawData(std::vector<uint8_t>* rawData) {
rawData->push_back(this->type);
if (Helper::writeString(rawData, this->name).isError) {
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
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::toRawData(std::vector<uint8_t>* rawData) {
rawData->push_back(this->type);
if (Helper::writeString(rawData, this->name).isError) {
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
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::toRawData(std::vector<uint8_t>* rawData) {
rawData->push_back(this->type);
if (Helper::writeString(rawData, this->name).isError) {
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
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();
}
}
bool validateRawListContents(uint8_t data[], uint64_t dataSize, uint64_t initialPosition, uint64_t* processedDataSize) {

View File

@ -38,6 +38,8 @@
#include <cstdint>
#include <vector>
#include <tinyutf8/tinyutf8.h>
#include <mutex>
#include "error.hpp"
namespace NBT {
@ -61,7 +63,7 @@ namespace NBT {
void writeDouble(std::vector<uint8_t>* destination, double data);
void writeInt8Array(std::vector<uint8_t>* destination, std::vector<int8_t> data);
void writeInt8Array(std::vector<uint8_t>* destination, int8_t data[], uint32_t dataSize);
void writeString(std::vector<uint8_t>* destination, tiny_utf8::string data);
ErrorOrVoid writeString(std::vector<uint8_t>* destination, tiny_utf8::string data);
void writeInt32Array(std::vector<uint8_t>* destination, std::vector<int32_t> data);
void writeInt32Array(std::vector<uint8_t>* destination, int32_t data[], uint32_t dataSize);
void writeInt64Array(std::vector<uint8_t>* destination, std::vector<int64_t> data);
@ -87,20 +89,103 @@ namespace NBT {
const uint8_t INT64_ARRAY= 12;
// This is a workaround that's not part of the spec.
const uint8_t INVALID = 255;
// This class is used as a placeholder for implementing the end tag.
class End {};
}
// generic class that all tag types are derived from
template <typename T>
struct Tag {
const uint8_t type = TagType::INVALID;
T* containedData;
namespace Tag {
Tag();
ErrorOr<std::vector<uint8_t>> toRawData();
class Generic {
protected:
std::mutex mutex;
uint8_t type;
public:
tiny_utf8::string name;
Generic();
virtual ~Generic();
virtual ErrorOrVoid toRawData(std::vector<uint8_t>* rawData);
uint8_t getTagType();
};
class End: public Generic {
public:
End();
ErrorOrVoid toRawData(std::vector<uint8_t>* rawData) override;
};
class Int8: public Generic {
private:
int8_t value;
public:
Int8();
Int8(tiny_utf8::string name, int8_t value);
ErrorOrVoid toRawData(std::vector<uint8_t>* rawData) override;
int8_t getValue();
void setValue(int8_t value);
};
class Int16: public Generic {
private:
int16_t value;
public:
Int16();
Int16(tiny_utf8::string name, int16_t value);
ErrorOrVoid toRawData(std::vector<uint8_t>* rawData) override;
int16_t getValue();
void setValue(int16_t value);
};
class Int32: public Generic {
private:
int32_t value;
public:
Int32();
Int32(tiny_utf8::string name, int32_t value);
ErrorOrVoid toRawData(std::vector<uint8_t>* rawData) override;
int32_t getValue();
void setValue(int32_t value);
};
class Int64: public Generic {
private:
int64_t value;
public:
Int64();
Int64(tiny_utf8::string name, int64_t value);
ErrorOrVoid toRawData(std::vector<uint8_t>* rawData) override;
int64_t getValue();
void setValue(int64_t value);
};
class Float: public Generic {
private:
float value;
public:
Float();
Float(tiny_utf8::string name, float value);
ErrorOrVoid toRawData(std::vector<uint8_t>* rawData) override;
float getValue();
void setValue(float value);
};
class Double: public Generic {
private:
double value;
public:
Double();
Double(tiny_utf8::string name, double value);
ErrorOrVoid toRawData(std::vector<uint8_t>* rawData) override;
double getValue();
void setValue(double value);
};
}
bool validateRawNBTData(uint8_t data[], uint64_t dataSize, uint64_t initialPosition=0, uint64_t* processedDataSize=nullptr);
}

View File

@ -573,6 +573,11 @@ int main(){
NBT::Helper::writeString(exportedString, normalString);
ASSERT(javaStdString1 == *exportedString);
//check that we get an error when trying to write a string that is too long
std::string overrunString = std::string(0xFFFFF, '.');
ASSERT(NBT::Helper::writeString(exportedString, tiny_utf8::string(overrunString)).isError);
ASSERT(NBT::Helper::writeString(exportedString, tiny_utf8::string(overrunString)).errorCode == ErrorCodes::OVERRUN);
std::cout << "Passed writeString NBT helper test." << std::endl;
return 0;

View File

@ -30,15 +30,15 @@ int main(){
std::cout << "################################################################################" << std::endl;
//Byte tag constructor test
uint8_t bytetest[] = {0x01, 0x00, 0x02, 0x68, 0x69, 0x32};
NBT::Byte byte = NBT::Byte(bytetest);
//uint8_t bytetest[] = {0x01, 0x00, 0x02, 0x68, 0x69, 0x32};
//NBT::Byte byte = NBT::Byte(bytetest);
ASSERT(byte.tagType == 1);
ASSERT(byte.nameSize == 2);
ASSERT(byte.content = 0x32);
ASSERT(byte.name == tiny_utf8::string("hi"));
//ASSERT(byte.tagType == 1);
//ASSERT(byte.nameSize == 2);
//ASSERT(byte.content = 0x32);
//ASSERT(byte.name == tiny_utf8::string("hi"));
std::cout << "Passed Byte Tag constructor test." << std::endl;
//std::cout << "Passed Byte Tag constructor test." << std::endl;
return 0;
}

View File

@ -1,31 +0,0 @@
// 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 <string>
#include <vector>
#include <iostream>
#include "../lib/nbt.hpp"
#include "../lib/error.hpp"
#include "../lib/javacompat.hpp"
int main() {
std::cout << "================================================================================" << std::endl;
std::cout << "NBT write string helper failure mode test" << std::endl;
std::cout << "================================================================================" << std::endl;
std::cout << "This is supposed to abort." << std::endl;
std::vector<uint8_t>* exportedString = new std::vector<uint8_t>();
std::string overrunString = std::string(0xFFFFF, '.');
NBT::Helper::writeString(exportedString, tiny_utf8::string(overrunString));
}