forked from UNN-ITMM-Software/devtools-course-practice
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ванюшкин Дмитрий - Лабораторная работа №2 (UNN-ITMM-Software#117)
* 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
1 parent
870b07f
commit cc2c6c7
Showing
8 changed files
with
558 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
64
modules/vanushkin_d_base64_converter/include/base64_converter.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
217
modules/vanushkin_d_base64_converter/src/base64_converter.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}") |
Oops, something went wrong.