Compare commits

...

5 Commits

Author SHA1 Message Date
BodgeMaster d402d4e057 lib/nbt: Move the functions for getting tag sizes into the helper namespace, give up on handling lists the same as all other tags
I tried dealing with lists in the same way as with other more basic tags
but came to the conclusion that this is most likely not feasible in the same
way that it is not feasible for compounds. It would require a mini-parser
that can deal with all sorts of tags (including nested lists and compounds).

Instead, an approach more similar to the recursion for compound tags will
be used (using its own function to deal with the missing tag headers ofc).
2022-08-11 07:49:31 +02:00
BodgeMaster f2ae84c062 lib/nbt: Various minor fixes to get the program to compile properly 2022-08-11 06:57:42 +02:00
BodgeMaster 1ee8d47e2c lib/nbt: remove a function used to get the next tag type which introduced unnecessary complexity 2022-08-11 06:56:06 +02:00
BodgeMaster 3741d844f1 Environment: don't unset PROJECT_BASE_DIR which is needed for the aliases to work properly 2022-08-11 06:53:34 +02:00
BodgeMaster 72dffde7c9 test/nbt*: rename files, move byte tag object test from helper test file into its own file 2022-08-11 06:20:07 +02:00
6 changed files with 173 additions and 170 deletions

View File

@ -44,6 +44,4 @@ if [ -f "$PROJECT_BASE_DIR/.localenv.bashrc" ]; then
source "$PROJECT_BASE_DIR/.localenv.bashrc"
fi
unset PROJECT_BASE_DIR
echo "done."

View File

@ -370,6 +370,132 @@ namespace NBT {
writeInt64(destination, data[i]);
}
}
//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)
//
// 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 complex code.
//
// Regardinng 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 the tag headers
// of contained tags being absent so they may as well get their
// own function for this as well.
ErrorOr<uint64_t> nextTagTotalSize(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 separately
if (nextTag == TagType::COMPOUND || nextTag == TagType::LIST) return ErrorOr<uint64_t>(false, ErrorCodes::NOT_YET_KNOWN);
// deal with end tag before trying to access the name
if (nextTag == TagType::END) return ErrorOr<uint64_t>(1);
// get name size
ErrorOr<int16_t> nameSize = helper::readInt16(data, dataSize, currentPosition+1);
if (nameSize.isError) {
return ErrorOr<uint64_t>(true, nameSize.errorCode);
}
switch (nextTag) {
case TagType::INT8:
// type byte + name size + data byte -> 4 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+4);
case TagType::INT16:
// type byte + name size + 2 data bytes -> 5 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+5);
case TagType::INT32:
// type byte + name size + 4 data bytes -> 7 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+7);
case TagType::INT64:
// type byte + name size + 8 data bytes -> 11 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+11);
case TagType::FLOAT:
// type byte + name size + 4 data bytes -> 7 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+7);
case TagType::DOUBLE:
// type byte + name size + 8 data bytes -> 11 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+11);
case TagType::INT8_ARRAY: {
// type byte + name size + 4 size bytes -> 7 bytes
uint64_t totalSize = (uint64_t) nameSize.value+7;
// add size of actual data (1 byte per entry)
ErrorOr<int32_t> arraySize = helper::readInt32(data, dataSize, currentPosition+totalSize);
if (arraySize.isError) {
return ErrorOr<uint64_t>(true, arraySize.errorCode);
}
totalSize += (uint64_t) arraySize.value;
return ErrorOr<uint64_t>(totalSize);
}
case TagType::STRING: {
// type byte + name size + 2 size bytes -> 5 bytes
uint64_t totalSize = (uint64_t) nameSize.value+5;
// add size of actual data
ErrorOr<int16_t> stringSize = helper::readInt16(data, dataSize, currentPosition+totalSize);
if (stringSize.isError) {
return ErrorOr<uint64_t>(true, stringSize.errorCode);
}
totalSize += (uint64_t) stringSize.value;
return ErrorOr<uint64_t>(totalSize);
}
case TagType::INT32_ARRAY: {
// type byte + name size + 4 size bytes -> 7 bytes
uint64_t totalSize = (uint64_t) nameSize.value+7;
// add size of actual data (4 bytes per entry)
ErrorOr<int16_t> arraySize = helper::readInt16(data, dataSize, currentPosition+totalSize);
if (arraySize.isError) {
return ErrorOr<uint64_t>(true, arraySize.errorCode);
}
totalSize += (uint64_t) arraySize.value*4;
return ErrorOr<uint64_t>(totalSize);
}
case TagType::INT64_ARRAY: {
// type byte + name size + 4 size bytes -> 7 bytes
uint64_t totalSize = (uint64_t) nameSize.value+7;
// add size of actual data (8 bytes per entry)
ErrorOr<int16_t> arraySize = helper::readInt16(data, dataSize, currentPosition+totalSize);
if (arraySize.isError) {
return ErrorOr<uint64_t>(true, arraySize.errorCode);
}
totalSize += (uint64_t) arraySize.value*8;
return ErrorOr<uint64_t>(totalSize);
}
// unknown tag or parsing error
default:
return ErrorOr<uint64_t>(true, ErrorCodes::UNKNOWN);
}
}
ErrorOr<uint32_t> nextTagDataSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition){
uint8_t nextTag;
if (dataSize <= currentPosition) {
return ErrorOr<uint32_t>(true, ErrorCodes::OVERRUN);
} else {
nextTag = data[currentPosition];
}
// deal with compound tags separately
if (nextTag == TagType::COMPOUND) return ErrorOr<uint32_t>(true, ErrorCodes::NOT_YET_KNOWN);
// deal with end tag before trying to access the name
if (nextTag == TagType::END) return 0;
//TODO: implement for all the remaining types
// unknown tag or parsing error
return ErrorOr<uint32_t>(true, ErrorCodes::UNKNOWN);
}
}
//Tag constructors
@ -424,161 +550,6 @@ namespace NBT {
}
}
ErrorOr<uint8_t> nextTagType(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
if (dataSize <= currentPosition) {
return ErrorOr<uint8_t>(true, ErrorCodes::OVERRUN);
} else {
return ErrorOr<uint8_t>(data[currentPosition]);
}
}
//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)
ErrorOr<uint64_t> nextTagTotalSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
ErrorOr<uint8_t> nextTag = nextTagType(data, dataSize, currentPosition);
if (nextTag.isError) {
return ErrorOr<uint64_t>(true, nextTag.errorCode);
}
// deal with compound tags separately
if (nextTag.value == TagType::COMPOUND) return ErrorOr<uint64_t>(false, ErrorCodes::NOT_YET_KNOWN);
// deal with end tag before trying to access the name
if (nextTag.value == TagType::END) return ErrorOr<uint64_t>(1);
// get name size
ErrorOr<uint16_t> nameSize = (uint16_t) helper::readInt16(data, dataSize, currentPosition+1);
if (nameSize.isError) {
return ErrorOr<uint64_t>(true, nameSize.errorCode);
}
switch (nextTag.value) {
case TagType::INT8:
// type byte + name size + data byte -> 4 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+4);
case TagType::INT16:
// type byte + name size + 2 data bytes -> 5 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+5);
case TagType::INT32:
// type byte + name size + 4 data bytes -> 7 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+7);
case TagType::INT64:
// type byte + name size + 8 data bytes -> 11 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+11);
case TagType::FLOAT:
// type byte + name size + 4 data bytes -> 7 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+7);
case TagType::DOUBLE:
// type byte + name size + 8 data bytes -> 11 bytes
return ErrorOr<uint64_t>((uint64_t) nameSize.value+11);
case TagType::INT8_ARRAY:
// type byte + name size + 4 size bytes -> 7 bytes
uint64_t totalSize = (uint64_t) nameSize.value+7;
// add size of actual data (1 byte per entry)
ErrorOr<int32_t> arraySize = helper::readInt32(data, dataSize, currentPosition+totalSize);
if (arraySize.isError) {
return ErrorOr<uint64_t>(true, arraySize.errorCode);
}
totalSize += (uint64_t) arraySize.value;
return ErrorOr<uint64_t>(totalSize);
case TagType::STRING:
// type byte + name size + 2 size bytes -> 5 bytes
uint64_t totalSize = (uint64_t) nameSize.value+5;
// add size of actual data
ErrorOr<int16_t> stringSize = helper::readInt16(data, dataSize, currentPosition+totalSize);
if (stringSize.isError) {
return ErrorOr<uint64_t>(true, stringSize.errorCode);
}
totalSize += (uint64_t) stringSize.value;
return ErrorOr<uint64_t>(totalSize);
case TagType::LIST:
// type byte + name size + type prefix + 4 size bytes -> 8 bytes
uint64_t totalSize = (uint64_t) nameSize.value+8;
// determine size of actual data
ErrorOr<uint8_t> containedType = nextTagType(data, dataSize, currentPosition+totalSize-1);
if (containedType.isError) {
return ErrorOr<uint64_t>(true, containedType.errorCode);
}
ErrorOr<int16_t> listSize = helper::readInt16(data, dataSize, currentPosition+totalSize);
if (listSize.isError) {
return ErrorOr<uint64_t>(true, listSize.errorCode);
}
// Can we just multiply list size with data type size?
if (containedType.value == TagType::END || containedType.value == TagType::INT8 || containedType.value == TagType::INT16 || containedType.value == TagType::INT32 || containedType.value == TagType::INT64 || containedType.value == TagType::FLOAT || containedType.value == TagType::DOUBLE) {
uint8_t factor;
switch (containedType.value) {
case TagType::END:
factor = 1;
case TagType::INT8:
factor = 1;
case TagType::INT16:
factor = 2;
case TagType::INT32:
factor = 4;
case TagType::INT64:
factor = 8;
case TagType::FLOAT:
factor = 4;
case TagType::DOUBLE:
factor = 8;
default:
// How would you even get here?
return ErrorOr<uint64_t>(true, ErrorCodes::UNKNOWN);
}
totalSize += listSize*factor;
return ErrorOr<uint64_t>(totalSize);
} else {
if (containedType.value == TagType::COMPOUND || containedType.value == TagType::LIST) return ErrorOr<uint64_t>(false, ErrorCodes::NOT_YET_KNOWN);
//TODO: INT8_ARRAY, STRING, INT32_ARRAY, INT64_ARRAY
}
return ErrorOr<uint64_t>(true, ErrorCodes::UNKNOWN);
case TagType::INT32_ARRAY:
// type byte + name size + 4 size bytes -> 7 bytes
uint64_t totalSize = (uint64_t) nameSize.value+7;
// add size of actual data (4 bytes per entry)
ErrorOr<int16_t> arraySize = helper::readInt16(data, dataSize, currentPosition+totalSize);
if (arraySize.isError) {
return ErrorOr<uint64_t>(true, arraySize.errorCode);
}
totalSize += (uint64_t) arraySize.value*4;
return ErrorOr<uint64_t>(totalSize);
case TagType::INT64_ARRAY:
// type byte + name size + 4 size bytes -> 7 bytes
uint64_t totalSize = (uint64_t) nameSize.value+7;
// add size of actual data (8 bytes per entry)
ErrorOr<int16_t> arraySize = helper::readInt16(data, dataSize, currentPosition+totalSize);
if (arraySize.isError) {
return ErrorOr<uint64_t>(true, arraySize.errorCode);
}
totalSize += (uint64_t) arraySize.value*8;
return ErrorOr<uint64_t>(totalSize);
// fall-through in case of unknown tag or parsing error
default:
return ErrorOr<uint64_t>(true, ErrorCodes::UNKNOWN);
}
}
ErrorOr<uint32_t> nextTagDataSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition){
ErrorOr<uint8_t> nextTag = nexttagType(data, dataSize, currentPosition);
if (nextTag.isError) {
return ErrorOr<int64_t>(true, nextTag.errorCode);
}
// deal with compound tags separately
if (nextTag.value == TagType::COMPOUND) return ErrorOr<uint64_t>(true, ErrorCodes::NOT_YET_KNOWN);
// deal with end tag before trying to access the name
if (nextTag.value == TagType::END) return 0;
//TODO: implement for all the remaining types
// fall-through in case of unknown tag or parsing error
return ErrorOr<uint32_t>(true, ErrorCodes::UNKNOWN);
}
bool validateRawNBTData(uint8_t data[], uint64_t dataSize, uint64_t initialPosition){
//TODO: find out the size of the next tag
//TODO: consume tag

View File

@ -66,6 +66,9 @@ namespace NBT {
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);
void writeInt64Array(std::vector<uint8_t>* destination, int64_t data[], uint32_t dataSize);
ErrorOr<uint64_t> nextTagTotalSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition);
ErrorOr<uint32_t> nextTagDataSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition);
}
namespace TagType {
@ -110,8 +113,5 @@ namespace NBT {
bool validate(uint8_t data[]);
};
ErrorOr<uint8_t> nextTagType(uint8_t data[], uint64_t dataSize, uint64_t currentPosition);
ErrorOr<uint64_t> nextTagTotalSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition);
ErrorOr<uint32_t> nextTagDataSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition);
bool validateRawNBTData(uint8_t data[], int length, uint64_t initialPosition=0);
}

View File

@ -524,15 +524,5 @@ int main(){
std::cout << "Passed writeString NBT helper test." << std::endl;
//Byte tag constructor test
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"));
std::cout << "Passed Byte Tag constructor test." << std::endl;
return 0;
}

44
src/test/nbt_tags.cpp Normal file
View File

@ -0,0 +1,44 @@
// 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 <iostream>
#include <cstdint>
#include <vector>
#include <fstream>
#include "assert.hpp"
#include "../lib/nbt.hpp"
#include "../lib/error.hpp"
#include "../lib/javacompat.hpp"
int main(){
std::cout << "################################################################################" << std::endl;
std::cout << "NBT object tests" << std::endl;
std::cout << "################################################################################" << std::endl;
//Byte tag constructor test
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"));
std::cout << "Passed Byte Tag constructor test." << std::endl;
return 0;
}