Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encode type before header, simplify access to pixel data #6

Merged
merged 2 commits into from
Jun 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ set(PXCRYPT_QX_COMPONENTS

include(OB/FetchQx)
ob_fetch_qx(
REF "83e840ca996942c973f9bf5f9d9d6efc8cef029f"
REF "08d59b2aa53df3b53da44148740adfc39e1da5c1"
COMPONENTS
${PXCRYPT_QX_COMPONENTS}
)
Expand Down
11 changes: 11 additions & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,42 @@ ob_add_standard_library(${LIB_TARGET_NAME}
tools/bit_chunker.h
tools/byte_compositer.h
tools/ch_sequence_generator.h
tools/px_access_read.h
tools/px_access_write.h
tools/px_sequence_generator.h
tools/px_skimmer.h
tools/px_weaver.h
HEADERS_API
COMMON "${PROJECT_NAMESPACE_LC}"
FILES
_internal.h
decode.h
encdec.h
encode.h
IMPLEMENTATION
_internal.cpp
decode.cpp
encode.cpp
encdec_p.cpp
tools/bit_chunker.cpp
tools/byte_compositer.cpp
tools/ch_sequence_generator.cpp
tools/px_access_read.cpp
tools/px_access_write.cpp
tools/px_sequence_generator.cpp
tools/px_skimmer.cpp
tools/px_weaver.cpp
DOC_ONLY
encdec.dox
LINKS
PRIVATE
$<BUILD_INTERFACE:magic_enum::magic_enum>
PUBLIC
Qt6::Core
Qt6::Gui
Qx::Core
CONFIG STANDARD
)

# Build interface wrapper for magic_enum is a workaround for a CMake limitation.
# See https://github.com/oblivioncth/STARpp/blob/500d19354fe394ba5a1c84e56208d2186866c5e8/lib/CMakeLists.txt
18 changes: 18 additions & 0 deletions lib/include/pxcrypt/_internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef _INTERNAL_H
#define _INTERNAL_H

// Qt Includes
#include <QSize>

// Shared Library Support
#include "pxcrypt/pxcrypt_codec_export.h"

/*! @cond */
namespace _PxCrypt
{
//-Namespace Functions-------------------------------------------------------------------------------------------------
PXCRYPT_CODEC_EXPORT quint64 calculateMaximumPayloadBits(const QSize& dim, quint16 tagSize, quint8 bpc);
}
/*! @endcond */

#endif // _INTERNAL_H
12 changes: 1 addition & 11 deletions lib/include/pxcrypt/decode.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,10 @@
// Qx Includes
#include <qx/core/qx-genericerror.h>

// Project Includes
#include "pxcrypt/encdec.h"

namespace PxCrypt
{
//-Namespace Types ---------------------------------------------------------------------------------------------
struct DecodeSettings
{
QByteArray psk;
EncType type = Absolute;
};

//-Namespace Functions-------------------------------------------------------------------------------------------------
PXCRYPT_CODEC_EXPORT Qx::GenericError decode(QByteArray& dec, QString& tag, const QImage& enc, DecodeSettings set, const QImage& medium = QImage());
PXCRYPT_CODEC_EXPORT Qx::GenericError decode(QByteArray& dec, QString& tag, const QImage& enc, QByteArrayView psk = QByteArrayView(), const QImage& medium = QImage());

}

Expand Down
2 changes: 1 addition & 1 deletion lib/include/pxcrypt/encdec.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace PxCrypt
//-Namespace Enums-----------------------------------------------------------------------------------------------------
enum EncType : quint8 {
Relative = 0,
Absolute = 1
Absolute = 4
};

}
Expand Down
22 changes: 22 additions & 0 deletions lib/src/_internal.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Unit Includes
#include "pxcrypt/_internal.h"

// Project Includes
#include "encdec_p.h"

namespace _PxCrypt
{
//-Namespace Functions-------------------------------------------------------------------------------------------------
/*!
* @warning This functions exists primarily for testing the library. The practical amount of storage
* available within an image is always lower than this. Use calculateMaximumStorage() instead.
*
* Returns the maximum number of bits that can be stored within an image of dimensions @a dim and tag of size
* @a tagSize when using @a bpc bits per channel.
*/
quint64 calculateMaximumPayloadBits(const QSize& dim, quint16 tagSize, quint8 bpc)
{
return PxCrypt::calcMaxPayloadBits(dim, tagSize, bpc);
}

}
103 changes: 42 additions & 61 deletions lib/src/decode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
// Project Includes
#include "pxcrypt/encdec.h"
#include "encdec_p.h"
#include "tools/px_access_read.h"
#include "tools/px_skimmer.h"
#include "tools/byte_compositer.h"

// Qx Includes
#include <qx/core/qx-bitarray.h>
#include <qx/core/qx-integrity.h>

// Magic Enum Include
#include <magic_enum.hpp>

/*!
* @file decode.h
*
Expand All @@ -27,19 +31,18 @@ namespace
// Errors
const QString ERR_DECODING_FAILED = QSL("Decoding failed.");
const QString ERR_INVALID_SOURCE = QSL("The encoded image is invalid.");
const QString ERR_MISSING_MEDIUM = QSL("The required medium image is invalid.");
const QString ERR_MISSING_MEDIUM = QSL("The encoded image requires a medium to be decoded but none was provided.");
const QString ERR_DIMMENSION_MISTMATCH = QSL("The required medium image has different dimmensions than the encoded image.");
const QString ERR_NOT_LARGE_ENOUGH = QSL("The provided image is not large enough to be an encoded image.");
const QString ERR_INVALID_HEADER = QSL("The provided image is not encoded, the wrong encoding type was specified,"
" or the password/medium are incorrect.");
const QString ERR_NOT_MAGIC = QSL("The provided image is not encoded or the password/medium are incorrect.");
const QString ERR_LENGTH_MISMATCH = QSL("The encoded image's header indicates it contains more data than possible.");
const QString ERR_UNEXPECTED_END = QSL("All pixels were skimmed before the expected payload size was reached.");
const QString ERR_CHECKSUM_MISMATCH = QSL("The payload's checksum did not match the expected value.");

//-Unit Private Functions ---------------------------------------------------------------------------------------------
bool canFitHeader(const QSize& dim, quint8 bpc)
{
int bits = (dim.width() * dim.height() - 1) * 3 * bpc; // -1 For bpp indicator
int bits = (dim.width() * dim.height() - META_PIXELS) * 3 * bpc;
return bits >= HEADER_BYTES * 8;
}
}
Expand All @@ -50,25 +53,6 @@ namespace
// DecodeSettings
//===============================================================================================================

/*!
* @struct DecodeSettings <pxcrypt/decode.h>
*
* @brief The DecodeSettings struct holds tuning parameters that are required to properly decode
* data from an image.
*/

/*!
* @var QByteArray DecodeSettings::psk
*
* The key that was used when encoding the original data, if any.
*/

/*!
* @var EncType DecodeSettings::type
*
* The encoding strategy that was used when encoding the original data.
*/

//-Namespace Functions-------------------------------------------------------------------------------------------------

/*!
Expand All @@ -77,13 +61,13 @@ namespace
* @param[out] dec The original payload.
* @param[out] tag The image's/payload's tag
* @param[in] enc The image with the encoded data
* @param[in] set Tuneables for properly decoding the input image
* @param[in] psk The key that was used when encoding the original data, if any.
* @param[in] medium The original medium used when encoding the payload. Ignored for encoding types other than EncType::Relative
* @return An error object that describes cause of failure if encoding fails.
*
* @sa encode(), DecodeSettings.
* @sa encode().
*/
Qx::GenericError decode(QByteArray& dec, QString& tag, const QImage& enc, DecodeSettings set, const QImage& medium)
Qx::GenericError decode(QByteArray& dec, QString& tag, const QImage& enc, QByteArrayView psk, const QImage& medium)
{
// Clear buffer
dec.clear();
Expand All @@ -92,44 +76,52 @@ Qx::GenericError decode(QByteArray& dec, QString& tag, const QImage& enc, Decode
if(enc.isNull())
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_INVALID_SOURCE);

// Ensure medium image is valid if applicable
if(set.type == EncType::Relative)
{
if(medium.isNull())
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_MISSING_MEDIUM);

if(medium.size() != enc.size())
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_DIMMENSION_MISTMATCH);
}
// Ensure image meets bare minimum space for meta pixels
if(enc.size().width() * enc.size().height() < META_PIXELS)
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_NOT_LARGE_ENOUGH);

// Ensure standard pixel format
QImage encStd = enc; // Because of Qt's CoW system this occurs almost no penalty if the format is already correct
if(encStd.format() != QImage::Format_RGBA8888)
encStd.convertTo(QImage::Format_RGBA8888);

QImage mediumStd = medium;
if(!mediumStd.isNull() && mediumStd.format() != QImage::Format_RGBA8888)
mediumStd.convertTo(QImage::Format_RGBA8888);
// Prepare pixel access, automatically reads meta pixels
PxAccessRead pAccess(&encStd, !psk.isEmpty() ? psk : DEFAULT_SEED);

// Check BPC
Qx::BitArray bpcBits(3);
// Ensure BPC is valid
if(pAccess.bpc() < 1 || pAccess.bpc() > 7)
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_NOT_MAGIC);

QRgb first = enc.pixel(0, 0);
bpcBits.setBit(0, qRed(first) & 0x01);
bpcBits.setBit(1, qGreen(first) & 0x01);
bpcBits.setBit(2, qBlue(first) & 0x01);
quint8 bpc = bpcBits.toInteger<quint8>();
// Ensure type is valid
if(!magic_enum::enum_contains(pAccess.type()))
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_NOT_MAGIC);

// Ensure at least header can fit
if(!canFitHeader(encStd.size(), bpc))
if(!canFitHeader(encStd.size(), pAccess.bpc()))
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_NOT_LARGE_ENOUGH);

// Reserve space for header
dec.reserve(HEADER_BYTES);

// Ensure medium image is valid if applicable
QImage mediumStd;
if(pAccess.type() == EncType::Relative)
{
mediumStd = medium;

if(mediumStd.isNull())
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_MISSING_MEDIUM);

if(mediumStd.size() != encStd.size())
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_DIMMENSION_MISTMATCH);

if(!mediumStd.isNull() && mediumStd.format() != QImage::Format_RGBA8888)
mediumStd.convertTo(QImage::Format_RGBA8888);
}

// Prepare pixel skimmer and byte compositer
PxSkimmer pSkimmer(&encStd, &mediumStd, !set.psk.isEmpty() ? set.psk : DEFAULT_SEED, bpc, set.type);
ByteCompositer bCompositer(&dec, bpc);
PxSkimmer pSkimmer(&pAccess, &mediumStd);
ByteCompositer bCompositer(&dec, pAccess.bpc());

// Read header
while(dec.size() != HEADER_BYTES)
Expand All @@ -138,31 +130,20 @@ Qx::GenericError decode(QByteArray& dec, QString& tag, const QImage& enc, Decode
// Extract header components
QDataStream hs(dec);
QByteArray hMagic(MAGIC_NUM.size(), Qt::Uninitialized);
EncType hType;
quint32 hPayloadChecksum;
quint16 hTagSize;
quint32 hPayloadSize;

hs.readRawData(hMagic.data(), hMagic.size());
hs >> hType;
hs >> hPayloadChecksum;
hs >> hTagSize;
hs >> hPayloadSize;

// Ensure header is valid
if(hMagic != MAGIC_NUM)
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_INVALID_HEADER);

if(hType != set.type)
{
/* NOTE: This should be impossible given the data would be garbage with
* the wrong encoding type, hence why this only causes a warning;
* however, potential future data types may require re-evaluating this check.
*/
qWarning("mismatched encoding type!");
}
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_NOT_MAGIC);

quint64 maxStorage = calcMaxPayloadSize(encStd.size(), hTagSize, bpc);
quint64 maxStorage = calcMaxPayloadBytes(encStd.size(), hTagSize, pAccess.bpc());
if(hPayloadSize > maxStorage)
return Qx::GenericError(Qx::GenericError::Critical, ERR_DECODING_FAILED, ERR_NOT_LARGE_ENOUGH);

Expand Down
13 changes: 9 additions & 4 deletions lib/src/encdec_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,20 @@ namespace PxCrypt
/*! @cond */

//-Namespace Functions-------------------------------------------------------------------------------------------------
quint64 calcMaxPayloadSize(const QSize& dim, quint16 tagSize, quint8 bpc)
quint64 calcMaxPayloadBits(const QSize& dim, quint16 tagSize, quint8 bpc)
{
// Returns 0 if no data can fit
quint64 pixles = dim.width() * dim.height() - 1; // -1 For bpp indicator
quint64 pixles = dim.width() * dim.height() - META_PIXELS;
quint64 headerBits = HEADER_BYTES * 8;
quint64 tagBits = tagSize* 8;
quint64 tagBits = tagSize * 8;
quint64 payloadBits = Qx::constrainedSub(pixles * bpc * 3, headerBits + tagBits);

return payloadBits / 8; // Truncate down
return payloadBits;
}

quint64 calcMaxPayloadBytes(const QSize& dim, quint16 tagSize, quint8 bpc)
{
return calcMaxPayloadBits(dim, tagSize, bpc) / 8; // Truncate down
}

/*! @endcond */
Expand Down
14 changes: 6 additions & 8 deletions lib/src/encdec_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,37 @@
// Qx Includes
#include <qx/utility/qx-macros.h>

// Project Includes
#include "pxcrypt/encdec.h"

namespace PxCrypt
{
/*! @cond */

//-Namespace Enums-------------------------------------------------------------------------------------------------
enum Channel : quint8{
Red = 0,
Blue = 1,
Green = 2,
Green = 1,
Blue = 2,
Alpha = 3
};

//-Namespace Variables-------------------------------------------------------------------------------------------------
const QByteArray MAGIC_NUM = QBAL("PXC");

const int META_PIXELS = 2; // BPC + EncType

const int MAGIC_SIZE = MAGIC_NUM.size();
const int TYPE_SIZE = sizeof(EncType);
const int CHECKSUM_SIZE = sizeof(quint32);
const int TAG_LENGTH_SIZE = sizeof(quint16);
const int PAYLOAD_LENGTH_SIZE = sizeof(quint32);
const int HEADER_BYTES = MAGIC_SIZE +
TYPE_SIZE +
CHECKSUM_SIZE +
TAG_LENGTH_SIZE +
PAYLOAD_LENGTH_SIZE;

static inline const QByteArray DEFAULT_SEED = QBAL("The best and most secure seed that is possible to exist!");

//-Namespace Functions-------------------------------------------------------------------------------------------------
quint64 calcMaxPayloadSize(const QSize& dim, quint16 tagSize, quint8 bpc);
quint64 calcMaxPayloadBits(const QSize& dim, quint16 tagSize, quint8 bpc);
quint64 calcMaxPayloadBytes(const QSize& dim, quint16 tagSize, quint8 bpc);

/*! @endcond */
}
Expand Down
Loading