Full Code of halfgaar/FlashMQ for AI

master af7ab0c2be24 cached
235 files
1.7 MB
434.5k tokens
461 symbols
1 requests
Download .txt
Showing preview only (1,780K chars total). Download the full file or copy to clipboard to get everything.
Repository: halfgaar/FlashMQ
Branch: master
Commit: af7ab0c2be24
Files: 235
Total size: 1.7 MB

Directory structure:
gitextract_eh9hwlw7/

├── .build-ci.sh
├── .clang-format
├── .clang-format.original
├── .dockerignore
├── .get-os-codename-and-stamp.sh
├── .github/
│   └── workflows/
│       ├── building.yml
│       ├── codestyle.yml
│       ├── docker.yml
│       ├── linting.yml
│       ├── testing.yml
│       └── testing_non_sse.yml
├── .gitignore
├── CMakeLists.shared
├── CMakeLists.txt
├── Dockerfile
├── FlashMQTests/
│   ├── CMakeLists.txt
│   ├── UTF-8-test.txt
│   ├── bridgeprefixtests.cpp
│   ├── conffiletemp.cpp
│   ├── conffiletemp.h
│   ├── configtests.cpp
│   ├── dnstests.cpp
│   ├── filecloser.cpp
│   ├── filecloser.h
│   ├── flashmqtempdir.cpp
│   ├── flashmqtempdir.h
│   ├── main.cpp
│   ├── mainappasfork.cpp
│   ├── mainappasfork.h
│   ├── mainappinthread.cpp
│   ├── mainappinthread.h
│   ├── maintests.cpp
│   ├── maintests.h
│   ├── plugins/
│   │   ├── curlfunctions.cpp
│   │   ├── curlfunctions.h
│   │   ├── test_plugin.cpp
│   │   ├── test_plugin.h
│   │   └── test_plugin.pro
│   ├── plugintests.cpp
│   ├── retaintests.cpp
│   ├── run-make-from-ci.sh
│   ├── run-tests-from-ci.sh
│   ├── sharedsubscriptionstests.cpp
│   ├── subscriptionidtests.cpp
│   ├── testhelpers.cpp
│   ├── testhelpers.h
│   ├── testinitializer.cpp
│   ├── testinitializer.h
│   ├── tst_maintests.cpp
│   ├── utiltests.cpp
│   ├── websockettests.cpp
│   └── willtests.cpp
├── LICENSE
├── README.md
├── acksender.cpp
├── acksender.h
├── acltree.cpp
├── acltree.h
├── backgroundworker.cpp
├── backgroundworker.h
├── bindaddr.cpp
├── bindaddr.h
├── bridgeconfig.cpp
├── bridgeconfig.h
├── bridgeinfodb.cpp
├── bridgeinfodb.h
├── build.sh
├── checkedsharedptr.h
├── checkedweakptr.h
├── cirbuf.cpp
├── cirbuf.h
├── client.cpp
├── client.h
├── clientacceptqueue.cpp
├── clientacceptqueue.h
├── configfileparser.cpp
├── configfileparser.h
├── debian/
│   ├── conffiles
│   ├── flashmq.service
│   ├── postinst
│   ├── postrm
│   ├── preinst
│   └── prerm
├── derivablecounter.cpp
├── derivablecounter.h
├── dnsresolver.cpp
├── dnsresolver.h
├── driftcounter.cpp
├── driftcounter.h
├── enums.h
├── evpencodectxmanager.cpp
├── evpencodectxmanager.h
├── examples/
│   └── plugin_libcurl/
│       ├── CMakeLists.txt
│       ├── LICENSE
│       ├── README.md
│       ├── build.sh
│       ├── src/
│       │   ├── authenticatingclient.cpp
│       │   ├── authenticatingclient.h
│       │   ├── curl_functions.cpp
│       │   ├── curl_functions.h
│       │   ├── plugin_libcurl.cpp
│       │   ├── pluginstate.cpp
│       │   └── pluginstate.h
│       └── vendor/
│           ├── flashmq_plugin.h
│           └── flashmq_public.h
├── exceptions.cpp
├── exceptions.h
├── fdmanaged.cpp
├── fdmanaged.h
├── flags.h
├── flashmq.conf
├── flashmq_plugin.cpp
├── flashmq_plugin.h
├── flashmq_plugin_deprecated.h
├── flashmq_public.h
├── flashmqtestclient.cpp
├── flashmqtestclient.h
├── fmqmain.cpp
├── fmqmain.h
├── fmqsockaddr.cpp
├── fmqsockaddr.h
├── fmqssl.cpp
├── fmqssl.h
├── forward_declarations.h
├── fuzz-helper.sh
├── globals.cpp
├── globals.h
├── globalstats.cpp
├── globalstats.h
├── globber.cpp
├── globber.h
├── haproxy.cpp
├── haproxy.h
├── http.cpp
├── http.h
├── iowrapper.cpp
├── iowrapper.h
├── listener.cpp
├── listener.h
├── lockedsharedptr.h
├── lockedweakptr.cpp
├── lockedweakptr.h
├── logger.cpp
├── logger.h
├── main.cpp
├── mainapp.cpp
├── mainapp.h
├── man/
│   ├── .gitignore
│   ├── Makefile
│   ├── README.md
│   ├── docbook5-refentry-xslt/
│   │   ├── docbook5-refentry-to-html5.xsl
│   │   └── docbook5-refentry-to-manpage.xsl
│   ├── flashmq-docbook5-refentry-to-html5.xsl
│   ├── flashmq-docbook5-refentry-to-manpage.xsl
│   ├── flashmq.1
│   ├── flashmq.1.dbk5
│   ├── flashmq.1.html
│   ├── flashmq.conf.5
│   ├── flashmq.conf.5.dbk5
│   ├── flashmq.conf.5.html
│   ├── refentry.colophon.dbk5
│   └── reference.dbk5
├── mosquittoauthoptcompatwrap.cpp
├── mosquittoauthoptcompatwrap.h
├── mqtt5properties.cpp
├── mqtt5properties.h
├── mqttpacket.cpp
├── mqttpacket.h
├── mutexowned.h
├── network.cpp
├── network.h
├── nocopy.cpp
├── nocopy.h
├── oneinstancelock.cpp
├── oneinstancelock.h
├── packetdatatypes.cpp
├── packetdatatypes.h
├── persistencefile.cpp
├── persistencefile.h
├── persistencefunctions.cpp
├── persistencefunctions.h
├── plugin.cpp
├── plugin.h
├── pluginloader.cpp
├── pluginloader.h
├── publishcopyfactory.cpp
├── publishcopyfactory.h
├── qospacketqueue.cpp
├── qospacketqueue.h
├── queuedtasks.cpp
├── queuedtasks.h
├── release.sh
├── retainedmessage.cpp
├── retainedmessage.h
├── retainedmessagesdb.cpp
├── retainedmessagesdb.h
├── rwlockguard.cpp
├── rwlockguard.h
├── scopedsocket.cpp
├── scopedsocket.h
├── sdnotify.cpp
├── sdnotify.h
├── session.cpp
├── session.h
├── sessionsandsubscriptionsdb.cpp
├── sessionsandsubscriptionsdb.h
├── settings.cpp
├── settings.h
├── sharedsubscribers.cpp
├── sharedsubscribers.h
├── sslctxmanager.cpp
├── sslctxmanager.h
├── subscription.cpp
├── subscription.h
├── subscriptionstore.cpp
├── subscriptionstore.h
├── threaddata.cpp
├── threaddata.h
├── threadglobals.cpp
├── threadglobals.h
├── threadlocalutils.cpp
├── threadlocalutils.h
├── threadlocked.h
├── threadloop.cpp
├── threadloop.h
├── types.cpp
├── types.h
├── unscopedlock.cpp
├── unscopedlock.h
├── utils.cpp
├── utils.h
├── variablebyteint.cpp
├── variablebyteint.h
├── x509manager.cpp
└── x509manager.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .build-ci.sh
================================================
#!/bin/bash

set -e  # If any step reports a problem consider the whole build a failure
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
sudo mv linuxdeploy-x86_64.AppImage /usr/local/bin
sudo chmod +x /usr/local/bin/linuxdeploy-x86_64.AppImage
sudo apt update
sudo apt install -y shellcheck
shellcheck debian/post* debian/pre*
./build.sh
./FlashMQBuildRelease/FlashMQ --version
sudo dpkg -i ./FlashMQBuildRelease/*.deb
set +e  # Prevent Travis internals from breaking our build, see https://github.com/travis-ci/travis-ci/issues/891


================================================
FILE: .clang-format
================================================
---
Language:        Cpp
# BasedOnStyle:  LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: BlockIndent
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
AlignEscapedNewlines: Right
AlignOperands:   Align
AlignTrailingComments: false
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
  - __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
  AfterCaseLabel:  true
  AfterClass:      true
  AfterControlStatement: Always
  AfterEnum:       false
  AfterFunction:   true
  AfterNamespace:  false
  AfterObjCDeclaration: false
  AfterStruct:     false
  AfterUnion:      false
  AfterExternBlock: false
  BeforeCatch:     true
  BeforeElse:      true
  BeforeLambdaBody: false
  BeforeWhile:     true
  IndentBraces:    false
  SplitEmptyFunction: true
  SplitEmptyRecord: true
  SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
#BreakBeforeBraces: Allman
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: AfterColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit:     180
CommentPragmas:  '^ IWYU pragma:'
QualifierAlignment: Leave
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat:   false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: Never
BasedOnStyle:    ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: true
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
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequiresClause: true
IndentWidth:     4
IndentWrappedFunctionNames: false
InsertBraces:    false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd:   ''
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PenaltyIndentedWhitespace: 0
PointerAlignment: Right
PPIndentWidth:   -1
ReferenceAlignment: Pointer
ReflowComments:  false
RemoveBracesLLVM: false
RequiresClausePosition: OwnLine
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes:    Never
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
  AfterControlStatements: true
  AfterForeachMacros: true
  AfterFunctionDefinitionName: false
  AfterFunctionDeclarationName: false
  AfterIfMacros:   true
  AfterOverloadedOperator: false
  AfterRequiresInClause: false
  AfterRequiresInExpression: false
  BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles:  Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
  Minimum:         1
  Maximum:         -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard:        Latest
StatementAttributeLikeMacros:
  - Q_EMIT
StatementMacros:
  - Q_UNUSED
  - QT_REQUIRE_VERSION
TabWidth:        8
UseCRLF:         false
UseTab:          Never
WhitespaceSensitiveMacros:
  - STRINGIZE
  - PP_STRINGIZE
  - BOOST_PP_STRINGIZE
  - NS_SWIFT_NAME
  - CF_SWIFT_NAME
...



================================================
FILE: .clang-format.original
================================================
---
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
AlignEscapedNewlines: Right
AlignOperands:   Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
  - __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
  AfterCaseLabel:  false
  AfterClass:      false
  AfterControlStatement: Never
  AfterEnum:       false
  AfterFunction:   false
  AfterNamespace:  false
  AfterObjCDeclaration: false
  AfterStruct:     false
  AfterUnion:      false
  AfterExternBlock: false
  BeforeCatch:     false
  BeforeElse:      false
  BeforeLambdaBody: false
  BeforeWhile:     false
  IndentBraces:    false
  SplitEmptyFunction: true
  SplitEmptyRecord: true
  SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit:     80
CommentPragmas:  '^ IWYU pragma:'
QualifierAlignment: Leave
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat:   false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: BinPack
BasedOnStyle:    ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: true
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
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequiresClause: true
IndentWidth:     2
IndentWrappedFunctionNames: false
InsertBraces:    false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd:   ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PenaltyIndentedWhitespace: 0
PointerAlignment: Right
PPIndentWidth:   -1
ReferenceAlignment: Pointer
ReflowComments:  true
RemoveBracesLLVM: false
RequiresClausePosition: OwnLine
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes:    CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
  AfterControlStatements: true
  AfterForeachMacros: true
  AfterFunctionDefinitionName: false
  AfterFunctionDeclarationName: false
  AfterIfMacros:   true
  AfterOverloadedOperator: false
  AfterRequiresInClause: false
  AfterRequiresInExpression: false
  BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles:  Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
  Minimum:         1
  Maximum:         -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard:        Latest
StatementAttributeLikeMacros:
  - Q_EMIT
StatementMacros:
  - Q_UNUSED
  - QT_REQUIRE_VERSION
TabWidth:        8
UseCRLF:         false
UseTab:          Never
WhitespaceSensitiveMacros:
  - STRINGIZE
  - PP_STRINGIZE
  - BOOST_PP_STRINGIZE
  - NS_SWIFT_NAME
  - CF_SWIFT_NAME
...



================================================
FILE: .dockerignore
================================================
Dockerfile


================================================
FILE: .get-os-codename-and-stamp.sh
================================================
#!/bin/bash

my_codename=""
my_int_version=""

if [[ -e "/etc/os-release" ]]; then
  eval "$(cat "/etc/os-release")"
  my_codename="$VERSION_CODENAME"
  my_int_version=${VERSION_ID//./}
elif [[ -e "/etc/lsb-release" ]]; then
  eval "$(cat "/etc/lsb-release")"
  my_codename="$DISTRIB_CODENAME"
  my_int_version=${DISTRIB_RELEASE//./}
else
  1>&2 echo "Error in determing os codename"
  exit 1
fi

if [[ -z "$my_codename" ]]; then
  1>&2 echo "ERROR in determining OS codename"
  exit 1
fi

if [[ ! "$my_int_version" =~ ^[0-9]+$ ]]; then
  1>&2 echo "ERROR: int version '$my_int_version' is not an int. We need a numeric string for proper debian-revision version comparison."
  exit 1
fi

# Sequence numbers makes sure that when one upgrades the OS, the package for
# the new distro version is selected.
sequence="$my_int_version"

if [[ -z "$sequence" ]]; then
  1>&2 echo "ERROR: no OS sequence defined for $my_codename"
  exit 2
fi

echo -n "${sequence}+${my_codename}+${EPOCHSECONDS}"


================================================
FILE: .github/workflows/building.yml
================================================
name: Building
on: [push]
jobs:
  compilation:
    strategy:
      matrix:
        include:
          - os: ubuntu-24.04
            friendly: clang
            CXX: /usr/bin/clang++
            aptpkg: clang
          - os: ubuntu-24.04
            friendly: gcc
            CXX: /usr/bin/g++
            aptpkg: build-essential
          - os: ubuntu-22.04
            friendly: clang
            CXX: /usr/bin/clang++
            aptpkg: clang
          - os: ubuntu-22.04
            friendly: gcc
            CXX: /usr/bin/g++
            aptpkg: build-essential
    runs-on: ${{ matrix.os }}
    name: "${{ matrix.os }}: ${{ matrix.friendly }}"
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - run: sudo apt update
      # Build prerequisites
      - run: sudo apt install -y xsltproc
      - run: sudo apt install -y cmake libssl-dev libcurl4-openssl-dev ${{ matrix.aptpkg }}
      # Build example plugin(s)
      - run: CXX="${{ matrix.CXX }}" ./examples/plugin_libcurl/build.sh "Release"
      # Building
      - run: CXX="${{ matrix.CXX }}" ./build.sh "Release"
      # Testing build results
      - run: ./FlashMQBuildRelease/flashmq --version
      - run: sudo dpkg -i ./FlashMQBuildRelease/*.deb


================================================
FILE: .github/workflows/codestyle.yml
================================================
name: Checking code style
on: [pull_request]
jobs:
  check_cpp_codestyle:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - run: sudo apt install -y clang-format-15
      - run: git fetch origin "$GITHUB_BASE_REF" --depth 1  # Github CI only checks out a single commit, so we need to fetch what to compare against
      - run: CHANGES=$(git diff -U0 --no-color "origin/$GITHUB_BASE_REF" | clang-format-diff-15 -p1); [[ -z "$CHANGES" ]] || { echo "Please fix the following C++ style issues:"; echo "$CHANGES"; exit 2; }


================================================
FILE: .github/workflows/docker.yml
================================================
name: Docker
on: [push]
jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - run: docker build . -t halfgaar/flashmq
      - run: docker run halfgaar/flashmq /bin/flashmq --version


================================================
FILE: .github/workflows/linting.yml
================================================
name: Linting
on: [push]
jobs:
  shellcheck:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - run: sudo apt install -y shellcheck
      - run: shellcheck debian/post* debian/pre*
      - run: find . -type f -iname '*.sh' -exec shellcheck '{}' '+'
  markdownlint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - run: sudo snap install mdl
        # Exclude MD013: line-length. If it looks good to the dev editing the Markdown file it's good enough for us
      - run: find . -type f -iname '*.md' -exec mdl --rules "~MD013" '{}' '+'


================================================
FILE: .github/workflows/testing.yml
================================================
name: Testing
on: [push]
jobs:
  compilation:
    strategy:
      matrix:
        include:
          - os: ubuntu-24.04
            friendly: clang
            compiler: clang++
            aptpkg: clang
          - os: ubuntu-24.04
            friendly: gcc
            compiler: g++
            aptpkg: build-essential
          - os: ubuntu-22.04
            friendly: clang
            compiler: clang++
            aptpkg: clang
          - os: ubuntu-22.04
            friendly: gcc
            compiler: g++
            aptpkg: build-essential
    runs-on: ${{ matrix.os }}
    defaults:
      run:
        working-directory: FlashMQTests
    name: "Normal test - ${{ matrix.os }}: ${{ matrix.friendly }}"
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - run: sudo apt update
      # Build prerequisites
      - run: sudo apt install -y cmake libssl-dev libcurl4-openssl-dev ${{ matrix.aptpkg }}
      # Building
      - run: ./run-make-from-ci.sh --compiler "${{ matrix.compiler }}"
      - run: ./run-tests-from-ci.sh


================================================
FILE: .github/workflows/testing_non_sse.yml
================================================
name: TestingNoSse
on: [push]
jobs:
  compilation:
    strategy:
      matrix:
        include:
          - os: ubuntu-24.04
            friendly: clang
            compiler: clang++
            aptpkg: clang
          - os: ubuntu-24.04
            friendly: gcc
            compiler: g++
            aptpkg: build-essential
          - os: ubuntu-22.04
            friendly: clang
            compiler: clang++
            aptpkg: clang
          - os: ubuntu-22.04
            friendly: gcc
            compiler: g++
            aptpkg: build-essential
    runs-on: ${{ matrix.os }}
    defaults:
      run:
        working-directory: FlashMQTests
    name: "Non-SSE test - ${{ matrix.os }}: ${{ matrix.friendly }}"
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - run: sudo apt update
      # Build prerequisites
      - run: sudo apt install -y cmake libssl-dev libcurl4-openssl-dev ${{ matrix.aptpkg }}
      # Building
      - run: ./run-make-from-ci.sh --compiler "${{ matrix.compiler }}" --extra-config "FMQ_NO_SSE=1"
      - run: ./run-tests-from-ci.sh


================================================
FILE: .gitignore
================================================
*.user
build-*
FlashMQBuild*
*.swp
compile_commands.json
.clangd/
.cache/
build
FlashMQTests/build


================================================
FILE: CMakeLists.shared
================================================
# This file is shared between FlashMQ itself and FlashMQTests
# It should not contain any definitions that isn't used by both

# When building FlashMQTests:
# CMAKE_CURRENT_SOURCE_DIR -> /home/user/FlashMQ/FlashMQTests
# CMAKE_CURRENT_LIST_DIR   -> /home/user/FlashMQ
#
# When building FlashMQ:
# CMAKE_CURRENT_SOURCE_DIR -> /home/user/FlashMQ
# CMAKE_CURRENT_LIST_DIR   -> /home/user/FlashMQ

if (CMAKE_CURRENT_LIST_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
    set(RELPATH "./")
else()
    set(RELPATH "../")
endif()

message("Determined RELPATH: ${RELPATH}")

set(FLASHMQ_HEADERS
    ${RELPATH}forward_declarations.h
    ${RELPATH}mainapp.h
    ${RELPATH}utils.h
    ${RELPATH}threaddata.h
    ${RELPATH}client.h
    ${RELPATH}session.h
    ${RELPATH}mqttpacket.h
    ${RELPATH}exceptions.h
    ${RELPATH}types.h
    ${RELPATH}subscriptionstore.h
    ${RELPATH}rwlockguard.h
    ${RELPATH}retainedmessage.h
    ${RELPATH}cirbuf.h
    ${RELPATH}logger.h
    ${RELPATH}plugin.h
    ${RELPATH}configfileparser.h
    ${RELPATH}sslctxmanager.h
    ${RELPATH}iowrapper.h
    ${RELPATH}mosquittoauthoptcompatwrap.h
    ${RELPATH}settings.h
    ${RELPATH}listener.h
    ${RELPATH}unscopedlock.h
    ${RELPATH}scopedsocket.h
    ${RELPATH}bindaddr.h
    ${RELPATH}oneinstancelock.h
    ${RELPATH}evpencodectxmanager.h
    ${RELPATH}acltree.h
    ${RELPATH}enums.h
    ${RELPATH}threadlocalutils.h
    ${RELPATH}flashmq_plugin.h
    ${RELPATH}flashmq_plugin_deprecated.h
    ${RELPATH}retainedmessagesdb.h
    ${RELPATH}persistencefile.h
    ${RELPATH}sessionsandsubscriptionsdb.h
    ${RELPATH}qospacketqueue.h
    ${RELPATH}threadglobals.h
    ${RELPATH}threadloop.h
    ${RELPATH}publishcopyfactory.h
    ${RELPATH}variablebyteint.h
    ${RELPATH}mqtt5properties.h
    ${RELPATH}globalstats.h
    ${RELPATH}derivablecounter.h
    ${RELPATH}packetdatatypes.h
    ${RELPATH}haproxy.h
    ${RELPATH}network.h
    ${RELPATH}subscription.h
    ${RELPATH}sharedsubscribers.h
    ${RELPATH}pluginloader.h
    ${RELPATH}queuedtasks.h
    ${RELPATH}acksender.h
    ${RELPATH}bridgeconfig.h
    ${RELPATH}dnsresolver.h
    ${RELPATH}globber.h
    ${RELPATH}bridgeinfodb.h
    ${RELPATH}x509manager.h
    ${RELPATH}backgroundworker.h
    ${RELPATH}fmqmain.h
    ${RELPATH}driftcounter.h
    ${RELPATH}lockedweakptr.h
    ${RELPATH}lockedsharedptr.h
    ${RELPATH}globals.h
    ${RELPATH}nocopy.h
    ${RELPATH}fdmanaged.h
    ${RELPATH}checkedweakptr.h
    ${RELPATH}mutexowned.h
    ${RELPATH}http.h
    ${RELPATH}fmqsockaddr.h
    ${RELPATH}flags.h
    ${RELPATH}persistencefunctions.h
    ${RELPATH}clientacceptqueue.h
    ${RELPATH}checkedsharedptr.h
    ${RELPATH}flashmq_public.h
    ${RELPATH}sdnotify.h
    ${RELPATH}threadlocked.h
)

set(FLASHMQ_IMPLS
    ${RELPATH}mainapp.cpp
    ${RELPATH}utils.cpp
    ${RELPATH}threaddata.cpp
    ${RELPATH}client.cpp
    ${RELPATH}session.cpp
    ${RELPATH}mqttpacket.cpp
    ${RELPATH}exceptions.cpp
    ${RELPATH}types.cpp
    ${RELPATH}subscriptionstore.cpp
    ${RELPATH}rwlockguard.cpp
    ${RELPATH}retainedmessage.cpp
    ${RELPATH}cirbuf.cpp
    ${RELPATH}logger.cpp
    ${RELPATH}plugin.cpp
    ${RELPATH}configfileparser.cpp
    ${RELPATH}sslctxmanager.cpp
    ${RELPATH}iowrapper.cpp
    ${RELPATH}mosquittoauthoptcompatwrap.cpp
    ${RELPATH}settings.cpp
    ${RELPATH}listener.cpp
    ${RELPATH}unscopedlock.cpp
    ${RELPATH}scopedsocket.cpp
    ${RELPATH}bindaddr.cpp
    ${RELPATH}oneinstancelock.cpp
    ${RELPATH}evpencodectxmanager.cpp
    ${RELPATH}acltree.cpp
    ${RELPATH}threadlocalutils.cpp
    ${RELPATH}flashmq_plugin.cpp
    ${RELPATH}retainedmessagesdb.cpp
    ${RELPATH}persistencefile.cpp
    ${RELPATH}sessionsandsubscriptionsdb.cpp
    ${RELPATH}qospacketqueue.cpp
    ${RELPATH}threadglobals.cpp
    ${RELPATH}threadloop.cpp
    ${RELPATH}publishcopyfactory.cpp
    ${RELPATH}variablebyteint.cpp
    ${RELPATH}mqtt5properties.cpp
    ${RELPATH}globalstats.cpp
    ${RELPATH}derivablecounter.cpp
    ${RELPATH}packetdatatypes.cpp
    ${RELPATH}haproxy.cpp
    ${RELPATH}network.cpp
    ${RELPATH}subscription.cpp
    ${RELPATH}sharedsubscribers.cpp
    ${RELPATH}pluginloader.cpp
    ${RELPATH}queuedtasks.cpp
    ${RELPATH}acksender.cpp
    ${RELPATH}bridgeconfig.cpp
    ${RELPATH}dnsresolver.cpp
    ${RELPATH}bridgeinfodb.cpp
    ${RELPATH}globber.cpp
    ${RELPATH}x509manager.cpp
    ${RELPATH}backgroundworker.cpp
    ${RELPATH}fmqmain.cpp
    ${RELPATH}driftcounter.cpp
    ${RELPATH}lockedweakptr.cpp
    ${RELPATH}globals.cpp
    ${RELPATH}nocopy.cpp
    ${RELPATH}fdmanaged.cpp
    ${RELPATH}http.cpp
    ${RELPATH}fmqsockaddr.cpp
    ${RELPATH}fmqssl.cpp
    ${RELPATH}persistencefunctions.cpp
    ${RELPATH}clientacceptqueue.cpp
    ${RELPATH}sdnotify.cpp
    )


================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.5)
cmake_policy(SET CMP0048 NEW)
include(CheckCXXCompilerFlag)
include(CMakeLists.shared)

project(FlashMQ VERSION 1.26.1 LANGUAGES CXX)

add_definitions(-DOPENSSL_API_COMPAT=0x10100000L)
add_definitions(-DFLASHMQ_VERSION=\"${PROJECT_VERSION}\")

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if (FMQ_ASAN)
    message("Building with ASAN.")
    add_compile_options(-fsanitize=address)
    add_link_options(-fsanitize=address)
endif()

check_cxx_compiler_flag("-msse4.2"   COMPILER_RT_HAS_MSSE4_2_FLAG)
if (${COMPILER_RT_HAS_MSSE4_2_FLAG})
    SET(CMAKE_CXX_FLAGS "-msse4.2")
endif()

add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden)
add_link_options(-rdynamic)

add_compile_options(-Wall -Wextra)

add_executable(flashmq
    ${FLASHMQ_HEADERS}
    ${FLASHMQ_IMPLS}

    main.cpp
    )

target_link_libraries(flashmq pthread dl ssl crypto resolv anl)

execute_process(COMMAND ../.get-os-codename-and-stamp.sh OUTPUT_VARIABLE OS_CODENAME)

install(TARGETS flashmq
  RUNTIME DESTINATION "/usr/bin/")

install(DIRECTORY DESTINATION "/var/lib/flashmq")
install(DIRECTORY DESTINATION "/var/log/flashmq")
install(FILES flashmq.conf DESTINATION "/etc/flashmq")
install(FILES debian/flashmq.service DESTINATION "/lib/systemd/system")
install(FILES man/flashmq.conf.5 DESTINATION "/usr/share/man/man5")
install(FILES man/flashmq.1 DESTINATION "/usr/share/man/man1")

SET(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/debian/conffiles;${CMAKE_CURRENT_SOURCE_DIR}/debian/preinst;${CMAKE_CURRENT_SOURCE_DIR}/debian/postinst;${CMAKE_CURRENT_SOURCE_DIR}/debian/postrm;${CMAKE_CURRENT_SOURCE_DIR}/debian/prerm")

SET(CPACK_GENERATOR "DEB")
SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Wiebe Cazemier <wiebe@flashmq.org>")
SET(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Light-weight, high performance MQTT server capable of million+ messages per second.")
SET(CPACK_PACKAGE_HOMEPAGE_URL "https://www.flashmq.org/")
SET(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
SET(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT")
SET(CPACK_DEBIAN_PACKAGE_RELEASE ${OS_CODENAME})
SET(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
SET(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
SET(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
INCLUDE(CPack)


================================================
FILE: Dockerfile
================================================
# build target, used for building the binary, providing shared libraries and could be used as a development env
FROM debian:trixie-slim AS build

# install build dependencies
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install g++ make cmake libssl-dev file

# create flashmq user and group for runtime image below
RUN useradd --system --shell /bin/false --user-group --no-log-init flashmq

ARG BUILD_TYPE=Release
RUN echo "Building ${BUILD_TYPE} version"

WORKDIR /usr/src/app
COPY . .
RUN rm -rf FlashMQBuild* 2>/dev/null
RUN ./build.sh ${BUILD_TYPE}

# convert docker buildx platform name to Debian platform name
FROM scratch AS run-amd64
ARG PLATFORM=x86_64
ARG LD_LOCATION=/lib64/ld-linux-x86-64.so.2

FROM scratch AS run-arm64
ARG PLATFORM=aarch64
ARG LD_LOCATION=/lib/ld-linux-aarch64.so.1

# from scratch image is empty
FROM run-$TARGETARCH AS run

USER flashmq:flashmq
COPY --from=build /etc/passwd /etc/passwd
COPY --from=build /etc/group /etc/group

# copy in the shared libaries in use discovered using ldd on release binary
COPY --from=build /lib/${PLATFORM}-linux-gnu/libpthread.so.0 /lib/${PLATFORM}-linux-gnu/libpthread.so.0
COPY --from=build /lib/${PLATFORM}-linux-gnu/libdl.so.2 /lib/${PLATFORM}-linux-gnu/libdl.so.2
COPY --from=build /usr/lib/${PLATFORM}-linux-gnu/libssl.so.3 /usr/lib/${PLATFORM}-linux-gnu/libssl.so.3
COPY --from=build /usr/lib/${PLATFORM}-linux-gnu/libcrypto.so.3 /usr/lib/${PLATFORM}-linux-gnu/libcrypto.so.3
COPY --from=build /usr/lib/${PLATFORM}-linux-gnu/libstdc++.so.6 /usr/lib/${PLATFORM}-linux-gnu/libstdc++.so.6
COPY --from=build /lib/${PLATFORM}-linux-gnu/libgcc_s.so.1 /lib/${PLATFORM}-linux-gnu/libgcc_s.so.1
COPY --from=build /lib/${PLATFORM}-linux-gnu/libc.so.6 /lib/${PLATFORM}-linux-gnu/libc.so.6
COPY --from=build ${LD_LOCATION} ${LD_LOCATION}
COPY --from=build /lib/${PLATFORM}-linux-gnu/libm.so.6 /lib/${PLATFORM}-linux-gnu/libm.so.6
COPY --from=build /lib/${PLATFORM}-linux-gnu/libresolv.so.2 /lib/${PLATFORM}-linux-gnu/libresolv.so.2
COPY --from=build /lib/${PLATFORM}-linux-gnu/libanl.so.1 /lib/${PLATFORM}-linux-gnu/libanl.so.1
COPY --from=build /lib/${PLATFORM}-linux-gnu/libz.so.1 /lib/${PLATFORM}-linux-gnu/libz.so.1
COPY --from=build /lib/${PLATFORM}-linux-gnu/libzstd.so.1 /lib/${PLATFORM}-linux-gnu/libzstd.so.1

# copy in the FlashMQ binary itself
COPY --from=build /usr/src/app/FlashMQBuildRelease/flashmq /bin/flashmq

EXPOSE 1883
CMD ["/bin/flashmq"]


================================================
FILE: FlashMQTests/CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.5)
cmake_policy(SET CMP0048 NEW)
include(CheckCXXCompilerFlag)
include(../CMakeLists.shared)

project(FlashMQTests VERSION 1.0.0 LANGUAGES CXX)

add_definitions(-DOPENSSL_API_COMPAT=0x10100000L)
add_definitions(-DFLASHMQ_VERSION=\"${PROJECT_VERSION}\")
add_definitions(-DTESTING)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if (FMQ_NO_SSE)
    add_definitions(-DFMQ_NO_SSE)
    message("Building tests wihout SSE.")
else()
    SET(CMAKE_CXX_FLAGS "-msse4.2")
endif()

add_link_options(-rdynamic)

add_compile_options(-Wall -Wextra -fsanitize=address)
add_link_options(-fsanitize=address)

add_library(test_plugin SHARED
    plugins/test_plugin.h plugins/test_plugin.cpp
    plugins/curlfunctions.h plugins/curlfunctions.cpp
)

target_link_libraries(test_plugin curl)

# Copying to a version 0.0.1 file is a bit of a hack. I'm not sure how to version it in this CMake otherwise.
add_custom_command(TARGET test_plugin POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "plugins/")
add_custom_command(TARGET test_plugin POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:test_plugin> "plugins/libtest_plugin.so.0.0.1")

# Copy in a way that always copies, to avoid unexpected staleness.
add_custom_command(TARGET test_plugin POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/UTF-8-test.txt"
    "${CMAKE_BINARY_DIR}/UTF-8-test.txt")
add_custom_command(TARGET test_plugin POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/../fuzztests/plainwebsocketpacket1_handshake.dat"
    "${CMAKE_BINARY_DIR}/plainwebsocketpacket1_handshake.dat")

add_executable(flashmq-tests
    ${FLASHMQ_HEADERS}
    ${FLASHMQ_IMPLS}

    ../flashmqtestclient.cpp ../flashmqtestclient.h
    main.cpp
    mainappinthread.h mainappinthread.cpp
    maintests.h maintests.cpp
    testhelpers.cpp testhelpers.h
    flashmqtempdir.h flashmqtempdir.cpp
    tst_maintests.cpp
    retaintests.cpp
    conffiletemp.cpp conffiletemp.h
    filecloser.cpp filecloser.h
    plugintests.cpp
    configtests.cpp
    sharedsubscriptionstests.cpp
    websockettests.cpp
    willtests.cpp
    dnstests.cpp
    utiltests.cpp
    testinitializer.h testinitializer.cpp
    mainappasfork.h mainappasfork.cpp
    subscriptionidtests.cpp
    bridgeprefixtests.cpp

    )

target_compile_options(flashmq-tests PUBLIC -fvisibility=hidden -fvisibility-inlines-hidden)
target_link_options(flashmq-tests PUBLIC -rdynamic)

target_include_directories(flashmq-tests PUBLIC ..)

target_link_libraries(flashmq-tests pthread dl ssl crypto resolv anl)







================================================
FILE: FlashMQTests/bridgeprefixtests.cpp
================================================
#include "maintests.h"
#include "conffiletemp.h"
#include "flashmqtestclient.h"
#include "mainappasfork.h"
#include "testhelpers.h"
#include "flashmqtempdir.h"

void waitForMessagesOverBridge(FlashMQTestClient &one, FlashMQTestClient &two, const std::string &topic)
{
    int wait_i = 0;
    for(wait_i = 0; wait_i < 10; wait_i++)
    {
        one.publish(topic, "connectiontest", 2);

        try
        {
            two.waitForMessageCount(1);
            break;
        }
        catch (std::exception &ex) { }
    }

    if (wait_i >= 10)
        throw std::runtime_error("Timeout waiting for messages over bridge");
}

void MainTests::forkingTestBridgeWithLocalAndRemotePrefix()
{
    for (const std::string protocol_version : {"mqtt5", "mqtt3.1"})
    {
        cleanup();

        ConfFileTemp confFile;
        const std::string config = R"(
allow_anonymous true
log_debug false

bridge {
    address ::1
    port 21883
    subscribe ManglerRemote/shoes 2
    publish ManglerLocal/boots 2
    clientid_prefix Mangler
    local_prefix ManglerLocal/
    remote_prefix ManglerRemote/
    protocol_version %s
}
listen {
    protocol mqtt
    port 51183
})";
        confFile.writeLine(formatString(config, protocol_version.c_str()));
        confFile.closeFile();

        std::vector<std::string> args {"--config-file", confFile.getFilePath()};

        MainAppAsFork app(args);
        app.start();
        app.waitForStarted(51183);

        // We will consider the test server initialized here the 'remote' broker.
        init();

        FlashMQTestClient clientToLocalWithBridge;
        clientToLocalWithBridge.start();

        FlashMQTestClient clientToRemote;
        clientToRemote.start();

        clientToLocalWithBridge.connectClient(ProtocolVersion::Mqtt31, 51183);
        clientToLocalWithBridge.subscribe("#", 1);

        clientToRemote.connectClient(ProtocolVersion::Mqtt5);
        clientToRemote.subscribe("#", 1);

        waitForMessagesOverBridge(clientToLocalWithBridge, clientToRemote, "ManglerLocal/boots");

        clientToLocalWithBridge.clearReceivedLists();
        clientToRemote.clearReceivedLists();

        {
            clientToLocalWithBridge.publish("ManglerLocal/boots", "asdf", 2);
            clientToRemote.waitForMessageCount(1);
            auto ro = clientToRemote.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerRemote/boots"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "asdf");
        }

        clientToLocalWithBridge.clearReceivedLists();
        clientToRemote.clearReceivedLists();

        {
            clientToRemote.publish("ManglerRemote/shoes", "are made for walking", 2);
            clientToLocalWithBridge.waitForMessageCount(1);
            auto ro = clientToLocalWithBridge.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerLocal/shoes"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "are made for walking");
        }
    }
}

/**
 * @brief Test the internal packet cache; that we don't accidentally cache packets with the prefixes applied.
 *
 * The PublishCopyFactory was temporarily broken first to write this test.
 */
void MainTests::forkingTestBridgePrefixesOtherClientsUnaffected()
{
    for (const std::string protocol_version : {"mqtt5", "mqtt3.1"})
    {
        cleanup();

        ConfFileTemp confFile;
        const std::string config = R"(
allow_anonymous true
log_debug false

bridge {
    address ::1
    port 21883
    subscribe ManglerRemote/shoes 2
    publish ManglerLocal/boots 2
    clientid_prefix Mangler
    local_prefix ManglerLocal/
    remote_prefix ManglerRemote/
    protocol_version %s
}
listen {
    protocol mqtt
    port 51183
})";
        confFile.writeLine(formatString(config, protocol_version.c_str()));
        confFile.closeFile();

        std::vector<std::string> args {"--config-file", confFile.getFilePath()};

        MainAppAsFork app(args);
        app.start();
        app.waitForStarted(51183);

        // We will consider the test server initialized here the 'remote' broker.
        init();

        FlashMQTestClient randomOtherLocalClient1;
        randomOtherLocalClient1.start();
        randomOtherLocalClient1.connectClient(ProtocolVersion::Mqtt5, 51183);

        // Not using wild-cards because we happen to know that produces the correct delivery order to subscribers to test this.
        randomOtherLocalClient1.subscribe("ManglerLocal/boots", 1);
        randomOtherLocalClient1.subscribe("ManglerRemote/boots", 1);

        FlashMQTestClient clientToLocalWithBridge;
        clientToLocalWithBridge.start();

        FlashMQTestClient clientToRemote;
        clientToRemote.start();

        clientToLocalWithBridge.connectClient(ProtocolVersion::Mqtt31, 51183);
        clientToLocalWithBridge.subscribe("#", 1);

        clientToRemote.connectClient(ProtocolVersion::Mqtt5);
        clientToRemote.subscribe("#", 1);

        FlashMQTestClient randomOtherLocalClient2;
        randomOtherLocalClient2.start();
        randomOtherLocalClient2.connectClient(ProtocolVersion::Mqtt5, 51183);

        // Not using wild-cards because we happen to know that produces the correct delivery order to subscribers to test this.
        randomOtherLocalClient2.subscribe("ManglerLocal/boots", 1);
        randomOtherLocalClient2.subscribe("ManglerRemote/boots", 1);

        FlashMQTestClient randomOtherLocalClient3;
        randomOtherLocalClient3.start();
        randomOtherLocalClient3.connectClient(ProtocolVersion::Mqtt5, 51183);

        // For this one, we do use the wildcard subscription, just to be sure.
        randomOtherLocalClient3.subscribe("ManglerLocal/#", 1);

        waitForMessagesOverBridge(clientToLocalWithBridge, clientToRemote, "ManglerLocal/boots");

        clientToLocalWithBridge.clearReceivedLists();
        clientToRemote.clearReceivedLists();

        {
            clientToLocalWithBridge.publish("ManglerLocal/boots", "asdf", 2);
            clientToRemote.waitForMessageCount(1);
            auto ro = clientToRemote.receivedObjects.lock();
            //FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerRemote/boots"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "asdf");
        }

        clientToLocalWithBridge.clearReceivedLists();


        {
            randomOtherLocalClient1.waitForMessageCount(2);
            auto ro = randomOtherLocalClient1.receivedObjects.lock();
            FMQ_VERIFY(std::all_of(ro->receivedPublishes.begin(), ro->receivedPublishes.end(), [](MqttPacket &p) {
                return startsWith(p.getTopic(), "ManglerLocal/");
            }));
        }

        {
            randomOtherLocalClient2.waitForMessageCount(2);
            auto ro = randomOtherLocalClient2.receivedObjects.lock();
            FMQ_VERIFY(std::all_of(ro->receivedPublishes.begin(), ro->receivedPublishes.end(), [](MqttPacket &p) {
                return startsWith(p.getTopic(), "ManglerLocal/");
            }));
        }

        {
            randomOtherLocalClient3.waitForMessageCount(2);
            auto ro = randomOtherLocalClient3.receivedObjects.lock();
            FMQ_VERIFY(std::all_of(ro->receivedPublishes.begin(), ro->receivedPublishes.end(), [](MqttPacket &p) {
                return startsWith(p.getTopic(), "ManglerLocal/");
            }));
        }
    }
}

void MainTests::forkingTestBridgeWithOnlyRemotePrefix()
{
    for (const std::string protocol_version : {"mqtt5", "mqtt3.1"})
    {
        cleanup();

        ConfFileTemp confFile;
        const std::string config = R"(
allow_anonymous true
log_debug false

bridge {
    address ::1
    port 21883
    subscribe ManglerRemote/shoes 2
    publish boots 2
    clientid_prefix Mangler
    remote_prefix ManglerRemote/
    protocol_version %s
}
listen {
    protocol mqtt
    port 51183
})";
        confFile.writeLine(formatString(config, protocol_version.c_str()));
        confFile.closeFile();

        std::vector<std::string> args {"--config-file", confFile.getFilePath()};

        MainAppAsFork app(args);
        app.start();
        app.waitForStarted(51183);

        // We will consider the test server initialized here the 'remote' broker.
        init();

        FlashMQTestClient clientToLocalWithBridge;
        clientToLocalWithBridge.start();

        FlashMQTestClient clientToRemote;
        clientToRemote.start();

        FlashMQTestClient receiverToLocal;
        receiverToLocal.start();

        FlashMQTestClient receiverToLocal2;
        receiverToLocal2.start();

        receiverToLocal2.connectClient(ProtocolVersion::Mqtt31, 51183);
        receiverToLocal2.subscribe("#", 1);

        clientToLocalWithBridge.connectClient(ProtocolVersion::Mqtt31, 51183);
        clientToLocalWithBridge.subscribe("#", 1);

        clientToRemote.connectClient(ProtocolVersion::Mqtt5);
        clientToRemote.subscribe("#", 1);

        waitForMessagesOverBridge(clientToLocalWithBridge, clientToRemote, "boots");

        clientToLocalWithBridge.clearReceivedLists();
        clientToRemote.clearReceivedLists();

        receiverToLocal.connectClient(ProtocolVersion::Mqtt31, 51183);
        receiverToLocal.subscribe("#", 1);

        {
            clientToLocalWithBridge.publish("boots", "asdf", 2);
            clientToRemote.waitForMessageCount(1);
            auto ro = clientToRemote.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerRemote/boots"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "asdf");
        }

        // Make sure other clients connected to the server with prefixes defined get normal topics. I.e. test packet cache.
        {
            receiverToLocal.waitForMessageCount(1);
            auto roLocal = receiverToLocal.receivedObjects.lock();
            FMQ_COMPARE(roLocal->receivedPublishes.at(0).getTopic(), std::string("boots"));
            FMQ_COMPARE(roLocal->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(roLocal->receivedPublishes.at(0).getPayloadView(), "asdf");
        }

        {
            receiverToLocal2.waitForMessageCount(1);
            auto roLocal2 = receiverToLocal2.receivedObjects.lock();
            FMQ_COMPARE(roLocal2->receivedPublishes.back().getTopic(), std::string("boots"));
            FMQ_COMPARE(roLocal2->receivedPublishes.back().getQos(), 1);
            FMQ_COMPARE(roLocal2->receivedPublishes.back().getPayloadView(), "asdf");
        }

        clientToLocalWithBridge.clearReceivedLists();
        clientToRemote.clearReceivedLists();

        {
            clientToRemote.publish("ManglerRemote/shoes", "are made for walking", 2);
            clientToLocalWithBridge.waitForMessageCount(1);
            auto ro = clientToLocalWithBridge.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("shoes"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "are made for walking");
        }


    }
}

void MainTests::forkingTestBridgeWithOnlyLocalPrefix()
{
    for (const std::string protocol_version : {"mqtt5", "mqtt3.1"})
    {
        cleanup();

        ConfFileTemp confFile;
        const std::string config = R"(
allow_anonymous true
log_debug false

bridge {
    address ::1
    port 21883
    subscribe shoes 2
    publish ManglerLocal/boots 2
    clientid_prefix Mangler
    local_prefix ManglerLocal/
    protocol_version %s
}
listen {
    protocol mqtt
    port 51183
})";
        confFile.writeLine(formatString(config, protocol_version.c_str()));
        confFile.closeFile();

        std::vector<std::string> args {"--config-file", confFile.getFilePath()};

        MainAppAsFork app(args);
        app.start();
        app.waitForStarted(51183);

        // We will consider the test server initialized here the 'remote' broker.
        init();

        FlashMQTestClient clientToLocalWithBridge;
        clientToLocalWithBridge.start();

        FlashMQTestClient clientToRemote;
        clientToRemote.start();

        FlashMQTestClient senderToLocal;
        senderToLocal.start();

        clientToLocalWithBridge.connectClient(ProtocolVersion::Mqtt31, 51183);
        clientToLocalWithBridge.subscribe("#", 1);

        clientToRemote.connectClient(ProtocolVersion::Mqtt5);
        clientToRemote.subscribe("#", 1);

        waitForMessagesOverBridge(clientToLocalWithBridge, clientToRemote, "ManglerLocal/boots");

        clientToLocalWithBridge.clearReceivedLists();
        clientToRemote.clearReceivedLists();

        {
            clientToLocalWithBridge.publish("ManglerLocal/boots", "asdf", 2);
            clientToRemote.waitForMessageCount(1);
            auto ro = clientToRemote.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("boots"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "asdf");
        }

        clientToLocalWithBridge.clearReceivedLists();
        clientToRemote.clearReceivedLists();

        {
            clientToRemote.publish("shoes", "are made for walking", 2);
            clientToLocalWithBridge.waitForMessageCount(1);
            auto ro = clientToLocalWithBridge.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerLocal/shoes"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "are made for walking");
        }

        // Make sure other clients connected to the server with prefixes defined get normal topics.
        {
            clientToLocalWithBridge.clearReceivedLists();
            senderToLocal.connectClient(ProtocolVersion::Mqtt31, 51183);
            senderToLocal.publish("panic", "attack", 0);
            clientToLocalWithBridge.waitForMessageCount(1);
            auto ro = clientToLocalWithBridge.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("panic"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 0);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "attack");
        }
    }
}

/**
 * @brief Test that we don't apply the prefix on outgoing messages when it's the same as the topic string.
 *
 * When having a prefix of 'one/two/', this is to prevent a subscription like 'one/two/#', turning topic 'one/two/'
 * into '', which is illegal (while empty subtopic strings, like caused by trailing slash, is legal).
 */
void MainTests::forkingTestBridgeOutgoingTopicEqualsPrefix()
{
    cleanup();

    ConfFileTemp confFile;
    const std::string config = R"(
allow_anonymous true
log_debug false

bridge {
    address ::1
    port 21883
    subscribe shoes 2
    publish ManglerLocal/# 2
    clientid_prefix Mangler
    local_prefix ManglerLocal/
    protocol_version mqtt5
}
listen {
    protocol mqtt
    port 51183
})";
    confFile.writeLine(config);
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    MainAppAsFork app(args);
    app.start();
    app.waitForStarted(51183);

    // We will consider the test server initialized here the 'remote' broker.
    init();

    FlashMQTestClient clientToLocalWithBridge;
    clientToLocalWithBridge.start();

    FlashMQTestClient clientToRemote;
    clientToRemote.start();

    FlashMQTestClient senderToLocal;
    senderToLocal.start();

    clientToLocalWithBridge.connectClient(ProtocolVersion::Mqtt31, 51183);
    clientToLocalWithBridge.subscribe("#", 1);

    clientToRemote.connectClient(ProtocolVersion::Mqtt5);
    clientToRemote.subscribe("#", 1);

    waitForMessagesOverBridge(clientToLocalWithBridge, clientToRemote, "ManglerLocal/boots");

    clientToLocalWithBridge.clearReceivedLists();
    clientToRemote.clearReceivedLists();

    {
        clientToLocalWithBridge.publish("ManglerLocal/", "WC4WQbYJb76TguUT", 2);
        clientToRemote.waitForMessageCount(1);
        auto ro = clientToRemote.receivedObjects.lock();
        FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerLocal/"));
        FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
        FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "WC4WQbYJb76TguUT");
    }
}

/**
 * @brief Same as forkingTestBridgeOutgoingTopicEqualsPrefix, but then for incoming.
 */
void MainTests::forkingTestBridgeIncomingTopicEqualsPrefix()
{
    cleanup();

    ConfFileTemp confFile;
    const std::string config = R"(
allow_anonymous true
log_debug false

bridge {
    address ::1
    port 21883
    subscribe ManglerRemote/# 2
    publish boots 2
    clientid_prefix Mangler
    remote_prefix ManglerRemote/
    protocol_version mqtt5
}
listen {
    protocol mqtt
    port 51183
})";
    confFile.writeLine(config);
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    MainAppAsFork app(args);
    app.start();
    app.waitForStarted(51183);

    // We will consider the test server initialized here the 'remote' broker.
    init();

    FlashMQTestClient clientToLocalWithBridge;
    clientToLocalWithBridge.start();

    FlashMQTestClient clientToRemote;
    clientToRemote.start();

    clientToLocalWithBridge.connectClient(ProtocolVersion::Mqtt31, 51183);
    clientToLocalWithBridge.subscribe("#", 1);

    clientToRemote.connectClient(ProtocolVersion::Mqtt5);
    clientToRemote.subscribe("#", 1);

    waitForMessagesOverBridge(clientToLocalWithBridge, clientToRemote, "boots");

    clientToLocalWithBridge.clearReceivedLists();
    clientToRemote.clearReceivedLists();

    {
        clientToRemote.publish("ManglerRemote/", "XN0KoFeDOimgRiTs", 2);
        clientToLocalWithBridge.waitForMessageCount(1);
        auto ro = clientToLocalWithBridge.receivedObjects.lock();
        FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerRemote/"));
        FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
        FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "XN0KoFeDOimgRiTs");
    }
}

void MainTests::forkingTestBridgeZWithLocalAndRemotePrefixRetained()
{
    for (const std::string protocol_version : {"mqtt5", "mqtt3.1"})
    {
        cleanup();

        ConfFileTemp conf_file_remote;
        {
            const std::string config_remote = R"(
allow_anonymous true
log_debug false

listen {
    protocol mqtt
    port 51883
})";
            conf_file_remote.writeLine(formatString(config_remote, protocol_version.c_str()));
            conf_file_remote.closeFile();
        }

        std::vector<std::string> args_remote {"--config-file", conf_file_remote.getFilePath()};

        MainAppAsFork remoteServer(args_remote);
        remoteServer.start();
        remoteServer.waitForStarted(51883);

        FlashMQTestClient clientToRemote;
        clientToRemote.start();
        clientToRemote.connectClient(ProtocolVersion::Mqtt5, 51883);

        {
            Publish pub("ManglerRemote/shoes/retainme", "asdf", 2);
            pub.retain = true;
            clientToRemote.publish(pub);
        }

        ConfFileTemp conf_file_local;
        {
            const std::string config_local = R"(
allow_anonymous true
log_debug false

bridge {
    address ::1
    port 51883
    subscribe ManglerRemote/shoes/# 2
    publish ManglerLocal/boots/# 2
    clientid_prefix Mangler
    local_prefix ManglerLocal/
    remote_prefix ManglerRemote/
    protocol_version %s
}
listen {
    protocol mqtt
    port 21883
})";
            conf_file_local.writeLine(formatString(config_local, protocol_version.c_str()));
            conf_file_local.closeFile();

            std::vector<std::string> args_local {"--config-file", conf_file_local.getFilePath()};

            // We consider our normal test client as 'local'
            init(args_local);
        }

        FlashMQTestClient clientToLocal;
        clientToLocal.start();
        clientToLocal.connectClient(ProtocolVersion::Mqtt5);
        clientToLocal.subscribe("#", 1);
        clientToLocal.waitForMessageCount(1);

        {
            auto ro = clientToLocal.receivedObjects.lock();

            FMQ_COMPARE(ro->receivedPublishes.size(), static_cast<size_t>(1));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerLocal/shoes/retainme"));
        }

        clientToLocal.clearReceivedLists();

        waitForMessagesOverBridge(clientToRemote, clientToLocal, "ManglerRemote/shoes/connectiontest");

        {
            auto ro = clientToLocal.receivedObjects.lock();

            FMQ_COMPARE(ro->receivedPublishes.size(), static_cast<size_t>(1));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerLocal/shoes/connectiontest"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), std::string("connectiontest"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getRetain(), false);
        }

        {
            FlashMQTestClient clientToLocal;
            clientToLocal.start();

            clientToLocal.connectClient(ProtocolVersion::Mqtt5);
            clientToLocal.subscribe("#", 1);

            clientToLocal.waitForPacketCount(1);
            auto ro = clientToLocal.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.size(), static_cast<size_t>(1));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerLocal/shoes/retainme"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "asdf");
            FMQ_COMPARE(ro->receivedPublishes.at(0).getRetain(), true);
        }

        // Push a retained message to local
        {
            FlashMQTestClient clientToLocal;
            clientToLocal.start();
            clientToLocal.connectClient(ProtocolVersion::Mqtt5);
            Publish pub("ManglerLocal/boots/retainmetoo", "zuOJHOekvdkGD9FH", 2);
            pub.retain = true;
            clientToLocal.publish(pub);
        }

        // That we then must see as retained on the remote
        {
            FlashMQTestClient clientToRemote;
            clientToRemote.start();

            clientToRemote.connectClient(ProtocolVersion::Mqtt5, 51883);
            clientToRemote.subscribe("ManglerRemote/boots/#", 2);

            clientToRemote.waitForMessageCount(1);
            auto ro = clientToRemote.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.size(), static_cast<size_t>(1));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerRemote/boots/retainmetoo"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 2);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "zuOJHOekvdkGD9FH");
            FMQ_COMPARE(ro->receivedPublishes.at(0).getRetain(), true);
        }
    }
}

/**
 * @brief Test if queued QoS messages have have their prefixes applied. Special care is taken in FlashMQ to support
 * that (the topic_override is part of the QueuedPublish), so we need to test it.
 */
void MainTests::forkingTestBridgeWithLocalAndRemotePrefixQueuedQoS()
{
    for (const std::string protocol_version : {"mqtt5", "mqtt3.1.1"})
    {
        cleanup();

        ConfFileTemp conf_file_local;
        const std::string config_local = R"(
allow_anonymous true
log_debug false

bridge {
    address ::1
    port 21883
    subscribe ManglerRemote/shoes/# 2
    publish ManglerLocal/boots/# 2
    clientid_prefix Mangler
    remote_clean_start false
    local_clean_start false
    remote_session_expiry_interval 300
    local_session_expiry_interval 300
    local_prefix ManglerLocal/
    remote_prefix ManglerRemote/
    protocol_version %s
}
listen {
    protocol mqtt
    port 51883
})";
        conf_file_local.writeLine(formatString(config_local, protocol_version.c_str()));
        conf_file_local.closeFile();

        std::vector<std::string> args_local {"--config-file", conf_file_local.getFilePath()};

        MainAppAsFork localServer(args_local);
        localServer.start();
        localServer.waitForStarted(51883);

        FlashMQTestClient clientToLocalWithBridge;
        clientToLocalWithBridge.start();
        clientToLocalWithBridge.connectClient(ProtocolVersion::Mqtt5, 51883);

        FlashMQTempDir remote_server_storage_dir;

        ConfFileTemp conf_file_remote;
        const std::string config_remote = R"(
allow_anonymous true
log_debug false
storage_dir %s

listen {
    protocol mqtt
    port 21883
}
)";
        conf_file_remote.writeLine(formatString(config_remote, remote_server_storage_dir.getPath().c_str()));
        conf_file_remote.closeFile();
        std::vector<std::string> args_remote {"--config-file", conf_file_remote.getFilePath()};

        // Bring the remote on-line
        init(args_remote);

        FlashMQTestClient clientToRemote;
        clientToRemote.start();

        clientToRemote.connectClient(ProtocolVersion::Mqtt5);
        clientToRemote.subscribe("#", 1);

        waitForMessagesOverBridge(clientToLocalWithBridge, clientToRemote, "ManglerLocal/boots/connectiontest");

        {
            auto ro = clientToRemote.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.size(), static_cast<size_t>(1));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerRemote/boots/connectiontest"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), std::string("connectiontest"));
        }

        clientToRemote.clearReceivedLists();

        // Stop the remote
        cleanup();

        Publish pub("ManglerLocal/boots/queue_me", "late to the party", 1);
        clientToLocalWithBridge.publish(pub);

        std::cout << "Starting remote server again" << std::endl;

        // Start the remote again
        init(args_remote);

        {
            FlashMQTestClient clientToRemote;
            clientToRemote.start();

            clientToRemote.connectClient(ProtocolVersion::Mqtt5, false, 1000, [](Connect &c) {
                c.clientid = "QueuedReceiver_666";
            });
            clientToRemote.subscribe("+/boots/queue_me", 1);

            clientToRemote.waitForMessageCount(1, 10);
            auto ro = clientToRemote.receivedObjects.lock();
            FMQ_COMPARE(ro->receivedPublishes.size(), static_cast<size_t>(1));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getTopic(), std::string("ManglerRemote/boots/queue_me"));
            FMQ_COMPARE(ro->receivedPublishes.at(0).getQos(), 1);
            FMQ_COMPARE(ro->receivedPublishes.at(0).getPayloadView(), "late to the party");
        }
    }

}














================================================
FILE: FlashMQTests/conffiletemp.cpp
================================================
#include "conffiletemp.h"

#include <vector>
#include <unistd.h>
#include <stdexcept>

ConfFileTemp::ConfFileTemp()
{
    const std::string templateName("/tmp/flashmqconf_XXXXXX");
    std::vector<char> nameBuf(templateName.size() + 1, 0);
    std::copy(templateName.begin(), templateName.end(), nameBuf.begin());
    this->fd = mkstemp(nameBuf.data());

    if (this->fd < 0)
    {
        throw std::runtime_error("mkstemp error.");
    }

    this->filePath = nameBuf.data();
}

ConfFileTemp::~ConfFileTemp()
{
    closeFile();

    if (!this->filePath.empty())
        unlink(this->filePath.c_str());
}

const std::string &ConfFileTemp::getFilePath() const
{
    if (fd > 0)
        throw std::runtime_error("You first need to close the file before using it.");

    return this->filePath;
}

void ConfFileTemp::writeLine(const std::string &line)
{
    if (write(this->fd, line.c_str(), line.size()) < 0)
        throw std::runtime_error("Config file write failed");
    if (write(this->fd, "\n", 1) < 0)
        throw std::runtime_error("Config file write failed");
}

void ConfFileTemp::closeFile()
{
    if (this->fd < 0)
        return;

    close(this->fd);
    this->fd = -1;
}


================================================
FILE: FlashMQTests/conffiletemp.h
================================================
#ifndef CONFFILETEMP_H
#define CONFFILETEMP_H

#include <string>

class ConfFileTemp
{
    int fd = -1;
    std::string filePath;

public:
    ConfFileTemp();
    ~ConfFileTemp();

    const std::string &getFilePath() const;
    void writeLine(const std::string &line);
    void closeFile();

    ConfFileTemp &operator=(const ConfFileTemp &other) = delete;
    ConfFileTemp(const ConfFileTemp &other) = delete;
    ConfFileTemp(ConfFileTemp &&other) = delete;
};

#endif // CONFFILETEMP_H


================================================
FILE: FlashMQTests/configtests.cpp
================================================
#include "maintests.h"
#include "testhelpers.h"
#include "conffiletemp.h"
#include "exceptions.h"
#include "settings.h"

void MainTests::test_loading_second_value()
{
    /* this is expected to work*/
    {
        ConfFileTemp config;
        config.writeLine("bridge {");
        config.writeLine("  address localhost");
        config.writeLine("  publish send/this 1"); // this value should be different from the default (0)
        config.writeLine("}");
        config.closeFile();

        ConfigFileParser parser(config.getFilePath());
        parser.loadFile(false);

        Settings settings = parser.getSettings();

        std::list<BridgeConfig> bridges = settings.stealBridges();
        FMQ_VERIFY(!bridges.empty());
        BridgeConfig &bridge = bridges.front();

        FMQ_COMPARE(bridge.publishes[0].topic, "send/this");
        FMQ_COMPARE(bridge.publishes[0].qos, (uint8_t)1);
    }

    /* this is expecte to fail because "address" doesn't take a second value */
    {
        ConfFileTemp config;
        config.writeLine("bridge {");
        config.writeLine("  address localhost thisisnotok");
        config.writeLine("  publish send/this 1");
        config.writeLine("}");
        config.closeFile();

        ConfigFileParser parser(config.getFilePath());
        try
        {
            parser.loadFile(false);
            FMQ_FAIL("The config parser is too liberal");
        }
        catch (ConfigFileException &ex)
        {
            /* Excellent, what we wanted */
        }
    }
}

void MainTests::test_parsing_numbers()
{
    /* this should work: 180 */
    {
        ConfFileTemp config;
        config.writeLine("expire_sessions_after_seconds 180");
        config.closeFile();

        ConfigFileParser parser(config.getFilePath());
        parser.loadFile(false);

        Settings settings = parser.getSettings();

        FMQ_COMPARE(settings.expireSessionsAfterSeconds, (uint32_t)180);
    }

    /* this should fail: 180days */
    {
        ConfFileTemp config;
        config.writeLine("expire_sessions_after_seconds 180days");
        config.closeFile();

        ConfigFileParser parser(config.getFilePath());
        try
        {
            parser.loadFile(false);
            FMQ_FAIL("The parser was too liberal");
        }
        catch (ConfigFileException&)
        {
            /* Good! This is where we want to end up in */
        }
    }

    /* this should also fail: 180 days */
    {
        ConfFileTemp config;
        config.writeLine("expire_sessions_after_seconds 180 days");
        config.closeFile();

        ConfigFileParser parser(config.getFilePath());
        try
        {
            parser.loadFile(false);
            FMQ_FAIL("The parser was too liberal");
        }
        catch (ConfigFileException&)
        {
            /* Good! This is where we want to end up in */
        }
    }

    /* Last one that should fail: 180 days and a bit */
    {
        ConfFileTemp config;
        config.writeLine("expire_sessions_after_seconds 180 days and a bit more");
        config.closeFile();

        ConfigFileParser parser(config.getFilePath());
        try
        {
            parser.loadFile(false);
            FMQ_FAIL("The parser was too liberal");
        }
        catch (ConfigFileException&)
        {
            /* Good! This is where we want to end up in */
        }
    }
}

void MainTests::testStringDistances()
{
    FMQ_COMPARE(distanceBetweenStrings("", ""), (unsigned int)0);
    FMQ_COMPARE(distanceBetweenStrings("dog", ""), (unsigned int)3);
    FMQ_COMPARE(distanceBetweenStrings("", "dog"), (unsigned int)3);
    FMQ_COMPARE(distanceBetweenStrings("dog", "horse"), (unsigned int)4);
    FMQ_COMPARE(distanceBetweenStrings("horse", "dog"), (unsigned int)4);
    FMQ_COMPARE(distanceBetweenStrings("industry", "interest"), (unsigned int)6);
    FMQ_COMPARE(distanceBetweenStrings("kitten", "sitting"), (unsigned int)3);
    FMQ_COMPARE(distanceBetweenStrings("uninformed", "uniformed"), (unsigned int)1);
}

void MainTests::testConfigSuggestion()
{
    // User made a small typo: 'session' instead of 'sessions'
    {
        ConfFileTemp config;
        config.writeLine("expire_session_after_seconds 180");
        config.closeFile();

        ConfigFileParser parser(config.getFilePath());

        try
        {
            parser.loadFile(false);
            FMQ_FAIL("The parser is too liberal");
        }
        catch (ConfigFileException &ex)
        {
            FMQ_COMPARE(ex.what(), "Config key 'expire_session_after_seconds' is not valid (here). Did you mean: expire_sessions_after_seconds ?");
        }
    }

    // User entered gibberish. Let's not suggest gibberish back
    {
        ConfFileTemp config;
        config.writeLine("foobarbaz 180");
        config.closeFile();

        ConfigFileParser parser(config.getFilePath());

        try
        {
            parser.loadFile(false);
            FMQ_FAIL("The parser is too liberal");
        }
        catch (ConfigFileException &ex)
        {
            FMQ_COMPARE(ex.what(), "Config key 'foobarbaz' is not valid (here).");
        }
    }
}

void MainTests::testFlags()
{
    Flags<PersistenceDataToSave> flags;

    FMQ_VERIFY(flags.hasNone());

    flags.setAll();

    FMQ_VERIFY(flags.hasAll());

    flags.clearFlag(PersistenceDataToSave::BridgeInfo);

    FMQ_VERIFY(flags.hasFlagSet(PersistenceDataToSave::SessionsAndSubscriptions));
    FMQ_VERIFY(flags.hasFlagSet(PersistenceDataToSave::RetainedMessages));
    FMQ_VERIFY(!flags.hasFlagSet(PersistenceDataToSave::BridgeInfo));

    flags.clearFlag(PersistenceDataToSave::RetainedMessages);

    FMQ_VERIFY(flags.hasFlagSet(PersistenceDataToSave::SessionsAndSubscriptions));
    FMQ_VERIFY(!flags.hasFlagSet(PersistenceDataToSave::RetainedMessages));
    FMQ_VERIFY(!flags.hasFlagSet(PersistenceDataToSave::BridgeInfo));

    flags.clearFlag(PersistenceDataToSave::SessionsAndSubscriptions);

    FMQ_VERIFY(!flags.hasFlagSet(PersistenceDataToSave::SessionsAndSubscriptions));
    FMQ_VERIFY(!flags.hasFlagSet(PersistenceDataToSave::RetainedMessages));
    FMQ_VERIFY(!flags.hasFlagSet(PersistenceDataToSave::BridgeInfo));

    flags.setFlag(PersistenceDataToSave::SessionsAndSubscriptions);

    FMQ_VERIFY(flags.hasFlagSet(PersistenceDataToSave::SessionsAndSubscriptions));
    FMQ_VERIFY(!flags.hasFlagSet(PersistenceDataToSave::RetainedMessages));
    FMQ_VERIFY(!flags.hasFlagSet(PersistenceDataToSave::BridgeInfo));

    flags.setFlag(PersistenceDataToSave::RetainedMessages);

    FMQ_VERIFY(flags.hasFlagSet(PersistenceDataToSave::SessionsAndSubscriptions));
    FMQ_VERIFY(flags.hasFlagSet(PersistenceDataToSave::RetainedMessages));
    FMQ_VERIFY(!flags.hasFlagSet(PersistenceDataToSave::BridgeInfo));

    flags.setFlag(PersistenceDataToSave::BridgeInfo);

    FMQ_VERIFY(flags.hasFlagSet(PersistenceDataToSave::SessionsAndSubscriptions));
    FMQ_VERIFY(flags.hasFlagSet(PersistenceDataToSave::RetainedMessages));
    FMQ_VERIFY(flags.hasFlagSet(PersistenceDataToSave::BridgeInfo));

    flags.clearAll();

    FMQ_VERIFY(flags.hasNone());
}


================================================
FILE: FlashMQTests/dnstests.cpp
================================================
#include "maintests.h"
#include "testhelpers.h"

#include "utils.h"

void MainTests::testDnsResolver()
{
    try
    {
        DnsResolver resolver;
        resolver.query("demo.flashmq.org", ListenerProtocol::IPv46, std::chrono::milliseconds(5000));

        int count = 0;
        while (++count < 100)
        {
            std::list<FMQSockaddr> results = resolver.getResult();
            if (!results.empty())
            {
                QVERIFY(std::any_of(results.begin(), results.end(), [](FMQSockaddr &x){return x.getText() == "89.188.6.194";}));
                QVERIFY(std::any_of(results.begin(), results.end(), [](FMQSockaddr &x){return x.getText() == "2a01:1b0:7996:418:83:137:146:230";}));
                break;
            }

            usleep(10000);
        }

        if (count >= 100)
            QVERIFY(false);
    }
    catch (std::exception &ex)
    {
        QVERIFY2(false, ex.what());
    }
}

void MainTests::testDnsResolverDontCancel()
{
    try
    {
        DnsResolver resolver;
        resolver.query("demo.flashmq.org", ListenerProtocol::IPv46, std::chrono::milliseconds(5000));
        resolver.query("demo.flashmq.org", ListenerProtocol::IPv46, std::chrono::milliseconds(5000));

        int count = 0;
        while (++count < 100)
        {
            std::list<FMQSockaddr> results = resolver.getResult();
            if (!results.empty())
            {
                QVERIFY(std::any_of(results.begin(), results.end(), [](FMQSockaddr &x){return x.getText() == "89.188.6.194";}));
                QVERIFY(std::any_of(results.begin(), results.end(), [](FMQSockaddr &x){return x.getText() == "2a01:1b0:7996:418:83:137:146:230";}));
                break;
            }

            usleep(10000);
        }

        if (count >= 100)
            QVERIFY(false);
    }
    catch (std::exception &ex)
    {
        QVERIFY2(false, ex.what());
    }
}

void MainTests::testDnsResolverSecondQuery()
{
    try
    {
        DnsResolver resolver;
        for (int i = 0; i < 2; i++)
        {
            resolver.query("demo.flashmq.org", ListenerProtocol::IPv46, std::chrono::milliseconds(5000));

            int count = 0;
            while (++count < 100)
            {
                std::list<FMQSockaddr> results = resolver.getResult();
                if (!results.empty())
                {
                    QVERIFY(std::any_of(results.begin(), results.end(), [](FMQSockaddr &x){return x.getText() == "89.188.6.194";}));
                    QVERIFY(std::any_of(results.begin(), results.end(), [](FMQSockaddr &x){return x.getText() == "2a01:1b0:7996:418:83:137:146:230";}));
                    break;
                }

                usleep(10000);
            }

            if (count >= 100)
                QVERIFY(false);
        }
    }
    catch (std::exception &ex)
    {
        QVERIFY2(false, ex.what());
    }
}

void MainTests::testDnsResolverInvalid()
{
    try
    {
        DnsResolver resolver;
        const std::string rnd = getSecureRandomString(16);
        const std::string domain = rnd + ".flashmq.org";
        resolver.query(domain, ListenerProtocol::IPv46, std::chrono::milliseconds(5000));

        int count = 0;
        while (count++ < 60)
        {
            std::list<FMQSockaddr> results = resolver.getResult();
            if (!results.empty())
            {
                for (const auto &r : results)
                {
                    std::cerr << "Wrong DNS result: " << r.getText() << std::endl;
                }
                QVERIFY2(false, "A DNS result was returned when we expected nothing.");
                break;
            }

            usleep(100000);
        }

        QVERIFY2(false, "It took too long to get a result. That's weird, because we should have gotten a timeout exception.");
    }
    catch (std::exception &ex)
    {
        const std::string err = str_tolower(ex.what());
        std::cout << "For reference, the error that we're scanning: " << err << std::endl;
        QVERIFY(strContains(err, "name or service not known"));
    }
}

void MainTests::testGetResultWhenThereIsNone()
{
    try
    {
        DnsResolver resolver;
        std::list<FMQSockaddr> results = resolver.getResult();
        QVERIFY(results.empty());
        QVERIFY(false);
    }
    catch (std::exception &ex)
    {
        std::string err = str_tolower(ex.what());
        QVERIFY(strContains(err, "no dns query in progress"));
    }
}


================================================
FILE: FlashMQTests/filecloser.cpp
================================================
#include "filecloser.h"

#include <unistd.h>

FileCloser::FileCloser(int fd) :
    fd(fd)
{

}

FileCloser::~FileCloser()
{
    if (fd >= 0)
        close(fd);
    fd = -1;
}


================================================
FILE: FlashMQTests/filecloser.h
================================================
#ifndef FILECLOSER_H
#define FILECLOSER_H


class FileCloser
{
    int fd = -1;

public:
    FileCloser(int fd);
    ~FileCloser();
};

#endif // FILECLOSER_H


================================================
FILE: FlashMQTests/flashmqtempdir.cpp
================================================
#include "flashmqtempdir.h"

#include <sys/types.h>
#include <unistd.h>
#include <filesystem>

#include "utils.h"

FlashMQTempDir::FlashMQTempDir()
{
    const std::string templateName(std::filesystem::temp_directory_path() / "flashmq_test_XXXXXX");
    std::vector<char> nameBuf(templateName.size() + 1, 0);
    std::copy(templateName.begin(), templateName.end(), nameBuf.begin());
    this->path = std::string(mkdtemp(nameBuf.data()));
}

FlashMQTempDir::~FlashMQTempDir()
{
    if (this->path.empty() || !strContains(this->path, "flashmq_test_"))
        return;

    // Not pretty, but whatever works...
    int pid = fork();
    if (pid == 0)
    {
        execlp("rm", "rm", "-rf", "--", this->path.c_str(), (char*)NULL);
    }
}

const std::filesystem::path &FlashMQTempDir::getPath() const
{
    return this->path;
}


================================================
FILE: FlashMQTests/flashmqtempdir.h
================================================
#ifndef FLASHMQTEMPDIR_H
#define FLASHMQTEMPDIR_H

#include <filesystem>
#include <stdlib.h>
#include <string>
#include <vector>

class FlashMQTempDir
{
    std::filesystem::path path;

public:
    FlashMQTempDir();
    ~FlashMQTempDir();
    const std::filesystem::path &getPath() const;
};

#endif // FLASHMQTEMPDIR_H


================================================
FILE: FlashMQTests/main.cpp
================================================
#include <iostream>
#include <vector>

#include "maintests.h"

void printHelp(const std::string &arg0)
{
    std::cout << std::endl;
    std::cout << "Usage: " << arg0 << " [ --skip-tests-with-internet ] [ --skip-server-tests ] " << " <tests> " << std::endl;
}

int main(int argc, char *argv[])
{
    bool skip_tests_with_internet = false;
    bool skip_server_tests = false;
    bool abort_on_first_fail = false;
    std::vector<std::string> tests;
    bool option_list_terminated = false;

    for (int i = 1; i < argc ; i++)
    {
        const std::string name(argv[i]);

        if (option_list_terminated)
            tests.push_back(name);
        else if (name == "--")
            option_list_terminated = true;
        else if (name == "--help")
        {
            printHelp(argv[0]);
            return 1;
        }
        else if (name == "--skip-tests-with-internet")
            skip_tests_with_internet = true;
        else if (name == "--skip-server-tests")
            skip_server_tests = true;
        else if (name == "--abort-on-first-fail")
            abort_on_first_fail = true;
        else if (name.find("--") == 0)
        {
            std::cerr << "Unknown argument " << name << std::endl;
            printHelp(argv[0]);
            return 1;
        }
        else
            tests.push_back(name);
    }

    MainTests maintests;
    if (!maintests.test(skip_tests_with_internet, skip_server_tests, abort_on_first_fail, tests))
        return 1;

    return 0;
}


================================================
FILE: FlashMQTests/mainappasfork.cpp
================================================
#include "mainappasfork.h"
#include <mainapp.h>
#include "signal.h"
#include "fmqmain.h"
#include "sys/wait.h"

std::string MainAppAsFork::getConfigFileFromArgs(const std::vector<std::string> &args)
{
    std::string result = "";
    bool next = false;
    for(const std::string &arg : args)
    {
        if (arg == "--config-file")
        {
            next = true;
            continue;
        }

        if (next)
        {
            result = arg;
            break;
        }
    }
    return result;
}

MainAppAsFork::MainAppAsFork()
{
    defaultConf.writeLine("allow_anonymous true");
    defaultConf.closeFile();
    args.push_back("--config-file");
    args.push_back(defaultConf.getFilePath());
}

MainAppAsFork::MainAppAsFork(const std::vector<std::string> &args) :
    args(args)
{

}

MainAppAsFork::~MainAppAsFork()
{
    this->stop();
}

void MainAppAsFork::start()
{
    // We must not have threads when we fork.
    Logger::getInstance()->quit();

    pid_t pid = fork();

    if (pid < 0)
        throw std::runtime_error("What the fork?");

    if (pid == 0)
    {
        try
        {
            std::list<std::vector<char>> argCopies;

            const std::string programName = "FlashMQTests";
            std::vector<char> programNameCopy(programName.size() + 1, 0);
            std::copy(programName.begin(), programName.end(), programNameCopy.begin());
            argCopies.push_back(std::move(programNameCopy));

            for (const std::string &arg : args)
            {
                std::vector<char> copyArg(arg.size() + 1, 0);
                std::copy(arg.begin(), arg.end(), copyArg.begin());
                argCopies.push_back(std::move(copyArg));
            }

            char *argv[256];
            memset(argv, 0, 256*sizeof (char*));

            int i = 0;
            for (std::vector<char> &copy : argCopies)
            {
                argv[i++] = copy.data();
            }

            int r = fmqmain(i, argv);
            ::exit(r);
        }
        catch (std::exception &ex)
        {
            std::cout << "The forked process threw an exception: " << ex.what() << std::endl;
            std::cerr << "The forked process threw an exception: " << ex.what() << std::endl;
        }

        // Does not call destructors.
        abort();
    }

    this->child = pid;
}

void MainAppAsFork::stop()
{
    if (this->child <= 0)
        return;

    kill(this->child, SIGTERM);
    int status = 0;
    waitpid(this->child, &status, 0);

    this->child = -1;
}

void MainAppAsFork::waitForStarted(int port)
{
    int sockfd = check<std::runtime_error>(socket(AF_INET, SOCK_STREAM, 0));

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_port = htons(port);
    addr.sin_family = AF_INET;

    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);

    struct sockaddr *addr2 = reinterpret_cast<sockaddr*>(&addr);

    bool timeout = false;
    int n = 0;
    while (connect(sockfd, addr2, sizeof(struct sockaddr_in)) != 0)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(20));

        if (n++ > 100)
        {
            timeout = true;
            break;
        }
    }

    close(sockfd);

    if (timeout)
        throw std::runtime_error("Forked mainapp failed to start?");
}


================================================
FILE: FlashMQTests/mainappasfork.h
================================================
#ifndef MAINAPPASFORK_H
#define MAINAPPASFORK_H

#include <vector>
#include <string>
#include <unistd.h>
#include "conffiletemp.h"

/**
 * @brief The MainAppAsFork class provides a way to run the FlashMQ server in a separate process for tests. This is convenient for isolation,
 * running multiple versions (bridging) and avoids conflicts with (thread) globals. But, it can also be inconvenient, because you
 * don't have access to the internal state (for assertions) and on abnormal exist the child processes may linger (although we
 * could devise stuff against that).
 */
class MainAppAsFork
{
    pid_t child = -1;
    std::vector<std::string> args;
    ConfFileTemp defaultConf;
public:
    static std::string getConfigFileFromArgs(const std::vector<std::string> &args);
    MainAppAsFork();
    MainAppAsFork(const std::vector<std::string> &args);
    ~MainAppAsFork();
    void start();
    void stop();
    void waitForStarted(int port=21883);
};

#endif // MAINAPPASFORK_H


================================================
FILE: FlashMQTests/mainappinthread.cpp
================================================
#include "mainappinthread.h"

MainAppInThread::MainAppInThread()
{

}

MainAppInThread::MainAppInThread(const std::vector<std::string> &args) :
    mArgs(args)
{

}

MainAppInThread::~MainAppInThread()
{
    stopApp();
}

void MainAppInThread::start()
{
    const auto args = mArgs;

    auto thread_task = [this, args]()
    {
        std::list<std::vector<char>> argCopies;

        const std::string programName = "FlashMQTests";
        std::vector<char> programNameCopy(programName.size() + 1, 0);
        std::copy(programName.begin(), programName.end(), programNameCopy.begin());
        argCopies.push_back(std::move(programNameCopy));

        for (const std::string &arg : args)
        {
            std::vector<char> copyArg(arg.size() + 1, 0);
            std::copy(arg.begin(), arg.end(), copyArg.begin());
            argCopies.push_back(std::move(copyArg));
        }

        char *argv[256];
        memset(argv, 0, 256*sizeof (char*));

        int i = 0;
        for (std::vector<char> &copy : argCopies)
        {
            argv[i++] = copy.data();
        }

        std::shared_ptr<MainApp> mainapp;

        {
            auto mainapp_locked = mMainApp.lock();
            std::shared_ptr<MainApp> &mainapp_member = *mainapp_locked;
            mainapp_member = MainApp::initMainApp(i, argv);
            mainapp = mainapp_member;
        }

        // A hack: when I supply args I probably define a config for auth stuff.
        if (args.empty())
            mainapp->settings.allowAnonymous = true;

        mainapp->start();
    };

    this->thread = std::thread(thread_task);
}

void MainAppInThread::stopApp()
{
    {
        auto mainapp_locked = mMainApp.lock();
        std::shared_ptr<MainApp> &mainapp = *mainapp_locked;

        if (mainapp)
            mainapp->quit();
    }

    if (this->thread.joinable())
        this->thread.join();
}

void MainAppInThread::waitForStarted()
{
    auto started_f = [this]
    {
        std::shared_ptr<MainApp> mainapp;

        {
            auto mainapp_locked = mMainApp.lock();
            mainapp = *mainapp_locked;
        }

        if (!mainapp)
            return false;

        return mainapp->getStarted();
    };

    int n = 0;
    while(!started_f())
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));

        if (n++ > 500)
            throw std::runtime_error("Waiting for app to start failed.");
    }
}



================================================
FILE: FlashMQTests/mainappinthread.h
================================================
#ifndef MAINAPPINTHREAD_H
#define MAINAPPINTHREAD_H

#include <thread>

#include "mainapp.h"
#include "mutexowned.h"

class MainAppInThread
{
    std::thread thread;
    const std::vector<std::string> mArgs;
    MutexOwned<std::shared_ptr<MainApp>> mMainApp;
public:
    MainAppInThread();
    MainAppInThread(const std::vector<std::string> &args);
    ~MainAppInThread();

    void start();
    void stopApp();
    void waitForStarted();
};

#endif // MAINAPPINTHREAD_H


================================================
FILE: FlashMQTests/maintests.cpp
================================================
#include <unistd.h>

#include "maintests.h"
#include "testhelpers.h"
#include "testinitializer.h"

#include "threadglobals.h"

void MainTests::testAsserts()
{
    {
        auto a = [] {
            assert_count = 0;
            assert_fail_count = 0;
            QCOMPARE(1, 1);
        };
        a();
        if (assert_count != 1 || assert_fail_count != 0)
            throw std::exception();
    }

    {
        auto a = [] {
            assert_count = 0;
            assert_fail_count = 0;
            QCOMPARE(1, 2);
        };
        a();
        if (assert_count != 1 || assert_fail_count != 1)
            throw std::exception();
    }

    {
        auto a = [] {
            assert_count = 0;
            assert_fail_count = 0;
            QVERIFY(true);
        };
        a();
        if (assert_count != 1 || assert_fail_count != 0)
            throw std::exception();
    }

    {
        auto a = [] {
            assert_count = 0;
            assert_fail_count = 0;
            QVERIFY(false);
        };
        a();
        if (assert_count != 1 || assert_fail_count != 1)
            throw std::exception();
    }

    {
        auto a = [] {
            assert_count = 0;
            assert_fail_count = 0;
            QVERIFY2(true, "");
        };
        a();
        if (assert_count != 1 || assert_fail_count != 0)
            throw std::exception();
    }

    {
        auto a = [] {
            assert_count = 0;
            assert_fail_count = 0;
            QVERIFY2(false, "");
        };
        a();
        if (assert_count != 1 || assert_fail_count != 1)
            throw std::exception();
    }

    {
        auto a = [] {
            assert_count = 0;
            assert_fail_count = 0;
            QFAIL("");
        };
        a();
        if (assert_count != 1 || assert_fail_count != 1)
            throw std::exception();
    }

    {
        auto a = [] {
            assert_count = 0;
            assert_fail_count = 0;
            MYCASTCOMPARE(static_cast<ssize_t>(1), static_cast<size_t>(1));
        };
        a();
        if (assert_count != 1 || assert_fail_count != 0)
            throw std::exception();
    }

    {
        auto a = [] {
            assert_count = 0;
            assert_fail_count = 0;
            MYCASTCOMPARE(static_cast<ssize_t>(1), static_cast<size_t>(2));
        };
        a();
        if (assert_count != 1 || assert_fail_count != 1)
            throw std::exception();
    }

}

void MainTests::initBeforeEachTest(const std::vector<std::string> &args, bool startServer)
{
    mainApp.reset();

    if (startServer)
    {
        mainApp = std::make_unique<MainAppInThread>(args);
        mainApp->start();
        mainApp->waitForStarted();
    }

    // We test functions directly that the server normally only calls from worker threads, in which thread data is available. This is kind of a dummy-fix, until
    // we actually need correct thread data at those points (at this point, it's only to increase message counters).
    this->dummyThreadData = std::make_shared<ThreadData>(666, settings, pluginLoader, std::weak_ptr<MainApp>());
    ThreadGlobals::assignThreadData(dummyThreadData);
    ThreadGlobals::assignSettings(&this->settings);
}

void MainTests::initBeforeEachTest(bool startServer)
{
    std::vector<std::string> args;
    initBeforeEachTest(args, startServer);
}

void MainTests::cleanupAfterEachTest()
{
    if (this->mainApp)
        this->mainApp->stopApp();
    this->mainApp.reset();
}

void MainTests::registerFunction(const std::string &name, std::function<void ()> f, bool requiresServer, bool requiresInternet)
{
    TestFunction &tf = testFunctions[name];
    tf.f = f;
    tf.requiresServer = requiresServer;
    tf.requiresInternet = requiresInternet;
}

void MainTests::testDummy()
{
    FMQ_COMPARE(1,1);
}

MainTests::MainTests()
{
    static int instanceCount = 0;

    if (instanceCount++ > 0)
        throw std::runtime_error("Don't instantiate this more than once.");

    asserts_print = false;
    testAsserts();
    asserts_print = true;

    assert_count = 0;
    assert_fail_count = 0;

    /*
     * Forking tests need to be done first (by alphabet), otherwise you break everything with DNS. This is a limitation
     * of getaddrinfo_a(). So, name them appropriately.
     */
    REGISTER_FUNCTION(forkingTestForkingTestServer);
    REGISTER_FUNCTION(forkingTestSaveAndLoadDelayedWill);
    REGISTER_FUNCTION(forkingTestBridgeWithLocalAndRemotePrefix);
    REGISTER_FUNCTION(forkingTestBridgePrefixesOtherClientsUnaffected);
    REGISTER_FUNCTION(forkingTestBridgeWithOnlyRemotePrefix);
    REGISTER_FUNCTION(forkingTestBridgeWithOnlyLocalPrefix);
    REGISTER_FUNCTION(forkingTestBridgeOutgoingTopicEqualsPrefix);
    REGISTER_FUNCTION(forkingTestBridgeIncomingTopicEqualsPrefix);
    REGISTER_FUNCTION(forkingTestBridgeWithLocalAndRemotePrefixQueuedQoS);
    REGISTER_FUNCTION(forkingTestBridgeZWithLocalAndRemotePrefixRetained);

    REGISTER_FUNCTION(testDummy);
    REGISTER_FUNCTION3(test_circbuf);
    REGISTER_FUNCTION3(test_circbuf_unwrapped_doubling);
    REGISTER_FUNCTION3(test_circbuf_wrapped_doubling);
    REGISTER_FUNCTION3(test_circbuf_full_wrapped_buffer_doubling);
    REGISTER_FUNCTION3(test_cirbuf_vector_methods);
    REGISTER_FUNCTION3(test_validSubscribePath);
    REGISTER_FUNCTION(test_retained);
    REGISTER_FUNCTION(test_retained_double_set);
    REGISTER_FUNCTION(test_retained_mode_drop);
    REGISTER_FUNCTION(test_retained_mode_downgrade);
    REGISTER_FUNCTION(test_retained_mode_no_retain);
    REGISTER_FUNCTION(test_retained_changed);
    REGISTER_FUNCTION(test_retained_removed);
    REGISTER_FUNCTION(test_retained_tree);
    REGISTER_FUNCTION(test_retained_global_expire);
    REGISTER_FUNCTION(test_retained_per_message_expire);
    REGISTER_FUNCTION(test_retained_tree_purging);
    REGISTER_FUNCTION(testRetainAsPublished);
    REGISTER_FUNCTION(testRetainAsPublishedNegative);
    REGISTER_FUNCTION(testRetainedParentOfWildcard);
    REGISTER_FUNCTION(testRetainedWildcard);
    REGISTER_FUNCTION(testRetainedAclReadCheck);
    REGISTER_FUNCTION(testRetainHandlingDontGiveRetain);
    REGISTER_FUNCTION(testRetainHandlingDontGiveRetainOnExistingSubscription);
    REGISTER_FUNCTION(test_various_packet_sizes);
    REGISTER_FUNCTION3(test_acl_tree);
    REGISTER_FUNCTION3(test_acl_tree2);
    REGISTER_FUNCTION3(test_acl_patterns_username);
    REGISTER_FUNCTION3(test_acl_patterns_clientid);
    REGISTER_FUNCTION(test_loading_acl_file);
    REGISTER_FUNCTION3(test_loading_second_value);
    REGISTER_FUNCTION3(test_parsing_numbers);
    REGISTER_FUNCTION3(test_validUtf8Generic);

#ifndef FMQ_NO_SSE
    REGISTER_FUNCTION3(test_sse_split);
    REGISTER_FUNCTION3(test_validUtf8Sse);
    REGISTER_FUNCTION3(test_utf8_nonchars);
    REGISTER_FUNCTION3(test_utf8_overlong);
    REGISTER_FUNCTION3(test_utf8_compare_implementation);
#endif

    REGISTER_FUNCTION3(testPacketInt16Parse);
    REGISTER_FUNCTION3(testRetainedMessageDB);
    REGISTER_FUNCTION3(testRetainedMessageDBNotPresent);
    REGISTER_FUNCTION3(testRetainedMessageDBEmptyList);
    REGISTER_FUNCTION3(testSavingSessions);
    REGISTER_FUNCTION3(testParsePacket);
    REGISTER_FUNCTION3(testbufferToMqttPacketsFuzz);
    REGISTER_FUNCTION(testDowngradeQoSOnSubscribeQos2to2);
    REGISTER_FUNCTION(testDowngradeQoSOnSubscribeQos2to1);
    REGISTER_FUNCTION(testDowngradeQoSOnSubscribeQos2to0);
    REGISTER_FUNCTION(testDowngradeQoSOnSubscribeQos1to1);
    REGISTER_FUNCTION(testDowngradeQoSOnSubscribeQos1to0);
    REGISTER_FUNCTION(testDowngradeQoSOnSubscribeQos0to0);
    REGISTER_FUNCTION(testNotMessingUpQosLevels);
    REGISTER_FUNCTION(testUnSubscribe);
    REGISTER_FUNCTION(testUnsubscribeNonExistingWildcard);
    REGISTER_FUNCTION(testBasicsWithFlashMQTestClient);
    REGISTER_FUNCTION(testDontRemoveSessionGivenToNewClientWithSameId);
    REGISTER_FUNCTION(testKeepSubscriptionOnKickingOutExistingClientWithCleanSessionFalse);
    REGISTER_FUNCTION(testPickUpSessionWithSubscriptionsAfterDisconnect);
    REGISTER_FUNCTION(testMqtt3will);
    REGISTER_FUNCTION(testMqtt3NoWillOnDisconnect);
    REGISTER_FUNCTION(testMqtt5NoWillOnDisconnect);
    REGISTER_FUNCTION(testMqtt5DelayedWill);
    REGISTER_FUNCTION(testMqtt5DelayedWillAlwaysOnSessionEnd);
    REGISTER_FUNCTION(testWillOnSessionTakeOvers);
    REGISTER_FUNCTION(testOverrideWillDelayOnSessionDestructionByTakeOver);
    REGISTER_FUNCTION(testDisabledWills);
    REGISTER_FUNCTION(testMqtt5DelayedWillsDisabled);
    REGISTER_FUNCTION3(testStringDistances);
    REGISTER_FUNCTION3(testConfigSuggestion);
    REGISTER_FUNCTION3(testFlags);
    REGISTER_FUNCTION(testIncomingTopicAlias);
    REGISTER_FUNCTION(testOutgoingTopicAlias);
    REGISTER_FUNCTION(testOutgoingTopicAliasBeyondMax);
    REGISTER_FUNCTION(testOutgoingTopicAliasStoredPublishes);
    REGISTER_FUNCTION(testReceivingRetainedMessageWithQoS);
    REGISTER_FUNCTION(testQosDowngradeOnOfflineClients);
    REGISTER_FUNCTION(testPacketOrderOnSessionPickup);
    REGISTER_FUNCTION(testUserProperties);
    REGISTER_FUNCTION(testMessageExpiry);
    REGISTER_FUNCTION(testExpiredQueuedMessages);
    REGISTER_FUNCTION(testQoSPublishQueue);
    REGISTER_FUNCTION3(testQoSPublishQueueMemoryLeak);
    REGISTER_FUNCTION3(testTimePointToAge);
    REGISTER_FUNCTION(testMosquittoPasswordFile);
    REGISTER_FUNCTION(testOverrideAllowAnonymousToTrue);
    REGISTER_FUNCTION(testOverrideAllowAnonymousToFalse);
    REGISTER_FUNCTION(testKeepAllowAnonymousFalse);
    REGISTER_FUNCTION(testAllowAnonymousWithoutPasswordsLoaded);
    REGISTER_FUNCTION3(testAddrMatchesSubnetIpv4);
    REGISTER_FUNCTION3(testAddrMatchesSubnetIpv6);
    REGISTER_FUNCTION(testSharedSubscribersUnit);
    REGISTER_FUNCTION(testSharedSubscribers);
    REGISTER_FUNCTION(testDisconnectedSharedSubscribers);
    REGISTER_FUNCTION(testUnsubscribedSharedSubscribers);
    REGISTER_FUNCTION(testSharedSubscribersSurviveRestart);
    REGISTER_FUNCTION(testSharedSubscriberDoesntGetRetainedMessages);
    REGISTER_FUNCTION(testExtendedAuthOneStepSucceed);
    REGISTER_FUNCTION(testExtendedAuthOneStepDeny);
    REGISTER_FUNCTION(testExtendedAuthOneStepBadAuthMethod);
    REGISTER_FUNCTION(testExtendedAuthTwoStep);
    REGISTER_FUNCTION(testExtendedAuthTwoStepSecondStepFail);
    REGISTER_FUNCTION(testExtendedReAuth);
    REGISTER_FUNCTION(testExtendedReAuthTwoStep);
    REGISTER_FUNCTION(testExtendedReAuthFail);
    REGISTER_FUNCTION(testSimpleAuthAsync);
    REGISTER_FUNCTION(testFailedAsyncClientCrashOnSession);
    REGISTER_FUNCTION(testAsyncWithImmediateFollowUpPackets);
    REGISTER_FUNCTION(testAsyncWithException);
    REGISTER_FUNCTION(testPluginAuthFail);
    REGISTER_FUNCTION(testPluginAuthSucceed);
    REGISTER_FUNCTION(testPluginOnDisconnect);
    REGISTER_FUNCTION(testPluginGetClientAddress);
    REGISTER_FUNCTION(testChangePublish);
    REGISTER_FUNCTION(testClientRemovalByPlugin);
    REGISTER_FUNCTION(testSubscriptionRemovalByPlugin);
    REGISTER_FUNCTION(testPublishByPlugin);
    REGISTER_FUNCTION(testWillDenialByPlugin);
    REGISTER_FUNCTION(testPluginMainInit);
    REGISTER_FUNCTION2(testAsyncCurl, true, true);
    REGISTER_FUNCTION(testSubscribeWithoutRetainedDelivery);
    REGISTER_FUNCTION(testDontUpgradeWildcardDenyMode);
    REGISTER_FUNCTION(testAlsoDontApproveOnErrorInPluginWithWildcardDenyMode);
    REGISTER_FUNCTION(testDenyWildcardSubscription);
    REGISTER_FUNCTION(testUserPropertiesPresent);
    REGISTER_FUNCTION(testPublishInThread);
    REGISTER_FUNCTION(testPublishToItself);
    REGISTER_FUNCTION(testNoLocalPublishToItself);
    REGISTER_FUNCTION3(testTopicMatchingInSubscriptionTree);
    REGISTER_FUNCTION2(testDnsResolver, false, true);
    REGISTER_FUNCTION2(testDnsResolverDontCancel, false, true);
    REGISTER_FUNCTION2(testDnsResolverSecondQuery, false, true);
    REGISTER_FUNCTION2(testDnsResolverInvalid, false, true);
    REGISTER_FUNCTION(testGetResultWhenThereIsNone);
    REGISTER_FUNCTION(testWebsocketPing);
    REGISTER_FUNCTION(testWebsocketCorruptLengthFrame);
    REGISTER_FUNCTION(testWebsocketHugePing);
    REGISTER_FUNCTION(testWebsocketManyBigPingFrames);
    REGISTER_FUNCTION(testWebsocketClose);
    REGISTER_FUNCTION3(testStartsWith);
    REGISTER_FUNCTION3(testStringValuesParsing);
    REGISTER_FUNCTION3(testStringValuesParsingEscaping);
    REGISTER_FUNCTION3(testStringValuesFuzz);
    REGISTER_FUNCTION3(testStringValuesInvalid);
    REGISTER_FUNCTION2(testPreviouslyValidConfigFile, false, false);
    REGISTER_FUNCTION3(testNoCopy);
    REGISTER_FUNCTION3(testBase64);
    REGISTER_FUNCTION(testSessionTakeoverOtherUsername);
    REGISTER_FUNCTION(testCorrelationData);
    REGISTER_FUNCTION(testSubscriptionIdOnlineClient);
    REGISTER_FUNCTION(testSubscriptionIdOfflineClient);
    REGISTER_FUNCTION(testSubscriptionIdRetainedMessages);
    REGISTER_FUNCTION(testSubscriptionIdSharedSubscriptions);
    REGISTER_FUNCTION(testSubscriptionIdChange);
    REGISTER_FUNCTION(testSubscriptionIdOverlappingSubscriptions);
}

bool MainTests::test(bool skip_tests_with_internet, bool skip_server_tests, bool abort_on_first_fail, const std::vector<std::string> &tests)
{
    int testCount = 0;
    int testPassCount = 0;
    int testFailCount = 0;
    int testExceptionCount = 0;

    std::map<std::string, TestFunction> *selectedTests = &this->testFunctions;
    std::map<std::string, TestFunction> subset;

    for(const std::string &test_name : tests)
    {
        auto pos = this->testFunctions.find(test_name);

        if (pos == this->testFunctions.end())
        {
            std::cerr << "Test '" << test_name << "' not found." << std::endl;
            return false;
        }

        subset[test_name] = pos->second;
    }

    if (!subset.empty())
    {
        selectedTests = &subset;
    }

    std::vector<std::string> failedTests;

    for (const auto &pair : *selectedTests)
    {
        if (abort_on_first_fail && testFailCount > 0)
            break;

        const TestFunction &tf = pair.second;

        if (skip_tests_with_internet && tf.requiresInternet)
            continue;

        if (skip_server_tests && tf.requiresServer)
            continue;

        testCount++;

        try
        {
            std::cout << CYAN << "INIT" << COLOR_END << ": " << pair.first << std::endl;

            if (!isatty(2))
                std::cerr << "INIT: " << pair.first << std::endl;

            TestInitializer testInitializer(this);
            testInitializer.init(tf.requiresServer);

            const int failCountBefore = assert_fail_count;
            const int assertCountBefore = assert_count;

            std::cout << CYAN << "RUN" << COLOR_END << ": " << pair.first << std::endl;

            if (!isatty(2))
                std::cerr << "RUN: " << pair.first << std::endl;

            tf.f();

            const int failCountAfter = assert_fail_count;
            const int assertCountAfter = assert_count;

            testInitializer.cleanup();

            if (assertCountBefore == assertCountAfter)
            {
                std::cout << RED << "FAIL" << COLOR_END << ": " << pair.first << ": no asserts performed" << std::endl;
                testFailCount++;
                failedTests.push_back(pair.first);
            }
            else if (failCountBefore != failCountAfter)
            {
                std::cout << RED << "FAIL" << COLOR_END << ": " << pair.first << std::endl;
                testFailCount++;
                failedTests.push_back(pair.first);
            }
            else
            {
                std::cout << GREEN << "PASS" << COLOR_END << ": " << pair.first << std::endl;
                testPassCount++;
            }
        }
        catch (std::exception &ex)
        {
            // TODO: get details

            testFailCount++;
            testExceptionCount++;
            failedTests.push_back(pair.first);

            std::cout << RED << "FAIL EXCEPTION" << COLOR_END << ": " << pair.first << ": " << ex.what() << std::endl;
        }

        std::cout << std::endl;
    }

    Logger::getInstance()->quit();

    std::cout << "Tests run: " << testCount << ". Passed: " << testPassCount << ". Failed: "
              << testFailCount << " (of which " << testExceptionCount << " exceptions). Total assertions: "
              << assert_count << "." << std::endl;

    std::cout << std::endl << std::endl;

    if (testCount == 0)
    {
        std::cout << std::endl << RED << "No tests ran." << COLOR_END << std::endl;
        return false;
    }
    else if (assert_fail_count == 0 && testFailCount == 0)
    {
        std::cout << std::endl << GREEN << "TESTS PASSED" << COLOR_END << std::endl;
        return true;
    }
    else
    {
        std::cout << "Failed tests: " << std::endl;

        for (const std::string &test_name : failedTests)
        {
            std::cout << " - " << test_name << std::endl;
        }

        std::cout << std::endl << RED << "TESTS FAILED" << COLOR_END << std::endl;
        return false;
    }
}


================================================
FILE: FlashMQTests/maintests.h
================================================
#ifndef MAINTESTS_H
#define MAINTESTS_H

#include <memory>
#include <unordered_map>
#include <functional>

#include "mainappinthread.h"

#define REGISTER_FUNCTION(name) registerFunction(#name, std::bind(&MainTests::name, this))
#define REGISTER_FUNCTION2(name, server, internet) registerFunction(#name, std::bind(&MainTests::name, this), server, internet)
#define REGISTER_FUNCTION3(name) registerFunction(#name, std::bind(&MainTests::name, this), false, false)

struct TestFunction
{
    std::function<void()> f;
    bool requiresServer = true;
    bool requiresInternet = false;
};

class MainTests
{
    friend class TestInitializer;

    std::shared_ptr<ThreadData> dummyThreadData;
    std::unique_ptr<MainAppInThread> mainApp;
    Settings settings;
    std::shared_ptr<PluginLoader> pluginLoader = std::make_shared<PluginLoader>();

    std::map<std::string, TestFunction> testFunctions;

    void testAsserts();

    void initBeforeEachTest(const std::vector<std::string> &args, bool startServer=true);
    void initBeforeEachTest(bool startServer=true);
    void cleanupAfterEachTest();
    void registerFunction(const std::string &name, std::function<void ()> f, bool requiresServer=true, bool requiresInternet=false);

    // Compatability for porting the tests away from Qt. The function names are too vague so want to phase them out.
    void init(const std::vector<std::string> &args) { initBeforeEachTest(args);}
    void init() {initBeforeEachTest();}
    void cleanup() {cleanupAfterEachTest();}

    void testParsePacketHelper(const std::string &topic, uint8_t from_qos, bool retain);
    void testTopicMatchingInSubscriptionTreeHelper(const std::string &subscribe_topic, const std::string &publish_topic, int match_count=1);

    void testDummy();

    void test_circbuf();
    void test_circbuf_unwrapped_doubling();
    void test_circbuf_wrapped_doubling();
    void test_circbuf_full_wrapped_buffer_doubling();
    void test_cirbuf_vector_methods();

    void test_validSubscribePath();

    /**
     * Retain tests
     */
    void test_retained();
    void test_retained_double_set();
    void test_retained_mode_drop();
    void test_retained_mode_downgrade();
    void test_retained_mode_no_retain();
    void test_retained_changed();
    void test_retained_removed();
    void test_retained_tree();
    void test_retained_global_expire();
    void test_retained_per_message_expire();
    void test_retained_tree_purging();
    void testRetainAsPublished();
    void testRetainAsPublishedNegative();
    void testRetainedParentOfWildcard();
    void testRetainedWildcard();
    void testRetainedAclReadCheck();
    void testRetainHandlingDontGiveRetain();
    void testRetainHandlingDontGiveRetainOnExistingSubscription();

    void test_various_packet_sizes();

    void test_acl_tree();
    void test_acl_tree2();
    void test_acl_patterns_username();
    void test_acl_patterns_clientid();
    void test_loading_acl_file();

    void test_loading_second_value();
    void test_parsing_numbers();
    void test_validUtf8Generic();

#ifndef FMQ_NO_SSE
    void test_sse_split();
    void test_validUtf8Sse();
    void test_utf8_nonchars();
    void test_utf8_overlong();
    void test_utf8_compare_implementation();
#endif

    void testPacketInt16Parse();

    void testRetainedMessageDB();
    void testRetainedMessageDBNotPresent();
    void testRetainedMessageDBEmptyList();

    void testSavingSessions();

    void testParsePacket();
    void testbufferToMqttPacketsFuzz();

    void testDowngradeQoSOnSubscribeQos2to2();
    void testDowngradeQoSOnSubscribeQos2to1();
    void testDowngradeQoSOnSubscribeQos2to0();
    void testDowngradeQoSOnSubscribeQos1to1();
    void testDowngradeQoSOnSubscribeQos1to0();
    void testDowngradeQoSOnSubscribeQos0to0();

    void testNotMessingUpQosLevels();

    void testUnSubscribe();
    void testUnsubscribeNonExistingWildcard();

    void testBasicsWithFlashMQTestClient();

    void testDontRemoveSessionGivenToNewClientWithSameId();
    void testKeepSubscriptionOnKickingOutExistingClientWithCleanSessionFalse();
    void testPickUpSessionWithSubscriptionsAfterDisconnect();

    /**
     * Will tests.
     */
    void testMqtt3will();
    void testMqtt3NoWillOnDisconnect();
    void testMqtt5NoWillOnDisconnect();
    void testMqtt5DelayedWill();
    void testMqtt5DelayedWillAlwaysOnSessionEnd();
    void testWillOnSessionTakeOvers();
    void testOverrideWillDelayOnSessionDestructionByTakeOver();
    void testDisabledWills();
    void testMqtt5DelayedWillsDisabled();

    void testStringDistances();
    void testConfigSuggestion();
    void testFlags();


    void testIncomingTopicAlias();
    void testOutgoingTopicAlias();
    void testOutgoingTopicAliasBeyondMax();
    void testOutgoingTopicAliasStoredPublishes();

    void testReceivingRetainedMessageWithQoS();

    void testQosDowngradeOnOfflineClients();
    void testPacketOrderOnSessionPickup();

    void testUserProperties();

    void testMessageExpiry();

    void testExpiredQueuedMessages();
    void testQoSPublishQueue();
    void testQoSPublishQueueMemoryLeak();

    void testTimePointToAge();

    void testMosquittoPasswordFile();
    void testOverrideAllowAnonymousToTrue();
    void testOverrideAllowAnonymousToFalse();
    void testKeepAllowAnonymousFalse();
    void testAllowAnonymousWithoutPasswordsLoaded();

    void testAddrMatchesSubnetIpv4();
    void testAddrMatchesSubnetIpv6();

    /**
     * Shared subscriptions tests
     */
    void testSharedSubscribersUnit();
    void testSharedSubscribers();
    void testDisconnectedSharedSubscribers();
    void testUnsubscribedSharedSubscribers();
    void testSharedSubscribersSurviveRestart();
    void testSharedSubscriberDoesntGetRetainedMessages();

    /**
     * Plugin tests
     */
    void testExtendedAuthOneStepSucceed();
    void testExtendedAuthOneStepDeny();
    void testExtendedAuthOneStepBadAuthMethod();
    void testExtendedAuthTwoStep();
    void testExtendedAuthTwoStepSecondStepFail();
    void testExtendedReAuth();
    void testExtendedReAuthTwoStep();
    void testExtendedReAuthFail();
    void testSimpleAuthAsync();
    void testFailedAsyncClientCrashOnSession();
    void testAsyncWithImmediateFollowUpPackets();
    void testAsyncWithException();
    void testPluginAuthFail();
    void testPluginAuthSucceed();
    void testPluginOnDisconnect();
    void testPluginGetClientAddress();
    void testChangePublish();
    void testClientRemovalByPlugin();
    void testSubscriptionRemovalByPlugin();
    void testPublishByPlugin();
    void testWillDenialByPlugin();
    void testPluginMainInit();
    void testAsyncCurl();
    void testSubscribeWithoutRetainedDelivery();
    void testDontUpgradeWildcardDenyMode();
    void testAlsoDontApproveOnErrorInPluginWithWildcardDenyMode();
    void testDenyWildcardSubscription();
    void testUserPropertiesPresent();
    void testPublishInThread();

    void testPublishToItself();
    void testNoLocalPublishToItself();

    void testTopicMatchingInSubscriptionTree();

    void testDnsResolver();
    void testDnsResolverDontCancel();
    void testDnsResolverSecondQuery();
    void testDnsResolverInvalid();
    void testGetResultWhenThereIsNone();

    void testWebsocketPing();
    void testWebsocketCorruptLengthFrame();
    void testWebsocketHugePing();
    void testWebsocketManyBigPingFrames();
    void testWebsocketClose();

    void testStartsWith();

    void forkingTestForkingTestServer();

    void testStringValuesParsing();
    void testStringValuesParsingEscaping();
    void testStringValuesFuzz();
    void testStringValuesInvalid();
    void testPreviouslyValidConfigFile();

    void forkingTestSaveAndLoadDelayedWill();

    void testBase64();
    void testNoCopy();
    void testSessionTakeoverOtherUsername();
    void testCorrelationData();
    void testSubscriptionIdOnlineClient();
    void testSubscriptionIdOfflineClient();
    void testSubscriptionIdRetainedMessages();
    void testSubscriptionIdSharedSubscriptions();
    void testSubscriptionIdChange();
    void testSubscriptionIdOverlappingSubscriptions();

    void forkingTestBridgeWithLocalAndRemotePrefix();
    void forkingTestBridgePrefixesOtherClientsUnaffected();
    void forkingTestBridgeWithOnlyRemotePrefix();
    void forkingTestBridgeWithOnlyLocalPrefix();
    void forkingTestBridgeOutgoingTopicEqualsPrefix();
    void forkingTestBridgeIncomingTopicEqualsPrefix();
    void forkingTestBridgeZWithLocalAndRemotePrefixRetained();
    void forkingTestBridgeWithLocalAndRemotePrefixQueuedQoS();

public:
    MainTests();

    bool test(bool skip_tests_with_internet, bool skip_server_tests, bool abort_on_first_fail, const std::vector<std::string> &tests);
};

#endif // MAINTESTS_H


================================================
FILE: FlashMQTests/plugins/curlfunctions.cpp
================================================
#include "curlfunctions.h"
#include <sys/epoll.h>
#include "../../flashmq_plugin.h"
#include "test_plugin.h"
#include <cstring>

/**
 * @brief This is curl telling us what events to watch for.
 * @param easy
 * @param s
 * @param what
 * @param clientp
 * @param socketp
 * @return
 */
int socket_event_watch_notification(CURL *easy, curl_socket_t s, int what,  void *clientp, void *socketp)
{
    (void)easy;
    (void)clientp;
    (void)socketp;

    if (what == CURL_POLL_REMOVE)
        flashmq_poll_remove_fd(s);
    else
    {
        int events = 0;

        if (what == CURL_POLL_IN)
            events |= EPOLLIN;
        else if (what == CURL_POLL_OUT)
            events |= EPOLLOUT;
        else if (what == CURL_POLL_INOUT)
            events = EPOLLIN | EPOLLOUT;
        else
            return 1;

        flashmq_poll_add_fd(s, events, std::weak_ptr<void>());
    }

    return 0;
}

void check_all_active_curls(TestPluginData *p, CURLM *curlMulti)
{
    CURLMsg *msg;
    int msgs_left;
    while((msg = curl_multi_info_read(curlMulti, &msgs_left)))
    {
        if (msg->msg == CURLMSG_DONE)
        {
            CURL *easy = msg->easy_handle;
            AuthenticatingClient *c = nullptr;
            curl_easy_getinfo(easy, CURLINFO_PRIVATE, &c);

            flashmq_logf(LOG_INFO, "Libcurl said: %s", curl_easy_strerror(msg->data.result));

            std::string answer(c->response.data(), std::min<int>(9, c->response.size()));

            if (answer == "<!doctype")
                flashmq_continue_async_authentication_v4(c->client, AuthResult::success, std::string(), std::string(), 0);
            else
                flashmq_continue_async_authentication_v4(c->client, AuthResult::login_denied, std::string(), std::string(), 0);

            // Normally we have to have something in the AuthenticatingClient to look up which request to delete, but we only have one here.
            p->curlTestClient.reset();
        }
    }
}

void call_timed_curl_multi_socket_action(CURLM *multi, TestPluginData *p)
{
    p->current_timer = 0;

    int a = 0;
    int rc = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &a);

    /* Curl says: "When this function returns error, the state of all transfers are uncertain and they cannot be
     * continued. curl_multi_socket_action should not be called again on the same multi handle after an error has
     * been returned, unless first removing all the handles and adding new ones."
     */
    if (rc != CURLM_OK)
    {
        // This would normally be removing all our pending requests, but we only have one here.
        p->curlTestClient.reset();
        return;
    }

    check_all_active_curls(p, multi);
}

int timer_callback(CURLM *multi, long timeout_ms, void *clientp)
{
    TestPluginData *p = static_cast<TestPluginData*>(clientp);

    // We also remove the last known task before it executes if curl tells us to install a new one. This
    // is suggested by the unclear and incomplete example at https://curl.se/libcurl/c/CURLMOPT_TIMERFUNCTION.html.
    if (timeout_ms == -1 || p->current_timer > 0)
    {
        flashmq_remove_task(p->current_timer);
        p->current_timer = 0;
    }

    if (timeout_ms >= 0)
    {
        auto f = std::bind(&call_timed_curl_multi_socket_action, multi, p);
        p->current_timer = flashmq_add_task(f, timeout_ms);
    }
    return CURLM_OK;
}

size_t curl_write_cb(char *data, size_t n, size_t l, void *userp)
{
    AuthenticatingClient *ac = static_cast<AuthenticatingClient*>(userp);

    int pos = ac->response.size();
    ac->response.resize(ac->response.size() + n*l);
    std::memcpy(&ac->response[pos], data, n*l);

    return n*l;
}


================================================
FILE: FlashMQTests/plugins/curlfunctions.h
================================================
#ifndef CURLFUNCTIONS_H
#define CURLFUNCTIONS_H

#include <curl/curl.h>
#include "test_plugin.h"

int socket_event_watch_notification(CURL *easy, curl_socket_t s, int what,  void *clientp, void *socketp);
void check_all_active_curls(TestPluginData *p, CURLM *curlMulti);
void call_timed_curl_multi_socket_action(CURLM *multi, TestPluginData *p);
int timer_callback(CURLM *multi, long timeout_ms, void *clientp);
size_t curl_write_cb(char *data, size_t n, size_t l, void *userp);

#endif // CURLFUNCTIONS_H


================================================
FILE: FlashMQTests/plugins/test_plugin.cpp
================================================
#include <functional>
#include <unistd.h>
#include <cassert>
#include <cstring>
#include <stdexcept>
#include <optional>

#include "../../flashmq_plugin.h"
#include "test_plugin.h"

#include <curl/curl.h>
#include <sys/epoll.h>
#include "curlfunctions.h"


TestPluginData::TestPluginData() :
    curlMulti(curl_multi_init(), curl_multi_cleanup)
{
    if (!curlMulti)
        throw std::runtime_error("Curl failed to init");

    curl_multi_setopt(curlMulti.get(), CURLMOPT_SOCKETFUNCTION, socket_event_watch_notification);
    curl_multi_setopt(curlMulti.get(), CURLMOPT_TIMERFUNCTION, timer_callback);
    curl_multi_setopt(curlMulti.get(), CURLMOPT_TIMERDATA, this);
}

TestPluginData::~TestPluginData()
{
    if (this->t.joinable())
        t.join();
}

void get_auth_result_delayed(std::weak_ptr<Client> client, AuthResult result)
{
    usleep(500000);

    flashmq_continue_async_authentication_v4(client, result, "", "", 0);
}


int flashmq_plugin_version()
{
    return FLASHMQ_PLUGIN_VERSION;
}

void flashmq_plugin_allocate_thread_memory(void **thread_data, std::unordered_map<std::string, std::string> &plugin_opts)
{
    TestPluginData *p = new TestPluginData();
    *thread_data = p;
    (void)plugin_opts;
}

void flashmq_plugin_deallocate_thread_memory(void *thread_data, std::unordered_map<std::string, std::string> &plugin_opts)
{
    (void)plugin_opts;

    TestPluginData *p = static_cast<TestPluginData*>(thread_data);
    delete p;
}

void flashmq_plugin_poll_event_received(void *thread_data, int fd, uint32_t events, const std::weak_ptr<void> &ptr)
{
    (void)ptr;

    TestPluginData *p = static_cast<TestPluginData*>(thread_data);

    int new_events = CURL_CSELECT_ERR;

    if (events & EPOLLIN)
    {
        new_events &= ~CURL_CSELECT_ERR;
        new_events |= CURL_CSELECT_IN;
    }
    if (events & EPOLLOUT)
    {
        new_events &= ~CURL_CSELECT_ERR;
        new_events |= CURL_CSELECT_OUT;
    }

    int n = -1;
    if (curl_multi_socket_action(p->curlMulti.get(), fd, new_events, &n) != CURLM_OK)
    {
        p->curlTestClient.reset();
        return;
    }

    check_all_active_curls(p, p->curlMulti.get());
}

void flashmq_plugin_init(void *thread_data, std::unordered_map<std::string, std::string> &plugin_opts, bool reloading)
{
    (void)thread_data;
    (void)plugin_opts;
    (void)reloading;

    TestPluginData *p = static_cast<TestPluginData*>(thread_data);

    if (plugin_opts.find("main_init was here") != plugin_opts.end())
        p->main_init_ran = true;
}

void flashmq_plugin_deinit(void *thread_data, std::unordered_map<std::string, std::string> &plugin_opts, bool reloading)
{
    (void)thread_data;
    (void)plugin_opts;
    (void)reloading;
}

void flashmq_plugin_periodic_event(void *thread_data)
{
    (void)thread_data;
}

AuthResult flashmq_plugin_login_check(void *thread_data, const std::string &clientid, const std::string &username, const std::string &password,
                                      const std::vector<std::pair<std::string, std::string>> *userProperties, const std::weak_ptr<Client> &client)
{
    (void)thread_data;
    (void)clientid;
    (void)username;
    (void)password;
    (void)userProperties;
    (void)client;

    if (username.find("async") == 0)
    {
        TestPluginData *p = static_cast<TestPluginData*>(thread_data);
        p->c = client;

        AuthResult result = password == "success" ? AuthResult::success : AuthResult::login_denied;

        auto delayedResult = std::bind(&get_auth_result_delayed, p->c, result);

        if (p->t.joinable())
            p->t.join();

        p->t = std::thread(delayedResult);

        return AuthResult::async;
    }

    if (username == "failme")
        return AuthResult::login_denied;

    if (username == "getaddress")
    {
        struct sockaddr_storage addr_mem;
        struct sockaddr *addr = reinterpret_cast<sockaddr*>(&addr_mem);
        socklen_t addrlen = sizeof(addr_mem);

        std::string text;
        flashmq_get_client_address_v4(client, &text, addr, &addrlen);

        sockaddr sockaddr_after;
        std::memcpy(&sockaddr_after, addr, std::min<socklen_t>(addrlen, sizeof(sockaddr)));

        flashmq_publish_message("getaddresstest/address", 0, false, text);

        if (sockaddr_after.sa_family == AF_INET)
        {
            flashmq_publish_message("getaddresstest/family", 0, false, "AF_INET");
        }
    }

    if (username == "curl")
    {
        TestPluginData *p = static_cast<TestPluginData*>(thread_data);

        p->curlTestClient = std::make_unique<AuthenticatingClient>();
        p->curlTestClient->client = client;

        curl_easy_setopt(p->curlTestClient->easy_handle.get(), CURLOPT_WRITEFUNCTION, curl_write_cb);
        curl_easy_setopt(p->curlTestClient->easy_handle.get(), CURLOPT_WRITEDATA, p->curlTestClient.get());
        curl_easy_setopt(p->curlTestClient->easy_handle.get(), CURLOPT_PRIVATE, p->curlTestClient.get());

        // Keep in mind that DNS resovling may be blocking too. You could perhaps resolve the DNS once and use the result.
        curl_easy_setopt(p->curlTestClient->easy_handle.get(), CURLOPT_URL, "http://www.google.com/");

        p->curlTestClient->addToMulti(p->curlMulti);

        return AuthResult::async;
    }

    return AuthResult::success;
}

void publish_in_thread()
{
    flashmq_publish_message("topic/from/thread", 0, false, "payload from thread");
}

AuthResult flashmq_plugin_acl_check(
    void *thread_data, const AclAccess access, const std::string &clientid, const std::string &username,
    const std::string &topic, const std::vector<std::string> &subtopics, const std::string &shareName,
    std::string_view payload, const uint8_t qos, const bool retain,
    const std::optional<std::string> &correlationData, const std::optional<std::string> &responseTopic,
    const std::optional<std::string> &contentType,
    const std::optional<std::chrono::time_point<std::chrono::steady_clock>> expiresAt,
    const std::vector<std::pair<std::string, std::string>> *userProperties)
{
    (void)thread_data;
    (void)access;
    (void)clientid;
    (void)username;
    (void)subtopics;
    (void)qos;
    (void)retain;
    (void)correlationData;
    (void)responseTopic;
    (void)userProperties;
    (void)shareName;
    (void)contentType;
    (void)expiresAt;

    if (clientid == "return_error")
        return AuthResult::error;

    if (access == AclAccess::subscribe && clientid == "success_without_retained_delivery")
        return AuthResult::success_without_retained_delivery;

    if (clientid == "test_user_without_retain_as_published_CswU21YA" && access == AclAccess::read)
        assert(!retain);

    if (clientid == "test_user_with_retain_as_published_v8sIeCvI" && access == AclAccess::read)
        assert(retain);

    assert(access == AclAccess::subscribe || !payload.empty());

    if (access == AclAccess::register_will && topic == "will/disallowed")
        return AuthResult::acl_denied;

    if (topic == "removeclient" || topic == "removeclientandsession")
    {
        std::weak_ptr<Session> ses;
        flashmq_get_session_pointer(clientid, username, ses);
        flashmq_plugin_remove_client_v4(ses, topic == "removeclientandsession", ServerDisconnectReasons::NormalDisconnect);
    }

    if (clientid == "unsubscribe" && access == AclAccess::write)
    {
        std::weak_ptr<Session> session;
        flashmq_get_session_pointer(clientid, username, session);
        flashmq_plugin_remove_subscription_v4(session, topic);
    }

    if (clientid == "generate_publish")
    {
        flashmq_logf(LOG_INFO, "Publishing from plugin.");

        const std::string topic = "generated/topic";
        const std::string payload = "money";
        flashmq_publish_message(topic, 0, false, payload);
    }

    if ((access == AclAccess::read || access == AclAccess::write) && topic == "test_user_property")
    {
        assert(userProperties);
    }

    if (topic == "publish_in_thread" && access == AclAccess::write)
    {
        std::thread t(publish_in_thread);
        pthread_setname_np(t.native_handle(), "PubInThread");
        t.join();
    }

    return AuthResult::success;
}

AuthResult flashmq_plugin_extended_auth(void *thread_data, const std::string &clientid, ExtendedAuthStage stage, const std::string &authMethod,
                                        const std::string &authData, const std::vector<std::pair<std::string, std::string>> *userProperties, std::string &returnData,
                                        std::string &username, const std::weak_ptr<Client> &client)
{
    (void)thread_data;
    (void)stage;
    (void)authMethod;
    (void)authData;
    (void)username;
    (void)clientid;
    (void)userProperties;
    (void)returnData;
    (void)client;

    if (authMethod == "always_good_passing_back_the_auth_data")
    {
        if (authData == "actually not good.")
            return AuthResult::login_denied;

        returnData = authData;
        return AuthResult::success;
    }
    if (authMethod == "always_fail")
    {
        return AuthResult::login_denied;
    }
    if (authMethod == "two_step")
    {
        if (authData == "Hello")
            returnData = "Hello back";

        if (authData == "grant me already!")
        {
            returnData = "OK, if you insist.";
            return AuthResult::success;
        }
        else if (authData == "whoops, wrong data.")
            return AuthResult::login_denied;
        else
            return AuthResult::auth_continue;
    }

    return AuthResult::auth_method_not_supported;
}

bool flashmq_plugin_alter_publish(
    void *thread_data, const std::string &clientid, std::string &topic, const std::vector<std::string> &subtopics,
    std::string_view payload, uint8_t &qos, bool &retain, std::optional<std::string> &correlationData,
    std::optional<std::string> &responseTopic, std::optional<std::string> &contentType,
    std::vector<std::pair<std::string, std::string>> *userProperties)
{
    (void)thread_data;
    (void)clientid;
    (void)subtopics;
    (void)qos;
    (void)retain;
    (void)correlationData;
    (void)responseTopic;
    (void)userProperties;
    (void)contentType;

    TestPluginData *p = static_cast<TestPluginData*>(thread_data);

    assert(!payload.empty());

    if (topic == "changeme")
    {
        topic = "changed";
        qos = 2;
        return true;
    }

    if (topic == "check_main_init_presence" && p->main_init_ran)
    {
        topic = "check_main_init_presence_confirmed";
        return true;
    }

    return false;
}

void flashmq_plugin_client_disconnected(void *thread_data, const std::string &clientid)
{
    (void)thread_data;

    flashmq_logf(LOG_INFO, "flashmq_plugin_client_disconnected called for '%s'", clientid.c_str());
    flashmq_publish_message("disconnect/confirmed", 0, false, "adsf");
}

void flashmq_plugin_main_init(std::unordered_map<std::string, std::string> &plugin_opts)
{
    (void)plugin_opts;

    flashmq_logf(LOG_INFO, "The tester was here.");

    // The plugin_opts aren't const. I don't know if that was a mistake or not anymore, but it works in my favor now.
    plugin_opts["main_init was here"] = "true";

    if (curl_global_init(CURL_GLOBAL_ALL) != 0)
        throw std::runtime_error("Global curl init failed to init");
}

void flashmq_plugin_main_deinit(std::unordered_map<std::string, std::string> &plugin_opts)
{
    (void)plugin_opts;

    curl_global_cleanup();
}

AuthenticatingClient::AuthenticatingClient() :
    easy_handle(curl_easy_init(), curl_easy_cleanup)
{

}

AuthenticatingClient::~AuthenticatingClient()
{
    auto x = registeredAtMultiHandle.lock();

    if (x)
    {
        curl_multi_remove_handle(x.get(), easy_handle.get());
    }
}

void AuthenticatingClient::addToMulti(std::shared_ptr<CURLM> &curlMulti)
{
    if (curl_multi_add_handle(curlMulti.get(), easy_handle.get()) != CURLM_OK)
        throw std::runtime_error("curl_multi_add_handle failed");

    registeredAtMultiHandle = curlMulti;
}


================================================
FILE: FlashMQTests/plugins/test_plugin.h
================================================
#ifndef TESTPLUGIN_H
#define TESTPLUGIN_H

#include <memory>
#include <vector>
#include <thread>
#include "../../forward_declarations.h"

#include <curl/curl.h>

struct AuthenticatingClient
{
    std::weak_ptr<Client> client;
    std::vector<char> response;
    std::unique_ptr<CURL, void(*)(CURL*)> easy_handle;
    std::weak_ptr<CURLM> registeredAtMultiHandle;

public:
    AuthenticatingClient();
    ~AuthenticatingClient();

    void addToMulti(std::shared_ptr<CURLM> &curlMulti);
};


class TestPluginData
{
public:
    std::thread t;
    std::weak_ptr<Client> c;
    bool main_init_ran = false;
    std::shared_ptr<CURLM> curlMulti;
    uint32_t current_timer = 0;

    // Normally we keep some kind of indexed record of requests, but in our test plugin, we just track one.
    std::unique_ptr<AuthenticatingClient> curlTestClient;

public:
    TestPluginData();
    ~TestPluginData();
};



#endif // TESTPLUGIN_H


================================================
FILE: FlashMQTests/plugins/test_plugin.pro
================================================
QT -= gui

CONFIG += c++17

TARGET = test_plugin
TEMPLATE = lib

VERSION=0.0.1

LIBS += -lcurl

HEADERS += test_plugin.h \
    curlfunctions.h

SOURCES += test_plugin.cpp \
    curlfunctions.cpp


================================================
FILE: FlashMQTests/plugintests.cpp
================================================
#include "maintests.h"
#include "testhelpers.h"
#include "conffiletemp.h"
#include "flashmqtestclient.h"

#include <sys/sysinfo.h>

void MainTests::testWillDenialByPlugin()
{
    std::vector<ProtocolVersion> versions { ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5 };

    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    std::unique_ptr<FlashMQTestClient> sender = std::make_unique<FlashMQTestClient>();
    sender->start();
    std::shared_ptr<WillPublish> will = std::make_shared<WillPublish>();
    will->topic = "will/allowed";
    will->payload = "mypayload";
    sender->setWill(will);
    sender->connectClient(ProtocolVersion::Mqtt311);

    FlashMQTestClient receiver;
    receiver.start();
    receiver.connectClient(ProtocolVersion::Mqtt311);
    receiver.subscribe("will/+", 0);

    sender.reset();

    receiver.waitForMessageCount(1);

    {
        auto ro = receiver.receivedObjects.lock();

        MqttPacket pubPack = ro->receivedPublishes.front();
        std::shared_ptr<Client> client = receiver.getClient();
        pubPack.parsePublishData(client);

        QCOMPARE(pubPack.getPublishData().topic, "will/allowed");
        QCOMPARE(pubPack.getPublishData().payload, "mypayload");
        QCOMPARE(pubPack.getPublishData().qos, 0);
    }

    receiver.clearReceivedLists();

    // Now set a will that we will deny.

    {
        sender = std::make_unique<FlashMQTestClient>();
        sender->start();
        std::shared_ptr<WillPublish> will2 = std::make_shared<WillPublish>();
        will2->topic = "will/disallowed";
        will2->payload = "mypayload";
        sender->setWill(will2);
        sender->connectClient(ProtocolVersion::Mqtt311);

        sender.reset();
        usleep(500000);
        receiver.waitForMessageCount(0);

        auto ro = receiver.receivedObjects.lock();
        QVERIFY(ro->receivedPublishes.empty());
    }
}

void MainTests::testPluginAuthFail()
{
    std::vector<ProtocolVersion> versions { ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5 };

    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    for (ProtocolVersion &version : versions)
    {

        FlashMQTestClient client;
        client.start();
        client.connectClient(version, false, 120, [](Connect &connect) {
            connect.username = "failme";
            connect.password = "boo";
        });

        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);

        ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();

        if (version >= ProtocolVersion::Mqtt5)
            QVERIFY(connAckData.reasonCode == ReasonCodes::NotAuthorized);
        else
            QVERIFY(static_cast<uint8_t>(connAckData.reasonCode) == 5);
    }
}

void MainTests::testPluginAuthSucceed()
{
    std::vector<ProtocolVersion> versions { ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5 };

    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    for (ProtocolVersion &version : versions)
    {

        FlashMQTestClient client;
        client.start();
        client.connectClient(version, false, 120, [](Connect &connect) {
            connect.username = "passme";
            connect.password = "boo";
        });

        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);

        ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();

        QVERIFY(connAckData.reasonCode == ReasonCodes::Success);
    }
}

void MainTests::testExtendedAuthOneStepSucceed()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.username = "me";
        connect.password = "me me";

        connect.authenticationMethod = "always_good_passing_back_the_auth_data";
        connect.authenticationData = "I have a proposal to put to ye.";
    });

    auto ro = client.receivedObjects.lock();

    QVERIFY(ro->receivedPackets.size() == 1);

    ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();

    QVERIFY(connAckData.reasonCode == ReasonCodes::Success);
    QVERIFY(connAckData.authData == "I have a proposal to put to ye.");
}

void MainTests::testExtendedAuthOneStepDeny()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.username = "me";
        connect.password = "me me";

        connect.authenticationMethod = "always_fail";
    });

    auto ro = client.receivedObjects.lock();

    QVERIFY(ro->receivedPackets.size() == 1);

    ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();

    QVERIFY(connAckData.reasonCode == ReasonCodes::NotAuthorized);
}

void MainTests::testExtendedAuthOneStepBadAuthMethod()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.username = "me";
        connect.password = "me me";

        connect.authenticationMethod = "doesnt_exist";
    });

    auto ro = client.receivedObjects.lock();

    QVERIFY(ro->receivedPackets.size() == 1);

    ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();

    QVERIFY(connAckData.reasonCode == ReasonCodes::BadAuthenticationMethod);
}

void MainTests::testExtendedAuthTwoStep()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.username = "me";
        connect.password = "me me";

        connect.authenticationMethod = "two_step";
        connect.authenticationData = "Hello";
    });

    {
        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);

        AuthPacketData authData = ro->receivedPackets.front().parseAuthData();

        QVERIFY(authData.reasonCode == ReasonCodes::ContinueAuthentication);
        QVERIFY(authData.data == "Hello back");
    }

    client.clearReceivedLists();

    const Auth auth(ReasonCodes::ContinueAuthentication, "two_step", "grant me already!");
    client.writeAuth(auth);

    client.waitForConnack();

    auto ro = client.receivedObjects.lock();

    QVERIFY(ro->receivedPackets.size() == 1);

    ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();

    QVERIFY(connAckData.reasonCode == ReasonCodes::Success);
    QVERIFY(connAckData.authData == "OK, if you insist.");
}

void MainTests::testExtendedAuthTwoStepSecondStepFail()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.username = "me";
        connect.password = "me me";

        connect.authenticationMethod = "two_step";
        connect.authenticationData = "Hello";
    });

    {
        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);

        AuthPacketData authData = ro->receivedPackets.front().parseAuthData();

        QVERIFY(authData.reasonCode == ReasonCodes::ContinueAuthentication);
        QVERIFY(authData.data == "Hello back");
    }

    client.clearReceivedLists();

    const Auth auth(ReasonCodes::ContinueAuthentication, "two_step", "whoops, wrong data.");
    client.writeAuth(auth);

    client.waitForConnack();

    auto ro = client.receivedObjects.lock();

    QVERIFY(ro->receivedPackets.size() == 1);

    ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();

    QVERIFY(connAckData.reasonCode == ReasonCodes::NotAuthorized);
}

void MainTests::testExtendedReAuth()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.username = "me";
        connect.password = "me me";

        connect.authenticationMethod = "always_good_passing_back_the_auth_data";
        connect.authenticationData = "Santa Claus";
    });

    {
        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);

        ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();

        QVERIFY(connAckData.reasonCode == ReasonCodes::Success);
    }

    client.clearReceivedLists();

    // Then reauth.

    Auth auth(ReasonCodes::ContinueAuthentication, "always_good_passing_back_the_auth_data", "Again Santa Claus");
    client.writeAuth(auth);

    client.waitForConnack();

    auto ro = client.receivedObjects.lock();

    QVERIFY(ro->receivedPackets.size() == 1);

    AuthPacketData authData = ro->receivedPackets.front().parseAuthData();

    QVERIFY(authData.reasonCode == ReasonCodes::Success);
    QVERIFY(authData.data == "Again Santa Claus");
}

void MainTests::testExtendedReAuthTwoStep()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.username = "me";
        connect.password = "me me";

        connect.authenticationMethod = "two_step";
        connect.authenticationData = "Hello";
    });

    {
        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);

        AuthPacketData authData = ro->receivedPackets.front().parseAuthData();

        QVERIFY(authData.reasonCode == ReasonCodes::ContinueAuthentication);
        QVERIFY(authData.data == "Hello back");
    }

    client.clearReceivedLists();

    const Auth auth(ReasonCodes::ContinueAuthentication, "two_step", "grant me already!");
    client.writeAuth(auth);

    client.waitForConnack();

    {
        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);

        ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();

        QVERIFY(connAckData.reasonCode == ReasonCodes::Success);
        QVERIFY(connAckData.authData == "OK, if you insist.");
    }

    client.clearReceivedLists();

    // Then reauth.

    const Auth reauth(ReasonCodes::ReAuthenticate, "two_step", "Hello");
    client.writeAuth(reauth);
    client.waitForConnack();

    {
        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);

        AuthPacketData reauthData = ro->receivedPackets.front().parseAuthData();

        QVERIFY(reauthData.reasonCode == ReasonCodes::ContinueAuthentication);
        QVERIFY(reauthData.data == "Hello back");
    }

    client.clearReceivedLists();

    const Auth reauthFinish(ReasonCodes::ContinueAuthentication, "two_step", "grant me already!");
    client.writeAuth(reauthFinish);

    client.waitForConnack();

    {
        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);

        AuthPacketData reauthFinishData = ro->receivedPackets.front().parseAuthData();

        QVERIFY(reauthFinishData.reasonCode == ReasonCodes::Success);
        QVERIFY(reauthFinishData.data == "OK, if you insist.");
    }
}

void MainTests::testExtendedReAuthFail()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.username = "me";
        connect.password = "me me";

        connect.authenticationMethod = "always_good_passing_back_the_auth_data";
        connect.authenticationData = "I have a proposal to put to ye.";
    });

    {
        auto ro = client.receivedObjects.lock();
        QVERIFY(ro->receivedPackets.size() == 1);
        ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();
        QVERIFY(connAckData.reasonCode == ReasonCodes::Success);
        QVERIFY(connAckData.authData == "I have a proposal to put to ye.");
    }

    client.clearReceivedLists();

    // Then reauth.
    {
        const Auth reauth(ReasonCodes::ReAuthenticate, "always_good_passing_back_the_auth_data", "actually not good.");
        client.writeAuth(reauth);
        client.waitForPacketCount(1);

        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);
        QVERIFY(ro->receivedPackets.front().packetType == PacketType::DISCONNECT);

        DisconnectData data = ro->receivedPackets.front().parseDisconnectData();

        QVERIFY(data.reasonCode == ReasonCodes::NotAuthorized);
    }
}

void MainTests::testSimpleAuthAsync()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    std::list<std::string> results { "success", "fail" };

    for (std::string &result : results)
    {
        FlashMQTestClient client;
        client.start();
        client.connectClient(ProtocolVersion::Mqtt5, false, 120, [&](Connect &connect) {
            connect.username = "async";
            connect.password = result;
        });

        auto ro = client.receivedObjects.lock();

        QVERIFY(ro->receivedPackets.size() == 1);

        ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();

        if (result == "success")
            QVERIFY(connAckData.reasonCode == ReasonCodes::Success);
        else
            QVERIFY(connAckData.reasonCode == ReasonCodes::NotAuthorized);
    }
}

/**
 * There was a crash when doing session stuff with a client that was rejected by exception
 * in continuationOfAuthentication (by duplicate session id between different users).
 */
void MainTests::testFailedAsyncClientCrashOnSession()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    std::list<FlashMQTestClient> clients;

    clients.emplace_back();
    clients.back().start();
    clients.back().connectClient(ProtocolVersion::Mqtt5, false, 120, [&](Connect &connect) {
            connect.username = "async1";
            connect.password = "success";
            connect.clientid = "duplicate";
        }, 21883);

    clients.emplace_back();
    clients.back().start();
    clients.back().connectClient(ProtocolVersion::Mqtt5, false, 120, [&](Connect &connect) {
            connect.username = "async2";
            connect.password = "success";
            connect.clientid = "duplicate";
        }, 21883, false);

    // Because it crashed, we don't have events to wait for.
    std::this_thread::sleep_for(std::chrono::seconds(1));

    /*
     * An if statement in a test is dodgy, but the underlying client was made weak after this
     * test was written, so when the original problem occurs, the client hasn't disconnected nor expired.
     */
    if (!clients.back().clientExpired())
    {
        Publish pub("sdf", "wer", 2);
        MqttPacket pubPack(ProtocolVersion::Mqtt5, pub);
        if (pub.qos > 0)
            pubPack.setPacketId(3);
        clients.back().getClient()->writeMqttPacketAndBlameThisClient(pubPack);
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    // Test if the server still works after that.
    clients.front().clearReceivedLists();
    clients.front().publish("sdf", "sfd", 2);
    auto ro = clients.front().receivedObjects.lock();
    FMQ_VERIFY(!ro->receivedPackets.empty());
    FMQ_COMPARE(ro->receivedPackets.back().packetType, PacketType::PUBCOMP);
}

/**
 * We send a subscription directly after authentication, without waiting for it. These should be queued and dealt
 * with after the login was approved.
 */
void MainTests::testAsyncWithImmediateFollowUpPackets()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [&](Connect &connect) {
        connect.username = "async";
        connect.password = "success";
    }, 21883, false);

    {
        const uint16_t packet_id = 66;

        std::vector<Subscribe> subs;
        subs.emplace_back("our/random/topic", 0);
        MqttPacket subPack(client.getProtocolVersion(), packet_id, 0, subs);
        client.getClient()->writeMqttPacketAndBlameThisClient(subPack);
    }

    client.waitForConnack();
    client.getClient()->setAuthenticated(true);

    {
        auto ro = client.receivedObjects.lock();
        FMQ_VERIFY(ro->receivedPackets.size() >= 1);
        ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();
        QVERIFY(connAckData.reasonCode == ReasonCodes::Success);
    }

    client.clearReceivedLists();

    {
        FlashMQTestClient client2;
        client2.start();
        client2.connectClient(ProtocolVersion::Mqtt311);
        client2.publish("our/random/topic", "1Xs0QC5XInKLGHfm", 0);
    }

    client.waitForMessageCount(1);

    {
        auto ro = client.receivedObjects.lock();
        MqttPacket &p = ro->receivedPublishes.front();
        FMQ_COMPARE(p.getTopic(), "our/random/topic");
        FMQ_COMPARE(p.getPayloadCopy(), "1Xs0QC5XInKLGHfm");
    }
}

/**
 * We're testing behavior that throws an internal FlashMQ exception in handling async auth, namely trying
 * to take over session with a different username. An exception inside the plugin would not be a good test,
 * because that is handled locally by the authentication.
 */
void MainTests::testAsyncWithException()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    // =========

    for (ProtocolVersion p : {ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5})
    {
        FlashMQTestClient client1;
        client1.start();
        client1.connectClient(p, true, 600, [](Connect &connect) {
            connect.clientid = "thesameclientid";
            connect.username = "async0";
            connect.password = "success";
        });

        {
            auto ro = client1.receivedObjects.lock();
            auto &pack = ro->receivedPackets.at(0);
            FMQ_COMPARE(pack.packetType, PacketType::CONNACK);
            ConnAckData ackData = pack.parseConnAckData();
            FMQ_COMPARE(ackData.reasonCode, ReasonCodes::Success);
        }

        FlashMQTestClient client2;
        client2.start();
        client2.connectClient(p, true, 600, [](Connect &connect) {
            connect.clientid = "thesameclientid";
            connect.username = "async1";
            connect.password = "success";
        });

        {
            auto ro = client2.receivedObjects.lock();
            auto &pack = ro->receivedPackets.at(0);
            FMQ_COMPARE(pack.packetType, PacketType::CONNACK);
            ConnAckData ackData = pack.parseConnAckData();
            int expectedCode = p == ProtocolVersion::Mqtt5 ? static_cast<int>(ReasonCodes::NotAuthorized) : static_cast<int>(ConnAckReturnCodes::NotAuthorized);
            FMQ_COMPARE(static_cast<int>(ackData.reasonCode), expectedCode);
        }
    }
}

void MainTests::testClientRemovalByPlugin()
{
    std::list<std::string> methods { "removeclient", "removeclientandsession"};

    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    for (std::string &method : methods)
    {
        cleanup();
        init(args);

        FlashMQTestClient sender;
        sender.start();
        sender.connectClient(ProtocolVersion::Mqtt5, false, 120);
        const std::string sender_client_id = sender.getClientId();

        FlashMQTestClient receiver;
        receiver.start();
        receiver.connectClient(ProtocolVersion::Mqtt5);
        receiver.subscribe("#", 2);

        sender.publish(method, "asdf", 0);

        sender.waitForDisconnectPacket();

        auto ro = sender.receivedObjects.lock();
        QVERIFY(ro->receivedPackets.size() == 1);
        QVERIFY(ro->receivedPackets.front().packetType == PacketType::DISCONNECT);

        std::shared_ptr<SubscriptionStore> store = globals->subscriptionStore;
        std::shared_ptr<Session> session = store->lockSession(sender_client_id);

        if (method == "removeclient")
        {
            QVERIFY(session);
        }
        else
        {
            QVERIFY(!session);
        }

    }
}

void MainTests::testSubscriptionRemovalByPlugin()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    sender.start();
    sender.connectClient(ProtocolVersion::Mqtt5);

    FlashMQTestClient receiver;
    receiver.start();
    receiver.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.clientid = "unsubscribe";
    });
    receiver.subscribe("a/b/c", 2);

    FlashMQTestClient dummyreceiver;
    dummyreceiver.start();
    dummyreceiver.connectClient(ProtocolVersion::Mqtt5);
    dummyreceiver.subscribe("#", 2);

    sender.publish("a/b/c", "asdf", 0);

    receiver.waitForMessageCount(1);
    {
        auto ro = receiver.receivedObjects.lock();
        QVERIFY(ro->receivedPublishes.size() == 1);
    }

    receiver.clearReceivedLists();
    sender.clearReceivedLists();

    receiver.publish("a/b/c", "sdf", 0); // Because the clientid of this client, this will unsubscribe.
    dummyreceiver.clearReceivedLists();

    // A hack way to make sure the relevant thread has done the unsubscribe (by doing work we can detect).
    const int nprocs = get_nprocs();
    for (int i = 0; i < nprocs; i++)
        receiver.publish("waitforthis", "sdf", 0);
    dummyreceiver.waitForPacketCount(nprocs);
    receiver.clearReceivedLists();
    dummyreceiver.clearReceivedLists();

    sender.publish("a/b/c", "asdf", 0);
    usleep(200000);
    receiver.waitForMessageCount(0);

    {
        auto ro = receiver.receivedObjects.lock();
        QVERIFY(ro->receivedPublishes.empty());
    }
}

void MainTests::testPublishByPlugin()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    sender.start();
    sender.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.clientid = "generate_publish";
    });

    FlashMQTestClient receiver;
    receiver.start();
    receiver.connectClient(ProtocolVersion::Mqtt5, false, 120);
    receiver.subscribe("#", 2);

    sender.publish("boo", "booboo", 0);

    receiver.waitForMessageCount(2);

    auto ro = receiver.receivedObjects.lock();

    MYCASTCOMPARE(ro->receivedPublishes.size(), 2);

    QVERIFY(std::any_of(ro->receivedPublishes.begin(), ro->receivedPublishes.end(), [](MqttPacket &p) {
        return p.getTopic() == "boo";
    }));

    QVERIFY(std::any_of(ro->receivedPublishes.begin(), ro->receivedPublishes.end(), [](MqttPacket &p) {
        return p.getTopic() == "generated/topic";
            }));
}

void MainTests::testChangePublish()
{
    std::vector<ProtocolVersion> versions { ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5 };

    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    for (ProtocolVersion &version : versions)
    {
        FlashMQTestClient sender;
        sender.start();
        sender.connectClient(version, false, 120);

        FlashMQTestClient receiver;
        receiver.start();
        receiver.connectClient(version, false, 100);
        receiver.subscribe("#", 2);

        FlashMQTestClient receiver_of_pattern;
        receiver_of_pattern.start();
        receiver_of_pattern.connectClient(version, false, 100);
        receiver_of_pattern.subscribe("changed", 2);

        sender.publish("changeme", "hello", 1);

        receiver.waitForMessageCount(1);

        {
            auto ro = receiver.receivedObjects.lock();
            MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
            MYCASTCOMPARE(ro->receivedPublishes.front().getTopic(), "changed");
            MYCASTCOMPARE(ro->receivedPublishes.front().getPayloadCopy(), "hello");
            MYCASTCOMPARE(ro->receivedPublishes.front().getQos(), 2);
        }

        receiver_of_pattern.waitForMessageCount(1);

        {
            auto ro = receiver_of_pattern.receivedObjects.lock();
            MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
            MYCASTCOMPARE(ro->receivedPublishes.front().getTopic(), "changed");
            MYCASTCOMPARE(ro->receivedPublishes.front().getPayloadCopy(), "hello");
            MYCASTCOMPARE(ro->receivedPublishes.front().getQos(), 2);
        }
    }
}

void MainTests::testPluginOnDisconnect()
{
    std::vector<ProtocolVersion> versions { ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5 };

    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient receiver;
    receiver.start();
    receiver.connectClient(ProtocolVersion::Mqtt5);
    receiver.subscribe("#", 0);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5);
    client.disconnect(ReasonCodes::Success);

    receiver.waitForMessageCount(1);
    {
        auto ro = receiver.receivedObjects.lock();
        MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
        QCOMPARE(ro->receivedPublishes.front().getTopic(), "disconnect/confirmed");
    }
}

void MainTests::testPluginGetClientAddress()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient receiver;
    receiver.start();
    receiver.connectClient(ProtocolVersion::Mqtt5);
    receiver.subscribe("getaddresstest/#", 0);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.username = "getaddress";
    });

    try
    {
        receiver.waitForMessageCount(2);
    }
    catch(std::exception &e)
    {
        auto ro = receiver.receivedObjects.lock();
        MYCASTCOMPARE(ro->receivedPublishes.size(), 2);
    }

    auto ro = receiver.receivedObjects.lock();

    QCOMPARE(ro->receivedPublishes[0].getTopic(), "getaddresstest/address");
    QCOMPARE(ro->receivedPublishes[0].getPayloadCopy(), "127.0.0.1");

    QCOMPARE(ro->receivedPublishes[1].getTopic(), "getaddresstest/family");
    QCOMPARE(ro->receivedPublishes[1].getPayloadCopy(), "AF_INET");
}

void MainTests::testPluginMainInit()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    sender.start();
    sender.connectClient(ProtocolVersion::Mqtt5);

    FlashMQTestClient receiver;
    receiver.start();
    receiver.connectClient(ProtocolVersion::Mqtt5, false, 120);
    receiver.subscribe("#", 2);

    sender.publish("check_main_init_presence", "booboo", 0);

    receiver.waitForMessageCount(1);

    auto ro = receiver.receivedObjects.lock();

    MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
    MYCASTCOMPARE(ro->receivedPublishes.front().getTopic(), "check_main_init_presence_confirmed");
}

void MainTests::testAsyncCurl()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, false, 120, [](Connect &connect) {
        connect.username = "curl";
        connect.password = "boo";
    });

    auto ro = client.receivedObjects.lock();
    QVERIFY(ro->receivedPackets.size() == 1);
    ConnAckData connAckData = ro->receivedPackets.front().parseConnAckData();
    QVERIFY(connAckData.reasonCode == ReasonCodes::Success);
}

void MainTests::testSubscribeWithoutRetainedDelivery()
{
    // Control case without plugin loaded.
    {
        FlashMQTestClient sender;
        FlashMQTestClient receiver;

        sender.start();
        receiver.start();

        const std::string payload = "retained payload";
        const std::string topic = "retaintopic/one/two/three";

        sender.connectClient(ProtocolVersion::Mqtt5);

        Publish pub1(topic, payload, 0);
        pub1.retain = true;
        sender.publish(pub1);

        receiver.connectClient(ProtocolVersion::Mqtt5, true, 0, [] (Connect &connect){
            connect.clientid = "success_without_retained_delivery";
        });
        receiver.subscribe(topic, 0);

        receiver.waitForMessageCount(1);
        auto ro = receiver.receivedObjects.lock();
        MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
    }

    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string payload = "retained payload";
    const std::string topic = "retaintopic/one/two/three";

    sender.connectClient(ProtocolVersion::Mqtt5);

    Publish pub1(topic, payload, 0);
    pub1.retain = true;
    sender.publish(pub1);

    receiver.connectClient(ProtocolVersion::Mqtt5, true, 0, [] (Connect &connect){
        connect.clientid = "success_without_retained_delivery";
    });
    receiver.subscribe(topic, 0);

    usleep(250000);

    {
        auto ro = receiver.receivedObjects.lock();
        QVERIFY(ro->receivedPublishes.empty());
    }

    sender.publish("retaintopic/one/two/three", "on-line payload", 0);

    receiver.waitForMessageCount(1);

    {
        auto ro = receiver.receivedObjects.lock();
        MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
        QVERIFY(ro->receivedPublishes.front().getPayloadView() == "on-line payload");
    }
}

void MainTests::testDontUpgradeWildcardDenyMode()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.writeLine("minimum_wildcard_subscription_depth 2");
    confFile.writeLine("wildcard_subscription_deny_mode deny_retained_only");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string payload = "retained payload";
    const std::string topic = "retaintopic/one/two/three";

    sender.connectClient(ProtocolVersion::Mqtt5);

    Publish pub1(topic, payload, 0);
    pub1.retain = true;
    sender.publish(pub1);

    receiver.connectClient(ProtocolVersion::Mqtt5);
    receiver.subscribe("#", 0);

    usleep(250000);

    {
        auto ro = receiver.receivedObjects.lock();
        QVERIFY(ro->receivedPublishes.empty());
    }

    sender.publish("retaintopic/one/two/three", "on-line payload", 0);

    receiver.waitForMessageCount(1);

    {
        auto ro = receiver.receivedObjects.lock();
        MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
        QVERIFY(ro->receivedPublishes.front().getPayloadView() == "on-line payload");
    }
}

void MainTests::testAlsoDontApproveOnErrorInPluginWithWildcardDenyMode()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.writeLine("minimum_wildcard_subscription_depth 2");
    confFile.writeLine("wildcard_subscription_deny_mode deny_retained_only");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string payload = "retained payload";
    const std::string topic = "retaintopic/one/two/three";

    sender.connectClient(ProtocolVersion::Mqtt5);

    Publish pub1(topic, payload, 0);
    pub1.retain = true;
    sender.publish(pub1);

    receiver.connectClient(ProtocolVersion::Mqtt5, true, 0, [] (Connect &connect){
        connect.clientid = "return_error";
    });

    bool suback_errored = false;

    try
    {
        receiver.subscribe("#", 0);
    }
    catch (SubAckIsError &ex)
    {
        suback_errored = true;
    }

    QVERIFY(suback_errored);

    usleep(250000);

    {
        auto ro = receiver.receivedObjects.lock();
        QVERIFY(ro->receivedPublishes.empty());
    }

    sender.publish("retaintopic/one/two/three", "on-line payload", 0);

    usleep(250000);

    {
        auto ro = receiver.receivedObjects.lock();
        QVERIFY(ro->receivedPublishes.empty());
    }
}

void MainTests::testDenyWildcardSubscription()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.writeLine("minimum_wildcard_subscription_depth 2");
    confFile.writeLine("wildcard_subscription_deny_mode deny_all");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string payload = "retained payload";
    const std::string topic = "retaintopic/one/two/three";

    sender.connectClient(ProtocolVersion::Mqtt5);

    Publish pub1(topic, payload, 0);
    pub1.retain = true;
    sender.publish(pub1);

    receiver.connectClient(ProtocolVersion::Mqtt5, true, 0, [] (Connect &connect){
        connect.clientid = "success_without_retained_delivery";
    });

    bool suback_errored = false;

    try
    {
        receiver.subscribe("bla/#", 0);
    }
    catch (SubAckIsError &ex)
    {
        suback_errored = true;
    }

    QVERIFY(suback_errored);
}

void MainTests::testUserPropertiesPresent()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string payload = "werwer payload";
    const std::string topic = "test_user_property";

    sender.connectClient(ProtocolVersion::Mqtt5);

    receiver.connectClient(ProtocolVersion::Mqtt5, true, 0, [] (Connect &connect){
        connect.clientid = "qq5HD9s9VDomlF2l";
    });
    receiver.subscribe(topic, 0);

    Publish pub1(topic, payload, 0);
    pub1.addUserProperty("myprop", "myval");
    sender.publish(pub1);

    receiver.waitForMessageCount(1);

    auto ro = receiver.receivedObjects.lock();

    MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
    QVERIFY(ro->receivedPublishes.front().getPayloadView() == payload);
    QVERIFY(ro->receivedPublishes.front().getUserProperties() != nullptr);

    auto props = ro->receivedPublishes.front().getUserProperties();
    QVERIFY(std::any_of(props->begin(), props->end(), [](std::pair<std::string, std::string> &p) {
        return p.first == "myprop" && p.second == "myval";
    }));
}

void MainTests::testPublishInThread()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    sender.connectClient(ProtocolVersion::Mqtt5);

    receiver.connectClient(ProtocolVersion::Mqtt5);
    receiver.subscribe("topic/from/thread", 0);

    Publish pub1("publish_in_thread", "dummy", 0);
    sender.publish(pub1);

    receiver.waitForMessageCount(1);

    auto ro = receiver.receivedObjects.lock();
    MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
    QVERIFY(ro->receivedPublishes.front().getPayloadView() == "payload from thread");
}












================================================
FILE: FlashMQTests/retaintests.cpp
================================================
#include "maintests.h"
#include "flashmqtestclient.h"
#include "conffiletemp.h"
#include "testhelpers.h"

#include "utils.h"
#include "retainedmessagesdb.h"

void MainTests::test_retained()
{
    std::vector<ProtocolVersion> protocols {ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5};

    for (const ProtocolVersion senderVersion : protocols)
    {
        for (const ProtocolVersion receiverVersion : protocols)
        {
            FlashMQTestClient sender;
            FlashMQTestClient receiver;

            sender.start();
            receiver.start();

            const std::string payload = "We are testing";
            const std::string topic = "retaintopic";

            sender.connectClient(senderVersion);

            Publish pub1(topic, payload, 0);
            pub1.retain = true;
            sender.publish(pub1);

            Publish pub2("dummy2", "Nobody sees this", 0);
            pub2.retain = true;
            sender.publish(pub2);

            receiver.connectClient(receiverVersion);
            receiver.subscribe("dummy", 0);
            receiver.subscribe(topic, 0);

            receiver.waitForMessageCount(1);

            {
                auto ro = receiver.receivedObjects.lock();

                MYCASTCOMPARE(ro->receivedPublishes.size(), 1);

                MqttPacket &msg = ro->receivedPublishes.front();
                QCOMPARE(msg.getPayloadCopy(), payload);
                QCOMPARE(msg.getTopic(), topic);
                QVERIFY(msg.getRetain());

                ro->clear();
            }

            sender.publish(pub1);
            receiver.waitForMessageCount(1);

            {
                auto ro = receiver.receivedObjects.lock();

                QVERIFY2(ro->receivedPublishes.size() == 1, "There must be one message in the received list");
                MqttPacket &msg2 = ro->receivedPublishes.front();
                QCOMPARE(msg2.getPayloadCopy(), payload);
                QCOMPARE(msg2.getTopic(), topic);
                QVERIFY2(!msg2.getRetain(), "Getting a retained message while already being subscribed must be marked as normal, not retain.");
            }
        }
    }
}

/**
 * @brief MainTests::test_retained_double_set Test incepted because of different locking paths in first tree node and second tree node.
 */
void MainTests::test_retained_double_set()
{
    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string topic = "one/two/three";

    sender.connectClient(ProtocolVersion::Mqtt5);

    {
        Publish pub("one", "dummy node creator", 0);
        pub.retain = true;
        pub.qos = 1;
        sender.publish(pub);
    }

    Publish pub1(topic, "nobody sees this", 0);
    pub1.retain = true;
    pub1.qos = 1;
    sender.publish(pub1);

    pub1.payload = "We are setting twice";
    sender.publish(pub1);

    {
        // Confirm the retained messages are present.
        FlashMQTestClient c;
        c.start();
        c.connectClient(ProtocolVersion::Mqtt5);
        c.subscribe("one/#", 0);
        c.waitForMessageCount(2);
    }

    receiver.connectClient(ProtocolVersion::Mqtt5);
    receiver.subscribe("one/#", 0);

    receiver.waitForMessageCount(2);

    MYCASTCOMPARE(receiver.receivedObjects.lock()->receivedPublishes.size(), 2);

    {
        auto ro = receiver.receivedObjects.lock();
        auto msg = std::find_if(ro->receivedPublishes.begin(), ro->receivedPublishes.end(), [](const MqttPacket &p) {return p.getTopic() == "one";});
        FMQ_VERIFY(msg != ro->receivedPublishes.end());
        QCOMPARE(msg->getPayloadCopy(), "dummy node creator");
        QCOMPARE(msg->getTopic(), "one");
        QVERIFY(msg->getRetain());
    }

    {
        auto ro = receiver.receivedObjects.lock();
        auto msg2 = std::find_if(ro->receivedPublishes.begin(), ro->receivedPublishes.end(), [&](const MqttPacket &p) {return p.getTopic() == topic;});
        FMQ_VERIFY(msg2 != ro->receivedPublishes.end());
        QCOMPARE(msg2->getPayloadCopy(), "We are setting twice");
        QCOMPARE(msg2->getTopic(), topic);
        QVERIFY(msg2->getRetain());
    }
}

/**
 * @brief MainTests::test_retained_disabled Copied from test_retained and adjusted
 */
void MainTests::test_retained_mode_drop()
{
    ConfFileTemp confFile;
    confFile.writeLine("allow_anonymous yes");
    confFile.writeLine("retained_messages_mode drop");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    std::vector<ProtocolVersion> protocols {ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5};

    for (const ProtocolVersion senderVersion : protocols)
    {
        for (const ProtocolVersion receiverVersion : protocols)
        {
            FlashMQTestClient sender;
            FlashMQTestClient receiver;

            sender.start();
            receiver.start();

            const std::string payload = "We are testing";
            const std::string topic = "retaintopic";

            sender.connectClient(senderVersion);

            Publish pub1(topic, payload, 0);
            pub1.retain = true;
            sender.publish(pub1);

            Publish pub2("dummy2", "Nobody sees this", 0);
            pub2.retain = true;
            sender.publish(pub2);

            receiver.connectClient(receiverVersion);
            receiver.subscribe("dummy", 0);
            receiver.subscribe(topic, 0);

            usleep(250000);

            receiver.waitForMessageCount(0);

            {
                auto ro = receiver.receivedObjects.lock();
                QVERIFY2(ro->receivedPublishes.empty(), "In drop mode, retained publishes should be stored as retained messages.");
            }

            receiver.clearReceivedLists();

            sender.publish(pub1);

            usleep(250000);
            receiver.waitForMessageCount(0);

            {
                auto ro = receiver.receivedObjects.lock();
                QVERIFY(ro->receivedPublishes.empty());
            }
        }
    }
}

/**
 * @brief MainTests::test_retained_mode_downgrade copied from test_retained and adjusted
 */
void MainTests::test_retained_mode_downgrade()
{
    ConfFileTemp confFile;
    confFile.writeLine("allow_anonymous yes");
    confFile.writeLine("retained_messages_mode downgrade");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    std::vector<ProtocolVersion> protocols {ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5};

    for (const ProtocolVersion senderVersion : protocols)
    {
        for (const ProtocolVersion receiverVersion : protocols)
        {
            FlashMQTestClient sender;
            FlashMQTestClient receiver;

            sender.start();
            receiver.start();

            const std::string payload = "We are testing";
            const std::string topic = "retaintopic";

            sender.connectClient(senderVersion);

            Publish pub1(topic, payload, 0);
            pub1.retain = true;
            sender.publish(pub1);

            Publish pub2("dummy2", "Nobody sees this", 0);
            pub2.retain = true;
            sender.publish(pub2);

            receiver.connectClient(receiverVersion);
            receiver.subscribe("dummy", 0);
            receiver.subscribe(topic, 0, false, true);

            usleep(250000);

            receiver.waitForMessageCount(0);

            {
                auto ro = receiver.receivedObjects.lock();
                QVERIFY2(ro->receivedPublishes.empty(), "In downgrade mode, retained publishes should not be stored as retained messages.");
            }

            receiver.clearReceivedLists();

            sender.publish(pub1);
            receiver.waitForMessageCount(1);

            {
                auto ro = receiver.receivedObjects.lock();
                MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
                MqttPacket &msg2 = ro->receivedPublishes.front();
                QCOMPARE(msg2.getPayloadCopy(), payload);
                QCOMPARE(msg2.getTopic(), topic);
                QVERIFY2(!msg2.getRetain(), "Getting a retained message while already being subscribed must be marked as normal, not retain.");
            }
        }
    }
}

/**
 * @brief Tests 'enabled_without_retaining', which relays the message with the 'retain' flag set, but does not retain.
 */
void MainTests::test_retained_mode_no_retain()
{
    ConfFileTemp confFile;
    confFile.writeLine("allow_anonymous yes");
    confFile.writeLine("retained_messages_mode enabled_without_retaining");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string payload = "We are testing";
    const std::string topic = "retaintopic/foo/bar";

    sender.connectClient(ProtocolVersion::Mqtt5);

    receiver.connectClient(ProtocolVersion::Mqtt5);
    receiver.subscribe(topic, 0, false, true);

    Publish pub1(topic, payload, 0);
    pub1.retain = true;
    sender.publish(pub1);

    receiver.waitForMessageCount(1);

    {
        auto ro = receiver.receivedObjects.lock();

        QVERIFY(ro->receivedPublishes.size() == 1);

        MqttPacket &msg = ro->receivedPublishes.front();
        QCOMPARE(msg.getPayloadCopy(), payload);
        QCOMPARE(msg.getTopic(), topic);
        QVERIFY2(msg.getRetain(), "We were supposed to have seen the retain flag, because 'retain as published' was on.");
    }

    FlashMQTestClient late_receiver;
    late_receiver.start();
    late_receiver.connectClient(ProtocolVersion::Mqtt5);
    late_receiver.subscribe("#", 0);

    usleep(250000);

    receiver.waitForMessageCount(0);
    QVERIFY2(late_receiver.receivedObjects.lock()->receivedPublishes.empty(), "In enabled_without_retaining mode, retained publishes should not be stored as retained messages.");
}

void MainTests::test_retained_changed()
{
    FlashMQTestClient sender;
    sender.start();
    sender.connectClient(ProtocolVersion::Mqtt311);

    const std::string topic = "retaintopic";

    Publish p(topic, "We are testing", 0);
    p.retain = true;
    p.qos = 1;
    sender.publish(p);

    p.payload = "Changed payload";
    sender.publish(p);

    FlashMQTestClient receiver;
    receiver.start();
    receiver.connectClient(ProtocolVersion::Mqtt5);
    receiver.subscribe(topic, 0);

    receiver.waitForMessageCount(1);

    auto ro = receiver.receivedObjects.lock();

    MYCASTCOMPARE(ro->receivedPublishes.size(), 1);

    MqttPacket &pack = ro->receivedPublishes.front();
    QCOMPARE(pack.getPayloadCopy(), p.payload);
    QVERIFY(pack.getRetain());
}

void MainTests::test_retained_removed()
{
    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    std::string payload = "We are testing";
    std::string topic = "retaintopic";

    sender.connectClient(ProtocolVersion::Mqtt311);

    Publish pub1(topic, payload, 1);
    pub1.retain = true;
    sender.publish(pub1);

    pub1.payload = "";

    sender.publish(pub1);

    receiver.connectClient(ProtocolVersion::Mqtt311);
    receiver.subscribe(topic, 0);
    usleep(100000);
    receiver.waitForMessageCount(0);

    auto ro = receiver.receivedObjects.lock();
    QVERIFY2(ro->receivedPublishes.empty(), "We erased the retained message. We shouldn't have received any.");
}

/**
 * @brief MainTests::test_retained_tree tests a bug I found, where '+/+' yields different results than '#', where it should be the same.
 */
void MainTests::test_retained_tree()
{
    FlashMQTestClient sender;
    sender.start();

    std::string payload = "We are testing";
    const std::string topic1 = "TopicA/B";
    const std::string topic2 = "Topic/C";
    const std::string topic3 = "TopicB/C";
    const std::list<std::string> topics {topic1, topic2, topic3};

    sender.connectClient(ProtocolVersion::Mqtt311);

    Publish p1(topic1, payload, 0);
    p1.retain = true;
    sender.publish(p1);

    Publish p2(topic2, payload, 0);
    p2.retain = true;
    sender.publish(p2);

    Publish p3(topic3, payload, 0);
    p3.retain = true;
    sender.publish(p3);

    FlashMQTestClient receiver;
    receiver.start();
    receiver.connectClient(ProtocolVersion::Mqtt5);

    receiver.subscribe("+/+", 0);

    receiver.waitForMessageCount(3);

    auto ro = receiver.receivedObjects.lock();

    QCOMPARE(ro->receivedPublishes.size(), topics.size());

    for (const std::string &s : topics)
    {
        bool r = std::any_of(ro->receivedPublishes.begin(), ro->receivedPublishes.end(), [&](MqttPacket &pack)
        {
            return pack.getTopic() == s && pack.getPayloadCopy() == payload;
        });

        QVERIFY2(r, formatString("%s not found in retained messages.", s.c_str()).c_str());
    }

}

void MainTests::test_retained_global_expire()
{
    std::vector<ProtocolVersion> versions { ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5 };

    ConfFileTemp confFile;
    confFile.writeLine("allow_anonymous yes");
    confFile.writeLine("expire_retained_messages_after_seconds 1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    std::vector<ProtocolVersion> protocols {ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5};

    for (const ProtocolVersion senderVersion : protocols)
    {
        for (const ProtocolVersion receiverVersion : protocols)
        {
            FlashMQTestClient sender;
            FlashMQTestClient receiver;

            sender.start();
            receiver.start();

            sender.connectClient(senderVersion);

            Publish pub1("retaintopic/1", "We are testing", 0);
            pub1.retain = true;
            sender.publish(pub1);

            Publish pub2("retaintopic/2", "asdf", 0);
            pub2.retain = true;
            sender.publish(pub2);

            usleep(2000000);

            receiver.connectClient(receiverVersion);
            receiver.subscribe("#", 0);

            usleep(500000);
            receiver.waitForMessageCount(0);

            auto ro = receiver.receivedObjects.lock();
            MYCASTCOMPARE(ro->receivedPublishes.size(), 0);
        }
    }
}

void MainTests::test_retained_per_message_expire()
{
    std::vector<ProtocolVersion> versions { ProtocolVersion::Mqtt311, ProtocolVersion::Mqtt5 };

    ConfFileTemp confFile;
    confFile.writeLine("allow_anonymous yes");
    confFile.writeLine("expire_retained_messages_after_seconds 10");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    sender.connectClient(ProtocolVersion::Mqtt5);

    Publish pub1("retaintopic/1", "We are testing", 0);
    pub1.retain = true;
    sender.publish(pub1);

    Publish pub2("retaintopic/2", "asdf", 0);
    pub2.retain = true;
    pub2.setExpireAfter(1);
    sender.publish(pub2);

    usleep(2000000);

    receiver.connectClient(ProtocolVersion::Mqtt5);
    receiver.subscribe("#", 0);

    usleep(500000);
    receiver.waitForMessageCount(1);

    auto ro = receiver.receivedObjects.lock();

    MYCASTCOMPARE(ro->receivedPublishes.size(), 1);

    MqttPacket &msg = ro->receivedPublishes.front();
    QCOMPARE(msg.getPayloadCopy(), "We are testing");
    QCOMPARE(msg.getTopic(), "retaintopic/1");
    QVERIFY(msg.getRetain());
}

void MainTests::test_retained_tree_purging()
{
    std::shared_ptr<SubscriptionStore> store = globals->subscriptionStore;

    int toDeleteCount = 0;

    for (int i = 0; i < 10; i++)
    {
        for (int j = 0; j < 10; j++)
        {
            std::string topic = formatString("retain%d/bla%d/asdf", i, j);
            Publish pub(topic, "willnotexpire", 0);

            if (i % 2 == 0)
            {
                pub.setExpireAfter(1);
                pub.payload = "willexpire";

                toDeleteCount++;
            }

            std::vector<std::string> subtopics = splitTopic(topic);
            store->setRetainedMessage(pub, subtopics);
        }
    }

    {
        Publish pubStray("retain0/bla5", "willnotexpire", 0);
        std::vector<std::string> subtopics = splitTopic(pubStray.topic);
        store->setRetainedMessage(pubStray, subtopics);
    }

    const int beforeCount = store->getAllRetainedMessages().size();

    usleep(2000000);

    store->expireRetainedMessages();

    std::vector<RetainedMessage> list;
    const std::chrono::time_point<std::chrono::steady_clock> limit = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000);
    std::deque<std::weak_ptr<RetainedMessageNode>> deferred;
    store->getRetainedMessages(store->retainedMessagesRoot.get(), list, limit, 100000, deferred);

    QVERIFY(deferred.empty());

    QVERIFY(std::none_of(list.begin(), list.end(), [](RetainedMessage &rm) {
        return rm.publish.payload == "willexpire";
    }));

    QVERIFY(std::all_of(list.begin(), list.end(), [](RetainedMessage &rm) {
        return rm.publish.payload == "willnotexpire";
    }));

    MYCASTCOMPARE(store->getAllRetainedMessages().size(), beforeCount - toDeleteCount);
}

void MainTests::testRetainAsPublished()
{
    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5);

    FlashMQTestClient client2;
    client2.start();
    client2.connectClient(ProtocolVersion::Mqtt5);

    FlashMQTestClient client3;
    client3.start();
    client3.connectClient(ProtocolVersion::Mqtt5);

    client2.subscribe("mytopic", 1, false, false);
    client.subscribe("mytopic", 1, false, true);
    client3.subscribe("mytopic", 1, false, false);

    try
    {
        Publish pub("mytopic", "mypayload", 1);
        pub.retain = true;
        client.publish(pub);
    }
    catch (std::exception &ex)
    {
        QVERIFY2(false, ex.what());
    }

    try
    {
        client.waitForMessageCount(1);
    }
    catch (std::exception &ex)
    {
        QVERIFY2(false, ex.what());
    }

    auto ro = client.receivedObjects.lock();
    MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
    const MqttPacket &first = ro->receivedPublishes.front();
    QVERIFY(first.getRetain());
}

void MainTests::testRetainAsPublishedNegative()
{
    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5);

    client.subscribe("mytopic", 1, false, false);

    try
    {
        Publish pub("mytopic", "mypayload", 1);
        pub.retain = true;
        client.publish(pub);
    }
    catch (std::exception &ex)
    {
        QVERIFY2(false, ex.what());
    }

    try
    {
        client.waitForMessageCount(1);
    }
    catch (std::exception &ex)
    {
        QVERIFY2(false, ex.what());
    }

    auto ro = client.receivedObjects.lock();
    MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
    const MqttPacket &first = ro->receivedPublishes.front();
    QVERIFY(!first.getRetain());
}

/**
 * @brief MainTests::testRetainedParentOfWildcard tests whether subscribing to 'one/two/three/four/#' gives you 'one/two/three/four'.
 */
void MainTests::testRetainedParentOfWildcard()
{
    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string payload = "We are testing testRetainedParentOfWildcard";
    const std::string publish_topic = "one/two/three/four";

    sender.connectClient(ProtocolVersion::Mqtt5);

    Publish pub1(publish_topic, payload, 0);
    pub1.retain = true;
    sender.publish(pub1);

    receiver.connectClient(ProtocolVersion::Mqtt5);
    receiver.subscribe("dummy", 0);
    receiver.subscribe("one/two/three/four/#", 0);

    try
    {
        receiver.waitForMessageCount(1);
    }
    catch (std::exception &ex)
    {
        QVERIFY2(false, "Exception happened. Likely waited for retained messages, but none received.");
    }

    auto ro = receiver.receivedObjects.lock();

    MYCASTCOMPARE(ro->receivedPublishes.size(), 1);

    MqttPacket &msg = ro->receivedPublishes.front();
    QCOMPARE(msg.getPayloadCopy(), payload);
    QCOMPARE(msg.getTopic(), publish_topic);
    QVERIFY(msg.getRetain());
}

void MainTests::testRetainedWildcard()
{
    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string payload = "We are testing testRetainedWildcard";
    const std::string publish_topic = "one/two/three/four";

    sender.connectClient(ProtocolVersion::Mqtt5);

    Publish pub1(publish_topic, payload, 0);
    pub1.retain = true;
    sender.publish(pub1);

    Publish pub2("publish/into/nothing", payload, 0);
    pub2.retain = true;
    sender.publish(pub2);

    receiver.connectClient(ProtocolVersion::Mqtt5);
    receiver.subscribe("dummy", 0);
    receiver.subscribe("one/two/three/#", 0);

    try
    {
        receiver.waitForMessageCount(1);
    }
    catch (std::exception &ex)
    {
        QVERIFY2(false, "Exception happened. Likely waited for retained messages, but none received.");
    }

    auto ro = receiver.receivedObjects.lock();

    MYCASTCOMPARE(ro->receivedPublishes.size(), 1);

    MqttPacket &msg = ro->receivedPublishes.front();
    QCOMPARE(msg.getPayloadCopy(), payload);
    QCOMPARE(msg.getTopic(), publish_topic);
    QVERIFY(msg.getRetain());
}

/**
 * @brief MainTests::testRetainedAclReadCheck tests the manipulation of the retain bit in the original incoming packet.
 *
 * This has to do with the optimization in CopyFactory, that under certain conditions, the original packet's vector is
 * just used to write to the client.
 */
void MainTests::testRetainedAclReadCheck()
{
    ConfFileTemp confFile;
    confFile.writeLine("plugin plugins/libtest_plugin.so.0.0.1");
    confFile.closeFile();

    std::vector<std::string> args {"--config-file", confFile.getFilePath()};

    cleanup();
    init(args);

    FlashMQTestClient client;
    client.start();
    client.connectClient(ProtocolVersion::Mqtt5, true, 30, [](Connect &connect) {
        connect.clientid = "test_user_with_retain_as_published_v8sIeCvI";
    });

    client.subscribe("mytopic", 1, false, true);

    FlashMQTestClient client2;
    client2.start();
    client2.connectClient(ProtocolVersion::Mqtt5, true, 30, [](Connect &connect) {
        connect.clientid = "test_user_without_retain_as_published_CswU21YA";
    });

    client2.subscribe("mytopic", 1, false, false);

    FlashMQTestClient publish_client;
    publish_client.start();
    publish_client.connectClient(ProtocolVersion::Mqtt5);

    Publish pub("mytopic", "mypayload", 1);
    pub.retain = true;
    publish_client.publish(pub);

    try
    {
        client.waitForMessageCount(1);
        client2.waitForMessageCount(1);
    }
    catch (std::exception &ex)
    {
        QVERIFY2(false, ex.what());
    }

    auto ro = client.receivedObjects.lock();
    auto ro2 = client2.receivedObjects.lock();

    MYCASTCOMPARE(ro->receivedPublishes.size(), 1);
    MYCASTCOMPARE(ro2->receivedPublishes.size(), 1);
}

void MainTests::testRetainHandlingDontGiveRetain()
{
    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string payload = "retained payload";
    const std::string topic = "retaintopic/one/two/three";

    sender.connectClient(ProtocolVersion::Mqtt5);

    Publish pub1(topic, payload, 0);
    pub1.retain = true;
    sender.publish(pub1);

    receiver.connectClient(ProtocolVersion::Mqtt5, true, 0);
    receiver.subscribe(topic, 0, false, false, 0, RetainHandling::DoNotSendRetainedMessages);

    usleep(250000);
    auto ro = receiver.receivedObjects.lock();
    QVERIFY(ro->receivedPublishes.empty());
}

void MainTests::testRetainHandlingDontGiveRetainOnExistingSubscription()
{
    FlashMQTestClient sender;
    FlashMQTestClient receiver;

    sender.start();
    receiver.start();

    const std::string payload = "retained payload";
    const std::string topic = "retaintopic/one/two/three";

    sender.connectClient(ProtocolVersion::Mqtt5);

    Publish pub1(topic, payload, 0);
    pub1.retain = true;
    sender.publish(pub1);

    {
        receiver.connectClient(ProtocolVersion::Mqtt5, true, 0);
        receiver.subscribe(topic, 0, false, false, 0, RetainHandling::SendRetainedMessagesAtNewSubscribeOnly);
        receiver.waitForMessageCount(1);
        auto ro = receiver.receivedObjects.lock();
        QVERIFY(ro->receivedPublishes.size() == 1);
    }

    receiver.clearReceivedLists();

    receiver.subscribe(topic, 1, false, false, 0, RetainHandling::SendRetainedMessagesAtNewSubscribeOnly);

    usleep(250000);
    auto ro = receiver.receivedObjects.lock();
    QVERIFY(ro->receivedPublishes.empty());
}




================================================
FILE: FlashMQTests/run-make-from-ci.sh
================================================
#!/bin/bash

extra_configs=()
compiler="g++"

while [ -n "$*" ]; do
  flag=$1
  value=$2

  case "$flag" in
    "--compiler")
      compiler="$value"
      shift
    ;;
    "--extra-config")
      extra_configs+=("$value")
      shift
    ;;
    "--")
      shift
      break
    ;;
    *)
      echo -e "unknown option $flag\n"
      exit 1
    ;;
  esac

  shift
done

set -u

for item in "${extra_configs[@]}"; do
  extra_configs_expanded=("${extra_configs_expanded[@]}" "-D" "$item")
done

CXX="$compiler" cmake -DCMAKE_BUILD_TYPE=Debug -S . -B buildtests "${extra_configs_expanded[@]}"

nprocs=4
if _nprocs=$(nproc); then
  nprocs="$_nprocs"
fi

make -C buildtests -j "$nprocs"


================================================
FILE: FlashMQTests/run-tests-from-ci.sh
================================================
#!/bin/bash

STDERR_LOG=$(mktemp)

cd buildtests || exit 1

# Using --abort-on-first-fail because the output can be hard to find in CI when it's swamped out by the rest.
if ./flashmq-tests --abort-on-first-fail 2> "$STDERR_LOG" ; then
  echo -e '\033[01;32mSUCCESS!\033[00m'
else
  echo -e '\033[01;31mBummer\033[00m'
  echo -e "\n\nTail of stderr:\n\n"
  tail -n 200 "$STDERR_LOG"
  exit 1
fi


================================================
FILE: FlashMQTests/sharedsubscriptionstests.cpp
================================================
#include "maintests.h"
#include "testhelpers.h"
#include "flashmqtestclient.h"
#include "flashmqtempdir.h"
#include "conffiletemp.h"

#include "utils.h"
#include "threadglobals.h"

void MainTests::testSharedSubscribersUnit()
{
    Settings settings;
    std::shared_ptr<PluginLoader> pluginLoader = std::make_shared<PluginLoader>();
    std::shared_ptr<ThreadData> t(new ThreadData(0, settings, pluginLoader, std::weak_ptr<MainApp>()));

    ThreadGlobals::assignThreadData(t);

    std::shared_ptr<Client> c1(new Client(ClientType::Normal, -1, t, FmqSsl(), ConnectionProtocol::Mqtt, HaProxyMode::Off, nullptr, settings, false));
    c1->setClientProperties(ProtocolVersion::Mqtt5, "clientid1", {}, "user1", true, 60);

    std::shared_ptr<Client> c2(new Client(ClientType::Normal, -1, t, FmqSsl(), ConnectionProtocol::Mqtt, HaProxyMode::Off, nullptr, settings, false));
    c2->setClientProperties(ProtocolVersion::Mqtt5, "clientid2", {}, "user2", true, 60);

    std::shared_ptr<Client> c3(new Client(ClientType::Normal, -1, t, FmqSsl(), ConnectionProtocol::Mqtt, HaProxyMode::Off, nullptr, settings, false));
    c3->setClientProperties(ProtocolVersion::Mqtt5, "clientid3", {}, "user3", true, 60);

    std::shared_ptr<Session> ses1 = std::make_shared<Session>(c1->getClientId(), c1->getUsername(), std::optional<std::string>());
    ses1->assignActiveConnection(c1);

    std::shared_ptr<Session> ses2 = std::make_shared<Session>(c2->getClientId(), c2->getUsername(), std::optional<std::string>());
    ses2->assignActiveConnection(c2);

    std::shared_ptr<Session> ses3 = std::make_shared<Session>(c3->getClientId(), c3->getUsername(), std::optional<std::string>());
    ses3->assignActiveConnection(c3);

    SharedSubscribers s("dummy", {});

    s[ses1->getClientId()].session = ses1;
    MYCASTCOMPARE(s.members.size(), 1);

    s[ses2->getClientId()].session = ses2;
    MYCASTCOMPARE(s.members.size(), 2);

    s[ses3->getClientId()].session = ses3;
    MYCASTCOMPARE(s.members.size(), 3);

    s[ses2->getClientId()].reset();
    MYCASTCOMPARE(s.members.size(), 3);

    QCOMPARE(*s.getNext(), s[ses1->getClientId()]);
    QCOMPARE(*s.getNext(), s[ses3->getClientId()]);

    s.purgeAndReIndex();
    MYCASTCOMPARE(s.members.size(), 2);

    // We still should get the same two active members
    QCOMPARE(*s.getNext(), s[ses1->getClientId()]);
    QCOMPARE(*s.getNext(), s[ses3->getClientId()]);

    s.erase(ses3->getClientId());

    // Now we only have one left
    QCOMPARE(*s.getNext(), s[ses1->getClientId()]);
    QCOMPARE(*s.getNext(), s[ses1->getClientId()]);

    s.erase(ses1->getClientId());
    QVERIFY(!s.empty());
    s.purgeAndReIndex();
    QVERIFY(s.empty());
}

void MainTests::testSharedSubscribers()
{
    FlashMQTestClient receiver1;
    receiver1.start();
    receiver1.connectClient(ProtocolVersion::Mqtt5);

    FlashMQTestClient receiver2;
    receiver2.start();
    receiver2.connectClient(ProtocolVersion::Mqtt5);

    receiver1.subscribe("$share/ahTahHu5/one/two/three", 1);
    receiver2.subscribe("$share/ahTahHu5/one/two/three", 1);

    FlashMQTestClient sender;
    sender.start();
    sender.connectClient(ProtocolVersion::Mqtt5);

    sender.publish("one/two/three", "rainy day", 1);
    sender.publish("one/two/three", "sunny day", 1);

    receiver1.waitForMessageCount(1);
    receiver2.waitForMessageCount(1);

    {
        auto ro1 = receiver1.receivedObjects.lock();
        auto ro2 = receiver2.receivedObjects.lock();

        MYCASTCOMPARE(ro1->receivedPublishes.size(), 1);
        MYCASTCOMPARE(ro2->receivedPublishes.size(), 1);

        int rain = std::count_if(ro1->receivedPublishes.begin(), ro1->receivedPublishes.end(),
                                 [](const MqttPacket &pack) { return pack.getPayloadCopy() == "rainy day";});
        int sun = std::count_if(ro2->receivedPublishes.begin(), ro2->receivedPublishes.end(),
                                [](const MqttPacket &pack) { return pack.getPayloadCopy() == "sunny day";});

        QCOMPARE(rain, 1);
        QCOMPARE(sun, 1);
    }

    receiver1.unsubscribe("$share/ahTahHu5/one/two/three");

    receiver1.clearReceivedLists();
    receiver2.clearReceivedLists();

    sender.publish("one/two/thre
Download .txt
gitextract_eh9hwlw7/

├── .build-ci.sh
├── .clang-format
├── .clang-format.original
├── .dockerignore
├── .get-os-codename-and-stamp.sh
├── .github/
│   └── workflows/
│       ├── building.yml
│       ├── codestyle.yml
│       ├── docker.yml
│       ├── linting.yml
│       ├── testing.yml
│       └── testing_non_sse.yml
├── .gitignore
├── CMakeLists.shared
├── CMakeLists.txt
├── Dockerfile
├── FlashMQTests/
│   ├── CMakeLists.txt
│   ├── UTF-8-test.txt
│   ├── bridgeprefixtests.cpp
│   ├── conffiletemp.cpp
│   ├── conffiletemp.h
│   ├── configtests.cpp
│   ├── dnstests.cpp
│   ├── filecloser.cpp
│   ├── filecloser.h
│   ├── flashmqtempdir.cpp
│   ├── flashmqtempdir.h
│   ├── main.cpp
│   ├── mainappasfork.cpp
│   ├── mainappasfork.h
│   ├── mainappinthread.cpp
│   ├── mainappinthread.h
│   ├── maintests.cpp
│   ├── maintests.h
│   ├── plugins/
│   │   ├── curlfunctions.cpp
│   │   ├── curlfunctions.h
│   │   ├── test_plugin.cpp
│   │   ├── test_plugin.h
│   │   └── test_plugin.pro
│   ├── plugintests.cpp
│   ├── retaintests.cpp
│   ├── run-make-from-ci.sh
│   ├── run-tests-from-ci.sh
│   ├── sharedsubscriptionstests.cpp
│   ├── subscriptionidtests.cpp
│   ├── testhelpers.cpp
│   ├── testhelpers.h
│   ├── testinitializer.cpp
│   ├── testinitializer.h
│   ├── tst_maintests.cpp
│   ├── utiltests.cpp
│   ├── websockettests.cpp
│   └── willtests.cpp
├── LICENSE
├── README.md
├── acksender.cpp
├── acksender.h
├── acltree.cpp
├── acltree.h
├── backgroundworker.cpp
├── backgroundworker.h
├── bindaddr.cpp
├── bindaddr.h
├── bridgeconfig.cpp
├── bridgeconfig.h
├── bridgeinfodb.cpp
├── bridgeinfodb.h
├── build.sh
├── checkedsharedptr.h
├── checkedweakptr.h
├── cirbuf.cpp
├── cirbuf.h
├── client.cpp
├── client.h
├── clientacceptqueue.cpp
├── clientacceptqueue.h
├── configfileparser.cpp
├── configfileparser.h
├── debian/
│   ├── conffiles
│   ├── flashmq.service
│   ├── postinst
│   ├── postrm
│   ├── preinst
│   └── prerm
├── derivablecounter.cpp
├── derivablecounter.h
├── dnsresolver.cpp
├── dnsresolver.h
├── driftcounter.cpp
├── driftcounter.h
├── enums.h
├── evpencodectxmanager.cpp
├── evpencodectxmanager.h
├── examples/
│   └── plugin_libcurl/
│       ├── CMakeLists.txt
│       ├── LICENSE
│       ├── README.md
│       ├── build.sh
│       ├── src/
│       │   ├── authenticatingclient.cpp
│       │   ├── authenticatingclient.h
│       │   ├── curl_functions.cpp
│       │   ├── curl_functions.h
│       │   ├── plugin_libcurl.cpp
│       │   ├── pluginstate.cpp
│       │   └── pluginstate.h
│       └── vendor/
│           ├── flashmq_plugin.h
│           └── flashmq_public.h
├── exceptions.cpp
├── exceptions.h
├── fdmanaged.cpp
├── fdmanaged.h
├── flags.h
├── flashmq.conf
├── flashmq_plugin.cpp
├── flashmq_plugin.h
├── flashmq_plugin_deprecated.h
├── flashmq_public.h
├── flashmqtestclient.cpp
├── flashmqtestclient.h
├── fmqmain.cpp
├── fmqmain.h
├── fmqsockaddr.cpp
├── fmqsockaddr.h
├── fmqssl.cpp
├── fmqssl.h
├── forward_declarations.h
├── fuzz-helper.sh
├── globals.cpp
├── globals.h
├── globalstats.cpp
├── globalstats.h
├── globber.cpp
├── globber.h
├── haproxy.cpp
├── haproxy.h
├── http.cpp
├── http.h
├── iowrapper.cpp
├── iowrapper.h
├── listener.cpp
├── listener.h
├── lockedsharedptr.h
├── lockedweakptr.cpp
├── lockedweakptr.h
├── logger.cpp
├── logger.h
├── main.cpp
├── mainapp.cpp
├── mainapp.h
├── man/
│   ├── .gitignore
│   ├── Makefile
│   ├── README.md
│   ├── docbook5-refentry-xslt/
│   │   ├── docbook5-refentry-to-html5.xsl
│   │   └── docbook5-refentry-to-manpage.xsl
│   ├── flashmq-docbook5-refentry-to-html5.xsl
│   ├── flashmq-docbook5-refentry-to-manpage.xsl
│   ├── flashmq.1
│   ├── flashmq.1.dbk5
│   ├── flashmq.1.html
│   ├── flashmq.conf.5
│   ├── flashmq.conf.5.dbk5
│   ├── flashmq.conf.5.html
│   ├── refentry.colophon.dbk5
│   └── reference.dbk5
├── mosquittoauthoptcompatwrap.cpp
├── mosquittoauthoptcompatwrap.h
├── mqtt5properties.cpp
├── mqtt5properties.h
├── mqttpacket.cpp
├── mqttpacket.h
├── mutexowned.h
├── network.cpp
├── network.h
├── nocopy.cpp
├── nocopy.h
├── oneinstancelock.cpp
├── oneinstancelock.h
├── packetdatatypes.cpp
├── packetdatatypes.h
├── persistencefile.cpp
├── persistencefile.h
├── persistencefunctions.cpp
├── persistencefunctions.h
├── plugin.cpp
├── plugin.h
├── pluginloader.cpp
├── pluginloader.h
├── publishcopyfactory.cpp
├── publishcopyfactory.h
├── qospacketqueue.cpp
├── qospacketqueue.h
├── queuedtasks.cpp
├── queuedtasks.h
├── release.sh
├── retainedmessage.cpp
├── retainedmessage.h
├── retainedmessagesdb.cpp
├── retainedmessagesdb.h
├── rwlockguard.cpp
├── rwlockguard.h
├── scopedsocket.cpp
├── scopedsocket.h
├── sdnotify.cpp
├── sdnotify.h
├── session.cpp
├── session.h
├── sessionsandsubscriptionsdb.cpp
├── sessionsandsubscriptionsdb.h
├── settings.cpp
├── settings.h
├── sharedsubscribers.cpp
├── sharedsubscribers.h
├── sslctxmanager.cpp
├── sslctxmanager.h
├── subscription.cpp
├── subscription.h
├── subscriptionstore.cpp
├── subscriptionstore.h
├── threaddata.cpp
├── threaddata.h
├── threadglobals.cpp
├── threadglobals.h
├── threadlocalutils.cpp
├── threadlocalutils.h
├── threadlocked.h
├── threadloop.cpp
├── threadloop.h
├── types.cpp
├── types.h
├── unscopedlock.cpp
├── unscopedlock.h
├── utils.cpp
├── utils.h
├── variablebyteint.cpp
├── variablebyteint.h
├── x509manager.cpp
└── x509manager.h
Download .txt
SYMBOL INDEX (461 symbols across 134 files)

FILE: FlashMQTests/bridgeprefixtests.cpp
  function waitForMessagesOverBridge (line 8) | void waitForMessagesOverBridge(FlashMQTestClient &one, FlashMQTestClient...

FILE: FlashMQTests/conffiletemp.h
  function class (line 6) | class ConfFileTemp

FILE: FlashMQTests/filecloser.h
  function class (line 5) | class FileCloser

FILE: FlashMQTests/flashmqtempdir.h
  function class (line 9) | class FlashMQTempDir

FILE: FlashMQTests/main.cpp
  function printHelp (line 6) | void printHelp(const std::string &arg0)
  function main (line 12) | int main(int argc, char *argv[])

FILE: FlashMQTests/mainappasfork.cpp
  type sockaddr_in (line 116) | struct sockaddr_in
  type sockaddr_in (line 117) | struct sockaddr_in
  type sockaddr (line 123) | struct sockaddr
  type sockaddr_in (line 127) | struct sockaddr_in

FILE: FlashMQTests/mainappasfork.h
  function class (line 15) | class MainAppAsFork

FILE: FlashMQTests/mainappinthread.h
  function class (line 9) | class MainAppInThread

FILE: FlashMQTests/maintests.h
  type TestFunction (line 14) | struct TestFunction
  function class (line 21) | class MainTests

FILE: FlashMQTests/plugins/curlfunctions.cpp
  function socket_event_watch_notification (line 16) | int socket_event_watch_notification(CURL *easy, curl_socket_t s, int wha...
  function check_all_active_curls (line 43) | void check_all_active_curls(TestPluginData *p, CURLM *curlMulti)
  function call_timed_curl_multi_socket_action (line 70) | void call_timed_curl_multi_socket_action(CURLM *multi, TestPluginData *p)
  function timer_callback (line 91) | int timer_callback(CURLM *multi, long timeout_ms, void *clientp)
  function curl_write_cb (line 111) | size_t curl_write_cb(char *data, size_t n, size_t l, void *userp)

FILE: FlashMQTests/plugins/test_plugin.cpp
  function get_auth_result_delayed (line 33) | void get_auth_result_delayed(std::weak_ptr<Client> client, AuthResult re...
  function flashmq_plugin_version (line 41) | int flashmq_plugin_version()
  function flashmq_plugin_allocate_thread_memory (line 46) | void flashmq_plugin_allocate_thread_memory(void **thread_data, std::unor...
  function flashmq_plugin_deallocate_thread_memory (line 53) | void flashmq_plugin_deallocate_thread_memory(void *thread_data, std::uno...
  function flashmq_plugin_poll_event_received (line 61) | void flashmq_plugin_poll_event_received(void *thread_data, int fd, uint3...
  function flashmq_plugin_init (line 90) | void flashmq_plugin_init(void *thread_data, std::unordered_map<std::stri...
  function flashmq_plugin_deinit (line 102) | void flashmq_plugin_deinit(void *thread_data, std::unordered_map<std::st...
  function flashmq_plugin_periodic_event (line 109) | void flashmq_plugin_periodic_event(void *thread_data)
  function AuthResult (line 114) | AuthResult flashmq_plugin_login_check(void *thread_data, const std::stri...
  function publish_in_thread (line 186) | void publish_in_thread()
  function AuthResult (line 191) | AuthResult flashmq_plugin_acl_check(
  function AuthResult (line 269) | AuthResult flashmq_plugin_extended_auth(void *thread_data, const std::st...
  function flashmq_plugin_alter_publish (line 314) | bool flashmq_plugin_alter_publish(
  function flashmq_plugin_client_disconnected (line 350) | void flashmq_plugin_client_disconnected(void *thread_data, const std::st...
  function flashmq_plugin_main_init (line 358) | void flashmq_plugin_main_init(std::unordered_map<std::string, std::strin...
  function flashmq_plugin_main_deinit (line 371) | void flashmq_plugin_main_deinit(std::unordered_map<std::string, std::str...

FILE: FlashMQTests/plugins/test_plugin.h
  type AuthenticatingClient (line 11) | struct AuthenticatingClient
  function class (line 26) | class TestPluginData

FILE: FlashMQTests/testhelpers.cpp
  function fmq_assert (line 10) | bool fmq_assert(bool b, const char *failmsg, const char *actual, const c...
  function fmq_fail (line 41) | void fmq_fail(const char *failmsg, const char *file, int line)

FILE: FlashMQTests/testhelpers.h
  function fmq_compare (line 45) | inline bool fmq_compare(const std::string &s1, const std::string &s2, co...
  function fmq_compare (line 53) | bool fmq_compare(const T1 &t1, const T2 &t2, const char *actual, const c...
  function fmq_compare (line 58) | inline bool fmq_compare(const char *c1, const char *c2, const char *actu...
  function myCastCompare (line 80) | bool myCastCompare(const T1 &t1, const T2 &t2, const char *actual, const...

FILE: FlashMQTests/testinitializer.h
  function class (line 9) | class TestInitializer

FILE: FlashMQTests/tst_maintests.cpp
  function testDowngradeQoSOnSubscribeHelper (line 1245) | void testDowngradeQoSOnSubscribeHelper(const uint8_t pub_qos, const uint...
  type sockaddr_in (line 2953) | struct sockaddr_in
  type sockaddr_in (line 2954) | struct sockaddr_in
  type sockaddr_in6 (line 2977) | struct sockaddr_in6
  type sockaddr_in6 (line 2978) | struct sockaddr_in6

FILE: FlashMQTests/websockettests.cpp
  function pollFd (line 13) | void pollFd(int fd, bool throw_on_timeout)
  function readFromSocket (line 28) | std::vector<char> readFromSocket(int fd, bool throw_on_timeout, size_t e...

FILE: acksender.h
  function class (line 18) | class AckSender

FILE: acltree.cpp
  function AclNode (line 22) | AclNode *AclNode::getChildren(const std::string &subtopic, bool register...
  function AclNode (line 48) | const AclNode *AclNode::getChildren(const std::string &subtopic) const
  function AclNode (line 55) | AclNode *AclNode::getChildrenPlus()
  function AclNode (line 63) | const AclNode *AclNode::getChildrenPlus() const
  function AuthResult (line 236) | AuthResult AclTree::findPermission(const std::vector<std::string> &subto...
  function AclGrant (line 287) | AclGrant stringToAclGrant(const std::string &s)

FILE: acltree.h
  type class (line 20) | enum class
  type class (line 28) | enum class

FILE: backgroundworker.cpp
  type pollfd (line 12) | struct pollfd
  type pollfd (line 13) | struct pollfd

FILE: backgroundworker.h
  function class (line 12) | class BackgroundWorker

FILE: bindaddr.cpp
  type sockaddr_in (line 49) | struct sockaddr_in
  type sockaddr_in6 (line 65) | struct sockaddr_in6
  type sockaddr_un (line 81) | struct sockaddr_un

FILE: bindaddr.h
  function class (line 23) | class BindAddr

FILE: bridgeconfig.cpp
  function FMQSockaddr (line 97) | FMQSockaddr BridgeState::popDnsResult()

FILE: bridgeconfig.h
  type class (line 14) | enum class
  type BridgeTopicPath (line 21) | struct BridgeTopicPath
  function class (line 36) | class BridgeClientGroupIds
  function std (line 103) | const std::string &getClientid() const;

FILE: bridgeinfodb.h
  type BridgeInfoForSerializing (line 13) | struct BridgeInfoForSerializing
  function ReadVersion (line 27) | enum class ReadVersion

FILE: checkedweakptr.h
  function class (line 6) | class __attribute__((visibility("default"))) PointerNullException : publ...

FILE: cirbuf.h
  function class (line 23) | class CirBuf

FILE: client.cpp
  type sockaddr (line 65) | struct sockaddr
  type epoll_event (line 152) | struct epoll_event
  function HaProxyStage (line 170) | HaProxyStage Client::readHaProxyData()
  function ProtocolVersion (line 200) | ProtocolVersion Client::getProtocolVersion() const
  function DisconnectStage (line 271) | DisconnectStage Client::readFdIntoBuffer()
  function PacketDropReason (line 335) | PacketDropReason Client::writeMqttPacket(const MqttPacket &packet)
  function PacketDropReason (line 378) | PacketDropReason Client::writeMqttPacketAndBlameThisClient(
  function PacketDropReason (line 448) | PacketDropReason Client::writeMqttPacketAndBlameThisClient(const MqttPac...
  function FMQSockaddr (line 528) | const FMQSockaddr &Client::getAddr() const
  type epoll_event (line 890) | struct epoll_event
  type epoll_event (line 917) | struct epoll_event
  function X509ClientVerification (line 1201) | X509ClientVerification Client::getX509ClientVerification() const
  function AllowListenerAnonymous (line 1211) | AllowListenerAnonymous Client::getAllowAnonymousOverride() const

FILE: client.h
  type StowedClientRegistrationData (line 45) | struct StowedClientRegistrationData
  type class (line 54) | enum class
  type AsyncAuthResult (line 61) | struct AsyncAuthResult
  function class (line 71) | class Client

FILE: clientacceptqueue.h
  type ClientAcceptQueue (line 12) | struct ClientAcceptQueue

FILE: configfileparser.cpp
  function full_stoi (line 37) | int full_stoi(const std::string &key, const std::string &value)
  function full_stoul (line 54) | unsigned long full_stoul(const std::string &key, const std::string &valu...
  type stat (line 142) | struct stat
  type stat (line 179) | struct stat
  function Settings (line 1435) | const Settings &ConfigFileParser::getSettings()

FILE: configfileparser.h
  type class (line 25) | enum class
  function newVal (line 39) | long long newVal{std::stoll(value, &len)};
  function value_to_int (line 53) | long unsigned>::type
  function class (line 98) | class ConfigFileParser

FILE: derivablecounter.h
  function class (line 22) | class DerivableCounter

FILE: dnsresolver.cpp
  type gaicb (line 58) | struct gaicb
  type addrinfo (line 96) | struct addrinfo

FILE: dnsresolver.h
  function class (line 24) | class DnsResolver

FILE: driftcounter.h
  function class (line 14) | class DriftCounter

FILE: enums.h
  type class (line 14) | enum class
  type class (line 21) | enum class
  type class (line 28) | enum class
  type class (line 36) | enum class
  type class (line 42) | enum class
  type class (line 49) | enum class
  type class (line 55) | enum class

FILE: evpencodectxmanager.h
  type EvpEncodeCtxManager (line 17) | struct EvpEncodeCtxManager

FILE: examples/plugin_libcurl/src/authenticatingclient.h
  function namespace (line 39) | namespace ExampleCurlPlugin

FILE: examples/plugin_libcurl/src/curl_functions.cpp
  function socket_event_watch_notification (line 48) | int socket_event_watch_notification(CURL *easy, curl_socket_t s, int wha...
  function timer_callback (line 77) | int timer_callback(CURLM *multi, long timeout_ms, void *clientp)
  function call_timed_curl_multi_socket_action (line 97) | void call_timed_curl_multi_socket_action(CURLM *multi, PluginState *s)
  function check_all_active_curls (line 117) | void check_all_active_curls(PluginState *p, CURLM *curlMulti)
  function curl_write_cb (line 145) | size_t curl_write_cb(char *data, size_t n, size_t l, void *userp)

FILE: examples/plugin_libcurl/src/plugin_libcurl.cpp
  function flashmq_plugin_version (line 41) | int flashmq_plugin_version()
  function flashmq_plugin_main_init (line 46) | void flashmq_plugin_main_init(std::unordered_map<std::string, std::strin...
  function flashmq_plugin_main_deinit (line 54) | void flashmq_plugin_main_deinit(std::unordered_map<std::string, std::str...
  function flashmq_plugin_allocate_thread_memory (line 61) | void flashmq_plugin_allocate_thread_memory(void **thread_data, std::unor...
  function flashmq_plugin_deallocate_thread_memory (line 69) | void flashmq_plugin_deallocate_thread_memory(void *thread_data, std::uno...
  function flashmq_plugin_init (line 83) | void flashmq_plugin_init(void *thread_data, std::unordered_map<std::stri...
  function flashmq_plugin_deinit (line 90) | void flashmq_plugin_deinit(void *thread_data, std::unordered_map<std::st...
  function flashmq_plugin_poll_event_received (line 105) | void flashmq_plugin_poll_event_received(void *thread_data, int fd, uint3...
  function AuthResult (line 131) | AuthResult flashmq_plugin_login_check(
  function AuthResult (line 176) | AuthResult flashmq_plugin_acl_check(

FILE: examples/plugin_libcurl/src/pluginstate.h
  function namespace (line 40) | namespace ExampleCurlPlugin

FILE: examples/plugin_libcurl/vendor/flashmq_public.h
  type class (line 47) | enum class
  type class (line 79) | enum class
  type class (line 95) | enum class
  function ServerDisconnectReasons (line 108) | enum class ServerDisconnectReasons

FILE: exceptions.h
  function namespace (line 23) | namespace FlashMQ

FILE: fdmanaged.h
  function class (line 5) | class FdManaged

FILE: flags.h
  function setFlag (line 29) | void setFlag(FlagType val)
  function clearFlag (line 34) | void clearFlag(FlagType val)
  function clearAll (line 39) | void clearAll()
  function setAll (line 44) | void setAll()

FILE: flashmq_plugin.cpp
  function flashmq_logf (line 22) | void flashmq_logf(int level, const char *str, ...)
  function mosquitto_log_printf (line 32) | void mosquitto_log_printf(int level, const char *fmt, ...)
  function flashmq_plugin_remove_client (line 44) | void flashmq_plugin_remove_client(const std::string &clientid, bool also...
  function flashmq_plugin_remove_client_v4 (line 69) | void flashmq_plugin_remove_client_v4(const std::weak_ptr<Session> &sessi...
  function flashmq_plugin_remove_subscription (line 90) | void flashmq_plugin_remove_subscription(const std::string &clientid, con...
  function flashmq_plugin_remove_subscription_v4 (line 107) | void flashmq_plugin_remove_subscription_v4(const std::weak_ptr<Session> ...
  function flashmq_plugin_add_subscription (line 124) | bool flashmq_plugin_add_subscription(
  function flashmq_continue_async_authentication (line 145) | void flashmq_continue_async_authentication(const std::weak_ptr<Client> &...
  function flashmq_continue_async_authentication_v4 (line 150) | void flashmq_continue_async_authentication_v4(
  function flashmq_publish_message (line 167) | void flashmq_publish_message(const std::string &topic, const uint8_t qos...
  function flashmq_get_client_address (line 202) | void flashmq_get_client_address(const std::weak_ptr<Client> &client, std...
  function flashmq_get_client_address_v4 (line 226) | void flashmq_get_client_address_v4(const std::weak_ptr<Client> &client, ...
  function flashmq_poll_add_fd (line 249) | void flashmq_poll_add_fd(int fd, uint32_t events, const std::weak_ptr<vo...
  function flashmq_poll_remove_fd (line 259) | void flashmq_poll_remove_fd(uint32_t fd)
  function sockaddr (line 269) | sockaddr *FlashMQSockAddr::getAddr()
  type sockaddr_in6 (line 276) | struct sockaddr_in6
  function flashmq_add_task (line 279) | uint32_t flashmq_add_task(std::function<void ()> f, uint32_t delay_in_ms)
  function flashmq_remove_task (line 289) | void flashmq_remove_task(uint32_t id)
  function flashmq_get_session_pointer (line 299) | void flashmq_get_session_pointer(const std::string &clientid, const std:...
  function flashmq_get_client_pointer (line 309) | void flashmq_get_client_pointer(const std::weak_ptr<Session> &session, s...
  function flashmq_defer_thread_ready (line 316) | void flashmq_defer_thread_ready()
  function flashmq_signal_thread_ready (line 329) | void flashmq_signal_thread_ready()

FILE: flashmq_plugin_deprecated.h
  function class (line 11) | class API FlashMQSockAddr

FILE: flashmq_public.h
  type class (line 47) | enum class
  type class (line 79) | enum class
  type class (line 95) | enum class
  function ServerDisconnectReasons (line 108) | enum class ServerDisconnectReasons

FILE: flashmqtestclient.cpp
  type sockaddr_in (line 108) | struct sockaddr_in
  type sockaddr (line 119) | struct sockaddr
  type sockaddr (line 129) | struct sockaddr
  function ProtocolVersion (line 390) | ProtocolVersion FlashMQTestClient::getProtocolVersion()

FILE: flashmqtestclient.h
  function class (line 23) | class SubAckIsError : public std::runtime_error
  function class (line 32) | class FlashMQTestClient

FILE: fmqmain.cpp
  function signal_handler (line 24) | void signal_handler(int signal)
  function register_signal_handers (line 53) | int register_signal_handers()
  function fmqmain (line 83) | int fmqmain(int argc, char *argv[])

FILE: fmqsockaddr.cpp
  type sockaddr_in (line 15) | struct sockaddr_in
  type sockaddr_in6 (line 19) | struct sockaddr_in6
  type sockaddr_un (line 23) | struct sockaddr_un
  function sockaddr (line 38) | const sockaddr *FMQSockaddr::getSockaddr() const
  function socklen_t (line 48) | socklen_t FMQSockaddr::getSize() const
  type sockaddr_in (line 63) | struct sockaddr_in
  type sockaddr_in6 (line 70) | struct sockaddr_in6
  type sockaddr_in (line 83) | struct sockaddr_in
  type sockaddr_in6 (line 89) | struct sockaddr_in6
  type in_addr (line 95) | struct in_addr
  type in6_addr (line 112) | struct in6_addr

FILE: fmqsockaddr.h
  function class (line 13) | class FMQSockaddr

FILE: fmqssl.h
  function class (line 9) | class FmqSsl

FILE: globals.h
  function class (line 19) | class Globals

FILE: globalstats.h
  function class (line 21) | class GlobalStats

FILE: globber.h
  function class (line 8) | class GlobT
  function class (line 19) | class Globber

FILE: haproxy.cpp
  function read_ha_proxy_helper (line 26) | size_t read_ha_proxy_helper(int fd, void *buf, size_t nbytes)
  function get_ha_proxy_pp2_field (line 55) | std::optional<std::string> get_ha_proxy_pp2_field(const std::unordered_m...
  function HaProxySslData (line 65) | HaProxySslData read_ha_proxy_pp2_ssl(const std::vector<unsigned char> &d...
  function read_ha_proxy_pp2_tlv (line 109) | std::unordered_map<int, std::variant<std::string, HaProxySslData>> read_...

FILE: haproxy.h
  type proxy_hdr_v2 (line 36) | struct proxy_hdr_v2
  type proxy_ipv4_addr (line 45) | struct proxy_ipv4_addr
  type proxy_ipv6_addr (line 54) | struct proxy_ipv6_addr
  type proxy_unix_addr (line 63) | struct proxy_unix_addr
  type proxy_ipv4_addr (line 71) | struct proxy_ipv4_addr
  type proxy_ipv6_addr (line 72) | struct proxy_ipv6_addr
  type proxy_unix_addr (line 73) | struct proxy_unix_addr
  type HaProxySslData (line 76) | struct HaProxySslData
  function HaProxyConnectionType (line 90) | enum class HaProxyConnectionType

FILE: http.cpp
  function generateWebsocketAcceptString (line 12) | std::string generateWebsocketAcceptString(const std::string &websocketKey)
  function generateInvalidWebsocketVersionHttpHeaders (line 30) | std::string generateInvalidWebsocketVersionHttpHeaders(const int wantedV...
  function generateBadHttpRequestReponse (line 40) | std::string generateBadHttpRequestReponse(const std::string &msg)
  function generateWebsocketAnswer (line 50) | std::string generateWebsocketAnswer(const std::string &acceptString, con...
  function generateRedirect (line 63) | std::string generateRedirect(const std::string &location)
  function parseHttpHeader (line 78) | std::optional<HttpRequest> parseHttpHeader(CirBuf &buf)
  function websocketCloseCodeToString (line 195) | std::string websocketCloseCodeToString(uint16_t code)

FILE: http.h
  function class (line 10) | class HttpRequest

FILE: iowrapper.cpp
  function verify_callback (line 167) | static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
  function WebsocketState (line 260) | WebsocketState IoWrapper::getWebsocketState() const
  function X509Manager (line 265) | X509Manager IoWrapper::getPeerCertificate() const
  type proxy_hdr_v2 (line 287) | struct proxy_hdr_v2
  type proxy_ipv4_addr (line 355) | struct proxy_ipv4_addr
  type sockaddr_in (line 358) | struct sockaddr_in
  type sockaddr (line 364) | struct sockaddr
  type proxy_ipv6_addr (line 368) | struct proxy_ipv6_addr
  type sockaddr_in6 (line 371) | struct sockaddr_in6
  type in6_addr (line 374) | struct in6_addr
  type sockaddr (line 377) | struct sockaddr

FILE: iowrapper.h
  type class (line 35) | enum class
  type class (line 45) | enum class
  type IncompleteSslWrite (line 64) | struct IncompleteSslWrite
  type IncompleteWebsocketRead (line 76) | struct IncompleteWebsocketRead
  type class (line 89) | enum class
  type class (line 96) | enum class
  function class (line 108) | class IoWrapper

FILE: listener.cpp
  function X509ClientVerification (line 188) | X509ClientVerification Listener::getX509ClientVerficationMode() const

FILE: listener.h
  type class (line 22) | enum class
  type Listener (line 30) | struct Listener

FILE: lockedsharedptr.h
  function reset (line 22) | void reset()

FILE: lockedweakptr.h
  function expired (line 29) | bool expired()

FILE: logger.cpp
  function Logger (line 95) | Logger *Logger::getInstance()
  function StreamToLog (line 123) | StreamToLog Logger::log(int level)
  function logSslError (line 391) | int logSslError(const char *str, size_t len, void *u)

FILE: logger.h
  function LogLevel (line 25) | enum class LogLevel
  function class (line 65) | class Logger

FILE: main.cpp
  function main (line 3) | int main(int argc, char *argv[])

FILE: mainapp.cpp
  type option (line 474) | struct option
  type epoll_event (line 583) | struct epoll_event
  type sockaddr_storage (line 746) | struct sockaddr_storage
  type sockaddr (line 747) | struct sockaddr
  type rlimit (line 1028) | struct rlimit

FILE: mainapp.h
  function class (line 36) | class MainApp

FILE: mosquittoauthoptcompatwrap.cpp
  function mosquitto_auth_opt (line 43) | mosquitto_auth_opt &mosquitto_auth_opt::operator=(const mosquitto_auth_o...

FILE: mosquittoauthoptcompatwrap.h
  type mosquitto_auth_opt (line 25) | struct mosquitto_auth_opt
  function size (line 42) | struct AuthOptCompatWrap

FILE: mqtt5properties.cpp
  function VariableByteInt (line 31) | const VariableByteInt &Mqtt5PropertyBuilder::getVarInt()

FILE: mqtt5properties.h
  function class (line 19) | class Mqtt5PropertyBuilder

FILE: mqttpacket.cpp
  function HandleResult (line 541) | HandleResult MqttPacket::handle(std::shared_ptr<Client> &sender)
  function ConnectData (line 609) | ConnectData MqttPacket::parseConnectData(std::shared_ptr<Client> &sender)
  function ConnAckData (line 943) | ConnAckData MqttPacket::parseConnAckData()
  function AuthPacketData (line 1421) | AuthPacketData MqttPacket::parseAuthData()
  function DisconnectData (line 1534) | DisconnectData MqttPacket::parseDisconnectData()
  function PubRecData (line 2288) | PubRecData MqttPacket::parsePubRecData()
  function SubAckData (line 2400) | SubAckData MqttPacket::parseSubAckData()
  function Publish (line 2781) | const Publish &MqttPacket::getPublishData()

FILE: mqttpacket.h
  type class (line 29) | enum class
  function class (line 40) | class MqttPacket

FILE: mutexowned.h
  function class (line 6) | class __attribute__((visibility("default"))) MutexOwnedObjectNull : publ...

FILE: network.cpp
  type sockaddr_in (line 20) | struct sockaddr_in
  type in_addr (line 24) | struct in_addr
  type sockaddr_in6 (line 41) | struct sockaddr_in6
  type sockaddr_in (line 106) | struct sockaddr_in
  type sockaddr_in (line 107) | struct sockaddr_in
  type sockaddr_in6 (line 116) | struct sockaddr_in6
  type in6_addr (line 119) | struct in6_addr
  type sockaddr (line 139) | struct sockaddr
  type sockaddr (line 139) | struct sockaddr
  type sockaddr (line 145) | struct sockaddr
  type sockaddr (line 145) | struct sockaddr

FILE: network.h
  function class (line 18) | class Network

FILE: oneinstancelock.cpp
  type flock (line 40) | struct flock

FILE: oneinstancelock.h
  function class (line 20) | class OneInstanceLock

FILE: packetdatatypes.h
  type ConnectData (line 19) | struct ConnectData
  type ConnAckData (line 56) | struct ConnAckData
  type AuthPacketData (line 81) | struct AuthPacketData
  type DisconnectData (line 88) | struct DisconnectData
  type SubAckData (line 97) | struct SubAckData
  type PubRecData (line 104) | struct PubRecData

FILE: persistencefile.h
  function class (line 33) | class PersistenceFileCantBeOpened : public std::runtime_error
  type class (line 54) | enum class

FILE: persistencefunctions.cpp
  function saveState (line 22) | void saveState(const Settings &settings, const std::list<BridgeInfoForSe...
  function saveBridgeInfo (line 62) | void saveBridgeInfo(const std::string &filePath, const std::list<BridgeI...
  function loadBridgeInfo (line 71) | std::list<BridgeConfig> loadBridgeInfo(Settings &settings)
  function correctBackupDbPermissions (line 113) | void correctBackupDbPermissions(const std::string &dir)

FILE: plugin.cpp
  function AuthResult (line 315) | AuthResult Authentication::aclCheck(Publish &publishData, std::string_vi...
  function AuthResult (line 329) | AuthResult Authentication::aclCheck(
  function AuthResult (line 440) | AuthResult Authentication::loginCheck(const std::string &clientid, const...
  function AuthResult (line 508) | AuthResult Authentication::extendedAuth(const std::string &clientid, Ext...
  type stat (line 710) | struct stat
  type timespec (line 712) | struct timespec
  type stat (line 811) | struct stat
  type timespec (line 813) | struct timespec
  function AuthResult (line 886) | AuthResult Authentication::aclCheckFromMosquittoAclFile(const std::strin...
  function AuthResultToString (line 973) | std::string AuthResultToString(AuthResult r)

FILE: plugin.h
  type class (line 26) | enum class
  type MosquittoPasswordFileEntry (line 42) | struct MosquittoPasswordFileEntry
  type mosquitto_auth_opt (line 56) | struct mosquitto_auth_opt
  type mosquitto_auth_opt (line 57) | struct mosquitto_auth_opt
  type mosquitto_auth_opt (line 58) | struct mosquitto_auth_opt
  type mosquitto_auth_opt (line 59) | struct mosquitto_auth_opt
  type AuthResult (line 69) | typedef AuthResult(*F_flashmq_plugin_acl_check_v1)(void *thread_data, co...
  type AuthResult (line 72) | typedef AuthResult(*F_flashmq_plugin_login_check_v1)(void *thread_data, ...
  type AuthResult (line 75) | typedef AuthResult(*F_flashmq_plugin_extended_auth_v1)(void *thread_data...
  type AuthResult (line 87) | typedef AuthResult(*F_flashmq_plugin_acl_check_v2)(void *thread_data, co...
  type AuthResult (line 94) | typedef AuthResult(*F_flashmq_plugin_acl_check_v3)(
  type AuthResult (line 104) | typedef AuthResult(*F_flashmq_plugin_acl_check_v4)(
  type AuthResult (line 119) | typedef AuthResult(*F_flashmq_plugin_acl_check_v5)(
  function class (line 134) | class Authentication

FILE: pluginloader.cpp
  function PluginFamily (line 110) | PluginFamily PluginLoader::getPluginFamily() const

FILE: pluginloader.h
  type class (line 19) | enum class
  function class (line 31) | class PluginLoader

FILE: publishcopyfactory.cpp
  function MqttPacket (line 33) | MqttPacket *PublishCopyFactory::getOptimumPacket(
  function Publish (line 140) | Publish PublishCopyFactory::getNewPublish(uint8_t new_max_qos, bool reta...

FILE: publishcopyfactory.h
  function class (line 31) | class PublishCopyFactory

FILE: qospacketqueue.cpp
  function Publish (line 30) | Publish &QueuedPublish::getPublish()

FILE: qospacketqueue.h
  function class (line 25) | class QueuedPublish

FILE: queuedtasks.h
  type QueuedTask (line 20) | struct QueuedTask
  function class (line 36) | class QueuedTasks

FILE: retainedmessage.cpp
  function RetainedMessage (line 33) | RetainedMessage &RetainedMessage::operator=(const Publish &pub)

FILE: retainedmessage.h
  type RetainedMessage (line 17) | struct RetainedMessage
  function namespace (line 34) | namespace std {

FILE: retainedmessagesdb.h
  function ReadVersion (line 37) | enum class ReadVersion

FILE: rwlockguard.h
  function class (line 18) | class RWLockGuard

FILE: scopedsocket.cpp
  function ScopedSocket (line 48) | ScopedSocket &ScopedSocket::operator=(ScopedSocket &&other)
  type epoll_event (line 95) | struct epoll_event

FILE: scopedsocket.h
  function class (line 21) | class ScopedSocket

FILE: sdnotify.cpp
  function closep (line 27) | static void closep(int *fd) {
  function notify (line 35) | static int notify(const char *message) {
  function notify_ready (line 88) | int notify_ready(void) {
  function notify_reloading (line 92) | int notify_reloading(void) {
  function notify_stopping (line 116) | int notify_stopping(void) {

FILE: session.cpp
  function PacketDropReason (line 119) | PacketDropReason Session::writePacket(PublishCopyFactory &copyFactory, c...

FILE: session.h
  function class (line 29) | class Session

FILE: sessionsandsubscriptionsdb.cpp
  function SubscriptionOptionsByte (line 71) | SubscriptionOptionsByte SubscriptionForSerializing::getSubscriptionOptio...
  function SessionsAndSubscriptionsResult (line 112) | SessionsAndSubscriptionsResult SessionsAndSubscriptionsDB::readDataV3V4V...
  function SessionsAndSubscriptionsResult (line 481) | SessionsAndSubscriptionsResult SessionsAndSubscriptionsDB::readData()

FILE: sessionsandsubscriptionsdb.h
  type SubscriptionForSerializing (line 34) | struct SubscriptionForSerializing
  type SessionsAndSubscriptionsResult (line 52) | struct SessionsAndSubscriptionsResult
  function ReadVersion (line 61) | enum class ReadVersion

FILE: settings.cpp
  function checkUniqueBridgeNames (line 17) | void checkUniqueBridgeNames(const std::list<BridgeConfig> &bridges)
  function AuthOptCompatWrap (line 40) | AuthOptCompatWrap &Settings::getAuthOptsCompat()
  type sockaddr (line 102) | struct sockaddr
  type sockaddr (line 107) | struct sockaddr

FILE: settings.h
  type class (line 30) | enum class
  type class (line 40) | enum class
  type class (line 47) | enum class
  type class (line 53) | enum class
  function class (line 62) | class Settings

FILE: sharedsubscribers.cpp
  function Subscription (line 28) | Subscription &SharedSubscribers::operator[](const std::string &clientid)
  function Subscription (line 45) | const Subscription *SharedSubscribers::getFirst() const
  function Subscription (line 61) | const Subscription *SharedSubscribers::getNext()
  function Subscription (line 80) | const Subscription *SharedSubscribers::getNext(size_t hash) const

FILE: sharedsubscribers.h
  function class (line 24) | class SharedSubscribers

FILE: sslctxmanager.cpp
  function SSL_CTX (line 26) | SSL_CTX *SslCtxManager::get() const

FILE: sslctxmanager.h
  function class (line 18) | class SslCtxManager

FILE: subscription.h
  type Subscription (line 17) | struct Subscription

FILE: subscriptionstore.cpp
  function AddSubscriptionType (line 59) | AddSubscriptionType SubscriptionNode::addSubscriber(
  function AddSubscriptionType (line 256) | AddSubscriptionType SubscriptionStore::addSubscription(

FILE: subscriptionstore.h
  type ReceivingSubscriber (line 32) | struct ReceivingSubscriber
  type class (line 43) | enum class
  function class (line 50) | class SubscriptionNode
  function isOrphaned (line 89) | bool isOrphaned() const;

FILE: threaddata.cpp
  type epoll_event (line 59) | struct epoll_event
  type epoll_event (line 1149) | struct epoll_event
  function ThreadData (line 1395) | ThreadData *ThreadDataOwner::operator->() const

FILE: threaddata.h
  type KeepAliveCheck (line 42) | struct KeepAliveCheck
  type QueuedRetainedMessage (line 50) | struct QueuedRetainedMessage
  type Clients (line 59) | struct Clients
  type ThreadDataOwner (line 65) | struct ThreadDataOwner
  function class (line 85) | class ThreadData

FILE: threadglobals.cpp
  function Settings (line 38) | Settings *ThreadGlobals::getSettings()

FILE: threadglobals.h
  function class (line 20) | class ThreadGlobals

FILE: threadlocalutils.h
  function class (line 25) | class SimdUtils

FILE: threadloop.cpp
  function do_thread_work (line 18) | void do_thread_work(std::shared_ptr<ThreadData> threadData)

FILE: threadloop.h
  type ReadyClient (line 37) | struct ReadyClient

FILE: types.cpp
  function RetainHandling (line 47) | RetainHandling SubscriptionOptionsByte::getRetainHandling() const

FILE: types.h
  type class (line 27) | enum class
  type class (line 47) | enum class
  type class (line 55) | enum class
  type class (line 90) | enum class
  type class (line 103) | enum class
  type class (line 156) | enum class
  type class (line 163) | enum class
  type SubscriptionOptionsByte (line 170) | struct SubscriptionOptionsByte
  function class (line 183) | class ConnAck
  function class (line 197) | class SubAck
  function getLengthWithoutFixedHeader (line 217) | size_t getLengthWithoutFixedHeader() const;
  function class (line 285) | class WillPublish : public Publish
  type Connect (line 332) | struct Connect
  type Subscribe (line 357) | struct Subscribe
  type Unsubscribe (line 373) | struct Unsubscribe
  type class (line 380) | enum class

FILE: unscopedlock.h
  function class (line 22) | class UnscopedLock

FILE: utils.cpp
  function split (line 41) | std::list<std::string> split(const std::string &input, const char sep, s...
  function strContains (line 58) | bool strContains(const std::string &s, const std::string &needle)
  function isValidUtf8Generic (line 64) | bool isValidUtf8Generic(const char *s, bool alsoCheckInvalidPublishChars)
  function isValidPublishPath (line 70) | bool isValidPublishPath(const std::string &s)
  function isValidSubscribePath (line 84) | bool isValidSubscribePath(const std::string &s)
  function isValidShareName (line 109) | bool isValidShareName(const std::string &s)
  function containsDangerousCharacters (line 123) | bool containsDangerousCharacters(const std::string &s)
  function splitTopic (line 142) | std::vector<std::string> splitTopic(const std::string &topic)
  function splitToVector (line 170) | std::vector<std::string> splitToVector(const std::string &input, const c...
  function ltrim (line 188) | void ltrim(std::string &s)
  function rtrim (line 195) | void rtrim(std::string &s)
  function rtrim (line 202) | void rtrim(std::string &s, char c)
  function trim (line 209) | void trim(std::string &s)
  function startsWith (line 223) | bool startsWith(const std::string &s, const std::string &needle)
  function endsWith (line 238) | bool endsWith(const std::string &s, const std::string &ending)
  function getSecureRandomString (line 245) | std::string getSecureRandomString(const ssize_t len)
  function str_tolower (line 273) | std::string str_tolower(std::string s)
  function stringTruthiness (line 280) | bool stringTruthiness(const std::string &val)
  function isPowerOfTwo (line 291) | bool isPowerOfTwo(int n)
  function base64Decode (line 296) | std::vector<unsigned char> base64Decode(const std::string &s)
  function base64Encode (line 320) | std::string base64Encode(const unsigned char *input, const int length)
  function testSsl (line 334) | void testSsl(const std::string &fullchain, const std::string &privkey)
  function testSslVerifyLocations (line 369) | void testSslVerifyLocations(const std::string &caFile, const std::string...
  function formatString (line 389) | std::string formatString(const std::string str, ...)
  function dirnameOf (line 410) | std::string_view dirnameOf(std::string_view path)
  function getFileSize (line 416) | size_t getFileSize(const std::string &path)
  function getFreeSpace (line 428) | uint64_t getFreeSpace(const std::string &path)
  function sa_family_t (line 445) | sa_family_t getFamilyFromSockAddr(const sockaddr *addr)
  function sockaddrToString (line 456) | std::string sockaddrToString(const sockaddr *addr)
  function protocolVersionString (line 491) | std::string protocolVersionString(ProtocolVersion p)
  function distanceBetweenStrings (line 515) | unsigned int distanceBetweenStrings(const std::string &stringA, const st...
  function ageFromTimePoint (line 592) | uint32_t ageFromTimePoint(const std::chrono::time_point<std::chrono::ste...
  function timepointFromAge (line 599) | std::chrono::time_point<std::chrono::steady_clock> timepointFromAge(cons...
  function ReasonCodes (line 606) | ReasonCodes authResultToReasonCode(AuthResult authResult)
  function maskAllSignalsCurrentThread (line 628) | int maskAllSignalsCurrentThread()
  function parseSubscriptionShare (line 637) | void parseSubscriptionShare(std::vector<std::string> &subtopics, std::st...
  function timestampWithMillis (line 679) | std::string timestampWithMillis()
  function exceptionOnNonMqtt (line 699) | void exceptionOnNonMqtt(const std::vector<char> &data)
  function getFirstWildcardDepth (line 728) | uint16_t getFirstWildcardDepth(const std::vector<std::string> &subtopics)
  function reasonCodeToString (line 747) | std::string reasonCodeToString(ReasonCodes code)
  function packetTypeToString (line 848) | std::string packetTypeToString(PacketType ptype)
  function propertyToString (line 894) | std::string propertyToString(Mqtt5Properties p)
  function unlink_if_sock (line 963) | void unlink_if_sock(const std::string &path)
  function fmq_ensure_fail (line 978) | void fmq_ensure_fail(const char *file, int line)
  function get_pw_name (line 984) | std::optional<SysUserFields> get_pw_name(const std::string &user)
  function get_gr_name (line 1007) | std::optional<SysGroupFields> get_gr_name(const std::string &group)
  function try_stoul (line 1029) | std::optional<unsigned long> try_stoul(const std::string &s) noexcept

FILE: utils.h
  type sockaddr (line 200) | struct sockaddr
  type sockaddr (line 201) | struct sockaddr
  type stat (line 214) | struct stat
  type stat (line 215) | struct stat
  type SysUserFields (line 398) | struct SysUserFields
  type SysGroupFields (line 407) | struct SysGroupFields
  function string (line 427) | string make_string(const T &input, const size_t offset, const size_t len)

FILE: variablebyteint.cpp
  function VariableByteInt (line 28) | VariableByteInt &VariableByteInt::operator=(uint32_t x)

FILE: variablebyteint.h
  function class (line 18) | class VariableByteInt

FILE: x509manager.cpp
  function X509 (line 14) | X509 *X509Manager::get()

FILE: x509manager.h
  function class (line 7) | class X509Manager
Condensed preview — 235 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,821K chars).
[
  {
    "path": ".build-ci.sh",
    "chars": 590,
    "preview": "#!/bin/bash\n\nset -e  # If any step reports a problem consider the whole build a failure\nwget https://github.com/linuxdep"
  },
  {
    "path": ".clang-format",
    "chars": 6052,
    "preview": "---\nLanguage:        Cpp\n# BasedOnStyle:  LLVM\nAccessModifierOffset: -4\nAlignAfterOpenBracket: BlockIndent\nAlignArrayOfS"
  },
  {
    "path": ".clang-format.original",
    "chars": 6031,
    "preview": "---\nLanguage:        Cpp\n# BasedOnStyle:  LLVM\nAccessModifierOffset: -2\nAlignAfterOpenBracket: Align\nAlignArrayOfStructu"
  },
  {
    "path": ".dockerignore",
    "chars": 11,
    "preview": "Dockerfile\n"
  },
  {
    "path": ".get-os-codename-and-stamp.sh",
    "chars": 988,
    "preview": "#!/bin/bash\n\nmy_codename=\"\"\nmy_int_version=\"\"\n\nif [[ -e \"/etc/os-release\" ]]; then\n  eval \"$(cat \"/etc/os-release\")\"\n  m"
  },
  {
    "path": ".github/workflows/building.yml",
    "chars": 1236,
    "preview": "name: Building\non: [push]\njobs:\n  compilation:\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-24.04"
  },
  {
    "path": ".github/workflows/codestyle.yml",
    "chars": 572,
    "preview": "name: Checking code style\non: [pull_request]\njobs:\n  check_cpp_codestyle:\n    runs-on: ubuntu-latest\n    steps:\n      - "
  },
  {
    "path": ".github/workflows/docker.yml",
    "chars": 247,
    "preview": "name: Docker\non: [push]\njobs:\n  docker:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actio"
  },
  {
    "path": ".github/workflows/linting.yml",
    "chars": 641,
    "preview": "name: Linting\non: [push]\njobs:\n  shellcheck:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: "
  },
  {
    "path": ".github/workflows/testing.yml",
    "chars": 1054,
    "preview": "name: Testing\non: [push]\njobs:\n  compilation:\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-24.04\n"
  },
  {
    "path": ".github/workflows/testing_non_sse.yml",
    "chars": 1090,
    "preview": "name: TestingNoSse\non: [push]\njobs:\n  compilation:\n    strategy:\n      matrix:\n        include:\n          - os: ubuntu-2"
  },
  {
    "path": ".gitignore",
    "chars": 99,
    "preview": "*.user\nbuild-*\nFlashMQBuild*\n*.swp\ncompile_commands.json\n.clangd/\n.cache/\nbuild\nFlashMQTests/build\n"
  },
  {
    "path": "CMakeLists.shared",
    "chars": 4738,
    "preview": "# This file is shared between FlashMQ itself and FlashMQTests\n# It should not contain any definitions that isn't used by"
  },
  {
    "path": "CMakeLists.txt",
    "chars": 2290,
    "preview": "cmake_minimum_required(VERSION 3.5)\ncmake_policy(SET CMP0048 NEW)\ninclude(CheckCXXCompilerFlag)\ninclude(CMakeLists.share"
  },
  {
    "path": "Dockerfile",
    "chars": 2442,
    "preview": "# build target, used for building the binary, providing shared libraries and could be used as a development env\nFROM deb"
  },
  {
    "path": "FlashMQTests/CMakeLists.txt",
    "chars": 2586,
    "preview": "cmake_minimum_required(VERSION 3.5)\ncmake_policy(SET CMP0048 NEW)\ninclude(CheckCXXCompilerFlag)\ninclude(../CMakeLists.sh"
  },
  {
    "path": "FlashMQTests/bridgeprefixtests.cpp",
    "chars": 27368,
    "preview": "#include \"maintests.h\"\n#include \"conffiletemp.h\"\n#include \"flashmqtestclient.h\"\n#include \"mainappasfork.h\"\n#include \"tes"
  },
  {
    "path": "FlashMQTests/conffiletemp.cpp",
    "chars": 1188,
    "preview": "#include \"conffiletemp.h\"\n\n#include <vector>\n#include <unistd.h>\n#include <stdexcept>\n\nConfFileTemp::ConfFileTemp()\n{\n  "
  },
  {
    "path": "FlashMQTests/conffiletemp.h",
    "chars": 490,
    "preview": "#ifndef CONFFILETEMP_H\n#define CONFFILETEMP_H\n\n#include <string>\n\nclass ConfFileTemp\n{\n    int fd = -1;\n    std::string "
  },
  {
    "path": "FlashMQTests/configtests.cpp",
    "chars": 7115,
    "preview": "#include \"maintests.h\"\n#include \"testhelpers.h\"\n#include \"conffiletemp.h\"\n#include \"exceptions.h\"\n#include \"settings.h\"\n"
  },
  {
    "path": "FlashMQTests/dnstests.cpp",
    "chars": 4448,
    "preview": "#include \"maintests.h\"\n#include \"testhelpers.h\"\n\n#include \"utils.h\"\n\nvoid MainTests::testDnsResolver()\n{\n    try\n    {\n "
  },
  {
    "path": "FlashMQTests/filecloser.cpp",
    "chars": 175,
    "preview": "#include \"filecloser.h\"\n\n#include <unistd.h>\n\nFileCloser::FileCloser(int fd) :\n    fd(fd)\n{\n\n}\n\nFileCloser::~FileCloser("
  },
  {
    "path": "FlashMQTests/filecloser.h",
    "chars": 159,
    "preview": "#ifndef FILECLOSER_H\n#define FILECLOSER_H\n\n\nclass FileCloser\n{\n    int fd = -1;\n\npublic:\n    FileCloser(int fd);\n    ~Fi"
  },
  {
    "path": "FlashMQTests/flashmqtempdir.cpp",
    "chars": 825,
    "preview": "#include \"flashmqtempdir.h\"\n\n#include <sys/types.h>\n#include <unistd.h>\n#include <filesystem>\n\n#include \"utils.h\"\n\nFlash"
  },
  {
    "path": "FlashMQTests/flashmqtempdir.h",
    "chars": 320,
    "preview": "#ifndef FLASHMQTEMPDIR_H\n#define FLASHMQTEMPDIR_H\n\n#include <filesystem>\n#include <stdlib.h>\n#include <string>\n#include "
  },
  {
    "path": "FlashMQTests/main.cpp",
    "chars": 1499,
    "preview": "#include <iostream>\n#include <vector>\n\n#include \"maintests.h\"\n\nvoid printHelp(const std::string &arg0)\n{\n    std::cout <"
  },
  {
    "path": "FlashMQTests/mainappasfork.cpp",
    "chars": 3307,
    "preview": "#include \"mainappasfork.h\"\n#include <mainapp.h>\n#include \"signal.h\"\n#include \"fmqmain.h\"\n#include \"sys/wait.h\"\n\nstd::str"
  },
  {
    "path": "FlashMQTests/mainappasfork.h",
    "chars": 982,
    "preview": "#ifndef MAINAPPASFORK_H\n#define MAINAPPASFORK_H\n\n#include <vector>\n#include <string>\n#include <unistd.h>\n#include \"conff"
  },
  {
    "path": "FlashMQTests/mainappinthread.cpp",
    "chars": 2426,
    "preview": "#include \"mainappinthread.h\"\n\nMainAppInThread::MainAppInThread()\n{\n\n}\n\nMainAppInThread::MainAppInThread(const std::vecto"
  },
  {
    "path": "FlashMQTests/mainappinthread.h",
    "chars": 471,
    "preview": "#ifndef MAINAPPINTHREAD_H\n#define MAINAPPINTHREAD_H\n\n#include <thread>\n\n#include \"mainapp.h\"\n#include \"mutexowned.h\"\n\ncl"
  },
  {
    "path": "FlashMQTests/maintests.cpp",
    "chars": 17085,
    "preview": "#include <unistd.h>\n\n#include \"maintests.h\"\n#include \"testhelpers.h\"\n#include \"testinitializer.h\"\n\n#include \"threadgloba"
  },
  {
    "path": "FlashMQTests/maintests.h",
    "chars": 8779,
    "preview": "#ifndef MAINTESTS_H\n#define MAINTESTS_H\n\n#include <memory>\n#include <unordered_map>\n#include <functional>\n\n#include \"mai"
  },
  {
    "path": "FlashMQTests/plugins/curlfunctions.cpp",
    "chars": 3686,
    "preview": "#include \"curlfunctions.h\"\n#include <sys/epoll.h>\n#include \"../../flashmq_plugin.h\"\n#include \"test_plugin.h\"\n#include <c"
  },
  {
    "path": "FlashMQTests/plugins/curlfunctions.h",
    "chars": 506,
    "preview": "#ifndef CURLFUNCTIONS_H\n#define CURLFUNCTIONS_H\n\n#include <curl/curl.h>\n#include \"test_plugin.h\"\n\nint socket_event_watch"
  },
  {
    "path": "FlashMQTests/plugins/test_plugin.cpp",
    "chars": 12021,
    "preview": "#include <functional>\n#include <unistd.h>\n#include <cassert>\n#include <cstring>\n#include <stdexcept>\n#include <optional>"
  },
  {
    "path": "FlashMQTests/plugins/test_plugin.h",
    "chars": 922,
    "preview": "#ifndef TESTPLUGIN_H\n#define TESTPLUGIN_H\n\n#include <memory>\n#include <vector>\n#include <thread>\n#include \"../../forward"
  },
  {
    "path": "FlashMQTests/plugins/test_plugin.pro",
    "chars": 195,
    "preview": "QT -= gui\n\nCONFIG += c++17\n\nTARGET = test_plugin\nTEMPLATE = lib\n\nVERSION=0.0.1\n\nLIBS += -lcurl\n\nHEADERS += test_plugin.h"
  },
  {
    "path": "FlashMQTests/plugintests.cpp",
    "chars": 39430,
    "preview": "#include \"maintests.h\"\n#include \"testhelpers.h\"\n#include \"conffiletemp.h\"\n#include \"flashmqtestclient.h\"\n\n#include <sys/"
  },
  {
    "path": "FlashMQTests/retaintests.cpp",
    "chars": 25000,
    "preview": "#include \"maintests.h\"\n#include \"flashmqtestclient.h\"\n#include \"conffiletemp.h\"\n#include \"testhelpers.h\"\n\n#include \"util"
  },
  {
    "path": "FlashMQTests/run-make-from-ci.sh",
    "chars": 683,
    "preview": "#!/bin/bash\n\nextra_configs=()\ncompiler=\"g++\"\n\nwhile [ -n \"$*\" ]; do\n  flag=$1\n  value=$2\n\n  case \"$flag\" in\n    \"--compi"
  },
  {
    "path": "FlashMQTests/run-tests-from-ci.sh",
    "chars": 394,
    "preview": "#!/bin/bash\n\nSTDERR_LOG=$(mktemp)\n\ncd buildtests || exit 1\n\n# Using --abort-on-first-fail because the output can be hard"
  },
  {
    "path": "FlashMQTests/sharedsubscriptionstests.cpp",
    "chars": 10773,
    "preview": "#include \"maintests.h\"\n#include \"testhelpers.h\"\n#include \"flashmqtestclient.h\"\n#include \"flashmqtempdir.h\"\n#include \"con"
  },
  {
    "path": "FlashMQTests/subscriptionidtests.cpp",
    "chars": 10946,
    "preview": "#include \"maintests.h\"\n#include \"flashmqtestclient.h\"\n#include \"testhelpers.h\"\n\nvoid MainTests::testSubscriptionIdOnline"
  },
  {
    "path": "FlashMQTests/testhelpers.cpp",
    "chars": 1449,
    "preview": "#include \"testhelpers.h\"\n\n#include <iostream>\n#include <cstring>\n\nint assert_count;\nint assert_fail_count;\nbool asserts_"
  },
  {
    "path": "FlashMQTests/testhelpers.h",
    "chars": 2759,
    "preview": "#ifndef TESTHELPERS_H\n#define TESTHELPERS_H\n\n#include <sstream>\n\n#define RED \"\\033[01;31m\"\n#define GREEN \"\\033[01;32m\"\n#"
  },
  {
    "path": "FlashMQTests/testinitializer.cpp",
    "chars": 414,
    "preview": "#include \"testinitializer.h\"\n\nTestInitializer::TestInitializer(MainTests *tests) :\n    tests(tests)\n{\n\n}\n\nvoid TestIniti"
  },
  {
    "path": "FlashMQTests/testinitializer.h",
    "chars": 554,
    "preview": "#ifndef TESTINITIALIZER_H\n#define TESTINITIALIZER_H\n\n#include \"maintests.h\"\n\n/**\n * @brief Simple RAII way to make sure "
  },
  {
    "path": "FlashMQTests/tst_maintests.cpp",
    "chars": 116588,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "FlashMQTests/utiltests.cpp",
    "chars": 9764,
    "preview": "#include <random>\n\n#include \"maintests.h\"\n#include \"testhelpers.h\"\n\n#include \"utils.h\"\n#include \"exceptions.h\"\n#include "
  },
  {
    "path": "FlashMQTests/websockettests.cpp",
    "chars": 27307,
    "preview": "#include \"maintests.h\"\n#include \"testhelpers.h\"\n\n#include <fstream>\n#include <sys/types.h>\n#include <sys/socket.h>\n#incl"
  },
  {
    "path": "FlashMQTests/willtests.cpp",
    "chars": 14921,
    "preview": "#include <stdlib.h>\n\n#include \"maintests.h\"\n#include \"testhelpers.h\"\n#include \"flashmqtestclient.h\"\n#include \"conffilete"
  },
  {
    "path": "LICENSE",
    "chars": 10248,
    "preview": "This Open Software License (the “License”) applies to any original work of\nauthorship (the “Original Work”) whose owner "
  },
  {
    "path": "README.md",
    "chars": 2670,
    "preview": "# FlashMQ\n\n![building](https://github.com/halfgaar/FlashMQ/actions/workflows/building.yml/badge.svg)\n![testing](https://"
  },
  {
    "path": "acksender.cpp",
    "chars": 1028,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "acksender.h",
    "chars": 890,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "acltree.cpp",
    "chars": 9160,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "acltree.h",
    "chars": 2929,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "backgroundworker.cpp",
    "chars": 2898,
    "preview": "#include <string.h>\n#include <unistd.h>\n#include <poll.h>\n\n#include \"backgroundworker.h\"\n\n#include \"logger.h\"\n\n\nvoid Bac"
  },
  {
    "path": "backgroundworker.h",
    "chars": 621,
    "preview": "#ifndef BACKGROUNDWORKER_H\n#define BACKGROUNDWORKER_H\n\n\n#include <thread>\n#include <functional>\n#include <list>\n#include"
  },
  {
    "path": "bindaddr.cpp",
    "chars": 4624,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "bindaddr.h",
    "chars": 1631,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "bridgeconfig.cpp",
    "chars": 13245,
    "preview": "#include \"bridgeconfig.h\"\n\n#include <sstream>\n#include <openssl/ssl.h>\n#include <openssl/err.h>\n#include <cassert>\n\n#inc"
  },
  {
    "path": "bridgeconfig.h",
    "chars": 4481,
    "preview": "#ifndef BRIDGECONFIG_H\n#define BRIDGECONFIG_H\n\n#include <memory>\n#include <string>\n#include <vector>\n#include <optional>"
  },
  {
    "path": "bridgeinfodb.cpp",
    "chars": 2375,
    "preview": "#include \"bridgeinfodb.h\"\n\nusing std::unordered_map;\n\nBridgeInfoForSerializing::BridgeInfoForSerializing(const BridgeCon"
  },
  {
    "path": "bridgeinfodb.h",
    "chars": 1041,
    "preview": "#ifndef BRIDGEINFODB_H\n#define BRIDGEINFODB_H\n\n#include <list>\n\n#include \"persistencefile.h\"\n#include \"bridgeconfig.h\"\n\n"
  },
  {
    "path": "build.sh",
    "chars": 2641,
    "preview": "#!/bin/bash\n\nthisfile=$(readlink --canonicalize \"$0\")\nthisdir=$(dirname \"$thisfile\")\nscript_name=$(basename \"$0\")\n\nDEFAU"
  },
  {
    "path": "checkedsharedptr.h",
    "chars": 917,
    "preview": "#ifndef CHECKEDSHAREDPTR_H\n#define CHECKEDSHAREDPTR_H\n\n#include <memory>\n#include <cassert>\n#include <stdexcept>\n\ntempla"
  },
  {
    "path": "checkedweakptr.h",
    "chars": 859,
    "preview": "#ifndef CHECKEDWEAKPTR_H\n#define CHECKEDWEAKPTR_H\n\n#include <memory>\n\nclass __attribute__((visibility(\"default\"))) Point"
  },
  {
    "path": "cirbuf.cpp",
    "chars": 6971,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "cirbuf.h",
    "chars": 2462,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "client.cpp",
    "chars": 39502,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "client.h",
    "chars": 11979,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "clientacceptqueue.cpp",
    "chars": 1672,
    "preview": "#include <unistd.h>\n#include \"utils.h\"\n#include \"logger.h\"\n#include \"clientacceptqueue.h\"\n\n\n\nvoid ClientAcceptQueue::wak"
  },
  {
    "path": "clientacceptqueue.h",
    "chars": 693,
    "preview": "#ifndef CLIENTACCEPTQUEUE_H\n#define CLIENTACCEPTQUEUE_H\n\n#include <sys/eventfd.h>\n\n#include <vector>\n\n#include \"mutexown"
  },
  {
    "path": "configfileparser.cpp",
    "chars": 59752,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "configfileparser.h",
    "chars": 3604,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "debian/conffiles",
    "chars": 26,
    "preview": "/etc/flashmq/flashmq.conf\n"
  },
  {
    "path": "debian/flashmq.service",
    "chars": 313,
    "preview": "[Unit]\nDescription=FlashMQ MQTT server\nAfter=network.target\n\n[Service]\nType=notify\nUser=root\nGroup=root\nLimitNOFILE=infi"
  },
  {
    "path": "debian/postinst",
    "chars": 671,
    "preview": "#!/bin/bash -e\n\nFRESH_INSTALL=true\n\nif [[ \"$1\" == \"configure\" && \"$2\" != \"\" ]]; then\n  FRESH_INSTALL=false\nfi\n\nif \"$FRES"
  },
  {
    "path": "debian/postrm",
    "chars": 153,
    "preview": "#!/bin/bash -e\n\nif [[ \"$1\" != \"upgrade\" ]]; then\n  echo \"Disabling FlashMQ systemd service\"\n  systemctl disable flashmq."
  },
  {
    "path": "debian/preinst",
    "chars": 321,
    "preview": "#!/bin/bash -e\n\nif [[ ! -f /etc/lsb-release && ! -f /etc/debian_version ]]; then\n  echo \"This is not a Debian or Ubuntu "
  },
  {
    "path": "debian/prerm",
    "chars": 269,
    "preview": "#!/bin/bash -e\n\necho \"Stopping FlashMQ systemd service\"\n\nif systemctl is-active --quiet flashmq.service; then\n  systemct"
  },
  {
    "path": "derivablecounter.cpp",
    "chars": 1274,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "derivablecounter.h",
    "chars": 861,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "dnsresolver.cpp",
    "chars": 2791,
    "preview": "#include \"dnsresolver.h\"\n\n#include <unistd.h>\n#include <cstring>\n#include <signal.h>\n#include <stdexcept>\n#include <thre"
  },
  {
    "path": "dnsresolver.h",
    "chars": 1231,
    "preview": "#ifndef DNSRESOLVER_H\n#define DNSRESOLVER_H\n\n#include <netdb.h>\n#include <arpa/inet.h>\n#include <string>\n#include <list>"
  },
  {
    "path": "driftcounter.cpp",
    "chars": 1498,
    "preview": "#include \"driftcounter.h\"\n\n#include <algorithm>\n#include <numeric>\n\n#include \"settings.h\"\n\nDriftCounter::DriftCounter()\n"
  },
  {
    "path": "driftcounter.h",
    "chars": 835,
    "preview": "#ifndef DRIFTCOUNTER_H\n#define DRIFTCOUNTER_H\n\n#include <chrono>\n#include <array>\n#include <optional>\n\n/**\n * @brief The"
  },
  {
    "path": "enums.h",
    "chars": 871,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "evpencodectxmanager.cpp",
    "chars": 545,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "evpencodectxmanager.h",
    "chars": 527,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "examples/plugin_libcurl/CMakeLists.txt",
    "chars": 680,
    "preview": "cmake_minimum_required(VERSION 3.5)\ncmake_policy(SET CMP0048 NEW)\n\nproject(plugin_libcurl VERSION 1.0.0 LANGUAGES CXX)\n\n"
  },
  {
    "path": "examples/plugin_libcurl/LICENSE",
    "chars": 1250,
    "preview": "The FlashMQ example plugin 'plugin_libcurl' is free and unencumbered\nsoftware released into the public domain.\n\nAnyone i"
  },
  {
    "path": "examples/plugin_libcurl/README.md",
    "chars": 1650,
    "preview": "# Async auth using libcurl\n\nThis example plugin demonstrates the use of the async authentication interface in the `flash"
  },
  {
    "path": "examples/plugin_libcurl/build.sh",
    "chars": 495,
    "preview": "#!/bin/bash\n\nthisfile=$(readlink --canonicalize \"$0\")\nthisdir=$(dirname \"$thisfile\")\n\nBUILD_TYPE=\"Release\"\nif [[ \"$1\" =="
  },
  {
    "path": "examples/plugin_libcurl/src/authenticatingclient.cpp",
    "chars": 1943,
    "preview": "/*\nThis file is part of FlashMQ example plugin 'plugin_libcurl'\nand is free and unencumbered software released into the "
  },
  {
    "path": "examples/plugin_libcurl/src/authenticatingclient.h",
    "chars": 1906,
    "preview": "/*\nThis file is part of FlashMQ example plugin 'plugin_libcurl'\nand is free and unencumbered software released into the "
  },
  {
    "path": "examples/plugin_libcurl/src/curl_functions.cpp",
    "chars": 5175,
    "preview": "/*\nThis file is part of FlashMQ example plugin 'plugin_libcurl'\nand is free and unencumbered software released into the "
  },
  {
    "path": "examples/plugin_libcurl/src/curl_functions.h",
    "chars": 1843,
    "preview": "/*\nThis file is part of FlashMQ example plugin 'plugin_libcurl'\nand is free and unencumbered software released into the "
  },
  {
    "path": "examples/plugin_libcurl/src/plugin_libcurl.cpp",
    "chars": 6552,
    "preview": "/*\nThis file is part of FlashMQ example plugin 'plugin_libcurl'\nand is free and unencumbered software released into the "
  },
  {
    "path": "examples/plugin_libcurl/src/pluginstate.cpp",
    "chars": 2729,
    "preview": "/*\nThis file is part of FlashMQ example plugin 'plugin_libcurl'\nand is free and unencumbered software released into the "
  },
  {
    "path": "examples/plugin_libcurl/src/pluginstate.h",
    "chars": 2012,
    "preview": "/*\nThis file is part of FlashMQ example plugin 'plugin_libcurl'\nand is free and unencumbered software released into the "
  },
  {
    "path": "examples/plugin_libcurl/vendor/flashmq_plugin.h",
    "chars": 14520,
    "preview": "/*\n * This file is part of FlashMQ (https://www.flashmq.org). It defines the\n * plugin interface.\n *\n * - flashmq_plugin"
  },
  {
    "path": "examples/plugin_libcurl/vendor/flashmq_public.h",
    "chars": 14071,
    "preview": "/*\n * This file is part of FlashMQ (https://www.flashmq.org). It describes\n * public FlashMQ functions available to plug"
  },
  {
    "path": "exceptions.cpp",
    "chars": 290,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "exceptions.h",
    "chars": 2501,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "fdmanaged.cpp",
    "chars": 262,
    "preview": "#include \"fdmanaged.h\"\n\n#include <unistd.h>\n\nFdManaged::FdManaged(int fd) :\n    fd(fd)\n{\n\n}\n\nFdManaged::~FdManaged()\n{\n "
  },
  {
    "path": "fdmanaged.h",
    "chars": 307,
    "preview": "#ifndef FDMANAGED_H\n#define FDMANAGED_H\n\n\nclass FdManaged\n{\n    int fd = -1;\n\npublic:\n    FdManaged() = default;\n    FdM"
  },
  {
    "path": "flags.h",
    "chars": 801,
    "preview": "#ifndef FLAGS_H\n#define FLAGS_H\n\n#include <stdint.h>\n#include <limits>\n\ntemplate<typename FlagType>\nclass Flags\n{\n    ui"
  },
  {
    "path": "flashmq.conf",
    "chars": 124,
    "preview": "# https://www.flashmq.org/documentation/config-file/\n\nlog_file    /var/log/flashmq/flashmq.log\nstorage_dir /var/lib/flas"
  },
  {
    "path": "flashmq_plugin.cpp",
    "chars": 10506,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "flashmq_plugin.h",
    "chars": 14520,
    "preview": "/*\n * This file is part of FlashMQ (https://www.flashmq.org). It defines the\n * plugin interface.\n *\n * - flashmq_plugin"
  },
  {
    "path": "flashmq_plugin_deprecated.h",
    "chars": 893,
    "preview": "#ifndef FLASHMQ_PLUGIN_DEPRECATED_H\n#define FLASHMQ_PLUGIN_DEPRECATED_H\n\n#include <string>\n\n#include \"flashmq_public.h\"\n"
  },
  {
    "path": "flashmq_public.h",
    "chars": 14071,
    "preview": "/*\n * This file is part of FlashMQ (https://www.flashmq.org). It describes\n * public FlashMQ functions available to plug"
  },
  {
    "path": "flashmqtestclient.cpp",
    "chars": 12998,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "flashmqtestclient.h",
    "chars": 2826,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "fmqmain.cpp",
    "chars": 3072,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "fmqmain.h",
    "chars": 101,
    "preview": "void signal_handler(int signal);\nint register_signal_handers();\nint fmqmain(int argc, char *argv[]);\n"
  },
  {
    "path": "fmqsockaddr.cpp",
    "chars": 3442,
    "preview": "#include \"fmqsockaddr.h\"\n\n#include <cstring>\n#include <stdexcept>\n#include \"utils.h\"\n\nFMQSockaddr::FMQSockaddr(const soc"
  },
  {
    "path": "fmqsockaddr.h",
    "chars": 855,
    "preview": "#ifndef FMQSOCKADDR_H\n#define FMQSOCKADDR_H\n\n#include <string>\n#include <arpa/inet.h>\n#include <sys/un.h>\n#include <vect"
  },
  {
    "path": "fmqssl.cpp",
    "chars": 784,
    "preview": "#include <openssl/ssl.h>\n#include \"fmqssl.h\"\n\nFmqSsl::~FmqSsl()\n{\n    if (!d) return;\n\n    /*\n     * We write the shutdo"
  },
  {
    "path": "fmqssl.h",
    "chars": 497,
    "preview": "#ifndef FMQSSL_H\n#define FMQSSL_H\n\n#include <memory>\n#include <openssl/ssl.h>\n\n#include \"sslctxmanager.h\"\n\nclass FmqSsl\n"
  },
  {
    "path": "forward_declarations.h",
    "chars": 548,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "fuzz-helper.sh",
    "chars": 1610,
    "preview": "#!/bin/bash\n#\n# Quick 'n dirty Script to build and run FlashMQ with American Fuzzy Lop.\n\nthisfile=$(readlink --canonical"
  },
  {
    "path": "globals.cpp",
    "chars": 1017,
    "preview": "#include \"globals.h\"\n#include \"threadglobals.h\"\n\nGlobals globals;\n\n/**\n * Normally there's 'ThreadGlobals::getThreadData"
  },
  {
    "path": "globals.h",
    "chars": 1274,
    "preview": "#ifndef GLOBALS_H\n#define GLOBALS_H\n\n#include <memory>\n#include <pthread.h>\n\n#include \"subscriptionstore.h\"\n#include \"gl"
  },
  {
    "path": "globalstats.cpp",
    "chars": 686,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "globalstats.h",
    "chars": 746,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "globber.cpp",
    "chars": 596,
    "preview": "#include \"globber.h\"\n\n#include <cstring>\n#include <stdexcept>\n\nGlobT::GlobT()\n{\n\n}\n\nGlobT::~GlobT()\n{\n    globfree(&resu"
  },
  {
    "path": "globber.h",
    "chars": 310,
    "preview": "#ifndef GLOBBER_H\n#define GLOBBER_H\n\n#include <glob.h>\n#include <string>\n#include <vector>\n\nclass GlobT\n{\n    friend cla"
  },
  {
    "path": "haproxy.cpp",
    "chars": 5148,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "haproxy.h",
    "chars": 2377,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "http.cpp",
    "chars": 6151,
    "preview": "#include \"http.h\"\n\n#include <sstream>\n\n#include <openssl/evp.h>\n#include <openssl/ssl.h>\n#include <openssl/err.h>\n\n#incl"
  },
  {
    "path": "http.h",
    "chars": 1220,
    "preview": "#ifndef HTTP_H\n#define HTTP_H\n\n#include <string>\n\n#include \"cirbuf.h\"\n#include \"types.h\"\n#include \"exceptions.h\"\n\nclass "
  },
  {
    "path": "iowrapper.cpp",
    "chars": 40416,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "iowrapper.h",
    "chars": 4805,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "listener.cpp",
    "chars": 7611,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "listener.h",
    "chars": 2266,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "lockedsharedptr.h",
    "chars": 577,
    "preview": "#ifndef LOCKEDSHAREDPTR_H\n#define LOCKEDSHAREDPTR_H\n\n#include <mutex>\n#include <memory>\n\ntemplate<typename T>\nclass Lock"
  },
  {
    "path": "lockedweakptr.cpp",
    "chars": 28,
    "preview": "#include \"lockedweakptr.h\"\n\n"
  },
  {
    "path": "lockedweakptr.h",
    "chars": 688,
    "preview": "#ifndef LOCKEDWEAKPTR_H\n#define LOCKEDWEAKPTR_H\n\n#include <mutex>\n#include <memory>\n\ntemplate<typename T>\nclass LockedWe"
  },
  {
    "path": "logger.cpp",
    "chars": 9304,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "logger.h",
    "chars": 2446,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "main.cpp",
    "chars": 92,
    "preview": "#include \"fmqmain.h\"\n\nint main(int argc, char *argv[])\n{\n    return fmqmain(argc, argv);\n}\n\n"
  },
  {
    "path": "mainapp.cpp",
    "chars": 43440,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "mainapp.h",
    "chars": 3677,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "man/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "man/Makefile",
    "chars": 427,
    "preview": "GROFF_TARGETS := flashmq.1 flashmq.conf.5\nHTML5_TARGETS := flashmq.1.html flashmq.conf.5.html\n\nall: $(GROFF_TARGETS) $(H"
  },
  {
    "path": "man/README.md",
    "chars": 188,
    "preview": "To render a man page:\n\n```\nman -l flashmq.1\nman -l flashmq.conf.5\n```\n\nTo enable color on Debian derivates:\n\n```\nenv GRO"
  },
  {
    "path": "man/docbook5-refentry-xslt/docbook5-refentry-to-html5.xsl",
    "chars": 19985,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xsl:stylesheet version=\"1.0\"\n  xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n"
  },
  {
    "path": "man/docbook5-refentry-xslt/docbook5-refentry-to-manpage.xsl",
    "chars": 22872,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xsl:stylesheet version=\"1.0\"\n  xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n"
  },
  {
    "path": "man/flashmq-docbook5-refentry-to-html5.xsl",
    "chars": 10695,
    "preview": "<xsl:stylesheet version=\"1.0\"\n  xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n  xmlns:exsl=\"http://exslt.org/common\"\n"
  },
  {
    "path": "man/flashmq-docbook5-refentry-to-manpage.xsl",
    "chars": 4488,
    "preview": "<xsl:stylesheet version=\"1.0\"\n  xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n  xmlns:dbk=\"http://docbook.org/ns/docb"
  },
  {
    "path": "man/flashmq.1",
    "chars": 3556,
    "preview": ".if \\n(.g .ds T< \\\\FC\n.if \\n(.g .ds T> \\\\F[\\n[.fam]]\n.color\n.de URL\n\\\\$2 \\(la\\\\$1\\(ra\\\\$3\n..\n.if \\n(.g .mso www.tmac\n.TH"
  },
  {
    "path": "man/flashmq.1.dbk5",
    "chars": 6871,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<refentry\n    xml:id=\"flashmq.1\"\n    xml:lang=\"en\"\n    xmlns=\"http://docbook.org/"
  },
  {
    "path": "man/flashmq.1.html",
    "chars": 17686,
    "preview": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" data-color-scheme=\"light\" data-fallback-color-scheme=\"light\" "
  },
  {
    "path": "man/flashmq.conf.5",
    "chars": 56035,
    "preview": ".if \\n(.g .ds T< \\\\FC\n.if \\n(.g .ds T> \\\\F[\\n[.fam]]\n.color\n.de URL\n\\\\$2 \\(la\\\\$1\\(ra\\\\$3\n..\n.if \\n(.g .mso www.tmac\n.TH"
  },
  {
    "path": "man/flashmq.conf.5.dbk5",
    "chars": 87711,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<refentry\n    xml:id=\"flashmq.conf.5\"\n    xml:lang=\"en\"\n    xmlns=\"http://docbook"
  },
  {
    "path": "man/flashmq.conf.5.html",
    "chars": 111472,
    "preview": "<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" data-color-scheme=\"light\" data-fallback-color-scheme=\"light\" "
  },
  {
    "path": "man/refentry.colophon.dbk5",
    "chars": 887,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<refsect1 xml:id=\"colophon\"\n  xml:lang=\"en\"\n  xmlns=\"http://docbook.org/ns/docboo"
  },
  {
    "path": "man/reference.dbk5",
    "chars": 270,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<reference\n    xml:lang=\"en\"\n    xmlns=\"http://docbook.org/ns/docbook\"\n    versio"
  },
  {
    "path": "mosquittoauthoptcompatwrap.cpp",
    "chars": 1527,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "mosquittoauthoptcompatwrap.h",
    "chars": 1710,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "mqtt5properties.cpp",
    "chars": 7050,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "mqtt5properties.h",
    "chars": 2343,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "mqttpacket.cpp",
    "chars": 106741,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "mqttpacket.h",
    "chars": 7927,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "mutexowned.h",
    "chars": 2264,
    "preview": "#ifndef MUTEXOWNED_H\n#define MUTEXOWNED_H\n\n#include <mutex>\n\nclass __attribute__((visibility(\"default\"))) MutexOwnedObje"
  },
  {
    "path": "network.cpp",
    "chars": 4529,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "network.h",
    "chars": 826,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "nocopy.cpp",
    "chars": 21,
    "preview": "#include \"nocopy.h\"\n\n"
  },
  {
    "path": "nocopy.h",
    "chars": 635,
    "preview": "#ifndef NOCOPY_H\n#define NOCOPY_H\n\n#include <optional>\n\ntemplate<typename T>\nclass NoCopy\n{\n    std::optional<T> data;\n\n"
  },
  {
    "path": "oneinstancelock.cpp",
    "chars": 1456,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "oneinstancelock.h",
    "chars": 653,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "packetdatatypes.cpp",
    "chars": 892,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "packetdatatypes.h",
    "chars": 2726,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "persistencefile.cpp",
    "chars": 13232,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "persistencefile.h",
    "chars": 2619,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "persistencefunctions.cpp",
    "chars": 4133,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2025 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "persistencefunctions.h",
    "chars": 728,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2025 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "plugin.cpp",
    "chars": 37048,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "plugin.h",
    "chars": 15405,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "pluginloader.cpp",
    "chars": 3858,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "pluginloader.h",
    "chars": 1542,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "publishcopyfactory.cpp",
    "chars": 6950,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "publishcopyfactory.h",
    "chars": 2609,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "qospacketqueue.cpp",
    "chars": 5999,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "qospacketqueue.h",
    "chars": 3001,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "queuedtasks.cpp",
    "chars": 3032,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "queuedtasks.h",
    "chars": 1291,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "release.sh",
    "chars": 2423,
    "preview": "#!/bin/bash\n\nSEMVER_REGEXP=\"^([0-9]+)\\.([0-9]+)\\.([0-9]+)$\"\n\nSCRIPT_NAME=$(basename \"$0\")\nSCRIPT_DIR=\"$(dirname \"$(realp"
  },
  {
    "path": "retainedmessage.cpp",
    "chars": 1645,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "retainedmessage.h",
    "chars": 1185,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "retainedmessagesdb.cpp",
    "chars": 7525,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "retainedmessagesdb.h",
    "chars": 1765,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "rwlockguard.cpp",
    "chars": 2726,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "rwlockguard.h",
    "chars": 727,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "scopedsocket.cpp",
    "chars": 2192,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "scopedsocket.h",
    "chars": 1140,
    "preview": "/*\nThis file is part of FlashMQ (https://www.flashmq.org)\nCopyright (C) 2021-2023 Wiebe Cazemier\n\nFlashMQ is free softwa"
  },
  {
    "path": "sdnotify.cpp",
    "chars": 3226,
    "preview": "#include \"sdnotify.h\"\n\n/* SPDX-License-Identifier: MIT-0 */\n\n/* Implement the systemd notify protocol without external d"
  }
]

// ... and 35 more files (download for full content)

About this extraction

This page contains the full source code of the halfgaar/FlashMQ GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 235 files (1.7 MB), approximately 434.5k tokens, and a symbol index with 461 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!