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:
236
.clang-format
Normal file
236
.clang-format
Normal 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
245
.cmake-format.yaml
Normal 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
8
.gitignore
vendored
@@ -1,3 +1,11 @@
|
||||
# VScode
|
||||
.vscode/
|
||||
|
||||
# CMake
|
||||
.cache/
|
||||
build/
|
||||
compile_commands.json
|
||||
|
||||
# ---> C++
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
27
CMakeLists.txt
Normal file
27
CMakeLists.txt
Normal 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
61
cmake/FindAutotools.cmake
Normal 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
11
cmake/GetCatch2.cmake
Normal 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
14
cmake/GetCppTrace.cmake
Normal 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
11
cmake/GetSpdlog.cmake
Normal 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
42
cmake/GetUnwind.cmake
Normal 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
45
docs/CMakeLists.txt
Normal 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")
|
||||
27425
docs/mqtt_spec/MQTT Version 5.0.html
Normal file
27425
docs/mqtt_spec/MQTT Version 5.0.html
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/mqtt_spec/MQTT Version 5.0_files/OASISLogo-v2.0.jpg
Normal file
BIN
docs/mqtt_spec/MQTT Version 5.0_files/OASISLogo-v2.0.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
19735
docs/mqtt_spec/MQTT Version 5.0_files/page-script.js
Normal file
19735
docs/mqtt_spec/MQTT Version 5.0_files/page-script.js
Normal file
File diff suppressed because it is too large
Load Diff
4
scripts/crashlog.txt
Normal file
4
scripts/crashlog.txt
Normal 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
BIN
scripts/mqttd
Executable file
Binary file not shown.
53
scripts/process_crashlog.py
Executable file
53
scripts/process_crashlog.py
Executable 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
7
setup.cfg
Normal 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
13
src/CMakeLists.txt
Normal 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
14
src/core/CMakeLists.txt
Normal 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})
|
||||
|
||||
12
src/core/broker/CMakeLists.txt
Normal file
12
src/core/broker/CMakeLists.txt
Normal 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)
|
||||
62
src/core/broker/connection_listener.cpp
Normal file
62
src/core/broker/connection_listener.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
65
src/core/broker/connection_listener.hpp
Normal file
65
src/core/broker/connection_listener.hpp
Normal 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_
|
||||
73
src/core/broker/session_manager.cpp
Normal file
73
src/core/broker/session_manager.cpp
Normal 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);
|
||||
}
|
||||
96
src/core/broker/session_manager.hpp
Normal file
96
src/core/broker/session_manager.hpp
Normal 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
12
src/core/exit_codes.hpp
Normal 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_
|
||||
17
src/libmqttd/CMakeLists.txt
Normal file
17
src/libmqttd/CMakeLists.txt
Normal 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})
|
||||
|
||||
34
src/libmqttd/exceptions.hpp
Normal file
34
src/libmqttd/exceptions.hpp
Normal 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_
|
||||
13
src/libmqttd/network/CMakeLists.txt
Normal file
13
src/libmqttd/network/CMakeLists.txt
Normal 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})
|
||||
9
src/libmqttd/network/connection/CMakeLists.txt
Normal file
9
src/libmqttd/network/connection/CMakeLists.txt
Normal 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})
|
||||
37
src/libmqttd/network/connection/connect_ack.cpp
Normal file
37
src/libmqttd/network/connection/connect_ack.cpp
Normal 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;
|
||||
}
|
||||
103
src/libmqttd/network/connection/connect_ack.hpp
Normal file
103
src/libmqttd/network/connection/connect_ack.hpp
Normal 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_
|
||||
135
src/libmqttd/network/connection/connect_packet.cpp
Normal file
135
src/libmqttd/network/connection/connect_packet.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
116
src/libmqttd/network/connection/connect_packet.hpp
Normal file
116
src/libmqttd/network/connection/connect_packet.hpp
Normal 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_
|
||||
17
src/libmqttd/network/connection/last_will.hpp
Normal file
17
src/libmqttd/network/connection/last_will.hpp
Normal 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_
|
||||
9
src/libmqttd/network/disconnection/CMakeLists.txt
Normal file
9
src/libmqttd/network/disconnection/CMakeLists.txt
Normal 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})
|
||||
143
src/libmqttd/network/disconnection/disconnect_packet.cpp
Normal file
143
src/libmqttd/network/disconnection/disconnect_packet.cpp
Normal 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;
|
||||
}
|
||||
97
src/libmqttd/network/disconnection/disconnect_packet.hpp
Normal file
97
src/libmqttd/network/disconnection/disconnect_packet.hpp
Normal 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_
|
||||
9
src/libmqttd/network/packet_interface/CMakeLists.txt
Normal file
9
src/libmqttd/network/packet_interface/CMakeLists.txt
Normal 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})
|
||||
60
src/libmqttd/network/packet_interface/fixed_header.cpp
Normal file
60
src/libmqttd/network/packet_interface/fixed_header.cpp
Normal 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;
|
||||
}
|
||||
|
||||
45
src/libmqttd/network/packet_interface/fixed_header.hpp
Normal file
45
src/libmqttd/network/packet_interface/fixed_header.hpp
Normal 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_
|
||||
63
src/libmqttd/network/packet_interface/packet_interface.cpp
Normal file
63
src/libmqttd/network/packet_interface/packet_interface.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
94
src/libmqttd/network/packet_interface/packet_interface.hpp
Normal file
94
src/libmqttd/network/packet_interface/packet_interface.hpp
Normal 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_
|
||||
380
src/libmqttd/network/packet_interface/property.cpp
Normal file
380
src/libmqttd/network/packet_interface/property.cpp
Normal 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;
|
||||
}
|
||||
119
src/libmqttd/network/packet_interface/property.hpp
Normal file
119
src/libmqttd/network/packet_interface/property.hpp
Normal 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_
|
||||
11
src/libmqttd/protocol/CMakeLists.txt
Normal file
11
src/libmqttd/protocol/CMakeLists.txt
Normal 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})
|
||||
11
src/libmqttd/protocol/session/CMakeLists.txt
Normal file
11
src/libmqttd/protocol/session/CMakeLists.txt
Normal 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})
|
||||
123
src/libmqttd/protocol/session/session.cpp
Normal file
123
src/libmqttd/protocol/session/session.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
135
src/libmqttd/protocol/session/session.hpp
Normal file
135
src/libmqttd/protocol/session/session.hpp
Normal 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_
|
||||
9
src/libmqttd/protocol/session/states/CMakeLists.txt
Normal file
9
src/libmqttd/protocol/session/states/CMakeLists.txt
Normal 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})
|
||||
53
src/libmqttd/protocol/session/states/state_connect.cpp
Normal file
53
src/libmqttd/protocol/session/states/state_connect.cpp
Normal 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());
|
||||
}
|
||||
51
src/libmqttd/protocol/session/states/state_connect.hpp
Normal file
51
src/libmqttd/protocol/session/states/state_connect.hpp
Normal 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_
|
||||
36
src/libmqttd/protocol/session/states/state_disconnect.cpp
Normal file
36
src/libmqttd/protocol/session/states/state_disconnect.cpp
Normal 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();
|
||||
}
|
||||
51
src/libmqttd/protocol/session/states/state_disconnect.hpp
Normal file
51
src/libmqttd/protocol/session/states/state_disconnect.hpp
Normal 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_
|
||||
48
src/libmqttd/protocol/session/states/state_interface.hpp
Normal file
48
src/libmqttd/protocol/session/states/state_interface.hpp
Normal 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_
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
10
src/libmqttd/types/CMakeLists.txt
Normal file
10
src/libmqttd/types/CMakeLists.txt
Normal 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})
|
||||
|
||||
45
src/libmqttd/types/binary_data.cpp
Normal file
45
src/libmqttd/types/binary_data.cpp
Normal 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;
|
||||
}
|
||||
67
src/libmqttd/types/binary_data.hpp
Normal file
67
src/libmqttd/types/binary_data.hpp
Normal 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_
|
||||
42
src/libmqttd/types/type_interface.hpp
Normal file
42
src/libmqttd/types/type_interface.hpp
Normal 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_
|
||||
40
src/libmqttd/types/utf8_string.cpp
Normal file
40
src/libmqttd/types/utf8_string.cpp
Normal 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;
|
||||
}
|
||||
70
src/libmqttd/types/utf8_string.hpp
Normal file
70
src/libmqttd/types/utf8_string.hpp
Normal 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_
|
||||
27
src/libmqttd/types/utf8_string_pair.cpp
Normal file
27
src/libmqttd/types/utf8_string_pair.cpp
Normal 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;
|
||||
}
|
||||
73
src/libmqttd/types/utf8_string_pair.hpp
Normal file
73
src/libmqttd/types/utf8_string_pair.hpp
Normal 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_
|
||||
103
src/libmqttd/types/variable_byte_int.cpp
Normal file
103
src/libmqttd/types/variable_byte_int.cpp
Normal 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;
|
||||
};
|
||||
109
src/libmqttd/types/variable_byte_int.hpp
Normal file
109
src/libmqttd/types/variable_byte_int.hpp
Normal 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_
|
||||
10
src/libmqttd/utilities/CMakeLists.txt
Normal file
10
src/libmqttd/utilities/CMakeLists.txt
Normal 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})
|
||||
|
||||
38
src/libmqttd/utilities/bit.hpp
Normal file
38
src/libmqttd/utilities/bit.hpp
Normal 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_
|
||||
16
src/libmqttd/utilities/bytes.cpp
Normal file
16
src/libmqttd/utilities/bytes.cpp
Normal 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
|
||||
34
src/libmqttd/utilities/bytes.hpp
Normal file
34
src/libmqttd/utilities/bytes.hpp
Normal 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_
|
||||
31
src/libmqttd/utilities/types.hpp
Normal file
31
src/libmqttd/utilities/types.hpp
Normal 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
105
src/mqttd.cpp
Normal 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
22
src/mqttd_stacktracer.cpp
Normal 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
5
src/version.hpp
Normal 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
11
tests/CMakeLists.txt
Normal 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)
|
||||
|
||||
64
tests/protocol/test_types.cpp
Normal file
64
tests/protocol/test_types.cpp
Normal 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
41
version.sh
Executable 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"
|
||||
|
||||
Reference in New Issue
Block a user