Skip to content

Commit

Permalink
Ванюшкин Дмитрий - Лабораторная работа №2 (UNN-ITMM-Software#117)
Browse files Browse the repository at this point in the history
* add: module description CMake file

* add: source module CMakeLists.txt

* add: test module CMakeLists.txt

* add: unit tests entry point

* refactor: change copyright

* add: base64_converter header

* add: base64_converter implementation

* add: tests for base64 encoder

* add: tests for base64 decoder

* documentation: add method description

* fix: compile errors on linux CI machine

* refactor: remove move-semantic constructor

* change: add some tests

* change: add throws tests

* change: remove move semantic
  • Loading branch information
dmitry-vnn authored Apr 11, 2024
1 parent 870b07f commit cc2c6c7
Show file tree
Hide file tree
Showing 8 changed files with 558 additions and 0 deletions.
12 changes: 12 additions & 0 deletions modules/vanushkin_d_base64_converter/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Declare variables for binaries' names
get_filename_component(DIR_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
set(MODULE "${DIR_NAME}")
set(LIBRARY "lib_${MODULE}")
set(TESTS "test_${MODULE}")

# Include directory with public headers
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

# Add all submodules
add_subdirectory(src)
add_subdirectory(test)
64 changes: 64 additions & 0 deletions modules/vanushkin_d_base64_converter/include/base64_converter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2024 Vanushkin Dmitry

#pragma once

#include <vector>
#include <string>
#include <unordered_map>
#include <utility>

class Base64Encoder {
private:
std::string _message;

public:
explicit Base64Encoder(const std::string &message);

std::string Encode() const;

private:
std::vector<char> ConvertStringToBytesByAddingEmptyBytes() const;

unsigned char InterpretSixthBitsGroupToUChar(
const std::vector<char> &vector, size_t sixthIndex) const;
};

class Base64Decoder {
private:
std::string _encodedMessage;

public:
explicit Base64Decoder(const std::string &encodedMessage);

std::string Decode() const;

private:
std::pair<std::vector<unsigned char>, size_t>
ConvertEncodedMessageBytesFromBase64Alphabet() const;
};

namespace internal {

class Base64Alphabet {
private:
std::string alphabet;
std::unordered_map<unsigned char, unsigned char> inverseAlphabet;

private:
Base64Alphabet();

public:
static Base64Alphabet& GetInstance() {
static Base64Alphabet instance;
return instance;
}

Base64Alphabet(const Base64Alphabet&) = delete;
void operator=(const Base64Alphabet&) = delete;

unsigned char GetSymbolByIndex(unsigned char index) const;

unsigned char GetIndexBySymbol(unsigned char symbol) const;
};

} // namespace internal
18 changes: 18 additions & 0 deletions modules/vanushkin_d_base64_converter/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
set(target ${LIBRARY})

file(GLOB srcs "*.cpp")
file(GLOB hdrs "../include/*.h")
set_source_files_properties(${srcs} ${hdrs} PROPERTIES
LABELS "${MODULE};Library")

add_library(${target} STATIC ${srcs} ${hdrs})
set_target_properties(${target} PROPERTIES
OUTPUT_NAME ${MODULE}
LABELS "${MODULE};Library")

if (UNIX)
target_link_libraries(${target} ${CMAKE_THREAD_LIBS_INIT})
endif (UNIX)
target_link_libraries(${target} ${LIBRARY_DEPS})

set(LIBRARY_DEPS "${LIBRARY_DEPS};${target}" PARENT_SCOPE)
217 changes: 217 additions & 0 deletions modules/vanushkin_d_base64_converter/src/base64_converter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Copyright 2024 Vanushkin Dmitry

#include <algorithm>
#include <unordered_map>
#include <stdexcept>
#include "include/base64_converter.h"

Base64Encoder::Base64Encoder(const std::string &message) : _message(message) {}

// Конвертирует сообщение (с кодировкой ASCII без расширения)
// в формат base64
// Реализация:
// разбиваем байты сообщения на блоки размера по 6 бит
// каждый такой блок, в численной интерпретации
// представляет собой число в диапазоне от 0..63,
// преобразуем каждый такой блок в символ кодирующего
// алфавита (A-Za-z0-9) (блок выступает индексом)
// получаем в итоге закодированное сообщение из
// преобразованных символов
std::string Base64Encoder::Encode() const {
auto buffer = ConvertStringToBytesByAddingEmptyBytes();
auto addedEmptyBytes = buffer.size() - _message.size();

auto sixthsCount = (buffer.size() * 8) / 6;

auto encodedMessage = std::vector<unsigned char>();
encodedMessage.reserve(sixthsCount);

for (size_t sixthIndex = 0; sixthIndex < sixthsCount; ++sixthIndex) {
auto number = InterpretSixthBitsGroupToUChar(buffer, sixthIndex);

encodedMessage.push_back(internal::Base64Alphabet::GetInstance()
.GetSymbolByIndex(number));
}

// Пустые байты кодируются символом =
for (size_t i = 0; i < addedEmptyBytes; ++i) {
encodedMessage[encodedMessage.size() - 1 - i] = '=';
}

// Создаём строку из вектора символов
return {encodedMessage.cbegin(), encodedMessage.cend()};
}

// Преобразует сообщение в набор символов,
// добавляя необходимое количество нулевых байтов в конец,
// такое, чтобы общее число бит было кратно 6,
// откуда следует, что сумма n + k должна делится на 3,
// где n - число символов в сообщении (число байт),
// k - необходимое для делимости нулевые байты
std::vector<char> Base64Encoder
::ConvertStringToBytesByAddingEmptyBytes() const {
auto remainder = _message.size() % 3;
auto emptyBytesNeeded = (3 - remainder) % 3;

std::vector<char> buffer(_message.cbegin(), _message.cend());

buffer.reserve(emptyBytesNeeded);
while (emptyBytesNeeded--) {
buffer.push_back(0);
}

return buffer;
}

// Преобразует группу из 6ти битов в число, по заданному
// индексу группы и выровненному набору байтов сообщения
// 8 - размер байта в битах (кто не знал)
unsigned char Base64Encoder::InterpretSixthBitsGroupToUChar(
const std::vector<char>& vector, size_t sixthIndex) const {
auto startByteIndex = (6 * sixthIndex) / 8;
auto startBitIndex = (6 * sixthIndex) % 8;

auto endBitIndex = (startBitIndex + 6 - 1) % 8;

auto startByte = vector[startByteIndex];

// Шестёрка битов полностью содержится в байте
if (endBitIndex > startBitIndex) {
// Смещаем байт к концу, удаляя последние 2 - startBitIndex битов
auto part = (unsigned char) (startByte >> (2 - startBitIndex));

// Set first and second bits of part to zero, 192 is 0x0011 11111
return part & ~192;
}

// Шестёрка содержится в соседних байтах
// Достаём часть из первого байта, выравниваем до двух нулевых битов слева
auto highestPart = ((unsigned char)(startByte << startBitIndex))
>> (startBitIndex - 1 - endBitIndex);

// Достаём часть из второго байта, выравнивая по правому краю
auto lowestPart = vector[startByteIndex + 1] >> (7 - endBitIndex);

return highestPart + lowestPart;
}

Base64Decoder::Base64Decoder(const std::string &encodedMessage) :
_encodedMessage(encodedMessage) {}

// Конвертирует сообщение в формате base64 в исходное сообщение
// Реализация:
// каждый байт интерпретируем,
// как символ кодирующего алфавита (A-Za-z0-9), преобразуя байт
// в индекс символа из этого алфавита,
// получаем новый набор байт, в котором у каждого байта
// первые два символа - нулевые, т.е. 6 существенных битов
// преобразуем сообщение в новый набор байтов, игнорируя
// у каждого исходного байта первые два бита
// конвертируем набор байтов в строку
std::string Base64Decoder::Decode() const {
// Чтобы сообщение могло быть декодированно, существенно число бит
// (игрорируя первые два у каждого байта) должно делится на 8
// (8 - 2)k ⋮ 8 => 3k ⋮ 4
auto alsoBytesNeedToDecode = (3 * _encodedMessage.size()) % 4;

if (alsoBytesNeedToDecode != 0) {
auto message = "Encoded message must as least have "
+ std::to_string(alsoBytesNeedToDecode)
+ " bytes to decode";
throw std::runtime_error(message);
}

auto pair = ConvertEncodedMessageBytesFromBase64Alphabet();

auto& convertedMessage = pair.first;
auto specialBytesCount = pair.second;

auto bytesFromDecodedMessage =
(3 * _encodedMessage.size()) / 4 - specialBytesCount;

std::vector<char> decodedMessage;
decodedMessage.reserve(bytesFromDecodedMessage);

auto encodedByteIndex = 0;
auto encodedStartBitFromByteIndex = 0;

for (size_t decodedByteIndex = 0; decodedByteIndex <
bytesFromDecodedMessage; decodedByteIndex++) {
auto encodedByte = convertedMessage[encodedByteIndex];
auto highestPart = encodedByte << (encodedStartBitFromByteIndex + 2);

auto endBitAbsoluteIndex = (encodedStartBitFromByteIndex + 4 + 7) % 8;
auto endByte = convertedMessage[encodedByteIndex + 1];
auto lowestPart = endByte >> (7 - endBitAbsoluteIndex);

decodedMessage.push_back(highestPart + lowestPart);

encodedByteIndex++;
if (endBitAbsoluteIndex + 1 == 8) {
encodedByteIndex++;
encodedStartBitFromByteIndex = 0;
} else {
encodedStartBitFromByteIndex = endBitAbsoluteIndex - 1;
}
}

return {decodedMessage.cbegin(), decodedMessage.cend()};
}

std::pair<std::vector<unsigned char>, size_t>
Base64Decoder::ConvertEncodedMessageBytesFromBase64Alphabet() const {
auto& alphabet = internal::Base64Alphabet::GetInstance();

std::vector<unsigned char> converted;
converted.reserve(_encodedMessage.size());

auto countOfSpecialByteSymbol = std::count(
_encodedMessage.begin(), _encodedMessage.end(), '=');

if (countOfSpecialByteSymbol > 2) {
throw std::runtime_error(
"special symbol must be appears max 2 times at the endl");
}

auto expectOnlySpecialSymbolsStringEndOfEncoded = _encodedMessage.substr(
_encodedMessage.size() - countOfSpecialByteSymbol,
countOfSpecialByteSymbol);

for (const auto &c : expectOnlySpecialSymbolsStringEndOfEncoded)
if (c != '=')
throw std::runtime_error(
"special symbol must be appears max 2 times at the endl");

for (const auto& symbol : _encodedMessage) {
if (symbol == '=') {
converted.push_back(0);
} else {
converted.push_back(alphabet.GetIndexBySymbol(symbol));
}
}

return std::move(std::make_pair(
std::move(converted), countOfSpecialByteSymbol));
}

internal::Base64Alphabet::Base64Alphabet():
alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz0123456789"),
inverseAlphabet() {
for (size_t i = 0; i < alphabet.size(); ++i) {
inverseAlphabet[alphabet[i]] = i;
}
}

unsigned char internal::Base64Alphabet::GetIndexBySymbol(
unsigned char symbol) const {
if (!inverseAlphabet.count(symbol)) {
throw std::runtime_error("Unknown symbol");
}
return inverseAlphabet.at(symbol);
}

unsigned char internal::Base64Alphabet::GetSymbolByIndex(
unsigned char index) const {
return alphabet[index];
}
27 changes: 27 additions & 0 deletions modules/vanushkin_d_base64_converter/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
set(target ${TESTS})

file(GLOB srcs "*.cpp")
set_source_files_properties(${srcs} PROPERTIES
LABELS "${MODULE};Test")

add_executable(${target} ${srcs} ${hdrs})
set_target_properties(${target} PROPERTIES
LABELS "${MODULE};Test")

if (UNIX)
target_link_libraries(${target} gtest ${CMAKE_THREAD_LIBS_INIT} pthread)
endif (UNIX)
target_link_libraries(${target} gtest ${LIBRARY})

# VS2012 doesn't support correctly the tuples yet,
# see http://code.google.com/p/googletest/issues/detail?id=412
if(MSVC)
target_compile_definitions(${target} PUBLIC _VARIADIC_MAX=10)
endif()

add_test(
NAME ${MODULE}_gtest
COMMAND ${target}
)
set_tests_properties (${MODULE}_gtest PROPERTIES
LABELS "${MODULE}")
Loading

0 comments on commit cc2c6c7

Please sign in to comment.