Merge pull request 'feat/connection' (#5) from feat/connection into main

Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
2024-07-13 14:07:38 -03:00
77 changed files with 51337 additions and 0 deletions

236
.clang-format Normal file
View File

@@ -0,0 +1,236 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAfterAttributes: Never
BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 256
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
KeepEmptyLinesAtEOF: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: true
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...

245
.cmake-format.yaml Normal file
View File

@@ -0,0 +1,245 @@
_help_parse: Options affecting listfile parsing
parse:
_help_additional_commands:
- Specify structure for custom cmake functions
additional_commands:
foo:
flags:
- BAR
- BAZ
kwargs:
HEADERS: "*"
SOURCES: "*"
DEPENDS: "*"
_help_override_spec:
- Override configurations per-command where available
override_spec: {}
_help_vartags:
- Specify variable tags.
vartags: []
_help_proptags:
- Specify property tags.
proptags: []
_help_format: Options affecting formatting.
format:
_help_disable:
- Disable formatting entirely, making cmake-format a no-op
disable: false
_help_line_width:
- How wide to allow formatted cmake files
line_width: 128
_help_tab_size:
- How many spaces to tab for indent
tab_size: 2
_help_use_tabchars:
- If true, lines are indented using tab characters (utf-8
- 0x09) instead of <tab_size> space characters (utf-8 0x20).
- In cases where the layout would require a fractional tab
- character, the behavior of the fractional indentation is
- governed by <fractional_tab_policy>
use_tabchars: false
_help_fractional_tab_policy:
- If <use_tabchars> is True, then the value of this variable
- indicates how fractional indentions are handled during
- whitespace replacement. If set to 'use-space', fractional
- indentation is left as spaces (utf-8 0x20). If set to
- "`round-up` fractional indentation is replaced with a single"
- tab character (utf-8 0x09) effectively shifting the column
- to the next tabstop
fractional_tab_policy: use-space
_help_max_subgroups_hwrap:
- If an argument group contains more than this many sub-groups
- (parg or kwarg groups) then force it to a vertical layout.
max_subgroups_hwrap: 2
_help_max_pargs_hwrap:
- If a positional argument group contains more than this many
- arguments, then force it to a vertical layout.
max_pargs_hwrap: 6
_help_max_rows_cmdline:
- If a cmdline positional group consumes more than this many
- lines without nesting, then invalidate the layout (and nest)
max_rows_cmdline: 2
_help_separate_ctrl_name_with_space:
- If true, separate flow control names from their parentheses
- with a space
separate_ctrl_name_with_space: false
_help_separate_fn_name_with_space:
- If true, separate function names from parentheses with a
- space
separate_fn_name_with_space: false
_help_dangle_parens:
- If a statement is wrapped to more than one line, than dangle
- the closing parenthesis on its own line.
dangle_parens: false
_help_dangle_align:
- If the trailing parenthesis must be 'dangled' on its on
- "line, then align it to this reference: `prefix`: the start"
- "of the statement, `prefix-indent`: the start of the"
- "statement, plus one indentation level, `child`: align to"
- the column of the arguments
dangle_align: prefix
_help_min_prefix_chars:
- If the statement spelling length (including space and
- parenthesis) is smaller than this amount, then force reject
- nested layouts.
min_prefix_chars: 4
_help_max_prefix_chars:
- If the statement spelling length (including space and
- parenthesis) is larger than the tab width by more than this
- amount, then force reject un-nested layouts.
max_prefix_chars: 10
_help_max_lines_hwrap:
- If a candidate layout is wrapped horizontally but it exceeds
- this many lines, then reject the layout.
max_lines_hwrap: 2
_help_line_ending:
- What style line endings to use in the output.
line_ending: unix
_help_command_case:
- Format command names consistently as 'lower' or 'upper' case
command_case: canonical
_help_keyword_case:
- Format keywords consistently as 'lower' or 'upper' case
keyword_case: unchanged
_help_always_wrap:
- A list of command names which should always be wrapped
always_wrap: []
_help_enable_sort:
- If true, the argument lists which are known to be sortable
- will be sorted lexicographicall
enable_sort: true
_help_autosort:
- If true, the parsers may infer whether or not an argument
- list is sortable (without annotation).
autosort: false
_help_require_valid_layout:
- By default, if cmake-format cannot successfully fit
- everything into the desired linewidth it will apply the
- last, most aggressive attempt that it made. If this flag is
- True, however, cmake-format will print error, exit with non-
- zero status code, and write-out nothing
require_valid_layout: false
_help_layout_passes:
- A dictionary mapping layout nodes to a list of wrap
- decisions. See the documentation for more information.
layout_passes: {}
_help_markup: Options affecting comment reflow and formatting.
markup:
_help_bullet_char:
- What character to use for bulleted lists
bullet_char: "*"
_help_enum_char:
- What character to use as punctuation after numerals in an
- enumerated list
enum_char: .
_help_first_comment_is_literal:
- If comment markup is enabled, don't reflow the first comment
- block in each listfile. Use this to preserve formatting of
- your copyright/license statements.
first_comment_is_literal: false
_help_literal_comment_pattern:
- If comment markup is enabled, don't reflow any comment block
- which matches this (regex) pattern. Default is `None`
- (disabled).
literal_comment_pattern: null
_help_fence_pattern:
- Regular expression to match preformat fences in comments
- default= ``r'^\s*([`~]{3}[`~]*)(.*)$'``
fence_pattern: ^\s*([`~]{3}[`~]*)(.*)$
_help_ruler_pattern:
- Regular expression to match rulers in comments default=
- '``r''^\s*[^\w\s]{3}.*[^\w\s]{3}$''``'
ruler_pattern: ^\s*[^\w\s]{3}.*[^\w\s]{3}$
_help_explicit_trailing_pattern:
- If a comment line matches starts with this pattern then it
- is explicitly a trailing comment for the preceeding
- argument. Default is '#<'
explicit_trailing_pattern: "#<"
_help_hashruler_min_length:
- If a comment line starts with at least this many consecutive
- hash characters, then don't lstrip() them off. This allows
- for lazy hash rulers where the first hash char is not
- separated by space
hashruler_min_length: 10
_help_canonicalize_hashrulers:
- If true, then insert a space between the first hash char and
- remaining hash chars in a hash ruler, and normalize its
- length to fill the column
canonicalize_hashrulers: true
_help_enable_markup:
- enable comment markup parsing and reflow
enable_markup: true
_help_lint: Options affecting the linter
lint:
_help_disabled_codes:
- a list of lint codes to disable
disabled_codes: []
_help_function_pattern:
- regular expression pattern describing valid function names
function_pattern: "[0-9a-z_]+"
_help_macro_pattern:
- regular expression pattern describing valid macro names
macro_pattern: "[0-9A-Z_]+"
_help_global_var_pattern:
- regular expression pattern describing valid names for
- variables with global (cache) scope
global_var_pattern: "[A-Z][0-9A-Z_]+"
_help_internal_var_pattern:
- regular expression pattern describing valid names for
- variables with global scope (but internal semantic)
internal_var_pattern: _[A-Z][0-9A-Z_]+
_help_local_var_pattern:
- regular expression pattern describing valid names for
- variables with local scope
local_var_pattern: "[a-z][a-z0-9_]+"
_help_private_var_pattern:
- regular expression pattern describing valid names for
- privatedirectory variables
private_var_pattern: _[0-9a-z_]+
_help_public_var_pattern:
- regular expression pattern describing valid names for public
- directory variables
public_var_pattern: "[A-Z][0-9A-Z_]+"
_help_argument_var_pattern:
- regular expression pattern describing valid names for
- function/macro arguments and loop variables.
argument_var_pattern: "[a-z][a-z0-9_]+"
_help_keyword_pattern:
- regular expression pattern describing valid names for
- keywords used in functions or macros
keyword_pattern: "[A-Z][0-9A-Z_]+"
_help_max_conditionals_custom_parser:
- In the heuristic for C0201, how many conditionals to match
- within a loop in before considering the loop a parser.
max_conditionals_custom_parser: 2
_help_min_statement_spacing:
- Require at least this many newlines between statements
min_statement_spacing: 1
_help_max_statement_spacing:
- Require no more than this many newlines between statements
max_statement_spacing: 2
max_returns: 6
max_branches: 12
max_arguments: 5
max_localvars: 15
max_statements: 50
_help_encode: Options affecting file encoding
encode:
_help_emit_byteorder_mark:
- If true, emit the unicode byte-order mark (BOM) at the start
- of the file
emit_byteorder_mark: false
_help_input_encoding:
- Specify the encoding of the input file. Defaults to utf-8
input_encoding: utf-8
_help_output_encoding:
- Specify the encoding of the output file. Defaults to utf-8.
- Note that cmake only claims to support utf-8 so be careful
- when using anything else
output_encoding: utf-8
_help_misc: Miscellaneous configurations options.
misc:
_help_per_command:
- A dictionary containing any per-command configuration
- overrides. Currently only `command_case` is supported.
per_command: {}

8
.gitignore vendored
View File

@@ -1,3 +1,11 @@
# VScode
.vscode/
# CMake
.cache/
build/
compile_commands.json
# ---> C++
# Prerequisites
*.d

27
CMakeLists.txt Normal file
View File

@@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.20)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(ENV{TARGET} "x86_64-linux-gnu")
set(CMAKE_CXX_FLAGS "-Wall -fvisibility=hidden -fvisibility-inlines-hidden -Wformat -Wformat-security -rdynamic")
set(CMAKE_CXX_FLAGS_DEBUG "-g2 -O0")
set(CMAKE_CXX_FLAGS_RELEASE
"-fstack-protector-all -Wl,-z,relro,-z,now -Wl,-z,noexecstack -s -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1 -O3 -g2 -march=native -pipe -flto=auto"
)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
enable_testing()
execute_process(
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/version.sh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE MQTTD_VERSION)
project(
mqttd
VERSION ${MQTTD_VERSION}
LANGUAGES CXX)
add_subdirectory(src)
add_subdirectory(tests)
add_subdirectory(docs)

61
cmake/FindAutotools.cmake Normal file
View File

@@ -0,0 +1,61 @@
# https://github.com/scivision/cmakeutils/blob/main/cmake/FindAutotools.cmake
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindAutotools
-------------
The main purpose is to detect that commonly used Autotools programs are installed.
This lets the user know at CMake configuration time if the computer is ready to
build an autotools-based ExternalProject.
Result Variables
^^^^^^^^^^^^^^^^
``Autotools_FOUND``
indicates Autotools and associated programs are detected
#]=======================================================================]
find_program(
AUTOCONF_EXECUTABLE
NAMES autoconf
DOC "Autoconf")
if(AUTOCONF_EXECUTABLE)
execute_process(
COMMAND ${AUTOCONF_EXECUTABLE} --version
RESULT_VARIABLE ret
OUTPUT_VARIABLE out)
message(DEBUG "${out}")
if(ret EQUAL 0)
string(REGEX MATCH "autoconf .*([0-9]+\\.[0-9]+)" _m "${out}")
set(AUTOCONF_VERSION "${CMAKE_MATCH_1}")
endif()
endif()
find_program(
AUTOMAKE_EXECUTABLE
NAMES automake
DOC "Automake")
find_program(
LIBTOOL_EXECUTABLE
NAMES glibtool libtool NAMES_PER_DIR
DOC "libtool")
find_program(
M4_EXECUTABLE
NAMES gm4 m4 NAMES_PER_DIR
DOC "M4")
find_program(
MAKE_EXECUTABLE
NAMES gmake make NAMES_PER_DIR
DOC "GNU Make")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
Autotools
VERSION_VAR AUTOCONF_VERSION
REQUIRED_VARS AUTOCONF_EXECUTABLE AUTOMAKE_EXECUTABLE MAKE_EXECUTABLE)

11
cmake/GetCatch2.cmake Normal file
View File

@@ -0,0 +1,11 @@
include(FetchContent)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.2
)
FetchContent_MakeAvailable(Catch2)
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)

14
cmake/GetCppTrace.cmake Normal file
View File

@@ -0,0 +1,14 @@
include(FetchContent)
set(CPPTRACE_UNWIND_WITH_UNWIND Off)
set(CPPTRACE_UNWIND_WITH_LIBUNWIND On)
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF Off)
set(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE On)
set(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH On)
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.5.2 # <HASH or TAG>
GIT_PROGRESS TRUE
OVERRIDE_FIND_PACKAGE)
FetchContent_MakeAvailable(cpptrace)

11
cmake/GetSpdlog.cmake Normal file
View File

@@ -0,0 +1,11 @@
include(FetchContent)
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog
GIT_TAG v1.x
)
FetchContent_MakeAvailable(spdlog)
list(APPEND CMAKE_MODULE_PATH ${spdlog_SOURCE_DIR}/extras)

42
cmake/GetUnwind.cmake Normal file
View File

@@ -0,0 +1,42 @@
include(ExternalProject)
include(FindAutotools)
set(EXTERNAL_DEP_NAME "libunwind")
ExternalProject_Add(${EXTERNAL_DEP_NAME}
GIT_REPOSITORY https://github.com/libunwind/libunwind.git
GIT_TAG v1.8.0
GIT_SHALLOW True
UPDATE_DISCONNECTED true
CONFIGURE_HANDLED_BY_BUILD true
BUILD_BYPRODUCTS ${EXTERNAL_DEP_NAME}
PREFIX ${CMAKE_BINARY_DIR}/_deps/
SOURCE_DIR "${CMAKE_BINARY_DIR}/_deps/${EXTERNAL_DEP_NAME}-src"
BINARY_DIR "${CMAKE_BINARY_DIR}/_deps/${EXTERNAL_DEP_NAME}-build"
TMP_DIR "${CMAKE_BINARY_DIR}/_deps/${EXTERNAL_DEP_NAME}-subbuild/tmp"
STAMP_DIR "${CMAKE_BINARY_DIR}/_deps/${EXTERNAL_DEP_NAME}-subbuild/stamp"
DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/_deps/${EXTERNAL_DEP_NAME}-subbuild/download"
INSTALL_DIR "${CMAKE_BINARY_DIR}/_deps/${EXTERNAL_DEP_NAME}-bin"
BUILD_ALWAYS OFF
CONFIGURE_COMMAND autoreconf -i <SOURCE_DIR> && <SOURCE_DIR>/configure --prefix=<INSTALL_DIR>
BUILD_COMMAND make -j -C <BINARY_DIR>
INSTALL_COMMAND make install -C <BINARY_DIR>
# TEST_COMMAND make check -C <BINARY_DIR>
)
find_package(LibLZMA REQUIRED)
ExternalProject_Get_Property(${EXTERNAL_DEP_NAME} INSTALL_DIR)
#Hack to make CMake stop whining about folder not existent
file(MAKE_DIRECTORY ${INSTALL_DIR}/include/)
add_library(${EXTERNAL_DEP_NAME}::static INTERFACE IMPORTED GLOBAL)
target_include_directories(${EXTERNAL_DEP_NAME}::static INTERFACE ${INSTALL_DIR}/include/)
target_link_libraries(${EXTERNAL_DEP_NAME}::static INTERFACE ${INSTALL_DIR}/lib/libunwind.a LibLZMA::LibLZMA)
add_dependencies(${EXTERNAL_DEP_NAME}::static ${EXTERNAL_DEP_NAME})
add_library(${EXTERNAL_DEP_NAME}::dynamic INTERFACE IMPORTED GLOBAL)
target_include_directories(${EXTERNAL_DEP_NAME}::dynamic INTERFACE ${INSTALL_DIR}/include/)
target_link_libraries(${EXTERNAL_DEP_NAME}::dynamic INTERFACE ${INSTALL_DIR}/lib/libunwind.so LibLZMA::LibLZMA)
add_dependencies(${EXTERNAL_DEP_NAME}::dynamic ${EXTERNAL_DEP_NAME})

45
docs/CMakeLists.txt Normal file
View File

@@ -0,0 +1,45 @@
option(BUILD_DOC "Build documentation" ON)
find_package(Doxygen REQUIRED doxygen OPTIONAL_COMPONENTS dot mscgen dia)
if(NOT DOXYGEN_FOUND)
message(ERROR "Doxygen is needed to build documentation, please install it")
endif()
set(DOXYGEN_ALPHABETICAL_INDEX NO)
set(DOXYGEN_BUILTIN_STL_SUPPORT YES)
set(DOXYGEN_CASE_SENSE_NAMES NO)
set(DOXYGEN_CLASS_DIAGRAMS YES)
set(DOXYGEN_DISTRIBUTE_GROUP_DOC YES)
set(DOXYGEN_EXCLUDE build)
set(DOXYGEN_EXTRACT_ALL YES)
set(DOXYGEN_EXTRACT_LOCAL_CLASSES NO)
set(DOXYGEN_FILE_PATTERNS *.hpp)
set(DOXYGEN_GENERATE_TREEVIEW YES)
set(DOXYGEN_HIDE_FRIEND_COMPOUNDS YES)
set(DOXYGEN_HIDE_IN_BODY_DOCS YES)
set(DOXYGEN_HIDE_UNDOC_CLASSES YES)
set(DOXYGEN_HIDE_UNDOC_MEMBERS YES)
set(DOXYGEN_JAVADOC_AUTOBRIEF YES)
set(DOXYGEN_QT_AUTOBRIEF YES)
set(DOXYGEN_QUIET YES)
set(DOXYGEN_RECURSIVE YES)
set(DOXYGEN_REFERENCED_BY_RELATION YES)
set(DOXYGEN_REFERENCES_RELATION YES)
set(DOXYGEN_SORT_BY_SCOPE_NAME YES)
set(DOXYGEN_SORT_MEMBER_DOCS NO)
set(DOXYGEN_SOURCE_BROWSER YES)
set(DOXYGEN_STRIP_CODE_COMMENTS NO)
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md")
set(DOXYGEN_EXTRA_FILES "${CMAKE_SOURCE_DIR}/docs/mqtt_spec/")
doxygen_add_docs(
doc "${CMAKE_SOURCE_DIR}/src" "${CMAKE_SOURCE_DIR}/tests"
"${CMAKE_SOURCE_DIR}/README.md" ALL COMMENT "Generate HTML documentation")
add_custom_target(
open_docs
COMMAND ${CMAKE_COMMAND} -E env xdg-open
${CMAKE_SOURCE_DIR}/build/docs/html/index.html
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/build/docs/html
COMMENT "Opening Doxygen documentation")

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

File diff suppressed because it is too large Load Diff

4
scripts/crashlog.txt Normal file
View File

@@ -0,0 +1,4 @@
#1 0x4025af sp=0x7fff3ff7c270 <unknown> + 0x0
#2 0x7f0b9234114a sp=0x7fff3ff7c290 __libc_start_call_main + 0x7a
#3 0x7f0b9234120b sp=0x7fff3ff7c330 __libc_start_main + 0x8b
#4 0x4025e5 sp=0x7fff3ff7c390 <unknown> + 0x8b

BIN
scripts/mqttd Executable file

Binary file not shown.

53
scripts/process_crashlog.py Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
from pathlib import Path
import shutil
import subprocess
class Crashlog():
def __init__(self, crashlog: Path, executable: Path):
self.executable = executable
self.addresses = self.read_crashlog(crashlog)
self.translated_addresses = self.translate_addresses(self.addresses, executable)
def read_crashlog(self, crashlog: Path) -> list[str]:
_addresses = []
try:
with open(crashlog, "r") as f:
for line in f:
if len(line.strip()) <= 0:
continue
tokens = line.split()
_addresses.append(tokens[1])
except FileNotFoundError:
print("O arquivo não foi encontrado.")
except Exception as e:
print(f"Ocorreu um error: {e}")
finally:
return _addresses
def translate_addresses(self, addresses: list[str], executable: Path) -> dict[str, str]:
_translated_addr = {}
if shutil.which("addr2line") is None:
raise RuntimeError("addr2line binary not found")
for addr in addresses:
_path = executable.absolute()
_cmd = f"addr2line --functions --pretty-print --demangle --basenames --exe {_path} {addr}"
_process = subprocess.run(_cmd, shell=True, check=True, capture_output=True)
_translated_addr[addr] = _process.stdout
return _translated_addr
def __str__(self):
_stdout = ""
for key, value in self.translated_addresses.items():
_stdout += value.decode()
return _stdout
if __name__ == "__main__":
print(Crashlog(Path("crashlog.txt"), Path("mqttd")))
pass

7
setup.cfg Normal file
View File

@@ -0,0 +1,7 @@
[isort]
profile = black
[pycodestyle]
max-line-length = 128
ignore = E203,E701, E402, E501, E722

13
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,13 @@
include(GetSpdlog)
include(GetCppTrace)
add_subdirectory(libmqttd)
add_subdirectory(core)
add_executable(mqttd mqttd.cpp)
target_link_libraries(mqttd PRIVATE libcore libmqttd spdlog cpptrace::cpptrace)
target_include_directories(mqttd PUBLIC "${PROJECT_BINARY_DIR}")
add_executable(mqttd_stacktracer mqttd_stacktracer.cpp)
target_link_libraries(mqttd_stacktracer PRIVATE cpptrace::cpptrace)
target_include_directories(mqttd_stacktracer PUBLIC "${PROJECT_BINARY_DIR}")

14
src/core/CMakeLists.txt Normal file
View File

@@ -0,0 +1,14 @@
add_library(libcore "")
add_subdirectory(broker)
FILE(GLOB CPP_FILES CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES CONFIGURE_DEPENDS *.hpp)
target_sources(libcore
PRIVATE ${CPP_FILES}
PUBLIC ${HPP_FILES}
)
target_include_directories(libcore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,12 @@
FILE(GLOB CPP_FILES CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES CONFIGURE_DEPENDS *.hpp)
include(GetSpdlog)
target_sources(libcore
PRIVATE ${CPP_FILES}
PUBLIC ${HPP_FILES}
)
target_include_directories(libcore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(libcore PRIVATE spdlog libmqttd)

View File

@@ -0,0 +1,62 @@
#include "session_manager.hpp"
#include <connection_listener.hpp>
#include <csignal>
#include <thread>
ConnectionListener::ConnectionListener(int port) { this->connection_listener_port = port; }
ConnectionListener::~ConnectionListener() { this->stop(); }
void ConnectionListener::start() { this->listener = std::thread{&ConnectionListener::listen, this}; }
void ConnectionListener::stop() {
this->is_listening = false;
shutdown(this->connection_listener_socket, SHUT_RDWR);
close(this->connection_listener_socket);
this->join();
}
void ConnectionListener::join() {
if (this->listener.joinable())
this->listener.join();
}
void ConnectionListener::create_new_session(int socket_fd) {}
void ConnectionListener::listen() {
struct sockaddr_in serverAddr;
struct sockaddr_storage serverStorage;
socklen_t addr_size;
connection_listener_socket = socket(AF_INET, SOCK_STREAM, 0);
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(this->connection_listener_port);
int option = 1;
setsockopt(connection_listener_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
int rc = bind(connection_listener_socket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
if (rc != 0) {
int errno_tmp = errno;
spdlog::error("Failed to bind socket: " + std::string(strerror(errno_tmp)));
exit(ExitCode::BIND_ERROR);
}
rc = ::listen(connection_listener_socket, 50);
if (rc != 0) {
int errno_tmp = errno;
spdlog::error("Failed to listen on socket: " + std::string(strerror(errno_tmp)));
exit(ExitCode::LISTEN_ERROR);
}
this->is_listening = true;
spdlog::info("Listening on port " + std::to_string(this->connection_listener_port));
while (this->is_listening) {
addr_size = sizeof(serverStorage);
int new_connection_socket = accept(connection_listener_socket, (struct sockaddr *)&serverStorage, &addr_size);
SessionManager::get_instance().new_session(new_connection_socket);
}
}

View File

@@ -0,0 +1,65 @@
#ifndef INCLUDE_BROKER_CONNECTION_LISTENER_HPP_
#define INCLUDE_BROKER_CONNECTION_LISTENER_HPP_
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <thread>
#include <unistd.h>
#include <exit_codes.hpp>
#include <session.hpp>
#include <spdlog/spdlog.h>
/**
* @brief Class responsible for listening to incoming connections.
*/
class ConnectionListener {
public:
/**
* @brief Constructs a ConnectionListener object with the specified port.
*
* @param port The port to listen for incoming connections (default is 1883).
*/
ConnectionListener(int port = 1883);
/**
* @brief Destroys the ConnectionListener object.
*/
~ConnectionListener();
/**
* @brief Starts listening for incoming connections.
*/
void start();
/**
* @brief Stops listening for incoming connections.
*/
void stop();
/**
* @brief Joins the listener thread.
*/
void join();
private:
bool is_listening; /**< Flag indicating whether the listener is currently active. */
int connection_listener_socket; /**< Socket file descriptor for the listener. */
int connection_listener_port; /**< Port number to listen on. */
std::thread listener; /**< Listener thread responsible for accepting incoming connections. */
/**
* @brief Creates a new session for the accepted connection.
*
* @param socket_fd The socket file descriptor of the accepted connection.
*/
static void create_new_session(int socket_fd);
/**
* @brief Listens for incoming connections.
*/
void listen();
};
#endif // INCLUDE_BROKER_CONNECTION_LISTENER_HPP_

View File

@@ -0,0 +1,73 @@
#include "disconnect_packet.hpp"
#include "spdlog/spdlog.h"
#include <functional>
#include <memory>
#include <mutex>
#include <session_manager.hpp>
#include <shared_mutex>
#include <thread>
#include <utility>
SessionManager *SessionManager::singleton = nullptr;
SessionManager &SessionManager::get_instance() {
if (singleton == nullptr)
singleton = new SessionManager();
return *singleton;
}
SessionManager::~SessionManager() { this->shutdown_all_sessions(); }
void SessionManager::shutdown_all_sessions() {
for (auto &it : this->connected_sessions)
it.second->close(DisconnectReasonCode::SERVER_SHUTING_DOWN);
}
void SessionManager::new_session(int socket_fd) { std::thread{&SessionManager::create_session, this, socket_fd}.detach(); }
void SessionManager::add_session_to_pool(Session *session) {
this->check_for_session_takeover(session->client_id);
std::unique_lock<std::shared_mutex> lock(this->connected_sessions_lock);
this->connected_sessions.emplace(session->client_id, session);
spdlog::debug("Added session " + session->client_id + " to session pool");
spdlog::info("Client " + session->client_id + " connected");
}
void SessionManager::remove_session_from_pool(Session *session) {
std::unique_lock<std::shared_mutex> lock(this->connected_sessions_lock);
this->connected_sessions.erase(session->client_id);
spdlog::debug("Removed session " + session->client_id + " from session pool");
spdlog::info("Client " + session->client_id + " disconnected");
}
void SessionManager::create_session(int socket_fd) {
std::unique_ptr<Session> session(new Session(socket_fd));
std::function<void(Session *)> on_connect_callback = std::bind(&SessionManager::add_session_to_pool, this, std::placeholders::_1);
session->on_connect = on_connect_callback;
std::function<void(Session *)> on_disconnect_callback = std::bind(&SessionManager::remove_session_from_pool, this, std::placeholders::_1);
session->on_disconnect = on_disconnect_callback;
session->listen();
}
void SessionManager::check_for_session_takeover(const std::string &client_id) {
// Check if there is another session connected with same client id
std::shared_lock<std::shared_mutex> lock(this->connected_sessions_lock);
auto session_with_same_client_id = this->connected_sessions.find(client_id);
if (session_with_same_client_id != this->connected_sessions.end()) {
spdlog::warn("There is another session connected with ClientID " + client_id + ", disconnecting it");
auto session_to_disconnect = session_with_same_client_id->second;
// Unlock so session can call it's own disconnect callback, which is remove_session_from_pool
lock.unlock();
session_to_disconnect->close(DisconnectReasonCode::SESSION_TAKEN_OVER);
}
}
void SessionManager::print_sessions() {
std::shared_lock<std::shared_mutex> lock(this->connected_sessions_lock);
for (auto &it : this->connected_sessions)
spdlog::info(it.second->client_id);
}

View File

@@ -0,0 +1,96 @@
#ifndef INCLUDE_BROKER_SESSION_MANAGER_HPP_
#define INCLUDE_BROKER_SESSION_MANAGER_HPP_
#include "session.hpp"
#include <shared_mutex>
#include <string>
#include <unordered_map>
/**
* @brief Class responsible for managing MQTT sessions.
*/
class SessionManager {
public:
/**
* @brief Deleted copy constructor.
*/
SessionManager(const SessionManager &) = delete;
/**
* @brief Deleted copy assignment operator.
*/
SessionManager operator=(const SessionManager &) = delete;
/**
* @brief Retrieves the singleton instance of SessionManager.
*
* @return Reference to the singleton instance of SessionManager.
*/
static SessionManager &get_instance();
/**
* @brief Destroys the SessionManager object.
*/
~SessionManager();
/**
* @brief Creates a new session for the specified socket file descriptor.
*
* @param socket_fd The socket file descriptor for the new session.
*/
void new_session(int socket_fd);
/**
* @brief Shuts down the session with the given client ID.
*
* @param client_id The ID of the session to shut down.
*/
void shutdown_session(const std::string &client_id);
/**
* @brief Shuts down all active sessions.
*/
void shutdown_all_sessions();
/**
* @brief Prints information about all active sessions.
*/
void print_sessions();
private:
static SessionManager *singleton; /**< Pointer to the singleton instance of SessionManager. */
SessionManager(){}; /**< Private constructor to prevent instantiation. */
/**
* @brief Creates a new session for the specified socket file descriptor.
*
* @param socket_fd The socket file descriptor for the new session.
*/
void create_session(int socket_fd);
/**
* @brief Adds a session to the pool of connected sessions.
*
* @param session Pointer to the session to add.
*/
void add_session_to_pool(Session *);
/**
* @brief Removes a session from the pool of connected sessions.
*
* @param session Pointer to the session to remove.
*/
void remove_session_from_pool(Session *);
/**
* @brief Checks for session takeover based on the client ID.
*
* @param client_id The ID of the session to check.
*/
void check_for_session_takeover(const std::string &);
std::unordered_map<std::string, Session *> connected_sessions; /**< Map of client IDs to session pointers. */
mutable std::shared_mutex connected_sessions_lock; /**< Mutex for thread-safe access to connected_sessions. */
};
#endif // INCLUDE_BROKER_SESSION_MANAGER_HPP_

12
src/core/exit_codes.hpp Normal file
View File

@@ -0,0 +1,12 @@
#ifndef INCLUDE_CORE_EXIT_CODES_HPP_
#define INCLUDE_CORE_EXIT_CODES_HPP_
enum ExitCode {
SUCCESS = 0,
ERROR = 1,
BIND_ERROR = 101,
LISTEN_ERROR = 102,
RECV_ERROR = 103,
};
#endif // INCLUDE_CORE_EXIT_CODES_HPP_

View File

@@ -0,0 +1,17 @@
add_library(libmqttd "")
add_subdirectory(utilities)
add_subdirectory(types)
add_subdirectory(network)
add_subdirectory(protocol)
FILE(GLOB CPP_FILES CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES CONFIGURE_DEPENDS *.hpp)
target_sources(libmqttd
PRIVATE ${CPP_FILES}
PUBLIC ${HPP_FILES}
)
target_include_directories(libmqttd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,34 @@
#ifndef INCLUDE_LIBMQTTD_EXCEPTIONS_HPP_
#define INCLUDE_LIBMQTTD_EXCEPTIONS_HPP_
#include <stdexcept>
/**
* @brief Exception thrown when a function is not yet implemented.
*/
class NotImplemented : public std::logic_error {
public:
/**
* @brief Constructs a NotImplemented exception.
*/
NotImplemented() : std::logic_error("Function not yet implemented"){};
};
/**
* @brief Exception thrown when a received packet is malformed.
*/
class MalformedPacket : public std::runtime_error {
public:
/**
* @brief Constructs a MalformedPacket exception.
*/
MalformedPacket() : std::runtime_error("Received a malformed packet"){};
/**
* @brief Constructs a MalformedPacket exception with additional information.
*
* @param info Additional information about the malformed packet.
*/
MalformedPacket(std::string info) : std::runtime_error("Received a malformed packet: " + info) {}
};
#endif // INCLUDE_LIBMQTTD_EXCEPTIONS_HPP_

View File

@@ -0,0 +1,13 @@
FILE(GLOB CPP_FILES_LOCAL CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES_LOCAL CONFIGURE_DEPENDS *.hpp)
target_sources(libmqttd
PRIVATE ${CPP_FILES_LOCAL}
PUBLIC ${HPP_FILES_LOCAL}
)
add_subdirectory(packet_interface)
add_subdirectory(connection)
add_subdirectory(disconnection)
target_include_directories(libmqttd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,9 @@
FILE(GLOB CPP_FILES_LOCAL CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES_LOCAL CONFIGURE_DEPENDS *.hpp)
target_sources(libmqttd
PRIVATE ${CPP_FILES_LOCAL}
PUBLIC ${HPP_FILES_LOCAL}
)
target_include_directories(libmqttd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,37 @@
#include "fixed_header.hpp"
#include "packet_interface.hpp"
#include "property.hpp"
#include <connect_ack.hpp>
#include <csignal>
#include <cstddef>
#include <vector>
ConnectACK::ConnectACK() : IPacket() {
IPacket::fixed_header.packet_type = PacketType::CONNACK;
IPacket::fixed_header.packet_flags = 0;
// There is a obrigatory reason code and connect acknowledge flags after fixed header, thus we are garanted to have at least 2 bytes
IPacket::fixed_header.remaining_length = 2;
this->reason_code = ConnectReasonCode::SUCCESS;
this->session_present = false;
};
void ConnectACK::add_property(const PropertyIdentifier &prop, const PropertyValue &value) { this->properties[prop] = value; }
std::vector<std::byte> ConnectACK::as_bytes() {
std::vector<std::byte> variable_header_bytes;
if (session_present)
variable_header_bytes.emplace_back(std::byte(1));
else
variable_header_bytes.emplace_back(std::byte(0));
variable_header_bytes.emplace_back(std::byte(reason_code));
std::vector<std::byte> properties_bytes = properties.as_bytes();
variable_header_bytes.insert(variable_header_bytes.end(), properties_bytes.begin(), properties_bytes.end());
IPacket::fixed_header.remaining_length = variable_header_bytes.size();
std::vector<std::byte> fixed_header_bytes = IPacket::as_bytes();
fixed_header_bytes.insert(fixed_header_bytes.end(), variable_header_bytes.begin(), variable_header_bytes.end());
return fixed_header_bytes;
}

View File

@@ -0,0 +1,103 @@
#ifndef INCLUDE_CONNECTION_CONNECT_ACK_HPP_
#define INCLUDE_CONNECTION_CONNECT_ACK_HPP_
#include <packet_interface.hpp>
#include <property.hpp>
/**
* @brief Enum class representing the reason codes for MQTT Connect packets according to MQTT v5.0 specification.
*
* These reason codes are used to indicate the result of a connection attempt.
* Refer to <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901079">MQTT Version 5.0 topic 3.2.2.2</a> for more information
*/
enum class ConnectReasonCode : uint8_t {
SUCCESS = 0, /**< The Connection is accepted. */
UNSPECIFIED_ERROR = 128, /**< The Server does not wish to reveal the reason for the failure, or none of the other Reason Codes apply. */
MALFORMED_PACKET = 129, /**< Data within the CONNECT packet could not be correctly parsed. */
PROTOCOL_ERROR = 130, /**< Data in the CONNECT packet does not conform to this specification. */
IMPLEMENTATION_SPECIFIC_ERROR = 131, /**< The CONNECT is valid but is not accepted by this Server. */
UNSUPPORTED_PROTOCOL_VERSION = 132, /**< The Server does not support the version of the MQTT protocol requested by the Client. */
CLIENT_IDENTIFIER_NOT_VALID = 133, /**< The Client Identifier is a valid string but is not allowed by the Server. */
BAD_USERNAME_OR_PASSWORD = 134, /**< The provided username or password is not valid. */
NOT_AUTHORIZED = 135, /**< The Client is not authorized to connect. */
SERVER_UNAVAILABLE = 136, /**< The MQTT Server is not available. */
SERVER_BUSY = 137, /**< The Server is busy. Try again later. */
BANNED = 138, /**< This Client has been banned by administrative action. */
BAD_AUTHENTICATION_METHOD = 140, /**< The authentication method is not supported or does not match the authentication method currently in use. */
TOPIC_NAME_INVALID = 144, /**< The Will Topic Name is not malformed, but is not accepted by this Server. */
PACKET_TOO_LARGE = 149, /**< The CONNECT packet exceeded the maximum permissible size. */
QUOTA_EXCEEDED = 151, /**< An implementation or administrative imposed limit has been exceeded. */
PAYLOAD_FORMAT_INVALID = 153, /**< The Will Payload does not match the specified Payload Format Indicator. */
RETAIN_NOT_SUPPORTED = 154, /**< The Server does not support retained messages, and Will Retain was set to 1. */
QOS_NOT_SUPPORTED = 155, /**< The Server does not support the QoS set in Will QoS. */
USER_ANOTHER_SERVER = 156, /**< The Client should temporarily use another server. */
SERVER_MOVED = 157, /**< The Client should permanently use another server. */
CONNECTION_RATE_EXCEEDED = 159 /**< The connection rate limit has been exceeded. */
};
/**
* @brief Connect Acknowledge packet according to <a href="https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901074">MQTT Version 5.0 topic 3.2</a>.
*
* This class represents a Connect Acknowledge packet as defined in the MQTT Version 5.0 specification (topic 3.2).
*/
class ConnectACK : public IPacket {
public:
/**
* @brief Constructs a new Connect ACK packet with default values.
*
* The default values are: reason code set to SUCCESS (0x00) and session_present set to false.
*/
ConnectACK();
~ConnectACK() = default;
/**
* @brief Sets the session_present value.
*
* @param value The value to set for session_present.
*/
void set_session_present(const bool &value) { this->session_present = value; };
/**
* @brief Sets the reason code to be returned to the client.
*
* @param value The ConnectReasonCode to set as the reason code.
*/
void set_reason_code(const ConnectReasonCode &value) { this->reason_code = value; };
/**
* @brief Adds a property to the property map.
*
* @param identifier The identifier of the property to add.
* @param value The value of the property to add.
*/
void add_property(const PropertyIdentifier &identifier, const PropertyValue &value);
/**
* @brief Converts the packet to bytes for transmission over a socket to the client.
*
* @return A vector of bytes representing the packet.
*/
std::vector<std::byte> as_bytes();
private:
// Variable Header
/**
* @brief Indicates whether the Server is using Session State from a previous connection for the respective ClientID.
*/
bool session_present;
/**
* @brief The reason code indicating if the connection was accepted or not.
*
* Only ConnectReasonCode::SUCCESS indicates a successful connection.
*/
ConnectReasonCode reason_code;
/**
* @brief Map of MQTT properties associated with the packet.
*/
MQTTProperties properties;
};
#endif // INCLUDE_CONNECTION_CONNECT_ACK_HPP_

View File

@@ -0,0 +1,135 @@
#include "connect_packet.hpp"
#include "binary_data.hpp"
#include "property.hpp"
#include "spdlog/spdlog.h"
#include "utf8_string.hpp"
#include <iostream>
#include <iterator>
#include <sstream>
#include <stdexcept>
ConnectPacket::ConnectPacket(IPacket &packet) : IPacket(packet) {
parse_variable_header();
parse_payload();
}
ConnectPacket::~ConnectPacket() {}
ConnectPacket::ConnectPacket(std::vector<std::byte> data) : IPacket(data) { parse_variable_header(); }
void ConnectPacket::parse_variable_header() {
spdlog::trace("Parsing Connect variable header");
this->protocol_name = utf8_str(this->variable_header_start_byte);
if (this->protocol_name.as_string() != "MQTT")
throw std::invalid_argument("Wrong protocol name. Expected MQTT and received " + this->protocol_name.as_string());
auto protocol_version_start_byte = std::next(this->variable_header_start_byte, protocol_name.as_bytes().size());
this->protocol_version = utilities::types::get_fixed_size_integer<uint8_t>(protocol_version_start_byte);
auto connection_flags_start_byte = std::next(protocol_version_start_byte);
this->connection_flags = *connection_flags_start_byte;
auto keepalive_start_byte = std::next(connection_flags_start_byte);
this->keepalive = utilities::types::get_fixed_size_integer<uint16_t>(keepalive_start_byte);
auto header_properties_start_byte = std::next(keepalive_start_byte, sizeof(this->keepalive));
this->properties = MQTTProperties(header_properties_start_byte);
this->payload_start_byte = std::next(header_properties_start_byte, this->properties.as_bytes().size() + 1);
}
ConnectionFlagValue ConnectPacket::get_connection_flag(ConnectFlags flag) const {
uint8_t connection_flags_value = static_cast<uint8_t>(this->connection_flags);
uint8_t flag_pos = static_cast<uint8_t>(flag);
// WILL_QOS is a 2 byte integer value
if (flag == ConnectFlags::WILL_QOS) {
uint8_t qos = 0;
qos |= utilities::bit::get(connection_flags_value, flag_pos);
qos |= utilities::bit::get(connection_flags_value, flag_pos + 1) << 1;
return qos;
}
return static_cast<bool>(utilities::bit::get(connection_flags_value, flag_pos));
}
std::string ConnectPacket::as_string() const {
std::ostringstream packet_str;
packet_str << IPacket::as_string();
packet_str << "\t"
<< "\u21B3"
<< "Variable Hedaer" << std::endl;
packet_str << "\t\t"
<< "\u21B3"
<< "Protocol = " << this->protocol_name << std::endl;
packet_str << "\t\t"
<< "\u21B3"
<< "Protocol Version = " << std::to_string(this->protocol_version) << std::endl;
packet_str << "\t\t"
<< "\u21B3"
<< "Connect Flags = " << this->connection_flags << std::endl;
packet_str << "\t\t"
<< "\u21B3"
<< "KeepAlive = " << this->keepalive << std::endl;
packet_str << "\t\t"
<< "\u21B3"
<< "Packet identifier = " << packet_identifier << std::endl;
if (properties.size() > 0) {
packet_str << properties;
}
// Payload
packet_str << "\t"
<< "\u21B3"
<< "Payload" << std::endl;
packet_str << "\t\t"
<< "\u21B3"
<< "Client ID = " << this->client_id << std::endl;
if (std::get<bool>(get_connection_flag(ConnectFlags::WILL_FLAG))) {
packet_str << this->lastwill.properties;
packet_str << "\t\t"
<< "\u21B3"
<< "Last Will Topic = " << this->lastwill.will_topic << std::endl;
}
if (std::get<bool>(get_connection_flag(ConnectFlags::USERNAME_FLAG))) {
packet_str << "\t\t"
<< "\u21B3"
<< "Username = " << this->username << std::endl;
}
if (std::get<bool>(get_connection_flag(ConnectFlags::PASSWORD_FLAG))) {
packet_str << "\t\t"
<< "\u21B3"
<< "Password = " << this->password << std::endl;
}
return packet_str.str();
}
void ConnectPacket::parse_payload() {
spdlog::trace("Parsing Connect payload");
this->client_id = utf8_str(payload_start_byte);
auto next_field_start_byte = std::next(payload_start_byte, this->client_id.as_bytes().size());
if (std::get<bool>(get_connection_flag(ConnectFlags::WILL_FLAG))) {
this->lastwill.properties = MQTTProperties(next_field_start_byte);
//TODO: Why here we don't have to sum 1, while at variable_header_start_byte and payload_start_byte we do?
next_field_start_byte = std::next(next_field_start_byte, this->lastwill.properties.as_bytes().size());
this->lastwill.will_topic = utf8_str(next_field_start_byte);
next_field_start_byte = std::next(next_field_start_byte, this->lastwill.will_topic.as_bytes().size());
this->lastwill.will_payload = binary_data(next_field_start_byte);
next_field_start_byte = std::next(next_field_start_byte, this->lastwill.will_payload.as_bytes().size());
}
if (std::get<bool>(get_connection_flag(ConnectFlags::USERNAME_FLAG))) {
this->username = utf8_str(next_field_start_byte);
next_field_start_byte = std::next(next_field_start_byte, this->username.as_bytes().size());
}
if (std::get<bool>(get_connection_flag(ConnectFlags::PASSWORD_FLAG))) {
this->password = binary_data(next_field_start_byte);
next_field_start_byte = std::next(next_field_start_byte, this->password.as_bytes().size());
}
}

View File

@@ -0,0 +1,116 @@
#ifndef INCLUDE_CONNECTION_CONNECT_PACKET_HPP_
#define INCLUDE_CONNECTION_CONNECT_PACKET_HPP_
#include "exceptions.hpp"
#include "utf8_string.hpp"
#include <bit.hpp>
#include <cstddef>
#include <last_will.hpp>
#include <packet_interface.hpp>
#include <string>
#include <variant>
#include <vector>
/**
* @brief Enum class representing the flags for MQTT Connect packets.
*
* These flags are used to control the behavior of the connection attempt.
*/
enum ConnectFlags : uint16_t {
RESERVERD = 0, /**< Reserved flag. */
CLEAN_START = 1, /**< Clean start flag. */
WILL_FLAG = 2, /**< Will flag. */
WILL_QOS = 3, /**< Will QoS flag. */
WILL_RETAIN = 5, /**< Will retain flag.*/
PASSWORD_FLAG = 6, /**< Password flag. If set to 0, password MUST not be present in the payload*/
USERNAME_FLAG = 7, /**< Username flag. If set to 0, username MUST not be present in the payload*/
};
using ConnectionFlagValue = std::variant<bool, uint8_t>;
/**
* @brief Connect Packet according to MQTT Version 5.0 specification.
*
* This class represents a Connect packet as defined in the MQTT Version 5.0 specification.
*/
class ConnectPacket : public IPacket {
public:
/**
* @brief Constructs a new Connect Packet from an existing packet.
*
* @param packet The packet from which to construct the Connect Packet.
*/
ConnectPacket(IPacket &packet);
/**
* @brief Constructs a new Connect Packet from a vector of bytes.
*
* @param bytes The bytes from which to construct the Connect Packet.
*/
ConnectPacket(std::vector<std::byte> bytes);
/**
* @brief Destructs the Connect Packet.
*/
~ConnectPacket();
/**
* @brief Gets the value of a connection flag.
*
* @param flag The flag for which to get the value.
* @return The value of the flag.
*/
ConnectionFlagValue get_connection_flag(ConnectFlags flag) const;
/**
* @brief Converts the packet to a string for display purposes.
*
* @return A string representing the packet.
*/
std::string as_string() const final;
/**
* @brief Converts the packet to bytes for transmission over a socket to the client.
*
* This function is not implemented for ConnectPacket.
*
* @return A vector of bytes representing the packet.
*/
std::vector<std::byte> as_bytes() final { throw NotImplemented(); };
/**
* @brief Gets the client ID from the packet.
*
* @return The client ID as a string.
*/
inline std::string get_client_id() const { return this->client_id.as_string(); };
private:
/**
* @brief Parses the variable header of the packet.
*/
void parse_variable_header() final;
/**
* @brief Parses the payload of the packet.
*/
void parse_payload() final;
// Variable Header
utf8_str protocol_name; /**< The protocol name. */
std::uint8_t protocol_version; /**< The protocol version. */
std::byte connection_flags; /**< The connection flags. */
std::uint16_t keepalive; /**< The keepalive value. */
std::vector<std::byte>::const_iterator payload_start_byte; /**< Iterator to the start byte of the payload. */
uint16_t packet_identifier = 0; /**< The packet identifier. */
MQTTProperties properties; /**< The MQTT properties associated with the packet. */
uint size_in_bytes = 0; /**< The size of the packet in bytes. */
// Payload
utf8_str client_id; /**< The client ID. */
LastWill lastwill; /**< The last will message. */
utf8_str username; /**< The username. */
binary_data password; /**< The password. */
};
#endif // INCLUDE_CONNECTION_CONNECT_PACKET_HPP_

View File

@@ -0,0 +1,17 @@
#ifndef INCLUDE_CONNECTION_LAST_WILL_HPP_
#define INCLUDE_CONNECTION_LAST_WILL_HPP_
#include <property.hpp>
#include <variable_byte_int.hpp>
#include <binary_data.hpp>
#include <utf8_string.hpp>
struct LastWill {
MQTTProperties properties;
utf8_str will_topic;
binary_data will_payload;
};
using last_will = LastWill;
#endif // INCLUDE_CONNECTION_LAST_WILL_HPP_

View File

@@ -0,0 +1,9 @@
FILE(GLOB CPP_FILES_LOCAL CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES_LOCAL CONFIGURE_DEPENDS *.hpp)
target_sources(libmqttd
PRIVATE ${CPP_FILES_LOCAL}
PUBLIC ${HPP_FILES_LOCAL}
)
target_include_directories(libmqttd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,143 @@
#include "fixed_header.hpp"
#include "packet_interface.hpp"
#include "property.hpp"
#include "types.hpp"
#include <cstddef>
#include <disconnect_packet.hpp>
#include <vector>
DisconnectPacket::DisconnectPacket() : IPacket() {
IPacket::fixed_header.packet_type = PacketType::DISCONNECT;
// There is a obrigatory reason code
IPacket::fixed_header.remaining_length = 1;
this->reason_code = DisconnectReasonCode::UNSPECIFIED_ERROR;
};
DisconnectPacket::DisconnectPacket(IPacket &packet) : IPacket(packet) {
// There is a obrigatory reason code
IPacket::fixed_header.remaining_length = 1;
uint8_t reason_code = utilities::types::get_fixed_size_integer<uint8_t>(IPacket::variable_header_start_byte);
this->reason_code = DisconnectReasonCode(reason_code);
auto proprties_start_byte = std::next(IPacket::variable_header_start_byte);
this->properties = MQTTProperties(proprties_start_byte);
}
DisconnectPacket::DisconnectPacket(const std::vector<std::byte> &data) : IPacket(data) {
uint8_t reason_code = utilities::types::get_fixed_size_integer<uint8_t>(IPacket::variable_header_start_byte);
this->reason_code = DisconnectReasonCode(reason_code);
auto proprties_start_byte = std::next(IPacket::variable_header_start_byte);
this->properties = MQTTProperties(proprties_start_byte);
}
void DisconnectPacket::add_property(const PropertyIdentifier &prop, const PropertyValue &value) { this->properties[prop] = value; }
std::vector<std::byte> DisconnectPacket::as_bytes() {
std::vector<std::byte> variable_header_bytes;
variable_header_bytes.emplace_back(std::byte(reason_code));
std::vector<std::byte> properties_bytes = properties.as_bytes();
variable_header_bytes.insert(variable_header_bytes.end(), properties_bytes.begin(), properties_bytes.end());
IPacket::fixed_header.remaining_length = variable_header_bytes.size();
std::vector<std::byte> fixed_header_bytes = IPacket::as_bytes();
fixed_header_bytes.insert(fixed_header_bytes.end(), variable_header_bytes.begin(), variable_header_bytes.end());
return fixed_header_bytes;
}
std::ostream& operator<<(std::ostream& os, DisconnectReasonCode code) {
switch(code) {
case DisconnectReasonCode::NORMAL_DISCONNECTION:
os << "NORMAL_DISCONNECTION";
break;
case DisconnectReasonCode::DISCONNECT_WITH_WILL_MESSAGE:
os << "DISCONNECT_WITH_WILL_MESSAGE";
break;
case DisconnectReasonCode::UNSPECIFIED_ERROR:
os << "UNSPECIFIED_ERROR";
break;
case DisconnectReasonCode::MALFORMED_PACKET:
os << "MALFORMED_PACKET";
break;
case DisconnectReasonCode::PROTOCOL_ERROR:
os << "PROTOCOL_ERROR";
break;
case DisconnectReasonCode::IMPLEMENTATION_SPECIFIC_ERROR:
os << "IMPLEMENTATION_SPECIFIC_ERROR";
break;
case DisconnectReasonCode::NOT_AUTHORIZED:
os << "NOT_AUTHORIZED";
break;
case DisconnectReasonCode::SERVER_BUSY:
os << "SERVER_BUSY";
break;
case DisconnectReasonCode::SERVER_SHUTING_DOWN:
os << "SERVER_SHUTING_DOWN";
break;
case DisconnectReasonCode::KEEPALIVE_TIMEOUT:
os << "KEEPALIVE_TIMEOUT";
break;
case DisconnectReasonCode::SESSION_TAKEN_OVER:
os << "SESSION_TAKEN_OVER";
break;
case DisconnectReasonCode::TOPIC_FILTER_INVALID:
os << "TOPIC_FILTER_INVALID";
break;
case DisconnectReasonCode::TOPIC_NAME_INVALID:
os << "TOPIC_NAME_INVALID";
break;
case DisconnectReasonCode::RECEIVE_MAXIMUM_EXCEEDED:
os << "RECEIVE_MAXIMUM_EXCEEDED";
break;
case DisconnectReasonCode::TOPIC_ALIAS_INVALID:
os << "TOPIC_ALIAS_INVALID";
break;
case DisconnectReasonCode::PACKET_TOO_LARGE:
os << "PACKET_TOO_LARGE";
break;
case DisconnectReasonCode::MESSAGE_RATE_TOO_HIGH:
os << "MESSAGE_RATE_TOO_HIGH";
break;
case DisconnectReasonCode::QUOTA_EXCEEDED:
os << "QUOTA_EXCEEDED";
break;
case DisconnectReasonCode::ADMINISTRATIVE_ACTION:
os << "ADMINISTRATIVE_ACTION";
break;
case DisconnectReasonCode::PAYLOAD_FORMAT_INVALID:
os << "PAYLOAD_FORMAT_INVALID";
break;
case DisconnectReasonCode::RETAIN_NOT_SUPPORTED:
os << "RETAIN_NOT_SUPPORTED";
break;
case DisconnectReasonCode::QOS_NOT_SUPPORTED:
os << "QOS_NOT_SUPPORTED";
break;
case DisconnectReasonCode::USER_ANOTHER_SERVER:
os << "USER_ANOTHER_SERVER";
break;
case DisconnectReasonCode::SERVER_MOVED:
os << "SERVER_MOVED";
break;
case DisconnectReasonCode::SHARED_SUBSCRIPTION_NOT_SUPPORTED:
os << "SHARED_SUBSCRIPTION_NOT_SUPPORTED";
break;
case DisconnectReasonCode::CONNECTION_RATE_EXCEEDED:
os << "CONNECTION_RATE_EXCEEDED";
break;
case DisconnectReasonCode::MAXIMUM_CONNECT_TIMEOUT:
os << "MAXIMUM_CONNECT_TIMEOUT";
break;
case DisconnectReasonCode::SUBSCRIPTIONS_IDENTIFIERS_NOT_SUPPORTED:
os << "SUBSCRIPTIONS_IDENTIFIERS_NOT_SUPPORTED";
break;
case DisconnectReasonCode::WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED:
os << "WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED";
break;
default:
os << "Unknown DisconnectReasonCode";
break;
}
return os;
}

View File

@@ -0,0 +1,97 @@
#ifndef INCLUDE_DISCONNECTION_DISCONNECT_PACKET_HPP_
#define INCLUDE_DISCONNECTION_DISCONNECT_PACKET_HPP_
#include <packet_interface.hpp>
#include <property.hpp>
/**
* @brief Enum representing the reason codes for disconnecting from MQTT.
*/
enum class DisconnectReasonCode : uint8_t {
NORMAL_DISCONNECTION = 0, /**< Close the connection normally. Do not send the Will Message. */
DISCONNECT_WITH_WILL_MESSAGE = 4, /**< The Client wishes to disconnect but requires that the Server also publishes its Will Message. */
UNSPECIFIED_ERROR = 128, /**< The Connection is closed but the sender either does not wish to reveal the reason, or none of the other Reason Codes apply. */
MALFORMED_PACKET = 129, /**< The received packet does not conform to this specification. */
PROTOCOL_ERROR = 130, /**< An unexpected or out of order packet was received. */
IMPLEMENTATION_SPECIFIC_ERROR = 131, /**< The packet received is valid but cannot be processed by this implementation. */
NOT_AUTHORIZED = 135, /**< The request is not authorized. */
SERVER_BUSY = 137, /**< The Server is busy and cannot continue processing requests from this Client. */
SERVER_SHUTING_DOWN = 139, /**< The Server is shutting down. */
KEEPALIVE_TIMEOUT = 141, /**< The Connection is closed because no packet has been received for 1.5 times the Keepalive time. */
SESSION_TAKEN_OVER = 142, /**< Another Connection using the same ClientID has connected causing this Connection to be closed. */
TOPIC_FILTER_INVALID = 143, /**< The Topic Filter is correctly formed, but is not accepted by this Sever. */
TOPIC_NAME_INVALID = 144, /**< The Topic Name is correctly formed, but is not accepted by this Client or Server. */
RECEIVE_MAXIMUM_EXCEEDED = 147, /**< The Client or Server has received more than Receive Maximum publication for which it has not sent PUBACK or PUBCOMP. */
TOPIC_ALIAS_INVALID = 148, /**< The Client or Server has received a PUBLISH packet containing a Topic Alias which is greater than the Maximum Topic Alias it sent in the CONNECT or CONNACK packet. */
PACKET_TOO_LARGE = 149, /**< The packet size is greater than Maximum Packet Size for this Client or Server. */
MESSAGE_RATE_TOO_HIGH = 150, /**< Message rate too high. */
QUOTA_EXCEEDED = 151, /**< An implementation or administrative imposed limit has been exceeded. */
ADMINISTRATIVE_ACTION = 152, /**< The Connection is closed due to an administrative action. */
PAYLOAD_FORMAT_INVALID = 153, /**< The payload format does not match the one specified by the Payload Format Indicator. */
RETAIN_NOT_SUPPORTED = 154, /**< The Server has does not support retained messages. */
QOS_NOT_SUPPORTED = 155, /**< The Client specified a QoS greater than the QoS specified in a Maximum QoS in the CONNACK. */
USER_ANOTHER_SERVER = 156, /**< The Client should temporarily change its Server. */
SERVER_MOVED = 157, /**< The Server is moved and the Client should permanently change its server location. */
SHARED_SUBSCRIPTION_NOT_SUPPORTED = 158, /**< The Server does not support Shared Subscriptions. */
CONNECTION_RATE_EXCEEDED = 159, /**< This connection is closed because the connection rate is too high. */
MAXIMUM_CONNECT_TIMEOUT = 160, /**< This connection is closed because the connection rate is too high. */
SUBSCRIPTIONS_IDENTIFIERS_NOT_SUPPORTED = 161, /**< The Server does not support Subscription Identifiers; the subscription is not accepted. */
WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED = 162, /**< The Server does not support Wildcard Subscriptions; the subscription is not accepted. */
};
/**
* @brief Overload of the output operator to print the DisconnectReasonCode.
*/
std::ostream &operator<<(std::ostream &os, DisconnectReasonCode code);
/**
* @brief Class representing a Disconnect Packet in MQTT communication.
*/
class DisconnectPacket : public IPacket {
public:
/**
* @brief Default constructor.
*/
DisconnectPacket();
/**
* @brief Copy constructor.
*
* @param other The DisconnectPacket object to copy.
*/
DisconnectPacket(IPacket &other);
/**
* @brief Constructs a DisconnectPacket object from byte vector data.
*
* @param data The byte vector containing the packet data.
*/
DisconnectPacket(const std::vector<std::byte> &data);
/**
* @brief Destructor.
*/
~DisconnectPacket() = default;
/**
* @brief Adds a property to the Disconnect Packet.
*
* @param prop The property identifier.
* @param value The property value.
*/
void add_property(const PropertyIdentifier &prop, const PropertyValue &value);
DisconnectReasonCode reason_code; /**< The reason code for disconnecting. */
/**
* @brief Converts the Disconnect Packet to a byte vector.
*
* @return The byte vector representation of the Disconnect Packet.
*/
std::vector<std::byte> as_bytes();
private:
MQTTProperties properties; /**< The properties of the Disconnect Packet. */
};
#endif // INCLUDE_DISCONNECTION_DISCONNECT_PACKET_HPP_

View File

@@ -0,0 +1,9 @@
FILE(GLOB CPP_FILES CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES CONFIGURE_DEPENDS *.hpp)
target_sources(libmqttd
PRIVATE ${CPP_FILES}
PUBLIC ${HPP_FILES}
)
target_include_directories(libmqttd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,60 @@
#include <fixed_header.hpp>
std::ostream &operator<<(std::ostream &lhs, PacketType p) {
std::string packetTypeString;
switch (p) {
case PacketType::RESERVED:
packetTypeString = "RESERVED";
break;
case PacketType::CONNECT:
packetTypeString = "CONNECT";
break;
case PacketType::CONNACK:
packetTypeString = "CONNACK";
break;
case PacketType::PUBLISH:
packetTypeString = "PUBLISH";
break;
case PacketType::PUBACK:
packetTypeString = "PUBACK";
break;
case PacketType::PUBREC:
packetTypeString = "PUBREC";
break;
case PacketType::PUBREL:
packetTypeString = "PUBREL";
break;
case PacketType::PUBCOMP:
packetTypeString = "PUBCOMP";
break;
case PacketType::SUBSCRIBE:
packetTypeString = "SUBSCRIBE";
break;
case PacketType::SUBACK:
packetTypeString = "SUBACK";
break;
case PacketType::UNSUBSCRIBE:
packetTypeString = "UNSUBSCRIBE";
break;
case PacketType::UNSUBACK:
packetTypeString = "UNSUBACK";
break;
case PacketType::PINGREQ:
packetTypeString = "PINGREQ";
break;
case PacketType::PINGRESP:
packetTypeString = "PINGRESP";
break;
case PacketType::DISCONNECT:
packetTypeString = "DISCONNECT";
break;
case PacketType::AUTH:
packetTypeString = "AUTH";
break;
default:
packetTypeString = "Unknown";
break;
}
return lhs << packetTypeString;
}

View File

@@ -0,0 +1,45 @@
#ifndef INCLUDE_PACKET_INTERFACE_FIXED_HEADER_HPP_
#define INCLUDE_PACKET_INTERFACE_FIXED_HEADER_HPP_
#include "variable_byte_int.hpp"
#include <bitset>
#include <ostream>
/**
* @brief Enumeration of MQTT packet types.
*/
enum class PacketType : uint8_t {
RESERVED = 0, /**< Reserved. */
CONNECT = 1, /**< CONNECT: Client request to connect to Server. */
CONNACK = 2, /**< CONNACK: Connect acknowledgment. */
PUBLISH = 3, /**< PUBLISH: Publish message. */
PUBACK = 4, /**< PUBACK: Publish acknowledgment. */
PUBREC = 5, /**< PUBREC: Publish received (assured delivery part 1). */
PUBREL = 6, /**< PUBREL: Publish release (assured delivery part 2). */
PUBCOMP = 7, /**< PUBCOMP: Publish complete (assured delivery part 3). */
SUBSCRIBE = 8, /**< SUBSCRIBE: Client subscribe request. */
SUBACK = 9, /**< SUBACK: Subscribe acknowledgment. */
UNSUBSCRIBE = 10, /**< UNSUBSCRIBE: Unsubscribe request. */
UNSUBACK = 11, /**< UNSUBACK: Unsubscribe acknowledgment. */
PINGREQ = 12, /**< PINGREQ: PING request. */
PINGRESP = 13, /**< PINGRESP: PING response. */
DISCONNECT = 14, /**< DISCONNECT: Client is disconnecting. */
AUTH = 15, /**< AUTH: Authentication exchange. */
UNSET = 99, /**< Unset or unknown type. */
};
/**
* @brief Overload of the output operator to print the packet type.
*/
std::ostream &operator<<(std::ostream &lhs, PacketType p);
/**
* @brief Structure representing the fixed header of an MQTT packet.
*/
struct FixedHeader {
PacketType packet_type = PacketType(0); /**< Packet type. */
std::bitset<4> packet_flags = {0}; /**< Packet flags. */
int_vb remaining_length = int_vb(0); /**< Remaining length. */
};
#endif // INCLUDE_PACKET_INTERFACE_FIXED_HEADER_HPP_

View File

@@ -0,0 +1,63 @@
#include "packet_interface.hpp"
#include <cstddef>
#include <vector>
#include <sstream>
IPacket::IPacket() {
this->raw_data = {};
this->fixed_header.packet_type = PacketType::UNSET;
this->fixed_header.remaining_length = 0;
}
IPacket::IPacket(const std::vector<std::byte> &data) {
this->raw_data = data;
unsigned char fixed_header_packet_type = (static_cast<unsigned char>(raw_data[0]) >> 4) & 0x0F;
this->fixed_header.packet_type = PacketType(fixed_header_packet_type);
unsigned char fixed_header_packet_flags = (static_cast<unsigned char>(raw_data[0]) & 0x0F);
this->fixed_header.packet_flags = fixed_header_packet_flags;
auto it = data.begin() + 1;
this->fixed_header.remaining_length = int_vb(it);
this->variable_header_start_byte = std::next(raw_data.begin(), fixed_header.remaining_length.size() + 1);
}
IPacket::IPacket(const IPacket &packet) {
this->fixed_header.packet_type = packet.fixed_header.packet_type;
this->fixed_header.packet_flags = packet.fixed_header.packet_flags;
this->fixed_header.remaining_length = packet.fixed_header.remaining_length;
this->variable_header_start_byte = packet.variable_header_start_byte;
this->raw_data = packet.raw_data;
}
std::string IPacket::as_string() const {
std::ostringstream packet_str;
packet_str << std::endl;
packet_str << "Packet Type = " << this->get_packet_type() << std::endl;
packet_str << "\t"
<< "\u21B3"
<< "Packet flags = " << this->get_packet_flags() << std::endl;
packet_str << "\t"
<< "\u21B3"
<< "Remaining length = " << this->fixed_header.remaining_length << std::endl;
return packet_str.str();
}
std::vector<std::byte> IPacket::as_bytes() {
std::vector<std::byte> packet_bytes;
uint8_t flags_value = static_cast<uint8_t>(fixed_header.packet_flags.to_ulong());
uint8_t first_byte = 0;
first_byte = static_cast<uint16_t>(fixed_header.packet_type) << 4; // Shift de 4 bits para deixar espaço para os flags
first_byte |= flags_value;
packet_bytes.emplace_back(static_cast<std::byte>(first_byte));
std::vector<std::byte> remaining_length_bytes = fixed_header.remaining_length.as_bytes();
packet_bytes.insert(packet_bytes.end(), remaining_length_bytes.begin(), remaining_length_bytes.end());
return packet_bytes;
}

View File

@@ -0,0 +1,94 @@
#ifndef INCLUDE_NETWORK_CONTROL_PACKET_HPP_
#define INCLUDE_NETWORK_CONTROL_PACKET_HPP_
#include "fixed_header.hpp"
#include <bit.hpp>
#include <exceptions.hpp>
#include <types.hpp>
#include <variable_byte_int.hpp>
#include <vector>
/**
* @brief Interface class for network control packets.
*
* This class serves as an interface for network control packets in MQTT communication.
*/
class IPacket {
public:
/**
* @brief Constructs a new IPacket object from a byte vector.
*
* @param data The byte vector containing the packet data.
*/
IPacket(const std::vector<std::byte> &data);
/**
* @brief Copy constructor.
*
* @param other The IPacket object to copy.
*/
IPacket(const IPacket &other);
/**
* @brief Default constructor.
*/
IPacket();
/**
* @brief Virtual destructor.
*/
virtual ~IPacket() = default;
/**
* @brief Gets the length of the packet.
*
* @return The length of the packet in bytes.
*/
inline uint length() const { return sizeof(this->fixed_header) + this->fixed_header.remaining_length; }
/**
* @brief Gets the packet type.
*
* @return The packet type.
*/
inline PacketType get_packet_type() const { return this->fixed_header.packet_type; };
/**
* @brief Gets the packet flags.
*
* @return The packet flags.
*/
inline std::bitset<4> get_packet_flags() const { return this->fixed_header.packet_flags; };
/**
* @brief Converts the packet to a string representation.
*
* @return The string representation of the packet.
*/
virtual std::string as_string() const;
/**
* @brief Converts the packet to a byte vector.
*
* @return The byte vector representation of the packet.
*/
virtual std::vector<std::byte> as_bytes();
protected:
std::vector<std::byte> raw_data; /**< The raw data of the packet. */
FixedHeader fixed_header; /**< The fixed header of the packet. */
/**
* @brief Parses the variable header of the packet.
*/
virtual void parse_variable_header() { throw NotImplemented(); };
/**
* @brief Parses the payload of the packet.
*/
virtual void parse_payload() { throw NotImplemented(); };
std::vector<std::byte>::const_iterator variable_header_start_byte; /**< Iterator pointing to the start of the variable header. */
};
#endif // INCLUDE_NETWORK_CONTROL_PACKET_HPP_

View File

@@ -0,0 +1,380 @@
#include "bytes.hpp"
#include "spdlog/spdlog.h"
#include "type_interface.hpp"
#include "utf8_string_pair.hpp"
#include <cstddef>
#include <iostream>
#include <iterator>
#include <ostream>
#include <property.hpp>
#include <queue>
#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
MQTTProperties::MQTTProperties() {
this->properties[PropertyIdentifier::USER_PROPERTY] = UserProperty();
this->length = 0;
}
MQTTProperties::MQTTProperties(const std::vector<std::byte>::const_iterator &property_start) {
this->properties[PropertyIdentifier::USER_PROPERTY] = UserProperty();
this->length = int_vb(property_start);
spdlog::trace("Parsing Properties with length " + std::to_string(length));
auto current_byte = std::next(property_start, this->length.size());
while (std::distance(property_start, current_byte) < this->length) {
uint next_property_offset = 0;
PropertyIdentifier current_property = PropertyIdentifier(std::to_integer<uint8_t>(*current_byte));
std::ostringstream log_msg;
log_msg << "Parsing property " << current_property << " (ID " << std::to_string(int(current_property)) << ")";
spdlog::trace(log_msg.str());
switch (current_property) {
case PropertyIdentifier::PAYLOAD_FORMAT_INDICATOR: {
next_property_offset = 2;
std::byte property_value = *(std::next(current_byte, 1));
this->properties[PropertyIdentifier::PAYLOAD_FORMAT_INDICATOR] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::MESSAGE_EXPIRY_INTERVAL: {
uint32_t property_value = utilities::types::get_fixed_size_integer<uint32_t>(std::next(current_byte));
;
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::MESSAGE_EXPIRY_INTERVAL] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::CONTENT_TYPE: {
utf8_str property_value(std::next(current_byte));
this->properties[PropertyIdentifier::CONTENT_TYPE] = PropertyValue{property_value};
next_property_offset = 1 + property_value.as_bytes().size();
break;
}
case PropertyIdentifier::RESPONSE_TOPIC: {
utf8_str property_value(std::next(current_byte));
this->properties[PropertyIdentifier::RESPONSE_TOPIC] = PropertyValue{property_value};
next_property_offset = 1 + property_value.as_bytes().size();
break;
}
case PropertyIdentifier::CORRELATION_DATA: {
binary_data property_value(std::next(current_byte));
this->properties[PropertyIdentifier::CORRELATION_DATA] = PropertyValue{property_value};
next_property_offset = 1 + property_value.as_bytes().size();
}
case PropertyIdentifier::SUBSCRIPTION_IDENTIFIER: {
int_vb property_value(current_byte);
this->properties[PropertyIdentifier::CORRELATION_DATA] = PropertyValue{property_value};
next_property_offset = 1 + property_value.as_bytes().size();
}
case PropertyIdentifier::SESSION_EXPIRY_INTERVAL: {
uint32_t property_value = utilities::types::get_fixed_size_integer<uint32_t>(std::next(current_byte));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::SESSION_EXPIRY_INTERVAL] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::ASSIGNED_CLIENT_IDENTIFIER: {
utf8_str property_value(std::next(current_byte));
this->properties[PropertyIdentifier::ASSIGNED_CLIENT_IDENTIFIER] = PropertyValue{property_value};
next_property_offset = 1 + property_value.as_bytes().size();
break;
}
case PropertyIdentifier::SERVER_KEEP_ALIVE: {
uint16_t property_value = utilities::types::get_fixed_size_integer<uint16_t>(std::next(current_byte));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::SERVER_KEEP_ALIVE] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::AUTHENTICATION_METHOD: {
utf8_str property_value(std::next(current_byte));
this->properties[PropertyIdentifier::AUTHENTICATION_METHOD] = PropertyValue{property_value};
next_property_offset = 1 + property_value.as_bytes().size();
break;
}
case PropertyIdentifier::AUTHENTICATION_DATA: {
binary_data property_value(std::next(current_byte));
this->properties[PropertyIdentifier::AUTHENTICATION_DATA] = PropertyValue{property_value};
next_property_offset = 1 + property_value.as_bytes().size();
}
case PropertyIdentifier::REQUEST_PROBLEM_INFORMATION: {
next_property_offset = 2;
std::byte property_value = *(std::next(current_byte, 1));
this->properties[PropertyIdentifier::REQUEST_PROBLEM_INFORMATION] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::WILL_DELAY_INTERVAL: {
uint32_t property_value = utilities::types::get_fixed_size_integer<uint32_t>(std::next(current_byte));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::WILL_DELAY_INTERVAL] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::REQUEST_RESPONSE_INFORMATION: {
next_property_offset = 2;
std::byte property_value = *(std::next(current_byte, 1));
this->properties[PropertyIdentifier::REQUEST_RESPONSE_INFORMATION] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::RESPONSE_INFORMATION: {
utf8_str property_value(std::next(current_byte));
this->properties[PropertyIdentifier::RESPONSE_INFORMATION] = PropertyValue{property_value};
next_property_offset = 1 + property_value.as_bytes().size();
break;
}
case PropertyIdentifier::SERVER_REFERENCE: {
utf8_str property_value(std::next(current_byte));
this->properties[PropertyIdentifier::SERVER_REFERENCE] = PropertyValue{property_value};
next_property_offset = 1 + property_value.as_bytes().size();
break;
}
case PropertyIdentifier::REASON_STRING: {
utf8_str property_value(std::next(current_byte));
this->properties[PropertyIdentifier::REASON_STRING] = PropertyValue{property_value};
next_property_offset = 1 + property_value.as_bytes().size();
break;
}
case PropertyIdentifier::RECEIVE_MAXIMUM: {
uint16_t property_value = utilities::types::get_fixed_size_integer<uint16_t>(std::next(current_byte));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::RECEIVE_MAXIMUM] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::TOPIC_ALIAS_MAXIMUM: {
uint16_t property_value = utilities::types::get_fixed_size_integer<uint16_t>(std::next(current_byte));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::RECEIVE_MAXIMUM] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::TOPIC_ALIAS: {
uint16_t property_value = utilities::types::get_fixed_size_integer<uint16_t>(std::next(current_byte));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::RECEIVE_MAXIMUM] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::MAXIMUM_QOS: {
std::byte property_value = *(std::next(current_byte, 1));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::MAXIMUM_QOS] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::RETAIN_AVAILABLE: {
std::byte property_value = *(std::next(current_byte, 1));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::RETAIN_AVAILABLE] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::USER_PROPERTY: {
UTF8StringPair property_value(std::next(current_byte));
auto &user_property_list = std::get<UserProperty>(this->properties[PropertyIdentifier::USER_PROPERTY]);
user_property_list.push(property_value);
next_property_offset = 1 + property_value.as_bytes().size();
break;
}
case PropertyIdentifier::MAXIMUM_PACKET_SIZE: {
uint32_t property_value = utilities::types::get_fixed_size_integer<uint32_t>(std::next(current_byte));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::RECEIVE_MAXIMUM] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::WILDCARD_SUBSCRIPTION_AVAILABLE: {
std::byte property_value = *(std::next(current_byte, 1));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::WILDCARD_SUBSCRIPTION_AVAILABLE] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::SUBSCRIPTION_IDENTIFIER_AVAILABLE: {
std::byte property_value = *(std::next(current_byte, 1));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::SUBSCRIPTION_IDENTIFIER_AVAILABLE] = PropertyValue{property_value};
break;
}
case PropertyIdentifier::SHARED_SUBSCRIPTION_AVAILABLE: {
std::byte property_value = *(std::next(current_byte, 1));
next_property_offset = 1 + sizeof(property_value);
this->properties[PropertyIdentifier::SHARED_SUBSCRIPTION_AVAILABLE] = PropertyValue{property_value};
break;
}
default: {
uint8_t byte_value = std::to_integer<uint8_t>(*current_byte);
throw std::runtime_error("Invalid property received (" + std::to_string(byte_value) + ")");
}
}
current_byte = std::next(current_byte, next_property_offset);
}
}
std::vector<std::byte> MQTTProperties::as_bytes() const {
std::vector<std::byte> properties_bytes;
std::vector<std::byte> length_bytes = this->length.as_bytes();
properties_bytes.insert(properties_bytes.end(), length_bytes.begin(), length_bytes.end());
for (const auto &[prop_identifier, prop_value] : this->properties) {
std::vector<std::byte> property_bytes;
std::visit(
[&property_bytes](const auto &value) {
using T = std::decay_t<decltype(value)>;
if constexpr (std::is_base_of_v<TypeInterface, T>) {
property_bytes = value.as_bytes();
} else if constexpr (std::is_base_of_v<std::queue<UTF8StringPair>, T>) {
std::queue<UTF8StringPair> temp_queue = value;
// UserProperty is initalized as an empty queue, if we count that we will add an extra byte in payload resulting in inconsistencies
if (temp_queue.empty()) {
property_bytes.clear();
return;
}
while (!temp_queue.empty()) {
auto element_bytes = temp_queue.front().as_bytes();
temp_queue.pop();
property_bytes.insert(property_bytes.end(), element_bytes.begin(), element_bytes.end());
}
} else {
property_bytes = utilities::bytes::to_bytes(value);
}
},
prop_value);
if (!property_bytes.empty()) {
properties_bytes.push_back(static_cast<std::byte>(prop_identifier));
properties_bytes.insert(properties_bytes.end(), property_bytes.begin(), property_bytes.end());
}
}
return properties_bytes;
}
PropertyValue& MQTTProperties::operator[](PropertyIdentifier prop) {
return this->properties[prop];
this->length = int(this->length)+ 1;
}
struct VariantPrinter {
std::ostream &os;
template <typename T> void operator()(const T &value) const { os << value; }
};
std::ostream &operator<<(std::ostream &os, const PropertyIdentifier &value) {
switch (value) {
case PropertyIdentifier::PAYLOAD_FORMAT_INDICATOR:
os << "PAYLOAD_FORMAT_INDICATOR";
break;
case PropertyIdentifier::MESSAGE_EXPIRY_INTERVAL:
os << "MESSAGE_EXPIRY_INTERVAL";
break;
case PropertyIdentifier::CONTENT_TYPE:
os << "CONTENT_TYPE";
break;
case PropertyIdentifier::RESPONSE_TOPIC:
os << "RESPONSE_TOPIC";
break;
case PropertyIdentifier::CORRELATION_DATA:
os << "CORRELATION_DATA";
break;
case PropertyIdentifier::SUBSCRIPTION_IDENTIFIER:
os << "SUBSCRIPTION_IDENTIFIER";
break;
case PropertyIdentifier::SESSION_EXPIRY_INTERVAL:
os << "SESSION_EXPIRY_INTERVAL";
break;
case PropertyIdentifier::ASSIGNED_CLIENT_IDENTIFIER:
os << "ASSIGNED_CLIENT_IDENTIFIER";
break;
case PropertyIdentifier::SERVER_KEEP_ALIVE:
os << "SERVER_KEEP_ALIVE";
break;
case PropertyIdentifier::AUTHENTICATION_METHOD:
os << "AUTHENTICATION_METHOD";
break;
case PropertyIdentifier::AUTHENTICATION_DATA:
os << "AUTHENTICATION_DATA";
break;
case PropertyIdentifier::REQUEST_PROBLEM_INFORMATION:
os << "REQUEST_PROBLEM_INFORMATION";
break;
case PropertyIdentifier::WILL_DELAY_INTERVAL:
os << "WILL_DELAY_INTERVAL";
break;
case PropertyIdentifier::REQUEST_RESPONSE_INFORMATION:
os << "REQUEST_RESPONSE_INFORMATION";
break;
case PropertyIdentifier::RESPONSE_INFORMATION:
os << "RESPONSE_INFORMATION";
break;
case PropertyIdentifier::SERVER_REFERENCE:
os << "SERVER_REFERENCE";
break;
case PropertyIdentifier::REASON_STRING:
os << "REASON_STRING";
break;
case PropertyIdentifier::RECEIVE_MAXIMUM:
os << "RECEIVE_MAXIMUM";
break;
case PropertyIdentifier::TOPIC_ALIAS_MAXIMUM:
os << "TOPIC_ALIAS_MAXIMUM";
break;
case PropertyIdentifier::TOPIC_ALIAS:
os << "TOPIC_ALIAS";
break;
case PropertyIdentifier::MAXIMUM_QOS:
os << "MAXIMUM_QOS";
break;
case PropertyIdentifier::RETAIN_AVAILABLE:
os << "RETAIN_AVAILABLE";
break;
case PropertyIdentifier::USER_PROPERTY:
os << "USER_PROPERTY";
break;
case PropertyIdentifier::MAXIMUM_PACKET_SIZE:
os << "MAXIMUM_PACKET_SIZE";
break;
case PropertyIdentifier::WILDCARD_SUBSCRIPTION_AVAILABLE:
os << "WILDCARD_SUBSCRIPTION_AVAILABLE";
break;
case PropertyIdentifier::SUBSCRIPTION_IDENTIFIER_AVAILABLE:
os << "SUBSCRIPTION_IDENTIFIER_AVAILABLE";
break;
case PropertyIdentifier::SHARED_SUBSCRIPTION_AVAILABLE:
os << "SHARED_SUBSCRIPTION_AVAILABLE";
break;
default:
os << "Unknown PropertyIdentifier";
break;
}
return os;
}
std::ostream &operator<<(std::ostream &os, const std::byte &value) {
os << std::to_string(std::to_integer<uint8_t>(value));
return os;
}
std::ostream &operator<<(std::ostream &os, const PropertyValue &value) {
std::visit(VariantPrinter{os}, value);
return os;
}
std::ostream &operator<<(std::ostream &os, const UserProperty &value) {
UserProperty queue = value;
os << std::endl;
while (!queue.empty()) {
UTF8StringPair queue_value = queue.front();
os << "\t\t\t\t\u21B3(" << queue_value.get_key() << ", " << queue_value.get_value() << ")" << std::endl;
queue.pop();
}
return os;
}
std::ostream &operator<<(std::ostream &os, const PropertyMap &value) {
for (const auto &[property_key, property_value] : value) {
os << "\t\t\t\u21B3" << property_key << ": " << property_value << std::endl;
}
return os;
}
std::ostream &operator<<(std::ostream &os, const MQTTProperties &value) {
os << "\t\t\u21B3"
<< "Property Length = " << value.length << std::endl;
os << "\t\t\u21B3"
<< "Property Values" << std::endl;
os << value.properties;
return os;
}

View File

@@ -0,0 +1,119 @@
#ifndef INCLUDE_CONTROL_PACKET_PROPERTY_HPP_
#define INCLUDE_CONTROL_PACKET_PROPERTY_HPP_
#include <binary_data.hpp>
#include <cstddef>
#include <cstdint>
#include <map>
#include <ostream>
#include <queue>
#include <spdlog/spdlog.h>
#include <types.hpp>
#include <utf8_string.hpp>
#include <utf8_string_pair.hpp>
#include <variable_byte_int.hpp>
#include <variant>
/**
* @brief Enumeration of MQTT Control Packet Property Identifiers.
*/
enum class PropertyIdentifier : uint32_t {
PAYLOAD_FORMAT_INDICATOR = 1, /**< Payload Format Indicator. */
MESSAGE_EXPIRY_INTERVAL = 2, /**< Message Expiry Interval. */
CONTENT_TYPE = 3, /**< Content Type. */
RESPONSE_TOPIC = 8, /**< Response Topic. */
CORRELATION_DATA = 9, /**< Correlation Data. */
SUBSCRIPTION_IDENTIFIER = 11, /**< Subscription Identifier. */
SESSION_EXPIRY_INTERVAL = 17, /**< Session Expiry Interval. */
ASSIGNED_CLIENT_IDENTIFIER = 18, /**< Assigned Client Identifier. */
SERVER_KEEP_ALIVE = 19, /**< Server Keep Alive. */
AUTHENTICATION_METHOD = 21, /**< Authentication Method. */
AUTHENTICATION_DATA = 22, /**< Authentication Data. */
REQUEST_PROBLEM_INFORMATION = 23, /**< Request Problem Information. */
WILL_DELAY_INTERVAL = 24, /**< Will Delay Interval. */
REQUEST_RESPONSE_INFORMATION = 25, /**< Request Response Information. */
RESPONSE_INFORMATION = 26, /**< Response Information. */
SERVER_REFERENCE = 28, /**< Server Reference. */
REASON_STRING = 31, /**< Reason String. */
RECEIVE_MAXIMUM = 33, /**< Receive Maximum. */
TOPIC_ALIAS_MAXIMUM = 34, /**< Topic Alias Maximum. */
TOPIC_ALIAS = 35, /**< Topic Alias. */
MAXIMUM_QOS = 36, /**< Maximum QoS. */
RETAIN_AVAILABLE = 37, /**< Retain Available. */
USER_PROPERTY = 38, /**< User Property. */
MAXIMUM_PACKET_SIZE = 39, /**< Maximum Packet Size. */
WILDCARD_SUBSCRIPTION_AVAILABLE = 40, /**< Wildcard Subscription Available. */
SUBSCRIPTION_IDENTIFIER_AVAILABLE = 41, /**< Subscription Identifier Available. */
SHARED_SUBSCRIPTION_AVAILABLE = 42 /**< Shared Subscription Available. */
};
using UserProperty = std::queue<UTF8StringPair>; /**< User Property type definition. */
using PropertyValue = std::variant<std::byte, uint32_t, uint16_t, int_vb, binary_data, utf8_str, UserProperty>; /**< Property Value type definition. */
using PropertyMap = std::map<PropertyIdentifier, PropertyValue>; /**< Property Map type definition. */
std::ostream &operator<<(std::ostream &os, const UserProperty &value);
std::ostream &operator<<(std::ostream &os, const PropertyIdentifier &value);
std::ostream &operator<<(std::ostream &os, const PropertyValue &value);
std::ostream &operator<<(std::ostream &os, const PropertyMap &value);
/**
* @brief Class representing MQTT Control Packet Properties.
*/
class MQTTProperties {
public:
/**
* @brief Default constructor.
*/
MQTTProperties();
/**
* @brief Constructs MQTTProperties from a byte vector.
*
* @param property_start Iterator pointing to the start of the property data in the byte vector.
*/
MQTTProperties(const std::vector<std::byte>::const_iterator &property_start);
/**
* @brief Destructor.
*/
~MQTTProperties() = default;
/**
* @brief Converts MQTTProperties to a byte vector.
*
* @return Byte vector representation of MQTTProperties.
*/
std::vector<std::byte> as_bytes() const;
/**
* @brief Gets the size of MQTTProperties.
*
* @return The size of MQTTProperties.
*/
uint16_t size() const { return this->length; };
/**
* @brief Gets the property value for the specified property identifier.
*
* @param prop The property identifier.
* @return The property value.
*/
PropertyValue get_property(const PropertyIdentifier &prop) { return this->properties[prop]; };
/**
* @brief Overloads the subscript operator to access properties by identifier.
*
* @param prop The property identifier.
* @return The property value.
*/
PropertyValue &operator[](PropertyIdentifier prop);
friend std::ostream &operator<<(std::ostream &os, const std::byte &value);
friend std::ostream &operator<<(std::ostream &os, const MQTTProperties &value);
private:
int_vb length; /**< Length of the properties. */
PropertyMap properties; /**< Map containing MQTT properties. */
};
#endif // INCLUDE_CONTROL_PACKET_PROPERTY_HPP_

View File

@@ -0,0 +1,11 @@
add_subdirectory(session)
FILE(GLOB CPP_FILES_LOCAL CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES_LOCAL CONFIGURE_DEPENDS *.hpp)
target_sources(libmqttd
PRIVATE ${CPP_FILES_LOCAL}
PUBLIC ${HPP_FILES_LOCAL}
)
target_link_libraries(libmqttd PRIVATE spdlog)
target_include_directories(libmqttd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,11 @@
add_subdirectory(states)
FILE(GLOB CPP_FILES_LOCAL CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES_LOCAL CONFIGURE_DEPENDS *.hpp)
target_sources(libmqttd
PRIVATE ${CPP_FILES_LOCAL}
PUBLIC ${HPP_FILES_LOCAL}
)
target_link_libraries(libmqttd PRIVATE spdlog)
target_include_directories(libmqttd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,123 @@
#include "disconnection/disconnect_packet.hpp"
#include "packet_interface.hpp"
#include "state_disconnect.hpp"
#include "state_waiting_connection.hpp"
#include <chrono>
#include <connect_packet.hpp>
#include <spdlog/spdlog.h>
#include <state_connect.hpp>
#include <cstddef>
#include <iostream>
#include <session.hpp>
#include <sstream>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
#include <vector>
Session::Session(int socket_fd) {
this->socket = socket_fd;
this->current_state = nullptr;
this->current_packet = nullptr;
}
Session::~Session() {
// TODO: It's a normal disconnection here?
if (this->is_alive() || this->is_connected())
this->close(DisconnectReasonCode::NORMAL_DISCONNECTION);
}
void Session::close(const DisconnectReasonCode &reason_code) {
DisconnectPacket disconnect_packet;
disconnect_packet.reason_code = reason_code;
this->send(disconnect_packet.as_bytes());
this->close();
}
void Session::close() {
shutdown(this->socket, SHUT_RDWR);
::close(this->socket);
this->is_session_connected = false;
this->on_disconnect(this);
this->is_session_alive = false;
if (this->keepalive_thread.joinable())
this->keepalive_thread.join();
}
std::size_t Session::send(const std::vector<std::byte> &buffer) {
std::ostringstream log_msg;
log_msg << "Sending " << buffer.size() << " bytes";
spdlog::trace(log_msg.str());
::send(this->socket, buffer.data(), buffer.size(), 0);
return buffer.size();
}
void Session::set_state(ISessionState &state) {
this->current_state->exit(this);
this->current_state = &state;
this->current_state->enter(this);
this->current_state->process(this);
}
void Session::close_if_not_connected(uint timeout_sec) {
std::this_thread::sleep_for(std::chrono::seconds(timeout_sec));
if (this->is_connected())
return;
std::ostringstream log_msg;
log_msg << "Session timed out while waiting for CONNECT packet (timeout is " << timeout_sec << " seconds)";
spdlog::warn(log_msg.str());
this->close(DisconnectReasonCode::MAXIMUM_CONNECT_TIMEOUT);
}
void Session::listen() {
std::vector<std::byte> buffer(buffer_size);
this->current_state = &StateWaitingConnection::get_instance();
this->is_session_alive = true;
this->is_session_connected = false;
while (this->is_alive()) {
buffer.clear();
buffer.resize(buffer_size);
std::size_t bytes_received = recv(this->socket, buffer.data(), buffer.size(), 0);
if (bytes_received < 0) {
int errno_tmp = errno;
spdlog::error("Failed to receive data on socket: " + std::string(strerror(errno_tmp)));
return;
}
if (bytes_received == 0) {
std::ostringstream log_msg;
log_msg << "Session ";
if (!client_id.empty())
log_msg << "with client id " << client_id << " ";
log_msg << "was gracefully shutdown by client";
spdlog::debug(log_msg.str());
return;
}
this->current_packet = new IPacket(buffer);
switch (this->current_packet->get_packet_type()) {
case PacketType::CONNECT: {
this->set_state(StateConnect::get_instance());
break;
}
case PacketType::DISCONNECT: {
this->set_state(StateDisconnect::get_instance());
break;
}
default: {
std::string packet_type = std::to_string(static_cast<int>(this->current_packet->get_packet_type()));
spdlog::error("Received unknown packet type " + packet_type);
break;
}
}
delete this->current_packet;
}
}

View File

@@ -0,0 +1,135 @@
#ifndef INCLUDE_PROTOCOL_SESSION_HPP_
#define INCLUDE_PROTOCOL_SESSION_HPP_
#include "disconnection/disconnect_packet.hpp"
#include <packet_interface.hpp>
#include <cstddef>
#include <spdlog/spdlog.h>
#include <string>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
#include <vector>
// Forward declaration to resolve circular dependency
class ISessionState;
#include <state_interface.hpp>
/**
* @brief Class representing a session in the MQTT protocol.
*
* This class manages the communication session with an MQTT client.
*/
class Session {
public:
/**
* @brief Default constructor.
*/
Session();
/**
* @brief Constructor initializing the session with a socket file descriptor.
*
* @param socket_fd The socket file descriptor.
*/
Session(int socket_fd);
/**
* @brief Destructor.
*/
~Session();
std::string client_id; /**< The client ID associated with the session. */
/**
* @brief Starts listening for incoming data on the session socket.
*/
void listen();
/**
* @brief Closes the session.
*/
void close();
/**
* @brief Closes the session with a specific reason code.
*
* @param reason_code The reason code for disconnection.
*/
void close(const DisconnectReasonCode &reason_code);
/**
* @brief Sets the current state of the session.
*
* @param state The state to set.
*/
void set_state(ISessionState &state);
/**
* @brief Checks if the session is alive.
*
* @return True if the session is alive, false otherwise.
*/
inline bool is_alive() const { return this->is_session_alive; };
/**
* @brief Checks if the session is connected.
*
* @return True if the session is connected, false otherwise.
*/
inline bool is_connected() const { return this->is_session_connected; };
/**
* @brief Gets the current packet being processed by the session.
*
* @return A pointer to the current packet.
*/
inline IPacket *get_current_packet() const { return this->current_packet; };
/**
* @brief Gets the current state of the session.
*
* @return A pointer to the current state.
*/
inline ISessionState *get_current_state() const { return this->current_state; };
/**
* @brief Sends data over the session socket.
*
* @param buffer The data to send.
* @return The number of bytes sent.
*/
std::size_t send(const std::vector<std::byte> &buffer);
std::function<void(Session *)> on_connect; /**< Callback function invoked when a client connects. */
std::function<void(Session *)> on_disconnect; /**< Callback function invoked when a client disconnects. */
protected:
friend class StateConnect;
friend class StateDisconnect;
std::atomic<bool> is_session_alive; /**< Flag indicating if the session is alive. */
std::atomic<bool> is_session_connected; /**< Flag indicating if the session is connected. */
private:
ISessionState *current_state; /**< Pointer to the current session state. */
IPacket *current_packet; /**< Pointer to the current packet being processed. */
int socket; /**< Socket file descriptor for the session. */
std::thread keepalive_thread; /**< Thread for handling keepalive messages. */
const unsigned int buffer_size = 65535; /**< Max TCP packet bytes accepted in a single receive call. */
/**
* @brief Joins the keepalive thread.
*/
void join();
/**
* @brief Closes the session if it's not connected within a timeout period.
*
* @param timeout_sec Timeout period in seconds.
*/
void close_if_not_connected(uint timeout_sec = 10);
};
#endif // INCLUDE_PROTOCOL_SESSION_HPP_

View File

@@ -0,0 +1,9 @@
FILE(GLOB CPP_FILES_LOCAL CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES_LOCAL CONFIGURE_DEPENDS *.hpp)
target_sources(libmqttd
PRIVATE ${CPP_FILES_LOCAL}
PUBLIC ${HPP_FILES_LOCAL}
)
target_link_libraries(libmqttd PRIVATE spdlog)
target_include_directories(libmqttd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,53 @@
#include "connect_ack.hpp"
#include "connect_packet.hpp"
#include "packet_interface.hpp"
#include "spdlog/spdlog.h"
#include <sstream>
#include <state_connect.hpp>
#include <state_disconnect.hpp>
#include <stdexcept>
ISessionState &StateConnect::get_instance() {
static StateConnect singleton;
return singleton;
}
void StateConnect::enter(Session *session) {
std::ostringstream log_msg;
log_msg << "Session entered CONNECT state";
spdlog::trace(log_msg.str());
}
void StateConnect::exit(Session *session) {
std::ostringstream log_msg;
log_msg << "Session exited CONNECT state";
spdlog::trace(log_msg.str());
}
void StateConnect::process(Session *session) {
if (session->is_connected()) {
std::ostringstream log_msg;
log_msg << "Session " << session->client_id << " is already connected, this is a protocol error and this session will be closed";
spdlog::error(log_msg.str());
session->set_state(StateDisconnect::get_instance());
}
ConnectACK ack_packet;
ack_packet.set_session_present(false);
try {
IPacket *packet_interface = session->get_current_packet();
ConnectPacket packet(*packet_interface);
// TODO: Validate and auth packet
// TODO: Generate a client id if none as provided
session->client_id = packet.get_client_id();
session->on_connect(session);
ack_packet.set_reason_code(ConnectReasonCode::SUCCESS);
session->is_session_connected = true;
} catch (const std::invalid_argument &ex) {
ack_packet.set_reason_code(ConnectReasonCode::MALFORMED_PACKET);
session->is_session_connected = false;
}
session->send(ack_packet.as_bytes());
}

View File

@@ -0,0 +1,51 @@
#ifndef INCLUDE_FTM_STATE_CONNECT_HPP_
#define INCLUDE_FTM_STATE_CONNECT_HPP_
#include "state_interface.hpp"
/**
* @brief Class representing the state of establishing a connection in the MQTT protocol.
*
* This class defines the behavior and transitions associated with the connection state of a session.
*/
class StateConnect : public ISessionState {
public:
/**
* @brief Method called when entering the connection state.
*
* @param session Pointer to the session associated with the state.
*/
void enter(Session *session) final;
/**
* @brief Method called to process the session in the connection state.
*
* @param session Pointer to the session associated with the state.
*/
void process(Session *session) final;
/**
* @brief Method called when exiting the connection state.
*
* @param session Pointer to the session associated with the state.
*/
void exit(Session *session) final;
/**
* @brief Get the singleton instance of the connection state.
*
* @return Reference to the singleton instance of the connection state.
*/
static ISessionState &get_instance();
private:
/**
* @brief Private constructor to prevent external instantiation.
*/
StateConnect(){};
StateConnect(const StateConnect &); /**< Copy constructor. */
StateConnect &operator=(const StateConnect &); /**< Assignment operator. */
};
#endif // INCLUDE_FTM_STATE_CONNECT_HPP_

View File

@@ -0,0 +1,36 @@
#include "disconnect_packet.hpp"
#include "packet_interface.hpp"
#include "spdlog/spdlog.h"
#include <sstream>
#include <state_disconnect.hpp>
ISessionState &StateDisconnect::get_instance() {
static StateDisconnect singleton;
return singleton;
}
void StateDisconnect::enter(Session *session) {
std::ostringstream log_msg;
log_msg << "Session " << session->client_id << " entered DISCONNECT state";
spdlog::trace(log_msg.str());
}
void StateDisconnect::exit(Session *session) {
std::ostringstream log_msg;
log_msg << "Session " << session->client_id << " exited DISCONNECT state";
spdlog::trace(log_msg.str());
}
void StateDisconnect::process(Session *session) {
IPacket *packet_interface = session->get_current_packet();
DisconnectPacket disconnect_packet(*packet_interface);
std::ostringstream log_msg;
log_msg << "Session " << session->client_id << " was disconnected with reason code " << disconnect_packet.reason_code;
if (disconnect_packet.reason_code != DisconnectReasonCode::NORMAL_DISCONNECTION) {
spdlog::warn(log_msg.str());
return;
}
spdlog::info(log_msg.str());
session->close();
}

View File

@@ -0,0 +1,51 @@
#ifndef INCLUDE_STATES_STATE_DISCONNECT_HPP_
#define INCLUDE_STATES_STATE_DISCONNECT_HPP_
#include "state_interface.hpp"
/**
* @brief Class representing the state of disconnecting in the MQTT protocol.
*
* This class defines the behavior and transitions associated with the disconnect state of a session.
*/
class StateDisconnect : public ISessionState {
public:
/**
* @brief Method called when entering the disconnect state.
*
* @param session Pointer to the session associated with the state.
*/
void enter(Session *session) final;
/**
* @brief Method called to process the session in the disconnect state.
*
* @param session Pointer to the session associated with the state.
*/
void process(Session *session) final;
/**
* @brief Method called when exiting the disconnect state.
*
* @param session Pointer to the session associated with the state.
*/
void exit(Session *session) final;
/**
* @brief Get the singleton instance of the disconnect state.
*
* @return Reference to the singleton instance of the disconnect state.
*/
static ISessionState &get_instance();
private:
/**
* @brief Private constructor to prevent external instantiation.
*/
StateDisconnect(){};
StateDisconnect(const StateDisconnect &); /**< Copy constructor. */
StateDisconnect &operator=(const StateDisconnect &); /**< Assignment operator. */
};
#endif // INCLUDE_STATES_STATE_DISCONNECT_HPP_

View File

@@ -0,0 +1,48 @@
#ifndef INCLUDE_FTM_STATE_INTERFACE_HPP_
#define INCLUDE_FTM_STATE_INTERFACE_HPP_
#include <packet_interface.hpp>
// Forward declaration to resolve circular dependency
class Session;
#include <session.hpp>
/**
* @brief Interface class for defining session states in the MQTT protocol.
*
* This class serves as an interface for defining different states of a session in the MQTT protocol.
*/
class ISessionState {
public:
/**
* @brief Virtual destructor.
*/
virtual ~ISessionState(){};
/**
* @brief Method called when entering the state.
*
* @param session Pointer to the session associated with the state.
*/
virtual void enter(Session * session) = 0;
/**
* @brief Method called to process the session in the state.
*
* @param session Pointer to the session associated with the state.
*/
virtual void process(Session * session) = 0;
/**
* @brief Method called when exiting the state.
*
* @param session Pointer to the session associated with the state.
*/
virtual void exit(Session * session) = 0;
private:
IPacket packet; /**< Packet associated with the state. */
};
#endif // INCLUDE_FTM_STATE_INTERFACE_HPP_

View File

@@ -0,0 +1,24 @@
#include <sstream>
#include <state_waiting_connection.hpp>
ISessionState &StateWaitingConnection::get_instance() {
static StateWaitingConnection singleton;
return singleton;
}
void StateWaitingConnection::enter(Session *session) {
std::ostringstream log_msg;
log_msg << "Session entered WAITING CONNECTION state";
spdlog::trace(log_msg.str());
}
void StateWaitingConnection::exit(Session *session) {
std::ostringstream log_msg;
log_msg << "Session exited WAITING CONNECTION state";
spdlog::trace(log_msg.str());
}
void StateWaitingConnection::process(Session *session) {
return;
}

View File

@@ -0,0 +1,51 @@
#ifndef INCLUDE_STATES_STATE_WAITING_CONNECTION_HPP_
#define INCLUDE_STATES_STATE_WAITING_CONNECTION_HPP_
#include "state_interface.hpp"
/**
* @brief Class representing the state of waiting for a connection in the MQTT protocol.
*
* This class defines the behavior and transitions associated with the state of waiting for a connection in a session.
*/
class StateWaitingConnection : public ISessionState {
public:
/**
* @brief Method called when entering the state of waiting for a connection.
*
* @param session Pointer to the session associated with the state.
*/
void enter(Session *session) final;
/**
* @brief Method called to process the session in the state of waiting for a connection.
*
* @param session Pointer to the session associated with the state.
*/
void process(Session *session) final;
/**
* @brief Method called when exiting the state of waiting for a connection.
*
* @param session Pointer to the session associated with the state.
*/
void exit(Session *session) final;
/**
* @brief Get the singleton instance of the state of waiting for a connection.
*
* @return Reference to the singleton instance of the state of waiting for a connection.
*/
static ISessionState &get_instance();
private:
/**
* @brief Private constructor to prevent external instantiation.
*/
StateWaitingConnection() {};
StateWaitingConnection(const StateWaitingConnection &); /**< Copy constructor. */
StateWaitingConnection &operator=(const StateWaitingConnection &); /**< Assignment operator. */
};
#endif // INCLUDE_STATES_STATE_WAITING_CONNECTION_HPP_

View File

@@ -0,0 +1,10 @@
FILE(GLOB CPP_FILES_LOCAL CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES_LOCAL CONFIGURE_DEPENDS *.hpp)
target_sources(libmqttd
PRIVATE ${CPP_FILES_LOCAL}
PUBLIC ${HPP_FILES_LOCAL}
)
target_include_directories(libmqttd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,45 @@
#include "binary_data.hpp"
#include "bytes.hpp"
#include "spdlog/spdlog.h"
#include <iomanip>
BinaryData::BinaryData() {
this->data_size = 0;
this->data = {};
}
BinaryData::BinaryData(const std::vector<std::byte>::const_iterator &data_begin) {
this->data_size = std::to_integer<uint16_t>((*data_begin << 8) | *(std::next(data_begin, 1)));
auto data_end = std::next(data_begin, data_size);
if (std::distance(data_begin, data_end) < data_size)
throw std::runtime_error("Not enough bytes in the vector to construct BinaryData");
this->data.resize(this->data_size);
auto binary_data_begin = std::next(data_begin, 2);
auto binary_data_end = std::next(data_begin, this->data_size);
std::copy(binary_data_begin, binary_data_end, this->data.begin());
std::ostringstream log_msg;
log_msg << "New BinaryData with length " << this->size() << ": " << *this;
spdlog::trace(log_msg.str());
}
std::ostream &operator<<(std::ostream &os, const BinaryData &value) { return os << value.as_string(); }
std::string BinaryData::as_string() const {
std::stringstream binary_str;
binary_str << std::hex << std::setfill('0');
for (const auto &byte : this->data) {
binary_str << std::setw(2) << static_cast<int>(byte) << " ";
}
return binary_str.str();
}
std::vector<std::byte> BinaryData::as_bytes() const {
std::vector<std::byte> byte_vector = utilities::bytes::to_bytes(this->data_size);
byte_vector.insert(byte_vector.end(), data.begin(), data.end());
return byte_vector;
}

View File

@@ -0,0 +1,67 @@
#ifndef INCLUDE_TYPES_BINARY_DATA_HPP_
#define INCLUDE_TYPES_BINARY_DATA_HPP_
#include <cstdint>
#include <ostream>
#include <type_interface.hpp>
#include <vector>
/**
* @brief Class representing binary data.
*
* This class provides functionality to work with binary data, including conversion to string and byte vector.
*/
class BinaryData : public TypeInterface {
public:
/**
* @brief Default constructor.
*
* Constructs an empty BinaryData object.
*/
BinaryData();
/**
* @brief Constructor to create BinaryData from a byte vector iterator.
*
* Constructs a BinaryData object from the provided byte vector iterator.
*
* @param data_begin Iterator pointing to the beginning of the byte vector.
*/
BinaryData(const std::vector<std::byte>::const_iterator &data_begin);
/**
* @brief Destructor.
*/
~BinaryData() = default;
/**
* @brief Get the size of the binary data.
*
* @return The size of the binary data in bytes.
*/
uint16_t size() const final { return data_size; };
/**
* @brief Convert the binary data to a string representation.
*
* @return A string representing the binary data in hexadecimal.
*/
std::string as_string() const final;
/**
* @brief Convert the binary data to a byte vector.
*
* @return A byte vector representing the binary data.
*/
std::vector<std::byte> as_bytes() const final;
friend std::ostream &operator<<(std::ostream &, const BinaryData &);
protected:
std::vector<std::byte> data; /**< The binary data. */
uint16_t data_size; /**< The size of the binary data in bytes. */
};
using binary_data = BinaryData;
#endif // INCLUDE_TYPES_BINARY_DATA_HPP_

View File

@@ -0,0 +1,42 @@
#ifndef INCLUDE_TYPES_TYPE_INTERFACE_HPP_
#define INCLUDE_TYPES_TYPE_INTERFACE_HPP_
#include <cstdint>
#include <string>
#include <vector>
/**
* @brief Interface class for types.
*
* This class serves as an interface for various types, providing common functionality such as size retrieval and conversion to string and byte vector.
*/
class TypeInterface {
public:
/**
* @brief Virtual destructor.
*/
virtual ~TypeInterface() = default;
/**
* @brief Get the size of the type.
*
* @return The size of the type in bytes.
*/
virtual uint16_t size() const = 0;
/**
* @brief Convert the type to a string representation.
*
* @return A string representing the type.
*/
virtual std::string as_string() const = 0;
/**
* @brief Convert the type to a byte vector.
*
* @return A byte vector representing the type.
*/
virtual std::vector<std::byte> as_bytes() const = 0;
};
#endif // INCLUDE_TYPES_TYPE_INTERFACE_HPP_

View File

@@ -0,0 +1,40 @@
// TODO: Implement UTF8 string verifications such as Page 17, line 274
#include "bytes.hpp"
#include "spdlog/spdlog.h"
#include <cstddef>
#include <iterator>
#include <utf8_string.hpp>
#include <vector>
#include <sstream>
UTF8String::UTF8String() { this->utf8_string = ""; }
UTF8String::UTF8String(const std::vector<std::byte>::const_iterator &data_begin) {
uint16_t data_size = std::to_integer<uint16_t>((*data_begin << 8) | *(std::next(data_begin, 1)));
auto data_end = std::next(data_begin, data_size);
if (std::distance(data_begin, data_end) < data_size)
throw std::runtime_error("Not enough bytes in the vector to construct UTF8String");
auto utf8_string_begin = reinterpret_cast<const char *>(&(*std::next(data_begin, 2)));
auto utf8_string_end = reinterpret_cast<const char *>(&(*std::next(data_begin, 2 + data_size)));
this->utf8_string.resize(data_size);
this->utf8_string.assign(utf8_string_begin, utf8_string_end);
std::ostringstream log_msg;
log_msg << "New UTF8 String with length " << this->size() << ": " << *this;
spdlog::trace(log_msg.str());
}
std::ostream &operator<<(std::ostream &os, const UTF8String &value) {
os << value.utf8_string;
return os;
}
std::vector<std::byte> UTF8String::as_bytes() const {
std::vector<std::byte> byte_vector = utilities::bytes::to_bytes(this->size());
std::vector<std::byte> byte_str = utilities::bytes::to_bytes(this->utf8_string);
byte_vector.insert(byte_vector.end(), byte_str.begin(), byte_str.end());
return byte_vector;
}

View File

@@ -0,0 +1,70 @@
#ifndef INCLUDE_TYPES_UTF8_STRING_HPP_
#define INCLUDE_TYPES_UTF8_STRING_HPP_
#include <binary_data.hpp>
#include <cstddef>
#include <string>
#include <type_interface.hpp>
#include <vector>
/**
* @brief Class representing a UTF-8 string.
*
* This class provides functionality to work with UTF-8 encoded strings, including conversion to byte vector and string representation.
*/
class UTF8String : public TypeInterface {
public:
/**
* @brief Default constructor.
*/
UTF8String();
/**
* @brief Constructor that initializes the UTF-8 string from a byte vector.
*
* @param data_begin Iterator pointing to the start of the byte vector containing the UTF-8 string data.
*/
UTF8String(const std::vector<std::byte>::const_iterator &data_begin);
/**
* @brief Virtual destructor.
*/
~UTF8String() = default;
/**
* @brief Get the size of the UTF-8 string.
*
* @return The char count of the UTF-8 string.
*/
uint16_t size() const final { return static_cast<uint16_t>(utf8_string.size()); };
/**
* @brief Convert the UTF-8 string to a string representation.
*
* @return A string representing the UTF-8 string.
*/
std::string as_string() const final { return this->utf8_string; };
/**
* @brief Convert the UTF-8 string to a byte vector.
*
* @return A byte vector representing the UTF-8 string.
*/
std::vector<std::byte> as_bytes() const final;
/**
* @brief Overloaded output stream operator for UTF8String.
*
* @param os The output stream.
* @param value The UTF8String object.
* @return The output stream.
*/
friend std::ostream &operator<<(std::ostream &os, const UTF8String &value);
protected:
std::string utf8_string; /**< The UTF-8 string. */
};
using utf8_str = UTF8String;
#endif // INCLUDE_TYPES_UTF8_STRING_HPP_

View File

@@ -0,0 +1,27 @@
#include "spdlog/spdlog.h"
#include "utf8_string.hpp"
#include <cstddef>
#include <utf8_string_pair.hpp>
#include <vector>
#include <sstream>
UTF8StringPair::UTF8StringPair(const std::vector<std::byte>::const_iterator &data_begin) {
this->key = UTF8String(data_begin);
auto value_start_byte = std::next(data_begin, this->key.as_bytes().size());
this->value = UTF8String(value_start_byte);
std::ostringstream log_msg;
log_msg << "New UTF8 String Pair with length " << this->as_bytes().size() << ": " << *this;
spdlog::trace(log_msg.str());
}
std::ostream &operator<<(std::ostream &os, const UTF8StringPair &value) {
os << "(" << value.key << ", " << value.value << ")";
return os;
}
std::vector<std::byte> UTF8StringPair::as_bytes() const {
std::vector<std::byte> key_bytes = key.as_bytes();
std::vector<std::byte> value_bytes = value.as_bytes();
key_bytes.insert(key_bytes.end(), value_bytes.begin(), value_bytes.end());
return key_bytes;
}

View File

@@ -0,0 +1,73 @@
#ifndef INCLUDE_TYPES_UTF8_STRING_PAIR_HPP_
#define INCLUDE_TYPES_UTF8_STRING_PAIR_HPP_
#include <type_interface.hpp>
#include <utf8_string.hpp>
/**
* @brief Class representing a pair of UTF-8 strings.
*
* This class provides functionality to work with pairs of UTF-8 strings, including conversion to byte vector and string representation.
*/
class UTF8StringPair : public TypeInterface {
public:
/**
* @brief Default constructor.
*/
UTF8StringPair();
/**
* @brief Constructor that initializes the UTF-8 string pair from a byte vector.
*
* @param data_begin Iterator pointing to the start of the byte vector containing the UTF-8 string pair data.
*/
UTF8StringPair(const std::vector<std::byte>::const_iterator &data_begin);
/**
* @brief Virtual destructor.
*/
~UTF8StringPair() = default;
friend std::ostream &operator<<(std::ostream &os, const UTF8StringPair &value);
/**
* @brief Get the size of the UTF-8 string pair.
*
* @return The total character count of the UTF-8 string pair.
*/
uint16_t size() const final { return key.size() + value.size(); };
/**
* @brief Convert the UTF-8 string pair to a string representation.
*
* @return A string representing the UTF-8 string pair.
*/
std::string as_string() const final { return key.as_string() + "=" + value.as_string(); };
/**
* @brief Convert the UTF-8 string pair to a byte vector.
*
* @return A byte vector representing the UTF-8 string pair.
*/
std::vector<std::byte> as_bytes() const final;
/**
* @brief Get the key of the UTF-8 string pair.
*
* @return The key of the UTF-8 string pair.
*/
inline const UTF8String get_key() const { return this->key; };
/**
* @brief Get the value of the UTF-8 string pair.
*
* @return The value of the UTF-8 string pair.
*/
inline const UTF8String get_value() const { return this->value; };
private:
UTF8String key; /**< The key of the UTF-8 string pair. */
UTF8String value; /**< The value of the UTF-8 string pair. */
};
#endif // INCLUDE_TYPES_UTF8_STRING_PAIR_HPP_

View File

@@ -0,0 +1,103 @@
//TODO: This implemetation assumes 4 bytes from variable int always, according to specification it should occupy as many bytes as length()
#include "variable_byte_int.hpp"
#include "spdlog/spdlog.h"
#include <vector>
#include <sstream>
VariableByteInteger::VariableByteInteger() {
this->decoded_value = 0;
this->encoded_value = {std::byte(0)};
this->bytes_count = 1;
}
VariableByteInteger::VariableByteInteger(const uint32_t &value) {
if(value > this->max())
throw std::runtime_error("Maximum value of Variable Byte Integer is " + std::to_string(this->max()));
this->encoded_value = this->encode(value);
this->decoded_value = value;
std::ostringstream log_msg;
log_msg << "New VariableByteInteger with length " << this->size() << ": " << *this;
spdlog::trace(log_msg.str());
}
VariableByteInteger::VariableByteInteger(const std::vector<std::byte> &value) {
new (this) VariableByteInteger(value.begin());
}
VariableByteInteger::VariableByteInteger(const std::vector<std::byte>::const_iterator &it) {
if (std::distance(it, it + 4) < 4)
throw std::runtime_error("Not enough bytes in the vector to construct VariableByteInteger");
std::vector<std::byte> value;
value.resize(4);
std::copy(it, it+4, value.begin());
this->decoded_value = this->decode(value);
this->encoded_value = std::vector<std::byte>(value.begin(), value.begin() + this->bytes_count);
std::ostringstream log_msg;
log_msg << "New VariableByteInteger with length " << this->size() << ": " << *this;
spdlog::trace(log_msg.str());
}
VariableByteInteger::~VariableByteInteger() {}
template <typename T>
typename std::enable_if<std::is_integral<T>::value, VariableByteInteger &>::type
VariableByteInteger::operator=(T &t) {
this->encoded_value = this->encode(t);
this->decoded_value = t;
return *this;
}
std::ostream &operator<<(std::ostream &os, const VariableByteInteger &value) {
os << std::to_string(value.decoded_value);
return os;
}
std::vector<std::byte> VariableByteInteger::encode(const uint32_t &value) {
uint8_t encoded_byte = 0;
uint32_t value_to_encode = value;
std::vector<std::byte> encoded_value;
uint8_t current_byte = 0;
do {
encoded_byte = value_to_encode % 0x80;
value_to_encode = value_to_encode / 0x80;
if (value_to_encode > 0)
encoded_byte |= 0x80;
encoded_value.insert(encoded_value.end(), std::byte(encoded_byte));
current_byte++;
} while (value_to_encode > 0);
this->bytes_count = current_byte;
return encoded_value;
}
uint32_t VariableByteInteger::decode(const std::vector<std::byte> &value) {
uint8_t encoded_byte = 0;
std::vector<std::byte> value_to_decode = value;
uint32_t decoded_value = 0;
uint8_t current_byte = 0;
uint multiplier = 1;
do {
encoded_byte = std::to_integer<uint8_t>(value_to_decode[current_byte]);
decoded_value += (encoded_byte & 127) * multiplier;
if (multiplier > (128*128*128))
throw std::runtime_error("Malformed Variable Byte Integer");
multiplier *= 128;
current_byte++;
} while ((encoded_byte & 128) != 0);
this->bytes_count = current_byte;
return decoded_value;
};

View File

@@ -0,0 +1,109 @@
#ifndef INCLUDE_TYPES_VARIABLE_BYTE_INT_HPP_
#define INCLUDE_TYPES_VARIABLE_BYTE_INT_HPP_
#include <bit.hpp>
#include <cstdint>
#include <ostream>
#include <type_interface.hpp>
#include <type_traits>
#include <vector>
/**
* @brief Class representing a Variable Byte Integer according to MQTT v5 section 1.5.5.
*
* This class provides functionality to handle Variable Byte Integers as defined in the MQTT v5 specification.
*
* For more details
* @see file:///files/breno/mqttd/docs/mqtt_spec/MQTT%20Version%205.0.html#_Toc3901011
*/
class VariableByteInteger : public TypeInterface {
public:
/**
* @brief Creates a new Variable Byte Integer object with a default value of 0.
*/
VariableByteInteger();
/**
* @brief Creates a new Variable Byte Integer object from an integral integer value.
*
* @param decoded_value The integral value to initialize the Variable Byte Integer with.
*/
VariableByteInteger(const uint32_t &decoded_value);
/**
* @brief Creates a new Variable Byte Integer object from a byte vector representing an encoded Variable Byte Integer.
*
* @param encoded_value The byte vector representing the encoded Variable Byte Integer.
* The vector must have at least 4 bytes, further bytes will be ignored.
*/
VariableByteInteger(const std::vector<std::byte> &encoded_value);
/**
* @brief Creates a new Variable Byte Integer object from a byte vector iterator pointing to the beginning of the data representing an encoded Variable Byte Integer.
*
* @param encoded_value_start The iterator pointing to the beginning of the data representing the encoded Variable Byte Integer.
* The vector must have at least 4 bytes, further bytes will be ignored.
*/
VariableByteInteger(const std::vector<std::byte>::const_iterator &encoded_value_start);
~VariableByteInteger();
/**
* @brief Gets the size of the Variable Byte Integer in bytes.
*
* @return The size of the Variable Byte Integer in bytes.
*/
uint16_t size() const final { return this->bytes_count; };
/**
* @brief Gets the decoded value of the Variable Byte Integer as a string.
*
* @return The decoded value of the Variable Byte Integer as a string.
*/
std::string as_string() const final { return std::to_string(this->decoded_value); };
/**
* @brief Gets the byte array representing the encoded value of a Variable Byte Integer.
*
* @return The byte array representing the encoded value of a Variable Byte Integer.
*/
std::vector<std::byte> as_bytes() const final { return this->encoded_value; };
/**
* @brief Overloads the assignment operator for integral types.
*
* Enables assignment of a Variable Byte Integer from an integer.
*/
template <typename T> typename std::enable_if<std::is_integral<T>::value, VariableByteInteger &>::type operator=(T &);
/**
* @brief Overloads the output stream operator to print a Variable Byte Integer as its decoded value.
*/
friend std::ostream &operator<<(std::ostream &, const VariableByteInteger &);
/**
* @brief Conversion operator to uint32_t.
*
* @return The value of the Variable Byte Integer as a uint32_t.
*/
inline operator uint32_t() const { return this->decoded_value; };
/**
* @brief Gets the maximum value allowed for a Variable Byte Integer.
*
* @return The maximum value allowed for a Variable Byte Integer.
*/
inline uint32_t max() const { return 268435455; };
private:
std::vector<std::byte> encoded_value; /**< The value of the Variable Byte integer. */
std::vector<std::byte> encode(const unsigned int &); /**< Encodes an integral type to a byte vector representing a Variable Byte Integer. */
uint32_t decoded_value; /**< The value of the Variable Byte Integer as an integer. */
uint32_t decode(const std::vector<std::byte> &); /**< Decodes a byte vector to an integer. */
uint8_t bytes_count; /**< How many bytes the #encoded_value has. */
};
using int_vb = VariableByteInteger;
#endif // INCLUDE_TYPES_VARIABLE_BYTE_INT_HPP_

View File

@@ -0,0 +1,10 @@
FILE(GLOB CPP_FILES_LOCAL CONFIGURE_DEPENDS *.cpp)
FILE(GLOB HPP_FILES_LOCAL CONFIGURE_DEPENDS *.hpp)
target_sources(libmqttd
PRIVATE ${CPP_FILES_LOCAL}
PUBLIC ${HPP_FILES_LOCAL}
)
target_include_directories(libmqttd PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -0,0 +1,38 @@
#ifndef INCLUDE_UTILITIES_BIT_HPP_
#define INCLUDE_UTILITIES_BIT_HPP_
namespace utilities {
namespace bit {
/**
* @brief Set a bit at a specified position in a value.
*
* @tparam T The type of the value.
* @param value The value to set the bit in.
* @param position The position of the bit to set.
* @return The value with the specified bit set.
*/
template <typename T>
T set(T value, int position) {
T mask = static_cast<T>(1) << position;
return value | mask;
}
/**
* @brief Get the value of a bit at a specified position.
*
* @tparam T The type of the value.
* @param value The value to get the bit from.
* @param position The position of the bit to get.
* @return True if the bit at the specified position is set, false otherwise.
*/
template <typename T>
bool get(T value, int position) {
T mask = static_cast<T>(1) << position;
return (value & mask) != 0;
}
} // namespace bit
} // namespace utilities
#endif // INCLUDE_UTILITIES_BIT_HPP_

View File

@@ -0,0 +1,16 @@
#include <algorithm>
#include <bytes.hpp>
namespace utilities {
namespace bytes {
std::vector<std::byte> to_bytes(const std::string &value) {
std::vector<std::byte> bytes;
bytes.reserve(value.size());
std::transform(std::begin(value), std::end(value), std::back_inserter(bytes), [](char c) { return std::byte(c); });
return bytes;
}
} // namespace bytes
} // namespace utilities

View File

@@ -0,0 +1,34 @@
#ifndef INCLUDE_UTILITIES_BYTES_HPP_
#define INCLUDE_UTILITIES_BYTES_HPP_
#include <string>
#include <vector>
namespace utilities {
namespace bytes {
/**
* @brief Convert a string to a vector of bytes.
*
* @param value The string to convert.
* @return A vector containing the bytes of the string.
*/
std::vector<std::byte> to_bytes(const std::string &value);
/**
* @brief Convert a value to a vector of bytes.
*
* @tparam T The type of the value.
* @param value The value to convert.
* @return A vector containing the bytes of the value.
*/
template <typename T>
std::vector<std::byte> to_bytes(const T &value) {
const std::byte *byte_ptr = reinterpret_cast<const std::byte *>(&value);
return std::vector<std::byte>(byte_ptr, byte_ptr + sizeof(T));
}
} // namespace bytes
} // namespace utilities
#endif // INCLUDE_UTILITIES_BYTES_HPP_

View File

@@ -0,0 +1,31 @@
#ifndef INCLUDE_UTILITIES_TYPES_HPP_
#define INCLUDE_UTILITIES_TYPES_HPP_
#include <cstdint>
#include <type_traits>
#include <vector>
namespace utilities {
namespace types {
/**
* @brief Extracts a fixed-size integer value from a byte vector.
*
* @tparam T The type of the integer.
* @param it Iterator pointing to the beginning of the integer bytes in the vector.
* @return The extracted integer value.
*/
template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type get_fixed_size_integer(const std::vector<std::byte>::const_iterator &it) {
T value = 0;
for (unsigned int i = 0; i < sizeof(T); i++) {
uint8_t byte_value = std::to_integer<uint8_t>(*(std::next(it, i)));
value = (value << 8) | byte_value;
}
return value;
}
} // namespace types
} // namespace utilities
#endif // INCLUDE_UTILITIES_TYPES_HPP_

105
src/mqttd.cpp Normal file
View File

@@ -0,0 +1,105 @@
#include "version.hpp"
#include <connection_listener.hpp>
#include <cpptrace/cpptrace.hpp>
#include <csignal>
#include <iostream>
#include <set>
#include <sys/wait.h>
ConnectionListener clistener;
struct pipe_t {
union {
struct {
int read_end;
int write_end;
};
int data[2];
};
};
static_assert(sizeof(pipe_t) == 2 * sizeof(int), "Unexpected struct packing");
void send_trace(cpptrace::frame_ptr *buffer, std::size_t size) {
pipe_t input_pipe;
pipe(input_pipe.data);
const pid_t pid = fork();
if (pid == -1)
return;
if (pid == 0) { // child
dup2(input_pipe.read_end, STDIN_FILENO);
close(input_pipe.read_end);
close(input_pipe.write_end);
execl("mqttd_stacktracer", "mqttd_stacktracer", nullptr);
_exit(1);
}
for (std::size_t i = 0; i < size; i++) {
cpptrace::safe_object_frame frame;
cpptrace::get_safe_object_frame(buffer[i], &frame);
write(input_pipe.write_end, &frame, sizeof(frame));
}
close(input_pipe.read_end);
close(input_pipe.write_end);
waitpid(pid, nullptr, 0);
}
void termination_handler(int signal_num) {
spdlog::info("Received signal " + std::string(strsignal(signal_num)));
std::set<unsigned int> backtrace_signals = {SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGABRT, SIGTRAP};
auto is_backtrace_signal = backtrace_signals.find(signal_num);
if (is_backtrace_signal != backtrace_signals.end()) {
constexpr std::size_t SIZE = 256;
cpptrace::frame_ptr buffer[SIZE];
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, SIZE);
send_trace(buffer, count);
_exit(ExitCode::ERROR);
}
clistener.stop();
exit(ExitCode::SUCCESS);
}
void set_signal_handlers() {
std::set<unsigned int> termination_signals = {SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGSYS, SIGABRT, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTRAP};
// Blocking signals with set
sigset_t block_mask;
sigemptyset(&block_mask);
sigprocmask(SIG_BLOCK, &block_mask, NULL);
for (auto it = termination_signals.begin(); it != termination_signals.end(); it++) {
sigaddset(&block_mask, *it);
}
struct sigaction sigHandler;
memset(&sigHandler, 0, sizeof(sigHandler));
sigHandler.sa_handler = termination_handler;
sigHandler.sa_mask = block_mask;
sigHandler.sa_flags = 0;
for (auto it = termination_signals.begin(); it != termination_signals.end(); it++) {
sigaction(*it, &sigHandler, NULL);
}
}
void warmup_cpptrace() {
cpptrace::frame_ptr buffer[10];
cpptrace::safe_generate_raw_trace(buffer, 10);
cpptrace::safe_object_frame frame;
cpptrace::get_safe_object_frame(buffer[0], &frame);
}
int main() {
std::cout << "MQTTd Version " << MQTTD_VERSION_MAJOR << "." << MQTTD_VERSION_MINOR << "." << MQTTD_VERSION_PATCH << std::endl;
cpptrace::absorb_trace_exceptions(false);
cpptrace::register_terminate_handler();
cpptrace::enable_inlined_call_resolution(true);
warmup_cpptrace();
set_signal_handlers();
clistener.start();
clistener.join();
return EXIT_SUCCESS;
return 1;
}

22
src/mqttd_stacktracer.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include <cpptrace/cpptrace.hpp>
#include <cstdio>
#include <iostream>
#include <unistd.h>
int main() {
cpptrace::object_trace trace;
while (true) {
cpptrace::safe_object_frame frame;
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
if (res == 0)
break;
else if (res == 1){
trace.frames.push_back(frame.resolve());
}
else {
std::cerr << "Oops, size mismatch " << res << " " << sizeof(frame) << std::endl;
break;
}
}
trace.resolve().print();
}

5
src/version.hpp Normal file
View File

@@ -0,0 +1,5 @@
#define MQTTD_VERSION_MAJOR 0
#define MQTTD_VERSION_MINOR 0
#define MQTTD_VERSION_PATCH 1
#define MQTTD_COMMIT_HASH 2b70078e9ce0694833dfb760747bbb7bf7f0621a
#define MQTTD_BUILD_TIMESTAMP 1720889932

11
tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,11 @@
include(GetCatch2)
FILE(GLOB_RECURSE CPP_FILES CONFIGURE_DEPENDS *.cpp)
add_executable(tests ${CPP_FILES})
target_link_libraries(tests PRIVATE libmqttd Catch2::Catch2WithMain)
include(CTest)
include(Catch)
catch_discover_tests(tests)

View File

@@ -0,0 +1,64 @@
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <cstddef>
#include <variable_byte_int.hpp>
TEST_CASE("Variable Byte Integer Encoding", "[Variable Byte Integer]") {
int_vb num1 = 42;
std::vector<std::byte> expected_bytes1 = {std::byte(42)};
REQUIRE(num1.as_bytes() == expected_bytes1);
int_vb num2 = 2048;
std::vector<std::byte> expected_bytes2 = {std::byte(128), std::byte(16)};
REQUIRE(num2.as_bytes() == expected_bytes2);
REQUIRE_THROWS_AS([&](){ int_vb num3 = 4294967167;}(), std::runtime_error);
int_vb num4 = 0;
std::vector<std::byte> expected_bytes4 = {std::byte(0)};
REQUIRE(num4.as_bytes() == expected_bytes4);
int_vb num5 = 16383;
std::vector<std::byte> expected_bytes5 = {std::byte(255), std::byte(127)};
REQUIRE(num5.as_bytes() == expected_bytes5);
int_vb num6 = 2097151;
std::vector<std::byte> expected_bytes6 = {std::byte(255), std::byte(255), std::byte(127)};
REQUIRE(num6.as_bytes() == expected_bytes6);
}
TEST_CASE("VariableByte Integer Decoding", "[Variable Byte Integer]") {
// Caso de teste 1
uint32_t expected_num1 = 42;
std::vector<std::byte> bytes1 = {std::byte(42)};
int_vb num1(bytes1);
REQUIRE(uint32_t(num1) == expected_num1);
// Caso de teste 2
uint32_t expected_num2 = 2048;
std::vector<std::byte> bytes2 = {std::byte(128), std::byte(16)};
int_vb num2(bytes2);
REQUIRE(uint32_t(num2) == expected_num2);
// Caso de teste 3
std::vector<std::byte> bytes3 = {std::byte(255), std::byte(255), std::byte(255), std::byte(255)};
REQUIRE_THROWS_AS([&](){int_vb num3(bytes3);}(), std::runtime_error);
// Caso de teste 4
uint32_t expected_num4 = 0;
std::vector<std::byte> bytes4 = {std::byte(0), std::byte(0)};
int_vb num4(bytes4);
REQUIRE(uint32_t(num4) == expected_num4);
// Caso de teste 5
uint32_t expected_num5 = 16383;
std::vector<std::byte> bytes5 = {std::byte(255), std::byte(127)};
int_vb num5(bytes5);
REQUIRE(uint32_t(num5) == expected_num5);
// Caso de teste 6
uint32_t expected_num6 = 2097151;
std::vector<std::byte> bytes6 = {std::byte(255), std::byte(255), std::byte(127)};
int_vb num6(bytes6);
REQUIRE(uint32_t(num6) == expected_num6);
}

41
version.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
set -e
VERSION_FILE="$(pwd)/src/version.hpp"
MAJOR=$(grep -oP 'MQTTD_VERSION_MAJOR \K[0-9]+' "$VERSION_FILE")
MINOR=$(grep -oP 'MQTTD_VERSION_MINOR \K[0-9]+' "$VERSION_FILE")
PATCH=$(grep -oP 'MQTTD_VERSION_PATCH \K[0-9]+' "$VERSION_FILE")
_bump_version() {
case "$1" in
major)
MAJOR=$((MAJOR + 1))
;;
minor)
MINOR=$((MINOR + 1))
;;
patch)
PATCH=$((PATCH + 1))
;;
*)
echo "Unknown version semantic $1" >&2
exit 124
;;
esac
}
# Atualizar versões
[ "$#" -gt 0 ] && _bump_version "$1"
sed -i "s/MQTTD_VERSION_MAJOR [0-9]\+/MQTTD_VERSION_MAJOR $MAJOR/" "$VERSION_FILE"
sed -i "s/MQTTD_VERSION_MINOR [0-9]\+/MQTTD_VERSION_MINOR $MINOR/" "$VERSION_FILE"
sed -i "s/MQTTD_VERSION_PATCH [0-9]\+/MQTTD_VERSION_PATCH $PATCH/" "$VERSION_FILE"
# Atualizar informações de compilação
COMMIT_HASH=$(git rev-parse HEAD)
BUILD_TIMESTAMP=$(date +%s)
sed -i "s/MQTTD_COMMIT_HASH [a-zA-Z0-9]\+/MQTTD_COMMIT_HASH $COMMIT_HASH/" "$VERSION_FILE"
sed -i "s/MQTTD_BUILD_TIMESTAMP [0-9]\+/MQTTD_BUILD_TIMESTAMP $BUILD_TIMESTAMP/" "$VERSION_FILE"
# Exibir versão atualizada
printf "%d.%d.%d" "$MAJOR" "$MINOR" "$PATCH"