Skip to content

Commit

Permalink
Merge pull request #6 from oblivioncth/in_stream_type_info
Browse files Browse the repository at this point in the history
Encode type before header, simplify access to pixel data
  • Loading branch information
oblivioncth authored Jun 10, 2023
2 parents fb6da70 + a24bd5e commit a986a97
Show file tree
Hide file tree
Showing 31 changed files with 750 additions and 306 deletions.
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

0 comments on commit a986a97

Please sign in to comment.